From 72c582e7e50fba3dc6394c85718d05fe6fb49dca Mon Sep 17 00:00:00 2001 From: diondiondion Date: Mon, 17 Nov 2025 13:07:44 +0100 Subject: [PATCH 01/17] Fix superfluous border & spacing in domains list on the Moderation > Federation page (#36915) --- app/javascript/styles/mastodon/admin.scss | 4 ++-- app/javascript/styles_new/mastodon/admin.scss | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index c8a794c7ed..c3a14a4fbd 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -327,9 +327,9 @@ $content-width: 840px; font-weight: 700; color: $primary-text-color; text-transform: none; - padding-bottom: 0; + padding-top: 0; margin-bottom: 0; - border-bottom: 0; + border-top: 0; .comment { display: block; diff --git a/app/javascript/styles_new/mastodon/admin.scss b/app/javascript/styles_new/mastodon/admin.scss index ee0cec1a1e..d0cd3c5137 100644 --- a/app/javascript/styles_new/mastodon/admin.scss +++ b/app/javascript/styles_new/mastodon/admin.scss @@ -319,9 +319,9 @@ $content-width: 840px; font-weight: 700; color: var(--color-text-primary); text-transform: none; - padding-bottom: 0; + padding-top: 0; margin-bottom: 0; - border-bottom: 0; + border-top: 0; .comment { display: block; From 04c566e2e91815923a02943ecaacd0972b3f60cf Mon Sep 17 00:00:00 2001 From: Shugo Maeda Date: Mon, 17 Nov 2025 22:34:20 +0900 Subject: [PATCH 02/17] Fix ArgumentError of tootctl upgrade storage-schema (#36914) --- lib/mastodon/cli/upgrade.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mastodon/cli/upgrade.rb b/lib/mastodon/cli/upgrade.rb index 2cb5105794..d5822cacc0 100644 --- a/lib/mastodon/cli/upgrade.rb +++ b/lib/mastodon/cli/upgrade.rb @@ -123,12 +123,12 @@ module Mastodon::CLI progress.log("Moving #{previous_path} to #{upgraded_path}") if options[:verbose] begin - move_previous_to_upgraded + move_previous_to_upgraded(previous_path, upgraded_path) rescue => e progress.log(pastel.red("Error processing #{previous_path}: #{e}")) success = false - remove_directory + remove_directory(upgraded_path) end end From 04bdfa1957efdaa90d1f4310e70330088f0f2152 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:12:38 +0100 Subject: [PATCH 03/17] chore(deps): update dependency @vitejs/plugin-react to v5.1.1 (#36841) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 64 +++++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/yarn.lock b/yarn.lock index fe0be031b2..3c816b4730 100644 --- a/yarn.lock +++ b/yarn.lock @@ -97,39 +97,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.28.0, @babel/core@npm:^7.28.4": - version: 7.28.4 - resolution: "@babel/core@npm:7.28.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, @babel/core@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/core@npm:7.28.5" dependencies: "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.3" + "@babel/generator": "npm:^7.28.5" "@babel/helper-compilation-targets": "npm:^7.27.2" "@babel/helper-module-transforms": "npm:^7.28.3" "@babel/helpers": "npm:^7.28.4" - "@babel/parser": "npm:^7.28.4" + "@babel/parser": "npm:^7.28.5" "@babel/template": "npm:^7.27.2" - "@babel/traverse": "npm:^7.28.4" - "@babel/types": "npm:^7.28.4" + "@babel/traverse": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" "@jridgewell/remapping": "npm:^2.3.5" 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/ef5a6c3c6bf40d3589b5593f8118cfe2602ce737412629fb6e26d595be2fcbaae0807b43027a5c42ec4fba5b895ff65891f2503b5918c8a3ea3542ab44d4c278 + checksum: 10c0/535f82238027621da6bdffbdbe896ebad3558b311d6f8abc680637a9859b96edbf929ab010757055381570b29cf66c4a295b5618318d27a4273c0e2033925e72 languageName: node linkType: hard -"@babel/generator@npm:^7.28.3": - version: 7.28.3 - resolution: "@babel/generator@npm:7.28.3" +"@babel/generator@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/generator@npm:7.28.5" dependencies: - "@babel/parser": "npm:^7.28.3" - "@babel/types": "npm:^7.28.2" + "@babel/parser": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" "@jridgewell/gen-mapping": "npm:^0.3.12" "@jridgewell/trace-mapping": "npm:^0.3.28" jsesc: "npm:^3.0.2" - checksum: 10c0/0ff58bcf04f8803dcc29479b547b43b9b0b828ec1ee0668e92d79f9e90f388c28589056637c5ff2fd7bcf8d153c990d29c448d449d852bf9d1bc64753ca462bc + checksum: 10c0/9f219fe1d5431b6919f1a5c60db8d5d34fe546c0d8f5a8511b32f847569234ffc8032beb9e7404649a143f54e15224ecb53a3d11b6bb85c3203e573d91fca752 languageName: node linkType: hard @@ -334,7 +334,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.25.4, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.3, @babel/parser@npm:^7.28.4": +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.25.4, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.5": version: 7.28.5 resolution: "@babel/parser@npm:7.28.5" dependencies: @@ -1194,22 +1194,22 @@ __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.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.4": - version: 7.28.4 - resolution: "@babel/traverse@npm:7.28.4" +"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.26.10, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/traverse@npm:7.28.5" dependencies: "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.3" + "@babel/generator": "npm:^7.28.5" "@babel/helper-globals": "npm:^7.28.0" - "@babel/parser": "npm:^7.28.4" + "@babel/parser": "npm:^7.28.5" "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.28.4" + "@babel/types": "npm:^7.28.5" debug: "npm:^4.3.1" - checksum: 10c0/ee678fdd49c9f54a32e07e8455242390d43ce44887cea6567b233fe13907b89240c377e7633478a32c6cf1be0e17c2f7f3b0c59f0666e39c5074cc47b968489c + checksum: 10c0/f6c4a595993ae2b73f2d4cd9c062f2e232174d293edd4abe1d715bd6281da8d99e47c65857e8d0917d9384c65972f4acdebc6749a7c40a8fcc38b3c7fb3e706f 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.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5, @babel/types@npm:^7.4.4": +"@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.28.4, @babel/types@npm:^7.28.5, @babel/types@npm:^7.4.4": version: 7.28.5 resolution: "@babel/types@npm:7.28.5" dependencies: @@ -3315,10 +3315,10 @@ __metadata: languageName: node linkType: hard -"@rolldown/pluginutils@npm:1.0.0-beta.43": - version: 1.0.0-beta.43 - resolution: "@rolldown/pluginutils@npm:1.0.0-beta.43" - checksum: 10c0/1c17a0b16c277a0fdbab080fd22ef91e37c1f0d710ecfdacb6a080068062eb14ff030d0e9d2ec2325a1d4246dba0c49625755c82c0090f6cbf98d16e80183e02 +"@rolldown/pluginutils@npm:1.0.0-beta.47": + version: 1.0.0-beta.47 + resolution: "@rolldown/pluginutils@npm:1.0.0-beta.47" + checksum: 10c0/eb0cfa7334d66f090c47eaac612174936b05f26e789352428cb6e03575b590f355de30d26b42576ea4e613d8887b587119d19b2e4b3a8909ceb232ca1cf746c8 languageName: node linkType: hard @@ -4813,18 +4813,18 @@ __metadata: linkType: hard "@vitejs/plugin-react@npm:^5.0.0": - version: 5.1.0 - resolution: "@vitejs/plugin-react@npm:5.1.0" + version: 5.1.1 + resolution: "@vitejs/plugin-react@npm:5.1.1" dependencies: - "@babel/core": "npm:^7.28.4" + "@babel/core": "npm:^7.28.5" "@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.43" + "@rolldown/pluginutils": "npm:1.0.0-beta.47" "@types/babel__core": "npm:^7.20.5" react-refresh: "npm:^0.18.0" peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/e192a12e2b854df109eafb1d06c0bc848e8e2b162c686aa6b999b1048658983e72674b2068ccc37562fcce44d32ad92b65f3a4e1897a0cb7859c2ee69cc63eac + checksum: 10c0/e590efaea1eabfbb1beb6e8c9fac0742fd299808e3368e63b2825ce24740adb8a28fcb2668b14b7ca1bdb42890cfefe94d02dd358dcbbf8a27ddf377b9a82abf languageName: node linkType: hard From 59fdff5dc5f0a4251909f73d6681b3e579fff857 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 17 Nov 2025 09:13:52 -0500 Subject: [PATCH 04/17] Add spec for translation attempt with ineligible target language (#36813) --- .../requests/api/v1/statuses/translations_spec.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spec/requests/api/v1/statuses/translations_spec.rb b/spec/requests/api/v1/statuses/translations_spec.rb index e95ec1b101..eb17d9f6a7 100644 --- a/spec/requests/api/v1/statuses/translations_spec.rb +++ b/spec/requests/api/v1/statuses/translations_spec.rb @@ -50,6 +50,21 @@ RSpec.describe 'API V1 Statuses Translations' do end end + context 'with a public status marked with the same language as the current locale when translation backend cannot do same-language translation' do + let(:status) { Fabricate(:status, account: user.account, text: 'Esto está en español pero está marcado como inglés.', language: 'en') } + + it 'returns http forbidden with error message' do + subject + + expect(response) + .to have_http_status(403) + expect(response.media_type) + .to eq('application/json') + expect(response.parsed_body) + .to include(error: /not allowed/) + end + end + context 'with a private status' do let(:status) { Fabricate(:status, visibility: :private, account: user.account, text: 'Hola', language: 'es') } From 53703202fbb327f8ab9a3c69a5f88bb95e0a283e Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 17 Nov 2025 09:51:05 -0500 Subject: [PATCH 05/17] Add `IpBlock#to_cidr` method to build string (#35773) --- app/models/ip_block.rb | 3 ++- .../rest/admin/ip_block_serializer.rb | 2 +- app/views/admin/ip_blocks/_ip_block.html.haml | 2 +- lib/mastodon/cli/ip_blocks.rb | 4 ++-- spec/lib/mastodon/cli/ip_blocks_spec.rb | 10 +++++----- spec/models/ip_block_spec.rb | 16 ++++++++++++++++ spec/requests/api/v1/admin/ip_blocks_spec.rb | 4 ++-- 7 files changed, 29 insertions(+), 12 deletions(-) diff --git a/app/models/ip_block.rb b/app/models/ip_block.rb index 4c95ac38de..b3b678a6a1 100644 --- a/app/models/ip_block.rb +++ b/app/models/ip_block.rb @@ -31,9 +31,10 @@ class IpBlock < ApplicationRecord after_commit :reset_cache - def to_log_human_identifier + def to_cidr "#{ip}/#{ip.prefix}" end + alias to_log_human_identifier to_cidr class << self def blocked?(remote_ip) diff --git a/app/serializers/rest/admin/ip_block_serializer.rb b/app/serializers/rest/admin/ip_block_serializer.rb index 6a38f8b566..4b782da53a 100644 --- a/app/serializers/rest/admin/ip_block_serializer.rb +++ b/app/serializers/rest/admin/ip_block_serializer.rb @@ -9,6 +9,6 @@ class REST::Admin::IpBlockSerializer < ActiveModel::Serializer end def ip - "#{object.ip}/#{object.ip.prefix}" + object.to_cidr end end diff --git a/app/views/admin/ip_blocks/_ip_block.html.haml b/app/views/admin/ip_blocks/_ip_block.html.haml index 3dc6f8f8e5..0a1b864c6e 100644 --- a/app/views/admin/ip_blocks/_ip_block.html.haml +++ b/app/views/admin/ip_blocks/_ip_block.html.haml @@ -3,7 +3,7 @@ = f.check_box :ip_block_ids, { multiple: true, include_hidden: false }, ip_block.id .batch-table__row__content.pending-account .pending-account__header - %samp= link_to "#{ip_block.ip}/#{ip_block.ip.prefix}", admin_accounts_path(ip: "#{ip_block.ip}/#{ip_block.ip.prefix}") + %samp= link_to ip_block.to_cidr, admin_accounts_path(ip: ip_block.to_cidr) - if ip_block.comment.present? · = ip_block.comment diff --git a/lib/mastodon/cli/ip_blocks.rb b/lib/mastodon/cli/ip_blocks.rb index ea6b508c6f..ba31e6b5ce 100644 --- a/lib/mastodon/cli/ip_blocks.rb +++ b/lib/mastodon/cli/ip_blocks.rb @@ -107,9 +107,9 @@ module Mastodon::CLI IpBlock.severity_no_access.find_each do |ip_block| case options[:format] when 'nginx' - say "deny #{ip_block.ip}/#{ip_block.ip.prefix};" + say "deny #{ip_block.to_cidr};" else - say "#{ip_block.ip}/#{ip_block.ip.prefix}" + say ip_block.to_cidr end end end diff --git a/spec/lib/mastodon/cli/ip_blocks_spec.rb b/spec/lib/mastodon/cli/ip_blocks_spec.rb index d531b8b7a8..1f903ac065 100644 --- a/spec/lib/mastodon/cli/ip_blocks_spec.rb +++ b/spec/lib/mastodon/cli/ip_blocks_spec.rb @@ -251,12 +251,12 @@ RSpec.describe Mastodon::CLI::IpBlocks do it 'exports blocked IPs with "no_access" severity in plain format' do expect { subject } - .to output_results("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}") + .to output_results("#{first_ip_range_block.to_cidr}\n#{second_ip_range_block.to_cidr}") end it 'does not export blocked IPs with different severities' do expect { subject } - .to_not output_results("#{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}") + .to_not output_results(third_ip_range_block.to_cidr) end end @@ -265,19 +265,19 @@ RSpec.describe Mastodon::CLI::IpBlocks do it 'exports blocked IPs with "no_access" severity in plain format' do expect { subject } - .to output_results("deny #{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};\ndeny #{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix};") + .to output_results("deny #{first_ip_range_block.to_cidr};\ndeny #{second_ip_range_block.to_cidr};") end it 'does not export blocked IPs with different severities' do expect { subject } - .to_not output_results("deny #{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};") + .to_not output_results("deny #{third_ip_range_block.to_cidr};") end end context 'when --format option is not provided' do it 'exports blocked IPs in plain format by default' do expect { subject } - .to output_results("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}") + .to output_results("#{first_ip_range_block.to_cidr}\n#{second_ip_range_block.to_cidr}") end end end diff --git a/spec/models/ip_block_spec.rb b/spec/models/ip_block_spec.rb index 18fb7ea136..a8b0809511 100644 --- a/spec/models/ip_block_spec.rb +++ b/spec/models/ip_block_spec.rb @@ -26,6 +26,22 @@ RSpec.describe IpBlock do end end + describe '#to_cidr' do + subject { Fabricate.build(:ip_block, ip:).to_cidr } + + context 'with an IP and a specified prefix' do + let(:ip) { '192.168.1.0/24' } + + it { is_expected.to eq('192.168.1.0/24') } + end + + context 'with an IP and a default prefix' do + let(:ip) { '192.168.1.0' } + + it { is_expected.to eq('192.168.1.0/32') } + end + end + describe '.blocked?' do context 'when the IP is blocked' do it 'returns true' do diff --git a/spec/requests/api/v1/admin/ip_blocks_spec.rb b/spec/requests/api/v1/admin/ip_blocks_spec.rb index 59ef8d2966..161781c8ed 100644 --- a/spec/requests/api/v1/admin/ip_blocks_spec.rb +++ b/spec/requests/api/v1/admin/ip_blocks_spec.rb @@ -97,7 +97,7 @@ RSpec.describe 'IP Blocks' do expect(response.parsed_body) .to include( - ip: eq("#{ip_block.ip}/#{ip_block.ip.prefix}"), + ip: eq(ip_block.to_cidr), severity: eq(ip_block.severity.to_s) ) end @@ -216,7 +216,7 @@ RSpec.describe 'IP Blocks' do expect(response.content_type) .to start_with('application/json') expect(response.parsed_body).to match(hash_including({ - ip: "#{ip_block.ip}/#{ip_block.ip.prefix}", + ip: ip_block.to_cidr, severity: 'sign_up_requires_approval', comment: 'Decreasing severity', })) From caffb0fd633913e5c4c67c0b804aff0966c4d89b Mon Sep 17 00:00:00 2001 From: Echo Date: Mon, 17 Nov 2025 16:34:18 +0100 Subject: [PATCH 06/17] Emoji: Fix path resolution for emoji worker (#36897) --- .../mastodon/features/emoji/index.ts | 32 ++++++-- .../mastodon/features/emoji/loader.ts | 82 ++++++++++--------- .../mastodon/features/emoji/render.test.ts | 2 +- .../mastodon/features/emoji/worker.ts | 25 ++++-- app/javascript/mastodon/main.tsx | 2 +- 5 files changed, 83 insertions(+), 60 deletions(-) diff --git a/app/javascript/mastodon/features/emoji/index.ts b/app/javascript/mastodon/features/emoji/index.ts index 11ee26aac2..4b0f79133c 100644 --- a/app/javascript/mastodon/features/emoji/index.ts +++ b/app/javascript/mastodon/features/emoji/index.ts @@ -1,6 +1,7 @@ import { initialState } from '@/mastodon/initial_state'; import { toSupportedLocale } from './locale'; +import type { LocaleOrCustom } from './types'; import { emojiLogger } from './utils'; // eslint-disable-next-line import/default -- Importing via worker loader. import EmojiWorker from './worker?worker&inline'; @@ -24,19 +25,17 @@ export 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(); }, WORKER_TIMEOUT); - thisWorker.addEventListener('message', (event: MessageEvent) => { + worker.addEventListener('message', (event: MessageEvent) => { const { data: message } = event; if (message === 'ready') { log('worker ready, loading data'); clearTimeout(timeoutId); - thisWorker.postMessage('custom'); + messageWorker('custom'); void loadEmojiLocale(userLocale); // Load English locale as well, because people are still used to // using it from before we supported other locales. @@ -55,20 +54,35 @@ export function initializeEmoji() { async function fallbackLoad() { log('falling back to main thread for loading'); const { importCustomEmojiData } = await import('./loader'); - await importCustomEmojiData(); + const emojis = await importCustomEmojiData(); + if (emojis) { + log('loaded %d custom emojis', emojis.length); + } await loadEmojiLocale(userLocale); if (userLocale !== 'en') { await loadEmojiLocale('en'); } } -export async function loadEmojiLocale(localeString: string) { +async function loadEmojiLocale(localeString: string) { const locale = toSupportedLocale(localeString); + const { importEmojiData, localeToPath } = await import('./loader'); if (worker) { - worker.postMessage(locale); + const path = await localeToPath(locale); + log('asking worker to load locale %s from %s', locale, path); + messageWorker(locale, path); } else { - const { importEmojiData } = await import('./loader'); - await importEmojiData(locale); + const emojis = await importEmojiData(locale); + if (emojis) { + log('loaded %d emojis to locale %s', emojis.length, locale); + } } } + +function messageWorker(locale: LocaleOrCustom, path?: string) { + if (!worker) { + return; + } + worker.postMessage({ locale, path }); +} diff --git a/app/javascript/mastodon/features/emoji/loader.ts b/app/javascript/mastodon/features/emoji/loader.ts index 330c5e6a2a..7251559d6b 100644 --- a/app/javascript/mastodon/features/emoji/loader.ts +++ b/app/javascript/mastodon/features/emoji/loader.ts @@ -8,44 +8,64 @@ import { putLatestEtag, } from './database'; import { toSupportedLocale, toSupportedLocaleOrCustom } from './locale'; -import type { CustomEmojiData, LocaleOrCustom } from './types'; -import { emojiLogger } from './utils'; +import type { CustomEmojiData } from './types'; -const log = emojiLogger('loader'); - -export async function importEmojiData(localeString: string) { +export async function importEmojiData(localeString: string, path?: string) { const locale = toSupportedLocale(localeString); - const emojis = await fetchAndCheckEtag(locale); + + // Validate the provided path. + if (path && !/^[/a-z]*\/packs\/assets\/compact-\w+\.json$/.test(path)) { + throw new Error('Invalid path for emoji data'); + } else { + // Otherwise get the path if not provided. + path ??= await localeToPath(locale); + } + + const emojis = await fetchAndCheckEtag(locale, path); if (!emojis) { return; } const flattenedEmojis: FlatCompactEmoji[] = flattenEmojiData(emojis); - log('loaded %d for %s locale', flattenedEmojis.length, locale); await putEmojiData(flattenedEmojis, locale); + return flattenedEmojis; } export async function importCustomEmojiData() { - const emojis = await fetchAndCheckEtag('custom'); + const emojis = await fetchAndCheckEtag( + 'custom', + '/api/v1/custom_emojis', + ); if (!emojis) { return; } - log('loaded %d custom emojis', emojis.length); await putCustomEmojiData(emojis); + return emojis; } -async function fetchAndCheckEtag( - localeOrCustom: LocaleOrCustom, +const modules = import.meta.glob( + '../../../../../node_modules/emojibase-data/**/compact.json', + { + query: '?url', + import: 'default', + }, +); + +export function localeToPath(locale: Locale) { + const key = `../../../../../node_modules/emojibase-data/${locale}/compact.json`; + if (!modules[key] || typeof modules[key] !== 'function') { + throw new Error(`Unsupported locale: ${locale}`); + } + return modules[key](); +} + +export async function fetchAndCheckEtag( + localeString: string, + path: string, ): Promise { - const locale = toSupportedLocaleOrCustom(localeOrCustom); + const locale = toSupportedLocaleOrCustom(localeString); // Use location.origin as this script may be loaded from a CDN domain. - const url = new URL(location.origin); - if (locale === 'custom') { - url.pathname = '/api/v1/custom_emojis'; - } else { - const modulePath = await localeToPath(locale); - url.pathname = modulePath; - } + const url = new URL(path, location.origin); const oldEtag = await loadLatestEtag(locale); const response = await fetch(url, { @@ -60,38 +80,20 @@ async function fetchAndCheckEtag( } if (!response.ok) { throw new Error( - `Failed to fetch emoji data for ${localeOrCustom}: ${response.statusText}`, + `Failed to fetch emoji data for ${locale}: ${response.statusText}`, ); } const data = (await response.json()) as ResultType; if (!Array.isArray(data)) { - throw new Error( - `Unexpected data format for ${localeOrCustom}: expected an array`, - ); + throw new Error(`Unexpected data format for ${locale}: expected an array`); } // Store the ETag for future requests const etag = response.headers.get('ETag'); if (etag) { - await putLatestEtag(etag, localeOrCustom); + await putLatestEtag(etag, localeString); } return data; } - -const modules = import.meta.glob( - '../../../../../node_modules/emojibase-data/**/compact.json', - { - query: '?url', - import: 'default', - }, -); - -function localeToPath(locale: Locale) { - const key = `../../../../../node_modules/emojibase-data/${locale}/compact.json`; - if (!modules[key] || typeof modules[key] !== 'function') { - throw new Error(`Unsupported locale: ${locale}`); - } - return modules[key](); -} diff --git a/app/javascript/mastodon/features/emoji/render.test.ts b/app/javascript/mastodon/features/emoji/render.test.ts index 05dbc388c4..3c96cbfb55 100644 --- a/app/javascript/mastodon/features/emoji/render.test.ts +++ b/app/javascript/mastodon/features/emoji/render.test.ts @@ -162,7 +162,7 @@ describe('loadEmojiDataToState', () => { const dbCall = vi .spyOn(db, 'loadEmojiByHexcode') .mockRejectedValue(new db.LocaleNotLoadedError('en')); - vi.spyOn(loader, 'importEmojiData').mockResolvedValueOnce(); + vi.spyOn(loader, 'importEmojiData').mockResolvedValueOnce(undefined); const consoleCall = vi .spyOn(console, 'warn') .mockImplementationOnce(() => null); diff --git a/app/javascript/mastodon/features/emoji/worker.ts b/app/javascript/mastodon/features/emoji/worker.ts index 6fb7d36e93..5360484d77 100644 --- a/app/javascript/mastodon/features/emoji/worker.ts +++ b/app/javascript/mastodon/features/emoji/worker.ts @@ -1,18 +1,25 @@ -import { importEmojiData, importCustomEmojiData } from './loader'; +import { importCustomEmojiData, importEmojiData } 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; - void loadData(locale); +function handleMessage(event: MessageEvent<{ locale: string; path?: string }>) { + const { + data: { locale, path }, + } = event; + void loadData(locale, path); } -async function loadData(locale: string) { - if (locale !== 'custom') { - await importEmojiData(locale); +async function loadData(locale: string, path?: string) { + let importCount: number | undefined; + if (locale === 'custom') { + importCount = (await importCustomEmojiData())?.length; + } else if (path) { + importCount = (await importEmojiData(locale, path))?.length; } else { - await importCustomEmojiData(); + throw new Error('Path is required for loading locale emoji data'); + } + if (importCount) { + self.postMessage(`loaded ${importCount} emojis into ${locale}`); } - self.postMessage(`loaded ${locale}`); } diff --git a/app/javascript/mastodon/main.tsx b/app/javascript/mastodon/main.tsx index f89baf66cd..249baf65fc 100644 --- a/app/javascript/mastodon/main.tsx +++ b/app/javascript/mastodon/main.tsx @@ -9,7 +9,6 @@ import { me, reduceMotion } from 'mastodon/initial_state'; import ready from 'mastodon/ready'; import { store } from 'mastodon/store'; -import { initializeEmoji } from './features/emoji'; import { isProduction, isDevelopment } from './utils/environment'; function main() { @@ -30,6 +29,7 @@ function main() { }); } + const { initializeEmoji } = await import('./features/emoji/index'); initializeEmoji(); const root = createRoot(mountNode); From b14f11392971777bc52d2a145b07f052664bc97f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 09:22:25 +0100 Subject: [PATCH 07/17] New Crowdin Translations (automated) (#36922) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/cs.json | 3 + app/javascript/mastodon/locales/cy.json | 3 + app/javascript/mastodon/locales/de.json | 2 +- app/javascript/mastodon/locales/la.json | 91 +++++++++++++++++++++++-- config/locales/de.yml | 2 +- config/locales/eu.yml | 12 ++++ config/locales/hu.yml | 2 + config/locales/simple_form.cs.yml | 2 + config/locales/simple_form.cy.yml | 2 + config/locales/simple_form.hu.yml | 3 + 10 files changed, 116 insertions(+), 6 deletions(-) diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index 1ff8b589db..4aa5ddccf9 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -903,6 +903,7 @@ "status.edited_x_times": "Upraveno {count, plural, one {{count}krát} few {{count}krát} many {{count}krát} other {{count}krát}}", "status.embed": "Získejte kód pro vložení", "status.favourite": "Oblíbit", + "status.favourites_count": "{count, plural, one {{counter} oblíbený} few {{counter} oblíbené} many {{counter} oblíbených} other {{counter} oblíbených}}", "status.filter": "Filtrovat tento příspěvek", "status.history.created": "Uživatel {name} vytvořil {date}", "status.history.edited": "Uživatel {name} upravil {date}", @@ -937,12 +938,14 @@ "status.quotes.empty": "Tento příspěvek zatím nikdo necitoval. Pokud tak někdo učiní, uvidíte to zde.", "status.quotes.local_other_disclaimer": "Citace zamítnuté autorem nebudou zobrazeny.", "status.quotes.remote_other_disclaimer": "Pouze citace z {domain} zde budou zaručeně ukázány. Citace zamítnuté autorem nebudou zobrazeny.", + "status.quotes_count": "{count, plural, one {{counter} citace} few {{counter} citace} many {{counter} citací} other {{counter} citací}}", "status.read_more": "Číst více", "status.reblog": "Boostnout", "status.reblog_or_quote": "Boostnout nebo citovat", "status.reblog_private": "Sdílejte znovu se svými sledujícími", "status.reblogged_by": "Uživatel {name} boostnul", "status.reblogs.empty": "Tento příspěvek ještě nikdo neboostnul. Pokud to někdo udělá, zobrazí se zde.", + "status.reblogs_count": "{count, plural, one {{counter} boost} few {{counter} boosty} many {{counter} boostů} other {{counter} boostů}}", "status.redraft": "Smazat a přepsat", "status.remove_bookmark": "Odstranit ze záložek", "status.remove_favourite": "Odebrat z oblíbených", diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json index 1af108d35f..9f3631a0fe 100644 --- a/app/javascript/mastodon/locales/cy.json +++ b/app/javascript/mastodon/locales/cy.json @@ -903,6 +903,7 @@ "status.edited_x_times": "Golygwyd {count, plural, one {{count} gwaith} other {{count} gwaith}}", "status.embed": "Cael y cod mewnblannu", "status.favourite": "Ffafrio", + "status.favourites_count": "{count, plural, one {{counter} ffefryn} other {{counter} ffefryn}}", "status.filter": "Hidlo'r postiad hwn", "status.history.created": "Crëwyd gan {name} {date}", "status.history.edited": "Golygwyd gan {name} {date}", @@ -937,12 +938,14 @@ "status.quotes.empty": "Does neb wedi dyfynnu'r postiad hwn eto. Pan fydd rhywun yn gwneud hynny, bydd yn ymddangos yma.", "status.quotes.local_other_disclaimer": "Bydd dyfyniadau wedi'u gwrthod gan yr awdur ddim yn cael eu dangos.", "status.quotes.remote_other_disclaimer": "Dim ond dyfyniadau o {domain} sy'n siŵr o gael eu dangos yma. Bydd dyfyniadau wedi'u gwrthod gan yr awdur ddim yn cael eu dangos.", + "status.quotes_count": "{count, plural, one {{counter} dyfyniad} other {{counter} dyfyniad}}", "status.read_more": "Darllen rhagor", "status.reblog": "Hybu", "status.reblog_or_quote": "Hybu neu ddyfynnu", "status.reblog_private": "Rhannwch eto gyda'ch dilynwyr", "status.reblogged_by": "Hybodd {name}", "status.reblogs.empty": "Does neb wedi hybio'r post yma eto. Pan y bydd rhywun yn gwneud, byddent yn ymddangos yma.", + "status.reblogs_count": "{count, plural, one {{counter} hwb} other {{counter} hwb}}", "status.redraft": "Dileu ac ail lunio", "status.remove_bookmark": "Tynnu nod tudalen", "status.remove_favourite": "Tynnu o'r ffefrynnau", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index abddfbb905..0babff64b7 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -242,7 +242,7 @@ "confirmations.follow_to_list.message": "Du musst {name} folgen, um das Profil zu einer Liste hinzufügen zu können.", "confirmations.follow_to_list.title": "Profil folgen?", "confirmations.logout.confirm": "Abmelden", - "confirmations.logout.message": "Möchtest du dich wirklich abmelden?", + "confirmations.logout.message": "Bist du sicher, dass du dich abmelden möchtest?", "confirmations.logout.title": "Abmelden?", "confirmations.missing_alt_text.confirm": "Bildbeschreibung hinzufügen", "confirmations.missing_alt_text.message": "Dein Beitrag enthält Medien ohne Bildbeschreibung. Mit ALT-Texten erreichst Du auch Menschen, die blind oder sehbehindert sind.", diff --git a/app/javascript/mastodon/locales/la.json b/app/javascript/mastodon/locales/la.json index c13a3136cd..a3b416a1ce 100644 --- a/app/javascript/mastodon/locales/la.json +++ b/app/javascript/mastodon/locales/la.json @@ -189,7 +189,7 @@ "notification.update": "{name} nuntium correxit", "notification_requests.accept": "Accipe", "notification_requests.confirm_accept_multiple.message": "Tu es accepturus {count, plural, one {una notitia petitionem} other {# notitia petitiones}}. Certus esne procedere vis?", - "notification_requests.confirm_dismiss_multiple.message": "Tu {count, plural, one {unam petitionem notificationis} other {# petitiones notificationum}} abrogāre prōximum es. {count, plural, one {Illa} other {Eae}} facile accessū nōn erit. Certus es tē procedere velle?", + "notification_requests.confirm_dismiss_multiple.message": "Tu {count, plural, one {unam petitionem notificationis} other {# petitiones notificationum}} abrogāre prōximum es. {count, plural, one {it} other {Eae}} facile accessū nōn erit. Certus es tē procedere velle?", "notifications.filter.all": "Omnia", "notifications.filter.polls": "Eventus electionis", "notifications.group": "{count} Notificātiōnēs", @@ -246,19 +246,102 @@ "status.history.created": "{name} creatum {date}", "status.history.edited": "{name} correxit {date}", "status.open": "Expand this status", + "status.quotes.empty": "Nemo hanc commentationem adhuc citavit. Cum quis citaverit, hic apparebit.", + "status.quotes.local_other_disclaimer": "Citationes ab auctore reiec­tæ non monstrabuntur.", + "status.quotes.remote_other_disclaimer": "Tantum citae ex {domain} hic exhiberi praestantur. Citae ab auctore reiectae non exhibebuntur.", + "status.quotes_count": "{count, plural, one {{counter} citatio} other {{counter} citationes}}", + "status.read_more": "Plura lege", + "status.reblog": "Promovere", + "status.reblog_or_quote": "Promovere aut cita", + "status.reblog_private": "Iterum cum sectatoribus tuis communica", "status.reblogged_by": "{name} adiuvavit", + "status.reblogs.empty": "Nemo hanc publicationem adhuc promovit. Cum quis eam promoveat, hic apparebunt.", + "status.reblogs_count": "{count, plural, one {{counter} incrementum} other {{counter} incrementa}}", + "status.redraft": "Dele et redig", + "status.remove_bookmark": "Tolle signum", + "status.remove_favourite": "Tolle ad delectis", + "status.remove_quote": "Tolle", + "status.replied_in_thread": "In filo responsum", + "status.replied_to": "{name} respondit", + "status.reply": "Respondere", + "status.replyAll": "Responde ad filum", + "status.report": "Referre @{name}", + "status.request_quote": "Pretium petere", + "status.revoke_quote": "Tolle nuntium meum ex nuntio @{name}", + "status.sensitive_warning": "Materia delicata", + "status.share": "Communica", + "status.show_less_all": "Omnibus minus monstra", + "status.show_more_all": "Omnibus plura monstra", + "status.show_original": "Monstra originalem", "status.title.with_attachments": "{user} publicavit {attachmentCount, plural, one {unum annexum} other {{attachmentCount} annexa}}", + "status.translate": "Converte", + "status.translated_from_with": "Translatum ex {lang} per {provider}", + "status.uncached_media_warning": "Praevisum non praesto est", + "status.unmute_conversation": "Conversationem reserare", + "subscribed_languages.lead": "Tantum epistolae in linguis selectis in domo tua apparebunt et indices temporum post mutationem. Neminem eligatis qui epistolas in omnibus linguis recipiat.", + "subscribed_languages.save": "Servare mutationes", + "subscribed_languages.target": "Muta linguas subscriptas pro {target}", "tabs_bar.home": "Domi", + "tabs_bar.menu": "Elenchus", + "tabs_bar.notifications": "Acta Vicimediorum", + "tabs_bar.publish": "Nova publicatio", + "tabs_bar.search": "Quaere", + "terms_of_service.effective_as_of": "Valet ex {date}", + "terms_of_service.title": "Termini servitii", + "terms_of_service.upcoming_changes_on": "Mutationes venturae die {date}", "time_remaining.days": "{number, plural, one {# die} other {# dies}} restant", "time_remaining.hours": "{number, plural, one {# hora} other {# horae}} restant", "time_remaining.minutes": "{number, plural, one {# minutum} other {# minuta}} restant", + "time_remaining.moments": "Momenta reliqua", "time_remaining.seconds": "{number, plural, one {# secundum} other {# secunda}} restant", - "trends.counter_by_accounts": "{count, plural, one {{counter} persōna} other {{counter} persōnae}} in {days, plural, one {diē prīdiē} other {diēbus praeteritīs {days}}}", + "trends.counter_by_accounts": "{count, plural, one {{counter} persōna} other {{counter} persōnae}} in {days, plural, one {days} other {diēbus praeteritīs {days}}}", + "trends.trending_now": "Nunc in usu", "ui.beforeunload": "Si Mastodon discesseris, tua epitome peribit.", "units.short.billion": "{count} millia milionum", "units.short.million": "{count} milionum", "units.short.thousand": "{count} millia", - "upload_button.label": "Imaginēs, vīdeō aut fīle audītūs adde", + "upload_area.title": "Trahe et depone ad imponendum", + "upload_button.label": "Adde imagines, pelliculam, aut fasciculum sonorum.", + "upload_error.limit": "Limes onerationis superatus est.", + "upload_error.poll": "Nullis suffragiis licet fascicula imponere.", + "upload_error.quote": "Nullum oneramentum fasciculi cum citationibus permittitur.", + "upload_form.drag_and_drop.instructions": "Ad annexum mediorum tollendum, preme clavem \"Space\" aut \"Enter\". Dum traheis, utere clavibus sagittariis ad annexum mediorum in quamlibet partem movendum. Preme iterum \"Space\" aut \"Enter\" ad annexum mediorum in novo loco deponendum, aut preme \"Escape\" ad desinendum.", + "upload_form.drag_and_drop.on_drag_cancel": "Tractatio revocata est. Adiunctum medium {item} demissum est.", + "upload_form.drag_and_drop.on_drag_end": "Adhaesum medium {item} demissum est.", + "upload_form.drag_and_drop.on_drag_over": "Adhaesum medium {item} motum est.", + "upload_form.drag_and_drop.on_drag_start": "Adhaesum medium {item} sublatum est.", "upload_form.edit": "Recolere", - "upload_progress.label": "Uploading…" + "upload_progress.label": "Oneratur...", + "upload_progress.processing": "Processus…", + "username.taken": "Illud nomen usoris occupatum est. Aliud tenta.", + "video.close": "Pelliculam claude", + "video.download": "Prehendere fasciculus", + "video.exit_fullscreen": "Exitus ex plenum monitorium", + "video.expand": "Expande pelliculam", + "video.fullscreen": "Plenum monitorium", + "video.hide": "celare pellicula", + "video.mute": "Mutus", + "video.pause": "intermittere", + "video.play": "gignere", + "video.skip_backward": "Redire", + "video.skip_forward": "Progredi", + "video.unmute": "Sordes tollere", + "video.volume_down": "Volumen deminui", + "video.volume_up": "Volumen augete", + "visibility_modal.button_title": "Visibilitatem statuere", + "visibility_modal.direct_quote_warning.text": "Si praesentia configuramenta servaveris, sententia inserta in nexum convertetur.", + "visibility_modal.direct_quote_warning.title": "Citatio in mentitionibus privatis inseriri non possunt.", + "visibility_modal.header": "Visibilitas et interactio", + "visibility_modal.helper.direct_quoting": "Mentiones privatae in Mastodon scriptae ab aliis citari non possunt.", + "visibility_modal.helper.privacy_editing": "Visibilitas mutari non potest postquam nuntius publicatus est.", + "visibility_modal.helper.privacy_private_self_quote": "Citationes propriae nuntiorum privatorum publicari non possunt.", + "visibility_modal.helper.private_quoting": "Nuntii in Mastodon a sequacibus tantum scriptī ab aliis citari non possunt.", + "visibility_modal.helper.unlisted_quoting": "Cum te citant, eorum scriptum etiam ex indicibus popularibus celabitur.", + "visibility_modal.instructions": "Régula quis cum hoc scripto agere possit. Potes etiam ordinationes omnibus scriptis futuris adhibere, navigando ad Praeferentias > Regulas scriptionis praedefinitas.", + "visibility_modal.privacy_label": "Visibilitas", + "visibility_modal.quote_followers": "Sectatores tantum", + "visibility_modal.quote_label": "Quis citare potest", + "visibility_modal.quote_nobody": "Sicut me", + "visibility_modal.quote_public": "quisquis", + "visibility_modal.save": "Servare" } diff --git a/config/locales/de.yml b/config/locales/de.yml index 402f421ed6..7d73a7b856 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1285,7 +1285,7 @@ de: new_confirmation_instructions_sent: In wenigen Minuten wirst du eine neue E-Mail mit dem Bestätigungslink erhalten! title: Überprüfe dein E-Mail-Postfach sign_in: - preamble_html: Melde dich mit deinen Zugangsdaten für %{domain} an. Falls dein Konto auf einem anderen Server erstellt wurde, ist eine Anmeldung hier nicht möglich. + preamble_html: Melde dich mit deinen Zugangsdaten für %{domain} an. Falls dein Konto auf einem anderen Mastodon-Server erstellt wurde, ist eine Anmeldung hier nicht möglich. title: Bei %{domain} anmelden sign_up: manual_review: Registrierungen für den Server %{domain} werden manuell durch unsere Moderator*innen überprüft. Um uns dabei zu unterstützen, schreibe etwas über dich und sage uns, weshalb du ein Konto auf %{domain} anlegen möchtest. diff --git a/config/locales/eu.yml b/config/locales/eu.yml index 3da7a1194d..f73a1284d8 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -563,6 +563,7 @@ eu: create: Gehitu Moderazio Oharra created_msg: Instantziako moderazio oharra ongi sortu da! description_html: Ikusi eta idatzi oharrak beste moderatzaileentzat eta zuretzat etorkizunerako + title: Moderazio oharrak private_comment: Iruzkin pribatua public_comment: Iruzkin publikoa purge: Ezabatu betiko @@ -771,6 +772,8 @@ eu: 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. + move_down: Behera mugitu + move_up: Mugitu gora title: Zerbitzariaren arauak translation: Itzulpena translations: Itzulpenak @@ -808,6 +811,14 @@ eu: all: Guztiei disabled: Inori ez users: Saioa hasita duten erabiltzaile lokalei + feed_access: + modes: + public: Edonork + landing_page: + values: + about: Honi buruz + local_feed: Jario lokala + trends: Joerak registrations: moderation_recommandation: Mesedez, ziurtatu moderazio-talde egokia eta erreaktiboa duzula erregistroak guztiei ireki aurretik! preamble: Kontrolatu nork sortu dezakeen kontua zerbitzarian. @@ -862,6 +873,7 @@ eu: original_status: Jatorrizko bidalketa quotes: Aipuak reblogs: Bultzadak + replied_to_html: "%{acct_link}(r)i erantzuten" status_changed: Bidalketa aldatuta status_title: "%{name} erabiltzailearen bidalketa" trending: Joera diff --git a/config/locales/hu.yml b/config/locales/hu.yml index c1d20eadeb..a957165f26 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -1672,6 +1672,7 @@ hu: disabled_account: A jelenlegi fiókod nem lesz teljesen használható ezután. Viszont elérhető lesz majd az adatexport funkció, valamint a reaktiválás is. followers: Ez a művelet az összes követődet a jelenlegi fiókról az újra fogja költöztetni only_redirect_html: Az is lehetséges, hogy csak átirányítást raksz a profilodra. + other_data: Semmilyen más adat (beleértve a bejegyzéseket és a követett fiókokat) nem lesz automatikusan áthelyezve redirect: A jelenlegi fiókod profiljára átirányításról szóló figyelmeztetést rakunk, valamint már nem fogjuk mutatni a keresésekben moderation: title: Moderáció @@ -1928,6 +1929,7 @@ hu: 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. + quoted_user_not_mentioned: Nem idézhet meg nem említett felhasználót egy privát említési bejegyzésben. 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/simple_form.cs.yml b/config/locales/simple_form.cs.yml index b4f8ba94f7..ff3f56a0d4 100644 --- a/config/locales/simple_form.cs.yml +++ b/config/locales/simple_form.cs.yml @@ -374,7 +374,9 @@ cs: jurisdiction: Právní příslušnost min_age: Věková hranice user: + date_of_birth_1i: Rok date_of_birth_2i: Měsíc + date_of_birth_3i: Den role: Role time_zone: Časové pásmo user_role: diff --git a/config/locales/simple_form.cy.yml b/config/locales/simple_form.cy.yml index 9d1abbf0a6..5a723d20f7 100644 --- a/config/locales/simple_form.cy.yml +++ b/config/locales/simple_form.cy.yml @@ -376,7 +376,9 @@ cy: jurisdiction: Awdurdodaeth gyfreithiol min_age: Isafswm oedran user: + date_of_birth_1i: Blwyddyn date_of_birth_2i: Mis + date_of_birth_3i: Diwrnod role: Rôl time_zone: Cylchfa amser user_role: diff --git a/config/locales/simple_form.hu.yml b/config/locales/simple_form.hu.yml index 6468298c72..aa118bf5d6 100644 --- a/config/locales/simple_form.hu.yml +++ b/config/locales/simple_form.hu.yml @@ -88,6 +88,7 @@ hu: activity_api_enabled: Helyi bejegyzések, aktív felhasználók és új regisztrációk száma heti bontásban app_icon: WEBP, PNG, GIF vagy JPG. Mobileszközökön az alkalmazás alapértelmezett ikonját felülírja egy egyéni ikonnal. backups_retention_period: A felhasználók archívumokat állíthatnak elő a bejegyzéseikből, hogy később letöltsék azokat. Ha pozitív értékre van állítva, akkor a megadott számú nap után automatikusan törölve lesznek a tárhelyedről. + bootstrap_timeline_accounts: Ezek a fiókok rögzítve lesznek az új felhasználók követési ajánlásai tetején. Add meg a fiókok vesszővel elválasztott listáját. closed_registrations_message: Akkor jelenik meg, amikor a regisztráció le van zárva content_cache_retention_period: Minden más kiszolgálóról származó bejegyzés (megtolásokkal és válaszokkal együtt) törölve lesz a megadott számú nap elteltével, függetlenül a helyi felhasználók ezekkel a bejegyzésekkel történő interakcióitól. Ebben azok a bejegyzések is benne vannak, melyeket a helyi felhasználó könyvjelzőzött vagy kedvencnek jelölt. A különböző kiszolgálók felhasználói közötti privát üzenetek is el fognak veszni visszaállíthatatlanul. Ennek a beállításnak a használata különleges felhasználási esetekre javasolt, mert számos felhasználói elvárás fog eltörni, ha általános céllal használják. custom_css: A Mastodon webes verziójában használhatsz egyéni stílusokat. @@ -371,7 +372,9 @@ hu: jurisdiction: Joghatóság min_age: Minimális életkor user: + date_of_birth_1i: Év date_of_birth_2i: Hónap + date_of_birth_3i: Nap role: Szerep time_zone: Időzóna user_role: From b4daad8c894b23d8bf953626144d8bcb60b32c55 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 18 Nov 2025 16:37:14 +0100 Subject: [PATCH 08/17] Fix double encoding in links (#36925) --- app/javascript/mastodon/components/status/handled_link.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/components/status/handled_link.tsx b/app/javascript/mastodon/components/status/handled_link.tsx index 43763d9c32..8b6db98fda 100644 --- a/app/javascript/mastodon/components/status/handled_link.tsx +++ b/app/javascript/mastodon/components/status/handled_link.tsx @@ -38,7 +38,7 @@ export const HandledLink: FC> = ({ return ( @@ -71,7 +71,7 @@ export const HandledLink: FC> = ({ return ( Date: Tue, 18 Nov 2025 17:17:26 +0100 Subject: [PATCH 09/17] Change private quote education modal to not show up on self-quotes (#36926) --- .../features/compose/containers/compose_form_container.js | 2 ++ 1 file changed, 2 insertions(+) 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 e64a5904ac..4ace6d934d 100644 --- a/app/javascript/mastodon/features/compose/containers/compose_form_container.js +++ b/app/javascript/mastodon/features/compose/containers/compose_form_container.js @@ -13,6 +13,7 @@ import { import { pasteLinkCompose } from 'mastodon/actions/compose_typed'; import { openModal } from 'mastodon/actions/modal'; import { PRIVATE_QUOTE_MODAL_ID } from 'mastodon/features/ui/components/confirmation_modals/private_quote_notify'; +import { me } from 'mastodon/initial_state'; import ComposeForm from '../components/compose_form'; @@ -53,6 +54,7 @@ const mapStateToProps = state => ({ quoteToPrivate: !!state.getIn(['compose', 'quoted_status_id']) && state.getIn(['compose', 'privacy']) === 'private' + && state.getIn(['statuses', state.getIn(['compose', 'quoted_status_id']), 'account']) !== me && !state.getIn(['settings', 'dismissed_banners', PRIVATE_QUOTE_MODAL_ID]), isInReply: state.getIn(['compose', 'in_reply_to']) !== null, lang: state.getIn(['compose', 'language']), From be0e23bb0ad2912416cabf0d8676db3af23eef27 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 18 Nov 2025 17:20:50 +0100 Subject: [PATCH 10/17] Fix scroll-to-status in threaded view being unreliable (#36927) --- .../status/components/detailed_status.tsx | 30 ++++++++++++++++++- .../mastodon/features/status/index.jsx | 30 ++----------------- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/app/javascript/mastodon/features/status/components/detailed_status.tsx b/app/javascript/mastodon/features/status/components/detailed_status.tsx index d326734a01..b0c66b9338 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.tsx +++ b/app/javascript/mastodon/features/status/components/detailed_status.tsx @@ -4,7 +4,7 @@ @typescript-eslint/no-unsafe-assignment */ import type { CSSProperties } from 'react'; -import { useState, useRef, useCallback } from 'react'; +import { useState, useRef, useCallback, useEffect } from 'react'; import { FormattedMessage } from 'react-intl'; @@ -55,6 +55,8 @@ export const DetailedStatus: React.FC<{ pictureInPicture: any; onToggleHidden?: (status: any) => void; onToggleMediaVisibility?: () => void; + ancestors?: number; + multiColumn?: boolean; }> = ({ status, onOpenMedia, @@ -69,6 +71,8 @@ export const DetailedStatus: React.FC<{ pictureInPicture, onToggleMediaVisibility, onToggleHidden, + ancestors = 0, + multiColumn = false, }) => { const properStatus = status?.get('reblog') ?? status; const [height, setHeight] = useState(0); @@ -123,6 +127,30 @@ export const DetailedStatus: React.FC<{ if (onTranslate) onTranslate(status); }, [onTranslate, status]); + // The component is managed and will change if the status changes + // Ancestors can increase when loading a thread, in which case we want to scroll, + // or decrease if a post is deleted, in which case we don't want to mess with it + const previousAncestors = useRef(-1); + useEffect(() => { + if (nodeRef.current && previousAncestors.current < ancestors) { + nodeRef.current.scrollIntoView(true); + + // In the single-column interface, `scrollIntoView` will put the post behind the header, so compensate for that. + if (!multiColumn) { + const offset = document + .querySelector('.column-header__wrapper') + ?.getBoundingClientRect().bottom; + + if (offset) { + const scrollingElement = document.scrollingElement ?? document.body; + scrollingElement.scrollBy(0, -offset); + } + } + } + + previousAncestors.current = ancestors; + }, [ancestors, multiColumn]); + if (!properStatus) { return null; } diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index f3ce9858ba..77e8af4b67 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -164,8 +164,6 @@ class Status extends ImmutablePureComponent { componentDidMount () { attachFullscreenListener(this.onFullScreenChange); - - this._scrollStatusIntoView(); } UNSAFE_componentWillReceiveProps (nextProps) { @@ -487,35 +485,11 @@ class Status extends ImmutablePureComponent { this.statusNode = c; }; - _scrollStatusIntoView () { - const { status, multiColumn } = this.props; - - if (status) { - requestIdleCallback(() => { - this.statusNode?.scrollIntoView(true); - - // In the single-column interface, `scrollIntoView` will put the post behind the header, - // so compensate for that. - if (!multiColumn) { - const offset = document.querySelector('.column-header__wrapper')?.getBoundingClientRect()?.bottom; - if (offset) { - const scrollingElement = document.scrollingElement || document.body; - scrollingElement.scrollBy(0, -offset); - } - } - }); - } - } - componentDidUpdate (prevProps) { - const { status, ancestorsIds, descendantsIds } = this.props; + const { status, descendantsIds } = this.props; const isSameStatus = status && (prevProps.status?.get('id') === status.get('id')); - if (status && (ancestorsIds.length > prevProps.ancestorsIds.length || !isSameStatus)) { - this._scrollStatusIntoView(); - } - // Only highlight replies after the initial load if (prevProps.descendantsIds.length && isSameStatus) { const newRepliesIds = difference(descendantsIds, prevProps.descendantsIds); @@ -619,6 +593,8 @@ class Status extends ImmutablePureComponent { showMedia={this.state.showMedia} onToggleMediaVisibility={this.handleToggleMediaVisibility} pictureInPicture={pictureInPicture} + ancestors={this.props.ancestorsIds.length} + multiColumn={multiColumn} /> Date: Wed, 19 Nov 2025 04:28:43 -0500 Subject: [PATCH 11/17] Fix `Rails/RedirectBackOrTo` cop (#36930) --- app/controllers/admin/site_uploads_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/admin/site_uploads_controller.rb b/app/controllers/admin/site_uploads_controller.rb index 96e61cf6bb..e30c783a49 100644 --- a/app/controllers/admin/site_uploads_controller.rb +++ b/app/controllers/admin/site_uploads_controller.rb @@ -9,7 +9,7 @@ module Admin @site_upload.destroy! - redirect_back fallback_location: admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg') + redirect_back_or_to admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg') end private From 81ffd1d3c767a81f4cd18518860ccc57b6a1599a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 11:09:54 +0100 Subject: [PATCH 12/17] New Crowdin Translations (automated) (#36933) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/da.json | 14 +++++++------- app/javascript/mastodon/locales/de.json | 4 ++-- app/javascript/mastodon/locales/fi.json | 2 +- app/javascript/mastodon/locales/fr-CA.json | 2 +- app/javascript/mastodon/locales/fr.json | 2 +- config/locales/da.yml | 4 ++-- config/locales/de.yml | 14 +++++++------- config/locales/ru.yml | 2 ++ config/locales/simple_form.de.yml | 6 +++--- 9 files changed, 26 insertions(+), 24 deletions(-) diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 6b2f68bd13..29edfe3171 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -231,7 +231,7 @@ "confirmations.delete_list.title": "Slet liste?", "confirmations.discard_draft.confirm": "Kassér og fortsæt", "confirmations.discard_draft.edit.cancel": "Fortsæt redigering", - "confirmations.discard_draft.edit.message": "Hvis du fortsætter, kasseres alle ændringer, du har foretaget i det indlæg, du er i gang med at redigere.", + "confirmations.discard_draft.edit.message": "Hvis du fortsætter, vil alle ændringer, du har foretaget i det indlæg, du er er ved at redigere, blive slettet.", "confirmations.discard_draft.edit.title": "Kassér ændringer til dit indlæg?", "confirmations.discard_draft.post.cancel": "Genoptag udkast", "confirmations.discard_draft.post.message": "Hvis du fortsætter, kasseres det indlæg, du er i gang med at udforme.", @@ -507,7 +507,7 @@ "keyboard_shortcuts.pinned": "Åbn liste over fastgjorte indlæg", "keyboard_shortcuts.profile": "Åbn forfatters profil", "keyboard_shortcuts.quote": "Citér indlæg", - "keyboard_shortcuts.reply": "Besvar indlægget", + "keyboard_shortcuts.reply": "Besvar indlæg", "keyboard_shortcuts.requests": "Åbn liste over følgeanmodninger", "keyboard_shortcuts.search": "Fokusér søgebjælke", "keyboard_shortcuts.spoilers": "Vis/skjul indholdsadvarsel-felt", @@ -675,7 +675,7 @@ "notifications.column_settings.filter_bar.category": "Hurtigfiltreringsbjælke", "notifications.column_settings.follow": "Nye følgere:", "notifications.column_settings.follow_request": "Nye følgeanmodninger:", - "notifications.column_settings.group": "Gruppere", + "notifications.column_settings.group": "Gruppér", "notifications.column_settings.mention": "Omtaler:", "notifications.column_settings.poll": "Afstemningsresultater:", "notifications.column_settings.push": "Push-notifikationer", @@ -764,7 +764,7 @@ "privacy_policy.last_updated": "Senest opdateret {date}", "privacy_policy.title": "Privatlivspolitik", "quote_error.edit": "Citater kan ikke tilføjes ved redigering af et indlæg.", - "quote_error.poll": "Citering ikke tilladt i afstemninger.", + "quote_error.poll": "Citering er ikke tilladt med afstemninger.", "quote_error.private_mentions": "Citering er ikke tilladt med direkte omtaler.", "quote_error.quote": "Kun ét citat ad gangen er tilladt.", "quote_error.unauthorized": "Du har ikke tilladelse til at citere dette indlæg.", @@ -867,7 +867,7 @@ "search_results.title": "Søg efter \"{q}\"", "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.administered_by": "Administreret 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.", "server_banner.server_stats": "Serverstatstik:", "sign_in_banner.create_account": "Opret konto", @@ -902,7 +902,7 @@ "status.edited": "Senest redigeret {date}", "status.edited_x_times": "Redigeret {count, plural, one {{count} gang} other {{count} gange}}", "status.embed": "Hent indlejringskode", - "status.favourite": "Favorit", + "status.favourite": "Favoritmarkér", "status.favourites_count": "{count, plural, one {{counter} favorit} other {{counter} favoritter}}", "status.filter": "Filtrér dette indlæg", "status.history.created": "{name} oprettet {date}", @@ -991,7 +991,7 @@ "units.short.million": "{count} mio.", "units.short.thousand": "{count} tusind", "upload_area.title": "Træk og slip for at uploade", - "upload_button.label": "Tilføj billed-, video- eller lydfil(er)", + "upload_button.label": "Tilføj billeder, en video- eller lydfil", "upload_error.limit": "Grænse for filupload nået.", "upload_error.poll": "Filupload ikke tilladt for afstemninger.", "upload_error.quote": "Fil-upload ikke tilladt i citater.", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 0babff64b7..40cd74f23f 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -584,8 +584,8 @@ "navigation_bar.follows_and_followers": "Follower und Folge ich", "navigation_bar.import_export": "Importieren und exportieren", "navigation_bar.lists": "Listen", - "navigation_bar.live_feed_local": "Live-Feed (lokal)", - "navigation_bar.live_feed_public": "Live-Feed (öffentlich)", + "navigation_bar.live_feed_local": "Live-Feed (Dieser Server)", + "navigation_bar.live_feed_public": "Live-Feed (Alle Server)", "navigation_bar.logout": "Abmelden", "navigation_bar.moderation": "Moderation", "navigation_bar.more": "Mehr", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index d706e78c5e..4aa6c425b2 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -233,7 +233,7 @@ "confirmations.discard_draft.edit.cancel": "Palaa muokkaamaan", "confirmations.discard_draft.edit.message": "Jatkaminen tuhoaa kaikki muutokset, joita olet tehnyt julkaisuun, jota olet parhaillaan muokkaamassa.", "confirmations.discard_draft.edit.title": "Hylätäänkö luonnosjulkaisusi muutokset?", - "confirmations.discard_draft.post.cancel": "Palaa lunnokseen", + "confirmations.discard_draft.post.cancel": "Palaa luonnokseen", "confirmations.discard_draft.post.message": "Jatkaminen tuhoaa julkaisun, jota olet parhaillaan laatimassa.", "confirmations.discard_draft.post.title": "Hylätäänkö luonnosjulkaisusi?", "confirmations.discard_edit_media.confirm": "Hylkää", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index 3303a2ce2b..cc896dfadc 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -528,7 +528,7 @@ "limited_account_hint.action": "Afficher le profil quand même", "limited_account_hint.title": "Ce profil a été masqué par la modération de {domain}.", "link_preview.author": "Par {name}", - "link_preview.more_from_author": "Plus via {name}", + "link_preview.more_from_author": "Voir plus de {name}", "link_preview.shares": "{count, plural, one {{counter} message} other {{counter} messages}}", "lists.add_member": "Ajouter", "lists.add_to_list": "Ajouter à la liste", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index e875d7084a..1b9a3580e3 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -528,7 +528,7 @@ "limited_account_hint.action": "Afficher le profil quand même", "limited_account_hint.title": "Ce profil a été masqué par la modération de {domain}.", "link_preview.author": "Par {name}", - "link_preview.more_from_author": "Plus via {name}", + "link_preview.more_from_author": "Voir plus de {name}", "link_preview.shares": "{count, plural, one {{counter} message} other {{counter} messages}}", "lists.add_member": "Ajouter", "lists.add_to_list": "Ajouter à la liste", diff --git a/config/locales/da.yml b/config/locales/da.yml index 8369b90008..e8053aaf63 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -477,7 +477,7 @@ da: no_file: Ingen fil valgt export_domain_blocks: import: - description_html: En liste over domæneblokeringer er ved at blive importeret. Gennemgå listen meget nøje, især hvis man ikke selv har oprettet den. + description_html: Du er ved at importere en liste over domæneblokeringer. Gennemgå denne liste meget omhyggeligt, især hvis du ikke selv har udarbejdet den. existing_relationships_warning: Eksisterende følge-relationer private_comment_description_html: 'For at man lettere kan holde styr på, hvorfra importerede blokeringer kommer, oprettes disse med flg. private kommentar: %{comment}' private_comment_template: Importeret fra %{source} d. %{date} @@ -1186,7 +1186,7 @@ da: new_trending_statuses: title: Indlæg, der trender new_trending_tags: - title: Hashtags, der trender + title: Populære hashtags subject: Nye tendenser klar til gennemgang på %{instance} aliases: add_new: Opret alias diff --git a/config/locales/de.yml b/config/locales/de.yml index 7d73a7b856..b085fc4f01 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1089,16 +1089,16 @@ de: tag_servers_measure: Server tag_uses_measure: insgesamt description_html: Diese Hashtags werden derzeit in vielen Beiträgen verwendet, die dein Server sieht. Dies kann deinen Nutzer*innen helfen, herauszufinden, worüber die Leute im Moment am meisten schreiben. Hashtags werden erst dann öffentlich angezeigt, wenn du sie genehmigst. - listable: Kann vorgeschlagen werden + listable: Darf empfohlen werden no_tag_selected: Keine Hashtags wurden geändert, da keine ausgewählt wurden - not_listable: Wird nicht vorgeschlagen - not_trendable: Wird in den Trends nicht angezeigt - not_usable: Kann nicht verwendet werden + not_listable: Darf nicht vorgeschlagen werden + not_trendable: In Trends nicht erlaubt + not_usable: In Beiträgen nicht erlaubt peaked_on_and_decaying: In den Trends am %{date}, jetzt absteigend title: Angesagte Hashtags - trendable: Darf in den Trends erscheinen + trendable: In Trends erlaubt trending_rank: Platz %{rank} - usable: Darf verwendet werden + usable: In Beiträgen erlaubt usage_comparison: Heute %{today}-mal und gestern %{yesterday}-mal verwendet used_by_over_week: one: In den vergangenen 7 Tagen von einem Profil verwendet @@ -1122,7 +1122,7 @@ de: 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 + title: Profilnamen updated_msg: Regel für Profilnamen erfolgreich aktualisiert warning_presets: add_new: Neu hinzufügen diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 13ad3fe40e..fa81b360fa 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -928,6 +928,7 @@ ru: no_status_selected: Ничего не изменилось, так как ни один пост не был выделен open: Открыть запись original_status: Оригинальный пост + quotes: Цитаты reblogs: Продвинули replied_to_html: Ответ пользователю %{acct_link} status_changed: Пост изменен @@ -935,6 +936,7 @@ ru: title: Посты пользователя - @%{name} trending: Популярное view_publicly: Открыть по публичной ссылке + view_quoted_post: Просмотр цитируемого сообщения visibility: Видимость with_media: С файлами strikes: diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml index 46b61919a5..392d7076d1 100644 --- a/config/locales/simple_form.de.yml +++ b/config/locales/simple_form.de.yml @@ -353,10 +353,10 @@ de: indexable: Profilseite in Suchmaschinen einbeziehen show_application: App anzeigen, über die ich einen Beitrag veröffentlicht habe tag: - listable: Erlaube, dass dieser Hashtag in Suchen und Empfehlungen erscheint + listable: Dieser Hashtag darf in Suchen und Empfehlungen erscheinen name: Hashtag - trendable: Erlaube, dass dieser Hashtag in den Trends erscheint - usable: Beiträge dürfen diesen Hashtag lokal verwenden + trendable: Dieser Hashtag darf in den Trends erscheinen + usable: Dieser Hashtag darf lokal in Beiträgen verwendet werden terms_of_service: changelog: Was hat sich geändert? effective_date: Datum des Inkrafttretens From 4f6a7e44d101169be71e244cef04799a1b10ffd5 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 19 Nov 2025 05:14:51 -0500 Subject: [PATCH 13/17] Update rubocop-rspec to version 3.8.0 (#36853) --- Gemfile.lock | 12 ++++++------ spec/lib/mastodon/redis_configuration_spec.rb | 6 ++---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 73c54fc633..a64430872c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -349,7 +349,7 @@ GEM azure-blob (~> 0.5.2) hashie (~> 5.0) jmespath (1.6.2) - json (2.15.2) + json (2.16.0) json-canonicalization (1.0.0) json-jwt (1.17.0) activesupport (>= 4.2) @@ -446,7 +446,7 @@ GEM mime-types-data (3.2025.0924) mini_mime (1.1.5) mini_portile2 (2.8.9) - minitest (5.26.0) + minitest (5.26.1) msgpack (1.8.0) multi_json (1.17.0) mutex_m (0.3.0) @@ -759,7 +759,7 @@ GEM rubocop-ast (>= 1.47.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.47.1) + rubocop-ast (1.48.0) parser (>= 3.3.7.2) prism (~> 1.4) rubocop-capybara (2.22.1) @@ -778,10 +778,10 @@ GEM rack (>= 1.1) rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.44.0, < 2.0) - rubocop-rspec (3.7.0) + rubocop-rspec (3.8.0) lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) - rubocop-rspec_rails (2.31.0) + rubocop (~> 1.81) + rubocop-rspec_rails (2.32.0) lint_roller (~> 1.1) rubocop (~> 1.72, >= 1.72.1) rubocop-rspec (~> 3.5) diff --git a/spec/lib/mastodon/redis_configuration_spec.rb b/spec/lib/mastodon/redis_configuration_spec.rb index d8a8dd86d2..560b16ed30 100644 --- a/spec/lib/mastodon/redis_configuration_spec.rb +++ b/spec/lib/mastodon/redis_configuration_spec.rb @@ -73,8 +73,6 @@ RSpec.describe Mastodon::RedisConfiguration do end shared_examples 'sentinel support' do |prefix = nil| - prefix = prefix ? "#{prefix}_" : '' - context 'when configuring sentinel support' do around do |example| ClimateControl.modify "#{prefix}REDIS_PASSWORD": 'testpass1', "#{prefix}REDIS_HOST": 'redis2.example.com', "#{prefix}REDIS_SENTINELS": '192.168.0.1:3000,192.168.0.2:4000', "#{prefix}REDIS_SENTINEL_MASTER": 'mainsentinel' do @@ -199,7 +197,7 @@ RSpec.describe Mastodon::RedisConfiguration do it_behaves_like 'secondary configuration', 'SIDEKIQ' it_behaves_like 'setting a different driver' - it_behaves_like 'sentinel support', 'SIDEKIQ' + it_behaves_like 'sentinel support', 'SIDEKIQ_' end describe '#cache' do @@ -225,6 +223,6 @@ RSpec.describe Mastodon::RedisConfiguration do it_behaves_like 'secondary configuration', 'CACHE' it_behaves_like 'setting a different driver' - it_behaves_like 'sentinel support', 'CACHE' + it_behaves_like 'sentinel support', 'CACHE_' end end From 52b92bdc9c08e1bed350f38dbe531d4a71337b4d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 11:19:49 +0100 Subject: [PATCH 14/17] chore(deps): update dependency bootsnap to '~> 1.19.0' (#36906) 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 e54842fcda..599686d5ba 100644 --- a/Gemfile +++ b/Gemfile @@ -24,7 +24,7 @@ gem 'ruby-vips', '~> 2.2', require: false gem 'active_model_serializers', '~> 0.10' gem 'addressable', '~> 2.8' -gem 'bootsnap', '~> 1.18.0', require: false +gem 'bootsnap', '~> 1.19.0', require: false gem 'browser' gem 'charlock_holmes', '~> 0.7.7' gem 'chewy', '~> 7.3' diff --git a/Gemfile.lock b/Gemfile.lock index a64430872c..4f24c4106d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -129,7 +129,7 @@ GEM binding_of_caller (1.0.1) debug_inspector (>= 1.2.0) blurhash (0.1.8) - bootsnap (1.18.6) + bootsnap (1.19.0) msgpack (~> 1.2) brakeman (7.1.1) racc @@ -941,7 +941,7 @@ DEPENDENCIES better_errors (~> 2.9) binding_of_caller (~> 1.0) blurhash (~> 0.1) - bootsnap (~> 1.18.0) + bootsnap (~> 1.19.0) brakeman (~> 7.0) browser bundler-audit (~> 0.9) From c22b203bca7c49765d530f7f6ea088bd62cdc981 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 19 Nov 2025 11:35:10 +0100 Subject: [PATCH 15/17] Fix quoting overwriting current content warning (#36934) --- app/javascript/mastodon/reducers/compose.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index d4ed1bd505..784050d942 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -341,8 +341,8 @@ export const composeReducer = (state = initialState, action) => { const isDirect = state.get('privacy') === 'direct'; return state .set('quoted_status_id', isDirect ? null : status.get('id')) - .set('spoiler', status.get('sensitive')) - .set('spoiler_text', status.get('spoiler_text')) + .update('spoiler', spoiler => (spoiler) || !!status.get('spoiler_text')) + .update('spoiler_text', (spoiler_text) => spoiler_text || status.get('spoiler_text')) .update('privacy', (visibility) => { if (['public', 'unlisted'].includes(visibility) && status.get('visibility') === 'private') { return 'private'; From 4d0aab4a319320a7ca5d96c25e15eb91dbcaa7e7 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Wed, 19 Nov 2025 11:58:07 +0100 Subject: [PATCH 16/17] Fix `g` + `h` keyboard shortcut not working when a post is focused (#36935) --- .../mastodon/components/hotkeys/index.tsx | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/app/javascript/mastodon/components/hotkeys/index.tsx b/app/javascript/mastodon/components/hotkeys/index.tsx index b1484ec3ac..81ca28eb87 100644 --- a/app/javascript/mastodon/components/hotkeys/index.tsx +++ b/app/javascript/mastodon/components/hotkeys/index.tsx @@ -180,25 +180,24 @@ export function useHotkeys(handlers: HandlerMap) { if (shouldHandleEvent) { const matchCandidates: { - handler: (event: KeyboardEvent) => void; + // A candidate will be have an undefined handler if it's matched, + // but handled in a parent component rather than this one. + handler: ((event: KeyboardEvent) => void) | undefined; priority: number; }[] = []; (Object.keys(hotkeyMatcherMap) as HotkeyName[]).forEach( (handlerName) => { const handler = handlersRef.current[handlerName]; + const hotkeyMatcher = hotkeyMatcherMap[handlerName]; - if (handler) { - const hotkeyMatcher = hotkeyMatcherMap[handlerName]; + const { isMatch, priority } = hotkeyMatcher( + event, + bufferedKeys.current, + ); - const { isMatch, priority } = hotkeyMatcher( - event, - bufferedKeys.current, - ); - - if (isMatch) { - matchCandidates.push({ handler, priority }); - } + if (isMatch) { + matchCandidates.push({ handler, priority }); } }, ); From 366856f3bcdc2ff008b04e493a5de317ab83d5d0 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Wed, 19 Nov 2025 13:37:15 +0100 Subject: [PATCH 17/17] Fix theme-related Vite errors even when `theme_tokens` feature flag is disabled (#36936) --- config/vite/plugin-mastodon-themes.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/config/vite/plugin-mastodon-themes.ts b/config/vite/plugin-mastodon-themes.ts index 3e7bc7a8a8..98bda4cab0 100644 --- a/config/vite/plugin-mastodon-themes.ts +++ b/config/vite/plugin-mastodon-themes.ts @@ -40,13 +40,15 @@ export function MastodonThemes(): Plugin { // Get all files mentioned in the themes.yml file. const themes = await loadThemesFromConfig(projectRoot); + const allThemes = { + ...themes, + default_theme_tokens: 'styles_new/application.scss', + 'mastodon-light_theme_tokens': 'styles_new/mastodon-light.scss', + contrast_theme_tokens: 'styles_new/contrast.scss', + }; - for (const [themeName, themePath] of Object.entries(themes)) { + for (const [themeName, themePath] of Object.entries(allThemes)) { entrypoints[`themes/${themeName}`] = path.resolve(jsRoot, themePath); - entrypoints[`themes/${themeName}_theme_tokens`] = path.resolve( - jsRoot, - themePath.replace('styles/', 'styles_new/'), - ); } return {