diff --git a/.nvmrc b/.nvmrc
index 48ef2c10ba..d4c3d320cc 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-20.9
+20.10
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 030f311101..bc66bc4add 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -45,48 +45,6 @@ Metrics/PerceivedComplexity:
RSpec/ExampleLength:
Max: 22
-RSpec/LetSetup:
- Exclude:
- - 'spec/controllers/api/v1/accounts/statuses_controller_spec.rb'
- - 'spec/controllers/api/v1/filters_controller_spec.rb'
- - 'spec/controllers/api/v2/admin/accounts_controller_spec.rb'
- - 'spec/controllers/api/v2/filters/keywords_controller_spec.rb'
- - 'spec/controllers/api/v2/filters/statuses_controller_spec.rb'
- - 'spec/controllers/auth/confirmations_controller_spec.rb'
- - 'spec/controllers/auth/passwords_controller_spec.rb'
- - 'spec/controllers/auth/sessions_controller_spec.rb'
- - 'spec/controllers/follower_accounts_controller_spec.rb'
- - 'spec/controllers/following_accounts_controller_spec.rb'
- - 'spec/controllers/oauth/authorized_applications_controller_spec.rb'
- - 'spec/controllers/oauth/tokens_controller_spec.rb'
- - 'spec/controllers/settings/imports_controller_spec.rb'
- - 'spec/lib/activitypub/activity/delete_spec.rb'
- - 'spec/lib/vacuum/applications_vacuum_spec.rb'
- - 'spec/lib/vacuum/preview_cards_vacuum_spec.rb'
- - 'spec/models/account_spec.rb'
- - 'spec/models/account_statuses_cleanup_policy_spec.rb'
- - 'spec/models/canonical_email_block_spec.rb'
- - 'spec/models/status_spec.rb'
- - 'spec/models/user_spec.rb'
- - 'spec/services/account_statuses_cleanup_service_spec.rb'
- - 'spec/services/activitypub/fetch_featured_collection_service_spec.rb'
- - 'spec/services/activitypub/fetch_remote_status_service_spec.rb'
- - 'spec/services/activitypub/process_account_service_spec.rb'
- - 'spec/services/activitypub/process_collection_service_spec.rb'
- - 'spec/services/batched_remove_status_service_spec.rb'
- - 'spec/services/block_domain_service_spec.rb'
- - 'spec/services/bulk_import_service_spec.rb'
- - 'spec/services/delete_account_service_spec.rb'
- - 'spec/services/import_service_spec.rb'
- - 'spec/services/notify_service_spec.rb'
- - 'spec/services/remove_status_service_spec.rb'
- - 'spec/services/report_service_spec.rb'
- - 'spec/services/resolve_account_service_spec.rb'
- - 'spec/services/suspend_account_service_spec.rb'
- - 'spec/services/unallow_domain_service_spec.rb'
- - 'spec/services/unsuspend_account_service_spec.rb'
- - 'spec/workers/scheduler/user_cleanup_scheduler_spec.rb'
-
RSpec/MultipleExpectations:
Max: 8
diff --git a/Gemfile.lock b/Gemfile.lock
index 3b33402b22..9b78ca733e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -131,20 +131,20 @@ GEM
attr_required (1.0.1)
awrence (1.2.1)
aws-eventstream (1.3.0)
- aws-partitions (1.857.0)
- aws-sdk-core (3.188.0)
- aws-eventstream (~> 1, >= 1.0.2)
+ aws-partitions (1.860.0)
+ aws-sdk-core (3.189.0)
+ aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
- aws-sigv4 (~> 1.5)
+ aws-sigv4 (~> 1.8)
jmespath (~> 1, >= 1.6.1)
- aws-sdk-kms (1.73.0)
+ aws-sdk-kms (1.74.0)
aws-sdk-core (~> 3, >= 3.188.0)
aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.140.0)
- aws-sdk-core (~> 3, >= 3.188.0)
+ aws-sdk-s3 (1.141.0)
+ aws-sdk-core (~> 3, >= 3.189.0)
aws-sdk-kms (~> 1)
- aws-sigv4 (~> 1.6)
- aws-sigv4 (1.7.0)
+ aws-sigv4 (~> 1.8)
+ aws-sigv4 (1.8.0)
aws-eventstream (~> 1, >= 1.0.2)
azure-storage-blob (2.0.3)
azure-storage-common (~> 2.0)
@@ -220,7 +220,7 @@ GEM
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
date (3.3.4)
- debug (1.9.0)
+ debug (1.9.1)
irb (~> 1.10)
reline (>= 0.3.8)
debug_inspector (1.1.0)
@@ -376,8 +376,8 @@ GEM
rainbow (>= 2.2.2, < 4.0)
terminal-table (>= 1.5.1)
idn-ruby (0.1.5)
- io-console (0.6.0)
- irb (1.10.1)
+ io-console (0.7.1)
+ irb (1.11.0)
rdoc
reline (>= 0.3.8)
jmespath (1.6.2)
@@ -540,7 +540,7 @@ GEM
activesupport (>= 7.0.0)
rack
railties (>= 7.0.0)
- psych (5.1.1.1)
+ psych (5.1.2)
stringio
public_suffix (5.0.4)
puma (6.4.0)
@@ -614,7 +614,7 @@ GEM
link_header (~> 0.0, >= 0.0.8)
rdf-normalize (0.6.1)
rdf (~> 3.2)
- rdoc (6.6.1)
+ rdoc (6.6.2)
psych (>= 4.0.0)
redcarpet (3.6.0)
redis (4.8.1)
diff --git a/app/controllers/admin/follow_recommendations_controller.rb b/app/controllers/admin/follow_recommendations_controller.rb
index 841e3cc7fb..a54e41bd8c 100644
--- a/app/controllers/admin/follow_recommendations_controller.rb
+++ b/app/controllers/admin/follow_recommendations_controller.rb
@@ -8,7 +8,7 @@ module Admin
authorize :follow_recommendation, :show?
@form = Form::AccountBatch.new
- @accounts = filtered_follow_recommendations
+ @accounts = filtered_follow_recommendations.page(params[:page])
end
def update
diff --git a/app/controllers/api/v1/suggestions_controller.rb b/app/controllers/api/v1/suggestions_controller.rb
index 9737ae5cb6..9ba1cef63c 100644
--- a/app/controllers/api/v1/suggestions_controller.rb
+++ b/app/controllers/api/v1/suggestions_controller.rb
@@ -3,22 +3,23 @@
class Api::V1::SuggestionsController < Api::BaseController
include Authorization
- before_action -> { doorkeeper_authorize! :read }
+ before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index
+ before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :index
before_action :require_user!
+ before_action :set_suggestions
def index
- suggestions = suggestions_source.get(current_account, limit: limit_param(DEFAULT_ACCOUNTS_LIMIT))
- render json: suggestions.map(&:account), each_serializer: REST::AccountSerializer
+ render json: @suggestions.get(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:offset].to_i).map(&:account), each_serializer: REST::AccountSerializer
end
def destroy
- suggestions_source.remove(current_account, params[:id])
+ @suggestions.remove(params[:id])
render_empty
end
private
- def suggestions_source
- AccountSuggestions::PastInteractionsSource.new
+ def set_suggestions
+ @suggestions = AccountSuggestions.new(current_account)
end
end
diff --git a/app/controllers/api/v2/suggestions_controller.rb b/app/controllers/api/v2/suggestions_controller.rb
index 35eb276c01..8516796e86 100644
--- a/app/controllers/api/v2/suggestions_controller.rb
+++ b/app/controllers/api/v2/suggestions_controller.rb
@@ -3,17 +3,23 @@
class Api::V2::SuggestionsController < Api::BaseController
include Authorization
- before_action -> { doorkeeper_authorize! :read }
+ before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index
+ before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :index
before_action :require_user!
before_action :set_suggestions
def index
- render json: @suggestions, each_serializer: REST::SuggestionSerializer
+ render json: @suggestions.get(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:offset].to_i), each_serializer: REST::SuggestionSerializer
+ end
+
+ def destroy
+ @suggestions.remove(params[:id])
+ render_empty
end
private
def set_suggestions
- @suggestions = AccountSuggestions.get(current_account, limit_param(DEFAULT_ACCOUNTS_LIMIT))
+ @suggestions = AccountSuggestions.new(current_account)
end
end
diff --git a/app/javascript/mastodon/features/account/components/header.jsx b/app/javascript/mastodon/features/account/components/header.jsx
index 29b46cb43d..97f68c2217 100644
--- a/app/javascript/mastodon/features/account/components/header.jsx
+++ b/app/javascript/mastodon/features/account/components/header.jsx
@@ -35,6 +35,8 @@ import FollowRequestNoteContainer from '../containers/follow_request_note_contai
const messages = defineMessages({
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
follow: { id: 'account.follow', defaultMessage: 'Follow' },
+ followBack: { id: 'account.follow_back', defaultMessage: 'Follow back' },
+ mutual: { id: 'account.mutual', defaultMessage: 'Mutual' },
cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Withdraw follow request' },
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
@@ -82,6 +84,20 @@ const titleFromAccount = account => {
return `${prefix} (@${acct})`;
};
+const messageForFollowButton = relationship => {
+ if(!relationship) return messages.follow;
+
+ if (relationship.get('following') && relationship.get('followed_by')) {
+ return messages.mutual;
+ } else if (!relationship.get('following') && relationship.get('followed_by')) {
+ return messages.followBack;
+ } else if (relationship.get('following')) {
+ return messages.unfollow;
+ } else {
+ return messages.follow;
+ }
+};
+
const dateFormatOptions = {
month: 'short',
day: 'numeric',
@@ -253,9 +269,7 @@ class Header extends ImmutablePureComponent {
let info = [];
let menu = [];
- if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
- info.push();
- } else if (me !== account.get('id') && account.getIn(['relationship', 'blocking'])) {
+ if (me !== account.get('id') && account.getIn(['relationship', 'blocking'])) {
info.push();
}
@@ -281,7 +295,7 @@ class Header extends ImmutablePureComponent {
} else if (account.getIn(['relationship', 'requested'])) {
actionBtn = ;
} else if (!account.getIn(['relationship', 'blocking'])) {
- actionBtn = ;
+ actionBtn = ;
} else if (account.getIn(['relationship', 'blocking'])) {
actionBtn = ;
}
diff --git a/app/javascript/mastodon/features/onboarding/profile.jsx b/app/javascript/mastodon/features/onboarding/profile.jsx
index 09e6b2c6c6..daaef6065c 100644
--- a/app/javascript/mastodon/features/onboarding/profile.jsx
+++ b/app/javascript/mastodon/features/onboarding/profile.jsx
@@ -26,6 +26,8 @@ const messages = defineMessages({
uploadAvatar: { id: 'onboarding.profile.upload_avatar', defaultMessage: 'Upload profile picture' },
});
+const nullIfMissing = path => path.endsWith('missing.png') ? null : path;
+
export const Profile = () => {
const account = useAppSelector(state => state.getIn(['accounts', me]));
const [displayName, setDisplayName] = useState(account.get('display_name'));
@@ -61,8 +63,8 @@ export const Profile = () => {
setHeader(e.target?.files?.[0]);
}, [setHeader]);
- const avatarPreview = useMemo(() => avatar ? URL.createObjectURL(avatar) : account.get('avatar'), [avatar, account]);
- const headerPreview = useMemo(() => header ? URL.createObjectURL(header) : account.get('header'), [header, account]);
+ const avatarPreview = useMemo(() => avatar ? URL.createObjectURL(avatar) : nullIfMissing(account.get('avatar')), [avatar, account]);
+ const headerPreview = useMemo(() => header ? URL.createObjectURL(header) : nullIfMissing(account.get('header')), [header, account]);
const handleSubmit = useCallback(() => {
setIsSaving(true);
diff --git a/app/javascript/mastodon/locales/af.json b/app/javascript/mastodon/locales/af.json
index d3cc40c60c..6c37cdf5ca 100644
--- a/app/javascript/mastodon/locales/af.json
+++ b/app/javascript/mastodon/locales/af.json
@@ -30,7 +30,6 @@
"account.followers.empty": "Hierdie gebruiker het nog nie volgers nie.",
"account.following": "Volg",
"account.follows.empty": "Die gebruiker volg nog niemand.",
- "account.follows_you": "Volg jou",
"account.go_to_profile": "Gaan na profiel",
"account.hide_reblogs": "Versteek plasings wat deur @{name} aangestuur is",
"account.joined_short": "Aangesluit",
diff --git a/app/javascript/mastodon/locales/an.json b/app/javascript/mastodon/locales/an.json
index b2134551bf..23899b1007 100644
--- a/app/javascript/mastodon/locales/an.json
+++ b/app/javascript/mastodon/locales/an.json
@@ -35,7 +35,6 @@
"account.following": "Seguindo",
"account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Seguindo}}",
"account.follows.empty": "Este usuario encara no sigue a dengún.",
- "account.follows_you": "Te sigue",
"account.go_to_profile": "Ir ta lo perfil",
"account.hide_reblogs": "Amagar retutz de @{name}",
"account.joined_short": "S'unió",
diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json
index 979bc9a701..c0d07cc648 100644
--- a/app/javascript/mastodon/locales/ar.json
+++ b/app/javascript/mastodon/locales/ar.json
@@ -37,7 +37,6 @@
"account.following": "الاشتراكات",
"account.following_counter": "{count, plural, zero{لا يُتابِع أحدًا} one {يُتابِعُ واحد} two{يُتابِعُ اِثنان} few{يُتابِعُ {counter}} many{يُتابِعُ {counter}} other {يُتابِعُ {counter}}}",
"account.follows.empty": "لا يُتابع هذا المُستخدمُ أيَّ أحدٍ حتى الآن.",
- "account.follows_you": "يُتابِعُك",
"account.go_to_profile": "اذهب إلى الملف الشخصي",
"account.hide_reblogs": "إخفاء المعاد نشرها مِن @{name}",
"account.in_memoriam": "في الذكرى.",
diff --git a/app/javascript/mastodon/locales/ast.json b/app/javascript/mastodon/locales/ast.json
index 98f622c293..c4238edcd4 100644
--- a/app/javascript/mastodon/locales/ast.json
+++ b/app/javascript/mastodon/locales/ast.json
@@ -38,7 +38,6 @@
"account.following": "Siguiendo",
"account.following_counter": "{count, plural,one {Sigue a {counter}} other {Sigue a {counter}}}",
"account.follows.empty": "Esti perfil nun sigue a naide.",
- "account.follows_you": "Síguete",
"account.go_to_profile": "Dir al perfil",
"account.hide_reblogs": "Anubrir los artículos compartíos de @{name}",
"account.in_memoriam": "N'alcordanza.",
diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json
index f94a8b79ff..773af40343 100644
--- a/app/javascript/mastodon/locales/be.json
+++ b/app/javascript/mastodon/locales/be.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "Няма допісаў",
"account.featured_tags.title": "Тэгі, выбраныя {name}",
"account.follow": "Падпісацца",
+ "account.follow_back": "Падпісацца ў адказ",
"account.followers": "Падпісчыкі",
"account.followers.empty": "Ніхто пакуль не падпісаны на гэтага карыстальніка.",
"account.followers_counter": "{count, plural, one {{counter} падпісчык} few {{counter} падпісчыкі} many {{counter} падпісчыкаў} other {{counter} падпісчыка}}",
"account.following": "Падпіскі",
"account.following_counter": "{count, plural, one {{counter} падпіска} few {{counter} падпіскі} many {{counter} падпісак} other {{counter} падпіскі}}",
"account.follows.empty": "Карыстальнік ні на каго не падпісаны.",
- "account.follows_you": "Падпісаны на вас",
"account.go_to_profile": "Перайсці да профілю",
"account.hide_reblogs": "Схаваць пашырэнні ад @{name}",
"account.in_memoriam": "У памяць.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Не апавяшчаць",
"account.mute_short": "Ігнараваць",
"account.muted": "Ігнаруецца",
+ "account.mutual": "Узаемныя",
"account.no_bio": "Апісанне адсутнічае.",
"account.open_original_page": "Адкрыць арыгінальную старонку",
"account.posts": "Допісы",
diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json
index f57d868c7a..8a6d6040d6 100644
--- a/app/javascript/mastodon/locales/bg.json
+++ b/app/javascript/mastodon/locales/bg.json
@@ -38,7 +38,6 @@
"account.following": "Последвано",
"account.following_counter": "{count, plural, one {{counter} последван} other {{counter} последвани}}",
"account.follows.empty": "Потребителят още никого не следва.",
- "account.follows_you": "Следва ви",
"account.go_to_profile": "Към профила",
"account.hide_reblogs": "Скриване на подсилвания от @{name}",
"account.in_memoriam": "В памет на.",
diff --git a/app/javascript/mastodon/locales/bn.json b/app/javascript/mastodon/locales/bn.json
index b6e4fbb965..fe3d2a627c 100644
--- a/app/javascript/mastodon/locales/bn.json
+++ b/app/javascript/mastodon/locales/bn.json
@@ -37,7 +37,6 @@
"account.following": "অনুসরণ করা হচ্ছে",
"account.following_counter": "{count, plural,one {{counter} জনকে অনুসরণ} other {{counter} জনকে অনুসরণ}}",
"account.follows.empty": "এই সদস্য কাউকে এখনো ফলো করেন না.",
- "account.follows_you": "আপনাকে ফলো করে",
"account.go_to_profile": "প্রোফাইলে যান",
"account.hide_reblogs": "@{name}'র সমর্থনগুলি লুকিয়ে ফেলুন",
"account.in_memoriam": "স্মৃতিতে.",
diff --git a/app/javascript/mastodon/locales/br.json b/app/javascript/mastodon/locales/br.json
index 39cd732419..bea8b27b77 100644
--- a/app/javascript/mastodon/locales/br.json
+++ b/app/javascript/mastodon/locales/br.json
@@ -36,7 +36,6 @@
"account.following": "Koumanantoù",
"account.following_counter": "{count, plural, one{{counter} C'houmanant} two{{counter} Goumanant} other {{counter} a Goumanant}}",
"account.follows.empty": "An implijer·ez-mañ na heul den ebet.",
- "account.follows_you": "Ho heuilh",
"account.go_to_profile": "Gwelet ar profil",
"account.hide_reblogs": "Kuzh skignadennoù gant @{name}",
"account.joined_short": "Amañ abaoe",
diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json
index 5ae49325f6..86f1fb4765 100644
--- a/app/javascript/mastodon/locales/ca.json
+++ b/app/javascript/mastodon/locales/ca.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "No hi ha tuts",
"account.featured_tags.title": "etiquetes destacades de {name}",
"account.follow": "Segueix",
+ "account.follow_back": "Segueix",
"account.followers": "Seguidors",
"account.followers.empty": "A aquest usuari encara no el segueix ningú.",
"account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} Seguidors}}",
"account.following": "Seguint",
"account.following_counter": "{count, plural, other {{counter} Seguint-ne}}",
"account.follows.empty": "Aquest usuari encara no segueix ningú.",
- "account.follows_you": "Et segueix",
"account.go_to_profile": "Vés al perfil",
"account.hide_reblogs": "Amaga els impulsos de @{name}",
"account.in_memoriam": "En Memòria.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Silencia les notificacions",
"account.mute_short": "Silencia",
"account.muted": "Silenciat",
+ "account.mutual": "Mutu",
"account.no_bio": "No s'ha proporcionat cap descripció.",
"account.open_original_page": "Obre la pàgina original",
"account.posts": "Tuts",
diff --git a/app/javascript/mastodon/locales/ckb.json b/app/javascript/mastodon/locales/ckb.json
index 7e96418329..f1cafd1ac4 100644
--- a/app/javascript/mastodon/locales/ckb.json
+++ b/app/javascript/mastodon/locales/ckb.json
@@ -36,7 +36,6 @@
"account.following": "بەدوادا",
"account.following_counter": "{count, plural, one {{counter} شوێنکەوتوو} other {{counter} شوێنکەوتوو}}",
"account.follows.empty": "ئەم بەکارهێنەرە تا ئێستا شوێن کەس نەکەوتووە.",
- "account.follows_you": "شوێنت دەکەوێت",
"account.go_to_profile": "بڕۆ بۆ پڕۆفایلی",
"account.hide_reblogs": "داشاردنی بووستەکان لە @{name}",
"account.joined_short": "بەشداری کردووە",
diff --git a/app/javascript/mastodon/locales/co.json b/app/javascript/mastodon/locales/co.json
index d4bb2f82ba..9f90c3d211 100644
--- a/app/javascript/mastodon/locales/co.json
+++ b/app/javascript/mastodon/locales/co.json
@@ -19,7 +19,6 @@
"account.followers_counter": "{count, plural, one {{counter} Abbunatu} other {{counter} Abbunati}}",
"account.following_counter": "{count, plural, one {{counter} Abbunamentu} other {{counter} Abbunamenti}}",
"account.follows.empty": "St'utilizatore ùn seguita nisunu.",
- "account.follows_you": "Vi seguita",
"account.hide_reblogs": "Piattà spartere da @{name}",
"account.link_verified_on": "A prupietà di stu ligame hè stata verificata u {date}",
"account.locked_info": "U statutu di vita privata di u contu hè chjosu. U pruprietariu esamina manualmente e dumande d'abbunamentu.",
diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json
index 16bf5020c5..e18cabcec1 100644
--- a/app/javascript/mastodon/locales/cs.json
+++ b/app/javascript/mastodon/locales/cs.json
@@ -38,7 +38,6 @@
"account.following": "Sledujete",
"account.following_counter": "{count, plural, one {{counter} Sledovaný} few {{counter} Sledovaní} many {{counter} Sledovaných} other {{counter} Sledovaných}}",
"account.follows.empty": "Tento uživatel zatím nikoho nesleduje.",
- "account.follows_you": "Sleduje vás",
"account.go_to_profile": "Přejít na profil",
"account.hide_reblogs": "Skrýt boosty od @{name}",
"account.in_memoriam": "In Memoriam.",
diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json
index 4ecf48735e..17133631a5 100644
--- a/app/javascript/mastodon/locales/cy.json
+++ b/app/javascript/mastodon/locales/cy.json
@@ -37,7 +37,6 @@
"account.following": "Yn dilyn",
"account.following_counter": "{count, plural, one {Yn dilyn: {counter}} other {Yn dilyn: {counter}}}",
"account.follows.empty": "Nid yw'r defnyddiwr hwn yn dilyn unrhyw un eto.",
- "account.follows_you": "Yn eich dilyn chi",
"account.go_to_profile": "Mynd i'r proffil",
"account.hide_reblogs": "Cuddio hybiau gan @{name}",
"account.in_memoriam": "Er Cof.",
diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json
index 04fc43734f..3bc830ad27 100644
--- a/app/javascript/mastodon/locales/da.json
+++ b/app/javascript/mastodon/locales/da.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "Ingen indlæg",
"account.featured_tags.title": "{name}s fremhævede hashtags",
"account.follow": "Følg",
+ "account.follow_back": "Følg tilbage",
"account.followers": "Følgere",
"account.followers.empty": "Ingen følger denne bruger endnu.",
"account.followers_counter": "{count, plural, one {{counter} Følger} other {{counter} Følgere}}",
"account.following": "Følger",
"account.following_counter": "{count, plural, one {{counter} Følges} other {{counter} Følges}}",
"account.follows.empty": "Denne bruger følger ikke nogen endnu.",
- "account.follows_you": "Følger dig",
"account.go_to_profile": "Gå til profil",
"account.hide_reblogs": "Skjul boosts fra @{name}",
"account.in_memoriam": "Til minde om.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Slå lyden fra for notifikationer",
"account.mute_short": "Skjul (mute)",
"account.muted": "Skjult (muted)",
+ "account.mutual": "Fælles",
"account.no_bio": "Ingen beskrivelse til rådighed.",
"account.open_original_page": "Åbn oprindelig side",
"account.posts": "Indlæg",
diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json
index cf545e48c1..ddcbc58cee 100644
--- a/app/javascript/mastodon/locales/de.json
+++ b/app/javascript/mastodon/locales/de.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "Keine Beiträge",
"account.featured_tags.title": "Von {name} vorgestellte Hashtags",
"account.follow": "Folgen",
+ "account.follow_back": "Ebenfalls folgen",
"account.followers": "Follower",
"account.followers.empty": "Diesem Profil folgt noch niemand.",
"account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Follower}}",
"account.following": "Folge ich",
"account.following_counter": "{count, plural, one {{counter} Folge ich} other {{counter} Folge ich}}",
"account.follows.empty": "Dieses Profil folgt noch niemandem.",
- "account.follows_you": "Folgt dir",
"account.go_to_profile": "Profil aufrufen",
"account.hide_reblogs": "Geteilte Beiträge von @{name} ausblenden",
"account.in_memoriam": "Zum Andenken.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Benachrichtigungen stummschalten",
"account.mute_short": "Stummschalten",
"account.muted": "Stummgeschaltet",
+ "account.mutual": "Gegenseitig",
"account.no_bio": "Keine Beschreibung verfügbar.",
"account.open_original_page": "Ursprüngliche Seite öffnen",
"account.posts": "Beiträge",
diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json
index 02ce7120a3..83a986ba4e 100644
--- a/app/javascript/mastodon/locales/el.json
+++ b/app/javascript/mastodon/locales/el.json
@@ -36,7 +36,6 @@
"account.following": "Ακολουθείτε",
"account.following_counter": "{count, plural, one {{counter} Ακολουθεί} other {{counter} Ακολουθούν}}",
"account.follows.empty": "Αυτός ο χρήστης δεν ακολουθεί κανέναν ακόμα.",
- "account.follows_you": "Σε ακολουθεί",
"account.go_to_profile": "Μετάβαση στο προφίλ",
"account.hide_reblogs": "Απόκρυψη ενισχύσεων από @{name}",
"account.in_memoriam": "Εις μνήμην.",
diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json
index 1a67fecb60..37e5efa5ba 100644
--- a/app/javascript/mastodon/locales/en-GB.json
+++ b/app/javascript/mastodon/locales/en-GB.json
@@ -38,7 +38,6 @@
"account.following": "Following",
"account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
"account.follows.empty": "This user doesn't follow anyone yet.",
- "account.follows_you": "Follows you",
"account.go_to_profile": "Go to profile",
"account.hide_reblogs": "Hide boosts from @{name}",
"account.in_memoriam": "In Memoriam.",
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 05d7d16564..be7209d04a 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "No posts",
"account.featured_tags.title": "{name}'s featured hashtags",
"account.follow": "Follow",
+ "account.follow_back": "Follow back",
"account.followers": "Followers",
"account.followers.empty": "No one follows this user yet.",
"account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
"account.following": "Following",
"account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
"account.follows.empty": "This user doesn't follow anyone yet.",
- "account.follows_you": "Follows you",
"account.go_to_profile": "Go to profile",
"account.hide_reblogs": "Hide boosts from @{name}",
"account.in_memoriam": "In Memoriam.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Mute notifications",
"account.mute_short": "Mute",
"account.muted": "Muted",
+ "account.mutual": "Mutual",
"account.no_bio": "No description provided.",
"account.open_original_page": "Open original page",
"account.posts": "Posts",
diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json
index 2678d83a50..4daa699dcf 100644
--- a/app/javascript/mastodon/locales/eo.json
+++ b/app/javascript/mastodon/locales/eo.json
@@ -38,7 +38,6 @@
"account.following": "Sekvatoj",
"account.following_counter": "{count, plural, one {{counter} Sekvato} other {{counter} Sekvatoj}}",
"account.follows.empty": "La uzanto ankoraŭ ne sekvas iun ajn.",
- "account.follows_you": "Sekvas vin",
"account.go_to_profile": "Iri al profilo",
"account.hide_reblogs": "Kaŝi diskonigojn de @{name}",
"account.in_memoriam": "Memore.",
diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json
index 4573f4ab9d..eb6c0f3e4f 100644
--- a/app/javascript/mastodon/locales/es-AR.json
+++ b/app/javascript/mastodon/locales/es-AR.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "Sin mensajes",
"account.featured_tags.title": "Etiquetas destacadas de {name}",
"account.follow": "Seguir",
+ "account.follow_back": "Seguir también",
"account.followers": "Seguidores",
"account.followers.empty": "Todavía nadie sigue a este usuario.",
"account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}",
"account.following": "Siguiendo",
"account.following_counter": "{count, plural, other {Siguiendo a {counter}}}",
"account.follows.empty": "Todavía este usuario no sigue a nadie.",
- "account.follows_you": "Te sigue",
"account.go_to_profile": "Ir al perfil",
"account.hide_reblogs": "Ocultar adhesiones de @{name}",
"account.in_memoriam": "Cuenta conmemorativa.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Silenciar notificaciones",
"account.mute_short": "Silenciar",
"account.muted": "Silenciado",
+ "account.mutual": "Mutuo",
"account.no_bio": "Sin descripción provista.",
"account.open_original_page": "Abrir página original",
"account.posts": "Mensajes",
diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json
index 0d26afef23..fab5f6ec96 100644
--- a/app/javascript/mastodon/locales/es-MX.json
+++ b/app/javascript/mastodon/locales/es-MX.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "Sin publicaciones",
"account.featured_tags.title": "Etiquetas destacadas de {name}",
"account.follow": "Seguir",
+ "account.follow_back": "Seguir también",
"account.followers": "Seguidores",
"account.followers.empty": "Todavía nadie sigue a este usuario.",
"account.followers_counter": "{count, plural, one {{counter} Seguidor} other {{counter} Seguidores}}",
"account.following": "Siguiendo",
"account.following_counter": "{count, plural, other {{counter} Siguiendo}}",
"account.follows.empty": "Este usuario todavía no sigue a nadie.",
- "account.follows_you": "Te sigue",
"account.go_to_profile": "Ir al perfil",
"account.hide_reblogs": "Ocultar retoots de @{name}",
"account.in_memoriam": "En memoria.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Silenciar notificaciones",
"account.mute_short": "Silenciar",
"account.muted": "Silenciado",
+ "account.mutual": "Mutuo",
"account.no_bio": "Sin biografía.",
"account.open_original_page": "Abrir página original",
"account.posts": "Publicaciones",
diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json
index bbc8bcc759..0d6149f5f5 100644
--- a/app/javascript/mastodon/locales/es.json
+++ b/app/javascript/mastodon/locales/es.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "Sin publicaciones",
"account.featured_tags.title": "Etiquetas destacadas de {name}",
"account.follow": "Seguir",
+ "account.follow_back": "Seguir también",
"account.followers": "Seguidores",
"account.followers.empty": "Todavía nadie sigue a este usuario.",
"account.followers_counter": "{count, plural, one {{counter} Seguidor} other {{counter} Seguidores}}",
"account.following": "Siguiendo",
"account.following_counter": "{count, plural, other {Siguiendo a {counter}}}",
"account.follows.empty": "Este usuario todavía no sigue a nadie.",
- "account.follows_you": "Te sigue",
"account.go_to_profile": "Ir al perfil",
"account.hide_reblogs": "Ocultar impulsos de @{name}",
"account.in_memoriam": "Cuenta conmemorativa.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Silenciar notificaciones",
"account.mute_short": "Silenciar",
"account.muted": "Silenciado",
+ "account.mutual": "Mutuo",
"account.no_bio": "Sin biografía.",
"account.open_original_page": "Abrir página original",
"account.posts": "Publicaciones",
diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json
index a41aa02f80..c397334109 100644
--- a/app/javascript/mastodon/locales/et.json
+++ b/app/javascript/mastodon/locales/et.json
@@ -38,7 +38,6 @@
"account.following": "Jälgib",
"account.following_counter": "{count, plural, one {{counter} jälgitav} other {{counter} jälgitavat}}",
"account.follows.empty": "See kasutaja ei jälgi veel kedagi.",
- "account.follows_you": "Jälgib sind",
"account.go_to_profile": "Mine profiilile",
"account.hide_reblogs": "Peida @{name} jagamised",
"account.in_memoriam": "In Memoriam.",
diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json
index 26ed7add1e..c75f697e4c 100644
--- a/app/javascript/mastodon/locales/eu.json
+++ b/app/javascript/mastodon/locales/eu.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "Bidalketarik ez",
"account.featured_tags.title": "{name} erabiltzailearen nabarmendutako traolak",
"account.follow": "Jarraitu",
+ "account.follow_back": "Jarraitu bueltan",
"account.followers": "Jarraitzaileak",
"account.followers.empty": "Ez du inork erabiltzaile hau jarraitzen oraindik.",
"account.followers_counter": "{count, plural, one {Jarraitzaile {counter}} other {{counter} jarraitzaile}}",
"account.following": "Jarraitzen",
"account.following_counter": "{count, plural, one {{counter} jarraitzen} other {{counter} jarraitzen}}",
"account.follows.empty": "Erabiltzaile honek ez du inor jarraitzen oraindik.",
- "account.follows_you": "Jarraitzen dizu",
"account.go_to_profile": "Joan profilera",
"account.hide_reblogs": "Ezkutatu @{name} erabiltzailearen bultzadak",
"account.in_memoriam": "Oroimenezkoa.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Mututu jakinarazpenak",
"account.mute_short": "Mututu",
"account.muted": "Mutututa",
+ "account.mutual": "Elkarrekikoa",
"account.no_bio": "Ez da deskribapenik eman.",
"account.open_original_page": "Ireki jatorrizko orria",
"account.posts": "Bidalketa",
diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json
index 8e8930bfea..a93d0f6617 100644
--- a/app/javascript/mastodon/locales/fa.json
+++ b/app/javascript/mastodon/locales/fa.json
@@ -38,7 +38,6 @@
"account.following": "پی میگیرید",
"account.following_counter": "{count, plural, one {{counter} پیگرفته} other {{counter} پیگرفته}}",
"account.follows.empty": "این کاربر هنوز پیگیر کسی نیست.",
- "account.follows_you": "پیگیرتان است",
"account.go_to_profile": "رفتن به نمایه",
"account.hide_reblogs": "نهفتن تقویتهای @{name}",
"account.in_memoriam": "به یادبود.",
diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json
index e99de4d03a..dab7eac1e9 100644
--- a/app/javascript/mastodon/locales/fi.json
+++ b/app/javascript/mastodon/locales/fi.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "Ei julkaisuja",
"account.featured_tags.title": "Käyttäjän {name} esillä pidettävät aihetunnisteet",
"account.follow": "Seuraa",
+ "account.follow_back": "Seuraa takaisin",
"account.followers": "Seuraajat",
"account.followers.empty": "Kukaan ei seuraa tätä käyttäjää vielä.",
"account.followers_counter": "{count, plural, one {{counter} seuraaja} other {{counter} seuraajaa}}",
"account.following": "Seuratut",
"account.following_counter": "{count, plural, one {{counter} seurattu} other {{counter} seurattua}}",
"account.follows.empty": "Tämä käyttäjä ei vielä seuraa ketään.",
- "account.follows_you": "Seuraa sinua",
"account.go_to_profile": "Avaa profiili",
"account.hide_reblogs": "Piilota käyttäjän @{name} tehostukset",
"account.in_memoriam": "Muistoissamme.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Mykistä ilmoitukset",
"account.mute_short": "Mykistä",
"account.muted": "Mykistetty",
+ "account.mutual": "Molemmat",
"account.no_bio": "Kuvausta ei ole annettu.",
"account.open_original_page": "Avaa alkuperäinen sivu",
"account.posts": "Julkaisut",
diff --git a/app/javascript/mastodon/locales/fil.json b/app/javascript/mastodon/locales/fil.json
index bc76358875..ec27c8f60e 100644
--- a/app/javascript/mastodon/locales/fil.json
+++ b/app/javascript/mastodon/locales/fil.json
@@ -31,7 +31,6 @@
"account.followers.empty": "Wala pang sumusunod sa tagagamit na ito.",
"account.following": "Sinusundan",
"account.follows.empty": "Wala pang sinusundan ang tagagamit na ito.",
- "account.follows_you": "Sinusunod ka",
"account.go_to_profile": "Pumunta sa profile",
"account.hide_reblogs": "Itago ang mga pagpapalakas mula sa {name}",
"account.in_memoriam": "Sa Alaala Ni.",
diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json
index 45fafd15dc..9259f20154 100644
--- a/app/javascript/mastodon/locales/fo.json
+++ b/app/javascript/mastodon/locales/fo.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "Einki uppslag",
"account.featured_tags.title": "Tvíkrossar hjá {name}",
"account.follow": "Fylg",
+ "account.follow_back": "Fylg aftur",
"account.followers": "Fylgjarar",
"account.followers.empty": "Ongar fylgjarar enn.",
"account.followers_counter": "{count, plural, one {{counter} Fylgjari} other {{counter} Fylgjarar}}",
"account.following": "Fylgir",
"account.following_counter": "{count, plural, one {{counter} fylgir} other {{counter} fylgja}}",
"account.follows.empty": "Hesin brúkari fylgir ongum enn.",
- "account.follows_you": "Fylgir tær",
"account.go_to_profile": "Far til vanga",
"account.hide_reblogs": "Fjal lyft frá @{name}",
"account.in_memoriam": "In memoriam.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Sløkk fráboðanir",
"account.mute_short": "Doyv",
"account.muted": "Sløkt/ur",
+ "account.mutual": "Sínamillum",
"account.no_bio": "Lýsing vantar.",
"account.open_original_page": "Opna upprunasíðuna",
"account.posts": "Uppsløg",
diff --git a/app/javascript/mastodon/locales/fr-QC.json b/app/javascript/mastodon/locales/fr-QC.json
index e2067cc460..f2d99412d2 100644
--- a/app/javascript/mastodon/locales/fr-QC.json
+++ b/app/javascript/mastodon/locales/fr-QC.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "Aucune publication",
"account.featured_tags.title": "Hashtags inclus de {name}",
"account.follow": "Suivre",
+ "account.follow_back": "S'abonner en retour",
"account.followers": "abonné·e·s",
"account.followers.empty": "Personne ne suit ce compte pour l'instant.",
"account.followers_counter": "{count, plural, one {{counter} Abonné·e} other {{counter} Abonné·e·s}}",
"account.following": "Abonné·e",
"account.following_counter": "{count, plural, one {{counter} Abonnement} other {{counter} Abonnements}}",
"account.follows.empty": "Ce compte ne suit personne présentement.",
- "account.follows_you": "Vous suit",
"account.go_to_profile": "Voir ce profil",
"account.hide_reblogs": "Masquer les boosts de @{name}",
"account.in_memoriam": "En souvenir de",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Rendre les notifications muettes",
"account.mute_short": "Rendre muet",
"account.muted": "Masqué·e",
+ "account.mutual": "Mutuel",
"account.no_bio": "Description manquante.",
"account.open_original_page": "Ouvrir la page d'origine",
"account.posts": "Publications",
diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json
index 7db4bf7bc4..774702f98c 100644
--- a/app/javascript/mastodon/locales/fr.json
+++ b/app/javascript/mastodon/locales/fr.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "Aucun message",
"account.featured_tags.title": "Les hashtags en vedette de {name}",
"account.follow": "Suivre",
+ "account.follow_back": "S'abonner en retour",
"account.followers": "Abonné·e·s",
"account.followers.empty": "Personne ne suit cet·te utilisateur·rice pour l’instant.",
"account.followers_counter": "{count, plural, one {{counter} Abonné·e} other {{counter} Abonné·e·s}}",
"account.following": "Abonnements",
"account.following_counter": "{count, plural, one {{counter} Abonnement} other {{counter} Abonnements}}",
"account.follows.empty": "Cet·te utilisateur·rice ne suit personne pour l’instant.",
- "account.follows_you": "Vous suit",
"account.go_to_profile": "Aller au profil",
"account.hide_reblogs": "Masquer les partages de @{name}",
"account.in_memoriam": "En mémoire de.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Désactiver les alertes",
"account.mute_short": "Mettre en sourdine",
"account.muted": "Masqué·e",
+ "account.mutual": "Mutuel",
"account.no_bio": "Aucune description fournie.",
"account.open_original_page": "Ouvrir la page d'origine",
"account.posts": "Messages",
diff --git a/app/javascript/mastodon/locales/fy.json b/app/javascript/mastodon/locales/fy.json
index 2ec4a53bfd..ea42ef91c0 100644
--- a/app/javascript/mastodon/locales/fy.json
+++ b/app/javascript/mastodon/locales/fy.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "Gjin berjochten",
"account.featured_tags.title": "Utljochte hashtags fan {name}",
"account.follow": "Folgje",
+ "account.follow_back": "Weromfolgje",
"account.followers": "Folgers",
"account.followers.empty": "Noch net ien folget dizze brûker.",
"account.followers_counter": "{count, plural, one {{counter} folger} other {{counter} folgers}}",
"account.following": "Folgjend",
"account.following_counter": "{count, plural, one {{counter} folgjend} other {{counter} folgjend}}",
"account.follows.empty": "Dizze brûker folget noch net ien.",
- "account.follows_you": "Folget jo",
"account.go_to_profile": "Gean nei profyl",
"account.hide_reblogs": "Boosts fan @{name} ferstopje",
"account.in_memoriam": "Yn memoriam.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Meldingen negearje",
"account.mute_short": "Negearje",
"account.muted": "Negearre",
+ "account.mutual": "Jimme folgje inoar",
"account.no_bio": "Gjin omskriuwing opjûn.",
"account.open_original_page": "Orizjinele side iepenje",
"account.posts": "Berjochten",
diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json
index ee6b44c884..f0f9c55549 100644
--- a/app/javascript/mastodon/locales/ga.json
+++ b/app/javascript/mastodon/locales/ga.json
@@ -35,7 +35,6 @@
"account.following": "Ag leanúint",
"account.following_counter": "{count, plural, one {Ag leanúint cúntas amháin} other {Ag leanúint {counter} cúntas}}",
"account.follows.empty": "Ní leanann an t-úsáideoir seo duine ar bith fós.",
- "account.follows_you": "Do do leanúint",
"account.go_to_profile": "Téigh go dtí próifíl",
"account.hide_reblogs": "Folaigh moltaí ó @{name}",
"account.in_memoriam": "Cuimhneachán.",
diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json
index 91333c1a0a..11d83d6ceb 100644
--- a/app/javascript/mastodon/locales/gd.json
+++ b/app/javascript/mastodon/locales/gd.json
@@ -37,7 +37,6 @@
"account.following": "A’ leantainn",
"account.following_counter": "{count, plural, one {A’ leantainn {counter}} two {A’ leantainn {counter}} few {A’ leantainn {counter}} other {A’ leantainn {counter}}}",
"account.follows.empty": "Chan eil an cleachdaiche seo a’ leantainn neach sam bith fhathast.",
- "account.follows_you": "Gad leantainn",
"account.go_to_profile": "Tadhail air a’ phròifil",
"account.hide_reblogs": "Falaich na brosnachaidhean o @{name}",
"account.in_memoriam": "Mar chuimhneachan.",
diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json
index 08d7d49776..bfd20a4673 100644
--- a/app/javascript/mastodon/locales/gl.json
+++ b/app/javascript/mastodon/locales/gl.json
@@ -38,7 +38,6 @@
"account.following": "Seguindo",
"account.following_counter": "{count, plural, one {{counter} Seguindo} other {{counter} Seguindo}}",
"account.follows.empty": "Esta usuaria aínda non segue a ninguén.",
- "account.follows_you": "Séguete",
"account.go_to_profile": "Ir ao perfil",
"account.hide_reblogs": "Agochar promocións de @{name}",
"account.in_memoriam": "Lembranzas.",
diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json
index dda92c5c9a..658e16b856 100644
--- a/app/javascript/mastodon/locales/he.json
+++ b/app/javascript/mastodon/locales/he.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "אין חצרוצים",
"account.featured_tags.title": "התגיות המועדפות של {name}",
"account.follow": "לעקוב",
+ "account.follow_back": "החזרת עוקב",
"account.followers": "עוקבים",
"account.followers.empty": "אף אחד לא עוקב אחר המשתמש הזה עדיין.",
"account.followers_counter": "{count, plural,one {עוקב אחד} other {{counter} עוקבים}}",
"account.following": "נעקבים",
"account.following_counter": "{count, plural,one {עוקב אחרי {counter}}other {עוקב אחרי {counter}}}",
"account.follows.empty": "משתמש זה עדיין לא עוקב אחרי אף אחד.",
- "account.follows_you": "במעקב אחריך",
"account.go_to_profile": "מעבר לפרופיל",
"account.hide_reblogs": "להסתיר הידהודים מאת @{name}",
"account.in_memoriam": "פרופיל זכרון.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "השתקת התראות",
"account.mute_short": "השתקה",
"account.muted": "מושתק",
+ "account.mutual": "הדדיים",
"account.no_bio": "לא סופק תיאור.",
"account.open_original_page": "לפתיחת העמוד המקורי",
"account.posts": "פוסטים",
diff --git a/app/javascript/mastodon/locales/hi.json b/app/javascript/mastodon/locales/hi.json
index 387941b5e4..76d7c1d381 100644
--- a/app/javascript/mastodon/locales/hi.json
+++ b/app/javascript/mastodon/locales/hi.json
@@ -37,7 +37,6 @@
"account.following": "फॉलोइंग",
"account.following_counter": "{count, plural, one {{counter} निम्नलिखित} other {{counter} निम्नलिखित}}",
"account.follows.empty": "यह यूज़र् अभी तक किसी को फॉलो नहीं करता है।",
- "account.follows_you": "आपको फॉलो करता है",
"account.go_to_profile": "प्रोफाइल में जाएँ",
"account.hide_reblogs": "@{name} के बूस्ट छुपाएं",
"account.in_memoriam": "याद में",
diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json
index 6da7d6cd8d..654991a0f9 100644
--- a/app/javascript/mastodon/locales/hr.json
+++ b/app/javascript/mastodon/locales/hr.json
@@ -28,7 +28,6 @@
"account.following": "Pratim",
"account.following_counter": "{count, plural, one {{counter} praćeni} few{{counter} praćena} other {{counter} praćenih}}",
"account.follows.empty": "Korisnik/ca još ne prati nikoga.",
- "account.follows_you": "Prati te",
"account.go_to_profile": "Idi na profil",
"account.hide_reblogs": "Sakrij boostove od @{name}",
"account.in_memoriam": "U sjećanje.",
diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json
index 386b15811a..0d50e36feb 100644
--- a/app/javascript/mastodon/locales/hu.json
+++ b/app/javascript/mastodon/locales/hu.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "Nincs bejegyzés",
"account.featured_tags.title": "{name} kiemelt hashtagjei",
"account.follow": "Követés",
+ "account.follow_back": "Viszontkövetés",
"account.followers": "Követő",
"account.followers.empty": "Ezt a felhasználót még senki sem követi.",
"account.followers_counter": "{count, plural, one {{counter} Követő} other {{counter} Követő}}",
"account.following": "Követve",
"account.following_counter": "{count, plural, one {{counter} Követett} other {{counter} Követett}}",
"account.follows.empty": "Ez a felhasználó még senkit sem követ.",
- "account.follows_you": "Követ téged",
"account.go_to_profile": "Ugrás a profilhoz",
"account.hide_reblogs": "@{name} megtolásainak elrejtése",
"account.in_memoriam": "Emlékünkben.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Értesítések némítása",
"account.mute_short": "Némítás",
"account.muted": "Némítva",
+ "account.mutual": "Kölcsönös",
"account.no_bio": "Leírás nincs megadva.",
"account.open_original_page": "Eredeti oldal megnyitása",
"account.posts": "Bejegyzések",
diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json
index f2548c7d3a..835105218d 100644
--- a/app/javascript/mastodon/locales/hy.json
+++ b/app/javascript/mastodon/locales/hy.json
@@ -32,7 +32,6 @@
"account.following": "Հետեւած",
"account.following_counter": "{count, plural, one {{counter} Հետեւած} other {{counter} Հետեւած}}",
"account.follows.empty": "Այս օգտատէրը դեռ ոչ մէկի չի հետեւում։",
- "account.follows_you": "Հետեւում է քեզ",
"account.go_to_profile": "Գնալ անձնական հաշիւ",
"account.hide_reblogs": "Թաքցնել @{name}֊ի տարածածները",
"account.joined_short": "Միացել է",
diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json
index 8ecf36125d..5af20a97f7 100644
--- a/app/javascript/mastodon/locales/id.json
+++ b/app/javascript/mastodon/locales/id.json
@@ -37,7 +37,6 @@
"account.following": "Mengikuti",
"account.following_counter": "{count, plural, other {{counter} Mengikuti}}",
"account.follows.empty": "Pengguna ini belum mengikuti siapa pun.",
- "account.follows_you": "Mengikuti Anda",
"account.go_to_profile": "Buka profil",
"account.hide_reblogs": "Sembunyikan boosts dari @{name}",
"account.in_memoriam": "Mengenang.",
diff --git a/app/javascript/mastodon/locales/ie.json b/app/javascript/mastodon/locales/ie.json
index 966b272720..a7cf1caab4 100644
--- a/app/javascript/mastodon/locales/ie.json
+++ b/app/javascript/mastodon/locales/ie.json
@@ -38,7 +38,6 @@
"account.following": "Sequent",
"account.following_counter": "{count, plural, one {{counter} Sequent} other {{counter} Sequent}}",
"account.follows.empty": "Ti-ci usator ancor ne seque quemcunc.",
- "account.follows_you": "Seque te",
"account.go_to_profile": "Ear a profil",
"account.hide_reblogs": "Celar boosts de @{name}",
"account.in_memoriam": "In Memoriam.",
@@ -140,6 +139,7 @@
"compose.published.open": "Aperter",
"compose.saved.body": "Posta conservat.",
"compose_form.direct_message_warning_learn_more": "Aprender plu",
+ "compose_form.encryption_warning": "Postas in Mastodon ne es inciffrat de comense a fine. Ne posta quelcunc information sensitiv per Mastodon.",
"compose_form.hashtag_warning": "Ti-ci posta ne va esser listat sur quelcunc hashtag pro que it ne es public. Solmen public postas posse esser serchat per hashtag.",
"compose_form.lock_disclaimer": "Tui conto ne es {locked}. Quicunc posse sequer te por vider tui postas solmen por sequitores.",
"compose_form.lock_disclaimer.lock": "cludet",
@@ -197,6 +197,7 @@
"directory.new_arrivals": "Nov arivantes",
"directory.recently_active": "Recentmen activ",
"disabled_account_banner.account_settings": "Parametres del conto",
+ "disabled_account_banner.text": "Tui conto {disabledAccount} es actualmen desactivisat.",
"dismissable_banner.community_timeline": "Tis-ci es li postas max recent de gente con contos che {domain}.",
"dismissable_banner.dismiss": "Demisser",
"dismissable_banner.explore_links": "Tis-ci es li novas max distribuet che li social retage hodie. Novas plu nov, postat de plu diferent persones, es monstrat plu alt.",
@@ -321,6 +322,7 @@
"interaction_modal.on_another_server": "Sur un servitor diferent",
"interaction_modal.on_this_server": "Sur ti-ci servitor",
"interaction_modal.sign_in": "Tu ne ha initiat session che ti-ci servitor. U logia tui conto?",
+ "interaction_modal.sign_in_hint": "Nota: To es li websitu u tu adheret. Si tu ne rememora, sercha li benevenit-email in tui inbuxe. Tu anc posse introducter tui plen usator-nómine! (p.ex. @Mastodon@mastodon.social)",
"interaction_modal.title.favourite": "Favoritisar li posta de {name}",
"interaction_modal.title.follow": "Sequer {name}",
"interaction_modal.title.reblog": "Boostar li posta de {name}",
@@ -351,10 +353,17 @@
"keyboard_shortcuts.profile": "Aperter profil del autor",
"keyboard_shortcuts.reply": "Responder al posta",
"keyboard_shortcuts.requests": "Aperter liste de seque-petitiones",
+ "keyboard_shortcuts.search": "Infocar sercha-barre",
+ "keyboard_shortcuts.spoilers": "Monstrar/celar CW camp",
+ "keyboard_shortcuts.start": "Aperter \"Qualmen comensar\" columne",
+ "keyboard_shortcuts.toggle_hidden": "Monstrar/celar text detra CW",
"keyboard_shortcuts.toggle_sensitivity": "Monstrar/celar medie",
"keyboard_shortcuts.toot": "Crear un nov posta",
+ "keyboard_shortcuts.unfocus": "Desinfocar text-area de composition/serchar",
"keyboard_shortcuts.up": "Mover ad-supra in li liste",
"lightbox.close": "Cluder",
+ "lightbox.compress": "Compresser vise-buxe de image",
+ "lightbox.expand": "Expander vise-buxe de image",
"lightbox.next": "Sequent",
"lightbox.previous": "Precedent",
"limited_account_hint.action": "Monstrar profil totvez",
@@ -382,6 +391,7 @@
"mute_modal.hide_notifications": "Celar notificationes de ti-ci usator?",
"mute_modal.indefinite": "Índefinit",
"navigation_bar.about": "Information",
+ "navigation_bar.advanced_interface": "Aperter in li web-interfacie avansat",
"navigation_bar.blocks": "Bloccat usatores",
"navigation_bar.bookmarks": "Marcatores",
"navigation_bar.community_timeline": "Local témpor-linea",
@@ -399,6 +409,7 @@
"navigation_bar.lists": "Listes",
"navigation_bar.logout": "Exear",
"navigation_bar.mutes": "Silentiat usatores",
+ "navigation_bar.opened_in_classic_interface": "Postas, contos e altri specific págines es customalmen apertet in li classic web-interfacie.",
"navigation_bar.personal": "Personal",
"navigation_bar.pins": "Pinglat postas",
"navigation_bar.preferences": "Preferenties",
@@ -421,12 +432,16 @@
"notifications.clear_confirmation": "Vole tu vermen permanentmen aclarar omni tui notificationes?",
"notifications.column_settings.admin.report": "Nov raportas:",
"notifications.column_settings.admin.sign_up": "Nov registrationes:",
+ "notifications.column_settings.alert": "Notificationes sur li computator",
"notifications.column_settings.favourite": "Favorites:",
"notifications.column_settings.filter_bar.advanced": "Monstrar omni categories",
+ "notifications.column_settings.filter_bar.category": "Rapid filtre-barre",
+ "notifications.column_settings.filter_bar.show_bar": "Monstrar filtre-barre",
"notifications.column_settings.follow": "Nov sequitores:",
"notifications.column_settings.follow_request": "Nov petitiones de sequer:",
"notifications.column_settings.mention": "Mentiones:",
"notifications.column_settings.poll": "Resultates del balotation:",
+ "notifications.column_settings.push": "Notificationes push",
"notifications.column_settings.reblog": "Boosts:",
"notifications.column_settings.show": "Monstrar in columne",
"notifications.column_settings.sound": "Far son",
@@ -444,6 +459,12 @@
"notifications.grant_permission": "Dar permission.",
"notifications.group": "{count} notificationes",
"notifications.mark_as_read": "Marcar omni notificationes quam leet",
+ "notifications.permission_denied": "Notificationes sur li computator es índisponibil pro que on ha previamen rejectet un petition por navigator-permissiones",
+ "notifications.permission_denied_alert": "Notificationes sur li computator ne posse esser activisat, pro que navigator-permission ha esset previamen rejectet",
+ "notifications.permission_required": "Notificationes sur li computator es índisponibil pro que li besonat permission ne ha esset dat.",
+ "notifications_permission_banner.enable": "Activisar notificationes sur li computator",
+ "notifications_permission_banner.how_to_control": "Por reciver notificationes quande Mastodon ne es apert, activisa notificationes sur li computator. Tu posse decider precisimen quel species de interactiones genera notificationes per li buton {icon} in-supra quande ili es activisat.",
+ "notifications_permission_banner.title": "Nequande preterlassa quocunc",
"onboarding.action.back": "Retroear",
"onboarding.actions.back": "Retroear",
"onboarding.actions.go_to_explore": "Ear a vider lu populari",
@@ -474,9 +495,14 @@
"onboarding.steps.follow_people.title": "Personalisar tui hemal témpor-linea",
"onboarding.steps.publish_status.body": "Saluta li munde con text, images, videos o balotationes {emoji}",
"onboarding.steps.publish_status.title": "Crear tui unesim posta",
+ "onboarding.steps.setup_profile.body": "Ascresce tui interactiones per haver un profil detalliat.",
"onboarding.steps.setup_profile.title": "Personalisar tui profil",
"onboarding.steps.share_profile.body": "Di tui amics qualmen trovar te che Mastodon",
"onboarding.steps.share_profile.title": "Partir tui profil Mastodon",
+ "onboarding.tips.2fa": "Savet tu? Tu posse securisar tui conto per activisar 2-factor autentication in tui parametres de conto. Ti functiona con quelcunc aplication TOTP quel tu selecte, null númere de telefon besonat!",
+ "onboarding.tips.accounts_from_other_servers": "Savet tu? Pro que Mastodon es decentralisat, quelc profiles queles tu trova va esser logiat che servitores altri quam tui. Totvez tu posse interacter con les sin grates! Lor servitores es in li duesim demí de lor usator-nómines!",
+ "onboarding.tips.migration": "Savet tu? Si tu senti que {domain} ne es un bonissim servitor por te futurimen, tu posse mover te a un altri Mastodon-servitor sin perdir tui sequitores. Tu posse mem etablisser tui propri servitor!",
+ "onboarding.tips.verification": "Savet tu? Tu posse verificar tui conto per metter un ligament a tui Mastodon-profil in tui propri websitu e adjunter li websitu a tui profil. Null payament o documentes besonat!",
"password_confirmation.exceeds_maxlength": "Confirmation de passa-parol transpassa li maxim longore de passa-paroles",
"password_confirmation.mismatching": "Confirmation de passa-parol ne egala",
"picture_in_picture.restore": "Restaurar",
@@ -600,6 +626,8 @@
"status.admin_status": "Aperter ti-ci posta in li interfacie de moderation",
"status.block": "Bloccar @{name}",
"status.bookmark": "Marcar",
+ "status.cancel_reblog_private": "Desboostar",
+ "status.cannot_reblog": "Ti-ci posta ne posse esser boostat",
"status.copy": "Copiar ligament al posta",
"status.delete": "Deleter",
"status.detailed_status": "Detalliat vise de conversation",
@@ -627,6 +655,10 @@
"status.pin": "Pinglar sur profil",
"status.pinned": "Pinglat posta",
"status.read_more": "Leer plu",
+ "status.reblog": "Boostar",
+ "status.reblog_private": "Boostar con li original visibilitá",
+ "status.reblogged_by": "{name} boostat",
+ "status.reblogs.empty": "Ancor nequi ha boostat ti-ci posta. Quande alqui fa it, ilu va aparir ci.",
"status.redraft": "Deleter & redacter",
"status.remove_bookmark": "Remover marcator",
"status.replied_to": "Respondet a {name}",
@@ -664,6 +696,9 @@
"trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} persones}} durant li ultim {days, plural, one {die} other {{days} dies}}",
"trends.trending_now": "Actualmen populari",
"ui.beforeunload": "Tui íncomplet posta va esser perdit si tu lassa Mastodon.",
+ "units.short.billion": "{count}B",
+ "units.short.million": "{count}M",
+ "units.short.thousand": "{count}K",
"upload_area.title": "Trenar & lassar cader por cargar",
"upload_button.label": "Adjunter images, un video o un audio-file",
"upload_error.limit": "Límite de medie-cargationes transpassat.",
@@ -679,6 +714,7 @@
"upload_modal.apply": "Aplicar",
"upload_modal.applying": "Aplicant…",
"upload_modal.choose_image": "Selecter image",
+ "upload_modal.description_placeholder": "Li Europan lingues es membres del sam familie. Lor separat existentie es un mite",
"upload_modal.detect_text": "Detecter text del image",
"upload_modal.edit_media": "Redacter medie",
"upload_modal.hint": "Clicca o trena li circul por selecter li focal punctu quel va esser sempre visibil in omni previse-images.",
diff --git a/app/javascript/mastodon/locales/ig.json b/app/javascript/mastodon/locales/ig.json
index c24d28eea9..f163567f17 100644
--- a/app/javascript/mastodon/locales/ig.json
+++ b/app/javascript/mastodon/locales/ig.json
@@ -6,7 +6,6 @@
"account.follow": "Soro",
"account.followers": "Ndị na-eso",
"account.following": "Na-eso",
- "account.follows_you": "Na-eso gị",
"account.mute": "Mee ogbi @{name}",
"account.unfollow": "Kwụsị iso",
"account_note.placeholder": "Click to add a note",
diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json
index ba44408935..233b768457 100644
--- a/app/javascript/mastodon/locales/io.json
+++ b/app/javascript/mastodon/locales/io.json
@@ -37,7 +37,6 @@
"account.following": "Sequata",
"account.following_counter": "{count, plural, one {{counter} Sequas} other {{counter} Sequanti}}",
"account.follows.empty": "Ca uzanto ne sequa irgu til nun.",
- "account.follows_you": "Sequas tu",
"account.go_to_profile": "Irez al profilo",
"account.hide_reblogs": "Celez repeti de @{name}",
"account.in_memoriam": "Memorige.",
diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json
index 0df1a5e753..46e83b4dbf 100644
--- a/app/javascript/mastodon/locales/is.json
+++ b/app/javascript/mastodon/locales/is.json
@@ -38,7 +38,6 @@
"account.following": "Fylgist með",
"account.following_counter": "{count, plural, one {Fylgist með: {counter}} other {Fylgist með: {counter}}}",
"account.follows.empty": "Þessi notandi fylgist ennþá ekki með neinum.",
- "account.follows_you": "Fylgir þér",
"account.go_to_profile": "Fara í notandasnið",
"account.hide_reblogs": "Fela endurbirtingar fyrir @{name}",
"account.in_memoriam": "Minning.",
diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json
index fa659506a1..4fb4d88cbc 100644
--- a/app/javascript/mastodon/locales/it.json
+++ b/app/javascript/mastodon/locales/it.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "Nessun post",
"account.featured_tags.title": "Hashtag in evidenza di {name}",
"account.follow": "Segui",
+ "account.follow_back": "Segui a tua volta",
"account.followers": "Follower",
"account.followers.empty": "Ancora nessuno segue questo utente.",
"account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Follower}}",
"account.following": "Seguiti",
"account.following_counter": "{count, plural, one {{counter} Seguiti} other {{counter} Seguiti}}",
"account.follows.empty": "Questo utente non segue ancora nessuno.",
- "account.follows_you": "Ti segue",
"account.go_to_profile": "Vai al profilo",
"account.hide_reblogs": "Nascondi potenziamenti da @{name}",
"account.in_memoriam": "In memoria.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Silenzia notifiche",
"account.mute_short": "Silenzia",
"account.muted": "Mutato",
+ "account.mutual": "Reciproco",
"account.no_bio": "Nessuna descrizione fornita.",
"account.open_original_page": "Apri la pagina originale",
"account.posts": "Post",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index 5ad9d6dc94..3246415759 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -38,7 +38,6 @@
"account.following": "フォロー中",
"account.following_counter": "{counter} フォロー",
"account.follows.empty": "まだ誰もフォローしていません。",
- "account.follows_you": "フォローされています",
"account.go_to_profile": "プロフィールページへ",
"account.hide_reblogs": "@{name}さんからのブーストを非表示",
"account.in_memoriam": "故人を偲んで。",
diff --git a/app/javascript/mastodon/locales/ka.json b/app/javascript/mastodon/locales/ka.json
index 875ac3c195..9d977e9331 100644
--- a/app/javascript/mastodon/locales/ka.json
+++ b/app/javascript/mastodon/locales/ka.json
@@ -15,7 +15,6 @@
"account.featured_tags.last_status_never": "პოსტები არ არის",
"account.follow": "გაყოლა",
"account.followers": "მიმდევრები",
- "account.follows_you": "მოგყვებათ",
"account.hide_reblogs": "დაიმალოს ბუსტები @{name}-სგან",
"account.media": "მედია",
"account.mention": "ასახელეთ @{name}",
diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json
index e9d4b57de8..08c70a9405 100644
--- a/app/javascript/mastodon/locales/kab.json
+++ b/app/javascript/mastodon/locales/kab.json
@@ -22,7 +22,6 @@
"account.followers_counter": "{count, plural, one {{count} n umeḍfar} other {{count} n imeḍfaren}}",
"account.following_counter": "{count, plural, one {{counter} yettwaḍfaren} other {{counter} yettwaḍfaren}}",
"account.follows.empty": "Ar tura, amseqdac-agi ur yeṭṭafaṛ yiwen.",
- "account.follows_you": "Yeṭṭafaṛ-ik",
"account.hide_reblogs": "Ffer ayen i ibeṭṭu @{name}",
"account.link_verified_on": "Taɣara n useɣwen-a tettwasenqed ass n {date}",
"account.locked_info": "Amiḍan-agi uslig isekweṛ. D bab-is kan i izemren ad yeǧǧ, s ufus-is, win ara t-iḍefṛen.",
diff --git a/app/javascript/mastodon/locales/kk.json b/app/javascript/mastodon/locales/kk.json
index 189d792e38..e0e047ba14 100644
--- a/app/javascript/mastodon/locales/kk.json
+++ b/app/javascript/mastodon/locales/kk.json
@@ -34,7 +34,6 @@
"account.following": "Жазылым",
"account.following_counter": "{count, plural, one {{counter} жазылым} other {{counter} жазылым}}",
"account.follows.empty": "Бұл қолданушы әлі ешкімге жазылмаған.",
- "account.follows_you": "Сізге жазылған",
"account.go_to_profile": "Профиліне өту",
"account.hide_reblogs": "@{name} бустарын жасыру",
"account.joined_short": "Қосылған",
diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json
index 4606916c1d..5746ab67a5 100644
--- a/app/javascript/mastodon/locales/ko.json
+++ b/app/javascript/mastodon/locales/ko.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "게시물 없음",
"account.featured_tags.title": "{name} 님의 추천 해시태그",
"account.follow": "팔로우",
+ "account.follow_back": "맞팔로우",
"account.followers": "팔로워",
"account.followers.empty": "아직 아무도 이 사용자를 팔로우하고 있지 않습니다.",
"account.followers_counter": "{counter} 팔로워",
"account.following": "팔로잉",
"account.following_counter": "{counter} 팔로잉",
"account.follows.empty": "이 사용자는 아직 아무도 팔로우하고 있지 않습니다.",
- "account.follows_you": "나를 팔로우합니다",
"account.go_to_profile": "프로필로 이동",
"account.hide_reblogs": "@{name}의 부스트를 숨기기",
"account.in_memoriam": "고인의 계정입니다.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "알림 뮤트",
"account.mute_short": "뮤트",
"account.muted": "뮤트됨",
+ "account.mutual": "상호 팔로우",
"account.no_bio": "제공된 설명이 없습니다.",
"account.open_original_page": "원본 페이지 열기",
"account.posts": "게시물",
diff --git a/app/javascript/mastodon/locales/ku.json b/app/javascript/mastodon/locales/ku.json
index b940542677..7d8603cae1 100644
--- a/app/javascript/mastodon/locales/ku.json
+++ b/app/javascript/mastodon/locales/ku.json
@@ -36,7 +36,6 @@
"account.following": "Dişopîne",
"account.following_counter": "{count, plural, one {{counter} Dişopîne} other {{counter} Dişopîne}}",
"account.follows.empty": "Ev bikarhêner hin kesekî heya niha neşopandiye.",
- "account.follows_you": "Te dişopîne",
"account.go_to_profile": "Biçe bo profîlê",
"account.hide_reblogs": "Bilindkirinên ji @{name} veşêre",
"account.in_memoriam": "Di bîranînê de.",
diff --git a/app/javascript/mastodon/locales/kw.json b/app/javascript/mastodon/locales/kw.json
index ca08ca836e..8f384fe125 100644
--- a/app/javascript/mastodon/locales/kw.json
+++ b/app/javascript/mastodon/locales/kw.json
@@ -20,7 +20,6 @@
"account.followers_counter": "{count, plural, one {{counter} Holyer} other {{counter} Holyer}}",
"account.following_counter": "{count, plural, one {Ow holya {counter}} other {Ow holya {counter}}}",
"account.follows.empty": "Ny wra'n devnydhyer ma holya nagonan hwath.",
- "account.follows_you": "Y'th hol",
"account.hide_reblogs": "Kudha kenerthow a @{name}",
"account.link_verified_on": "Perghenogeth an kolm ma a veu checkys dhe {date}",
"account.locked_info": "Studh privetter an akont ma yw alhwedhys. An perghen a wra dasweles dre leuv piw a yll aga holya.",
diff --git a/app/javascript/mastodon/locales/lad.json b/app/javascript/mastodon/locales/lad.json
index ffdc044412..c54ed2a7e9 100644
--- a/app/javascript/mastodon/locales/lad.json
+++ b/app/javascript/mastodon/locales/lad.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "\"No ay publikasyones",
"account.featured_tags.title": "Etiketas avaliadas de {name}",
"account.follow": "Sige",
+ "account.follow_back": "Sige tamyen",
"account.followers": "Suivantes",
"account.followers.empty": "Por agora dingun no sige a este utilizador.",
"account.followers_counter": "{count, plural, one {{counter} suivante} other {{counter} suivantes}}",
"account.following": "Sigiendo",
"account.following_counter": "{count, plural, other {Sigiendo a {counter}}}",
"account.follows.empty": "Este utilizador ainda no sige a ningun.",
- "account.follows_you": "Te sige",
"account.go_to_profile": "Va al profil",
"account.hide_reblogs": "Eskonde repartajasyones de @{name}",
"account.in_memoriam": "De bendicha memoria.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Silensia avizos de @{name}",
"account.mute_short": "Silensia",
"account.muted": "Silensiado",
+ "account.mutual": "Mutual",
"account.no_bio": "No ay deskripsion.",
"account.open_original_page": "Avre pajina orijnala",
"account.posts": "Publikasyones",
@@ -77,6 +78,10 @@
"admin.dashboard.retention.average": "Media",
"admin.dashboard.retention.cohort": "Mez de enrejistrasyon",
"admin.dashboard.retention.cohort_size": "Muevos utilizadores",
+ "admin.impact_report.instance_accounts": "Profiles de kuentos esto efasaria",
+ "admin.impact_report.instance_followers": "Suivantes a los kualos nuestros utilizadores perderian",
+ "admin.impact_report.instance_follows": "Suivantes a los kualos sus utilizadores perderian",
+ "admin.impact_report.title": "Rezumen de impakto",
"alert.rate_limited.message": "Por favor aprova dempues de {retry_time, time, medium}.",
"alert.rate_limited.title": "Trafiko limitado",
"alert.unexpected.message": "Afito un yerro no asperado.",
@@ -220,6 +225,7 @@
"emoji_button.search_results": "Rizultados de bushkeda",
"emoji_button.symbols": "Simbolos",
"emoji_button.travel": "Viajes i lugares",
+ "empty_column.account_hides_collections": "Este utilizador desidio no mostrar esta enformasyon",
"empty_column.account_suspended": "Kuento suspendido",
"empty_column.account_timeline": "No ay publikasyones aki!",
"empty_column.account_unavailable": "Profil no desponivle",
@@ -294,20 +300,34 @@
"hashtag.column_settings.tag_mode.any": "Kualsekera de estos",
"hashtag.column_settings.tag_mode.none": "Dinguno de estos",
"hashtag.column_settings.tag_toggle": "Inkluir etiketas adisionalas en esta kolumna",
+ "hashtag.counter_by_accounts": "{count, plural, one {{counter} partisipante} other {{counter} partisipantes}}",
+ "hashtag.counter_by_uses": "{count, plural, one {{counter} publikasyon} other {{counter} publikasyones}}",
+ "hashtag.counter_by_uses_today": "{count, plural, one {{counter} publikasyon} other {{counter} publikasyones}} oy",
"hashtag.follow": "Segir etiketa",
"hashtag.unfollow": "Desegir etiketa",
+ "hashtags.and_other": "…i {count, plural, one {}other {# mas}}",
+ "home.actions.go_to_explore": "Ve los trendes",
+ "home.actions.go_to_suggestions": "Topa a djente para segir",
"home.column_settings.basic": "Opsyones bazikas",
"home.column_settings.show_reblogs": "Amostrar repartajasyones",
"home.column_settings.show_replies": "Amostrar repuestas",
+ "home.explore_prompt.body": "Tu linya prinsipala es una mikstura de publikasyones kon etiketas a las kualas eskojites a segir, la djente a la kuala eskojites a segir i las publikasyones ke eyos repartajan. Si esta demaziado trankila, puedes:",
+ "home.explore_prompt.title": "Esta es tu baza prinsipala en Mastodon.",
"home.hide_announcements": "Eskonde pregones",
+ "home.pending_critical_update.body": "Por favor aktualiza tu sirvidor de Mastodon pishin!",
"home.pending_critical_update.link": "Ve aktualizasyones",
+ "home.pending_critical_update.title": "Aktualizasyon de seguridad kritika esta desponivle!",
"home.show_announcements": "Amostra pregones",
"interaction_modal.description.favourite": "Kon un kuento en Mastodon, puedes markar esta publikasyon komo favorita para ke el autor sepa ke te plaze i para guadrarla para dempues.",
"interaction_modal.description.follow": "Kon un kuento en Mastodon, puedes segir a {name} para risivir sus publikasyones en tu linya temporal prinsipala.",
"interaction_modal.description.reblog": "Kon un kuento en Mastodon, puedes repartajar esta publikasyon para amostrarla a tus suivantes.",
"interaction_modal.description.reply": "Kon un kuento en Mastodon, puedes arispondir a esta publikasyon.",
+ "interaction_modal.login.action": "Va a tu sirvidor",
+ "interaction_modal.login.prompt": "Domeno del sirvidor de tu kuento, por enshemplo mastodon.social",
+ "interaction_modal.no_account_yet": "No tyenes kuento de Mastodon?",
"interaction_modal.on_another_server": "En otro sirvidor",
"interaction_modal.on_this_server": "En este sirvidor",
+ "interaction_modal.sign_in": "No estas konektado kon este sirvidor. Ande tyenes tu kuento?",
"interaction_modal.title.favourite": "Endika ke te plaze publikasyon de {name}",
"interaction_modal.title.follow": "Sige a {name}",
"interaction_modal.title.reblog": "Repartaja publikasyon de {name}",
@@ -356,11 +376,13 @@
"lightbox.previous": "Anterior",
"limited_account_hint.action": "Amostra el profil entanto",
"limited_account_hint.title": "Este profil fue eskondido por los moderadores de {domain}.",
+ "link_preview.author": "Publikasyon de {name}",
"lists.account.add": "Adjusta a lista",
"lists.account.remove": "Kita de lista",
"lists.delete": "Efasa lista",
"lists.edit": "Edita lista",
"lists.edit.submit": "Troka titolo",
+ "lists.exclusive": "Eskonder estas publikasyones de linya prinsipala",
"lists.new.create": "Adjusta lista",
"lists.new.title_placeholder": "Titolo de mueva lista",
"lists.replies_policy.followed": "Kualseker utilizardo segido",
@@ -377,6 +399,7 @@
"mute_modal.hide_notifications": "Eskonder avizos de este utilizador?",
"mute_modal.indefinite": "Indefinida",
"navigation_bar.about": "Sovre mozotros",
+ "navigation_bar.advanced_interface": "Avre en la enterfaz avanzada",
"navigation_bar.blocks": "Utilizadores blokados",
"navigation_bar.bookmarks": "Markadores",
"navigation_bar.community_timeline": "Linya de tiempo lokala",
@@ -394,6 +417,7 @@
"navigation_bar.lists": "Listas",
"navigation_bar.logout": "Salir",
"navigation_bar.mutes": "Utilizadores silensiados",
+ "navigation_bar.opened_in_classic_interface": "Publikasyones, kuentos i otras pajinas espesifikas se avren kon preferensyas predeterminadas en la enterfaz web klasika.",
"navigation_bar.personal": "Personal",
"navigation_bar.pins": "Publikasyones fiksadas",
"navigation_bar.preferences": "Preferensyas",
@@ -451,10 +475,33 @@
"notifications_permission_banner.title": "Nunka te piedres niente",
"onboarding.action.back": "Va atras",
"onboarding.actions.back": "Va atras",
+ "onboarding.actions.go_to_explore": "Va a los trendes",
+ "onboarding.actions.go_to_home": "Va a tu linya prinsipala",
+ "onboarding.compose.template": "Ke haber, #Mastodon?",
+ "onboarding.follows.title": "Personaliza tu linya prinsipala",
+ "onboarding.profile.discoverable": "Faz ke mi profil apareska en bushkedas",
"onboarding.profile.display_name": "Nombre amostrado",
+ "onboarding.profile.display_name_hint": "Tu nombre para amostrar.",
+ "onboarding.profile.note": "Tu deskripsyon",
+ "onboarding.profile.note_hint": "Puedes @enmentar a otra djente o #etiketas…",
+ "onboarding.profile.save_and_continue": "Guadra i kontinua",
+ "onboarding.profile.title": "Konfigurasyon de profil",
+ "onboarding.profile.upload_avatar": "Karga imaje de profil",
+ "onboarding.profile.upload_header": "Karga kavesera de profil",
+ "onboarding.share.message": "Soy {username} en #Mastodon! Segidme en {url}",
+ "onboarding.share.next_steps": "Posivles sigientes pasos:",
+ "onboarding.share.title": "Partaja tu profil",
+ "onboarding.start.skip": "No nesesitas ayudo para ampesar?",
+ "onboarding.start.title": "Lo logrates!",
+ "onboarding.steps.follow_people.body": "El buto de Mastodon es segir a djente interesante.",
+ "onboarding.steps.follow_people.title": "Personaliza tu linya prinsipala",
+ "onboarding.steps.publish_status.title": "Eskrive tu primera publikasyon",
+ "onboarding.steps.setup_profile.title": "Personaliza tu profil",
+ "onboarding.steps.share_profile.title": "Partaja tu profil de Mastodon",
"picture_in_picture.restore": "Restora",
"poll.closed": "Serrado",
"poll.refresh": "Arefreska",
+ "poll.reveal": "Mira los rezultados",
"poll.total_people": "{count, plural, one {# persona} other {# personas}}",
"poll.total_votes": "{count, plural, one {# voto} other {# votos}}",
"poll.vote": "Vota",
@@ -473,6 +520,7 @@
"privacy.unlisted.short": "No listado",
"privacy_policy.last_updated": "Ultima aktualizasyon: {date}",
"privacy_policy.title": "Politika de privasita",
+ "recommended": "Rekomendado",
"refresh": "Arefreska",
"regeneration_indicator.label": "Eskargando…",
"regeneration_indicator.sublabel": "Tu linya de tiempo prinsipala esta preparando!",
@@ -490,6 +538,7 @@
"reply_indicator.cancel": "Anula",
"report.block": "Bloka",
"report.block_explanation": "No veras sus publikasyones. No podra ver tus publikasyones ni segirte. Podra saver ke le blokates.",
+ "report.categories.legal": "Legal",
"report.categories.other": "Otros",
"report.categories.spam": "Spam",
"report.categories.violation": "El kontenido viola una o mas reglas del sirvidor",
@@ -503,9 +552,12 @@
"report.forward_hint": "Este kuento es de otro sirvidor. Embiar una kopia anonimizada del raporto ayi tamyen?",
"report.mute": "Silensia",
"report.mute_explanation": "No veras sus publikasyones. Ainda pueden segirte i no va saver ke le silensiates.",
+ "report.next": "Sigiente",
"report.placeholder": "Otros komentos",
"report.reasons.dislike": "No me plaze",
"report.reasons.dislike_description": "\"No es algo ke kero ver",
+ "report.reasons.legal": "Es ilegal",
+ "report.reasons.legal_description": "Kreyes ke esta violando la ley de tu paiz o el paiz del sirvidor",
"report.reasons.other": "Es otra koza",
"report.reasons.other_description": "El problem no es de las otras kategorias",
"report.reasons.spam": "Es spam",
@@ -525,6 +577,7 @@
"report.unfollow": "Desegir a @{name}",
"report.unfollow_explanation": "Estas sigiendo este kuento. Para no ver sus publikasyones en tu linya de tiempo, puedes deshar de segirlo.",
"report_notification.attached_statuses": "{count, plural, one {{count} publikasyon} other {{count} publikasyones}} atadas",
+ "report_notification.categories.legal": "Legal",
"report_notification.categories.other": "Otros",
"report_notification.categories.spam": "Spam",
"report_notification.categories.violation": "Violasion de reglas",
@@ -537,8 +590,14 @@
"search.quick_action.open_url": "Avre URL en Mastodon",
"search.quick_action.status_search": "Publikasyones ke koresponden kon {x}",
"search.search_or_paste": "Bushka o apega URL",
+ "search_popout.full_text_search_disabled_message": "No desponivle en {domain}.",
+ "search_popout.full_text_search_logged_out_message": "Solo desponivle kuando estas konektado kon tu kuento.",
+ "search_popout.language_code": "kodiche ISO de lingua",
+ "search_popout.options": "Opsyones de bushkeda",
"search_popout.quick_actions": "Aksiones rapidas",
"search_popout.recent": "Bushkedas resientes",
+ "search_popout.specific_date": "dato espesifiko",
+ "search_popout.user": "utilizador",
"search_results.accounts": "Profiles",
"search_results.all": "Todos",
"search_results.hashtags": "Etiketas",
@@ -555,12 +614,32 @@
"sign_in_banner.create_account": "Kriya kuento",
"sign_in_banner.sign_in": "Konektate",
"sign_in_banner.sso_redirect": "Konektate o enrejistrate",
+ "sign_in_banner.text": "Konektate para segir prefiles o etiketas, partajar publikasyones, arispondir a eyas i markar ke te plazen. Puedes tambyen enteraktuar dizde tu kuento en un sirvidor desferente.",
+ "status.admin_account": "Avre la enterfaz de moderasyon para @{name}",
+ "status.admin_domain": "Avre la enterfaz de moderasyon para @{domain}",
+ "status.admin_status": "Avre esto en la enterfaz de moderasyon",
+ "status.block": "Bloka a @{name}",
+ "status.bookmark": "Marka",
+ "status.cancel_reblog_private": "No repartaja",
+ "status.cannot_reblog": "Esta publikasyon no se puede repartajar",
+ "status.copy": "Kopia atadijo de publikasyon",
+ "status.delete": "Efasa",
+ "status.detailed_status": "Vista de konversasyon detalyada",
+ "status.direct": "Enmenta a @{name} en privado",
+ "status.direct_indicator": "Enmentadura privada",
+ "status.edit": "Edita",
+ "status.edited": "Editado {date}",
+ "status.edited_x_times": "Editado {count, plural, one {{count} vez} other {{count} vezes}}",
+ "status.embed": "Inkrusta",
+ "status.favourite": "Te plaze",
"status.filter": "Filtra esta publikasyon",
"status.filtered": "Filtrado",
"status.hide": "Eskonde publikasyon",
"status.history.created": "{name} kriyo {date}",
"status.history.edited": "{name} edito {date}",
"status.load_more": "Eskarga mas",
+ "status.media.open": "Klika para avrir",
+ "status.media.show": "Klika para amostrar",
"status.media_hidden": "Multimedia eskondidos",
"status.mention": "Enmenta a @{name}",
"status.more": "Mas",
@@ -588,6 +667,7 @@
"status.show_more": "Amostra mas",
"status.show_more_all": "Amostra mas para todo",
"status.show_original": "Amostra orijinal",
+ "status.title.with_attachments": "{user} publiko {attachmentCount, plural, one {un anekso} other {{attachmentCount} aneksos}}",
"status.translate": "Trezlada",
"status.translated_from_with": "Trezladado dizde {lang} kon {provider}",
"status.uncached_media_warning": "Vista previa no desponivle",
@@ -636,6 +716,7 @@
"upload_modal.preview_label": "Vista previa ({ratio})",
"upload_progress.label": "Kargando...",
"upload_progress.processing": "Prosesando…",
+ "username.taken": "Akel nombre de utilizador ya esta en uzo. Aprova otruno",
"video.close": "Serra video",
"video.download": "Abasha dosya",
"video.exit_fullscreen": "Sal de ekran kompleto",
diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json
index 58c80e4117..82f1669b1a 100644
--- a/app/javascript/mastodon/locales/lt.json
+++ b/app/javascript/mastodon/locales/lt.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "Nėra įrašų",
"account.featured_tags.title": "{name} rekomenduojamos grotažymės",
"account.follow": "Sekti",
+ "account.follow_back": "Sekti atgal",
"account.followers": "Sekėjai",
"account.followers.empty": "Šio naudotojo dar niekas neseka.",
"account.followers_counter": "{count, plural, one {{counter} sekėjas (-a)} few {{counter} sekėjai} many {{counter} sekėjo} other {{counter} sekėjų}}",
"account.following": "Seka",
"account.following_counter": "{count, plural, one {{counter} Seka} few {{counter} Seka} many {{counter} Seka} other {{counter} Seka}}",
"account.follows.empty": "Šis (-i) naudotojas (-a) dar nieko neseka.",
- "account.follows_you": "Seka tave",
"account.go_to_profile": "Eiti į profilį",
"account.hide_reblogs": "Slėpti pakėlimus iš @{name}",
"account.in_memoriam": "Atminimui.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Nutildyti pranešimus",
"account.mute_short": "Nutildyti",
"account.muted": "Nutildytas",
+ "account.mutual": "Abipusis",
"account.no_bio": "Nėra pateikto aprašymo.",
"account.open_original_page": "Atidaryti originalinį puslapį",
"account.posts": "Įrašai",
diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json
index 63ec6275ba..c06a1d9368 100644
--- a/app/javascript/mastodon/locales/lv.json
+++ b/app/javascript/mastodon/locales/lv.json
@@ -37,7 +37,6 @@
"account.following": "Seko",
"account.following_counter": "{count, plural, one {{counter} sekojamais} other {{counter} sekojamie}}",
"account.follows.empty": "Šis lietotājs pagaidām nevienam neseko.",
- "account.follows_you": "Seko tev",
"account.go_to_profile": "Doties uz profilu",
"account.hide_reblogs": "Slēpt @{name} izceltas ziņas",
"account.in_memoriam": "Piemiņai.",
diff --git a/app/javascript/mastodon/locales/mk.json b/app/javascript/mastodon/locales/mk.json
index 371218bbfe..bdef3f4a5f 100644
--- a/app/javascript/mastodon/locales/mk.json
+++ b/app/javascript/mastodon/locales/mk.json
@@ -25,7 +25,6 @@
"account.followers": "Следбеници",
"account.followers.empty": "Никој не го следи овој корисник сеуште.",
"account.follows.empty": "Корисникот не следи никој сеуште.",
- "account.follows_you": "Те следи тебе",
"account.hide_reblogs": "Сокриј буст од @{name}",
"account.link_verified_on": "Сопстевноста на овај линк беше проверен на {date}",
"account.locked_info": "Статусот на приватност на овај корисник е сетиран како заклучен. Корисникот одлучува кој можи да го следи него.",
diff --git a/app/javascript/mastodon/locales/ml.json b/app/javascript/mastodon/locales/ml.json
index b00cedc6fc..11636646b2 100644
--- a/app/javascript/mastodon/locales/ml.json
+++ b/app/javascript/mastodon/locales/ml.json
@@ -26,7 +26,6 @@
"account.following": "പിന്തുടരുന്നു",
"account.following_counter": "{count, plural, one {{counter} പിന്തുടരുന്നു} other {{counter} പിന്തുടരുന്നു}}",
"account.follows.empty": "ഈ ഉപയോക്താവ് ആരേയും ഇതുവരെ പിന്തുടരുന്നില്ല.",
- "account.follows_you": "നിങ്ങളെ പിന്തുടരുന്നു",
"account.go_to_profile": "പ്രൊഫൈലിലേക്ക് പോകാം",
"account.hide_reblogs": "@{name} ബൂസ്റ്റ് ചെയ്തവ മറയ്കുക",
"account.joined_short": "ജോയിൻ ചെയ്തിരിക്കുന്നു",
diff --git a/app/javascript/mastodon/locales/mr.json b/app/javascript/mastodon/locales/mr.json
index 75b75375b7..7f5b7d6524 100644
--- a/app/javascript/mastodon/locales/mr.json
+++ b/app/javascript/mastodon/locales/mr.json
@@ -35,7 +35,6 @@
"account.following": "अनुसरण",
"account.following_counter": "{count, plural, one {{counter} following} other {{counter} following}}",
"account.follows.empty": "हा वापरकर्ता अजूनपर्यंत कोणाचा अनुयायी नाही.",
- "account.follows_you": "तुमचा अनुयायी आहे",
"account.go_to_profile": "प्रोफाइल वर जा",
"account.hide_reblogs": "@{name} पासून सर्व बूस्ट लपवा",
"account.joined_short": "सामील झाले",
diff --git a/app/javascript/mastodon/locales/ms.json b/app/javascript/mastodon/locales/ms.json
index 724e07ae76..50a48db1ea 100644
--- a/app/javascript/mastodon/locales/ms.json
+++ b/app/javascript/mastodon/locales/ms.json
@@ -37,7 +37,6 @@
"account.following": "Mengikuti",
"account.following_counter": "{count, plural, one {{counter} Diikuti} other {{counter} Diikuti}}",
"account.follows.empty": "Pengguna ini belum mengikuti sesiapa.",
- "account.follows_you": "Mengikuti anda",
"account.go_to_profile": "Pergi ke profil",
"account.hide_reblogs": "Sembunyikan galakan daripada @{name}",
"account.in_memoriam": "Dalam Memoriam.",
diff --git a/app/javascript/mastodon/locales/my.json b/app/javascript/mastodon/locales/my.json
index 917419d172..3ca03b616a 100644
--- a/app/javascript/mastodon/locales/my.json
+++ b/app/javascript/mastodon/locales/my.json
@@ -38,7 +38,6 @@
"account.following": "စောင့်ကြည့်နေသည်",
"account.following_counter": "{count, plural, one {စောင့်ကြည့်ထားသူ {counter}} other {စောင့်ကြည့်ထားသူများ {counter}}}",
"account.follows.empty": "ဤသူသည် မည်သူ့ကိုမျှ စောင့်ကြည့်ခြင်း မရှိသေးပါ။",
- "account.follows_you": "သင့်ကို စောင့်ကြည့်နေသည်",
"account.go_to_profile": "ပရိုဖိုင်းသို့ သွားရန်",
"account.hide_reblogs": "@{name} ၏ မျှဝေမှုကို ဝှက်ထားရန်",
"account.in_memoriam": "အမှတ်တရ",
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
index 295b420fd4..86617d4a54 100644
--- a/app/javascript/mastodon/locales/nl.json
+++ b/app/javascript/mastodon/locales/nl.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "Geen berichten",
"account.featured_tags.title": "Uitgelichte hashtags van {name}",
"account.follow": "Volgen",
+ "account.follow_back": "Terugvolgen",
"account.followers": "Volgers",
"account.followers.empty": "Deze gebruiker heeft nog geen volgers of heeft deze verborgen.",
"account.followers_counter": "{count, plural, one {{counter} volger} other {{counter} volgers}}",
"account.following": "Volgend",
"account.following_counter": "{count, plural, one {{counter} volgend} other {{counter} volgend}}",
"account.follows.empty": "Deze gebruiker volgt nog niemand of heeft deze verborgen.",
- "account.follows_you": "Volgt jou",
"account.go_to_profile": "Ga naar profiel",
"account.hide_reblogs": "Boosts van @{name} verbergen",
"account.in_memoriam": "In memoriam.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Meldingen negeren",
"account.mute_short": "Negeren",
"account.muted": "Genegeerd",
+ "account.mutual": "Jullie volgen elkaar",
"account.no_bio": "Geen beschrijving opgegeven.",
"account.open_original_page": "Originele pagina openen",
"account.posts": "Berichten",
diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json
index 4750b1adc3..3ef2f80eaf 100644
--- a/app/javascript/mastodon/locales/nn.json
+++ b/app/javascript/mastodon/locales/nn.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "Ingen innlegg",
"account.featured_tags.title": "{name} sine framheva emneknaggar",
"account.follow": "Fylg",
+ "account.follow_back": "Følg tilbake",
"account.followers": "Fylgjarar",
"account.followers.empty": "Ingen fylgjer denne brukaren enno.",
"account.followers_counter": "{count, plural, one {{counter} fylgjar} other {{counter} fylgjarar}}",
"account.following": "Fylgjer",
"account.following_counter": "{count, plural, one {Fylgjer {counter}} other {Fylgjer {counter}}}",
"account.follows.empty": "Denne brukaren fylgjer ikkje nokon enno.",
- "account.follows_you": "Fylgjer deg",
"account.go_to_profile": "Gå til profil",
"account.hide_reblogs": "Skjul framhevingar frå @{name}",
"account.in_memoriam": "Til minne om.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Demp varslingar",
"account.mute_short": "Demp",
"account.muted": "Målbunden",
+ "account.mutual": "Felles",
"account.no_bio": "Inga skildring er gjeven.",
"account.open_original_page": "Opne originalsida",
"account.posts": "Tut",
diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json
index 1739d4aa31..29eaeddff5 100644
--- a/app/javascript/mastodon/locales/no.json
+++ b/app/javascript/mastodon/locales/no.json
@@ -38,7 +38,6 @@
"account.following": "Følger",
"account.following_counter": "{count, plural, one {{counter} som følges} other {{counter} som følges}}",
"account.follows.empty": "Denne brukeren følger ikke noen enda.",
- "account.follows_you": "Følger deg",
"account.go_to_profile": "Gå til profil",
"account.hide_reblogs": "Skjul fremhevinger fra @{name}",
"account.in_memoriam": "Til minne om.",
diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json
index 3812057fb0..833bfe6ace 100644
--- a/app/javascript/mastodon/locales/oc.json
+++ b/app/javascript/mastodon/locales/oc.json
@@ -34,7 +34,6 @@
"account.following": "Abonat",
"account.following_counter": "{count, plural, one {{counter} Abonaments} other {{counter} Abonaments}}",
"account.follows.empty": "Aqueste utilizaire sèc pas degun pel moment.",
- "account.follows_you": "Vos sèc",
"account.go_to_profile": "Anar al perfil",
"account.hide_reblogs": "Rescondre los partatges de @{name}",
"account.in_memoriam": "En Memòria.",
diff --git a/app/javascript/mastodon/locales/pa.json b/app/javascript/mastodon/locales/pa.json
index 708d8c3e98..132b695cda 100644
--- a/app/javascript/mastodon/locales/pa.json
+++ b/app/javascript/mastodon/locales/pa.json
@@ -18,7 +18,6 @@
"account.followers.empty": "ਇਸ ਵਰਤੋਂਕਾਰ ਨੂੰ ਹਾਲੇ ਕੋਈ ਫ਼ਾਲੋ ਨਹੀਂ ਕਰਦਾ ਹੈ।",
"account.following": "ਫ਼ਾਲੋ ਕੀਤਾ",
"account.follows.empty": "ਇਹ ਵਰਤੋਂਕਾਰ ਹਾਲੇ ਕਿਸੇ ਨੂੰ ਫ਼ਾਲੋ ਨਹੀਂ ਕਰਦਾ ਹੈ।",
- "account.follows_you": "ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕਰੋ",
"account.media": "ਮੀਡੀਆ",
"account.muted": "ਮੌਨ ਕੀਤੀਆਂ",
"account.posts": "ਪੋਸਟਾਂ",
diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json
index dc77f8f4e0..9a3710da71 100644
--- a/app/javascript/mastodon/locales/pl.json
+++ b/app/javascript/mastodon/locales/pl.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "Brak postów",
"account.featured_tags.title": "Polecane hasztagi {name}",
"account.follow": "Obserwuj",
+ "account.follow_back": "Obserwuj wzajemnie",
"account.followers": "Obserwujący",
"account.followers.empty": "Nikt jeszcze nie obserwuje tego użytkownika.",
"account.followers_counter": "{count, plural, one {{counter} obserwujący} few {{counter} obserwujących} many {{counter} obserwujących} other {{counter} obserwujących}}",
"account.following": "Obserwowani",
"account.following_counter": "{count, plural, one {{counter} obserwowany} few {{counter} obserwowanych} many {{counter} obserwowanych} other {{counter} obserwowanych}}",
"account.follows.empty": "Ten użytkownik nie obserwuje jeszcze nikogo.",
- "account.follows_you": "Obserwuje Cię",
"account.go_to_profile": "Przejdź do profilu",
"account.hide_reblogs": "Ukryj podbicia od @{name}",
"account.in_memoriam": "Ku pamięci.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Wycisz powiadomienia",
"account.mute_short": "Wycisz",
"account.muted": "Wyciszony",
+ "account.mutual": "Przyjaciele",
"account.no_bio": "Brak opisu.",
"account.open_original_page": "Otwórz stronę oryginalną",
"account.posts": "Wpisy",
diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json
index e7975dd76e..482cc8ee73 100644
--- a/app/javascript/mastodon/locales/pt-BR.json
+++ b/app/javascript/mastodon/locales/pt-BR.json
@@ -38,7 +38,6 @@
"account.following": "Seguindo",
"account.following_counter": "{count, plural, one {segue {counter}} other {segue {counter}}}",
"account.follows.empty": "Nada aqui.",
- "account.follows_you": "te segue",
"account.go_to_profile": "Ir ao perfil",
"account.hide_reblogs": "Ocultar boosts de @{name}",
"account.in_memoriam": "Em memória.",
diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json
index d5055b0dc5..a6d0ffee9a 100644
--- a/app/javascript/mastodon/locales/pt-PT.json
+++ b/app/javascript/mastodon/locales/pt-PT.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "Sem publicações",
"account.featured_tags.title": "#Etiquetas destacadas por {name}",
"account.follow": "Seguir",
+ "account.follow_back": "Seguir de volta",
"account.followers": "Seguidores",
"account.followers.empty": "Ainda ninguém segue este utilizador.",
"account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}",
"account.following": "A seguir",
"account.following_counter": "{count, plural, other {A seguir {counter}}}",
"account.follows.empty": "Este utilizador ainda não segue ninguém.",
- "account.follows_you": "Segue-te",
"account.go_to_profile": "Ir para o perfil",
"account.hide_reblogs": "Esconder partilhas de @{name}",
"account.in_memoriam": "Em Memória.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Silenciar notificações",
"account.mute_short": "Silenciar",
"account.muted": "Silenciada",
+ "account.mutual": "Mútuo",
"account.no_bio": "Nenhuma descrição fornecida.",
"account.open_original_page": "Abrir a página original",
"account.posts": "Publicações",
diff --git a/app/javascript/mastodon/locales/ro.json b/app/javascript/mastodon/locales/ro.json
index 5355f9935a..88dbfa4b8e 100644
--- a/app/javascript/mastodon/locales/ro.json
+++ b/app/javascript/mastodon/locales/ro.json
@@ -36,7 +36,6 @@
"account.following": "Urmăriți",
"account.following_counter": "{count, plural, one {Un abonament} few {{counter} abonamente} other {{counter} de abonamente}}",
"account.follows.empty": "Momentan acest utilizator nu are niciun abonament.",
- "account.follows_you": "Este abonat la tine",
"account.go_to_profile": "Mergi la profil",
"account.hide_reblogs": "Ascunde distribuirile de la @{name}",
"account.joined_short": "Înscris",
diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json
index f0c48236b9..37a8328c98 100644
--- a/app/javascript/mastodon/locales/ru.json
+++ b/app/javascript/mastodon/locales/ru.json
@@ -38,7 +38,6 @@
"account.following": "Подписки",
"account.following_counter": "{count, plural, one {{counter} подписка} many {{counter} подписок} other {{counter} подписки}}",
"account.follows.empty": "Этот пользователь пока ни на кого не подписался.",
- "account.follows_you": "Подписан(а) на вас",
"account.go_to_profile": "Перейти к профилю",
"account.hide_reblogs": "Скрыть продвижения от @{name}",
"account.in_memoriam": "В Памяти.",
diff --git a/app/javascript/mastodon/locales/sa.json b/app/javascript/mastodon/locales/sa.json
index 59379343b9..051ec5d6f8 100644
--- a/app/javascript/mastodon/locales/sa.json
+++ b/app/javascript/mastodon/locales/sa.json
@@ -36,7 +36,6 @@
"account.following": "अनुसरति",
"account.following_counter": "{count, plural, one {{counter} अनुसृतः} two {{counter} अनुसृतौ} other {{counter} अनुसृताः}}",
"account.follows.empty": "न कोऽप्यनुसृतो वर्तते",
- "account.follows_you": "त्वामनुसरति",
"account.go_to_profile": "प्रोफायिलं गच्छ",
"account.hide_reblogs": "@{name} मित्रस्य प्रकाशनानि छिद्यन्ताम्",
"account.in_memoriam": "स्मृत्याम्",
diff --git a/app/javascript/mastodon/locales/sc.json b/app/javascript/mastodon/locales/sc.json
index 7f29525e73..89e951f77d 100644
--- a/app/javascript/mastodon/locales/sc.json
+++ b/app/javascript/mastodon/locales/sc.json
@@ -30,7 +30,6 @@
"account.following": "Sighende",
"account.following_counter": "{count, plural, one {Sighende a {counter}} other {Sighende a {counter}}}",
"account.follows.empty": "Custa persone non sighit ancora a nemos.",
- "account.follows_you": "Ti sighit",
"account.hide_reblogs": "Cua is cumpartziduras de @{name}",
"account.in_memoriam": "In memoriam.",
"account.joined_short": "At aderidu",
diff --git a/app/javascript/mastodon/locales/sco.json b/app/javascript/mastodon/locales/sco.json
index 28dac9c2a2..0378cd2926 100644
--- a/app/javascript/mastodon/locales/sco.json
+++ b/app/javascript/mastodon/locales/sco.json
@@ -35,7 +35,6 @@
"account.following": "Follaein",
"account.following_counter": "{count, plural, one {{counter} Follaein} other {{counter} Follaein}}",
"account.follows.empty": "This uiser disnae follae oniebody yit.",
- "account.follows_you": "Follaes ye",
"account.go_to_profile": "Gang tae profile",
"account.hide_reblogs": "Dinnae shaw heezes fae @{name}",
"account.joined_short": "Jynt",
diff --git a/app/javascript/mastodon/locales/si.json b/app/javascript/mastodon/locales/si.json
index 835f699b82..c2d2a41cc5 100644
--- a/app/javascript/mastodon/locales/si.json
+++ b/app/javascript/mastodon/locales/si.json
@@ -26,7 +26,6 @@
"account.following": "අනුගමන",
"account.following_counter": "{count, plural, one {අනුගමන {counter}} other {අනුගමන {counter}}}",
"account.follows.empty": "තවමත් කිසිවෙක් අනුගමනය නොකරයි.",
- "account.follows_you": "ඔබව අනුගමනය කරයි",
"account.go_to_profile": "පැතිකඩට යන්න",
"account.joined_short": "එක් වූ දිනය",
"account.link_verified_on": "මෙම සබැඳියේ අයිතිය {date} දී පරීක්ෂා කෙරිණි",
diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json
index 0eb4041988..02425d73e0 100644
--- a/app/javascript/mastodon/locales/sk.json
+++ b/app/javascript/mastodon/locales/sk.json
@@ -38,7 +38,6 @@
"account.following": "Sledujem",
"account.following_counter": "{count, plural, one {{counter} Sledovaných} other {{counter} Sledujúcich}}",
"account.follows.empty": "Tento používateľ ešte nikoho nesleduje.",
- "account.follows_you": "Sleduje ťa",
"account.go_to_profile": "Prejdi na profil",
"account.hide_reblogs": "Skry zdieľania od @{name}",
"account.in_memoriam": "In Memoriam.",
diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json
index 4ef2681f9a..b3998b9110 100644
--- a/app/javascript/mastodon/locales/sl.json
+++ b/app/javascript/mastodon/locales/sl.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "Ni objav",
"account.featured_tags.title": "Izpostavljeni ključniki {name}",
"account.follow": "Sledi",
+ "account.follow_back": "Sledi nazaj",
"account.followers": "Sledilci",
"account.followers.empty": "Nihče ne sledi temu uporabniku.",
"account.followers_counter": "{count, plural, one {ima {counter} sledilca} two {ima {counter} sledilca} few {ima {counter} sledilce} other {ima {counter} sledilcev}}",
"account.following": "Sledim",
"account.following_counter": "{count, plural, one {sledi {count} osebi} two {sledi {count} osebama} few {sledi {count} osebam} other {sledi {count} osebam}}",
"account.follows.empty": "Ta uporabnik še ne sledi nikomur.",
- "account.follows_you": "Vam sledi",
"account.go_to_profile": "Pojdi na profil",
"account.hide_reblogs": "Skrij izpostavitve od @{name}",
"account.in_memoriam": "V spomin.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Utišaj obvestila",
"account.mute_short": "Utišaj",
"account.muted": "Utišan",
+ "account.mutual": "Vzajemno",
"account.no_bio": "Ni opisa.",
"account.open_original_page": "Odpri izvirno stran",
"account.posts": "Objave",
diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json
index 710224e1cb..48eac1487e 100644
--- a/app/javascript/mastodon/locales/sq.json
+++ b/app/javascript/mastodon/locales/sq.json
@@ -38,7 +38,6 @@
"account.following": "Ndjekje",
"account.following_counter": "{count, plural, one {{counter} i Ndjekur} other {{counter} të Ndjekur}}",
"account.follows.empty": "Ky përdorues ende s’ndjek kënd.",
- "account.follows_you": "Ju ndjek",
"account.go_to_profile": "Kalo te profili",
"account.hide_reblogs": "Fshih përforcime nga @{name}",
"account.in_memoriam": "In Memoriam.",
diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json
index 35bb8f9929..59ad0ae84e 100644
--- a/app/javascript/mastodon/locales/sr-Latn.json
+++ b/app/javascript/mastodon/locales/sr-Latn.json
@@ -38,7 +38,6 @@
"account.following": "Prati",
"account.following_counter": "{count, plural, one {{counter} prati} few {{counter} prati} other {{counter} prati}}",
"account.follows.empty": "Ovaj korisnik još uvek nikog ne prati.",
- "account.follows_you": "Prati vas",
"account.go_to_profile": "Idi na profil",
"account.hide_reblogs": "Sakrij podržavanja @{name}",
"account.in_memoriam": "U znak sećanja na.",
diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json
index 552c04d13f..79786b8d44 100644
--- a/app/javascript/mastodon/locales/sr.json
+++ b/app/javascript/mastodon/locales/sr.json
@@ -38,7 +38,6 @@
"account.following": "Прати",
"account.following_counter": "{count, plural, one {{counter} прати} few {{counter} прати} other {{counter} прати}}",
"account.follows.empty": "Овај корисник још увек никог не прати.",
- "account.follows_you": "Прати вас",
"account.go_to_profile": "Иди на профил",
"account.hide_reblogs": "Сакриј подржавања од @{name}",
"account.in_memoriam": "У знак сећања на.",
@@ -53,6 +52,7 @@
"account.mute_notifications_short": "Искључи обавештења",
"account.mute_short": "Искључи",
"account.muted": "Игнорисан",
+ "account.mutual": "Заједнички",
"account.no_bio": "Нема описа.",
"account.open_original_page": "Отвори оригиналну страницу",
"account.posts": "Објаве",
diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json
index 8a07da72d8..d3ca776bd9 100644
--- a/app/javascript/mastodon/locales/sv.json
+++ b/app/javascript/mastodon/locales/sv.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "Inga inlägg",
"account.featured_tags.title": "{name}s utvalda hashtaggar",
"account.follow": "Följ",
+ "account.follow_back": "Följ tillbaka",
"account.followers": "Följare",
"account.followers.empty": "Ingen följer denna användare än.",
"account.followers_counter": "{count, plural, one {{counter} följare} other {{counter} följare}}",
"account.following": "Följer",
"account.following_counter": "{count, plural, one {{counter} följd} other {{counter} följda}}",
"account.follows.empty": "Denna användare följer inte någon än.",
- "account.follows_you": "Följer dig",
"account.go_to_profile": "Gå till profilen",
"account.hide_reblogs": "Dölj boostar från @{name}",
"account.in_memoriam": "Till minne av.",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Stäng av aviseringsljud",
"account.mute_short": "Tysta",
"account.muted": "Tystad",
+ "account.mutual": "Ömsesidig",
"account.no_bio": "Ingen beskrivning angiven.",
"account.open_original_page": "Öppna den ursprungliga sidan",
"account.posts": "Inlägg",
diff --git a/app/javascript/mastodon/locales/ta.json b/app/javascript/mastodon/locales/ta.json
index ce9042e62b..6b2332d5b8 100644
--- a/app/javascript/mastodon/locales/ta.json
+++ b/app/javascript/mastodon/locales/ta.json
@@ -25,7 +25,6 @@
"account.following": "பின்தொடரும்",
"account.following_counter": "{count, plural,one {{counter} சந்தா} other {{counter} சந்தாக்கள்}}",
"account.follows.empty": "இந்த பயனர் இதுவரை யாரையும் பின்தொடரவில்லை.",
- "account.follows_you": "உங்களைப் பின்தொடர்கிறார்",
"account.hide_reblogs": "இருந்து ஊக்கியாக மறை @{name}",
"account.link_verified_on": "இந்த இணைப்பை உரிமையாளர் சரிபார்க்கப்பட்டது {date}",
"account.locked_info": "இந்தக் கணக்கு தனியுரிமை நிலை பூட்டப்பட்டுள்ளது. அவர்களைப் பின்தொடர்பவர் யார் என்பதை உரிமையாளர் கைமுறையாக மதிப்பாய்வு செய்கிறார்.",
diff --git a/app/javascript/mastodon/locales/te.json b/app/javascript/mastodon/locales/te.json
index f21c0ef57a..3c231871fa 100644
--- a/app/javascript/mastodon/locales/te.json
+++ b/app/javascript/mastodon/locales/te.json
@@ -12,7 +12,6 @@
"account.followers": "అనుచరులు",
"account.followers.empty": "ఈ వినియోగదారుడిని ఇంకా ఎవరూ అనుసరించడంలేదు.",
"account.follows.empty": "ఈ వినియోగదారి ఇంకా ఎవరినీ అనుసరించడంలేదు.",
- "account.follows_you": "మిమ్మల్ని అనుసరిస్తున్నారు",
"account.hide_reblogs": "@{name} నుంచి బూస్ట్ లను దాచిపెట్టు",
"account.link_verified_on": "ఈ లంకె యొక్క యాజమాన్యం {date}న పరీక్షించబడింది",
"account.locked_info": "ఈ ఖాతా యొక్క గోప్యత స్థితి లాక్ చేయబడి వుంది. ఈ ఖాతాను ఎవరు అనుసరించవచ్చో యజమానే నిర్ణయం తీసుకుంటారు.",
diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json
index 67b920393a..d14e37517d 100644
--- a/app/javascript/mastodon/locales/th.json
+++ b/app/javascript/mastodon/locales/th.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "ไม่มีโพสต์",
"account.featured_tags.title": "แฮชแท็กที่น่าสนใจของ {name}",
"account.follow": "ติดตาม",
+ "account.follow_back": "ติดตามกลับ",
"account.followers": "ผู้ติดตาม",
"account.followers.empty": "ยังไม่มีใครติดตามผู้ใช้นี้",
"account.followers_counter": "{count, plural, other {{counter} ผู้ติดตาม}}",
"account.following": "กำลังติดตาม",
"account.following_counter": "{count, plural, other {{counter} กำลังติดตาม}}",
"account.follows.empty": "ผู้ใช้นี้ยังไม่ได้ติดตามใคร",
- "account.follows_you": "ติดตามคุณ",
"account.go_to_profile": "ไปยังโปรไฟล์",
"account.hide_reblogs": "ซ่อนการดันจาก @{name}",
"account.in_memoriam": "เพื่อระลึกถึง",
diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json
index 8eb09bb7cb..e85db817b9 100644
--- a/app/javascript/mastodon/locales/tr.json
+++ b/app/javascript/mastodon/locales/tr.json
@@ -32,20 +32,20 @@
"account.featured_tags.last_status_never": "Gönderi yok",
"account.featured_tags.title": "{name} kişisinin öne çıkan etiketleri",
"account.follow": "Takip et",
+ "account.follow_back": "Geri takip et",
"account.followers": "Takipçi",
"account.followers.empty": "Henüz kimse bu kullanıcıyı takip etmiyor.",
"account.followers_counter": "{count, plural, one {{counter} Takipçi} other {{counter} Takipçi}}",
"account.following": "Takip Ediliyor",
"account.following_counter": "{count, plural, one {{counter} Takip Edilen} other {{counter} Takip Edilen}}",
"account.follows.empty": "Bu kullanıcı henüz kimseyi takip etmiyor.",
- "account.follows_you": "Seni takip ediyor",
"account.go_to_profile": "Profile git",
"account.hide_reblogs": "@{name} kişisinin boostlarını gizle",
"account.in_memoriam": "Hatırasına.",
"account.joined_short": "Katıldı",
"account.languages": "Abone olunan dilleri değiştir",
"account.link_verified_on": "Bu bağlantının sahipliği {date} tarihinde denetlendi",
- "account.locked_info": "Bu hesabın gizlilik durumu gizli olarak ayarlanmış. Sahibi, onu kimin takip edebileceğini manuel olarak onaylıyor.",
+ "account.locked_info": "Bu hesabın gizlilik durumu gizli olarak ayarlanmış. Sahibi, onu kimin takip edebileceğini elle onaylıyor.",
"account.media": "Medya",
"account.mention": "@{name} kişisinden bahset",
"account.moved_to": "{name} yeni hesabının artık şu olduğunu belirtti:",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "Bildirimleri sessize al",
"account.mute_short": "Sessize al",
"account.muted": "Susturuldu",
+ "account.mutual": "Karşılıklı",
"account.no_bio": "Herhangi bir açıklama belirtilmedi.",
"account.open_original_page": "Asıl sayfayı aç",
"account.posts": "Gönderiler",
@@ -345,7 +346,7 @@
"keyboard_shortcuts.down": "Listede aşağıya inmek için",
"keyboard_shortcuts.enter": "gönderiyi aç",
"keyboard_shortcuts.favourite": "Gönderiyi favorilerine ekle",
- "keyboard_shortcuts.favourites": "Favoriler listeni aç",
+ "keyboard_shortcuts.favourites": "Gözde listeni aç",
"keyboard_shortcuts.federated": "Federe akışı aç",
"keyboard_shortcuts.heading": "Klavye kısayolları",
"keyboard_shortcuts.home": "Ana akışı aç",
diff --git a/app/javascript/mastodon/locales/tt.json b/app/javascript/mastodon/locales/tt.json
index 6727f3e59a..47fe60bd25 100644
--- a/app/javascript/mastodon/locales/tt.json
+++ b/app/javascript/mastodon/locales/tt.json
@@ -35,7 +35,6 @@
"account.following": "Язылулар",
"account.following_counter": "{count, plural, one {{counter} язылу} other {{counter} язылу}}",
"account.follows.empty": "Беркемгә дә язылмаган әле.",
- "account.follows_you": "Сезгә язылган",
"account.go_to_profile": "Профильгә күчү",
"account.hide_reblogs": "Скрывать көчен нче @{name}",
"account.in_memoriam": "Истәлегенә.",
diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json
index 58a14c0ed2..92eacaad14 100644
--- a/app/javascript/mastodon/locales/uk.json
+++ b/app/javascript/mastodon/locales/uk.json
@@ -38,7 +38,6 @@
"account.following": "Ви стежите",
"account.following_counter": "{count, plural, one {{counter} підписка} few {{counter} підписки} many {{counter} підписок} other {{counter} підписки}}",
"account.follows.empty": "Цей користувач ще ні на кого не підписався.",
- "account.follows_you": "Підписується на вас",
"account.go_to_profile": "Перейти до профілю",
"account.hide_reblogs": "Сховати поширення від @{name}",
"account.in_memoriam": "Пам'ятник.",
@@ -53,6 +52,7 @@
"account.mute_notifications_short": "Не сповіщати",
"account.mute_short": "Ігнорувати",
"account.muted": "Приховується",
+ "account.mutual": "Взаємно",
"account.no_bio": "Немає опису.",
"account.open_original_page": "Відкрити оригінальну сторінку",
"account.posts": "Дописи",
diff --git a/app/javascript/mastodon/locales/ur.json b/app/javascript/mastodon/locales/ur.json
index 8fc3aff030..563b2dedf8 100644
--- a/app/javascript/mastodon/locales/ur.json
+++ b/app/javascript/mastodon/locales/ur.json
@@ -32,7 +32,6 @@
"account.following": "فالو کر رہے ہیں",
"account.following_counter": "{count, plural, one {{counter} پیروی کر رہے ہیں} other {{counter} پیروی کر رہے ہیں}}",
"account.follows.empty": "\"یہ صارف ہنوز کسی کی پیروی نہیں کرتا ہے\".",
- "account.follows_you": "آپ کا پیروکار ہے",
"account.go_to_profile": "پروفائل پر جائیں",
"account.hide_reblogs": "@{name} سے فروغ چھپائیں",
"account.in_memoriam": "یادگار میں۔",
diff --git a/app/javascript/mastodon/locales/uz.json b/app/javascript/mastodon/locales/uz.json
index 026cc115c1..8eeee42a5e 100644
--- a/app/javascript/mastodon/locales/uz.json
+++ b/app/javascript/mastodon/locales/uz.json
@@ -35,7 +35,6 @@
"account.following": "Kuzatish",
"account.following_counter": "{count, plural, one {{counter} ga Muxlis} other {{counter} larga muxlis}}",
"account.follows.empty": "Bu foydalanuvchi hali hech kimni kuzatmagan.",
- "account.follows_you": "Sizga obuna",
"account.go_to_profile": "Profilga o'tish",
"account.hide_reblogs": "@{name} dan boostlarni yashirish",
"account.joined_short": "Qo'shilgan",
diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json
index 9ac90b407e..721c7cd4ad 100644
--- a/app/javascript/mastodon/locales/vi.json
+++ b/app/javascript/mastodon/locales/vi.json
@@ -38,7 +38,6 @@
"account.following": "Đang theo dõi",
"account.following_counter": "{count, plural, one {{counter} Theo dõi} other {{counter} Theo dõi}}",
"account.follows.empty": "Người này chưa theo dõi ai.",
- "account.follows_you": "Đang theo dõi bạn",
"account.go_to_profile": "Xem hồ sơ",
"account.hide_reblogs": "Ẩn tút @{name} đăng lại",
"account.in_memoriam": "Tưởng Niệm.",
diff --git a/app/javascript/mastodon/locales/zgh.json b/app/javascript/mastodon/locales/zgh.json
index 5896a25b02..008a9636db 100644
--- a/app/javascript/mastodon/locales/zgh.json
+++ b/app/javascript/mastodon/locales/zgh.json
@@ -12,7 +12,6 @@
"account.edit_profile": "ⵙⵏⴼⵍ ⵉⴼⵔⵙ",
"account.follow": "ⴹⴼⵕ",
"account.followers": "ⵉⵎⴹⴼⴰⵕⵏ",
- "account.follows_you": "ⴹⴼⵕⵏ ⴽⵯⵏ",
"account.media": "ⴰⵙⵏⵖⵎⵉⵙ",
"account.mute": "ⵥⵥⵉⵥⵏ @{name}",
"account.muted": "ⵉⵜⵜⵓⵥⵉⵥⵏ",
diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json
index 1678fd7293..575e0c7aeb 100644
--- a/app/javascript/mastodon/locales/zh-CN.json
+++ b/app/javascript/mastodon/locales/zh-CN.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "暂无嘟文",
"account.featured_tags.title": "{name} 的精选标签",
"account.follow": "关注",
+ "account.follow_back": "回关",
"account.followers": "关注者",
"account.followers.empty": "目前无人关注此用户。",
"account.followers_counter": "被 {counter} 人关注",
"account.following": "正在关注",
"account.following_counter": "正在关注 {counter} 人",
"account.follows.empty": "此用户目前未关注任何人。",
- "account.follows_you": "关注了你",
"account.go_to_profile": "前往个人资料页",
"account.hide_reblogs": "隐藏来自 @{name} 的转嘟",
"account.in_memoriam": "谨此悼念。",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "关闭通知",
"account.mute_short": "隐藏",
"account.muted": "已隐藏",
+ "account.mutual": "互相关注",
"account.no_bio": "未提供描述。",
"account.open_original_page": "打开原始页面",
"account.posts": "嘟文",
diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json
index 263c707993..cd0845b6e2 100644
--- a/app/javascript/mastodon/locales/zh-HK.json
+++ b/app/javascript/mastodon/locales/zh-HK.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "暫無文章",
"account.featured_tags.title": "{name} 的精選標籤",
"account.follow": "關注",
+ "account.follow_back": "追蹤對方",
"account.followers": "追蹤者",
"account.followers.empty": "尚未有人追蹤這位使用者。",
"account.followers_counter": "有 {count, plural,one {{counter} 個} other {{counter} 個}}追蹤者",
"account.following": "正在追蹤",
"account.following_counter": "正在追蹤 {count, plural,one {{counter}}other {{counter} 人}}",
"account.follows.empty": "這位使用者尚未追蹤任何人。",
- "account.follows_you": "追蹤你",
"account.go_to_profile": "前往個人檔案",
"account.hide_reblogs": "隱藏 @{name} 的轉推",
"account.in_memoriam": "謹此悼念。",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "靜音通知",
"account.mute_short": "靜音",
"account.muted": "靜音",
+ "account.mutual": "互相追蹤",
"account.no_bio": "未提供描述。",
"account.open_original_page": "打開原始頁面",
"account.posts": "帖文",
diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json
index 8484e00c83..9983936953 100644
--- a/app/javascript/mastodon/locales/zh-TW.json
+++ b/app/javascript/mastodon/locales/zh-TW.json
@@ -32,13 +32,13 @@
"account.featured_tags.last_status_never": "沒有嘟文",
"account.featured_tags.title": "{name} 的推薦主題標籤",
"account.follow": "跟隨",
+ "account.follow_back": "跟隨回去",
"account.followers": "跟隨者",
"account.followers.empty": "尚未有人跟隨這位使用者。",
"account.followers_counter": "被 {count, plural, other {{counter} 人}}跟隨",
"account.following": "跟隨中",
"account.following_counter": "正在跟隨 {count,plural,other {{counter} 人}}",
"account.follows.empty": "這位使用者尚未跟隨任何人。",
- "account.follows_you": "跟隨了您",
"account.go_to_profile": "前往個人檔案",
"account.hide_reblogs": "隱藏來自 @{name} 的轉嘟",
"account.in_memoriam": "謹此悼念。",
@@ -53,6 +53,7 @@
"account.mute_notifications_short": "靜音推播通知",
"account.mute_short": "靜音",
"account.muted": "已靜音",
+ "account.mutual": "互相跟隨",
"account.no_bio": "無個人檔案描述",
"account.open_original_page": "檢視原始頁面",
"account.posts": "嘟文",
diff --git a/app/lib/content_security_policy.rb b/app/lib/content_security_policy.rb
index 966e41f03b..21c04c89fd 100644
--- a/app/lib/content_security_policy.rb
+++ b/app/lib/content_security_policy.rb
@@ -10,11 +10,15 @@ class ContentSecurityPolicy
end
def media_hosts
- [assets_host, cdn_host_value].compact
+ [assets_host, cdn_host_value, 'https://media.tenor.com'].concat(extra_data_hosts).compact
end
private
+ def extra_data_hosts
+ ENV.fetch('EXTRA_DATA_HOSTS', '').split('|')
+ end
+
def url_from_configured_asset_host
Rails.configuration.action_controller.asset_host
end
diff --git a/app/lib/potential_friendship_tracker.rb b/app/lib/potential_friendship_tracker.rb
deleted file mode 100644
index ab1cfeb742..0000000000
--- a/app/lib/potential_friendship_tracker.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-class PotentialFriendshipTracker
- EXPIRE_AFTER = 90.days.seconds
- MAX_ITEMS = 80
-
- WEIGHTS = {
- reply: 1,
- reaction: 5,
- favourite: 10,
- reblog: 20,
- }.freeze
-
- class << self
- include Redisable
-
- def record(account_id, target_account_id, action)
- return if account_id == target_account_id
-
- key = "interactions:#{account_id}"
- weight = WEIGHTS[action]
-
- redis.zincrby(key, weight, target_account_id)
- redis.zremrangebyrank(key, 0, -MAX_ITEMS)
- redis.expire(key, EXPIRE_AFTER)
- end
-
- def remove(account_id, target_account_id)
- redis.zrem("interactions:#{account_id}", target_account_id)
- end
- end
-end
diff --git a/app/models/account_domain_block.rb b/app/models/account_domain_block.rb
index db2e37184f..753935d6af 100644
--- a/app/models/account_domain_block.rb
+++ b/app/models/account_domain_block.rb
@@ -19,6 +19,7 @@ class AccountDomainBlock < ApplicationRecord
validates :domain, presence: true, uniqueness: { scope: :account_id }, domain: true
after_commit :invalidate_domain_blocking_cache
+ after_commit :invalidate_follow_recommendations_cache
private
@@ -26,4 +27,8 @@ class AccountDomainBlock < ApplicationRecord
Rails.cache.delete("exclude_domains_for:#{account_id}")
Rails.cache.delete(['exclude_domains', account_id, domain])
end
+
+ def invalidate_follow_recommendations_cache
+ Rails.cache.delete("follow_recommendations/#{account_id}")
+ end
end
diff --git a/app/models/account_suggestions.rb b/app/models/account_suggestions.rb
index d1774e62fa..d62176c7ca 100644
--- a/app/models/account_suggestions.rb
+++ b/app/models/account_suggestions.rb
@@ -1,28 +1,48 @@
# frozen_string_literal: true
class AccountSuggestions
+ include DatabaseHelper
+
SOURCES = [
AccountSuggestions::SettingSource,
- AccountSuggestions::PastInteractionsSource,
+ AccountSuggestions::FriendsOfFriendsSource,
+ AccountSuggestions::SimilarProfilesSource,
AccountSuggestions::GlobalSource,
].freeze
- def self.get(account, limit)
- SOURCES.each_with_object([]) do |source_class, suggestions|
- source_suggestions = source_class.new.get(
- account,
- skip_account_ids: suggestions.map(&:account_id),
- limit: limit - suggestions.size
- )
+ BATCH_SIZE = 40
- suggestions.concat(source_suggestions)
+ def initialize(account)
+ @account = account
+ end
+
+ def get(limit, offset = 0)
+ with_read_replica do
+ account_ids_with_sources = Rails.cache.fetch("follow_recommendations/#{@account.id}", expires_in: 15.minutes) do
+ SOURCES.flat_map { |klass| klass.new.get(@account, limit: BATCH_SIZE) }.each_with_object({}) do |(account_id, source), h|
+ (h[account_id] ||= []).concat(Array(source).map(&:to_sym))
+ end.to_a.shuffle
+ end
+
+ # The sources deliver accounts that haven't yet been followed, are not blocked,
+ # and so on. Since we reset the cache on follows, blocks, and so on, we don't need
+ # a complicated query on this end.
+
+ account_ids = account_ids_with_sources[offset, limit]
+ accounts_map = Account.where(id: account_ids.map(&:first)).includes(:account_stat).index_by(&:id)
+
+ account_ids.filter_map do |(account_id, source)|
+ next unless accounts_map.key?(account_id)
+
+ AccountSuggestions::Suggestion.new(
+ account: accounts_map[account_id],
+ source: source
+ )
+ end
end
end
- def self.remove(account, target_account_id)
- SOURCES.each do |source_class|
- source = source_class.new
- source.remove(account, target_account_id)
- end
+ def remove(target_account_id)
+ FollowRecommendationMute.create(account_id: @account.id, target_account_id: target_account_id)
end
end
diff --git a/app/models/account_suggestions/friends_of_friends_source.rb b/app/models/account_suggestions/friends_of_friends_source.rb
new file mode 100644
index 0000000000..28d0ab99b3
--- /dev/null
+++ b/app/models/account_suggestions/friends_of_friends_source.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+class AccountSuggestions::FriendsOfFriendsSource < AccountSuggestions::Source
+ def get(account, limit: 10)
+ Account.find_by_sql([<<~SQL.squish, { id: account.id, limit: limit }]).map { |row| [row.id, key] }
+ WITH first_degree AS (
+ SELECT target_account_id
+ FROM follows
+ JOIN accounts AS target_accounts ON follows.target_account_id = target_accounts.id
+ WHERE account_id = :id
+ AND NOT target_accounts.hide_collections
+ )
+ SELECT accounts.id, COUNT(*) AS frequency
+ FROM accounts
+ JOIN follows ON follows.target_account_id = accounts.id
+ JOIN account_stats ON account_stats.account_id = accounts.id
+ LEFT OUTER JOIN follow_recommendation_mutes ON follow_recommendation_mutes.target_account_id = accounts.id AND follow_recommendation_mutes.account_id = :id
+ WHERE follows.account_id IN (SELECT * FROM first_degree)
+ AND NOT EXISTS (SELECT 1 FROM follows f WHERE f.target_account_id = follows.target_account_id AND f.account_id = :id)
+ AND follows.target_account_id <> :id
+ AND accounts.discoverable
+ AND accounts.suspended_at IS NULL
+ AND accounts.silenced_at IS NULL
+ AND accounts.moved_to_account_id IS NULL
+ AND follow_recommendation_mutes.target_account_id IS NULL
+ GROUP BY accounts.id, account_stats.id
+ ORDER BY frequency DESC, account_stats.followers_count ASC
+ LIMIT :limit
+ SQL
+ end
+
+ private
+
+ def key
+ :friends_of_friends
+ end
+end
diff --git a/app/models/account_suggestions/global_source.rb b/app/models/account_suggestions/global_source.rb
index 651041d675..d68f285e4f 100644
--- a/app/models/account_suggestions/global_source.rb
+++ b/app/models/account_suggestions/global_source.rb
@@ -1,39 +1,13 @@
# frozen_string_literal: true
class AccountSuggestions::GlobalSource < AccountSuggestions::Source
- include Redisable
-
- def key
- :global
- end
-
- def get(account, skip_account_ids: [], limit: 40)
- account_ids = account_ids_for_locale(I18n.locale.to_s.split(/[_-]/).first) - [account.id] - skip_account_ids
-
- as_ordered_suggestions(
- scope(account).where(id: account_ids),
- account_ids
- ).take(limit)
- end
-
- def remove(_account, _target_account_id)
- nil
+ def get(account, limit: 10)
+ FollowRecommendation.localized(content_locale).joins(:account).merge(base_account_scope(account)).order(rank: :desc).limit(limit).pluck(:account_id, :reason)
end
private
- def scope(account)
- Account.searchable
- .followable_by(account)
- .not_excluded_by_account(account)
- .not_domain_blocked_by_account(account)
- end
-
- def account_ids_for_locale(locale)
- redis.zrevrange("follow_recommendations:#{locale}", 0, -1).map(&:to_i)
- end
-
- def to_ordered_list_key(account)
- account.id
+ def content_locale
+ I18n.locale.to_s.split(/[_-]/).first
end
end
diff --git a/app/models/account_suggestions/past_interactions_source.rb b/app/models/account_suggestions/past_interactions_source.rb
deleted file mode 100644
index d169394f11..0000000000
--- a/app/models/account_suggestions/past_interactions_source.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-class AccountSuggestions::PastInteractionsSource < AccountSuggestions::Source
- include Redisable
-
- def key
- :past_interactions
- end
-
- def get(account, skip_account_ids: [], limit: 40)
- account_ids = account_ids_for_account(account.id, limit + skip_account_ids.size) - skip_account_ids
-
- as_ordered_suggestions(
- scope.where(id: account_ids),
- account_ids
- ).take(limit)
- end
-
- def remove(account, target_account_id)
- redis.zrem("interactions:#{account.id}", target_account_id)
- end
-
- private
-
- def scope
- Account.searchable
- end
-
- def account_ids_for_account(account_id, limit)
- redis.zrevrange("interactions:#{account_id}", 0, limit).map(&:to_i)
- end
-
- def to_ordered_list_key(account)
- account.id
- end
-end
diff --git a/app/models/account_suggestions/setting_source.rb b/app/models/account_suggestions/setting_source.rb
index 6185732b4b..4b7275bf7a 100644
--- a/app/models/account_suggestions/setting_source.rb
+++ b/app/models/account_suggestions/setting_source.rb
@@ -1,32 +1,18 @@
# frozen_string_literal: true
class AccountSuggestions::SettingSource < AccountSuggestions::Source
- def key
- :staff
- end
-
- def get(account, skip_account_ids: [], limit: 40)
- return [] unless setting_enabled?
-
- as_ordered_suggestions(
- scope(account).where(setting_to_where_condition).where.not(id: skip_account_ids),
- usernames_and_domains
- ).take(limit)
- end
-
- def remove(_account, _target_account_id)
- nil
+ def get(account, limit: 10)
+ if setting_enabled?
+ base_account_scope(account).where(setting_to_where_condition).limit(limit).pluck(:id).zip([key].cycle)
+ else
+ []
+ end
end
private
- def scope(account)
- Account.searchable
- .followable_by(account)
- .not_excluded_by_account(account)
- .not_domain_blocked_by_account(account)
- .where(locked: false)
- .where.not(id: account.id)
+ def key
+ :featured
end
def usernames_and_domains
@@ -61,8 +47,4 @@ class AccountSuggestions::SettingSource < AccountSuggestions::Source
def setting
Setting.bootstrap_timeline_accounts
end
-
- def to_ordered_list_key(account)
- [account.username.downcase, account.domain&.downcase]
- end
end
diff --git a/app/models/account_suggestions/similar_profiles_source.rb b/app/models/account_suggestions/similar_profiles_source.rb
new file mode 100644
index 0000000000..733c5f0bbc
--- /dev/null
+++ b/app/models/account_suggestions/similar_profiles_source.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+class AccountSuggestions::SimilarProfilesSource < AccountSuggestions::Source
+ class QueryBuilder < AccountSearchService::QueryBuilder
+ def must_clauses
+ [
+ {
+ more_like_this: {
+ fields: %w(text text.stemmed),
+ like: @query.map { |id| { _index: 'accounts', _id: id } },
+ },
+ },
+
+ {
+ term: {
+ properties: 'discoverable',
+ },
+ },
+ ]
+ end
+
+ def must_not_clauses
+ [
+ {
+ terms: {
+ id: following_ids,
+ },
+ },
+
+ {
+ term: {
+ properties: 'bot',
+ },
+ },
+ ]
+ end
+
+ def should_clauses
+ {
+ term: {
+ properties: {
+ value: 'verified',
+ boost: 2,
+ },
+ },
+ }
+ end
+ end
+
+ def get(account, limit: 10)
+ recently_followed_account_ids = account.active_relationships.recent.limit(5).pluck(:target_account_id)
+
+ if Chewy.enabled? && !recently_followed_account_ids.empty?
+ QueryBuilder.new(recently_followed_account_ids, account).build.limit(limit).hits.pluck('_id').map(&:to_i).zip([key].cycle)
+ else
+ []
+ end
+ rescue Faraday::ConnectionFailed
+ []
+ end
+
+ private
+
+ def key
+ :similar_to_recently_followed
+ end
+end
diff --git a/app/models/account_suggestions/source.rb b/app/models/account_suggestions/source.rb
index 504d26a8bd..ee93a1342f 100644
--- a/app/models/account_suggestions/source.rb
+++ b/app/models/account_suggestions/source.rb
@@ -1,34 +1,18 @@
# frozen_string_literal: true
class AccountSuggestions::Source
- def key
- raise NotImplementedError
- end
-
def get(_account, **kwargs)
raise NotImplementedError
end
- def remove(_account, target_account_id)
- raise NotImplementedError
- end
-
protected
- def as_ordered_suggestions(scope, ordered_list)
- return [] if ordered_list.empty?
-
- map = scope.index_by { |account| to_ordered_list_key(account) }
-
- ordered_list.filter_map { |ordered_list_key| map[ordered_list_key] }.map do |account|
- AccountSuggestions::Suggestion.new(
- account: account,
- source: key
- )
- end
- end
-
- def to_ordered_list_key(_account)
- raise NotImplementedError
+ def base_account_scope(account)
+ Account.searchable
+ .followable_by(account)
+ .not_excluded_by_account(account)
+ .not_domain_blocked_by_account(account)
+ .where.not(id: account.id)
+ .joins("LEFT OUTER JOIN follow_recommendation_mutes ON follow_recommendation_mutes.target_account_id = accounts.id AND follow_recommendation_mutes.account_id = #{account.id}").where(follow_recommendation_mutes: { target_account_id: nil })
end
end
diff --git a/app/models/block.rb b/app/models/block.rb
index 11156ebab3..5476542a5a 100644
--- a/app/models/block.rb
+++ b/app/models/block.rb
@@ -26,15 +26,20 @@ class Block < ApplicationRecord
end
before_validation :set_uri, only: :create
- after_commit :remove_blocking_cache
+ after_commit :invalidate_blocking_cache
+ after_commit :invalidate_follow_recommendations_cache
private
- def remove_blocking_cache
+ def invalidate_blocking_cache
Rails.cache.delete("exclude_account_ids_for:#{account_id}")
Rails.cache.delete("exclude_account_ids_for:#{target_account_id}")
end
+ def invalidate_follow_recommendations_cache
+ Rails.cache.delete("follow_recommendations/#{account_id}")
+ end
+
def set_uri
self.uri = ActivityPub::TagManager.instance.generate_uri_for(self) if uri.nil?
end
diff --git a/app/models/concerns/account/associations.rb b/app/models/concerns/account/associations.rb
index bd79516749..68b562e672 100644
--- a/app/models/concerns/account/associations.rb
+++ b/app/models/concerns/account/associations.rb
@@ -65,6 +65,7 @@ module Account::Associations
has_one :deletion_request, class_name: 'AccountDeletionRequest', inverse_of: :account, dependent: :destroy
# Follow recommendations
+ has_one :follow_recommendation, inverse_of: :account, dependent: nil
has_one :follow_recommendation_suppression, inverse_of: :account, dependent: :destroy
# Account statuses cleanup policy
diff --git a/app/models/concerns/account/interactions.rb b/app/models/concerns/account/interactions.rb
index c0bdfb2d82..a7ff55e5c2 100644
--- a/app/models/concerns/account/interactions.rb
+++ b/app/models/concerns/account/interactions.rb
@@ -116,8 +116,6 @@ module Account::Interactions
rel.save! if rel.changed?
- remove_potential_friendship(other_account)
-
rel
end
@@ -131,13 +129,10 @@ module Account::Interactions
rel.save! if rel.changed?
- remove_potential_friendship(other_account)
-
rel
end
def block!(other_account, uri: nil)
- remove_potential_friendship(other_account)
block_relationships.create_with(uri: uri)
.find_or_create_by!(target_account: other_account)
end
@@ -148,8 +143,6 @@ module Account::Interactions
mute.expires_in = duration.zero? ? nil : duration
mute.save!
- remove_potential_friendship(other_account)
-
# When toggling a mute between hiding and allowing notifications, the mute will already exist, so the find_or_create_by! call will return the existing Mute without updating the hide_notifications attribute. Therefore, we check that hide_notifications? is what we want and set it if it isn't.
mute.update!(hide_notifications: notifications) if mute.hide_notifications? != notifications
@@ -311,10 +304,4 @@ module Account::Interactions
domain_blocking_by_domain: Account.domain_blocking_map_by_domain(domains, id),
})
end
-
- private
-
- def remove_potential_friendship(other_account)
- PotentialFriendshipTracker.remove(id, other_account.id)
- end
end
diff --git a/app/models/concerns/account/search.rb b/app/models/concerns/account/search.rb
index 40d87aaaa1..077e5d57b1 100644
--- a/app/models/concerns/account/search.rb
+++ b/app/models/concerns/account/search.rb
@@ -116,6 +116,7 @@ module Account::Search
[].tap do |properties|
properties << 'bot' if bot?
properties << 'verified' if fields.any?(&:verified?)
+ properties << 'discoverable' if discoverable?
end
end
diff --git a/app/models/follow.rb b/app/models/follow.rb
index 108f5c5d51..4d1598dcad 100644
--- a/app/models/follow.rb
+++ b/app/models/follow.rb
@@ -44,10 +44,10 @@ class Follow < ApplicationRecord
before_validation :set_uri, only: :create
after_create :increment_cache_counters
- after_create :invalidate_hash_cache
after_destroy :remove_endorsements
after_destroy :decrement_cache_counters
- after_destroy :invalidate_hash_cache
+ after_commit :invalidate_follow_recommendations_cache
+ after_commit :invalidate_hash_cache
private
@@ -74,4 +74,8 @@ class Follow < ApplicationRecord
Rails.cache.delete("followers_hash:#{target_account_id}:#{account.synchronization_uri_prefix}")
end
+
+ def invalidate_follow_recommendations_cache
+ Rails.cache.delete("follow_recommendations/#{account_id}")
+ end
end
diff --git a/app/models/follow_recommendation_filter.rb b/app/models/follow_recommendation_filter.rb
index 2fab975698..62a02eba5a 100644
--- a/app/models/follow_recommendation_filter.rb
+++ b/app/models/follow_recommendation_filter.rb
@@ -17,12 +17,9 @@ class FollowRecommendationFilter
def results
if params['status'] == 'suppressed'
- Account.joins(:follow_recommendation_suppression).order(FollowRecommendationSuppression.arel_table[:id].desc).to_a
+ Account.includes(:account_stat).joins(:follow_recommendation_suppression).order(FollowRecommendationSuppression.arel_table[:id].desc)
else
- account_ids = redis.zrevrange("follow_recommendations:#{@language}", 0, -1).map(&:to_i)
- accounts = Account.where(id: account_ids).index_by(&:id)
-
- account_ids.filter_map { |id| accounts[id] }
+ Account.includes(:account_stat).joins(:follow_recommendation).merge(FollowRecommendation.localized(@language).order(rank: :desc))
end
end
end
diff --git a/app/models/follow_recommendation_mute.rb b/app/models/follow_recommendation_mute.rb
new file mode 100644
index 0000000000..d166d0a620
--- /dev/null
+++ b/app/models/follow_recommendation_mute.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: follow_recommendation_mutes
+#
+# id :bigint(8) not null, primary key
+# account_id :bigint(8) not null
+# target_account_id :bigint(8) not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+class FollowRecommendationMute < ApplicationRecord
+ belongs_to :account
+ belongs_to :target_account, class_name: 'Account'
+
+ validates :target_account, uniqueness: { scope: :account_id }
+
+ after_commit :invalidate_follow_recommendations_cache
+
+ private
+
+ def invalidate_follow_recommendations_cache
+ Rails.cache.delete("follow_recommendations/#{account_id}")
+ end
+end
diff --git a/app/models/follow_recommendation_suppression.rb b/app/models/follow_recommendation_suppression.rb
index e261a2fe35..59e94dc6b3 100644
--- a/app/models/follow_recommendation_suppression.rb
+++ b/app/models/follow_recommendation_suppression.rb
@@ -11,19 +11,5 @@
#
class FollowRecommendationSuppression < ApplicationRecord
- include Redisable
-
belongs_to :account
-
- after_commit :remove_follow_recommendations, on: :create
-
- private
-
- def remove_follow_recommendations
- redis.pipelined do |pipeline|
- I18n.available_locales.each do |locale|
- pipeline.zrem("follow_recommendations:#{locale}", account_id)
- end
- end
- end
end
diff --git a/app/models/follow_request.rb b/app/models/follow_request.rb
index a5c23e09d4..3c5e8f96f0 100644
--- a/app/models/follow_request.rb
+++ b/app/models/follow_request.rb
@@ -45,10 +45,15 @@ class FollowRequest < ApplicationRecord
end
before_validation :set_uri, only: :create
+ after_commit :invalidate_follow_recommendations_cache
private
def set_uri
self.uri = ActivityPub::TagManager.instance.generate_uri_for(self) if uri.nil?
end
+
+ def invalidate_follow_recommendations_cache
+ Rails.cache.delete("follow_recommendations/#{account_id}")
+ end
end
diff --git a/app/models/mute.rb b/app/models/mute.rb
index 8fc5422624..1d18b30eea 100644
--- a/app/models/mute.rb
+++ b/app/models/mute.rb
@@ -23,11 +23,16 @@ class Mute < ApplicationRecord
validates :account_id, uniqueness: { scope: :target_account_id }
- after_commit :remove_blocking_cache
+ after_commit :invalidate_blocking_cache
+ after_commit :invalidate_follow_recommendations_cache
private
- def remove_blocking_cache
+ def invalidate_blocking_cache
Rails.cache.delete("exclude_account_ids_for:#{account_id}")
end
+
+ def invalidate_follow_recommendations_cache
+ Rails.cache.delete("follow_recommendations/#{account_id}")
+ end
end
diff --git a/app/models/preview_cards_status.rb b/app/models/preview_cards_status.rb
index 214eec22e5..5ff6352055 100644
--- a/app/models/preview_cards_status.rb
+++ b/app/models/preview_cards_status.rb
@@ -4,8 +4,8 @@
#
# Table name: preview_cards_statuses
#
-# preview_card_id :bigint(8) not null
-# status_id :bigint(8) not null
+# preview_card_id :bigint(8) not null, primary key
+# status_id :bigint(8) not null, primary key
# url :string
#
class PreviewCardsStatus < ApplicationRecord
diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb
index 7b85b09d8f..571a0fa57d 100644
--- a/app/services/account_search_service.rb
+++ b/app/services/account_search_service.rb
@@ -23,6 +23,7 @@ class AccountSearchService < BaseService
query: {
bool: {
must: must_clauses,
+ must_not: must_not_clauses,
},
},
@@ -49,6 +50,10 @@ class AccountSearchService < BaseService
end
end
+ def must_not_clauses
+ []
+ end
+
def should_clauses
if @account && !@options[:following]
[boost_following_query]
diff --git a/app/services/favourite_service.rb b/app/services/favourite_service.rb
index 6fdc92a173..ded50187f7 100644
--- a/app/services/favourite_service.rb
+++ b/app/services/favourite_service.rb
@@ -20,7 +20,7 @@ class FavouriteService < BaseService
Trends.statuses.register(status)
create_notification(favourite)
- bump_potential_friendship(account, status)
+ increment_statistics
favourite
end
@@ -37,11 +37,8 @@ class FavouriteService < BaseService
end
end
- def bump_potential_friendship(account, status)
+ def increment_statistics
ActivityTracker.increment('activity:interactions')
- return if account.following?(status.account_id)
-
- PotentialFriendshipTracker.record(account.id, status.account_id, :favourite)
end
def build_json(favourite)
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index 1c2d4d4a70..a437feb0fd 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -194,9 +194,6 @@ class PostStatusService < BaseService
return if !@status.reply? || @account.id == @status.in_reply_to_account_id
ActivityTracker.increment('activity:interactions')
- return if @account.following?(@status.in_reply_to_account_id)
-
- PotentialFriendshipTracker.record(@account.id, @status.in_reply_to_account_id, :reply)
end
def status_attributes
diff --git a/app/services/react_service.rb b/app/services/react_service.rb
index 4ac220f1e9..cfc3f99a78 100644
--- a/app/services/react_service.rb
+++ b/app/services/react_service.rb
@@ -19,7 +19,7 @@ class ReactService < BaseService
Trends.statuses.register(status)
create_notification(reaction)
- bump_potential_friendship(account, status)
+ increment_statistics
reaction
end
@@ -36,11 +36,8 @@ class ReactService < BaseService
end
end
- def bump_potential_friendship(account, status)
+ def increment_statistics
ActivityTracker.increment('activity:interactions')
- return if account.following?(status.account_id)
-
- PotentialFriendshipTracker.record(account.id, status.account_id, :reaction)
end
def build_json(reaction)
diff --git a/app/services/reblog_service.rb b/app/services/reblog_service.rb
index b73669f9d8..18e1748cea 100644
--- a/app/services/reblog_service.rb
+++ b/app/services/reblog_service.rb
@@ -33,7 +33,7 @@ class ReblogService < BaseService
ActivityPub::DistributionWorker.perform_async(reblog.id) unless reblogged_status.local_only?
create_notification(reblog)
- bump_potential_friendship(account, reblog)
+ increment_statistics
reblog
end
@@ -50,12 +50,8 @@ class ReblogService < BaseService
end
end
- def bump_potential_friendship(account, reblog)
+ def increment_statistics
ActivityTracker.increment('activity:interactions')
-
- return if account.following?(reblog.reblog.account_id)
-
- PotentialFriendshipTracker.record(account.id, reblog.reblog.account_id, :reblog)
end
def build_json(reblog)
diff --git a/app/views/admin/follow_recommendations/show.html.haml b/app/views/admin/follow_recommendations/show.html.haml
index dc65a72135..9c2063d3c5 100644
--- a/app/views/admin/follow_recommendations/show.html.haml
+++ b/app/views/admin/follow_recommendations/show.html.haml
@@ -38,3 +38,5 @@
= nothing_here 'nothing-here--under-tabs'
- else
= render partial: 'account', collection: @accounts, locals: { f: f }
+
+= paginate @accounts
diff --git a/app/workers/scheduler/follow_recommendations_scheduler.rb b/app/workers/scheduler/follow_recommendations_scheduler.rb
index ea59ad986b..ea5aa8b539 100644
--- a/app/workers/scheduler/follow_recommendations_scheduler.rb
+++ b/app/workers/scheduler/follow_recommendations_scheduler.rb
@@ -2,61 +2,11 @@
class Scheduler::FollowRecommendationsScheduler
include Sidekiq::Worker
- include Redisable
sidekiq_options retry: 0, lock: :until_executed, lock_ttl: 1.day.to_i
- # The maximum number of accounts that can be requested in one page from the
- # API is 80, and the suggestions API does not allow pagination. This number
- # leaves some room for accounts being filtered during live access
- SET_SIZE = 100
-
def perform
- # Maintaining a materialized view speeds-up subsequent queries significantly
AccountSummary.refresh
FollowRecommendation.refresh
-
- fallback_recommendations = FollowRecommendation.order(rank: :desc).limit(SET_SIZE)
-
- Trends.available_locales.each do |locale|
- recommendations = if AccountSummary.safe.filtered.localized(locale).exists? # We can skip the work if no accounts with that language exist
- FollowRecommendation.localized(locale).order(rank: :desc).limit(SET_SIZE).map { |recommendation| [recommendation.rank, recommendation.account_id] }
- else
- []
- end
-
- # Use language-agnostic results if there are not enough language-specific ones
- missing = SET_SIZE - recommendations.size
-
- if missing.positive? && fallback_recommendations.size.positive?
- max_fallback_rank = fallback_recommendations.first.rank || 0
-
- # Language-specific results should be above language-agnostic ones,
- # otherwise language-agnostic ones will always overshadow them
- recommendations.map! { |(rank, account_id)| [rank + max_fallback_rank, account_id] }
-
- added = 0
-
- fallback_recommendations.each do |recommendation|
- next if recommendations.any? { |(_, account_id)| account_id == recommendation.account_id }
-
- recommendations << [recommendation.rank, recommendation.account_id]
- added += 1
-
- break if added >= missing
- end
- end
-
- redis.multi do |multi|
- multi.del(key(locale))
- multi.zadd(key(locale), recommendations)
- end
- end
- end
-
- private
-
- def key(locale)
- "follow_recommendations:#{locale}"
end
end
diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb
index 4d1c7c6383..afc3e6841a 100644
--- a/config/initializers/content_security_policy.rb
+++ b/config/initializers/content_security_policy.rb
@@ -6,13 +6,11 @@
# See the Securing Rails Applications Guide for more information:
# https://guides.rubyonrails.org/security.html#content-security-policy-header
-def host_to_url(str)
- return if str.blank?
+require_relative '../../app/lib/content_security_policy'
- uri = Addressable::URI.parse("http#{Rails.configuration.x.use_https ? 's' : ''}://#{str}")
- uri.path += '/' unless uri.path.blank? || uri.path.end_with?('/')
- uri.to_s
-end
+policy = ContentSecurityPolicy.new
+assets_host = policy.assets_host
+media_hosts = policy.media_hosts
def sso_host
return unless ENV['ONE_CLICK_SSO_LOGIN'] == 'true'
@@ -32,51 +30,35 @@ def sso_host
end
end
-unless Rails.env.development?
- assets_host = Rails.configuration.action_controller.asset_host || "https://#{ENV['WEB_DOMAIN'] || Rails.configuration.x.local_domain}"
- data_hosts = [assets_host, 'https://media.tenor.com']
+Rails.application.config.content_security_policy do |p|
+ p.base_uri :none
+ p.default_src :none
+ p.frame_ancestors :none
+ p.font_src :self, assets_host
+ p.img_src :self, :data, :blob, *media_hosts
+ p.style_src :self, assets_host
+ p.media_src :self, :data, *media_hosts
+ p.frame_src :self, :https
+ p.manifest_src :self, assets_host
- if ENV['S3_ENABLED'] == 'true' || ENV['AZURE_ENABLED'] == 'true'
- attachments_host = host_to_url(ENV['S3_ALIAS_HOST'] || ENV['S3_CLOUDFRONT_HOST'] || ENV['AZURE_ALIAS_HOST'] || ENV['S3_HOSTNAME'] || "s3-#{ENV['S3_REGION'] || 'us-east-1'}.amazonaws.com")
- elsif ENV['SWIFT_ENABLED'] == 'true'
- attachments_host = ENV.fetch('SWIFT_OBJECT_URL')
- attachments_host = "https://#{Addressable::URI.parse(attachments_host).host}"
+ if sso_host.present?
+ p.form_action :self, sso_host
else
- attachments_host = nil
+ p.form_action :self
end
- data_hosts << attachments_host unless attachments_host.nil?
+ p.child_src :self, :blob, assets_host
+ p.worker_src :self, :blob, assets_host
- if ENV['PAPERCLIP_ROOT_URL']
- url = Addressable::URI.parse(assets_host) + ENV['PAPERCLIP_ROOT_URL']
- data_hosts << "https://#{url.host}"
- end
+ if Rails.env.development?
+ webpacker_public_host = ENV.fetch('WEBPACKER_DEV_SERVER_PUBLIC', Webpacker.config.dev_server[:public])
+ webpacker_urls = %w(ws http).map { |protocol| "#{protocol}#{Webpacker.dev_server.https? ? 's' : ''}://#{webpacker_public_host}" }
- data_hosts.concat(ENV['EXTRA_DATA_HOSTS'].split('|')) if ENV['EXTRA_DATA_HOSTS']
-
- data_hosts.uniq!
-
- Rails.application.config.content_security_policy do |p|
- p.base_uri :none
- p.default_src :none
- p.frame_ancestors :none
- p.font_src :self, assets_host
- p.img_src :self, :data, :blob, *data_hosts
- p.style_src :self, assets_host
- p.media_src :self, :data, *data_hosts
- p.frame_src :self, :https
- p.manifest_src :self, assets_host
-
- if sso_host.present?
- p.form_action :self, sso_host
- else
- p.form_action :self
- end
-
- p.child_src :self, :blob, assets_host
- p.worker_src :self, :blob, assets_host
- p.connect_src :self, :blob, :data, Rails.configuration.x.streaming_api_base_url, *data_hosts, 'https://api.tenor.com'
- p.script_src :self, assets_host, "'wasm-unsafe-eval'"
+ p.connect_src :self, :data, :blob, *media_hosts, Rails.configuration.x.streaming_api_base_url, 'https://api.tenor.com', *webpacker_urls
+ p.script_src :self, :unsafe_inline, :unsafe_eval, assets_host
+ else
+ p.connect_src :self, :data, :blob, *media_hosts, Rails.configuration.x.streaming_api_base_url, 'https://api.tenor.com'
+ p.script_src :self, assets_host, "'wasm-unsafe-eval'"
end
end
diff --git a/config/locales/activerecord.lad.yml b/config/locales/activerecord.lad.yml
index 625d897d0b..71a63d1cd1 100644
--- a/config/locales/activerecord.lad.yml
+++ b/config/locales/activerecord.lad.yml
@@ -33,6 +33,10 @@ lad:
attributes:
data:
malformed: tiene formato yerrado
+ status:
+ attributes:
+ reblog:
+ taken: de publikasyon ya existe
user:
attributes:
email:
@@ -49,3 +53,7 @@ lad:
position:
elevated: no puede ser mas alto ke tu rolo aktual
own_role: no se puede trokar kon tu rolo aktual
+ webhook:
+ attributes:
+ events:
+ invalid_permissions: no puedes inkluir evenimientos a los kualos no estas autorizado
diff --git a/config/locales/devise.ie.yml b/config/locales/devise.ie.yml
index 16d51dba16..0cf0fbe1fe 100644
--- a/config/locales/devise.ie.yml
+++ b/config/locales/devise.ie.yml
@@ -2,8 +2,16 @@
ie:
devise:
failure:
+ already_authenticated: Tu ha ja intrat.
+ inactive: Tui conto ancor ne ha esset activat.
invalid: Ínvalid %{authentication_keys} o passa-parol.
+ last_attempt: Hay solmen un prova ante que tui conto deveni serrat.
+ locked: Tui conto es serrat.
not_found_in_database: Ínvalid %{authentication_keys} o passa-parol.
+ pending: Tui conto es ancor sub revision.
+ timeout: Tui session ha expirat. Ples reintrar denov por continuar.
+ unauthenticated: Tu deve intrar o registrar te ante continuar.
+ unconfirmed: Tu deve confirmar tui e-posta ante continuar.
mailer:
email_changed:
extra: Si tu ne changeat tui email-adresse, it es probabil que alqui ha ganiat accesse a tui conto. Ples changear tui passa-parol strax o contacter li administrator del servitor si tu ne posse intrar tui conto.
@@ -20,9 +28,13 @@ ie:
title: Reiniciar passa-parol
two_factor_disabled:
explanation: 2-factor autentication por tui conto ha esset desactivisat. Aperter session nu es possibil solmen per email-adresse e passa-parol.
+ omniauth_callbacks:
+ failure: Ne posset autenticar te de %{kind} pro "%{reason}".
passwords:
no_token: Tu ne posse accessar ti-ci págine sin venir de un email pri reiniciar li passa-parol. Si tu ha venit de un email pri reiniciar li passa-parol, ples far cert que tu usat li complet URL providet.
send_instructions: Si tui email-adresse existe in nor database, tu va reciver un ligament por recuperar li passa-parol a tui email-adresse in quelc minutes. Ples vider tui spam-emails si tu ne recivet ti email.
send_paranoid_instructions: Si tui email-adresse existe in nor database, tu va reciver un ligament por recuperar li passa-parol a tui email-adresse in quelc minutes. Ples vider tui spam-emails si tu ne recivet ti email.
updated: Tui passa-parol ha esset changeat successosimen. Tu nu ha apertet session.
updated_not_active: Tui passa-parol ha esset changeat successosimen.
+ registrations:
+ signed_up: Benevenit! Tu ha successat registrar te.
diff --git a/config/locales/ie.yml b/config/locales/ie.yml
index 3a85602ba5..6013923e3f 100644
--- a/config/locales/ie.yml
+++ b/config/locales/ie.yml
@@ -119,6 +119,7 @@ ie:
remote_suspension_reversible_hint_html: Li conto ha esset suspendet che su servitor, e li data va esser completmen removet ye %{date}. Til tande, li lontan servitor posse restaurar ti conto sin quelcunc mal efectes. Si tu vole remover omni data del conto ínmediatmen, tu posse far it in infra.
remove_avatar: Remover li avatar
removed_avatar_msg: Successosimen removet li avatar-image de %{username}
+ removed_header_msg: Successosimen removet li cap-image de %{username}
resend_confirmation:
already_confirmed: Ti usator ja esset confirmat
send: Inviar li confirmation denov
@@ -280,6 +281,7 @@ ie:
update_announcement_html: "%{name} actualisat li proclamation %{target}"
update_custom_emoji_html: "%{name} actualisat emoji %{target}"
update_domain_block_html: "%{name} actualisat li dominia-blocca de %{target}"
+ update_ip_block_html: "%{name} changeat regul por IP %{target}"
update_status_html: "%{name} actualisat posta de %{target}"
update_user_role_html: "%{name} changeat li rol %{target}"
deleted_account: deletet conto
@@ -327,6 +329,7 @@ ie:
title: Adjunter nov customisat emoji
no_emoji_selected: Null emoji esset changeat pro que null esset selectet
not_permitted: Tu ne es permisset far ti action
+ overwrite: Remplazzar
shortcode_hint: Adminim 2 carácteres, solmen lítteres, ciffres e sublineas
title: Customisat emoji
uncategorized: Íncategorisat
@@ -431,6 +434,7 @@ ie:
title: Bloccar nov email-dominia
no_email_domain_block_selected: Null email-dominia-bloccas esset changeat pro que null esset selectet
not_permitted: Ne permisset
+ resolved_through_html: Resoluet per %{domain}
title: Bloccat email-dominias
export_domain_allows:
new:
@@ -448,6 +452,7 @@ ie:
title: Importar dominia-bloccas
no_file: Null file selectet
follow_recommendations:
+ description_html: "Seque-recomandationes auxilia nov usatores trovar interessant contenete. Quande un usator ne ha interactet con altres suficentmen por generar personalisat seque-recomandationes, ti contos es recomandat vicemen. Ili es recalculat dialmen de un mixtura de contos con li max mult recent interactiones e li max alt númeres de local sequitores por un specificat lingue."
language: Por li lingue
status: Statu
suppress: Supresser seque-recomandation
@@ -456,6 +461,7 @@ ie:
unsuppress: Restaurar seque-recomandation
instances:
availability:
+ no_failures_recorded: Null fallimentes registrat.
title: Disponibilitá
warning: Li ultim prova conexer a ti servitor ha esset ínsuccessosi
back_to_all: Omni
@@ -465,11 +471,30 @@ ie:
confirm_purge: Vole tu vermen permanentmen deleter data de ti dominia?
content_policies:
comment: Internal nota
+ policies:
+ reject_media: Rejecter medie
+ reject_reports: Rejecter raportes
+ silence: Limitar
+ suspend: Suspender
policy: Politica
reason: Visibil rason
title: Politicas pri contenete
+ dashboard:
+ instance_accounts_dimension: Max sequet contos
+ instance_accounts_measure: inmagasinat contos
+ instance_followers_measure: nor sequitores ta
+ instance_follows_measure: lor sequitores ci
+ instance_languages_dimension: Max grand lingues
+ instance_statuses_measure: salvat postas
delivery:
+ all: Omni
+ clear: Aclarar errores de liveration
+ failing: Fallint
+ restart: Recomensar liveration
+ stop: Haltar liveration
unavailable: Índisponibil
+ delivery_available: Liveration es disponibil
+ delivery_error_days: Dies de errores de liveration
delivery_error_hint: Si liveration ne es possibil durant %{count} dies, it va esser marcat automaticmen quam ínliverabil.
destroyed_msg: Data de %{domain} es nu in li linea por iminent deletion.
empty: Null dominias trovat.
@@ -547,6 +572,9 @@ ie:
other_description_html: Vider plu optiones por controlar li conduida del conto e customisar comunication al raportat conto.
resolve_description_html: Null action va esser fat contra li raportat conto, null admoniment registrat, e li raporte va esser cludet.
silence_description_html: Li conto va esser visibil nur a tis qui ja seque it o qui sercha it manualmen, limitante severimen su atingement. Ti sempre posse esser revertet. Ti clude omni raportes contra ti conto.
+ suspend_description_html: Ti-ci conto e omni contenete va esser ínaccessibil e finalmen deletet, e interacter con it va esser ínpossibil. Reversibil til que 30 dies ha passat. Clude omni raportes contra ti conto.
+ actions_description_html: Decider quel action a far por soluer ti raporte. Si tu fa un punitiv action contra li raportat conto, li usator va reciver un notification per email, except in li casu que li Spam categorie es selectet.
+ actions_description_remote_html: Decider quel action a far por soluer ti raporte. It va afecter solmen qualmen vor servitor comunica con ti lontan conto e tractar su contenete.
add_to_report: Adjunter plu al raporte
are_you_sure: Es tu cert?
assign_to_self: Assignar it a me
@@ -562,6 +590,9 @@ ie:
confirm_action: Confirmar moderatori action contra @%{acct}
created_at: Raportat
delete_and_resolve: Deleter postas
+ forwarded: Reinviat
+ forwarded_replies_explanation: Ti-ci raporte es de un lontan usator e pri lontan contenete. It ha esset reinviat a vos pro que li raportat contenete es un response a un de tui usatores.
+ forwarded_to: Misset anc a %{domain}
mark_as_resolved: Marcar quam soluet
mark_as_sensitive: Marcar quam sensitiv
mark_as_unresolved: Marcar quam ínsoluet
@@ -586,72 +617,383 @@ ie:
skip_to_actions: Ear rect al actiones
status: Statu
statuses: Contenete raportat
+ statuses_description_html: Ofensiv contenete va esser citat in comunication con li raportat conto
+ summary:
+ action_preambles:
+ delete_html: 'Tu va remover alcun postas de @%{acct}''. To va:'
+ mark_as_sensitive_html: 'Tu va marcar alcun postas de @%{acct} quam sensitiv. To va:'
+ silence_html: 'Tu va limitar li conto de @%{acct}. To va:'
+ suspend_html: 'Tu va suspender li conto de @%{acct}. To va:'
+ actions:
+ delete_html: Remover li ofendent postas
+ mark_as_sensitive_html: Marcar li medie del ofendent postas quam sensitiv
+ silence_html: Severimen limitar li atingement de @%{acct} per far su profil e contenete visibil solmen a persones qui ja seque ilu o qui sercha su profil manualmen
+ suspend_html: Suspender @%{acct}, fante su profil e contenete ínaccessibil e ínpossibil con quel interacter
+ close_report: 'Marcar raporte #%{id} quam resoluet'
+ close_reports_html: Marcar omni raportes contra @%{acct} quam soluet
+ delete_data_html: Deleter li profil e contenete de @%{acct} 30 dies pos nu, except si ilu es dessuspendet ante tande
+ preview_preamble_html: "@%{acct} va reciver un admoniment con li sequent contenete:"
+ record_strike_html: Registrar un admoniment contra @%{acct} por auxiliar vos tractar futur violationes de ti-ci conto
+ send_email_html: Misser un admoniment-email a @%{acct}
+ warning_placeholder: Ínobligatori additional rason por li moderatori action.
+ target_origin: Orígine del conto raportat
title: Raportes
+ unassign: Ínassignar
+ unknown_action_msg: 'Ínconosset action: %{action}'
+ unresolved: Ínresoluet
+ updated_at: Actualisat
+ view_profile: Vider profil
roles:
+ add_new: Adjunter un rol
+ assigned_users:
+ one: "%{count} usator"
+ other: "%{count} usatores"
categories:
+ administration: Administration
+ devops: DevOps
+ invites: Invitationes
moderation: Moderation
+ special: Special
+ delete: Deleter
+ description_html: Con roles por usatores, tu posse customisar li functiones e locs de Mastodon in queles tui usatores posse accesser.
+ edit: Modificar rol '%{name}'
+ everyone: Permissiones predefinit
+ permissions_count:
+ one: "%{count} permission"
+ other: "%{count} permissiones"
privileges:
administrator: Administrator
+ delete_user_data: Deleter Data de Usator
+ delete_user_data_description: Possibilisa que usatores mey deleter li data de altri usatores strax
+ invite_users: Invitar Usatores
+ invite_users_description: Permisse que usatores invita novones al servitor
manage_announcements: Tractar proclamationes
manage_announcements_description: Permisse usatores tractar proclamationes sur li servitor
+ manage_appeals: Gerer Apelles
+ manage_blocks: Gerer Bloccas
+ manage_blocks_description: Permisse que usatores blocca provisores de e-posta e IP-adresses
+ manage_custom_emojis: Gerer Customisat Emojis
+ manage_custom_emojis_description: Permisse que usatores gere customisat emojis sur li servitor
+ manage_federation: Gerer Federation
+ manage_federation_description: Permisse que usatores sive blocca sive permisse federation con altri domenes, e controla liverabilitá
+ manage_invites: Gerer Invitationes
+ manage_reports: Gerer Raportes
+ manage_roles: Gerer Roles
+ manage_rules: Gerer Regules
+ manage_rules_description: Permisse que usatores changea regules del servitor
+ manage_settings: Gerer Parametres
+ manage_settings_description: Permisse que usatores changea parametres del situ
+ manage_taxonomies: Gerer Taxonomies
+ manage_user_access: Gerer Usator-Accesse
manage_user_access_description: Permisse usatores desactivisar li 2-factor autentication de altri usatores, changear lor email-adresses, e reiniciar lor passa-paroles
manage_users: Gerer usatores
+ manage_webhooks: Gerer Web-crocs
+ view_audit_log_description: Permisse que usatores vide li historie de administrativ actiones sur li servitor
+ view_devops: DevOps
title: Roles
rules:
add_new: Adjunter un regule
+ delete: Deleter
+ edit: Redacter regul
+ empty: Ancor null regules de servitor ha esset definit.
+ title: Regules del servitor
settings:
about:
+ manage_rules: Gerer regules de servitor
title: Pri
appearance:
+ preamble: Customisar li interfacie web de Mastodon.
title: Aspecte
+ discovery:
+ follow_recommendations: Seque-recomandationes
+ preamble: Exposir interessant contenete es importantissim por incorporar nov usatores qui fórsan conosse nequi che Mastodon. Decider qualmen diferent utensiles de decovrition functiona che vor servitor.
+ profile_directory: Profilarium
+ public_timelines: Public témpor-lineas
+ publish_discovered_servers: Publicar decovrit servitores
+ publish_statistics: Publicar statisticas
+ title: Decovriment
+ trends: Tendenties
domain_blocks:
+ all: Ad omnes
+ disabled: A necun
users: A local usatores qui ha initiat session
+ registrations:
+ preamble: Decider qui posse crear un conto che vor servitor.
+ title: Registrationes
+ registrations_mode:
+ modes:
+ approved: Aprobation besonat por adhesion
+ none: Nequi posse registrar se
+ open: Quicunc posse registrar se
+ security:
+ authorized_fetch: Postular autentication de federat servitores
+ authorized_fetch_hint: Postular autentication de federat servitores possibilisa plu strict infortiament de ambi usatori e servitori bloccas. Támen, ti fórsan va limitar li potentie de vor servitor, reducter li atingement de vor responses, e possibilmen introducter problemas de compatibilitá con quelc federat servicies. Additionalmen, ti ne va preventer dedicat actores de accesser vor public postas e contos.
+ authorized_fetch_overridden_hint: Tu actualmen ne posse changear ti parametre pro que it es controlat de un environmental variabile.
+ federation_authentication: Infortiation de federational autentication
+ title: Parametres del servitor
site_uploads:
delete: Deleter cargat file
destroyed_msg: Cargat file successosimen deletet!
software_updates:
- type: Tip
+ critical_update: Critic — ples actualisar rapidmen
+ description: On recomanda que vu actualisa vor Mastodon-servitor regularimen por profiter del max recent fixes e facultates. In plu, quelcvez it es critic actualisar Mastodon promptmen por evitar problemas de securitá. Pro ti rasones, Mastodon questiona chascun 30 minutes ca hay actualisationes, e va notificar vos secun vor parametres pri email-notificationes.
+ documentation_link: Aprender plu
+ title: Actualisationes disponibil
+ type: Specie
version: Version
statuses:
account: Autor
+ application: Aplication
+ back_to_account: Retornar al págine del conto
+ back_to_report: Retornar al págine del raporte
+ batch:
+ remove_from_report: Remover de raporte
+ report: Raportar
+ deleted: Deletet
+ favourites: Favorites
+ history: Historie de versiones
+ in_reply_to: Respondent a
language: Lingue
+ media:
+ title: Medie
metadata: Metadata
+ no_status_selected: Null postas esset changeat pro que null esset selectet
+ open: Aperter posta
+ original_status: Original posta
+ reblogs: Boosts
+ status_changed: Posta modificat
+ title: Postas del conto
+ trending: Populari
visibility: Visibilitá
+ with_media: Con medie
+ strikes:
+ actions:
+ delete_statuses: "%{name} deletet li postas de %{target}"
+ disable: "%{name} gelat li conto de %{target}"
+ mark_statuses_as_sensitive: "%{name} marcat li postas de %{target} quam sensitiv"
+ none: "%{name} misset un admoniment a %{target}"
+ sensitive: "%{name} marcat li conto de %{target} quam sensitiv"
+ silence: "%{name} limitat li conto de %{target}"
+ suspend: "%{name} suspendet li conto de %{target}"
+ appeal_approved: Apellat
+ appeal_pending: Apelle pendent
+ appeal_rejected: Apelle rejectet
+ system_checks:
+ database_schema_check:
+ message_html: Hay pendent migrationes de database. Ples far les por far cert que li aplication functiona quam expectat
+ elasticsearch_preset:
+ action: Vider li documentation
+ elasticsearch_preset_single_node:
+ action: Vider li documentation
+ rules_check:
+ action: Gerer regules de servitor
+ software_version_critical_check:
+ message_html: Un critical actualisation por Mastodon es disposibil, ples actualisar tam rapidmen possibil.
+ software_version_patch_check:
+ action: Vider actualisationes disponibil
title: Administration
trends:
+ allow: Permisser
approved: Aprobat
+ disallow: Despermisser
+ links:
+ allow: Permisser ligament
+ allow_provider: Permisser editor
+ description_html: Ci hay ligamentes actualmen partit per mult contos sur queles tui servitor vide postas. It posse auxiliar tui usatores saver to quo eveni in li munde. Null ligamentes es monstrat publicmen ante que tu aproba li publicator. Tu posse anc aprobar o rejecter índividual ligamentes.
+ disallow: Despermisser ligament
+ disallow_provider: Despermisser editor
+ no_link_selected: Null ligamentes esset changeat pro que null esset selectet
+ publishers:
+ no_publisher_selected: Null editores esset changeat pro que necun esset selectet
+ title: Ligamentes in tendentie
+ usage_comparison: Partit %{today} vezes hodie, in comparation a %{yesterday} yer
+ not_allowed_to_trend: Ne permisset esser in tendentie
+ pending_review: Sub inspection
+ preview_card_providers:
+ allowed: Ligamentes de ti-ci editor posse esser in tendentie
+ description_html: Tis es dominias de queles ligamentes es sovente distribuet che vor servitor. Ligamentes ne va intrar li tendenties publicmen except si li dominia del ligament es aprobat. Vor aprobation (o rejection) aplica anc a subdominias.
+ rejected: Ligamentes de ti publicator ne va intrar li tendenties
+ title: Editores
+ rejected: Rejectet
statuses:
+ allow: Permisser posta
+ allow_account: Permisser autor
+ description_html: Tis es postas queles vor servitor conosse queles on actualmen distribue e favoritisa mult. It posse auxiliar vor nov e retornant usatores por trovar plu persones por sequer. Null postas es monstrat publicmen til que vu aproba li autor, e li autor permisse que su conto es suggestet a altres. Vu anc posse permisser o rejecter individual postas.
+ disallow: Despermisser posta
+ disallow_account: Despermisser autor
+ no_status_selected: Null populari postas esset changeat pro que null esset selectet
+ not_discoverable: Autor ne ha consentit esser decovribil
shared_by:
one: Partit o favoritisat un vez
other: Partit e favoritisat %{friendly_count} vezes
+ title: Populari postas
tags:
+ current_score: Actual puntes %{score}
+ dashboard:
+ tag_accounts_measure: unic usationes
+ tag_languages_dimension: Max usat lingues
+ tag_servers_dimension: Max populari servitores
+ tag_servers_measure: diferent servitores
+ tag_uses_measure: total usationes
+ description_html: Vi hashtags actualmen aparient in mult postas queles vor servitor vide. It posse auxiliar vor usatores decovrir pri quel gente parla nu. Null hashtags es monstrat til que vu aproba les.
listable: Suggestibil
+ no_tag_selected: Null hashtags esset changeat pro que null esset selectet
not_listable: Ne suggestibil
+ not_trendable: Ne va aparir in tendenties
not_usable: Prohibit
+ peaked_on_and_decaying: Atinget su cime ye %{date}, nu deperient
+ title: Populari hashtags
+ trendable: Posse aparir in tendenties
+ trending_rank: 'Tendentie #%{rank}'
usable: Permisset
usage_comparison: 'Usat hodie: %{today} vez(es), yer: %{yesterday}'
+ used_by_over_week:
+ one: Usat de un person durant li ultim semane
+ other: Usat de %{count} persones durant li ultim semane
+ title: Tendenties
+ trending: Populari
+ warning_presets:
+ add_new: Adjunter un nov
+ delete: Deleter
+ edit_preset: Modificar prefiguration de avise
+ empty: Vu ancor ha definit null prefigurationes de avise.
+ title: Modificar prefigurationes de avise
webhooks:
+ enabled: Activ
events: Evenimentes
status: Statu
+ admin_mailer:
+ new_trends:
+ new_trending_links:
+ title: Populari ligamentes
+ new_trending_statuses:
+ title: Populari postas
+ new_trending_tags:
+ title: Populari hashtags
+ subject: Nov tendenties a inspecter sur %{instance}
+ aliases:
+ add_new: Crear alias
+ created_msg: Successosimen creat un nov alias. Tu nu posse initiar li movement del antiqui conto.
+ deleted_msg: Successosimen removet li alias. Mover de ti-ta conto a ti-ci ne plu va esser possibil.
+ empty: Tu have null aliases.
+ hint_html: Si tu vole mover de un altri conto a ti-ci, ci tu posse crear un alias, quel es besonat ante que tu posse proceder a mover sequitores del antiqui conto a ti-ci. Ti-ci action, sol, es ínnociv e reversibil. Li conto-migration es initiat del antiqui conto.
+ remove: Desconexer alias
+ appearance:
+ advanced_web_interface: Avansat web-interfacie
+ advanced_web_interface_hint: 'Si tu vole usar li tot largore de tui ecran, li avansat web-interfacie permisse que tu mey configurar mult columnes diferent por vider tam mult information simultanmen quam tu vole: Hem, federat témpor-linea, quelcunc númere de listes e hashtags.'
+ animations_and_accessibility: Animationes e accessibilitá
+ confirmation_dialogs: Dialogs de confirmation
+ discovery: Decovriment
+ localization:
+ body: Mastodon es traductet de voluntarios.
+ guide_link: https://crowdin.com/project/mastodon
+ guide_link_text: Omnes posse contribuer.
+ sensitive_content: Sensitiv contenete
application_mailer:
+ notification_preferences: Changear parametres pri email
salutation: "%{name},"
+ settings: 'Changear parametres pri email: %{link}'
+ unsubscribe: Desabonnar
+ view: 'Vider:'
+ view_profile: Vider profil
+ view_status: Vider posta
+ applications:
+ created: Aplication successosimen creat
+ destroyed: Aplication successosimen deletet
+ logout: Exear
+ regenerate_token: Regenerar accesse-clave
+ token_regenerated: Accesse-clave successosimen regenerat
+ warning: Esse tre cuidosi pri ti-ci data. Nequande parti it a quicunc!
+ your_token: Tui accesse-clave
auth:
+ apply_for_account: Solicitar un conto
+ captcha_confirmation:
+ help_html: Si tu have desfacilitá solver li CAPTCHA, tu posse contacter nos a %{email} e noi posse auxiliar te.
+ hint_html: Just un cose plu! Noi deve confirmar que tu es homan (por que noi posse impedir li spam!). Solue li CAPTCHA in-infra e clicca "Avansar".
+ title: Control de securitá
confirmations:
+ awaiting_review: Tui email-adresse es confirmat! Li personale de %{domain} nu va tractar tui registration. Tu va reciver un email si ili aproba tui conto!
+ awaiting_review_title: On tracta tui registration
+ clicking_this_link: cliccar ti-ci ligament
+ login_link: intrar
+ proceed_to_login_html: Tu nu posse proceder a %{login_link}.
+ redirect_to_app_html: Tu deve har esset redirectet al aplication %{app_name}. Si to ne evenit, prova %{clicking_this_link} o retornar manualmen al aplication.
+ registration_complete: Tu ha completat tui registration che %{domain}!
welcome_title: Benevenit, %{name}!
+ wrong_email_hint: Si ti email-adresse ne es corect, tu posse changear it in li parametres de conto.
delete_account: Deleter li conto
+ delete_account_html: Si tu vole deleter tui conto, tu posse proceder ci. On va petir confirmation de te.
+ description:
+ prefix_invited_by_user: "@%{name} invita te adherer ti-ci servitor de Mastodon!"
+ prefix_sign_up: Adherer Mastodon hodie!
+ suffix: Med un conto, tu va posser sequer persones, postar actualisationes e exchangear missages con usatores de quelcunc Mastodon-servitor e plu!
+ didnt_get_confirmation: Ne recivet un confirmation-ligament?
+ dont_have_your_security_key: Ne have tui clave de securitá?
forgot_password: Obliviat tu tui passa-parol?
invalid_reset_password_token: Li clave por reiniciar li passa-parol es ínvalid o expirat. Ples demandar un nov.
+ link_to_otp: Introducte un 2-factor code de tui telefon o un code de recuperation
+ link_to_webauth: Usa tui aparate de clave de securitá
+ log_in_with: Intrar med
+ login: Intrar
+ logout: Exear
+ migrate_account: Mover te a un conto diferent
+ migrate_account_html: Si tu vole redirecter ti-ci conto a un altri, tu posse configurar it ci.
+ or_log_in_with: O intrar med
+ privacy_policy_agreement_html: Yo leet e consenti li politica pri privatie
+ progress:
+ confirm: Confirmar email
+ details: Tui detallies
+ review: Nor revise
+ rules: Acceptar regules
+ providers:
+ cas: CAS
+ saml: SAML
+ register: Adherer
+ registration_closed: "%{instance} ne accepta nov membres"
+ resend_confirmation: Reinviar ligament de confirmation
reset_password: Reiniciar passa-parol
rules:
accept: Acceptar
back: Retro
+ invited_by: 'Tu posse adherer %{domain} mersí al invitation quel tu recivet de:'
+ preamble: Tis es etablisset e infortiat del moderatores de %{domain}.
+ preamble_invited: Ante que tu procede, ples considerar li regules establisset del moderatores de %{domain}.
+ title: Quelc regules basic.
+ title_invited: Tu ha esset invitat.
security: Securitá
set_new_password: Establisser nov passa-parol
+ setup:
+ email_below_hint_html: Vider tui spam-emails, o petir un altre. Tu posse corectar tui email-adresse si it es íncorect.
+ email_settings_hint_html: Clicca li ligament quel noi inviat a te por verificar %{email}. Noi va atender ci.
+ link_not_received: Recivet null ligament?
+ new_confirmation_instructions_sent: Tu va reciver un nov email con li ligament de confirmation in quelc minutes!
+ title: Vider tui inbuxe
+ sign_in:
+ preamble_html: Aperter session med tui %{domain} detallies. Si tui conto logia che un diferent servitor, tu ne va posser intrar ci.
+ title: Aperter session che %{domain}
+ sign_up:
+ manual_review: Adhesiones a %{domain} es tractat manualmen de nor moderatores. Por auxiliar nos tractar tui aplication, scri un poc pri te e pro quo tu vole un conto che %{domain}.
+ preamble: Med un conto che ti-ci Mastodon-servitor, tu va posser sequer quelcunc altri person in li retage, sin egarda a u logia su conto.
+ title: Crear un conto che %{domain}.
+ status:
+ account_status: Statu del conto
+ confirming: Atendent li confirmation del email-adresse.
+ functional: Tui conto es completmen functional.
+ pending: Tu conto atende tractation de nor personale. Ti fórsan va durar quelc témpor. Tu va reciver un email si tui aplication es aprobat.
+ redirecting_to: Tui conto es ínactiv pro que it actualmen redirecte a %{acct}.
+ self_destruct: Pro que %{domain} va cluder bentost, tu have solmen limitat accesse a tui conto.
+ view_strikes: Vide anteyan admonimentes contra tui conto
+ too_fast: Formul inviat tro rapid, prova denov.
+ use_security_key: Usar clave de securitá
challenge:
- confirm: Continuar
+ confirm: Avansar
hint_html: "Nota: On ne va petir tui passa-parol denov por li venient hor."
invalid_password: Ínvalid passa-parol
prompt: Confirmar passa-parol por avansar
+ crypto:
+ errors:
+ invalid_key: ne es un valid clave Ed25519 o Curve25519
+ invalid_signature: ne es un valid signatura Ed25519
date:
formats:
default: "%d.%m.%Y"
@@ -659,73 +1001,234 @@ ie:
datetime:
distance_in_words:
about_x_hours: "%{count}h"
+ about_x_months: "%{count}me"
about_x_years: "%{count}a"
almost_x_years: "%{count}a"
+ half_a_minute: Just nu
+ less_than_x_minutes: "%{count}m"
+ less_than_x_seconds: Just nu
+ over_x_years: "%{count}a"
+ x_days: "%{count}d"
+ x_minutes: "%{count}m"
+ x_months: "%{count}me"
x_seconds: "%{count}s"
deletes:
+ challenge_not_passed: Li information quel tu dat ne esset corect
confirm_password: Introducte tui actual passa-parol por verificar tui identitá
+ confirm_username: Scri tu usator-nómine por confirmar li procedura
proceed: Deleter li conto
+ success_msg: Tui conto esset successosimen deletet
+ warning:
+ before: 'Ante proceder, ples leer ti notas cuidosimen:'
+ caches: Contenete quel ha esset inmagasinat de altri servitores fórsan va persister
+ data_removal: Tui postas e altri data va esser permanentmen removet
+ email_change_html: Tui posse changear tui email-adresse sin deleter tui conto
+ email_contact_html: Si ancor it ne ariva, tu posse inviar un email a %{email} por auxilie
+ email_reconfirmation_html: Si tu ne recive li confirmation-email, tu posse solicita it denov
+ irreversible: Tu ne va posser restaurar o reactivisar tui conto
+ more_details_html: Por plu mult detallies, vide li politica pri privatie.
+ username_available: Tui usator-nómine va retornar a esser disponibil
+ username_unavailable: Tui usator-nómine va restar índisponibil
disputes:
strikes:
+ action_taken: Action fat
appeal: Apellar
+ appeal_approved: Ti admoniment ha esset apellat successosimen e nu es ínvalid
+ appeal_rejected: Li apelle ha esset rejectet
+ appeal_submitted_at: Apelle inviat
+ appealed_msg: Tui apelle ha esset inviat. Si it es aprobat, tu va esser notificat.
+ appeals:
+ submit: Inviar apelle
+ approve_appeal: Aprobar apelle
+ associated_report: Associat raporte
+ created_at: Date
+ description_html: Tis es li actiones fat contra tui conto e admonimentes misset a te del personale de %{instance}.
+ recipient: Adressat a
+ reject_appeal: Rejecter apelle
+ status: 'Posta #%{id}'
+ status_removed: Posta ja removet del sistema
+ title: "%{action} de %{date}"
+ title_actions:
+ delete_statuses: Removement de posta
+ disable: Gelation de conto
+ mark_statuses_as_sensitive: Marcation de postas quam sensitiv
+ none: Admoniment
+ sensitive: Marcation de conto quam sensitiv
+ silence: Limitation de conto
+ suspend: Suspension de conto
+ your_appeal_approved: Tui apelle ha esset aprobat
+ your_appeal_pending: Tu ha fat un apelle
+ your_appeal_rejected: Tui apelle ha esset rejectet
+ domain_validator:
+ invalid_domain: ne es un valid dominia-nómine
+ edit_profile:
+ basic_information: Basic information
+ hint_html: "Customisa ti quel gente vide sur tui public profil e apu tui postas. Altri persones es plu probabil sequer te e interacter con te si tu have un detalliat profil e un profil-image."
+ other: Altri
+ errors:
+ '400': Li demande quel tu inviat esset ínvalid o misformat.
+ '403': Tu ne have permission vider ti-ci págine.
+ '404': Li págine quel tu sercha ne es ci.
+ '406': Ti-ci págine ne es disponibil in li formate petit.
+ '410': Li págine quel tu serchat ne plu existe ci.
+ '422':
+ content: Verification de securitá fallit. Esque tu blocca cookies?
+ title: Verification de securitá fallit
+ '429': Tro mult demandes
+ '500':
+ content: Pardona nos, alquo errat in nor servitor.
+ title: Ti-ci págine ne es corect
+ '503': On ne posset servir li págine pro un falliment temporari del servitor.
+ noscript_html: Por usar li web-aplication de Mastodon, ples activisar JavaScript. Alternativmen, prova un del nativ aplicationes por Mastodon por tui platform.
+ existing_username_validator:
+ not_found: ne posset trovar un local usator con ti usator-nómine
+ not_found_multiple: ne posset trovar %{usernames}
exports:
archive_takeout:
date: Date
+ download: Descargar tui archive
+ hint_html: Tu posse petir un archive de tui postas e cargat medie. Li exportat data va esser in li formate ActivityPub, leibil de quelcunc programmatura concordant. Tu posse petir un archive chascun 7 dies.
+ in_progress: Compilant tui archive...
+ request: Petir tui archive
size: Grandore
blocks: Tu ha bloccat
bookmarks: Marcatores
csv: CSV
+ domain_blocks: Dominia-bloccas
lists: Listes
mutes: Tu silentia
+ storage: Inmagasination de medie
featured_tags:
add_new: Adjunter un nov
+ errors:
+ limit: Tu ja ha pinglat li maxim númere de hashtags
hint_html: "Pinglar tui max important hashtags sur tui profil. Un bonissim maniere de mantener un registre de tui ovres e projectes, pinglat hashtags es monstrat prominentmen sur tui profil e permisse rapid accesse a tui propri postas."
filters:
contexts:
account: Profiles
+ home: Hem e listes
+ notifications: Notificationes
+ public: Public témpor-lineas
thread: Conversationes
+ edit:
+ add_keyword: Adjunter término
+ keywords: Términos
+ statuses: Individual postas
+ statuses_hint_html: Ti filtre aplica a selectet postas individual sin egarda a ca ili contene li términos in-infra. Reviser o remover postas del filtre.
+ title: Modificar filtre
+ errors:
+ deprecated_api_multiple_keywords: On ne posse changear ti parametres per ti-ci aplication pro que ili aplica a plu quam un filtrat término. Usa un aplication plu recent o li web-interfacie.
+ invalid_context: Null o ínvalid contextu dat
index:
+ contexts: Filtres in %{contexts}
+ delete: Deleter
+ empty: Tu have null filtres.
+ expires_in: Expira in %{distance}
+ expires_on: Expira ye %{date}
+ keywords:
+ one: "%{count} término"
+ other: "%{count} términos"
+ statuses:
+ one: "%{count} posta"
+ other: "%{count} postas"
+ statuses_long:
+ one: "%{count} individual posta celat"
+ other: "%{count} individual postas celat"
title: Filtres
new:
save: Conservar nov filtre
+ title: Adjunter nov filtre
+ statuses:
+ back_to_filter: Retornar al filtre
+ batch:
+ remove: Remover de filtre
+ index:
+ hint: Ti filtre aplica a selectet postas individual sin egarda a altri criteries. Tu posse adjunter plu postas a ti-ci filtre del web-interfacie.
+ title: Filtrat postas
generic:
all: Omni
+ all_items_on_page_selected_html:
+ one: "%{count} element in ti-ci págine es selectet."
+ other: Omni %{count} elementes in ti-ci págine es selectet.
+ all_matching_items_selected_html:
+ one: "%{count} element concordant tui sercha es selectet."
+ other: Omni %{count} elementes concordant tui sercha es selectet.
cancel: Anullar
changes_saved_msg: Modificationes conservat successosimen!
confirm: Confirmar
copy: Copiar
+ delete: Deleter
+ deselect: Desselecter omnis
none: 'Null'
+ order_by: Ordinar per
save_changes: Conservar changes
+ select_all_matching_items:
+ one: Selecter %{count} element concordant tui sercha.
+ other: Selecter omni %{count} elementes concordant tui sercha.
today: hodie
+ validation_errors:
+ one: Alquo ancor ne es bon! Ples controlar li errore in-infra
+ other: Alquo ancor ne es bon! Ples controlar li %{count} errores in-infra
imports:
errors:
empty: li file es vacui
+ incompatible_type: Íncompatibil con li selectet specie de importation
invalid_csv_file: 'Ínvalid file CSV. Errore: %{error}'
over_rows_processing_limit: contene plu quam %{count} lineas
too_large: li file es tro grand
+ failures: Fallites
imported: Importat
+ mismatched_types_warning: It apari que tu fórsan selectet li íncorect specie por ti importation, ples controlar denov.
modes:
merge: Coalescer
+ merge_long: Conservar existent registres e adjunter li novis
+ overwrite: Remplazzar
+ overwrite_long: Remplazzar existent registres per li novis
overwrite_preambles:
+ blocking_html: Tu va remplazzar tui liste de bloccat contos per til %{total_items} contos de %{filename}.
+ bookmarks_html: Tu va remplazzar tui marcatores per til %{total_items} postas de %{filename}.
+ domain_blocking_html: Tu va remplazzar tui liste de bloccat dominias per til %{total_items} dominias de %{filename}.
+ following_html: Tu va sequer til %{total_items} contos de %{filename} e dessequer omni altri contos.
+ lists_html: Tu va remplazzar tui listes per li contenete de %{filename}. Til %{total_items} contos va esser adjuntet a nov listes.
muting_html: Tu va remplazzar tui liste de silentiat contos per til %{total_items} contos de %{filename}.
preambles:
+ blocking_html: Tu va bloccar til %{total_items} contos de %{filename}.
+ bookmarks_html: Tu va adjunter til %{total_items} postas de %{filename} a tui marcatores.
+ domain_blocking_html: Tu va bloccar til %{total_items} dominias de %{filename}.
+ following_html: Tu va sequer til %{total_items} contos de %{filename}.
+ lists_html: Tu va adjunter til %{total_items} contos de %{filename} a tui listes. Nov listes va esser creat si ne hay un liste a quel adjunter.
muting_html: Tu va silentiar til %{total_items} contos de %{filename}.
+ preface: Tu posse importar data quel tu ha exportat de un altri servitor, quam un liste del gente quem tu seque o blocca.
+ recent_imports: Recent importationes
states:
finished: Finit
+ in_progress: Progressent
+ scheduled: Planat
+ unconfirmed: Ínconfirmat
+ status: Statu
+ success: Tui data esset cargat successosimen e va esser tractat tam tost quam es possibil
+ time_started: Comensat ye
titles:
- lists: Importar listes
- muting: Importation de silentiat contos
- type: Tip de importation
+ blocking: Important bloccat contos
+ bookmarks: Important marcatores
+ domain_blocking: Important bloccat dominias
+ following: Important sequet contos
+ lists: Important listes
+ muting: Important silentiat contos
+ type: Specie de importation
type_groups:
constructive: Seques e marcatores
destructive: Bloccas & silentias
types:
blocking: Liste de bloccas
bookmarks: Marcatores
+ domain_blocking: Liste de dominia-bloccas
following: Liste de sequetes
lists: Listes
+ muting: Liste de silentiationes
upload: Cargar
invites:
+ delete: Desactivisar
expired: Expirat
expires_in:
'1800': 30 minutes
@@ -735,14 +1238,52 @@ ie:
'604800': 1 semane
'86400': 1 die
expires_in_prompt: Nequande
+ generate: Generar ligament de invitation
+ invalid: Ti-ci invitation ne es valid
+ invited_by: 'Tu esset invitat de:'
+ max_uses:
+ one: 1 use
+ other: "%{count} uses"
+ max_uses_prompt: Null límite
+ prompt: Generar e distribuer ligamentes a altres por dar accesse a ti-ci servitor
+ table:
+ expires_at: Expira
+ uses: Uses
+ title: Invitar gente
+ lists:
+ errors:
+ limit: Tu ha atinget li maxim númere de listes
login_activities:
authentication_methods:
+ otp: aplication de 2-factor autentication
password: passa-parol
+ sign_in_token: code de securitá per email
+ webauthn: claves de securitá
description_html: Si tu vide activitá quel tu ne conosse, considera changear tui passa-parol e activisar 2-factor autentication.
+ empty: Null historie de autentication disponibil
+ failed_sign_in_html: Fallit prova de apertion de session per %{method} de %{ip} (%{browser})
+ successful_sign_in_html: Successosi apertion de session per %{method} de %{ip} (%{browser})
+ title: Historie de autentication
mail_subscriptions:
unsubscribe:
+ action: Yes, desabonnar
+ complete: Desabonnat
+ confirmation_html: Esque tu vermen vole desabonnar de reciver %{type} por Mastodon che %{domain} a tui email-adresse %{email}? Tu sempre posse reabonnar per tui parametres pri email-notificationes.
+ emails:
+ notification_emails:
+ favourite: email-notificationes pri favoritisationes
+ follow: email-notificationes pri seques
+ follow_request: email-notificationes pri seque-petitiones
+ mention: email-notificationes pri mentiones
+ reblog: email-notificationes pri boosts
+ resubscribe_html: Si tu ha desabonnat errarimen, tu posse reabonnar per tui parametres pri email-notificationes.
+ success_html: Tu ne plu va reciver %{type} por Mastodon che %{domain} a tui email-adresse %{email}.
title: Desabonnar
+ media_attachments:
+ validations:
+ images_and_video: On ne posse atachar un video a un posta quel ja contene images
migrations:
+ acct: Translocat a
set_redirect: Configurar un redirection
move_handler:
carry_mutes_over_text: Ti-ci usator movet se de %{acct}, quel tu hat silentiat.
@@ -754,13 +1295,41 @@ ie:
title: Nov petition de sequer
mention:
action: Responder
+ title: Nov mention
poll:
subject: Un balotation de %{name} ha finit
+ reblog:
+ body: 'Tui posta esset boostat de %{name}:'
+ subject: "%{name} boostat tui posta"
+ title: Nov boost
+ status:
+ subject: "%{name} just postat"
+ update:
+ subject: "%{name} modificat un posta"
+ notifications:
+ administration_emails: Email-notificationes pri administration
+ email_events: Evenimentes por email-notificationes
+ email_events_hint: 'Selecte li evenimentes pri queles tu vole reciver notificationes:'
+ other_settings: Parametres pri altri notificationes
+ number:
+ human:
+ decimal_units:
+ units:
+ billion: B
+ million: M
+ thousand: m
otp_authentication:
+ code_hint: Inmetter li code generat de tui aplication de autentication por confirmar
+ description_html: Si tu activisa 2-factor autentication per un aplication de autentication, aperter un session va postular que tu have possession de tui telefon, quel va generar codes por que tu mey inmetter les.
enable: Activar
+ instructions_html: "Scande ti-ci code QR ad-in Google Authenticator o un simil aplication TOTP in tu telefon. Pos nu, ti aplication va generar codes queles tu va dever inmetter quande tu aperte un session."
+ manual_instructions: 'Si tu ne posse scander li code QR e besona inmetter it manualmen, vi li crud-text secrete:'
setup: Configurar
+ wrong_code: Li inmettet code esset ínvalid! Esque li témpor del servitor e del aparate corect?
pagination:
+ newer: Plu nov
next: Seq
+ older: Plu old
prev: Prec
truncate: "…"
polls:
@@ -777,33 +1346,128 @@ ie:
too_many_options: ne posse contener plu quam %{max} optiones
preferences:
other: Altri
+ public_timelines: Public témpor-lineas
privacy:
+ hint_html: "Customisa qualmen tu vole que tui profil e tui postas posse esser trovat. Mastodon have un varietá de manieres por auxiliar te atinger un auditorie plu grand quande activisat. Prende un moment por reviser ti parametres por far cert que ili concorda tui casu de usation."
+ privacy: Privatie
+ privacy_hint_html: Decide quant mult tu vole revelar por li beneficie de altres. Gente decovri interessant profiles e frisc aplicationes per vider li seques de altres e vider de quel aplicationes ili posta, ma tu fórsan prefere celar ti.
+ reach: Atingement
+ reach_hint_html: Decide ca tu vole esser decovrit e sequet de nov persones. Vole tu que tui postas mey aparir sur li págine "Explorar"? Vole tu que altri gente posse vider te in lor seque-recomandationes? Vole tu acceptar omni nov sequitores automaticmen, o manualmen tractar chascun?
search: Sercha
+ search_hint_html: Decide qualmen tu vole esser trovat. Vole tu que gente trova te per ti pri quel tu ha postat publicmen? Vole tu que gente éxter Mastodon trova tui profil quande ili sercha li web? Ples memorar que complet exclusion de omni serchatores ne posse esser garantit por public information.
+ title: Privatie e atingement
+ privacy_policy:
+ title: Politica pri Privatie
+ reactions:
+ errors:
+ limit_reached: Límite de diferent reactiones atinget
+ unrecognized_emoji: ne es un reconosset emoji
relationships:
activity: Activitá de conto
+ confirm_follow_selected_followers: Esque tu vermen vole sequer selectet sequitores?
+ confirm_remove_selected_followers: Esque tu vermen vole remover selectet sequitores?
+ confirm_remove_selected_follows: Esque tu vermen vole remover selectet sequetes?
dormant: Dormient
+ follow_failure: Ne posset sequer quelc del contos selectet.
+ follow_selected_followers: Sequer selectet sequitores
+ followers: Sequitores
+ following: Sequetes
+ invited: Invitat
+ last_active: Ultimmen activ
+ most_recent: Max recent
+ moved: Movet
mutual: Reciproc
+ primary: Primari
+ relationship: Relation
+ remove_selected_domains: Remover omni sequitores del selectet dominias
+ remove_selected_followers: Remover selectet sequitores
+ remove_selected_follows: Dessequer selectet usatores
status: Statu del conto
+ rss:
+ content_warning: 'Avise pri li contenete:'
+ descriptions:
+ account: Public postas de @%{acct}
+ tag: 'Public postas con li hashtag #%{hashtag}'
+ scheduled_statuses:
+ over_daily_limit: Tu ha atinget li límite de %{limit} planat postas por hodie
+ over_total_limit: Tu ha atinget li límite de %{limit} planat postas
+ too_soon: Li planat date deve esser in li future
+ self_destruct:
+ lead_html: Ínfortunatmen, %{domain} va bentost permanentmen cluder. Si tu havet un conto ta, tu ne va posser continuar usar it, ma tu ancor posse demandar un archive de tui data.
+ title: Ti-ci servitor va cluder bentost
sessions:
activity: Ultim activitá
browser: Navigator
browsers:
+ alipay: Alipay
+ blackberry: BlackBerry
chrome: Chrome
+ edge: Microsoft Edge
electron: Electron
+ firefox: Firefox
generic: Ínconosset navigator
+ huawei_browser: Huawei Browser
+ ie: Internet Explorer
+ micro_messenger: MicroMessenger
+ nokia: Nokia S40 Ovi Browser
+ opera: Opera
+ otter: Otter
+ phantom_js: PhantomJS
+ qq: QQ Browser
+ safari: Safari
+ uc_browser: UC Browser
unknown_browser: Ínconosset navigator
+ weibo: Weibo
+ current_session: Actual session
description: "%{browser} in %{platform}"
explanation: Tis-ci es li navigatores queles actualmen ha initiat sessiones a tui Mastodon-conto.
ip: IP
platforms:
+ adobe_air: Adobe Air
+ android: Android
+ blackberry: BlackBerry
+ chrome_os: ChromeOS
+ firefox_os: Firefox OS
+ ios: iOS
+ kai_os: KaiOS
+ linux: Linux
+ mac: macOS
unknown_platform: Ínconosset platform
windows: Windows
+ windows_mobile: Windows Mobile
+ windows_phone: Windows Phone
revoke: Revocar
+ revoke_success: Session successosimen revocat
+ title: Sessiones
+ view_authentication_history: Vider li historie de autentication por tui conto
settings:
account: Conto
+ account_settings: Parametres del conto
+ aliases: Aliases del conto
appearance: Aspecte
+ authorized_apps: Autorisat aplicationes
+ back: Retornar a Mastodon
+ delete: Deletion de conto
+ development: Developation
+ edit_profile: Modificar profil
+ export: Exportation de data
+ featured_tags: Recomandat hashtags
+ import: Importar
+ import_and_export: Importation e exportation
+ migrate: Migration de conto
+ notifications: Notificationes
+ preferences: Preferenties
+ profile: Public profil
+ relationships: Sequetes e sequitores
+ statuses_cleanup: Automatisat deletion de postas
+ strikes: Admonimentes moderatori
+ two_factor_authentication: 2-factor autentication
+ webauthn_authentication: Claves de securitá
statuses:
attached:
+ audio:
+ one: "%{count} audio-file"
+ other: "%{count} audio-files"
description: 'Atachat: %{attached}'
image:
one: "%{count} image"
@@ -811,8 +1475,10 @@ ie:
video:
one: "%{count} video"
other: "%{count} videos"
+ boosted_from_html: Boostat de %{acct_link}
default_language: Sam quam li lingue del interfacie
edited_at_html: Modificat ye %{date}
+ open_in_web: Aperter in web
pin_errors:
direct: On ne posse pinglar postas queles es visibil solmen a mentionat usatores
limit: Tu ja ha pinglat li maxim númere de postas
@@ -826,38 +1492,131 @@ ie:
one: "%{count} vote"
other: "%{count} votes"
vote: Votar
+ show_more: Monstrar plu
+ show_newer: Monstrar plu nov
+ show_older: Monstrar plu old
title: "%{name}: «%{quote}»"
visibilities:
+ private_long: Monstrar solmen a sequitores
public: Public
+ public_long: Omnes posse vider
unlisted: Delistat
+ unlisted_long: Omnes posse vider, ma ne listat sur public témpor-lineas
statuses_cleanup:
+ enabled: Automaticmen deleter old postas
+ enabled_hint: Deleter automaticmen tui postas quande ili atinge un cert etá, except si ili concorda con un del exceptiones ci infra
exceptions: Exceptiones
+ explanation: Deletion de postas es un operation expensiv, e pro to es efectuat lentmen quande li servitor ne es ocupat. Pro to, on posse deleter tui postas un cert témpor pos atinger un cert etá.
+ ignore_favs: Ignorar favorites
+ interaction_exceptions: Exceptiones basat sur interactiones
+ keep_direct: Retener missages direct
+ keep_direct_hint: Ne delete quelcunc de tui direct missages
keep_pinned: Conservar pinglat postas
keep_pinned_hint: Delete null de tui pinglat postas
keep_polls: Conservar balotationes
keep_polls_hint: Delete null de tui balotationes
+ keep_self_fav: Retener postas favorit de te
min_age:
'1209600': 2 semanes
'31556952': 1 annu
+ strikes:
+ errors:
+ too_late: It es tro tard por apellar ti admoniment
+ tags:
+ does_not_match_previous_name: ne concorda li anteyan nómine
+ themes:
+ contrast: Mastodon (Alt contraste)
+ default: Mastodon (Obscur)
+ mastodon-light: Mastodon (Luminosi)
time:
formats:
default: "%d.%m.%Y ye %H:%M"
month: "%b %Y"
time: "%H:%M"
+ with_time_zone: "%d.%m.%Y ye %H:%M %Z"
+ translation:
+ errors:
+ quota_exceeded: Li servitor ha exhaustet nor quote por li servicie de traduction.
+ too_many_requests: Hay tro mult recent demandes al servicie de traduction.
two_factor_authentication:
add: Adjunter
+ disable: Desactivisar 2FA
+ disabled_success: 2-factor autentication successosimen desactivisat
+ edit: Modificar
+ enabled: 2-factor autentication es activisat
+ enabled_success: 2-factor autentication successosimen activisat
+ generate_recovery_codes: Generar codes de recuperation
+ lost_recovery_codes: Codes de recuperation lassa te recuperar accesse a tui conto si tu perdi tui telefon. Si tu ha perdit tui codes de recuperation, tu posse regenerar les ci. Tui antiqui codes de recuperation va esser ínvalidat.
+ methods: Metodes de 2-factor autentication
+ otp: Aplication de autentication
+ recovery_codes: Copiar tui codes de recuperation
+ recovery_codes_regenerated: Codes de recuperation successosimen regenerat
+ recovery_instructions_html: Si tu perdi accesse a tui telefon, tu posse usar un del codes de recuperation in-infra por reganiar accesse a tui conto. Mantener li securitá del codes de recuperation. Per exemple, tu fórsan va printar les e inmagasinar les con altri important documentes.
+ webauthn: Claves de securitá
user_mailer:
+ appeal_approved:
+ action: Ear a tui conto
+ explanation: Li apelle del admoniment contra tui conto ye %{strike_date} quel tu fat ye %{appeal_date} ha esset aprobat. Tui conto retorna a esser in bon statu.
+ subject: Tui apelle de %{date} ha esset aprobat
+ title: Apelle aprobat
+ appeal_rejected:
+ explanation: Li apelle del admoniment contra tui conto ye %{strike_date} quel tu fat ye %{appeal_date} ha esset rejectet.
+ subject: Tui apelle de %{date} ha esset rejectet
+ title: Apelle rejectet
+ backup_ready:
+ explanation: Tu solicitat un complet archive de tui Mastodon-conto. Nu it es pret por descargar!
+ subject: Tui archive es pret por descargar
+ title: Descargar archive
suspicious_sign_in:
change_password: changear tui passa-parol
+ details: 'Vi li detallies del apertion de session:'
+ explanation: On detectet un intration a tui conto de un nov IP-adresse.
+ further_actions_html: Si to ne esset tu, on recomanda que tu %{action} strax e activisar 2-factor autentication por mantener tui conto secur.
+ subject: Tui conto ha esset accesset de un nov IP-adresse
+ title: Un nov intration
warning:
+ appeal: Inviar un apelle
+ appeal_description: Si tu crede que ti es un error, tu posse inviar un apelle al personale de %{instance}.
categories:
spam: Spam
+ violation: Contenete viola li sequent regules del comunité
+ explanation:
+ delete_statuses: On judicat que quelc de tui postas viola un o plu del regules del comunité e pro to ha esset removet del moderatores del %{instance}.
+ disable: Tu ne plu posse usar tui conto, ma tui profil e altri data resta integri. Tu posse solicitar un archive de tui data, changear parametres del conto, o deleter tui conto.
+ mark_statuses_as_sensitive: Quelc de tui postas ha esset marcat quam sensitiv del moderatores de %{instance}. Ti significa que gente va dever cliccar li medie in li postas ante que un previse es monstrat. Tu self posse marcar medie quam sensitiv quande tu posta futurimen.
+ sensitive: Pos nu, omni tui cargat medie-files va esser marcat quam sensitiv e celat detra un avise que on deve cliccar por transpassar.
+ silence: Tu ancor posse usar tui conto, ma solmen gente qui ja seque te va posser vider tui postas in ti-ci servitor, e tu va esser excludet de utensiles de decovrition. Támen, altres ancor posse sequer te manualmen.
+ suspend: Tu ne plu posse usar tui conto, e tui profil e altri data nu es ínaccessibil. Tu ancor posse intrar por solicitar un archive de tui data til que li data es removet in circa 30 dies, ma noi va retener quelc basic data pri te por preventer te evitar li suspension.
reason: 'Rason:'
+ statuses: 'Postas citat:'
subject:
+ delete_statuses: Tui postas che %{acct} ha esset removet
+ disable: Tui conto %{acct} ha esset gelat
+ mark_statuses_as_sensitive: Tui postas che %{acct} ha esset marcat quam sensitiv
none: Admoniment por %{acct}
+ sensitive: Tui postas che %{acct} ve esser marcat quam sensitiv pos nu
+ title:
+ delete_statuses: Postas efaciat
+ sensitive: Conto marcat quam sensitiv
+ silence: Conto limitat
+ suspend: Conto suspendet
welcome:
+ edit_profile_action: Configuration de profil
+ explanation: Vi quelc suggestiones por que tu mey comensar
+ final_action: Comensar postar
subject: Benevenit a Mastodon
+ title: Benevenit, %{name}!
users:
seamless_external_login: Tu ha intrat per un servicie external, dunc parametres pri tui passa-parol e email-adresse ne es disponibil.
verification:
+ extra_instructions_html: 'Nota: Li ligament in tui websitu posse esser ínvisibil. Li important parte es rel="me" quel prevente fals self-identification in websitus con contenete generat de usatores. Tu posse mem usar un link element in li cap-section del págine vice a, ma li HTML code deve esser accessibil sin executer JavaScript.'
+ here_is_how: Vide qualmen
verification: Verification
+ verified_links: Tui verificat ligamentes
+ webauthn_credentials:
+ add: Adjunter nov clave de securitá
+ create:
+ error: Un problema evenit durant li adjuntion de tui clave de securitá. Ples provar denov.
+ success: Tui clave de securitá esset adjuntet con successe.
+ delete: Deleter
+ delete_confirmation: Vole tu vermen deleter ti-ci clave de securitá?
diff --git a/config/locales/lad.yml b/config/locales/lad.yml
index 626d2da7ec..471b830a3e 100644
--- a/config/locales/lad.yml
+++ b/config/locales/lad.yml
@@ -226,6 +226,56 @@ lad:
update_status: Aktualiza publikasyon
update_user_role: Aktualiza rolo
actions:
+ approve_appeal_html: "%{name} acheto la solisitasyon de moderasyon de %{target}"
+ approve_user_html: "%{name} acheto el enrejistramiento de %{target}"
+ assigned_to_self_report_html: "%{name} asinyo el raporto %{target} a si mezmo"
+ change_email_user_html: "%{name} troko el adreso de posta elektronika del utilizador %{target}"
+ change_role_user_html: "%{name} troko el rolo de %{target}"
+ confirm_user_html: "%{name} konfirmo el adreso de posta elektronika del utilizador %{target}"
+ create_account_warning_html: "%{name} embio una avertensya a %{target}"
+ create_announcement_html: "%{name} kriyo un muevo pregon %{target}"
+ create_canonical_email_block_html: "%{name} bloko la posta elektronika kon el hash %{target}"
+ create_custom_emoji_html: "%{name} kargo el muevo emoji %{target}"
+ create_domain_allow_html: "%{name} perimitio la federasyon kon el domeno %{target}"
+ create_domain_block_html: "%{name} bloko el domeno %{target}"
+ create_email_domain_block_html: "%{name} bloko el domeno de posta elektronika %{target}"
+ create_ip_block_html: "%{name} kriyo una regla para IP %{target}"
+ create_unavailable_domain_html: "%{name} detuvo las entregas al domeno %{target}"
+ create_user_role_html: "%{name} kriyo el rolo %{target}"
+ demote_user_html: "%{name} degrado a %{target}"
+ destroy_announcement_html: "%{name} supremio el pregon %{target}"
+ destroy_canonical_email_block_html: "%{name} dezbloko la posta elektronika kon el hash %{target}"
+ destroy_custom_emoji_html: "%{name} supremio el emoji %{target}"
+ destroy_domain_allow_html: "%{name} bloko la federasyon kon el domeno %{target}"
+ destroy_domain_block_html: "%{name} dezbloko el domeno %{target}"
+ destroy_email_domain_block_html: "%{name} dezbloko el domeno de posta elektronika %{target}"
+ destroy_instance_html: "\"%{name} purgo el domeno %{target}"
+ destroy_ip_block_html: "%{name} supremio una regla para la IP %{target}"
+ destroy_status_html: "%{name} supremio una publikasyon de %{target}"
+ destroy_unavailable_domain_html: "%{name} reinisyo las entregas al domeno %{target}"
+ destroy_user_role_html: "%{name} supremio el rolo %{target}"
+ disable_2fa_user_html: "%{name} dezaktivo el rekerimiento en dos pasos para el utilizador %{target}"
+ disable_custom_emoji_html: "%{name} dezaktivo el emoji %{target}"
+ disable_sign_in_token_auth_user_html: "%{name} inkapasito la autentifikasyon por token de posta elektronika para %{target}"
+ disable_user_html: "%{name} inkapasito la koneksion kon el kuento para el utilizador %{target}"
+ enable_custom_emoji_html: "%{name} aktivo el emoji %{target}"
+ enable_sign_in_token_auth_user_html: "%{name} tiene kapasitado la autentifikasyon por token de posta elektronika para %{target}"
+ enable_user_html: "%{name} kapasito koneksion kon kuento para el utilizador %{target}"
+ memorialize_account_html: "%{name} konvirtio el kuento de %{target} en una pajina de bendicha memoria"
+ promote_user_html: "%{name} promosyon al utilizador %{target}"
+ reject_appeal_html: "%{name} refuzo la solisitasyon de moderasyon de %{target}"
+ reject_user_html: "%{name} refuzo el enrejistramiento de %{target}"
+ remove_avatar_user_html: "%{name} supremio la imaje de profil de %{target}"
+ reopen_report_html: "%{name} reavrio el raporto %{target}"
+ resend_user_html: "%{name} tiene reembiado la posta de konfirmasyon para %{target}"
+ reset_password_user_html: "%{name} reinisyo el kod del utilizador %{target}"
+ resolve_report_html: "%{name} rezolvio el raporto %{target}"
+ sensitive_account_html: "%{name} marko la multimedia de %{target} komo sensivle"
+ silence_account_html: "%{name} silensyo el kuento de %{target}"
+ suspend_account_html: "%{name} suspendio el kuento de %{target}"
+ unassigned_report_html: "%{name} dezasinyo el raporto %{target}"
+ unblock_email_account_html: "%{name} tiene dezblokado el adreso de posta de %{target}"
+ unsensitive_account_html: "%{name} dezmarko la multimedia de %{target} komo sensivle"
unsilence_account_html: "%{name} kito el limite del kuento de %{target}"
unsuspend_account_html: "%{name} reaktivo el kuento de %{target}"
update_announcement_html: "%{name} aktualizo el pregon %{target}"
@@ -256,6 +306,7 @@ lad:
unpublish: Retirar publikasyon
unpublished_msg: Pregon retirado kon sukseso!
updated_msg: Pregon aktualizado kon sukseso!
+ critical_update_pending: Aktualizasyon kritika esta asperando
custom_emojis:
assign_category: Asinyar kategoria
by_domain: Domeno
@@ -280,7 +331,63 @@ lad:
title: Adjustar muevo emoji personalizado
no_emoji_selected: No se troko dingun emoji porke no eskojites dinguno
not_permitted: No tienes permiso para realizar esta aksyon
+ overwrite: Sobreskrive
+ shortcode: Kodiche kurto
+ title: Emojis personalizados
+ uncategorized: No kategorizado
+ unlist: No lista
+ unlisted: No listado
+ update_failed_msg: No se pudo aktualizar akel emoji
+ updated_msg: Emoji aktualizado kon sukseso!
+ upload: Karga
+ dashboard:
+ active_users: utilizadores aktivos
+ interactions: enteraksyones
+ media_storage: Magazinaje de multimedia
+ new_users: muevos utilizadores
+ opened_reports: raportos aviertos
+ pending_appeals_html:
+ one: "%{count} apelasyon esta asperando"
+ other: "%{count} apelasiones estan asperando"
+ pending_reports_html:
+ one: "%{count} raporto esta asperando"
+ other: "%{count} raportos estan asperando"
+ pending_tags_html:
+ one: "%{count} etiketa esta asperando"
+ other: "%{count} etiketas estan asperando"
+ pending_users_html:
+ one: "%{count} utilizador esta asperando"
+ other: "%{count} utilizadores estan asperando"
+ resolved_reports: raportos rezolvidos
+ software: Programario
+ sources: Manaderos de rejistro
+ space: Uzo de magazinaje
+ title: Pano
+ top_languages: Linguas mas aktivas
+ top_servers: Sirvidores mas aktivos
+ website: Sitio internetiko
+ disputes:
+ appeals:
+ empty: No se toparon apelasiones.
+ title: Apelasiones
+ domain_allows:
+ add_new: Perimite federasyon kon el domeno
+ created_msg: Federasyon kon el domeno permitida kon sukseso
+ destroyed_msg: Federasyon kon el domeno proibida kon sukseso
+ export: Eksporto
+ import: Importo
+ undo: Proibe federasyon kon el domeno
domain_blocks:
+ add_new: Adjusta muevo bloko de domeno
+ confirm_suspension:
+ cancel: Anula
+ confirm: Suspende
+ title: Konfirma bloko de domeno para %{domain}
+ created_msg: El bloko de domeno esta siendo prosesado
+ destroyed_msg: El bloko de domeno se dezizo
+ domain: Domeno
+ edit: Edita muevo bloko de domeno
+ existing_domain_block: Ya impusites limitos mas estriktos a %{name}.
existing_domain_block_html: Ya tienes forsado limitos mas estriktos a %{name}, kale dezblokarlo primero.
export: Eksporto
import: Importo
@@ -345,7 +452,69 @@ lad:
follow_recommendations:
description_html: "Las rekomendasyones de kuentos ayudan a los muevos utilizadores a topar presto kontenido enteresante. Kuando un utilizador no tiene enteraktuado kon otros lo sufisiente komo para djenerar rekomendasyones personalizadas de kuentos a las ke segir, en sus lugar se le rekomiendan estes kuentos. Se rekalkulan diariamente a partir de una mikstura de kuentos kon el mayor numero de enteraksyones rezientes i kon el mayor numero de suivantes lokales kon una lingua determinada."
language: Para la lingua
+ status: Estado
+ suppress: Inkapasita rekomendasyon de kuentos
+ suppressed: Inkapasitada
+ title: Rekomendasyones de kuentos
+ unsuppress: Restora rekomendasyones de kuentos
instances:
+ availability:
+ description_html:
+ one: Si el embio al domeno no reushe %{count} diya, no se aran mas provas de entrega a manko ke se risiva un embio dizde el domeno.
+ other: Si el embio al domeno no reushe %{count} diyas desferentes, no se aprovaran a entregar a manko ke se risiva un embio dizde el domeno.
+ failure_threshold_reached: Limito de provas no reushidas alkansado el %{date}.
+ failures_recorded:
+ one: Prova no reushida en %{count} diya.
+ other: Provas no reushidas en %{count} diyas desferentes.
+ no_failures_recorded: No ay provas no reushidas en el defter.
+ title: Disponivilita
+ warning: La ultima prova de koneksyon a este sirvidor no tuvo sukseso
+ back_to_all: Todos
+ back_to_limited: Limitados
+ back_to_warning: Avertensya
+ by_domain: Domeno
+ confirm_purge: Siguro ke keres supremir permanentemente los datos de este domeno?
+ content_policies:
+ comment: Nota interna
+ description_html: Puedes definir politikas de kontenido ke se aplikaran a todos los kuentos de este domeno i a kualkiera de sus subdomenos.
+ limited_federation_mode_description_html: Puedes eskojer si keres permitir federasyon kon este domeno.
+ policies:
+ reject_media: Refuza multimedia
+ reject_reports: Refuza raportos
+ silence: Limita
+ suspend: Suspende
+ policy: Politika
+ reason: Razon publika
+ title: Politikas de kontenido
+ dashboard:
+ instance_accounts_dimension: Kuentos mas segidos
+ instance_accounts_measure: kuentos magazinados
+ instance_followers_measure: muestros suivantes ayi
+ instance_follows_measure: sus suivantes aki
+ instance_languages_dimension: Linguas popularas
+ instance_media_attachments_measure: dosyas adjuntas guadradas
+ instance_reports_measure: raportos sovre eyos
+ instance_statuses_measure: mesajes magazinados
+ delivery:
+ all: Todos
+ clear: Alimpiar yerros de entrega
+ failing: Fayando
+ restart: Reinisyar entrega
+ stop: Detener entrega
+ unavailable: No desponivle
+ delivery_available: Entrega desponivle
+ delivery_error_days: Diyas de yerro de entrega
+ delivery_error_hint: Si la entrega no es posivle a lo longo de %{count} diyas, se markara otomatikamente komo no entregable.
+ destroyed_msg: Los datos de %{domain} estan agora en kola para sus iminente efasasyon.
+ empty: No se toparon domenos.
+ known_accounts:
+ one: "%{count} kuento konesido"
+ other: "%{count} kuentos konesidos"
+ moderation:
+ all: Todos
+ limited: Limitado
+ title: Moderasyon
+ private_comment: Komento privado
public_comment: Komento publiko
purge: Purga
purge_description_html: Si kreyes ke este domeno esta deskonektado, puedes efasar todos los rejistros de kuentos i los datos asosyados de este domeno de tu magazinaje. Esto puede levar un tiempo.
@@ -528,8 +697,47 @@ lad:
manage_rules: Administra reglas
manage_rules_description: Permete a los utilizadores trokar las reglas del sirvidor
manage_settings: Administra konfigurasyon
+ manage_settings_description: Permete a los utilizadores trokar la konfigurasyon del sitio
+ manage_taxonomies: Administra etiketas
+ manage_taxonomies_description: Permete a los utilizadores revizar el kontenido en trend i aktualizar la konfigurasyon de las etiketas
+ manage_user_access: Administra akseso de utilizadores
+ manage_user_access_description: Permete a los utilizadores dezaktivar la autentifikasyon en dos pasos de otros utilizadores, trokar sus adreso de posta elektronika i restableser sus kod
+ manage_users: Administra utilizadores
+ manage_users_description: Permete a los utilizadores ver los peratim de otros utilizadores i realizar aksyones de moderasyon kontra eyos
+ manage_webhooks: Administrar webhooks
+ view_dashboard: Ve pano
+ view_devops: DevOps
+ title: Rolos
+ rules:
+ add_new: Adjusta regla
+ delete: Efasa
+ edit: Edita regla
+ title: Reglas del sirvidor
settings:
+ about:
+ manage_rules: Administra reglas del sirvidor
+ title: Sovre esto
+ appearance:
+ preamble: Personaliza la enterfaz web de Mastodon.
+ title: Aparensya
+ branding:
+ title: Marka
+ content_retention:
+ title: Retensyon de kontenido
+ discovery:
+ follow_recommendations: Rekomendasyones de kuentos
+ profile_directory: Katalogo de profiles
+ public_timelines: Linyas de tiempo publikas
+ publish_discovered_servers: Publika sirvidores diskuviertos
+ publish_statistics: Publika estatistikas
+ title: Diskuvrimiento
+ trends: Trendes
+ domain_blocks:
+ all: A todos
+ disabled: A dinguno
+ users: Para los utilizadores lokales ke entrado en su kuento
registrations:
+ preamble: Kontrola ken puede kriyar un kuento en tu sirvidor.
title: Enrejistramientos
registrations_mode:
modes:
@@ -541,8 +749,13 @@ lad:
delete: Efasa dosya kargada
destroyed_msg: Dosya supremida kon sukseso!
software_updates:
+ critical_update: Kritiko – por favor aktualiza pishin
documentation_link: Ambezate mas
+ release_notes: Notas sovre la versyon
+ title: Aktualizasyones desponivles
type: Tipo
+ types:
+ major: Versyon prinsipala
version: Versyon
statuses:
account: Autor
@@ -579,7 +792,7 @@ lad:
silence: "%{name} limito el kuento de %{target}"
suspend: "%{name} suspendio el kuento de %{target}"
appeal_approved: Apelado
- appeal_pending: Apelasyon pendiente
+ appeal_pending: Apelasyon asperando
appeal_rejected: Apelasyon refuzada
system_checks:
database_schema_check:
@@ -598,6 +811,10 @@ lad:
message_html: No tienes definido dinguna regla del sirvidor.
sidekiq_process_check:
message_html: No ay dingun prosedura Sidekiq en egzekusion para la(s) kola(s) %{value}. Por favor, reviza tu konfigurasyon de Sidekiq
+ software_version_critical_check:
+ action: Amostra aktualizasyones desponivles
+ software_version_patch_check:
+ action: Amostra aktualizasyones desponivles
upload_check_privacy_error:
message_html: "Tu sirvidor de web es mal konfigurado. La privasita de tus utilizadores esta en riziko."
upload_check_privacy_error_object_storage:
@@ -620,3 +837,845 @@ lad:
no_link_selected: No se troko dingun atadijo porke no eskojites dinguno
publishers:
no_publisher_selected: No se troko dingun publikador porke no eskojites dinguno
+ title: Atadijos en trend
+ usage_comparison: Partajado %{today} vezes oy, komparado kon %{yesterday} ayer
+ not_allowed_to_trend: Sin permiso para estar en trendes
+ only_allowed: Solo las permetidas
+ pending_review: Revizion esta asperando
+ preview_card_providers:
+ allowed: Los atadijos de este medio pueden ser trend
+ description_html: Estos son domenos dizde los ke los atadijos akoruto se partajan en sus sirvidor. Los atadijos no seran trend publikamente a menos ke se achete el domeno del atadijo. Sus achetasion (o refuzo) se ekstende a los subdomenos.
+ rejected: Los atadijos de este medio no pueden ser trend
+ title: Medios
+ rejected: Refuzadas
+ statuses:
+ allow: Permete publikasyon
+ allow_account: Permete al autor
+ description_html: Estas son publikasyones ke tu sirvidor konese ke estan siendo partajadas muncho i plazen a munchas personas en este momento. Puedes ayudar a tus utilizadores muevos i retornantes a topar mas djente a la ke segir. No ay mesajes ke se amostren publikamente asta ke aproves el autor i el autor permeta ke su kuento sea sugjerado a otros. Tamyen puedes permeter o refuzar mesajes individuales.
+ disallow: No permete publikasyon
+ disallow_account: No permete al autor
+ no_status_selected: No se troko dinguna publikasyon en trend porke no seleksionates dingunas
+ not_discoverable: El autor no tiene optado por ser detektable
+ shared_by:
+ one: Repartajado por o plaze a alguno una vez
+ other: Repartajado por o plaze a alguno %{friendly_count} vezes
+ title: Publikasyones en trend
+ tags:
+ current_score: Puntuasion aktuala %{score}
+ dashboard:
+ tag_accounts_measure: uzos unikos
+ tag_languages_dimension: Linguas popularas
+ tag_servers_dimension: Sirvidores prinsipales
+ tag_servers_measure: desferentes sirvidores
+ tag_uses_measure: uzos totales
+ description_html: Estas son etiketas ke estan aparesiendo en munchas publikasyones ke tu sirvidor ve. Pueden ayudar a tus utilizadores a averiguar de ke avla mas djente en estos momentos. No ay etiketas ke se amostren publikamente asta ke las achetes.
+ listable: Pueden ser rekomendadas
+ no_tag_selected: No se troko dinguna etiketa al no eskojer dinguna
+ not_listable: No seran rekomendadas
+ not_trendable: No aperesera en trendes
+ not_usable: No se pueden uzar
+ title: Etiketas en trend
+ trendable: Pueden apareser en trendes
+ trending_rank: Trend n.º %{rank}
+ usable: Pueden uzarse
+ usage_comparison: Uzada %{today} vezes oy, komparado kon %{yesterday} ayer
+ used_by_over_week:
+ one: Uzada por una persona durante la ultima semana
+ other: Uzada por %{count} personas durante la ultima semana
+ title: Trendes
+ trending: En trend
+ warning_presets:
+ add_new: Adjusta muevo
+ delete: Efasa
+ edit_preset: Edita avizo predeterminado
+ empty: Ainda no tienes definido ningun avizo predeterminado.
+ title: Edita konfigurasyon predeterminada de avizos
+ webhooks:
+ add_new: Adjusta endpoint
+ delete: Efasa
+ description_html: Un webhook permete a Mastodon embiar avizos en tiempo real sovre los evenimientos eskojidos a tu propia aplikasyon, para ke tu aplikasyon pueda lanzar reaksyones otomatikamente.
+ disable: Inkapasita
+ disabled: Inkapasitado
+ edit: Edita endpoint
+ empty: Ainda no tienes ningun endpoint de webhook konfigurado.
+ enable: Kapasita
+ enabled: Aktivo
+ enabled_events:
+ one: 1 evenimiento kapasitado
+ other: "%{count} evenimientos kapasitados"
+ events: Evenimientos
+ new: Muevo webhook
+ rotate_secret: Rotar sekreto
+ secret: Firmando sekreto
+ status: Estado
+ title: Webhooks
+ webhook: Webhook
+ admin_mailer:
+ new_appeal:
+ actions:
+ delete_statuses: para supremir sus mesajes
+ disable: para konjelar su kuento
+ mark_statuses_as_sensitive: para markar sus mesajes komo sensivles
+ none: una avertensya
+ sensitive: para markar su kuento komo sensivle
+ silence: para limitar su kuento
+ suspend: para suspender su kuento
+ body: "%{target} esta apelando a una solisitasyon de moderasyon de %{action_taken_by} el %{date}, del tipo %{type}. Eyos eskrivieron:"
+ next_steps: Puedes achetar la apelasyon para dezazer la dechizyon de moderasyon, o ignorarla.
+ subject: "%{username} esta apelando a una dechizyon de moderasyon en %{instance}"
+ new_critical_software_updates:
+ subject: Ay aktualizasyones kritikas de Mastodon desponivles para %{instance}!
+ new_pending_account:
+ body: Los peratim del muevo kuento estan abashos. Puedes achetar o refuzar esta aplikasyon.
+ subject: Muevo kuento para revizion en %{instance} (%{username})
+ new_report:
+ body: "%{reporter} tiene raportado a %{target}"
+ body_remote: Alguno de %{domain} a raportado a %{target}
+ subject: Muevo raporto para la %{instance} (#%{id})
+ new_software_updates:
+ body: Ay mueva versyon de Mastodon, kizas keras aktualizar!
+ subject: Ay muevas versyones de Mastodon desponivles para %{instance}!
+ new_trends:
+ body: 'Los sigientes elementos nesesitan una revizion antes de ke se puedan amostrar publikamente:'
+ new_trending_links:
+ title: Atadijos en trend
+ new_trending_statuses:
+ title: Publikasyones en trend
+ new_trending_tags:
+ title: Etiketas en trend
+ aliases:
+ add_new: Kriya un alias
+ empty: No tienes aliases.
+ remove: Dezata alias
+ appearance:
+ advanced_web_interface: Enterfaz web avanzada
+ animations_and_accessibility: Animasyones i aksesivilita
+ confirmation_dialogs: Dialogos de konfirmasyon
+ discovery: Diskuvrimiento
+ localization:
+ guide_link_text: Todos pueden kontribuir.
+ sensitive_content: Kontenido sensivle
+ application_mailer:
+ notification_preferences: Troka preferensyas de posta
+ salutation: "%{name},"
+ settings: 'Troka preferensyas de posta: %{link}'
+ unsubscribe: Dezabona
+ view: 'Mira:'
+ view_profile: Ve profil
+ view_status: Ve publikasyon
+ applications:
+ created: Aplikasyon kriyada kon sukseso
+ destroyed: Aplikasyon supremida kon sukseso
+ logout: Sal
+ regenerate_token: Redjenera token de akseso
+ token_regenerated: Token de akseso redjenerado kon sukseso
+ warning: Ten muncho kudiado kon estos datos. No los partajes kon dinguno!
+ your_token: Tu token de akseso
+ auth:
+ apply_for_account: Solisita un kuento
+ captcha_confirmation:
+ title: Kontrolo de sigurita
+ confirmations:
+ awaiting_review_title: Estamos revizando tu enrejistramiento
+ clicking_this_link: klikando en este atadijo
+ login_link: konektate kon kuento
+ proceed_to_login_html: Agora puedes ir a %{login_link}.
+ welcome_title: Bienvenido, %{name}!
+ wrong_email_hint: Si este adreso de posta es inkorekto, puedes trokarlo en las preferensyas del kuento.
+ delete_account: Efasa kuento
+ delete_account_html: Si keres supremir tu kuento, puedes ir aki. Seras pedido de una konfirmasyon.
+ description:
+ prefix_invited_by_user: "@%{name} te envita a unirte a este sirvidor de Mastodon!"
+ prefix_sign_up: Adjuntate a Mastodon oy!
+ suffix: Kon un kuento podras segir a djente, publikar haberes e enterkambiar mesajes kon utilizadores de kualkier sirvidor de Mastodon i mas!
+ didnt_get_confirmation: No risivites el atadijo de konfirmasyon?
+ dont_have_your_security_key: No tienes tu yave de sigurita?
+ forgot_password: Neglijates tu kod?
+ invalid_reset_password_token: El token de reinisyo de kod es malato o ekspiro. Por favor pide uno muevo.
+ link_to_otp: Introduse un kodiche de autorizasyon en dos pasos dizde tu telefon o un kodiche de rekuperasyon
+ link_to_webauth: Utiliza tu aparato de yave de sigurita
+ log_in_with: Konektate kon kuento kon
+ login: Konektate kon kuento
+ logout: Sal
+ migrate_account: Transferate a otro kuento
+ migrate_account_html: Si keres readresar este kuento a otra distinta, puedes konfigurarlo aki.
+ or_log_in_with: O konektate kon tu kuento kon
+ privacy_policy_agreement_html: Tengo meldado i acheto la politika de privasita
+ progress:
+ confirm: Konfirma posta
+ details: Tus detalyos
+ review: Muestra revizyon
+ rules: Acheta reglas
+ providers:
+ cas: CAS
+ saml: SAML
+ register: Enrejistrate
+ registration_closed: "%{instance} no esta achetando a muevos miembros"
+ resend_confirmation: Reembia posta de konfirmasyon
+ reset_password: Reinisya kod
+ rules:
+ accept: Acheta
+ back: Atras
+ preamble: Estas son establesidas i aplikadas por los moderadores de %{domain}.
+ title: Algunas reglas bazikas.
+ title_invited: Fuites envitado.
+ security: Sigurita
+ set_new_password: Establese muevo kod
+ setup:
+ email_below_hint_html: Mira en tu kuti de spam o solisita de muevo. Si el adreso de posta elektronika ke aparese aki es yerrado, puedes trokarlo aki.
+ link_not_received: No risivites un atadijo?
+ sign_in:
+ preamble_html: Konektate kon tus kredensiales de %{domain}. Si tu kuento esta balabayado en otruno servidor, no puedras konektarte aki.
+ title: Konektate kon %{domain}
+ sign_up:
+ preamble: Kon un kuento en este sirvidor de Mastodon, podras segir a kualkier otra persona en la red, endependientemente del sirvidor en el ke se tope.
+ title: Kriya kuento de Mastodon en %{domain}.
+ status:
+ account_status: Estado del kuento
+ confirming: Bekleando konfirmasyon de posta elektronika.
+ functional: Tu kuento esta kompletamente funksyonal.
+ pending: Tu solisitasyon esta asperando la revizion por muestros administradores. Esto puede tadrar algun tiempo. Arisiviras una posta elektronika si la solisitasyon sea achetada.
+ redirecting_to: Tu kuento se topa inaktivo porke esta siendo readresado a %{acct}.
+ view_strikes: Ve amonestamientos pasados kontra tu kuento
+ too_fast: Formulario enviado demaziado rapido, aprovalo de muevo.
+ use_security_key: Uza la yave de sigurita
+ challenge:
+ confirm: Kontinua
+ hint_html: "Tip: No retornaremos a demandarte por el kod durante la sigiente ora."
+ invalid_password: Kod inkorekto
+ prompt: Konfirma kod para segir
+ crypto:
+ errors:
+ invalid_key: no es una yave Ed25519 o Curve25519 valida
+ invalid_signature: no es una firma Ed25519 valida
+ date:
+ formats:
+ default: "%d %b %Y"
+ with_month_name: "%d %B %Y"
+ datetime:
+ distance_in_words:
+ about_x_hours: "%{count} o"
+ about_x_months: "%{count} me"
+ about_x_years: "%{count} a"
+ almost_x_years: "%{count} a"
+ half_a_minute: Agora
+ less_than_x_minutes: "%{count} m"
+ less_than_x_seconds: Agora
+ over_x_years: "%{count} a"
+ x_days: "%{count} d"
+ x_minutes: "%{count} m"
+ x_months: "%{count} me"
+ x_seconds: "%{count} s"
+ deletes:
+ proceed: Efasa kuento
+ success_msg: Tu kuento fue efasado kon reusho
+ warning:
+ before: 'Antes de kontinuar, por favor melda kon atensyon las sigientes notas:'
+ username_unavailable: Tu nombre de utilizador no estara desponivle
+ disputes:
+ strikes:
+ action_taken: Aksyon tomada
+ appeal: Apela
+ appeal_rejected: La apelasyon fue refuzada
+ appeal_submitted_at: Apelasyon embiada
+ appealed_msg: Tu apelasyon fue embiada. Si la achetamos, se te avizara.
+ appeals:
+ submit: Embia apelasyon
+ approve_appeal: Acheta apelasyon
+ associated_report: Raporto asosiado
+ created_at: Kon data
+ description_html: Estas son las aksyones emprendidas kontra tu kuento i las avertensyas ke te an sido enviadas por la taifa de %{instance}.
+ recipient: Dirijda a
+ reject_appeal: Refuza apelasyon
+ status: 'Publikasyon #%{id}'
+ status_removed: Publikasyon ya supremida del sistem
+ title: "%{action} del %{date}"
+ title_actions:
+ delete_statuses: Efasasyon de publikasyon
+ disable: Konjelasyon del kuento
+ mark_statuses_as_sensitive: Markamiento de los mesajes komo sensivles
+ none: Avertensya
+ sensitive: Markamiento del kuento komo sensivle
+ silence: Limitasyon de kuento
+ suspend: Suspensyon de kuento
+ your_appeal_approved: Tu apelasyon fue achetada
+ your_appeal_pending: Tienes enviado una apelasyon
+ your_appeal_rejected: Tu apelasyon fue refuzada
+ domain_validator:
+ invalid_domain: no es un nombre de domeno valido
+ edit_profile:
+ basic_information: Enformasyon bazika
+ other: Otros
+ errors:
+ '400': La solisitasyon ke enviates no fue valida o fue malformada.
+ '403': No tienes permiso para ver esta pajina.
+ '404': La pajina ke bushkas no egziste.
+ '406': Esta pajina no esta desponivle en el formato solisitado.
+ '410': La pajina ke estavas bushkando no egziste mas.
+ '422':
+ content: Verifikasyon de sigurita no reushida. Estas blokando algunas cookies?
+ title: Verifikasyon de sigurita no reushida
+ '429': Demaziadas solisitasyones
+ '500':
+ content: Pardon, algo tiene fonksionado mal por muestra parte.
+ title: Esta pajina no es djusta
+ '503': La pajina no se tiene podido desbarkar por un yerro temporal del sirvidor.
+ noscript_html: Para uzar la aplikasyon web de Mastodon, por favor aktiva Javascript. Alternativamente, aprova alguna de las aplikasyones nativas para Mastodon para tu platforma.
+ existing_username_validator:
+ not_found: no se pudo topar un utilizador lokal kon akel nombre de utilizador
+ not_found_multiple: no se pudo topar %{usernames}
+ exports:
+ archive_takeout:
+ date: Data
+ download: Abasha tu dosya
+ hint_html: Puedes solisitar una dosya de tus publikasyones i dosyas multimedia kargadas. Los datos eksportados estaran en formato ActivityPub, meldavles por kualkier programario kompativle. Puedes solisitar una dosya kada 7 diyas.
+ in_progress: Rekopilando tu dosya...
+ request: Solisita tu dosya
+ size: Boy
+ blocks: Personas a las kualas tienes blokado
+ bookmarks: Markadores
+ csv: CSV
+ domain_blocks: Blokos de domenos
+ lists: Listas
+ mutes: Silensias
+ storage: Magazinaje de multimedia
+ featured_tags:
+ add_new: Adjusta muevo
+ errors:
+ limit: Tienes alkansado el karar maksimo de etiketas
+ filters:
+ contexts:
+ account: Profiles
+ home: Prinsipyo i listas
+ notifications: Avizos
+ public: Linyas de tiempo publikas
+ thread: Konversasyones
+ edit:
+ add_keyword: Adjusta biervo yave
+ keywords: Biervos yaves
+ statuses: Publikasyones individualas
+ title: Edita filtro
+ index:
+ contexts: Filtros en %{contexts}
+ delete: Efasa
+ empty: No tyenes filtros.
+ expires_in: Kaduka en %{distance}
+ expires_on: Kaduka en %{date}
+ keywords:
+ one: "%{count} biervo yave"
+ other: "%{count} biervos yave"
+ statuses:
+ one: "%{count} publikasyon"
+ other: "%{count} publikasyones"
+ statuses_long:
+ one: "%{count} publikasyon individuala eskondida"
+ other: "%{count} publikasyones individualas eskondidas"
+ title: Filtros
+ new:
+ save: Guadra muevo filtro
+ title: Adjusta muevo filtro
+ statuses:
+ back_to_filter: Retorna al filtro
+ batch:
+ remove: Kita del filtro
+ index:
+ title: Publikasyones filtradas
+ generic:
+ all: Todos
+ cancel: Anula
+ changes_saved_msg: Trokamientos guadrados kon reusho!
+ confirm: Konfirma
+ copy: Kopia
+ delete: Efasa
+ deselect: Deseleksyonar todo
+ none: Dinguno
+ order_by: Ordena por
+ save_changes: Guadra trokamientos
+ today: oy
+ imports:
+ errors:
+ empty: Dosya CSV vaziya
+ invalid_csv_file: 'Dosya CSV no valida. Yerro: %{error}'
+ over_rows_processing_limit: kontiene mas de %{count} filas
+ too_large: Dosya es mas grande
+ imported: Importado
+ modes:
+ merge: Une
+ merge_long: Manten rejistros egzistentes i adjusta muevos
+ overwrite: Sobreskrive
+ overwrite_long: Mete muevos rejistros en vez de los aktuales
+ preface: Puedes importar siertos datos, komo todas las personas a las kualas estas sigiendo o blokando en tu kuento en esta instansya, dizde dosyas eksportadas de otra instansya.
+ recent_imports: Importasyones resyentes
+ states:
+ finished: Finalizado
+ scheduled: Programado
+ unconfirmed: Sin konfirmasyon
+ status: Estado
+ success: Tus datos se tienen kargado djustamente i seran prosesados pishin
+ time_started: Ampesado el
+ titles:
+ blocking: Importando kuentos blokados
+ bookmarks: Importando markadores
+ domain_blocking: Importando domenos blokados
+ following: Importando kuentos segidos
+ lists: Importando listas
+ muting: Importando kuentos silensyados
+ type: Tipo de importasyon
+ type_groups:
+ constructive: Segidores i markadores
+ destructive: Blokos i silensyos
+ types:
+ blocking: Lista de blokos
+ bookmarks: Markadores
+ domain_blocking: Lista de domenos blokados
+ following: Lista de segidos
+ lists: Listas
+ muting: Lista de silensyados
+ upload: Karga
+ invites:
+ delete: Dezaktiva
+ expired: Kadukado
+ expires_in:
+ '1800': 30 minutos
+ '21600': 6 oras
+ '3600': 1 ora
+ '43200': 12 oras
+ '604800': 1 semana
+ '86400': 1 diya
+ expires_in_prompt: Nunkua
+ generate: Djenera un atadijo de envitasyon
+ invalid: Esta envitasyon no es valida
+ invited_by: 'Fuites envitado por:'
+ max_uses:
+ one: 1 uzo
+ other: "%{count} uzos"
+ max_uses_prompt: Sin limito
+ table:
+ expires_at: Kaduka
+ uses: Uzos
+ title: Envita a djente
+ lists:
+ errors:
+ limit: Tienes alkansado el karar maksimo de listas
+ login_activities:
+ authentication_methods:
+ otp: aplikasyon de autentifikasyon en dos pasos
+ password: kod
+ sign_in_token: kodiche de sigurita por posta elektronika
+ webauthn: yaves de sigurita
+ description_html: Si ves una aktivita ke no rekoneses, konsidera trokar tu kod i kapasitar la autentifikasyon en dos pasos.
+ empty: No ay estoria de autentifikasyon desponivle
+ failed_sign_in_html: Prova de inisiasyon de sesion no reushida kon %{method} de %{ip} (%{browser})
+ successful_sign_in_html: Prova de sesion reushida kon %{method} dizde %{ip} (%{browser})
+ title: Estoria de autentifikasyon
+ mail_subscriptions:
+ unsubscribe:
+ action: Si, dezabona
+ complete: Dezabonado
+ emails:
+ notification_emails:
+ favourite: avizos de favoritos por posta
+ follow: avizos de segidores por posta
+ follow_request: avizos de solisitasyones de segimyento por posta
+ mention: avizos de enmentaduras por posta
+ reblog: avizos de repartajasyones por posta
+ title: Dezabona
+ media_attachments:
+ validations:
+ images_and_video: No se puede adjuntar un video a un estado ke ya kontenga imajes
+ not_ready: No se pueden adjuntar dosyas ke no se tienen eskapado de prosesar. Aprovalo de muevo en un momento!
+ too_many: No se pueden adjuntar mas de 4 dosyas
+ migrations:
+ acct: Migrado a
+ cancel: Anula readreso
+ cancel_explanation: Al anular el readreso se reaktivara tu kuento aktual, ama no rekuperaras los suivantes ke ayan sido trasladados al otro kuento.
+ cancelled_msg: Tienes anulado el readreso djustamente.
+ errors:
+ already_moved: es el mezmo kuento al ke ya tienes migrado
+ missing_also_known_as: no es un alias de este kuento
+ move_to_self: no puede ser el kuento aktual
+ not_found: no se pudo topar
+ on_cooldown: Estas en tiempo de reutilizasyon
+ followers_count: Suivantes al momento de migrar
+ incoming_migrations: Migra de un kuento desferente
+ incoming_migrations_html: Para migrar de otro kuento a este, primero kale kriyar un alias del kuento.
+ moved_msg: Tu kuento agora se esta redirigiendo a %{acct} i tus suivantes se estan migrando.
+ not_redirecting: Tu kuento no se esta readresando a dinguna otro kuento aktualmente.
+ on_cooldown: Tienes migrado tu kuento rezientemente. Esta fonksyon estara desponivle de muevo en %{count} diyas.
+ past_migrations: Migrasyones pasadas
+ proceed_with_move: Migra suivantes
+ redirected_msg: Tu kuento agora readresa a %{acct}.
+ redirecting_to: Tu kuento se esta readresando a %{acct}.
+ set_redirect: Establese readreso
+ warning:
+ backreference_required: El muevo kuento deve ser konfigurado primero para fazer referensya a este
+ before: 'Antes de kontinuar, por favor melda kon atensyon las sigientes notas:'
+ cooldown: Dempues de migrar ay un periodo de aspera durante el kual no podras retornar a migrar
+ disabled_account: Tu kuento aktual no sera kompletamente utilizable dempues. Portanto, tendras akseso a la eksportasyon de datos ansi komo a la reaktivasyon.
+ followers: Esta aksion migrara a todos los suivantes del kuento aktual al muevo kuento
+ only_redirect_html: Alternativamente, solo puedes poner un readreso en tu profil.
+ moderation:
+ title: Moderasyon
+ notification_mailer:
+ admin:
+ report:
+ subject: "%{name} embio un raporto"
+ sign_up:
+ subject: "%{name} se enrejistro"
+ favourite:
+ body: 'Tu publikasyon fue favoritada por %{name}:'
+ subject: A %{name} le plaze tu publikasyon
+ title: Muevo favorito
+ follow:
+ body: "%{name} te esta sigiendo!"
+ subject: "%{name} te esta sigiendo"
+ title: Muevo suivante
+ follow_request:
+ action: Administra solisitudes de segimiento
+ body: "%{name} tiene solisitado segirte"
+ subject: 'Segidor esta asperando: %{name}'
+ title: Mueva solisitud de segimiento
+ mention:
+ action: Arisponde
+ body: 'Fuites enmentado por %{name} en:'
+ subject: Fuites enmentado por %{name}
+ title: Mueva enmentadura
+ poll:
+ subject: Una anketa de %{name} eskapo
+ reblog:
+ body: 'Tu publikasyon fue repartajada por %{name}:'
+ subject: "%{name} repartajo tu publikasyon"
+ title: Mueva repartajasyon
+ status:
+ subject: "%{name} publiko algo"
+ update:
+ subject: "%{name} edito una publikasyon"
+ notifications:
+ administration_emails: Avizos de administrasyon por posta
+ email_events: Evenimyentos para avizos por posta
+ other_settings: Otras preferensyas de avizos
+ number:
+ human:
+ decimal_units:
+ format: "%n%u"
+ units:
+ billion: MM
+ million: M
+ quadrillion: Ku
+ thousand: K
+ trillion: T
+ otp_authentication:
+ enable: Kapasita
+ instructions_html: "Eskanea este kodiche QR dizde Google Authenticator o una aplikasyon similar en tu telefon. A partir de agora, esta aplikasyon djenerara kodiches ke tendras ke ingresar kuando keras konektarte kon tu kuento."
+ manual_instructions: 'Si no puedes eskanear el kodiche QR i nesesitas introdusirlo manualmente, este es el sekreto en teksto plano:'
+ setup: Konfigura
+ wrong_code: El kodiche ingresado es malato! Es djusta la ora del aparato i el sirvidor?
+ pagination:
+ newer: Mas muevo
+ next: Sigiente
+ older: Mas viejo
+ prev: Anterior
+ truncate: "…"
+ polls:
+ errors:
+ already_voted: Ya tienes votado en esta anketa
+ duplicate_options: kontiene elementos duplikados
+ duration_too_long: esta demaziado leshos en el avenir
+ duration_too_short: es demaziado pronto
+ expired: La anketa ya tiene eskapado
+ invalid_choice: La opsyon de voto eskojida no egziste
+ over_character_limit: no puede trespasar %{max} karakteres kada uno
+ self_vote: No puedes votar en tus propias anketas
+ too_few_options: deve tener mas de un elemento
+ too_many_options: no puede kontener mas de %{max} elementos
+ preferences:
+ other: Otros
+ posting_defaults: Konfigurasyon predeterminada de publikasyones
+ public_timelines: Linyas de tiempo publikas
+ privacy:
+ privacy: Privasita
+ search: Bushkeda
+ privacy_policy:
+ title: Politika de privasita
+ reactions:
+ errors:
+ limit_reached: Limito de reaksyones desferentes alkansado
+ unrecognized_emoji: no es un emoji konesido
+ relationships:
+ activity: Aktivita del kuento
+ confirm_follow_selected_followers: Estas siguro ke keres segir a los suivantes eskojidos?
+ confirm_remove_selected_followers: Estas siguro ke keres kitar a los suivantes eskojidos?
+ confirm_remove_selected_follows: Estas siguro ke keres kitar los kuentos segidos a los kualos tienes eskojido?
+ dormant: Inaktivo
+ follow_failure: No se pudo segir algunos de los kuentos eskojidos.
+ follow_selected_followers: Sige a los suivantes eskojidos
+ followers: Suivantes
+ following: Segidos
+ invited: Envitados
+ last_active: Ultima aktivita
+ most_recent: Mas reziente
+ moved: Migrados
+ mutual: Mutual
+ primary: Prinsipal
+ relationship: Relasyon
+ remove_selected_followers: Kita a los suivantes eskojidos
+ remove_selected_follows: Desige a los utilizadores eskojidos
+ status: Estado del kuento
+ rss:
+ content_warning: 'Avertensya de kontenido:'
+ descriptions:
+ account: Publikasyones publikas de @%{acct}
+ tag: 'Publikasyones publikas etiketadas kon #%{hashtag}'
+ self_destruct:
+ title: Este sirvidor esta serrando
+ sessions:
+ activity: Ultima aktivita
+ browser: Navigador
+ browsers:
+ alipay: Alipay
+ blackberry: BlackBerry
+ chrome: Chrome
+ edge: Microsoft Edge
+ electron: Electron
+ firefox: Firefox
+ generic: Navigador deskonosido
+ huawei_browser: Huawei Browser
+ ie: Internet Explorer
+ micro_messenger: MicroMessenger
+ nokia: Nokia S40 Ovi Browser
+ opera: Opera
+ otter: Otter
+ phantom_js: PhantomJS
+ qq: QQ Browser
+ safari: Safari
+ uc_browser: UC Browser
+ unknown_browser: Navigador deskonosido
+ weibo: Weibo
+ current_session: Sesyon aktuala
+ description: "%{browser} en %{platform}"
+ ip: IP
+ platforms:
+ adobe_air: Adobe Air
+ android: Android
+ blackberry: BlackBerry
+ chrome_os: ChromeOS
+ firefox_os: Firefox OS
+ ios: iOS
+ kai_os: KaiOS
+ linux: Linux
+ mac: macOS
+ unknown_platform: Platforma deskonesida
+ windows: Windows
+ windows_mobile: Windows Mobile
+ windows_phone: Windows Phone
+ revoke: Revoka
+ revoke_success: Sesion revokada kon sukseso
+ title: Sesiones
+ view_authentication_history: Ve estoria de autentifikasyon de tu kuento
+ settings:
+ account: Kuento
+ account_settings: Preferensyas de kuento
+ aliases: Aliases del kuento
+ appearance: Aparensya
+ authorized_apps: Aplikasyones autorizadas
+ back: Retorna a Mastodon
+ delete: Efasa kuento
+ development: Dezvelopamiento
+ edit_profile: Edita profil
+ export: Eksporta enformasyon
+ featured_tags: Etiketas avaliadas
+ import: Importo
+ import_and_export: Importo i eksporto
+ migrate: Migrasyon de kuento
+ notifications: Avizos
+ preferences: Preferensyas
+ profile: Profil publiko
+ relationships: Segidos i suivantes
+ statuses_cleanup: Efasasyon otomatika de publikasyones
+ strikes: Amonestamientos de moderasyon
+ two_factor_authentication: Autentifikasyon en dos pasos
+ webauthn_authentication: Yaves de sigurita
+ statuses:
+ attached:
+ audio:
+ one: "%{count} audio"
+ other: "%{count} audios"
+ description: 'Atamiento: %{attached}'
+ image:
+ one: "%{count} imaje"
+ other: "%{count} imajes"
+ video:
+ one: "%{count} video"
+ other: "%{count} videos"
+ boosted_from_html: Repartajado dizde %{acct_link}
+ content_warning: 'Avertensya de kontenido: %{warning}'
+ default_language: La mezma ke la lingua de la enterfaz
+ disallowed_hashtags:
+ one: 'kontenia una etiketa no permetida: %{tags}'
+ other: 'kontenia las etiketas no permetidas: %{tags}'
+ edited_at_html: Editado %{date}
+ errors:
+ in_reply_not_found: La publikasion a la ke aprovas arispondir no egziste.
+ open_in_web: Avre en web
+ over_character_limit: limito de karakteres de %{max} superado
+ pin_errors:
+ direct: Las publikasyones ke son vizivles solo para los utilizadores enmentados no pueden fiksarse
+ limit: Ya tienes fiksado el numero maksimo de publikasyones
+ ownership: La publikasyon de otra persona no puede fiksarse
+ reblog: No se puede fixar una repartajasyon
+ poll:
+ total_people:
+ one: "%{count} persona"
+ other: "%{count} personas"
+ total_votes:
+ one: "%{count} voto"
+ other: "%{count} votos"
+ vote: Vota
+ show_more: Amostra mas
+ show_newer: Amostra mas muevos
+ show_older: Amostra mas viejos
+ show_thread: Amostra diskusyon
+ title: '%{name}: "%{quote}"'
+ visibilities:
+ direct: Direkto
+ private: Solo suivantes
+ private_long: Solo amostra a tus segidores
+ public: Publiko
+ public_long: Todos pueden ver
+ unlisted: No listado
+ statuses_cleanup:
+ enabled: Otomatikamente efasa publikasyones viejas
+ exceptions: Eksepsiones
+ ignore_favs: Ignora favoritos
+ ignore_reblogs: Ignora repartajasyones
+ keep_direct: Manten enmentaduras privadas
+ keep_direct_hint: No efasa dingunos mesajes privados
+ keep_media: Manten publikasyones kon atamientos
+ keep_media_hint: No efasa tus publikasyones kon atamientos de multimedia
+ keep_pinned: Manten publikasyones fiksadas
+ keep_pinned_hint: No efasa tus publikasyones fiksadas
+ keep_polls: Manten anketas
+ keep_polls_hint: No efasa tus anketas
+ keep_self_bookmark: Manten publikasyones kon markadores
+ keep_self_bookmark_hint: No efasa tus publikasyones kon tus markadores
+ keep_self_fav: Manten publikasyones ke te plazen
+ keep_self_fav_hint: No efasa las tus publikasyones kualas markates komo favoritas
+ min_age:
+ '1209600': 2 semanas
+ '15778476': 6 mezes
+ '2629746': 1 mez
+ '31556952': 1 anyo
+ '5259492': 2 mezes
+ '604800': 1 semana
+ '63113904': 2 anyos
+ '7889238': 3 mezes
+ min_favs: Manten publikasyones favoritadas a lo manko
+ min_reblogs: Manten publikasyones repartajadas a lo manko
+ stream_entries:
+ sensitive_content: Kontenido sensivle
+ strikes:
+ errors:
+ too_late: Es demaziado tadre para apelar este amonestamiento
+ tags:
+ does_not_match_previous_name: no koensida kon el nombre anterior
+ themes:
+ contrast: Mastodon (alto kontraste)
+ default: Mastodon (eskuro)
+ mastodon-light: Mastodon (klaro)
+ time:
+ formats:
+ default: "%d de %b del %Y, %H:%M"
+ month: "%b %Y"
+ time: "%H:%M"
+ with_time_zone: "%d de %b del %Y, %H:%M %Z"
+ two_factor_authentication:
+ add: Adjusta
+ disable: Inkapasita autentifikasyon en dos pasos
+ disabled_success: Autentifikasyon de dos pasos inkapasitada djustamente
+ edit: Edita
+ enabled: Autentifikasyon de dos pasos esta kapasitada
+ enabled_success: Autentifikasyon de dos pasos kapasitada djustamente
+ generate_recovery_codes: Jenera kodiches de rekuperasyon
+ lost_recovery_codes: Los kodiches de rekuperasyon te permeten obtener akseso a tu kuento si piedres tu telefon. Si tienes pedrido tus kodiches de rekuperasyon, puedes redjenerarlos aki. Tus viejos kodiches de rekuperasyon se aran malatos.
+ methods: Metodos de autentifikasyon de dos pasos
+ otp: Aplikasyon de autentifikasyon
+ recovery_codes: Fazer kopias de sigurita de tus kodiches de rekuperasyon
+ recovery_codes_regenerated: Kodiches de rekupersayon redjenerados kon sukseso
+ recovery_instructions_html: Si piedres akseso a tu telefon, puedes uzar uno de los sigientes kodiches de rekuperasyon para obtener akseso a tu kuento. Mantenlos a salvo. Por enshemplo, puedes imprimirlos i guadrarlos kon otros dokumentos emportantes.
+ webauthn: Yaves de sigurita
+ user_mailer:
+ appeal_approved:
+ action: Va a tu kuento
+ explanation: La apelasyon del amonestamiento kontra tu kuento del %{strike_date} ke mandates el %{appeal_date} fue achetada. Tu kuento se topa de muevo en dobro estado.
+ subject: Tu apelasyon del %{date} fue achetada
+ title: Apelasyon achetada
+ appeal_rejected:
+ explanation: La apelasyon del amonestamiento kontra tu kuento del %{strike_date} ke mandates el %{appeal_date} fue refuzada.
+ subject: Tu apelasyon del %{date} fue refuzada
+ title: Apelasyon refuzada
+ backup_ready:
+ explanation: Tienes solisitado una kopia kompleta de tu kuento de Mastodon. Ya esta pronta para abashar!
+ subject: Tu dosya esta pronta para abashar
+ title: Abasha dosya
+ suspicious_sign_in:
+ change_password: troka tu kod
+ details: 'Aki estan los peratim de la koneksyon kon tu kuento:'
+ explanation: Topimos una koneksyon kon tu kuento dizde un muevo adreso IP.
+ further_actions_html: Si no fuites tu, te rekomendamos ke %{action} pishin i kapasites la autentifikasyon en dos pasos para mantener tu kuento siguro.
+ subject: Tu kuento fue aksedido dizde un muevo adreso IP
+ title: Una mueva koneksyon kon tu kuento
+ warning:
+ appeal: Embia una apelasyon
+ appeal_description: Si kreyes ke esto es un yerro, puedes embiar una apelasyon a la taifa de %{instance}.
+ categories:
+ spam: Spam
+ violation: El kontenido viola las sigientes reglas de la komunita
+ explanation:
+ delete_statuses: Se tiene determinado ke algunos de tus mesajes violan una o mas reglas de la komunita i por tanto fueron supremidos por los moderadores de %{instance}.
+ disable: Ya no puedes uzar tu kuento, ama tu profil i el resto de datos permanesen intactos. Puedes solisitar una kopia de sigurita de tus datos, trokar la konfigurasyon de tu kuento o supremirlo.
+ mark_statuses_as_sensitive: Algunas de tus publikasyones an sido markados komo sensivles por los moderadores de %{instance}. Esto sinyifika ke la djente tendra ke pulsar los dosyas multimedia en las publikasyones antes de ke se amostre una vista previa. Puedes markar los dosyas multimedia komo sensivles tu mezmo kuando publikes en el avenir.
+ sensitive: A partir de agora todas los dosyas multimedia ke subas seran markados komo sensivles i eskondidos tras una avertensya.
+ reason: 'Razon:'
+ statuses: 'Publikasyones relevantes:'
+ subject:
+ delete_statuses: Tus publikasyones en %{acct} tienen sido efasadas
+ disable: Tu kuento %{acct} tiene sido konjelado
+ mark_statuses_as_sensitive: Tus publikasyones en %{acct} tienen sido markadas komo sensivles
+ none: Avertensya para %{acct}
+ sensitive: Tus publikasyones en %{acct} seran markadas komo sensivles dizde agora
+ silence: Tu kuento %{acct} tiene sido limitado
+ suspend: Tu kuento %{acct} tiene sido suspendido
+ title:
+ delete_statuses: Publikasyones efasadas
+ disable: Kuento konjelado
+ mark_statuses_as_sensitive: Publikasyones markadas komo sensivles
+ none: Avertensya
+ sensitive: Kuento markado komo sensivle
+ silence: Kuento limitado
+ suspend: Kuento suspendido
+ welcome:
+ edit_profile_action: Konfigurasyon de profil
+ final_action: Ampesa a publikar
+ subject: Bienvenido a Mastodon
+ title: Bienvenido, %{name}!
+ users:
+ follow_limit_reached: No puedes segir a mas de %{limit} personas
+ invalid_otp_token: Kodiche de dos pasos no valido
+ signed_in_as: 'Konektado komo:'
+ verification:
+ verification: Verifikasyon
+ verified_links: Tus atadijos verifikados
+ webauthn_credentials:
+ add: Adjusta mueva yave de sigurita
+ create:
+ success: Tu yave de sigurita fue adjustada kon sukseso.
+ delete: Efasa
+ delete_confirmation: Estas siguro ke keres efasar esta yave de sigurita?
+ destroy:
+ success: Tu yave de sigurita fue efasada kon sukseso.
+ invalid_credential: Yave de sigurita no valida
+ nickname_hint: Introduska el sovrenombre de tu mueva yave de sigurita
+ not_enabled: Ainda no tienes aktivado WebAuthn
+ not_supported: Este navigador no soporta yaves de sigurita
+ otp_required: Para uzar yaves de sigurita, por favor kapasite primero la autentifikasyon de dos pasos.
+ registered_on: Enrejistrado el %{date}
diff --git a/config/locales/simple_form.ie.yml b/config/locales/simple_form.ie.yml
index ada775b862..672ff9d003 100644
--- a/config/locales/simple_form.ie.yml
+++ b/config/locales/simple_form.ie.yml
@@ -4,30 +4,77 @@ ie:
hints:
account:
unlocked: Persones va posser sequer te sin petir aprobation. Desselecte si tu vole manualmen tractar petitiones de sequer e decider ca acceptar o rejecter nov sequitores.
+ account_warning_preset:
+ text: Tu posse usar posta-sintaxe, quam URLs, hashtags e mentiones
+ title: Ínobligatori. Ne visibil al recipiente
+ admin_account_action:
+ send_email_notification: Li usator va reciver un explication de ti quel evenit con su conto
+ text_html: Ínobligatori. Tu posse usar posta-sintaxe. Tu posse adjunter preconfigurationes de avises por sparar témpor
+ type_html: Selecte quo far con %{acct}
+ types:
+ disable: Prevente li usator de usar su conto, ma ne delete o cela su contenete.
+ none: Usa ti por inviar un admoniment al usator, sin initiante quelcunc altri action.
+ sensitive: Fortiar que omni medie-atachamentes de ti usator essaye marcat quam sensitiv.
+ silence: Preventer li usator de posser postar con public visibilitá, e celar tui postas e notificationes de gente qui ne seque ti. Ti clude omni raportes contra ti conto.
+ suspend: Preventer omni interaction de o a ti-ci conto e deleter su contenete. Reversibil ante 30 dies ha passat. Ti clude omni raportes contra ti conto.
+ warning_preset_id: Ínobligatori. Tu ancor posse adjunter customisat textu al fine del preconfiguration
announcement:
all_day: Si ti-ci es marcat, solmen li dates del periode de témpor va esser monstrat
ends_at: Ínobligatori. Li proclamation va esser despublicat automaticmen ye ti-ci témpor
scheduled_at: Lassar vacui por publicar li proclamation strax
starts_at: Ínobligatori. In li casu que tui proclamation es ligat a un specific periode de témpor
text: Tu posse usar posta-sintaxe. Ples considerar li spacie quel li proclamation va plenar sur li ecran del usator
+ appeal:
+ text: Tu posse apellar un admoniment solmen un vez
defaults:
+ autofollow: Persones qui adherer per li invitation va sequer te automaticmen
+ avatar: PNG, GIF o JPEG. Admaxim %{size}. Li grandore va esser contraet a %{dimensions}px
+ bot: Dir a altres que li conto primarimen far automatic actiones e que fórsan null homan vigila it
+ context: Un o multiplic contextus u li filtre deve aplicar
current_password: Por securital rasones, ples introducter li passa-parol del actual conto
+ current_username: Por confirmar, ples inmetter li usator-nómine del actual conto
+ digest: Misset solmen pos un long periode de ínactivitá e solmen si tu ha recivet quelcunc missages personal in tui inbuxe
+ email: On va misser te un email de confirmation
+ header: PNG, GIF o JPEG. Admaxim %{size}. Li grandore va esser contraet a %{dimensions}px
+ inbox_url: Copiar li URL del initial págine del relé quel tu vole usar
+ irreversible: Filtrat postas va desaparir ínreversibilmen, mem si li filtre es removet plu tard
+ locale: Li lingue del usator-interfacie, emails e notificationes push
password: Usa adminim 8 carácteres
+ phrase: Va aplicar sin egarda a ca li lítteres in li posta o su contenete-avise es majuscules o minuscules
+ scopes: Queles API li aplication va esser permisset accesser. Si tu selecte un capabilitá del max alt nivelle, tu ne deve selecter li individualis.
+ setting_aggregate_reblogs: Ne monstrar nov boosts por postas queles ha esset recentmen boostat (afecta solmen boostas recivet futurimen)
+ setting_always_send_emails: Generalmen notificationes per email ne va esser misset quande tu activmen usa Mastodon
+ setting_default_sensitive: Sensitiv medie es customarimen celat e posse esser revelat con un clicca
+ setting_display_media_default: Celar medie marcat quam sensitiv
+ setting_display_media_hide_all: Sempre celar medie
+ setting_display_media_show_all: Sempre monstrar medie
form_admin_settings:
bootstrap_timeline_accounts: Ti-ci contos va esser pinglat al parte superiori del recomandationes por nov usatores.
+ site_contact_username: Qualmen li gente posse atinger te sur Mastodon.
theme: Li dessine quel ínregistrat visitantes e nov usatores vide.
timeline_preview: Ínregistrat visitantes va posser vider li max recent public postas disponibil che li servitor.
trends_as_landing_page: Monstrar populari contenete a ínregistrat visitantes vice un description del servitor. Besona que tendenties es activisat.
form_challenge:
current_password: Tu nu intra un area secur
+ ip_block:
+ severities:
+ sign_up_block: Nov registrationes ne va esser possibil
+ sign_up_requires_approval: Nov registrationes va besonar tui aprobation
+ webhook:
+ events: Selecter evenimentes a misser
+ url: Ad u misser evenimentes
labels:
account:
fields:
value: Contenete
+ indexable: Includer public postas in resultates de sercha
account_warning_preset:
+ text: Textu prefigurat
title: Titul
admin_account_action:
+ send_email_notification: Notificar li usator per e-posta
type: Action
+ warning_preset_id: Usar un prefiguration de avise
announcement:
all_day: Eveniment del tot die
ends_at: Fine del eveniment
@@ -35,13 +82,37 @@ ie:
starts_at: Comense del eveniment
text: Proclamation
defaults:
+ chosen_languages: Filtrar lingues
confirm_new_password: Confirmar nov passa-parol
confirm_password: Confirmar passa-parol
+ context: Filtrar contextus
current_password: Actual passa-parol
+ data: Data
+ display_name: Nómine a monstrar
+ email: E-posta
+ expires_in: Expirar pos
honeypot: "%{label} (ne plenar)"
locale: Lingue del interfacie
new_password: Nov passa-parol
note: Biografie
password: Passa-parol
+ setting_default_language: Lingue in quel postar
+ setting_default_privacy: Privatie de postada
+ setting_default_sensitive: Sempre marcar medie quam sensitiv
+ setting_display_media_default: Predefinitiones
+ setting_display_media_hide_all: Celar omno
+ setting_display_media_show_all: Monstrar omno
+ setting_theme: Tema de situ
+ setting_trends: Monstrar li hodial tendenties
+ setting_unfollow_modal: Monstrar dialog de confirmation ante dessequer alquem
+ setting_use_pending_items: Mode lent
+ form_admin_settings:
+ registrations_mode: Qui posse registrar se
+ theme: Predefenit tema
+ trends: Possibilisar tendenties
+ trends_as_landing_page: Usar tendenties quam frontispicie
notification_emails:
follow_request: Alqui petit sequer te
+ trending_tag: Nov tendentie besonant inspection
+ tag:
+ trendable: Permisse que ti-ci hashtag apari sub tendenties
diff --git a/config/locales/simple_form.it.yml b/config/locales/simple_form.it.yml
index 9ad4323504..731379835a 100644
--- a/config/locales/simple_form.it.yml
+++ b/config/locales/simple_form.it.yml
@@ -6,7 +6,7 @@ it:
discoverable: I tuoi post pubblici e il tuo profilo potrebbero essere presenti o consigliati in varie aree di Mastodon e il tuo profilo potrebbe essere suggerito ad altri utenti.
display_name: Il tuo nome completo o il tuo soprannome.
fields: La tua homepage, i pronomi, l'età, tutto quello che vuoi.
- indexable: I tuoi post pubblici potrebbero apparire nei risultati di ricerca su Mastodon. Le persone che hanno interagito con i tuoi post potrebbero essere in grado di cercarli indipendentemente.
+ indexable: I tuoi post pubblici potrebbero apparire nei risultati di ricerca su Mastodon. Le persone che hanno interagito con i tuoi post potrebbero essere in grado di cercarli anche se non hai attivato questa impostazione.
note: 'Puoi @menzionare altre persone o usare gli #hashtags.'
show_collections: Le persone saranno in grado di navigare attraverso i tuoi seguaci e seguaci. Le persone che segui vedranno che li seguirai indipendentemente dalle tue impostazioni.
unlocked: Le persone saranno in grado di seguirti senza richiedere l'approvazione. Deseleziona se vuoi controllare le richieste di seguirti e scegli se accettare o rifiutare nuovi follower.
@@ -122,7 +122,7 @@ it:
webauthn: Se si tratta di una chiavetta USB assicurati di inserirla e, se necessario, toccarla.
settings:
indexable: La pagina del tuo profilo potrebbe apparire nei risultati di ricerca su Google, Bing e altri.
- show_application: Sarai sempre in grado di vedere quale app ha pubblicato il tuo post indipendentemente.
+ show_application: Tu sarai sempre in grado di vedere quale app ha pubblicato il tuo post anche se hai attivato questa impostazione.
tag:
name: Puoi cambiare solo il minuscolo/maiuscolo delle lettere, ad esempio, per renderlo più leggibile
user:
diff --git a/config/locales/simple_form.lad.yml b/config/locales/simple_form.lad.yml
index 4247487607..aeac5f1c58 100644
--- a/config/locales/simple_form.lad.yml
+++ b/config/locales/simple_form.lad.yml
@@ -4,6 +4,9 @@ lad:
hints:
account:
display_name: Tu nombre para amostrar.
+ fields: Tu deskripsyon, pronombres, edad, todo lo ke keras.
+ note: 'Puedes @enmentar a otra djente o #etiketas.'
+ show_collections: Otra djente podra ver tus segidos i suivantes. Personas a las kualas siges siempre podran ver que las estas sigiendo.
account_alias:
acct: Espesifika tu nombre de utilizador@domeno del kuento de ande keres migrar
account_migration:
@@ -55,6 +58,7 @@ lad:
setting_display_media_show_all: Amostra siempre el kontenido de multimedia
setting_use_blurhash: Los gradientes se bazan en los kolores de las imajes eskondidas pero faziendo velados los peratim
setting_use_pending_items: Eskonde muevas publikasyones detras de un klik en lugar de desplazar otomatikamente la linya
+ username: Solo puedes uzar letras, shifras i sulinyados
whole_word: Kuando el biervo yave o fraza es solo alfanumerika, solo sera aplikado si konkorda kon todo el biervo
domain_allow:
domain: Este domeno podra obtener datos de este sirvidor i los datos entrantes seran prosesados i archivados
@@ -113,6 +117,8 @@ lad:
sessions:
otp: 'Introduse el kodiche de autentifikasyon de dos pasos djenerado por tu aplikasyon de telefon o uza uno de tus kodiches de recuperasyon:'
webauthn: Si es una yave USB, asigurete de insertarla y, si es necesario, pulsala.
+ settings:
+ show_application: Tu siempre podras ver dizde kuala aplikasyon publikates tu publikasyon.
tag:
name: Solo se puede trokar la kapitalizasyon de las letras, por enshemplo, para ke sea mas meldable
user:
@@ -132,6 +138,9 @@ lad:
fields:
name: Etiketa
value: Kontenido
+ indexable: Inkluye publikasyones publikas en los rezultados de bushkeda
+ show_collections: Amostra tus segidos i suivantes en el profil
+ unlocked: Otomatikamente acheta muevos suivantes
account_alias:
acct: Alias del kuento viejo
account_migration:
@@ -168,6 +177,30 @@ lad:
confirm_password: Konfirma kod
context: Filtra kontekstos
current_password: Kod aktual
+ data: Datos
+ display_name: Nombre amostrado
+ email: Adreso de posta elektronika
+ expires_in: Kaduka dempues de
+ fields: Datos adisyonales
+ header: Imaje de kavesera
+ locale: Lingua de enterfaz
+ max_uses: Maksimo numero de uzos
+ new_password: Muevo kod
+ note: Deskripsyon
+ otp_attempt: Kodiche de dos pasos
+ password: Kod
+ phrase: Biervo yave o fraza
+ setting_aggregate_reblogs: Agrupa repartajasyones en linyas
+ setting_always_send_emails: Siempre embia avizos por posta
+ setting_auto_play_gif: Siempre reproduse los GIFs animados
+ setting_default_language: Lingua de publikasyones
+ setting_default_privacy: Privasita de publikasyones
+ setting_display_media_default: Predeterminado
+ setting_display_media_hide_all: Eskonde todo
+ setting_display_media_show_all: Amostra todo
+ setting_reduce_motion: Reduse el movimyento en animasyones
+ setting_system_font_ui: Uza el font predeterminado del sistem
+ setting_trends: Amostra los trendes de oy
setting_use_blurhash: Amostra gradientes koloridos para kontenido multimedia eskondido
setting_use_pending_items: Modo lento
severity: Severita
@@ -228,3 +261,40 @@ lad:
no_access: Bloka akseso
sign_up_block: Bloka enrejistrasyones
sign_up_requires_approval: Limita enrejistrasyones
+ severity: Regla
+ notification_emails:
+ favourite: A alguno le plaze tu publikasyon
+ follow: Alguno te ampeso a segir
+ follow_request: Alguno tiene solisitado segirte
+ mention: Alguno te enmento
+ pending_account: Muevo kuento nesesita revizyon
+ reblog: Alguno repartajo tu publikasyon
+ report: Muevo raporto fue embiado
+ software_updates:
+ all: Avizame de kada aktualizasyon
+ critical: Avizame solo de aktualizasyones kritikas
+ label: Mueva version de Mastodon esta desponivle
+ none: Nunkua avizame de aktualizasyones (no rekomendado)
+ patch: Avizame de aktualizasyones de yerros
+ rule:
+ text: Regla
+ settings:
+ indexable: Inkluye la pajina de profil en los bushkadores
+ tag:
+ name: Etiketa
+ user:
+ role: Rolo
+ user_role:
+ name: Nombre
+ permissions_as_keys: Permisos
+ position: Priorita
+ webhook:
+ events: Evenimientos kapasitados
+ url: URL de Endpoint
+ 'no': 'No'
+ not_recommended: No rekomendado
+ recommended: Rekomendado
+ required:
+ mark: "*"
+ text: rekerido
+ 'yes': Si
diff --git a/config/locales/tr.yml b/config/locales/tr.yml
index 098719eb7a..568607e70d 100644
--- a/config/locales/tr.yml
+++ b/config/locales/tr.yml
@@ -1272,7 +1272,7 @@ tr:
remove: Filtreden kaldır
index:
hint: Bu filtre diğer ölçütlerden bağımsız olarak tekil gönderileri seçmek için uygulanıyor. Web arayüzünü kullanarak bu filtreye daha fazla gönderi ekleyebilirsiniz.
- title: Filtrelenmiş gönderiler
+ title: Süzgeçlenmiş gönderiler
generic:
all: Tümü
all_items_on_page_selected_html:
diff --git a/config/routes/api.rb b/config/routes/api.rb
index ebe625c238..c86d019b95 100644
--- a/config/routes/api.rb
+++ b/config/routes/api.rb
@@ -308,10 +308,6 @@ namespace :api, format: false do
resources :statuses, only: [:show, :destroy]
end
- namespace :accounts do
- resources :relationships, only: :index
- end
-
namespace :admin do
resources :accounts, only: [:index]
end
diff --git a/db/migrate/20231211234923_create_follow_recommendation_mutes.rb b/db/migrate/20231211234923_create_follow_recommendation_mutes.rb
new file mode 100644
index 0000000000..92cb16e17b
--- /dev/null
+++ b/db/migrate/20231211234923_create_follow_recommendation_mutes.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class CreateFollowRecommendationMutes < ActiveRecord::Migration[7.1]
+ def change
+ create_table :follow_recommendation_mutes do |t|
+ t.references :account, null: false, foreign_key: { on_delete: :cascade }, index: false
+ t.references :target_account, null: false, foreign_key: { to_table: 'accounts', on_delete: :cascade }
+
+ t.timestamps
+ end
+
+ add_index :follow_recommendation_mutes, [:account_id, :target_account_id], unique: true
+ end
+end
diff --git a/db/migrate/20231212073317_add_languages_index_to_account_summaries.rb b/db/migrate/20231212073317_add_languages_index_to_account_summaries.rb
new file mode 100644
index 0000000000..8adaf792c3
--- /dev/null
+++ b/db/migrate/20231212073317_add_languages_index_to_account_summaries.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddLanguagesIndexToAccountSummaries < ActiveRecord::Migration[7.1]
+ disable_ddl_transaction!
+
+ def change
+ add_index :account_summaries, [:account_id, :language, :sensitive], algorithm: :concurrently
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 5b2e454696..f5aaba6b99 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.0].define(version: 2023_10_06_183200) do
+ActiveRecord::Schema[7.1].define(version: 2023_12_12_073317) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -474,6 +474,15 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_06_183200) do
t.index ["tag_id"], name: "index_featured_tags_on_tag_id"
end
+ create_table "follow_recommendation_mutes", force: :cascade do |t|
+ t.bigint "account_id", null: false
+ t.bigint "target_account_id", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["account_id", "target_account_id"], name: "idx_on_account_id_target_account_id_a8c8ddf44e", unique: true
+ t.index ["target_account_id"], name: "index_follow_recommendation_mutes_on_target_account_id"
+ end
+
create_table "follow_recommendation_suppressions", force: :cascade do |t|
t.bigint "account_id", null: false
t.datetime "created_at", null: false
@@ -1224,6 +1233,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_06_183200) do
add_foreign_key "favourites", "statuses", name: "fk_b0e856845e", on_delete: :cascade
add_foreign_key "featured_tags", "accounts", on_delete: :cascade
add_foreign_key "featured_tags", "tags", on_delete: :cascade
+ add_foreign_key "follow_recommendation_mutes", "accounts", column: "target_account_id", on_delete: :cascade
+ add_foreign_key "follow_recommendation_mutes", "accounts", on_delete: :cascade
add_foreign_key "follow_recommendation_suppressions", "accounts", on_delete: :cascade
add_foreign_key "follow_requests", "accounts", column: "target_account_id", name: "fk_9291ec025d", on_delete: :cascade
add_foreign_key "follow_requests", "accounts", name: "fk_76d644b0e7", on_delete: :cascade
@@ -1359,6 +1370,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_06_183200) do
WHERE ((accounts.suspended_at IS NULL) AND (accounts.silenced_at IS NULL) AND (accounts.moved_to_account_id IS NULL) AND (accounts.discoverable = true) AND (accounts.locked = false))
GROUP BY accounts.id;
SQL
+ add_index "account_summaries", ["account_id", "language", "sensitive"], name: "idx_on_account_id_language_sensitive_250461e1eb"
add_index "account_summaries", ["account_id"], name: "index_account_summaries_on_account_id", unique: true
create_view "global_follow_recommendations", materialized: true, sql_definition: <<-SQL
diff --git a/lib/mastodon/cli/maintenance.rb b/lib/mastodon/cli/maintenance.rb
index 2e5243468b..7b3a9852a6 100644
--- a/lib/mastodon/cli/maintenance.rb
+++ b/lib/mastodon/cli/maintenance.rb
@@ -40,6 +40,10 @@ module Mastodon::CLI
class BulkImport < ApplicationRecord; end
class SoftwareUpdate < ApplicationRecord; end
+ class DomainBlock < ApplicationRecord
+ scope :by_severity, -> { order(Arel.sql('(CASE severity WHEN 0 THEN 1 WHEN 1 THEN 2 WHEN 2 THEN 0 END), domain')) }
+ end
+
class PreviewCard < ApplicationRecord
self.inheritance_column = false
end
@@ -249,19 +253,7 @@ module Mastodon::CLI
say 'Deduplicating user records…'
- # Deduplicating email
- ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM users GROUP BY email HAVING count(*) > 1").each do |row|
- users = User.where(id: row['ids'].split(',')).sort_by(&:updated_at).reverse
- ref_user = users.shift
- say "Multiple users registered with e-mail address #{ref_user.email}.", :yellow
- say "e-mail will be disabled for the following accounts: #{users.map { |user| user.account.acct }.join(', ')}", :yellow
- say 'Please reach out to them and set another address with `tootctl account modify` or delete them.', :yellow
-
- users.each_with_index do |user, index|
- user.update!(email: "#{index} " + user.email)
- end
- end
-
+ deduplicate_users_process_email
deduplicate_users_process_confirmation_token
deduplicate_users_process_remember_token
deduplicate_users_process_password_token
@@ -280,6 +272,20 @@ module Mastodon::CLI
ActiveRecord::Base.connection.execute('REINDEX INDEX index_users_on_unconfirmed_email;') if migrator_version >= 2023_07_02_151753
end
+ def deduplicate_users_process_email
+ ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM users GROUP BY email HAVING count(*) > 1").each do |row|
+ users = User.where(id: row['ids'].split(',')).sort_by(&:updated_at).reverse
+ ref_user = users.shift
+ say "Multiple users registered with e-mail address #{ref_user.email}.", :yellow
+ say "e-mail will be disabled for the following accounts: #{users.map { |user| user.account.acct }.join(', ')}", :yellow
+ say 'Please reach out to them and set another address with `tootctl account modify` or delete them.', :yellow
+
+ users.each_with_index do |user, index|
+ user.update!(email: "#{index} " + user.email)
+ end
+ end
+ end
+
def deduplicate_users_process_confirmation_token
ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM users WHERE confirmation_token IS NOT NULL GROUP BY confirmation_token HAVING count(*) > 1").each do |row|
users = User.where(id: row['ids'].split(',')).sort_by(&:created_at).reverse.drop(1)
@@ -546,7 +552,7 @@ module Mastodon::CLI
if migrator_version < 2021_04_21_121431
ActiveRecord::Base.connection.add_index :tags, 'lower((name)::text)', name: 'index_tags_on_name_lower', unique: true
else
- ActiveRecord::Base.connection.execute 'CREATE UNIQUE INDEX CONCURRENTLY index_tags_on_name_lower_btree ON tags (lower(name) text_pattern_ops)'
+ ActiveRecord::Base.connection.execute 'CREATE UNIQUE INDEX index_tags_on_name_lower_btree ON tags (lower(name) text_pattern_ops)'
end
end
@@ -571,7 +577,7 @@ module Mastodon::CLI
say 'Deduplicating webhooks…'
ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM webhooks GROUP BY url HAVING count(*) > 1").each do |row|
- Webhooks.where(id: row['ids'].split(',')).sort_by(&:id).reverse.drop(1).each(&:destroy)
+ Webhook.where(id: row['ids'].split(',')).sort_by(&:id).reverse.drop(1).each(&:destroy)
end
say 'Restoring webhooks indexes…'
@@ -604,11 +610,7 @@ module Mastodon::CLI
say 'Please chose the one to keep unchanged, other ones will be automatically renamed.'
- ref_id = ask('Account to keep unchanged:') do |q|
- q.required true
- q.default 0
- q.convert :int
- end
+ ref_id = ask('Account to keep unchanged:', required: true, default: 0).to_i
accounts.delete_at(ref_id)
diff --git a/spec/controllers/admin/settings/branding_controller_spec.rb b/spec/controllers/admin/settings/branding_controller_spec.rb
index 435616022a..e30300b4e4 100644
--- a/spec/controllers/admin/settings/branding_controller_spec.rb
+++ b/spec/controllers/admin/settings/branding_controller_spec.rb
@@ -19,14 +19,6 @@ RSpec.describe Admin::Settings::BrandingController do
end
describe 'PUT #update' do
- around do |example|
- before = Setting.site_short_description
- Setting.site_short_description = nil
- example.run
- Setting.site_short_description = before
- Setting.new_setting_key = nil
- end
-
it 'cannot create a setting value for a non-admin key' do
expect(Setting.new_setting_key).to be_blank
diff --git a/spec/controllers/api/v1/accounts/statuses_controller_spec.rb b/spec/controllers/api/v1/accounts/statuses_controller_spec.rb
index 0e4fa93017..df71e94ace 100644
--- a/spec/controllers/api/v1/accounts/statuses_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/statuses_controller_spec.rb
@@ -10,11 +10,11 @@ describe Api::V1::Accounts::StatusesController do
before do
allow(controller).to receive(:doorkeeper_token) { token }
- Fabricate(:status, account: user.account)
end
describe 'GET #index' do
it 'returns expected headers', :aggregate_failures do
+ Fabricate(:status, account: user.account)
get :index, params: { account_id: user.account.id, limit: 1 }
expect(response).to have_http_status(200)
@@ -30,7 +30,6 @@ describe Api::V1::Accounts::StatusesController do
end
context 'with exclude replies' do
- let!(:older_statuses) { user.account.statuses.destroy_all }
let!(:status) { Fabricate(:status, account: user.account) }
let!(:status_self_reply) { Fabricate(:status, account: user.account, thread: status) }
diff --git a/spec/controllers/api/v1/filters_controller_spec.rb b/spec/controllers/api/v1/filters_controller_spec.rb
index 8d5408cf54..b0f64ccf41 100644
--- a/spec/controllers/api/v1/filters_controller_spec.rb
+++ b/spec/controllers/api/v1/filters_controller_spec.rb
@@ -15,10 +15,15 @@ RSpec.describe Api::V1::FiltersController do
describe 'GET #index' do
let(:scopes) { 'read:filters' }
let!(:filter) { Fabricate(:custom_filter, account: user.account) }
+ let!(:custom_filter_keyword) { Fabricate(:custom_filter_keyword, custom_filter: filter) }
it 'returns http success' do
get :index
expect(response).to have_http_status(200)
+ expect(body_as_json)
+ .to contain_exactly(
+ include(id: custom_filter_keyword.id.to_s)
+ )
end
end
diff --git a/spec/controllers/api/v1/trends/links_controller_spec.rb b/spec/controllers/api/v1/trends/links_controller_spec.rb
index 12d4198aaf..bcaf066f16 100644
--- a/spec/controllers/api/v1/trends/links_controller_spec.rb
+++ b/spec/controllers/api/v1/trends/links_controller_spec.rb
@@ -6,12 +6,6 @@ RSpec.describe Api::V1::Trends::LinksController do
render_views
describe 'GET #index' do
- around do |example|
- previous = Setting.trends
- example.run
- Setting.trends = previous
- end
-
context 'when trends are disabled' do
before { Setting.trends = false }
diff --git a/spec/controllers/api/v1/trends/statuses_controller_spec.rb b/spec/controllers/api/v1/trends/statuses_controller_spec.rb
index 69fdb270d5..25ab5f228a 100644
--- a/spec/controllers/api/v1/trends/statuses_controller_spec.rb
+++ b/spec/controllers/api/v1/trends/statuses_controller_spec.rb
@@ -6,12 +6,6 @@ RSpec.describe Api::V1::Trends::StatusesController do
render_views
describe 'GET #index' do
- around do |example|
- previous = Setting.trends
- example.run
- Setting.trends = previous
- end
-
context 'when trends are disabled' do
before { Setting.trends = false }
diff --git a/spec/controllers/api/v1/trends/tags_controller_spec.rb b/spec/controllers/api/v1/trends/tags_controller_spec.rb
index 9311392cd9..c889f1c5b9 100644
--- a/spec/controllers/api/v1/trends/tags_controller_spec.rb
+++ b/spec/controllers/api/v1/trends/tags_controller_spec.rb
@@ -6,12 +6,6 @@ RSpec.describe Api::V1::Trends::TagsController do
render_views
describe 'GET #index' do
- around do |example|
- previous = Setting.trends
- example.run
- Setting.trends = previous
- end
-
context 'when trends are disabled' do
before { Setting.trends = false }
diff --git a/spec/controllers/api/v2/admin/accounts_controller_spec.rb b/spec/controllers/api/v2/admin/accounts_controller_spec.rb
index 18b3950140..349f4d2528 100644
--- a/spec/controllers/api/v2/admin/accounts_controller_spec.rb
+++ b/spec/controllers/api/v2/admin/accounts_controller_spec.rb
@@ -34,28 +34,56 @@ RSpec.describe Api::V2::Admin::AccountsController do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
- [
- [{ status: 'active', origin: 'local', permissions: 'staff' }, [:admin_account]],
- [{ by_domain: 'example.org', origin: 'remote' }, [:remote_account]],
- [{ status: 'suspended' }, [:suspended_remote, :suspended_account]],
- [{ status: 'disabled' }, [:disabled_account]],
- [{ status: 'pending' }, [:pending_account]],
- ].each do |params, expected_results|
- context "when called with #{params.inspect}" do
- let(:params) { params }
+ context 'when called with status active and origin local and permissions staff' do
+ let(:params) { { status: 'active', origin: 'local', permissions: 'staff' } }
- it "returns the correct accounts (#{expected_results.inspect})" do
- expect(response).to have_http_status(200)
-
- expect(body_json_ids).to eq(expected_results.map { |symbol| send(symbol).id })
- end
-
- def body_json_ids
- body_as_json.map { |a| a[:id].to_i }
- end
+ it 'returns the correct accounts' do
+ expect(response).to have_http_status(200)
+ expect(body_json_ids).to eq([admin_account.id])
end
end
+ context 'when called with by_domain value and origin remote' do
+ let(:params) { { by_domain: 'example.org', origin: 'remote' } }
+
+ it 'returns the correct accounts' do
+ expect(response).to have_http_status(200)
+ expect(body_json_ids).to include(remote_account.id)
+ expect(body_json_ids).to_not include(other_remote_account.id)
+ end
+ end
+
+ context 'when called with status suspended' do
+ let(:params) { { status: 'suspended' } }
+
+ it 'returns the correct accounts' do
+ expect(response).to have_http_status(200)
+ expect(body_json_ids).to include(suspended_remote.id, suspended_account.id)
+ end
+ end
+
+ context 'when called with status disabled' do
+ let(:params) { { status: 'disabled' } }
+
+ it 'returns the correct accounts' do
+ expect(response).to have_http_status(200)
+ expect(body_json_ids).to include(disabled_account.id)
+ end
+ end
+
+ context 'when called with status pending' do
+ let(:params) { { status: 'pending' } }
+
+ it 'returns the correct accounts' do
+ expect(response).to have_http_status(200)
+ expect(body_json_ids).to include(pending_account.id)
+ end
+ end
+
+ def body_json_ids
+ body_as_json.map { |a| a[:id].to_i }
+ end
+
context 'with limit param' do
let(:params) { { limit: 1 } }
diff --git a/spec/controllers/api/v2/filters/keywords_controller_spec.rb b/spec/controllers/api/v2/filters/keywords_controller_spec.rb
index 5321f787a1..9f25237bcf 100644
--- a/spec/controllers/api/v2/filters/keywords_controller_spec.rb
+++ b/spec/controllers/api/v2/filters/keywords_controller_spec.rb
@@ -22,6 +22,10 @@ RSpec.describe Api::V2::Filters::KeywordsController do
it 'returns http success' do
get :index, params: { filter_id: filter.id }
expect(response).to have_http_status(200)
+ expect(body_as_json)
+ .to contain_exactly(
+ include(id: keyword.id.to_s)
+ )
end
context "when trying to access another's user filters" do
diff --git a/spec/controllers/api/v2/filters/statuses_controller_spec.rb b/spec/controllers/api/v2/filters/statuses_controller_spec.rb
index 5c2a623954..d9df803971 100644
--- a/spec/controllers/api/v2/filters/statuses_controller_spec.rb
+++ b/spec/controllers/api/v2/filters/statuses_controller_spec.rb
@@ -22,6 +22,10 @@ RSpec.describe Api::V2::Filters::StatusesController do
it 'returns http success' do
get :index, params: { filter_id: filter.id }
expect(response).to have_http_status(200)
+ expect(body_as_json)
+ .to contain_exactly(
+ include(id: status_filter.id.to_s)
+ )
end
context "when trying to access another's user filters" do
diff --git a/spec/controllers/auth/confirmations_controller_spec.rb b/spec/controllers/auth/confirmations_controller_spec.rb
index 58bc38f548..15403e8ea1 100644
--- a/spec/controllers/auth/confirmations_controller_spec.rb
+++ b/spec/controllers/auth/confirmations_controller_spec.rb
@@ -41,8 +41,9 @@ describe Auth::ConfirmationsController do
get :show, params: { confirmation_token: 'foobar' }
end
- it 'redirects to login' do
+ it 'redirects to login and confirms user' do
expect(response).to redirect_to(new_user_session_path)
+ expect(user.reload.confirmed_at).to_not be_nil
end
end
@@ -87,8 +88,9 @@ describe Auth::ConfirmationsController do
get :show, params: { confirmation_token: 'foobar' }
end
- it 'redirects to login' do
+ it 'redirects to login and confirms email' do
expect(response).to redirect_to(new_user_session_path)
+ expect(user.reload.unconfirmed_email).to be_nil
end
it 'does not queue up bootstrapping of home timeline' do
diff --git a/spec/controllers/auth/passwords_controller_spec.rb b/spec/controllers/auth/passwords_controller_spec.rb
index e7f7ab4676..d70490abcf 100644
--- a/spec/controllers/auth/passwords_controller_spec.rb
+++ b/spec/controllers/auth/passwords_controller_spec.rb
@@ -70,6 +70,7 @@ describe Auth::PasswordsController do
it 'deactivates all sessions' do
expect(user.session_activations.count).to eq 0
+ expect { session_activation.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
it 'revokes all access tokens' do
@@ -78,6 +79,7 @@ describe Auth::PasswordsController do
it 'removes push subscriptions' do
expect(Web::PushSubscription.where(user: user).or(Web::PushSubscription.where(access_token: access_token)).count).to eq 0
+ expect { web_push_subscription.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
diff --git a/spec/controllers/auth/registrations_controller_spec.rb b/spec/controllers/auth/registrations_controller_spec.rb
index a9b24a1004..37172f8d24 100644
--- a/spec/controllers/auth/registrations_controller_spec.rb
+++ b/spec/controllers/auth/registrations_controller_spec.rb
@@ -6,12 +6,6 @@ RSpec.describe Auth::RegistrationsController do
render_views
shared_examples 'checks for enabled registrations' do |path|
- around do |example|
- registrations_mode = Setting.registrations_mode
- example.run
- Setting.registrations_mode = registrations_mode
- end
-
it 'redirects if it is in single user mode while it is open for registration' do
Fabricate(:account)
Setting.registrations_mode = 'open'
@@ -82,12 +76,6 @@ RSpec.describe Auth::RegistrationsController do
end
context 'with open registrations' do
- around do |example|
- registrations_mode = Setting.registrations_mode
- example.run
- Setting.registrations_mode = registrations_mode
- end
-
it 'returns http success' do
Setting.registrations_mode = 'open'
get :new
@@ -120,12 +108,6 @@ RSpec.describe Auth::RegistrationsController do
post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', agreement: 'true' } }
end
- around do |example|
- registrations_mode = Setting.registrations_mode
- example.run
- Setting.registrations_mode = registrations_mode
- end
-
it 'redirects to setup' do
subject
expect(response).to redirect_to auth_setup_path
@@ -146,12 +128,6 @@ RSpec.describe Auth::RegistrationsController do
post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', agreement: 'false' } }
end
- around do |example|
- registrations_mode = Setting.registrations_mode
- example.run
- Setting.registrations_mode = registrations_mode
- end
-
it 'does not create user' do
subject
user = User.find_by(email: 'test@example.com')
@@ -166,12 +142,6 @@ RSpec.describe Auth::RegistrationsController do
post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', agreement: 'true' } }
end
- around do |example|
- registrations_mode = Setting.registrations_mode
- example.run
- Setting.registrations_mode = registrations_mode
- end
-
it 'redirects to setup' do
subject
expect(response).to redirect_to auth_setup_path
@@ -194,12 +164,6 @@ RSpec.describe Auth::RegistrationsController do
post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', invite_code: invite.code, agreement: 'true' } }
end
- around do |example|
- registrations_mode = Setting.registrations_mode
- example.run
- Setting.registrations_mode = registrations_mode
- end
-
it 'redirects to setup' do
subject
expect(response).to redirect_to auth_setup_path
@@ -224,14 +188,6 @@ RSpec.describe Auth::RegistrationsController do
post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', invite_code: invite.code, agreement: 'true' } }
end
- around do |example|
- registrations_mode = Setting.registrations_mode
- require_invite_text = Setting.require_invite_text
- example.run
- Setting.require_invite_text = require_invite_text
- Setting.registrations_mode = registrations_mode
- end
-
it 'redirects to setup' do
subject
expect(response).to redirect_to auth_setup_path
diff --git a/spec/controllers/auth/sessions_controller_spec.rb b/spec/controllers/auth/sessions_controller_spec.rb
index f341d75b79..212cc4d5e5 100644
--- a/spec/controllers/auth/sessions_controller_spec.rb
+++ b/spec/controllers/auth/sessions_controller_spec.rb
@@ -123,9 +123,8 @@ RSpec.describe Auth::SessionsController do
let(:previous_ip) { '1.2.3.4' }
let(:current_ip) { '4.3.2.1' }
- let!(:previous_login) { Fabricate(:login_activity, user: user, ip: previous_ip) }
-
before do
+ Fabricate(:login_activity, user: user, ip: previous_ip)
allow(controller.request).to receive(:remote_ip).and_return(current_ip)
user.update(current_sign_in_at: 1.month.ago)
post :create, params: { user: { email: user.email, password: user.password } }
@@ -328,12 +327,6 @@ RSpec.describe Auth::SessionsController do
Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret(32))
end
- let!(:recovery_codes) do
- codes = user.generate_otp_backup_codes!
- user.save
- return codes
- end
-
let!(:webauthn_credential) do
user.update(webauthn_id: WebAuthn.generate_user_id)
public_key_credential = WebAuthn::Credential.from_create(fake_client.create)
@@ -356,6 +349,11 @@ RSpec.describe Auth::SessionsController do
let(:fake_credential) { fake_client.get(challenge: challenge, sign_count: sign_count) }
+ before do
+ user.generate_otp_backup_codes!
+ user.save
+ end
+
context 'when using email and password' do
before do
post :create, params: { user: { email: user.email, password: user.password } }
diff --git a/spec/controllers/concerns/account_controller_concern_spec.rb b/spec/controllers/concerns/account_controller_concern_spec.rb
index 56ffcfb047..e3295204aa 100644
--- a/spec/controllers/concerns/account_controller_concern_spec.rb
+++ b/spec/controllers/concerns/account_controller_concern_spec.rb
@@ -11,12 +11,6 @@ describe AccountControllerConcern do
end
end
- around do |example|
- registrations_mode = Setting.registrations_mode
- example.run
- Setting.registrations_mode = registrations_mode
- end
-
before do
routes.draw { get 'success' => 'anonymous#success' }
end
diff --git a/spec/controllers/concerns/signature_verification_spec.rb b/spec/controllers/concerns/signature_verification_spec.rb
deleted file mode 100644
index 650cd21eaf..0000000000
--- a/spec/controllers/concerns/signature_verification_spec.rb
+++ /dev/null
@@ -1,305 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-describe SignatureVerification do
- let(:wrapped_actor_class) do
- Class.new do
- attr_reader :wrapped_account
-
- def initialize(wrapped_account)
- @wrapped_account = wrapped_account
- end
-
- delegate :uri, :keypair, to: :wrapped_account
- end
- end
-
- controller(ApplicationController) do
- include SignatureVerification
-
- before_action :require_actor_signature!, only: [:signature_required]
-
- def success
- head 200
- end
-
- def alternative_success
- head 200
- end
-
- def signature_required
- head 200
- end
- end
-
- before do
- routes.draw do
- match :via => [:get, :post], 'success' => 'anonymous#success'
- match :via => [:get, :post], 'signature_required' => 'anonymous#signature_required'
- end
- end
-
- context 'without signature header' do
- before do
- get :success
- end
-
- describe '#signed_request?' do
- it 'returns false' do
- expect(controller.signed_request?).to be false
- end
- end
-
- describe '#signed_request_account' do
- it 'returns nil' do
- expect(controller.signed_request_account).to be_nil
- end
- end
- end
-
- context 'with signature header' do
- let!(:author) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/actor') }
-
- context 'without body' do
- before do
- get :success
-
- fake_request = Request.new(:get, request.url)
- fake_request.on_behalf_of(author)
-
- request.headers.merge!(fake_request.headers)
- end
-
- describe '#signed_request?' do
- it 'returns true' do
- expect(controller.signed_request?).to be true
- end
- end
-
- describe '#signed_request_account' do
- it 'returns an account' do
- expect(controller.signed_request_account).to eq author
- end
-
- it 'returns nil when path does not match' do
- request.path = '/alternative-path'
- expect(controller.signed_request_account).to be_nil
- end
-
- it 'returns nil when method does not match' do
- post :success
- expect(controller.signed_request_account).to be_nil
- end
- end
- end
-
- context 'with a valid actor that is not an Account' do
- let(:actor) { wrapped_actor_class.new(author) }
-
- before do
- get :success
-
- fake_request = Request.new(:get, request.url)
- fake_request.on_behalf_of(author)
-
- request.headers.merge!(fake_request.headers)
-
- allow(ActivityPub::TagManager.instance).to receive(:uri_to_actor).with(anything) do
- actor
- end
- end
-
- describe '#signed_request?' do
- it 'returns true' do
- expect(controller.signed_request?).to be true
- end
- end
-
- describe '#signed_request_account' do
- it 'returns nil' do
- expect(controller.signed_request_account).to be_nil
- end
- end
-
- describe '#signed_request_actor' do
- it 'returns the expected actor' do
- expect(controller.signed_request_actor).to eq actor
- end
- end
- end
-
- context 'with request with unparsable Date header' do
- before do
- get :success
-
- fake_request = Request.new(:get, request.url)
- fake_request.add_headers({ 'Date' => 'wrong date' })
- fake_request.on_behalf_of(author)
-
- request.headers.merge!(fake_request.headers)
- end
-
- describe '#signed_request?' do
- it 'returns true' do
- expect(controller.signed_request?).to be true
- end
- end
-
- describe '#signed_request_account' do
- it 'returns nil' do
- expect(controller.signed_request_account).to be_nil
- end
- end
-
- describe '#signature_verification_failure_reason' do
- it 'contains an error description' do
- controller.signed_request_account
- expect(controller.signature_verification_failure_reason[:error]).to eq 'Invalid Date header: not RFC 2616 compliant date: "wrong date"'
- end
- end
- end
-
- context 'with request older than a day' do
- before do
- get :success
-
- fake_request = Request.new(:get, request.url)
- fake_request.add_headers({ 'Date' => 2.days.ago.utc.httpdate })
- fake_request.on_behalf_of(author)
-
- request.headers.merge!(fake_request.headers)
- end
-
- describe '#signed_request?' do
- it 'returns true' do
- expect(controller.signed_request?).to be true
- end
- end
-
- describe '#signed_request_account' do
- it 'returns nil' do
- expect(controller.signed_request_account).to be_nil
- end
- end
-
- describe '#signature_verification_failure_reason' do
- it 'contains an error description' do
- controller.signed_request_account
- expect(controller.signature_verification_failure_reason[:error]).to eq 'Signed request date outside acceptable time window'
- end
- end
- end
-
- context 'with inaccessible key' do
- before do
- get :success
-
- author = Fabricate(:account, domain: 'localhost:5000', uri: 'http://localhost:5000/actor')
- fake_request = Request.new(:get, request.url)
- fake_request.on_behalf_of(author)
- author.destroy
-
- request.headers.merge!(fake_request.headers)
-
- stub_request(:get, 'http://localhost:5000/actor#main-key').to_raise(Mastodon::HostValidationError)
- end
-
- describe '#signed_request?' do
- it 'returns true' do
- expect(controller.signed_request?).to be true
- end
- end
-
- describe '#signed_request_account' do
- it 'returns nil' do
- expect(controller.signed_request_account).to be_nil
- end
- end
- end
-
- context 'with body' do
- before do
- allow(controller).to receive(:actor_refresh_key!).and_return(author)
- post :success, body: 'Hello world'
-
- fake_request = Request.new(:post, request.url, body: 'Hello world')
- fake_request.on_behalf_of(author)
-
- request.headers.merge!(fake_request.headers)
- end
-
- describe '#signed_request?' do
- it 'returns true' do
- expect(controller.signed_request?).to be true
- end
- end
-
- describe '#signed_request_account' do
- it 'returns an account' do
- expect(controller.signed_request_account).to eq author
- end
- end
-
- context 'when path does not match' do
- before do
- request.path = '/alternative-path'
- end
-
- describe '#signed_request_account' do
- it 'returns nil' do
- expect(controller.signed_request_account).to be_nil
- end
- end
-
- describe '#signature_verification_failure_reason' do
- it 'contains an error description' do
- controller.signed_request_account
- expect(controller.signature_verification_failure_reason[:error]).to include('using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)')
- expect(controller.signature_verification_failure_reason[:signed_string]).to include("(request-target): post /alternative-path\n")
- end
- end
- end
-
- context 'when method does not match' do
- before do
- get :success
- end
-
- describe '#signed_request_account' do
- it 'returns nil' do
- expect(controller.signed_request_account).to be_nil
- end
- end
- end
-
- context 'when body has been tampered' do
- before do
- post :success, body: 'doo doo doo'
- end
-
- describe '#signed_request_account' do
- it 'returns nil when body has been tampered' do
- expect(controller.signed_request_account).to be_nil
- end
- end
- end
- end
- end
-
- context 'when a signature is required' do
- before do
- get :signature_required
- end
-
- context 'without signature header' do
- it 'returns HTTP 401' do
- expect(response).to have_http_status(401)
- end
-
- it 'returns an error' do
- expect(Oj.load(response.body)['error']).to eq 'Request not signed'
- end
- end
- end
-end
diff --git a/spec/controllers/follower_accounts_controller_spec.rb b/spec/controllers/follower_accounts_controller_spec.rb
index cb8c2a0e5b..dd78c96c05 100644
--- a/spec/controllers/follower_accounts_controller_spec.rb
+++ b/spec/controllers/follower_accounts_controller_spec.rb
@@ -48,6 +48,13 @@ describe FollowerAccountsController do
it 'returns followers' do
expect(response).to have_http_status(200)
+ expect(body_as_json)
+ .to include(
+ orderedItems: contain_exactly(
+ include(follow_from_bob.account.username),
+ include(follow_from_chris.account.username)
+ )
+ )
expect(body['totalItems']).to eq 2
expect(body['partOf']).to be_present
end
diff --git a/spec/controllers/following_accounts_controller_spec.rb b/spec/controllers/following_accounts_controller_spec.rb
index 095528ed07..7bb78fb420 100644
--- a/spec/controllers/following_accounts_controller_spec.rb
+++ b/spec/controllers/following_accounts_controller_spec.rb
@@ -48,6 +48,13 @@ describe FollowingAccountsController do
it 'returns followers' do
expect(response).to have_http_status(200)
+ expect(body_as_json)
+ .to include(
+ orderedItems: contain_exactly(
+ include(follow_of_bob.target_account.username),
+ include(follow_of_chris.target_account.username)
+ )
+ )
expect(body['totalItems']).to eq 2
expect(body['partOf']).to be_present
end
diff --git a/spec/controllers/oauth/authorized_applications_controller_spec.rb b/spec/controllers/oauth/authorized_applications_controller_spec.rb
index b54610604c..b46b944d0e 100644
--- a/spec/controllers/oauth/authorized_applications_controller_spec.rb
+++ b/spec/controllers/oauth/authorized_applications_controller_spec.rb
@@ -63,5 +63,9 @@ describe Oauth::AuthorizedApplicationsController do
it 'removes subscriptions for the application\'s access tokens' do
expect(Web::PushSubscription.where(user: user).count).to eq 0
end
+
+ it 'removes the web_push_subscription' do
+ expect { web_push_subscription.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
end
end
diff --git a/spec/controllers/oauth/tokens_controller_spec.rb b/spec/controllers/oauth/tokens_controller_spec.rb
index 973393bcf2..dd2d8ca70c 100644
--- a/spec/controllers/oauth/tokens_controller_spec.rb
+++ b/spec/controllers/oauth/tokens_controller_spec.rb
@@ -20,5 +20,9 @@ RSpec.describe Oauth::TokensController do
it 'removes web push subscription for token' do
expect(Web::PushSubscription.where(access_token: access_token).count).to eq 0
end
+
+ it 'removes the web_push_subscription' do
+ expect { web_push_subscription.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
end
end
diff --git a/spec/controllers/settings/imports_controller_spec.rb b/spec/controllers/settings/imports_controller_spec.rb
index 1e7b758931..89ec39e54d 100644
--- a/spec/controllers/settings/imports_controller_spec.rb
+++ b/spec/controllers/settings/imports_controller_spec.rb
@@ -22,6 +22,7 @@ RSpec.describe Settings::ImportsController do
it 'assigns the expected imports', :aggregate_failures do
expect(response).to have_http_status(200)
expect(assigns(:recent_imports)).to eq [import]
+ expect(assigns(:recent_imports)).to_not include(other_import)
expect(response.headers['Cache-Control']).to include('private, no-store')
end
end
@@ -138,6 +139,7 @@ RSpec.describe Settings::ImportsController do
let(:bulk_import) { Fabricate(:bulk_import, account: user.account, type: import_type, state: :finished) }
before do
+ rows.each { |data| Fabricate(:bulk_import_row, bulk_import: bulk_import, data: data) }
bulk_import.update(total_items: bulk_import.rows.count, processed_items: bulk_import.rows.count, imported_items: 0)
end
@@ -152,11 +154,11 @@ RSpec.describe Settings::ImportsController do
context 'with follows' do
let(:import_type) { 'following' }
- let!(:rows) do
+ let(:rows) do
[
{ 'acct' => 'foo@bar' },
{ 'acct' => 'user@bar', 'show_reblogs' => false, 'notify' => true, 'languages' => %w(fr de) },
- ].map { |data| Fabricate(:bulk_import_row, bulk_import: bulk_import, data: data) }
+ ]
end
include_examples 'export failed rows', "Account address,Show boosts,Notify on new posts,Languages\nfoo@bar,true,false,\nuser@bar,false,true,\"fr, de\"\n"
@@ -165,11 +167,11 @@ RSpec.describe Settings::ImportsController do
context 'with blocks' do
let(:import_type) { 'blocking' }
- let!(:rows) do
+ let(:rows) do
[
{ 'acct' => 'foo@bar' },
{ 'acct' => 'user@bar' },
- ].map { |data| Fabricate(:bulk_import_row, bulk_import: bulk_import, data: data) }
+ ]
end
include_examples 'export failed rows', "foo@bar\nuser@bar\n"
@@ -178,11 +180,11 @@ RSpec.describe Settings::ImportsController do
context 'with mutes' do
let(:import_type) { 'muting' }
- let!(:rows) do
+ let(:rows) do
[
{ 'acct' => 'foo@bar' },
{ 'acct' => 'user@bar', 'hide_notifications' => false },
- ].map { |data| Fabricate(:bulk_import_row, bulk_import: bulk_import, data: data) }
+ ]
end
include_examples 'export failed rows', "Account address,Hide notifications\nfoo@bar,true\nuser@bar,false\n"
@@ -191,11 +193,11 @@ RSpec.describe Settings::ImportsController do
context 'with domain blocks' do
let(:import_type) { 'domain_blocking' }
- let!(:rows) do
+ let(:rows) do
[
{ 'domain' => 'bad.domain' },
{ 'domain' => 'evil.domain' },
- ].map { |data| Fabricate(:bulk_import_row, bulk_import: bulk_import, data: data) }
+ ]
end
include_examples 'export failed rows', "bad.domain\nevil.domain\n"
@@ -204,11 +206,11 @@ RSpec.describe Settings::ImportsController do
context 'with bookmarks' do
let(:import_type) { 'bookmarks' }
- let!(:rows) do
+ let(:rows) do
[
{ 'uri' => 'https://foo.com/1' },
{ 'uri' => 'https://foo.com/2' },
- ].map { |data| Fabricate(:bulk_import_row, bulk_import: bulk_import, data: data) }
+ ]
end
include_examples 'export failed rows', "https://foo.com/1\nhttps://foo.com/2\n"
@@ -217,11 +219,11 @@ RSpec.describe Settings::ImportsController do
context 'with lists' do
let(:import_type) { 'lists' }
- let!(:rows) do
+ let(:rows) do
[
{ 'list_name' => 'Amigos', 'acct' => 'user@example.com' },
{ 'list_name' => 'Frenemies', 'acct' => 'user@org.org' },
- ].map { |data| Fabricate(:bulk_import_row, bulk_import: bulk_import, data: data) }
+ ]
end
include_examples 'export failed rows', "Amigos,user@example.com\nFrenemies,user@org.org\n"
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 82272b717f..ec9d908ee9 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -294,12 +294,6 @@ describe ApplicationHelper do
end
describe 'title' do
- around do |example|
- site_title = Setting.site_title
- example.run
- Setting.site_title = site_title
- end
-
it 'returns site title on production environment' do
Setting.site_title = 'site title'
allow(Rails.env).to receive(:production?).and_return(true)
@@ -320,12 +314,6 @@ describe ApplicationHelper do
allow(Rails.env).to receive(:production?).and_return(true)
end
- around do |example|
- site_title = Setting.site_title
- example.run
- Setting.site_title = site_title
- end
-
context 'with a page_title content_for value' do
it 'uses the value in the html title' do
Setting.site_title = 'Site Title'
diff --git a/spec/helpers/instance_helper_spec.rb b/spec/helpers/instance_helper_spec.rb
index cdbce56598..7e39b7cb3d 100644
--- a/spec/helpers/instance_helper_spec.rb
+++ b/spec/helpers/instance_helper_spec.rb
@@ -4,12 +4,6 @@ require 'rails_helper'
describe InstanceHelper do
describe 'site_title' do
- around do |example|
- site_title = Setting.site_title
- example.run
- Setting.site_title = site_title
- end
-
it 'Uses the Setting.site_title value when it exists' do
Setting.site_title = 'New site title'
diff --git a/spec/lib/activitypub/activity/delete_spec.rb b/spec/lib/activitypub/activity/delete_spec.rb
index 3a73b3726c..aec2c71e58 100644
--- a/spec/lib/activitypub/activity/delete_spec.rb
+++ b/spec/lib/activitypub/activity/delete_spec.rb
@@ -50,6 +50,10 @@ RSpec.describe ActivityPub::Activity::Delete do
it 'sends delete activity to followers of rebloggers' do
expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
end
+
+ it 'deletes the reblog' do
+ expect { reblog.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
end
end
diff --git a/spec/lib/mastodon/cli/maintenance_spec.rb b/spec/lib/mastodon/cli/maintenance_spec.rb
index 02169b7a42..ca492bbf69 100644
--- a/spec/lib/mastodon/cli/maintenance_spec.rb
+++ b/spec/lib/mastodon/cli/maintenance_spec.rb
@@ -52,5 +52,554 @@ describe Mastodon::CLI::Maintenance do
.and raise_error(SystemExit)
end
end
+
+ context 'when requirements are met' do
+ before do
+ allow(ActiveRecord::Migrator).to receive(:current_version).and_return(2023_08_22_081029) # The latest migration before the cutoff
+ agree_to_backup_warning
+ end
+
+ context 'with duplicate accounts' do
+ before do
+ prepare_duplicate_data
+ choose_local_account_to_keep
+ end
+
+ let(:duplicate_account_username) { 'username' }
+ let(:duplicate_account_domain) { 'host.example' }
+
+ it 'runs the deduplication process' do
+ expect { subject }
+ .to output_results(
+ 'Deduplicating accounts',
+ 'Multiple local accounts were found for',
+ 'Restoring index_accounts_on_username_and_domain_lower',
+ 'Reindexing textual indexes on accounts…',
+ 'Finished!'
+ )
+ .and change(duplicate_remote_accounts, :count).from(2).to(1)
+ .and change(duplicate_local_accounts, :count).from(2).to(1)
+ end
+
+ def duplicate_remote_accounts
+ Account.where(username: duplicate_account_username, domain: duplicate_account_domain)
+ end
+
+ def duplicate_local_accounts
+ Account.where(username: duplicate_account_username, domain: nil)
+ end
+
+ def prepare_duplicate_data
+ ActiveRecord::Base.connection.remove_index :accounts, name: :index_accounts_on_username_and_domain_lower
+ _remote_account = Fabricate(:account, username: duplicate_account_username, domain: duplicate_account_domain)
+ _remote_account_dupe = Fabricate.build(:account, username: duplicate_account_username, domain: duplicate_account_domain).save(validate: false)
+ _local_account = Fabricate(:account, username: duplicate_account_username, domain: nil)
+ _local_account_dupe = Fabricate.build(:account, username: duplicate_account_username, domain: nil).save(validate: false)
+ end
+
+ def choose_local_account_to_keep
+ allow(cli.shell)
+ .to receive(:ask)
+ .with(/Account to keep unchanged/, anything)
+ .and_return('0')
+ .once
+ end
+ end
+
+ context 'with duplicate users on email' do
+ before do
+ prepare_duplicate_data
+ end
+
+ let(:duplicate_email) { 'duplicate@example.host' }
+
+ it 'runs the deduplication process' do
+ expect { subject }
+ .to output_results(
+ 'Deduplicating user records',
+ 'Restoring users indexes',
+ 'Finished!'
+ )
+ .and change(duplicate_users, :count).from(2).to(1)
+ end
+
+ def duplicate_users
+ User.where(email: duplicate_email)
+ end
+
+ def prepare_duplicate_data
+ ActiveRecord::Base.connection.remove_index :users, :email
+ Fabricate(:user, email: duplicate_email)
+ Fabricate.build(:user, email: duplicate_email).save(validate: false)
+ end
+ end
+
+ context 'with duplicate users on confirmation_token' do
+ before do
+ prepare_duplicate_data
+ end
+
+ let(:duplicate_confirmation_token) { '123ABC' }
+
+ it 'runs the deduplication process' do
+ expect { subject }
+ .to output_results(
+ 'Deduplicating user records',
+ 'Unsetting confirmation token',
+ 'Restoring users indexes',
+ 'Finished!'
+ )
+ .and change(duplicate_users, :count).from(2).to(1)
+ end
+
+ def duplicate_users
+ User.where(confirmation_token: duplicate_confirmation_token)
+ end
+
+ def prepare_duplicate_data
+ ActiveRecord::Base.connection.remove_index :users, :confirmation_token
+ Fabricate(:user, confirmation_token: duplicate_confirmation_token)
+ Fabricate.build(:user, confirmation_token: duplicate_confirmation_token).save(validate: false)
+ end
+ end
+
+ context 'with duplicate users on reset_password_token' do
+ before do
+ prepare_duplicate_data
+ end
+
+ let(:duplicate_reset_password_token) { '123ABC' }
+
+ it 'runs the deduplication process' do
+ expect { subject }
+ .to output_results(
+ 'Deduplicating user records',
+ 'Unsetting password reset token',
+ 'Restoring users indexes',
+ 'Finished!'
+ )
+ .and change(duplicate_users, :count).from(2).to(1)
+ end
+
+ def duplicate_users
+ User.where(reset_password_token: duplicate_reset_password_token)
+ end
+
+ def prepare_duplicate_data
+ ActiveRecord::Base.connection.remove_index :users, :reset_password_token
+ Fabricate(:user, reset_password_token: duplicate_reset_password_token)
+ Fabricate.build(:user, reset_password_token: duplicate_reset_password_token).save(validate: false)
+ end
+ end
+
+ context 'with duplicate account_domain_blocks' do
+ before do
+ prepare_duplicate_data
+ end
+
+ let(:duplicate_domain) { 'example.host' }
+ let(:account) { Fabricate(:account) }
+
+ it 'runs the deduplication process' do
+ expect { subject }
+ .to output_results(
+ 'Removing duplicate account domain blocks',
+ 'Restoring account domain blocks indexes',
+ 'Finished!'
+ )
+ .and change(duplicate_account_domain_blocks, :count).from(2).to(1)
+ end
+
+ def duplicate_account_domain_blocks
+ AccountDomainBlock.where(account: account, domain: duplicate_domain)
+ end
+
+ def prepare_duplicate_data
+ ActiveRecord::Base.connection.remove_index :account_domain_blocks, [:account_id, :domain]
+ Fabricate(:account_domain_block, account: account, domain: duplicate_domain)
+ Fabricate.build(:account_domain_block, account: account, domain: duplicate_domain).save(validate: false)
+ end
+ end
+
+ context 'with duplicate announcement_reactions' do
+ before do
+ prepare_duplicate_data
+ end
+
+ let(:account) { Fabricate(:account) }
+ let(:announcement) { Fabricate(:announcement) }
+ let(:name) { Fabricate(:custom_emoji).shortcode }
+
+ it 'runs the deduplication process' do
+ expect { subject }
+ .to output_results(
+ 'Removing duplicate announcement reactions',
+ 'Restoring announcement_reactions indexes',
+ 'Finished!'
+ )
+ .and change(duplicate_announcement_reactions, :count).from(2).to(1)
+ end
+
+ def duplicate_announcement_reactions
+ AnnouncementReaction.where(account: account, announcement: announcement, name: name)
+ end
+
+ def prepare_duplicate_data
+ ActiveRecord::Base.connection.remove_index :announcement_reactions, [:account_id, :announcement_id, :name]
+ Fabricate(:announcement_reaction, account: account, announcement: announcement, name: name)
+ Fabricate.build(:announcement_reaction, account: account, announcement: announcement, name: name).save(validate: false)
+ end
+ end
+
+ context 'with duplicate conversations' do
+ before do
+ prepare_duplicate_data
+ end
+
+ let(:uri) { 'https://example.host/path' }
+
+ it 'runs the deduplication process' do
+ expect { subject }
+ .to output_results(
+ 'Deduplicating conversations',
+ 'Restoring conversations indexes',
+ 'Finished!'
+ )
+ .and change(duplicate_conversations, :count).from(2).to(1)
+ end
+
+ def duplicate_conversations
+ Conversation.where(uri: uri)
+ end
+
+ def prepare_duplicate_data
+ ActiveRecord::Base.connection.remove_index :conversations, :uri
+ Fabricate(:conversation, uri: uri)
+ Fabricate.build(:conversation, uri: uri).save(validate: false)
+ end
+ end
+
+ context 'with duplicate custom_emojis' do
+ before do
+ prepare_duplicate_data
+ end
+
+ let(:duplicate_shortcode) { 'wowzers' }
+ let(:duplicate_domain) { 'example.host' }
+
+ it 'runs the deduplication process' do
+ expect { subject }
+ .to output_results(
+ 'Deduplicating custom_emojis',
+ 'Restoring custom_emojis indexes',
+ 'Finished!'
+ )
+ .and change(duplicate_custom_emojis, :count).from(2).to(1)
+ end
+
+ def duplicate_custom_emojis
+ CustomEmoji.where(shortcode: duplicate_shortcode, domain: duplicate_domain)
+ end
+
+ def prepare_duplicate_data
+ ActiveRecord::Base.connection.remove_index :custom_emojis, [:shortcode, :domain]
+ Fabricate(:custom_emoji, shortcode: duplicate_shortcode, domain: duplicate_domain)
+ Fabricate.build(:custom_emoji, shortcode: duplicate_shortcode, domain: duplicate_domain).save(validate: false)
+ end
+ end
+
+ context 'with duplicate custom_emoji_categories' do
+ before do
+ prepare_duplicate_data
+ end
+
+ let(:duplicate_name) { 'name_value' }
+
+ it 'runs the deduplication process' do
+ expect { subject }
+ .to output_results(
+ 'Deduplicating custom_emoji_categories',
+ 'Restoring custom_emoji_categories indexes',
+ 'Finished!'
+ )
+ .and change(duplicate_custom_emoji_categories, :count).from(2).to(1)
+ end
+
+ def duplicate_custom_emoji_categories
+ CustomEmojiCategory.where(name: duplicate_name)
+ end
+
+ def prepare_duplicate_data
+ ActiveRecord::Base.connection.remove_index :custom_emoji_categories, :name
+ Fabricate(:custom_emoji_category, name: duplicate_name)
+ Fabricate.build(:custom_emoji_category, name: duplicate_name).save(validate: false)
+ end
+ end
+
+ context 'with duplicate domain_allows' do
+ before do
+ prepare_duplicate_data
+ end
+
+ let(:domain) { 'example.host' }
+
+ it 'runs the deduplication process' do
+ expect { subject }
+ .to output_results(
+ 'Deduplicating domain_allows',
+ 'Restoring domain_allows indexes',
+ 'Finished!'
+ )
+ .and change(duplicate_domain_allows, :count).from(2).to(1)
+ end
+
+ def duplicate_domain_allows
+ DomainAllow.where(domain: domain)
+ end
+
+ def prepare_duplicate_data
+ ActiveRecord::Base.connection.remove_index :domain_allows, :domain
+ Fabricate(:domain_allow, domain: domain)
+ Fabricate.build(:domain_allow, domain: domain).save(validate: false)
+ end
+ end
+
+ context 'with duplicate domain_blocks' do
+ before do
+ prepare_duplicate_data
+ end
+
+ let(:domain) { 'example.host' }
+
+ it 'runs the deduplication process' do
+ expect { subject }
+ .to output_results(
+ 'Deduplicating domain_blocks',
+ 'Restoring domain_blocks indexes',
+ 'Finished!'
+ )
+ .and change(duplicate_domain_blocks, :count).from(2).to(1)
+ end
+
+ def duplicate_domain_blocks
+ DomainBlock.where(domain: domain)
+ end
+
+ def prepare_duplicate_data
+ ActiveRecord::Base.connection.remove_index :domain_blocks, :domain
+ Fabricate(:domain_block, domain: domain)
+ Fabricate.build(:domain_block, domain: domain).save(validate: false)
+ end
+ end
+
+ context 'with duplicate email_domain_blocks' do
+ before do
+ prepare_duplicate_data
+ end
+
+ let(:domain) { 'example.host' }
+
+ it 'runs the deduplication process' do
+ expect { subject }
+ .to output_results(
+ 'Deduplicating email_domain_blocks',
+ 'Restoring email_domain_blocks indexes',
+ 'Finished!'
+ )
+ .and change(duplicate_email_domain_blocks, :count).from(2).to(1)
+ end
+
+ def duplicate_email_domain_blocks
+ EmailDomainBlock.where(domain: domain)
+ end
+
+ def prepare_duplicate_data
+ ActiveRecord::Base.connection.remove_index :email_domain_blocks, :domain
+ Fabricate(:email_domain_block, domain: domain)
+ Fabricate.build(:email_domain_block, domain: domain).save(validate: false)
+ end
+ end
+
+ context 'with duplicate media_attachments' do
+ before do
+ prepare_duplicate_data
+ end
+
+ let(:shortcode) { 'codenam' }
+
+ it 'runs the deduplication process' do
+ expect { subject }
+ .to output_results(
+ 'Deduplicating media_attachments',
+ 'Restoring media_attachments indexes',
+ 'Finished!'
+ )
+ .and change(duplicate_media_attachments, :count).from(2).to(1)
+ end
+
+ def duplicate_media_attachments
+ MediaAttachment.where(shortcode: shortcode)
+ end
+
+ def prepare_duplicate_data
+ ActiveRecord::Base.connection.remove_index :media_attachments, :shortcode
+ Fabricate(:media_attachment, shortcode: shortcode)
+ Fabricate.build(:media_attachment, shortcode: shortcode).save(validate: false)
+ end
+ end
+
+ context 'with duplicate preview_cards' do
+ before do
+ prepare_duplicate_data
+ end
+
+ let(:url) { 'https://example.host/path' }
+
+ it 'runs the deduplication process' do
+ expect { subject }
+ .to output_results(
+ 'Deduplicating preview_cards',
+ 'Restoring preview_cards indexes',
+ 'Finished!'
+ )
+ .and change(duplicate_preview_cards, :count).from(2).to(1)
+ end
+
+ def duplicate_preview_cards
+ PreviewCard.where(url: url)
+ end
+
+ def prepare_duplicate_data
+ ActiveRecord::Base.connection.remove_index :preview_cards, :url
+ Fabricate(:preview_card, url: url)
+ Fabricate.build(:preview_card, url: url).save(validate: false)
+ end
+ end
+
+ context 'with duplicate statuses' do
+ before do
+ prepare_duplicate_data
+ end
+
+ let(:uri) { 'https://example.host/path' }
+ let(:account) { Fabricate(:account) }
+
+ it 'runs the deduplication process' do
+ expect { subject }
+ .to output_results(
+ 'Deduplicating statuses',
+ 'Restoring statuses indexes',
+ 'Finished!'
+ )
+ .and change(duplicate_statuses, :count).from(2).to(1)
+ end
+
+ def duplicate_statuses
+ Status.where(uri: uri)
+ end
+
+ def prepare_duplicate_data
+ ActiveRecord::Base.connection.remove_index :statuses, :uri
+ Fabricate(:status, account: account, uri: uri)
+ duplicate = Fabricate.build(:status, account: account, uri: uri)
+ duplicate.save(validate: false)
+ Fabricate(:status_pin, account: account, status: duplicate)
+ Fabricate(:status, in_reply_to_id: duplicate.id)
+ Fabricate(:status, reblog_of_id: duplicate.id)
+ end
+ end
+
+ context 'with duplicate tags' do
+ before do
+ prepare_duplicate_data
+ end
+
+ let(:name) { 'tagname' }
+
+ it 'runs the deduplication process' do
+ expect { subject }
+ .to output_results(
+ 'Deduplicating tags',
+ 'Restoring tags indexes',
+ 'Finished!'
+ )
+ .and change(duplicate_tags, :count).from(2).to(1)
+ end
+
+ def duplicate_tags
+ Tag.where(name: name)
+ end
+
+ def prepare_duplicate_data
+ ActiveRecord::Base.connection.remove_index :tags, name: 'index_tags_on_name_lower_btree'
+ Fabricate(:tag, name: name)
+ Fabricate.build(:tag, name: name).save(validate: false)
+ end
+ end
+
+ context 'with duplicate webauthn_credentials' do
+ before do
+ prepare_duplicate_data
+ end
+
+ let(:external_id) { '123_123_123' }
+
+ it 'runs the deduplication process' do
+ expect { subject }
+ .to output_results(
+ 'Deduplicating webauthn_credentials',
+ 'Restoring webauthn_credentials indexes',
+ 'Finished!'
+ )
+ .and change(duplicate_webauthn_credentials, :count).from(2).to(1)
+ end
+
+ def duplicate_webauthn_credentials
+ WebauthnCredential.where(external_id: external_id)
+ end
+
+ def prepare_duplicate_data
+ ActiveRecord::Base.connection.remove_index :webauthn_credentials, :external_id
+ Fabricate(:webauthn_credential, external_id: external_id)
+ Fabricate.build(:webauthn_credential, external_id: external_id).save(validate: false)
+ end
+ end
+
+ context 'with duplicate webhooks' do
+ before do
+ prepare_duplicate_data
+ end
+
+ let(:url) { 'https://example.host/path' }
+
+ it 'runs the deduplication process' do
+ expect { subject }
+ .to output_results(
+ 'Deduplicating webhooks',
+ 'Restoring webhooks indexes',
+ 'Finished!'
+ )
+ .and change(duplicate_webhooks, :count).from(2).to(1)
+ end
+
+ def duplicate_webhooks
+ Webhook.where(url: url)
+ end
+
+ def prepare_duplicate_data
+ ActiveRecord::Base.connection.remove_index :webhooks, :url
+ Fabricate(:webhook, url: url)
+ Fabricate.build(:webhook, url: url).save(validate: false)
+ end
+ end
+
+ def agree_to_backup_warning
+ allow(cli.shell)
+ .to receive(:yes?)
+ .with('Continue? (Yes/No)')
+ .and_return(true)
+ .once
+ end
+ end
end
end
diff --git a/spec/lib/vacuum/applications_vacuum_spec.rb b/spec/lib/vacuum/applications_vacuum_spec.rb
index 57a222aafc..df5c860602 100644
--- a/spec/lib/vacuum/applications_vacuum_spec.rb
+++ b/spec/lib/vacuum/applications_vacuum_spec.rb
@@ -13,11 +13,11 @@ RSpec.describe Vacuum::ApplicationsVacuum do
let!(:unused_app) { Fabricate(:application, created_at: 1.month.ago) }
let!(:recent_app) { Fabricate(:application, created_at: 1.hour.ago) }
- let!(:active_access_token) { Fabricate(:access_token, application: app_with_token) }
- let!(:active_access_grant) { Fabricate(:access_grant, application: app_with_grant) }
- let!(:user) { Fabricate(:user, created_by_application: app_with_signup) }
-
before do
+ Fabricate(:access_token, application: app_with_token)
+ Fabricate(:access_grant, application: app_with_grant)
+ Fabricate(:user, created_by_application: app_with_signup)
+
subject.perform
end
diff --git a/spec/lib/vacuum/preview_cards_vacuum_spec.rb b/spec/lib/vacuum/preview_cards_vacuum_spec.rb
index c1b7f7e9c5..9dbdf0bc2f 100644
--- a/spec/lib/vacuum/preview_cards_vacuum_spec.rb
+++ b/spec/lib/vacuum/preview_cards_vacuum_spec.rb
@@ -30,5 +30,9 @@ RSpec.describe Vacuum::PreviewCardsVacuum do
it 'does not delete attached preview cards' do
expect(new_preview_card.reload).to be_persisted
end
+
+ it 'does not delete orphaned preview cards in the retention period' do
+ expect(orphaned_preview_card.reload).to be_persisted
+ end
end
end
diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb
index 522549125f..87aa8bc754 100644
--- a/spec/models/account_spec.rb
+++ b/spec/models/account_spec.rb
@@ -954,6 +954,7 @@ RSpec.describe Account do
it 'returns every usable non-suspended account' do
expect(described_class.searchable).to contain_exactly(silenced_local, silenced_remote, local_account, remote_account)
+ expect(described_class.searchable).to_not include(suspended_local, suspended_remote, unconfirmed, unapproved)
end
it 'does not mess with previously-applied scopes' do
diff --git a/spec/models/account_statuses_cleanup_policy_spec.rb b/spec/models/account_statuses_cleanup_policy_spec.rb
index 74fff30c95..da2a774b2d 100644
--- a/spec/models/account_statuses_cleanup_policy_spec.rb
+++ b/spec/models/account_statuses_cleanup_policy_spec.rb
@@ -235,13 +235,17 @@ RSpec.describe AccountStatusesCleanupPolicy do
describe '#compute_cutoff_id' do
subject { account_statuses_cleanup_policy.compute_cutoff_id }
- let!(:unrelated_status) { Fabricate(:status, created_at: 3.years.ago) }
let(:account_statuses_cleanup_policy) { Fabricate(:account_statuses_cleanup_policy, account: account) }
+ before { Fabricate(:status, created_at: 3.years.ago) }
+
context 'when the account has posted multiple toots' do
- let!(:very_old_status) { Fabricate(:status, created_at: 3.years.ago, account: account) }
- let!(:old_status) { Fabricate(:status, created_at: 3.weeks.ago, account: account) }
- let!(:recent_status) { Fabricate(:status, created_at: 2.days.ago, account: account) }
+ let!(:old_status) { Fabricate(:status, created_at: 3.weeks.ago, account: account) }
+
+ before do
+ Fabricate(:status, created_at: 3.years.ago, account: account)
+ Fabricate(:status, created_at: 2.days.ago, account: account)
+ end
it 'returns the most recent id that is still below policy age' do
expect(subject).to eq old_status.id
@@ -270,16 +274,16 @@ RSpec.describe AccountStatusesCleanupPolicy do
let!(:faved_secondary) { Fabricate(:status, created_at: 1.year.ago, account: account) }
let!(:reblogged_primary) { Fabricate(:status, created_at: 1.year.ago, account: account) }
let!(:reblogged_secondary) { Fabricate(:status, created_at: 1.year.ago, account: account) }
- let!(:recent_status) { Fabricate(:status, created_at: 2.days.ago, account: account) }
-
- let!(:media_attachment) { Fabricate(:media_attachment, account: account, status: status_with_media) }
- let!(:status_pin) { Fabricate(:status_pin, account: account, status: pinned_status) }
- let!(:favourite) { Fabricate(:favourite, account: account, status: self_faved) }
- let!(:bookmark) { Fabricate(:bookmark, account: account, status: self_bookmarked) }
+ let!(:recent_status) { Fabricate(:status, created_at: 2.days.ago, account: account) }
let(:account_statuses_cleanup_policy) { Fabricate(:account_statuses_cleanup_policy, account: account) }
before do
+ Fabricate(:media_attachment, account: account, status: status_with_media)
+ Fabricate(:status_pin, account: account, status: pinned_status)
+ Fabricate(:favourite, account: account, status: self_faved)
+ Fabricate(:bookmark, account: account, status: self_bookmarked)
+
faved_primary.status_stat.update(favourites_count: 4)
faved_secondary.status_stat.update(favourites_count: 5)
reblogged_primary.status_stat.update(reblogs_count: 4)
diff --git a/spec/models/canonical_email_block_spec.rb b/spec/models/canonical_email_block_spec.rb
index 0acff82377..c63483f968 100644
--- a/spec/models/canonical_email_block_spec.rb
+++ b/spec/models/canonical_email_block_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe CanonicalEmailBlock do
end
describe '.block?' do
- let!(:canonical_email_block) { Fabricate(:canonical_email_block, email: 'foo@bar.com') }
+ before { Fabricate(:canonical_email_block, email: 'foo@bar.com') }
it 'returns true for the same email' do
expect(described_class.block?('foo@bar.com')).to be true
diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb
index 8868308d32..4c6d886a5c 100644
--- a/spec/models/status_spec.rb
+++ b/spec/models/status_spec.rb
@@ -352,17 +352,29 @@ RSpec.describe Status do
context 'when given one tag' do
it 'returns the expected statuses' do
- expect(described_class.tagged_with([tag_cats.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_cats.id, status_with_all_tags.id)
- expect(described_class.tagged_with([tag_dogs.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_dogs.id, status_with_all_tags.id)
- expect(described_class.tagged_with([tag_zebras.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_tagged_with_zebras.id, status_with_all_tags.id)
+ expect(described_class.tagged_with([tag_cats.id]))
+ .to include(status_with_tag_cats, status_with_all_tags)
+ .and not_include(status_without_tags)
+ expect(described_class.tagged_with([tag_dogs.id]))
+ .to include(status_with_tag_dogs, status_with_all_tags)
+ .and not_include(status_without_tags)
+ expect(described_class.tagged_with([tag_zebras.id]))
+ .to include(status_tagged_with_zebras, status_with_all_tags)
+ .and not_include(status_without_tags)
end
end
context 'when given multiple tags' do
it 'returns the expected statuses' do
- expect(described_class.tagged_with([tag_cats.id, tag_dogs.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_cats.id, status_with_tag_dogs.id, status_with_all_tags.id)
- expect(described_class.tagged_with([tag_cats.id, tag_zebras.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_cats.id, status_tagged_with_zebras.id, status_with_all_tags.id)
- expect(described_class.tagged_with([tag_dogs.id, tag_zebras.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_dogs.id, status_tagged_with_zebras.id, status_with_all_tags.id)
+ expect(described_class.tagged_with([tag_cats.id, tag_dogs.id]))
+ .to include(status_with_tag_cats, status_with_tag_dogs, status_with_all_tags)
+ .and not_include(status_without_tags)
+ expect(described_class.tagged_with([tag_cats.id, tag_zebras.id]))
+ .to include(status_with_tag_cats, status_tagged_with_zebras, status_with_all_tags)
+ .and not_include(status_without_tags)
+ expect(described_class.tagged_with([tag_dogs.id, tag_zebras.id]))
+ .to include(status_with_tag_dogs, status_tagged_with_zebras, status_with_all_tags)
+ .and not_include(status_without_tags)
end
end
end
@@ -379,17 +391,26 @@ RSpec.describe Status do
context 'when given one tag' do
it 'returns the expected statuses' do
- expect(described_class.tagged_with_all([tag_cats.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_cats.id, status_with_all_tags.id)
- expect(described_class.tagged_with_all([tag_dogs.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_dogs.id, status_with_all_tags.id)
- expect(described_class.tagged_with_all([tag_zebras.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_tagged_with_zebras.id)
+ expect(described_class.tagged_with_all([tag_cats.id]))
+ .to include(status_with_tag_cats, status_with_all_tags)
+ .and not_include(status_without_tags)
+ expect(described_class.tagged_with_all([tag_dogs.id]))
+ .to include(status_with_tag_dogs, status_with_all_tags)
+ .and not_include(status_without_tags)
+ expect(described_class.tagged_with_all([tag_zebras.id]))
+ .to include(status_tagged_with_zebras)
+ .and not_include(status_without_tags)
end
end
context 'when given multiple tags' do
it 'returns the expected statuses' do
- expect(described_class.tagged_with_all([tag_cats.id, tag_dogs.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_all_tags.id)
- expect(described_class.tagged_with_all([tag_cats.id, tag_zebras.id]).reorder(:id).pluck(:id).uniq).to eq []
- expect(described_class.tagged_with_all([tag_dogs.id, tag_zebras.id]).reorder(:id).pluck(:id).uniq).to eq []
+ expect(described_class.tagged_with_all([tag_cats.id, tag_dogs.id]))
+ .to include(status_with_all_tags)
+ expect(described_class.tagged_with_all([tag_cats.id, tag_zebras.id]))
+ .to eq []
+ expect(described_class.tagged_with_all([tag_dogs.id, tag_zebras.id]))
+ .to eq []
end
end
end
@@ -406,17 +427,29 @@ RSpec.describe Status do
context 'when given one tag' do
it 'returns the expected statuses' do
- expect(described_class.tagged_with_none([tag_cats.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_dogs.id, status_tagged_with_zebras.id, status_without_tags.id)
- expect(described_class.tagged_with_none([tag_dogs.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_cats.id, status_tagged_with_zebras.id, status_without_tags.id)
- expect(described_class.tagged_with_none([tag_zebras.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_cats.id, status_with_tag_dogs.id, status_without_tags.id)
+ expect(described_class.tagged_with_none([tag_cats.id]))
+ .to include(status_with_tag_dogs, status_tagged_with_zebras, status_without_tags)
+ .and not_include(status_with_all_tags)
+ expect(described_class.tagged_with_none([tag_dogs.id]))
+ .to include(status_with_tag_cats, status_tagged_with_zebras, status_without_tags)
+ .and not_include(status_with_all_tags)
+ expect(described_class.tagged_with_none([tag_zebras.id]))
+ .to include(status_with_tag_cats, status_with_tag_dogs, status_without_tags)
+ .and not_include(status_with_all_tags)
end
end
context 'when given multiple tags' do
it 'returns the expected statuses' do
- expect(described_class.tagged_with_none([tag_cats.id, tag_dogs.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_tagged_with_zebras.id, status_without_tags.id)
- expect(described_class.tagged_with_none([tag_cats.id, tag_zebras.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_dogs.id, status_without_tags.id)
- expect(described_class.tagged_with_none([tag_dogs.id, tag_zebras.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_cats.id, status_without_tags.id)
+ expect(described_class.tagged_with_none([tag_cats.id, tag_dogs.id]))
+ .to include(status_tagged_with_zebras, status_without_tags)
+ .and not_include(status_with_all_tags)
+ expect(described_class.tagged_with_none([tag_cats.id, tag_zebras.id]))
+ .to include(status_with_tag_dogs, status_without_tags)
+ .and not_include(status_with_all_tags)
+ expect(described_class.tagged_with_none([tag_dogs.id, tag_zebras.id]))
+ .to include(status_with_tag_cats, status_without_tags)
+ .and not_include(status_with_all_tags)
end
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index a2f8d2ca44..ab5bd39b7b 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -175,16 +175,8 @@ RSpec.describe User do
let(:user) { Fabricate(:user, confirmed_at: nil, unconfirmed_email: new_email) }
context 'when the user is already approved' do
- around do |example|
- registrations_mode = Setting.registrations_mode
- Setting.registrations_mode = 'approved'
-
- example.run
-
- Setting.registrations_mode = registrations_mode
- end
-
before do
+ Setting.registrations_mode = 'approved'
user.approve!
end
@@ -199,13 +191,8 @@ RSpec.describe User do
end
context 'when the user does not require explicit approval' do
- around do |example|
- registrations_mode = Setting.registrations_mode
+ before do
Setting.registrations_mode = 'open'
-
- example.run
-
- Setting.registrations_mode = registrations_mode
end
it 'sets email to unconfirmed_email' do
@@ -219,13 +206,8 @@ RSpec.describe User do
end
context 'when the user requires explicit approval but is not approved' do
- around do |example|
- registrations_mode = Setting.registrations_mode
+ before do
Setting.registrations_mode = 'approved'
-
- example.run
-
- Setting.registrations_mode = registrations_mode
end
it 'sets email to unconfirmed_email' do
@@ -243,16 +225,8 @@ RSpec.describe User do
describe '#approve!' do
subject { user.approve! }
- around do |example|
- registrations_mode = Setting.registrations_mode
- Setting.registrations_mode = 'approved'
-
- example.run
-
- Setting.registrations_mode = registrations_mode
- end
-
before do
+ Setting.registrations_mode = 'approved'
allow(TriggerWebhookWorker).to receive(:perform_async)
end
@@ -448,6 +422,7 @@ RSpec.describe User do
it 'deactivates all sessions' do
expect(user.session_activations.count).to eq 0
+ expect { session_activation.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
it 'revokes all access tokens' do
@@ -456,6 +431,7 @@ RSpec.describe User do
it 'removes push subscriptions' do
expect(Web::PushSubscription.where(user: user).or(Web::PushSubscription.where(access_token: access_token)).count).to eq 0
+ expect { web_push_subscription.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
diff --git a/spec/presenters/instance_presenter_spec.rb b/spec/presenters/instance_presenter_spec.rb
index 8bf4f5a34d..0d6a416b3f 100644
--- a/spec/presenters/instance_presenter_spec.rb
+++ b/spec/presenters/instance_presenter_spec.rb
@@ -6,12 +6,6 @@ describe InstancePresenter do
let(:instance_presenter) { described_class.new }
describe '#description' do
- around do |example|
- site_description = Setting.site_short_description
- example.run
- Setting.site_short_description = site_description
- end
-
it 'delegates site_description to Setting' do
Setting.site_short_description = 'Site desc'
expect(instance_presenter.description).to eq 'Site desc'
@@ -19,12 +13,6 @@ describe InstancePresenter do
end
describe '#extended_description' do
- around do |example|
- site_extended_description = Setting.site_extended_description
- example.run
- Setting.site_extended_description = site_extended_description
- end
-
it 'delegates site_extended_description to Setting' do
Setting.site_extended_description = 'Extended desc'
expect(instance_presenter.extended_description).to eq 'Extended desc'
@@ -32,12 +20,6 @@ describe InstancePresenter do
end
describe '#email' do
- around do |example|
- site_contact_email = Setting.site_contact_email
- example.run
- Setting.site_contact_email = site_contact_email
- end
-
it 'delegates contact_email to Setting' do
Setting.site_contact_email = 'admin@example.com'
expect(instance_presenter.contact.email).to eq 'admin@example.com'
@@ -45,12 +27,6 @@ describe InstancePresenter do
end
describe '#account' do
- around do |example|
- site_contact_username = Setting.site_contact_username
- example.run
- Setting.site_contact_username = site_contact_username
- end
-
it 'returns the account for the site contact username' do
Setting.site_contact_username = 'aaa'
account = Fabricate(:account, username: 'aaa')
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 0e68fbe121..c9352a4d5d 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -153,6 +153,7 @@ RSpec::Sidekiq.configure do |config|
end
RSpec::Matchers.define_negated_matcher :not_change, :change
+RSpec::Matchers.define_negated_matcher :not_include, :include
def request_fixture(name)
Rails.root.join('spec', 'fixtures', 'requests', name).read
diff --git a/spec/requests/api/v1/instances/activity_spec.rb b/spec/requests/api/v1/instances/activity_spec.rb
index d1f92ef36e..4f2bc91ad6 100644
--- a/spec/requests/api/v1/instances/activity_spec.rb
+++ b/spec/requests/api/v1/instances/activity_spec.rb
@@ -4,12 +4,6 @@ require 'rails_helper'
RSpec.describe 'Activity' do
describe 'GET /api/v1/instance/activity' do
- around do |example|
- original = Setting.activity_api_enabled
- example.run
- Setting.activity_api_enabled = original
- end
-
context 'with activity api enabled' do
before { Setting.activity_api_enabled = true }
diff --git a/spec/requests/api/v1/instances/domain_blocks_spec.rb b/spec/requests/api/v1/instances/domain_blocks_spec.rb
index 99b5e2b6aa..397ecff084 100644
--- a/spec/requests/api/v1/instances/domain_blocks_spec.rb
+++ b/spec/requests/api/v1/instances/domain_blocks_spec.rb
@@ -4,12 +4,6 @@ require 'rails_helper'
RSpec.describe 'Domain Blocks' do
describe 'GET /api/v1/instance/domain_blocks' do
- around do |example|
- original = Setting.show_domain_blocks
- example.run
- Setting.show_domain_blocks = original
- end
-
before do
Fabricate(:domain_block)
end
diff --git a/spec/requests/api/v1/instances/peers_spec.rb b/spec/requests/api/v1/instances/peers_spec.rb
index d3400ae8fd..1a7975f8b7 100644
--- a/spec/requests/api/v1/instances/peers_spec.rb
+++ b/spec/requests/api/v1/instances/peers_spec.rb
@@ -4,12 +4,6 @@ require 'rails_helper'
RSpec.describe 'Peers' do
describe 'GET /api/v1/instance/peers' do
- around do |example|
- original = Setting.peers_api_enabled
- example.run
- Setting.peers_api_enabled = original
- end
-
context 'with peers api enabled' do
before { Setting.peers_api_enabled = true }
diff --git a/spec/requests/api/v1/suggestions_spec.rb b/spec/requests/api/v1/suggestions_spec.rb
index 42b7f86629..dc89613fc5 100644
--- a/spec/requests/api/v1/suggestions_spec.rb
+++ b/spec/requests/api/v1/suggestions_spec.rb
@@ -13,13 +13,12 @@ RSpec.describe 'Suggestions' do
get '/api/v1/suggestions', headers: headers, params: params
end
- let(:bob) { Fabricate(:account) }
- let(:jeff) { Fabricate(:account) }
+ let(:bob) { Fabricate(:account) }
+ let(:jeff) { Fabricate(:account) }
let(:params) { {} }
before do
- PotentialFriendshipTracker.record(user.account_id, bob.id, :reblog)
- PotentialFriendshipTracker.record(user.account_id, jeff.id, :favourite)
+ Setting.bootstrap_timeline_accounts = [bob, jeff].map(&:acct).join(',')
end
it_behaves_like 'forbidden for wrong scope', 'write'
@@ -65,17 +64,15 @@ RSpec.describe 'Suggestions' do
delete "/api/v1/suggestions/#{jeff.id}", headers: headers
end
- let(:suggestions_source) { instance_double(AccountSuggestions::PastInteractionsSource, remove: nil) }
- let(:bob) { Fabricate(:account) }
- let(:jeff) { Fabricate(:account) }
+ let(:bob) { Fabricate(:account) }
+ let(:jeff) { Fabricate(:account) }
+ let(:scopes) { 'write' }
before do
- PotentialFriendshipTracker.record(user.account_id, bob.id, :reblog)
- PotentialFriendshipTracker.record(user.account_id, jeff.id, :favourite)
- allow(AccountSuggestions::PastInteractionsSource).to receive(:new).and_return(suggestions_source)
+ Setting.bootstrap_timeline_accounts = [bob, jeff].map(&:acct).join(',')
end
- it_behaves_like 'forbidden for wrong scope', 'write'
+ it_behaves_like 'forbidden for wrong scope', 'read'
it 'returns http success' do
subject
@@ -86,8 +83,7 @@ RSpec.describe 'Suggestions' do
it 'removes the specified suggestion' do
subject
- expect(suggestions_source).to have_received(:remove).with(user.account, jeff.id.to_s).once
- expect(suggestions_source).to_not have_received(:remove).with(user.account, bob.id.to_s)
+ expect(FollowRecommendationMute.exists?(account: user.account, target_account: jeff)).to be true
end
context 'without an authorization header' do
diff --git a/spec/requests/cache_spec.rb b/spec/requests/cache_spec.rb
index ce73d3af75..dbba228740 100644
--- a/spec/requests/cache_spec.rb
+++ b/spec/requests/cache_spec.rb
@@ -242,17 +242,11 @@ describe 'Caching behavior' do
end
describe '/api/v1/instance/domain_blocks' do
- around do |example|
- old_setting = Setting.show_domain_blocks
+ before do
Setting.show_domain_blocks = show_domain_blocks
-
- example.run
-
- Setting.show_domain_blocks = old_setting
+ get '/api/v1/instance/domain_blocks'
end
- before { get '/api/v1/instance/domain_blocks' }
-
context 'when set to be publicly-available' do
let(:show_domain_blocks) { 'all' }
@@ -365,16 +359,8 @@ describe 'Caching behavior' do
end
describe '/api/v1/instance/domain_blocks' do
- around do |example|
- old_setting = Setting.show_domain_blocks
- Setting.show_domain_blocks = show_domain_blocks
-
- example.run
-
- Setting.show_domain_blocks = old_setting
- end
-
before do
+ Setting.show_domain_blocks = show_domain_blocks
get '/api/v1/instance/domain_blocks', headers: { 'Authorization' => "Bearer #{token.token}" }
end
diff --git a/spec/requests/content_security_policy_spec.rb b/spec/requests/content_security_policy_spec.rb
index 399776d673..0fd9dc05ca 100644
--- a/spec/requests/content_security_policy_spec.rb
+++ b/spec/requests/content_security_policy_spec.rb
@@ -20,7 +20,7 @@ describe 'Content-Security-Policy' do
"form-action 'self'",
"child-src 'self' blob: https://cb6e6126.ngrok.io",
"worker-src 'self' blob: https://cb6e6126.ngrok.io",
- "connect-src 'self' blob: data: ws://localhost:4000 https://cb6e6126.ngrok.io https://media.tenor.com https://api.tenor.com",
+ "connect-src 'self' data: blob: https://cb6e6126.ngrok.io ws://localhost:4000 https://media.tenor.com https://api.tenor.com",
"script-src 'self' https://cb6e6126.ngrok.io 'wasm-unsafe-eval'"
)
end
diff --git a/spec/requests/signature_verification_spec.rb b/spec/requests/signature_verification_spec.rb
new file mode 100644
index 0000000000..b753750b84
--- /dev/null
+++ b/spec/requests/signature_verification_spec.rb
@@ -0,0 +1,332 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'signature verification concern' do
+ before do
+ stub_tests_controller
+
+ # Signature checking is time-dependent, so travel to a fixed date
+ travel_to '2023-12-20T10:00:00Z'
+ end
+
+ after { Rails.application.reload_routes! }
+
+ # Include the private key so the tests can be easily adjusted and reviewed
+ let(:actor_keypair) do
+ OpenSSL::PKey.read(<<~PEM_TEXT)
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIEowIBAAKCAQEAqIAYvNFGbZ5g4iiK6feSdXD4bDStFM58A7tHycYXaYtzZQpI
+ eHXAmaXuZzXIwtrP4N0gIk8JNwZvXj2UPS+S07t0V9wNK94he01LV5EMz/GN4eNn
+ FmDL64HIEuKLvV8TvgjbUPRD6Y5X0UpKi2ZIFLSb96Q5w0Z/k7ntpVKV52y8kz5F
+ jr/O/0JuHryZe0yItzJh8kzFfeMf0EXzfSnaKvT7P9jhgC6uTre+jXyvVZjiHDrn
+ qvvucdI3I7DRfXo1OqARBrLjy+TdseUAjNYJ+OuPRI1URIWQI01DCHqcohVu9+Ar
+ +BiCjFp3ua+XMuJvrvbD61d1Fvig/9nbBRR+8QIDAQABAoIBAAgySHnFWI6gItR3
+ fkfiqIm80cHCN3Xk1C6iiVu+3oBOZbHpW9R7vl9e/WOA/9O+LPjiSsQOegtWnVvd
+ RRjrl7Hj20VDlZKv5Mssm6zOGAxksrcVbqwdj+fUJaNJCL0AyyseH0x/IE9T8rDC
+ I1GH+3tB3JkhkIN/qjipdX5ab8MswEPu8IC4ViTpdBgWYY/xBcAHPw4xuL0tcwzh
+ FBlf4DqoEVQo8GdK5GAJ2Ny0S4xbXHUURzx/R4y4CCts7niAiLGqd9jmLU1kUTMk
+ QcXfQYK6l+unLc7wDYAz7sFEHh04M48VjWwiIZJnlCqmQbLda7uhhu8zkF1DqZTu
+ ulWDGQECgYEA0TIAc8BQBVab979DHEEmMdgqBwxLY3OIAk0b+r50h7VBGWCDPRsC
+ STD73fQY3lNet/7/jgSGwwAlAJ5PpMXxXiZAE3bUwPmHzgF7pvIOOLhA8O07tHSO
+ L2mvQe6NPzjZ+6iAO2U9PkClxcvGvPx2OBvisfHqZLmxC9PIVxzruQECgYEAzjM6
+ BTUXa6T/qHvLFbN699BXsUOGmHBGaLRapFDBfVvgZrwqYQcZpBBhesLdGTGSqwE7
+ gWsITPIJ+Ldo+38oGYyVys+w/V67q6ud7hgSDTW3hSvm+GboCjk6gzxlt9hQ0t9X
+ 8vfDOYhEXvVUJNv3mYO60ENqQhILO4bQ0zi+VfECgYBb/nUccfG+pzunU0Cb6Dp3
+ qOuydcGhVmj1OhuXxLFSDG84Tazo7juvHA9mp7VX76mzmDuhpHPuxN2AzB2SBEoE
+ cSW0aYld413JRfWukLuYTc6hJHIhBTCRwRQFFnae2s1hUdQySm8INT2xIc+fxBXo
+ zrp+Ljg5Wz90SAnN5TX0AQKBgDaatDOq0o/r+tPYLHiLtfWoE4Dau+rkWJDjqdk3
+ lXWn/e3WyHY3Vh/vQpEqxzgju45TXjmwaVtPATr+/usSykCxzP0PMPR3wMT+Rm1F
+ rIoY/odij+CaB7qlWwxj0x/zRbwB7x1lZSp4HnrzBpxYL+JUUwVRxPLIKndSBTza
+ GvVRAoGBAIVBcNcRQYF4fvZjDKAb4fdBsEuHmycqtRCsnkGOz6ebbEQznSaZ0tZE
+ +JuouZaGjyp8uPjNGD5D7mIGbyoZ3KyG4mTXNxDAGBso1hrNDKGBOrGaPhZx8LgO
+ 4VXJ+ybXrATf4jr8ccZYsZdFpOphPzz+j55Mqg5vac5P1XjmsGTb
+ -----END RSA PRIVATE KEY-----
+ PEM_TEXT
+ end
+
+ context 'without a Signature header' do
+ it 'does not treat the request as signed' do
+ get '/activitypub/success'
+
+ expect(response).to have_http_status(200)
+ expect(body_as_json).to match(
+ signed_request: false,
+ signature_actor_id: nil,
+ error: 'Request not signed'
+ )
+ end
+
+ context 'when a signature is required' do
+ it 'returns http unauthorized with appropriate error' do
+ get '/activitypub/signature_required'
+
+ expect(response).to have_http_status(401)
+ expect(body_as_json).to match(
+ error: 'Request not signed'
+ )
+ end
+ end
+ end
+
+ context 'with an HTTP Signature from a known account' do
+ let!(:actor) { Fabricate(:account, domain: 'remote.domain', uri: 'https://remote.domain/users/bob', private_key: nil, public_key: actor_keypair.public_key.to_pem) }
+
+ context 'with a valid signature on a GET request' do
+ let(:signature_header) do
+ 'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="date host (request-target)",signature="Z8ilar3J7bOwqZkMp7sL8sRs4B1FT+UorbmvWoE+A5UeoOJ3KBcUmbsh+k3wQwbP5gMNUrra9rEWabpasZGphLsbDxfbsWL3Cf0PllAc7c1c7AFEwnewtExI83/qqgEkfWc2z7UDutXc2NfgAx89Ox8DXU/fA2GG0jILjB6UpFyNugkY9rg6oI31UnvfVi3R7sr3/x8Ea3I9thPvqI2byF6cojknSpDAwYzeKdngX3TAQEGzFHz3SDWwyp3jeMWfwvVVbM38FxhvAnSumw7YwWW4L7M7h4M68isLimoT3yfCn2ucBVL5Dz8koBpYf/40w7QidClAwCafZQFC29yDOg=="' # rubocop:disable Layout/LineLength
+ end
+
+ it 'successfuly verifies signature', :aggregate_failures do
+ expect(signature_header).to eq build_signature_string(actor_keypair, 'https://remote.domain/users/bob#main-key', 'get /activitypub/success', { 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', 'Host' => 'www.example.com' })
+
+ get '/activitypub/success', headers: {
+ 'Host' => 'www.example.com',
+ 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT',
+ 'Signature' => signature_header,
+ }
+
+ expect(response).to have_http_status(200)
+ expect(body_as_json).to match(
+ signed_request: true,
+ signature_actor_id: actor.id.to_s
+ )
+ end
+ end
+
+ context 'with a mismatching path' do
+ it 'fails to verify signature', :aggregate_failures do
+ get '/activitypub/alternative-path', headers: {
+ 'Host' => 'www.example.com',
+ 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT',
+ 'Signature' => 'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="date host (request-target)",signature="Z8ilar3J7bOwqZkMp7sL8sRs4B1FT+UorbmvWoE+A5UeoOJ3KBcUmbsh+k3wQwbP5gMNUrra9rEWabpasZGphLsbDxfbsWL3Cf0PllAc7c1c7AFEwnewtExI83/qqgEkfWc2z7UDutXc2NfgAx89Ox8DXU/fA2GG0jILjB6UpFyNugkY9rg6oI31UnvfVi3R7sr3/x8Ea3I9thPvqI2byF6cojknSpDAwYzeKdngX3TAQEGzFHz3SDWwyp3jeMWfwvVVbM38FxhvAnSumw7YwWW4L7M7h4M68isLimoT3yfCn2ucBVL5Dz8koBpYf/40w7QidClAwCafZQFC29yDOg=="', # rubocop:disable Layout/LineLength
+ }
+
+ expect(body_as_json).to match(
+ signed_request: true,
+ signature_actor_id: nil,
+ error: anything
+ )
+ end
+ end
+
+ context 'with a mismatching method' do
+ it 'fails to verify signature', :aggregate_failures do
+ post '/activitypub/success', headers: {
+ 'Host' => 'www.example.com',
+ 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT',
+ 'Signature' => 'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="date host (request-target)",signature="Z8ilar3J7bOwqZkMp7sL8sRs4B1FT+UorbmvWoE+A5UeoOJ3KBcUmbsh+k3wQwbP5gMNUrra9rEWabpasZGphLsbDxfbsWL3Cf0PllAc7c1c7AFEwnewtExI83/qqgEkfWc2z7UDutXc2NfgAx89Ox8DXU/fA2GG0jILjB6UpFyNugkY9rg6oI31UnvfVi3R7sr3/x8Ea3I9thPvqI2byF6cojknSpDAwYzeKdngX3TAQEGzFHz3SDWwyp3jeMWfwvVVbM38FxhvAnSumw7YwWW4L7M7h4M68isLimoT3yfCn2ucBVL5Dz8koBpYf/40w7QidClAwCafZQFC29yDOg=="', # rubocop:disable Layout/LineLength
+ }
+
+ expect(body_as_json).to match(
+ signed_request: true,
+ signature_actor_id: nil,
+ error: anything
+ )
+ end
+ end
+
+ context 'with an unparsable date' do
+ let(:signature_header) do
+ 'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="date host (request-target)",signature="d4B7nfx8RJcfdJDu1J//5WzPzK/hgtPkdzZx49lu5QhnE7qdV3lgyVimmhCFrO16bwvzIp9iRMyRLkNFxLiEeVaa1gqeKbldGSnU0B0OMjx7rFBa65vLuzWQOATDitVGiBEYqoK4v0DMuFCz2DtFaA/DIUZ3sty8bZ/Ea3U1nByLOO6MacARA3zhMSI0GNxGqsSmZmG0hPLavB3jIXoE3IDoQabMnC39jrlcO/a8h1iaxBm2WD8TejrImJullgqlJIFpKhIHI3ipQkvTGPlm9dx0y+beM06qBvWaWQcmT09eRIUefVsOAzIhUtS/7FVb/URhZvircIJDa7vtiFcmZQ=="' # rubocop:disable Layout/LineLength
+ end
+
+ it 'fails to verify signature', :aggregate_failures do
+ expect(signature_header).to eq build_signature_string(actor_keypair, 'https://remote.domain/users/bob#main-key', 'get /activitypub/success', { 'Date' => 'wrong date', 'Host' => 'www.example.com' })
+
+ get '/activitypub/success', headers: {
+ 'Host' => 'www.example.com',
+ 'Date' => 'wrong date',
+ 'Signature' => signature_header,
+ }
+
+ expect(body_as_json).to match(
+ signed_request: true,
+ signature_actor_id: nil,
+ error: 'Invalid Date header: not RFC 2616 compliant date: "wrong date"'
+ )
+ end
+ end
+
+ context 'with a request older than a day' do
+ let(:signature_header) do
+ 'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="date host (request-target)",signature="G1NuJv4zgoZ3B/ZIjzDWZHK4RC+5pYee74q8/LJEMCWXhcnAomcb9YHaqk1QYfQvcBUIXw3UZ3Q9xO8F9y0i8G5mzJHfQ+OgHqCoJk8EmGwsUXJMh5s1S5YFCRt8TT12TmJZz0VMqLq85ubueSYBM7QtUE/FzFIVLvz4RysgXxaXQKzdnM6+gbUEEKdCURpXdQt2NXQhp4MAmZH3+0lQoR6VxdsK0hx0Ji2PNp1nuqFTlYqNWZazVdLBN+9rETLRmvGXknvg9jOxTTppBVWnkAIl26HtLS3wwFVvz4pJzi9OQDOvLziehVyLNbU61hky+oJ215e2HuKSe2hxHNl1MA=="' # rubocop:disable Layout/LineLength
+ end
+
+ it 'fails to verify signature', :aggregate_failures do
+ expect(signature_header).to eq build_signature_string(actor_keypair, 'https://remote.domain/users/bob#main-key', 'get /activitypub/success', { 'Date' => 'Wed, 18 Dec 2023 10:00:00 GMT', 'Host' => 'www.example.com' })
+
+ get '/activitypub/success', headers: {
+ 'Host' => 'www.example.com',
+ 'Date' => 'Wed, 18 Dec 2023 10:00:00 GMT',
+ 'Signature' => signature_header,
+ }
+
+ expect(body_as_json).to match(
+ signed_request: true,
+ signature_actor_id: nil,
+ error: 'Signed request date outside acceptable time window'
+ )
+ end
+ end
+
+ context 'with a valid signature on a POST request' do
+ let(:digest_header) { 'SHA-256=ZOyIygCyaOW6GjVnihtTFtIS9PNmskdyMlNKiuyjfzw=' }
+ let(:signature_header) do
+ 'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="host date digest (request-target)",signature="gmhMjgMROGElJU3fpehV2acD5kMHeELi8EFP2UPHOdQ54H0r55AxIpji+J3lPe+N2qSb/4H1KXIh6f0lRu8TGSsu12OQmg5hiO8VA9flcA/mh9Lpk+qwlQZIPRqKP9xUEfqD+Z7ti5wPzDKrWAUK/7FIqWgcT/mlqB1R1MGkpMFc/q4CIs2OSNiWgA4K+Kp21oQxzC2kUuYob04gAZ7cyE/FTia5t08uv6lVYFdRsn4XNPn1MsHgFBwBMRG79ng3SyhoG4PrqBEi5q2IdLq3zfre/M6He3wlCpyO2VJNdGVoTIzeZ0Zz8jUscPV3XtWUchpGclLGSaKaq/JyNZeiYQ=="' # rubocop:disable Layout/LineLength
+ end
+
+ it 'successfuly verifies signature', :aggregate_failures do
+ expect(digest_header).to eq digest_value('Hello world')
+ expect(signature_header).to eq build_signature_string(actor_keypair, 'https://remote.domain/users/bob#main-key', 'post /activitypub/success', { 'Host' => 'www.example.com', 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', 'Digest' => digest_header })
+
+ post '/activitypub/success', params: 'Hello world', headers: {
+ 'Host' => 'www.example.com',
+ 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT',
+ 'Digest' => digest_header,
+ 'Signature' => signature_header,
+ }
+
+ expect(response).to have_http_status(200)
+ expect(body_as_json).to match(
+ signed_request: true,
+ signature_actor_id: actor.id.to_s
+ )
+ end
+ end
+
+ context 'when the Digest of a POST request is not signed' do
+ let(:digest_header) { 'SHA-256=ZOyIygCyaOW6GjVnihtTFtIS9PNmskdyMlNKiuyjfzw=' }
+ let(:signature_header) do
+ 'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="host date (request-target)",signature="CPD704CG8aCm8X8qIP8kkkiGp1qwFLk/wMVQHOGP0Txxan8c2DZtg/KK7eN8RG8tHx8br/yS2hJs51x4kXImYukGzNJd7ihE3T8lp+9RI1tCcdobTzr/VcVJHDFySdQkg266GCMijRQRZfNvqlJLiisr817PI+gNVBI5qV+vnVd1XhWCEZ+YSmMe8UqYARXAYNqMykTheojqGpTeTFGPUpTQA2Fmt2BipwIjcFDm2Hpihl2kB0MUS0x3zPmHDuadvzoBbN6m3usPDLgYrpALlh+wDs1dYMntcwdwawRKY1oE1XNtgOSum12wntDq3uYL4gya2iPdcw3c929b4koUzw=="' # rubocop:disable Layout/LineLength
+ end
+
+ it 'fails to verify signature', :aggregate_failures do
+ expect(digest_header).to eq digest_value('Hello world')
+ expect(signature_header).to eq build_signature_string(actor_keypair, 'https://remote.domain/users/bob#main-key', 'post /activitypub/success', { 'Host' => 'www.example.com', 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT' })
+
+ post '/activitypub/success', params: 'Hello world', headers: {
+ 'Host' => 'www.example.com',
+ 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT',
+ 'Digest' => digest_header,
+ 'Signature' => signature_header,
+ }
+
+ expect(body_as_json).to match(
+ signed_request: true,
+ signature_actor_id: nil,
+ error: 'Mastodon requires the Digest header to be signed when doing a POST request'
+ )
+ end
+ end
+
+ context 'with a tampered body on a POST request' do
+ let(:digest_header) { 'SHA-256=ZOyIygCyaOW6GjVnihtTFtIS9PNmskdyMlNKiuyjfzw=' }
+ let(:signature_header) do
+ 'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="host date digest (request-target)",signature="gmhMjgMROGElJU3fpehV2acD5kMHeELi8EFP2UPHOdQ54H0r55AxIpji+J3lPe+N2qSb/4H1KXIh6f0lRu8TGSsu12OQmg5hiO8VA9flcA/mh9Lpk+qwlQZIPRqKP9xUEfqD+Z7ti5wPzDKrWAUK/7FIqWgcT/mlqB1R1MGkpMFc/q4CIs2OSNiWgA4K+Kp21oQxzC2kUuYob04gAZ7cyE/FTia5t08uv6lVYFdRsn4XNPn1MsHgFBwBMRG79ng3SyhoG4PrqBEi5q2IdLq3zfre/M6He3wlCpyO2VJNdGVoTIzeZ0Zz8jUscPV3XtWUchpGclLGSaKaq/JyNZeiYQ=="' # rubocop:disable Layout/LineLength
+ end
+
+ it 'fails to verify signature', :aggregate_failures do
+ expect(digest_header).to_not eq digest_value('Hello world!')
+ expect(signature_header).to eq build_signature_string(actor_keypair, 'https://remote.domain/users/bob#main-key', 'post /activitypub/success', { 'Host' => 'www.example.com', 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', 'Digest' => digest_header })
+
+ post '/activitypub/success', params: 'Hello world!', headers: {
+ 'Host' => 'www.example.com',
+ 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT',
+ 'Digest' => 'SHA-256=ZOyIygCyaOW6GjVnihtTFtIS9PNmskdyMlNKiuyjfzw=',
+ 'Signature' => signature_header,
+ }
+
+ expect(body_as_json).to match(
+ signed_request: true,
+ signature_actor_id: nil,
+ error: 'Invalid Digest value. Computed SHA-256 digest: wFNeS+K3n/2TKRMFQ2v4iTFOSj+uwF7P/Lt98xrZ5Ro=; given: ZOyIygCyaOW6GjVnihtTFtIS9PNmskdyMlNKiuyjfzw='
+ )
+ end
+ end
+
+ context 'with a tampered path in a POST request' do
+ it 'fails to verify signature', :aggregate_failures do
+ post '/activitypub/alternative-path', params: 'Hello world', headers: {
+ 'Host' => 'www.example.com',
+ 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT',
+ 'Digest' => 'SHA-256=ZOyIygCyaOW6GjVnihtTFtIS9PNmskdyMlNKiuyjfzw=',
+ 'Signature' => 'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="host date digest (request-target)",signature="gmhMjgMROGElJU3fpehV2acD5kMHeELi8EFP2UPHOdQ54H0r55AxIpji+J3lPe+N2qSb/4H1KXIh6f0lRu8TGSsu12OQmg5hiO8VA9flcA/mh9Lpk+qwlQZIPRqKP9xUEfqD+Z7ti5wPzDKrWAUK/7FIqWgcT/mlqB1R1MGkpMFc/q4CIs2OSNiWgA4K+Kp21oQxzC2kUuYob04gAZ7cyE/FTia5t08uv6lVYFdRsn4XNPn1MsHgFBwBMRG79ng3SyhoG4PrqBEi5q2IdLq3zfre/M6He3wlCpyO2VJNdGVoTIzeZ0Zz8jUscPV3XtWUchpGclLGSaKaq/JyNZeiYQ=="', # rubocop:disable Layout/LineLength
+ }
+
+ expect(response).to have_http_status(200)
+ expect(body_as_json).to match(
+ signed_request: true,
+ signature_actor_id: nil,
+ error: anything
+ )
+ end
+ end
+ end
+
+ context 'with an inaccessible key' do
+ before do
+ stub_request(:get, 'https://remote.domain/users/alice#main-key').to_return(status: 404)
+ end
+
+ it 'fails to verify signature', :aggregate_failures do
+ get '/activitypub/success', headers: {
+ 'Host' => 'www.example.com',
+ 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT',
+ 'Signature' => 'keyId="https://remote.domain/users/alice#main-key",algorithm="rsa-sha256",headers="date host (request-target)",signature="Z8ilar3J7bOwqZkMp7sL8sRs4B1FT+UorbmvWoE+A5UeoOJ3KBcUmbsh+k3wQwbP5gMNUrra9rEWabpasZGphLsbDxfbsWL3Cf0PllAc7c1c7AFEwnewtExI83/qqgEkfWc2z7UDutXc2NfgAx89Ox8DXU/fA2GG0jILjB6UpFyNugkY9rg6oI31UnvfVi3R7sr3/x8Ea3I9thPvqI2byF6cojknSpDAwYzeKdngX3TAQEGzFHz3SDWwyp3jeMWfwvVVbM38FxhvAnSumw7YwWW4L7M7h4M68isLimoT3yfCn2ucBVL5Dz8koBpYf/40w7QidClAwCafZQFC29yDOg=="', # rubocop:disable Layout/LineLength
+ }
+
+ expect(body_as_json).to match(
+ signed_request: true,
+ signature_actor_id: nil,
+ error: 'Unable to fetch key JSON at https://remote.domain/users/alice#main-key'
+ )
+ end
+ end
+
+ private
+
+ def stub_tests_controller
+ stub_const('ActivityPub::TestsController', activitypub_tests_controller)
+
+ Rails.application.routes.draw do
+ # NOTE: RouteSet#draw removes all routes, so we need to re-insert one
+ resource :instance_actor, path: 'actor', only: [:show]
+
+ match :via => [:get, :post], '/activitypub/success' => 'activitypub/tests#success'
+ match :via => [:get, :post], '/activitypub/alternative-path' => 'activitypub/tests#alternative_success'
+ match :via => [:get, :post], '/activitypub/signature_required' => 'activitypub/tests#signature_required'
+ end
+ end
+
+ def activitypub_tests_controller
+ Class.new(ApplicationController) do
+ include SignatureVerification
+
+ before_action :require_actor_signature!, only: [:signature_required]
+
+ def success
+ render json: {
+ signed_request: signed_request?,
+ signature_actor_id: signed_request_actor&.id&.to_s,
+ }.merge(signature_verification_failure_reason || {})
+ end
+
+ alias_method :alternative_success, :success
+ alias_method :signature_required, :success
+ end
+ end
+
+ def digest_value(body)
+ "SHA-256=#{Digest::SHA256.base64digest(body)}"
+ end
+
+ def build_signature_string(keypair, key_id, request_target, headers)
+ algorithm = 'rsa-sha256'
+ signed_headers = headers.merge({ '(request-target)' => request_target })
+ signed_string = signed_headers.map { |key, value| "#{key.downcase}: #{value}" }.join("\n")
+ signature = Base64.strict_encode64(keypair.sign(OpenSSL::Digest.new('SHA256'), signed_string))
+
+ "keyId=\"#{key_id}\",algorithm=\"#{algorithm}\",headers=\"#{signed_headers.keys.join(' ').downcase}\",signature=\"#{signature}\""
+ end
+end
diff --git a/spec/services/account_statuses_cleanup_service_spec.rb b/spec/services/account_statuses_cleanup_service_spec.rb
index f7a88a9172..0ac113f105 100644
--- a/spec/services/account_statuses_cleanup_service_spec.rb
+++ b/spec/services/account_statuses_cleanup_service_spec.rb
@@ -39,6 +39,13 @@ describe AccountStatusesCleanupService, type: :service do
it 'actually deletes the statuses' do
subject.call(account_policy, 10)
expect(Status.find_by(id: [very_old_status.id, old_status.id, another_old_status.id])).to be_nil
+ expect { recent_status.reload }.to_not raise_error
+ end
+
+ it 'preserves recent and unrelated statuses' do
+ subject.call(account_policy, 10)
+ expect { unrelated_status.reload }.to_not raise_error
+ expect { recent_status.reload }.to_not raise_error
end
end
diff --git a/spec/services/activitypub/fetch_featured_collection_service_spec.rb b/spec/services/activitypub/fetch_featured_collection_service_spec.rb
index 466da891a8..a98108cea3 100644
--- a/spec/services/activitypub/fetch_featured_collection_service_spec.rb
+++ b/spec/services/activitypub/fetch_featured_collection_service_spec.rb
@@ -87,6 +87,7 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do
'https://example.com/account/pinned/unknown-inlined',
'https://example.com/account/pinned/unknown-reachable'
)
+ expect(actor.pinned_statuses).to_not include(known_status)
end
end
diff --git a/spec/services/activitypub/fetch_remote_status_service_spec.rb b/spec/services/activitypub/fetch_remote_status_service_spec.rb
index 826b67d884..3c64feaeec 100644
--- a/spec/services/activitypub/fetch_remote_status_service_spec.rb
+++ b/spec/services/activitypub/fetch_remote_status_service_spec.rb
@@ -8,7 +8,6 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do
subject { described_class.new }
let!(:sender) { Fabricate(:account, domain: 'foo.bar', uri: 'https://foo.bar') }
- let!(:recipient) { Fabricate(:account) }
let(:existing_status) { nil }
diff --git a/spec/services/activitypub/process_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb
index 09eb5ddee3..58dd2badbb 100644
--- a/spec/services/activitypub/process_account_service_spec.rb
+++ b/spec/services/activitypub/process_account_service_spec.rb
@@ -33,7 +33,7 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do
end
context 'when account is not suspended' do
- subject { described_class.new.call('alice', 'example.com', payload) }
+ subject { described_class.new.call(account.username, account.domain, payload) }
let!(:account) { Fabricate(:account, username: 'alice', domain: 'example.com') }
diff --git a/spec/services/activitypub/process_collection_service_spec.rb b/spec/services/activitypub/process_collection_service_spec.rb
index df526daf34..f4a2b8fec6 100644
--- a/spec/services/activitypub/process_collection_service_spec.rb
+++ b/spec/services/activitypub/process_collection_service_spec.rb
@@ -242,7 +242,8 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do
it 'does not process forged payload' do
allow(ActivityPub::Activity).to receive(:factory)
- subject.call(json, forwarder)
+ expect { subject.call(json, forwarder) }
+ .to_not change(actor.reload.statuses, :count)
expect(ActivityPub::Activity).to_not have_received(:factory).with(
hash_including(
diff --git a/spec/services/app_sign_up_service_spec.rb b/spec/services/app_sign_up_service_spec.rb
index d5946cf9b0..0adb473f17 100644
--- a/spec/services/app_sign_up_service_spec.rb
+++ b/spec/services/app_sign_up_service_spec.rb
@@ -28,13 +28,8 @@ RSpec.describe AppSignUpService, type: :service do
end
context 'when registrations are closed' do
- around do |example|
- tmp = Setting.registrations_mode
+ before do
Setting.registrations_mode = 'none'
-
- example.run
-
- Setting.registrations_mode = tmp
end
it 'raises an error', :aggregate_failures do
diff --git a/spec/services/batched_remove_status_service_spec.rb b/spec/services/batched_remove_status_service_spec.rb
index 8201c9d51f..991a852f0e 100644
--- a/spec/services/batched_remove_status_service_spec.rb
+++ b/spec/services/batched_remove_status_service_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe BatchedRemoveStatusService, type: :service do
let!(:jeff) { Fabricate(:account) }
let!(:hank) { Fabricate(:account, username: 'hank', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
- let(:status_alice_hello) { PostStatusService.new.call(alice, text: 'Hello @bob@example.com') }
+ let(:status_alice_hello) { PostStatusService.new.call(alice, text: "Hello @#{bob.pretty_acct}") }
let(:status_alice_other) { PostStatusService.new.call(alice, text: 'Another status') }
before do
diff --git a/spec/services/block_domain_service_spec.rb b/spec/services/block_domain_service_spec.rb
index 36dce9d196..fc3a1397d9 100644
--- a/spec/services/block_domain_service_spec.rb
+++ b/spec/services/block_domain_service_spec.rb
@@ -21,19 +21,19 @@ RSpec.describe BlockDomainService, type: :service do
end
it 'removes remote accounts from that domain' do
- expect(Account.find_remote('badguy666', 'evil.org').suspended?).to be true
+ expect(bad_account.reload.suspended?).to be true
end
it 'records suspension date appropriately' do
- expect(Account.find_remote('badguy666', 'evil.org').suspended_at).to eq DomainBlock.find_by(domain: 'evil.org').created_at
+ expect(bad_account.reload.suspended_at).to eq DomainBlock.find_by(domain: 'evil.org').created_at
end
it 'keeps already-banned accounts banned' do
- expect(Account.find_remote('badguy', 'evil.org').suspended?).to be true
+ expect(already_banned_account.reload.suspended?).to be true
end
it 'does not overwrite suspension date of already-banned accounts' do
- expect(Account.find_remote('badguy', 'evil.org').suspended_at).to_not eq DomainBlock.find_by(domain: 'evil.org').created_at
+ expect(already_banned_account.reload.suspended_at).to_not eq DomainBlock.find_by(domain: 'evil.org').created_at
end
it 'removes the remote accounts\'s statuses and media attachments' do
@@ -53,19 +53,19 @@ RSpec.describe BlockDomainService, type: :service do
end
it 'silences remote accounts from that domain' do
- expect(Account.find_remote('badguy666', 'evil.org').silenced?).to be true
+ expect(bad_account.reload.silenced?).to be true
end
it 'records suspension date appropriately' do
- expect(Account.find_remote('badguy666', 'evil.org').silenced_at).to eq DomainBlock.find_by(domain: 'evil.org').created_at
+ expect(bad_account.reload.silenced_at).to eq DomainBlock.find_by(domain: 'evil.org').created_at
end
it 'keeps already-banned accounts banned' do
- expect(Account.find_remote('badguy', 'evil.org').silenced?).to be true
+ expect(already_banned_account.reload.silenced?).to be true
end
it 'does not overwrite suspension date of already-banned accounts' do
- expect(Account.find_remote('badguy', 'evil.org').silenced_at).to_not eq DomainBlock.find_by(domain: 'evil.org').created_at
+ expect(already_banned_account.reload.silenced_at).to_not eq DomainBlock.find_by(domain: 'evil.org').created_at
end
it 'leaves the domains status and attachments, but clears media' do
diff --git a/spec/services/bulk_import_service_spec.rb b/spec/services/bulk_import_service_spec.rb
index 3a3f95ccc6..f703650c37 100644
--- a/spec/services/bulk_import_service_spec.rb
+++ b/spec/services/bulk_import_service_spec.rb
@@ -271,14 +271,15 @@ RSpec.describe BulkImportService do
let(:import_type) { 'domain_blocking' }
let(:overwrite) { false }
- let!(:rows) do
+ let(:rows) do
[
{ 'domain' => 'blocked.com' },
{ 'domain' => 'to_block.com' },
- ].map { |data| import.rows.create!(data: data) }
+ ]
end
before do
+ rows.each { |data| import.rows.create!(data: data) }
account.block_domain!('alreadyblocked.com')
account.block_domain!('blocked.com')
end
@@ -298,14 +299,15 @@ RSpec.describe BulkImportService do
let(:import_type) { 'domain_blocking' }
let(:overwrite) { true }
- let!(:rows) do
+ let(:rows) do
[
{ 'domain' => 'blocked.com' },
{ 'domain' => 'to_block.com' },
- ].map { |data| import.rows.create!(data: data) }
+ ]
end
before do
+ rows.each { |data| import.rows.create!(data: data) }
account.block_domain!('alreadyblocked.com')
account.block_domain!('blocked.com')
end
diff --git a/spec/services/delete_account_service_spec.rb b/spec/services/delete_account_service_spec.rb
index a2c57f1c1e..f04ba9cf14 100644
--- a/spec/services/delete_account_service_spec.rb
+++ b/spec/services/delete_account_service_spec.rb
@@ -28,74 +28,69 @@ RSpec.describe DeleteAccountService, type: :service do
let!(:account_note) { Fabricate(:account_note, account: account) }
it 'deletes associated owned and target records and target notifications' do
- expect { subject }
- .to delete_associated_owned_records
- .and delete_associated_target_records
- .and delete_associated_target_notifications
+ subject
+
+ expect_deletion_of_associated_owned_records
+ expect_deletion_of_associated_target_records
+ expect_deletion_of_associated_target_notifications
end
- def delete_associated_owned_records
- change do
- [
- account.statuses,
- account.media_attachments,
- account.notifications,
- account.favourites,
- account.active_relationships,
- account.passive_relationships,
- account.polls,
- account.account_notes,
- ].map(&:count)
- end.from([2, 1, 1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0, 0, 0])
+ def expect_deletion_of_associated_owned_records
+ expect { status.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { status_with_mention.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { mention.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { media_attachment.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { notification.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { favourite.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { active_relationship.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { passive_relationship.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { poll.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { poll_vote.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { account_note.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
- def delete_associated_target_records
- change(account_pins_for_account, :count).from(1).to(0)
+ def expect_deletion_of_associated_target_records
+ expect { endorsement.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
- def account_pins_for_account
- AccountPin.where(target_account: account)
- end
-
- def delete_associated_target_notifications
- change do
- %w(
- poll favourite status mention follow
- ).map { |type| Notification.where(type: type).count }
- end.from([1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0])
+ def expect_deletion_of_associated_target_notifications
+ expect { favourite_notification.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { follow_notification.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { mention_notification.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { poll_notification.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { status_notification.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
describe '#call on local account' do
before do
- stub_request(:post, 'https://alice.com/inbox').to_return(status: 201)
- stub_request(:post, 'https://bob.com/inbox').to_return(status: 201)
+ stub_request(:post, remote_alice.inbox_url).to_return(status: 201)
+ stub_request(:post, remote_bob.inbox_url).to_return(status: 201)
end
let!(:remote_alice) { Fabricate(:account, inbox_url: 'https://alice.com/inbox', domain: 'alice.com', protocol: :activitypub) }
let!(:remote_bob) { Fabricate(:account, inbox_url: 'https://bob.com/inbox', domain: 'bob.com', protocol: :activitypub) }
include_examples 'common behavior' do
- let!(:account) { Fabricate(:account) }
- let!(:local_follower) { Fabricate(:account) }
+ let(:account) { Fabricate(:account) }
+ let(:local_follower) { Fabricate(:account) }
it 'sends a delete actor activity to all known inboxes' do
subject
- expect(a_request(:post, 'https://alice.com/inbox')).to have_been_made.once
- expect(a_request(:post, 'https://bob.com/inbox')).to have_been_made.once
+ expect(a_request(:post, remote_alice.inbox_url)).to have_been_made.once
+ expect(a_request(:post, remote_bob.inbox_url)).to have_been_made.once
end
end
end
describe '#call on remote account' do
before do
- stub_request(:post, 'https://alice.com/inbox').to_return(status: 201)
- stub_request(:post, 'https://bob.com/inbox').to_return(status: 201)
+ stub_request(:post, account.inbox_url).to_return(status: 201)
end
include_examples 'common behavior' do
- let!(:account) { Fabricate(:account, inbox_url: 'https://bob.com/inbox', protocol: :activitypub, domain: 'bob.com') }
- let!(:local_follower) { Fabricate(:account) }
+ let(:account) { Fabricate(:account, inbox_url: 'https://bob.com/inbox', protocol: :activitypub, domain: 'bob.com') }
+ let(:local_follower) { Fabricate(:account) }
it 'sends expected activities to followed and follower inboxes' do
subject
diff --git a/spec/services/import_service_spec.rb b/spec/services/import_service_spec.rb
index 3936b2363f..d67da66caf 100644
--- a/spec/services/import_service_spec.rb
+++ b/spec/services/import_service_spec.rb
@@ -190,7 +190,7 @@ RSpec.describe ImportService, type: :service do
# Make sure to not actually go to the remote server
before do
- stub_request(:post, 'https://թութ.հայ/inbox').to_return(status: 200)
+ stub_request(:post, nare.inbox_url).to_return(status: 200)
end
it 'follows the listed account' do
diff --git a/spec/services/notify_service_spec.rb b/spec/services/notify_service_spec.rb
index 8fcb586580..50055dafc7 100644
--- a/spec/services/notify_service_spec.rb
+++ b/spec/services/notify_service_spec.rb
@@ -67,9 +67,10 @@ RSpec.describe NotifyService, type: :service do
context 'when the message chain is initiated by recipient, but is not direct message' do
let(:reply_to) { Fabricate(:status, account: recipient) }
- let!(:mention) { Fabricate(:mention, account: sender, status: reply_to) }
let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: reply_to)) }
+ before { Fabricate(:mention, account: sender, status: reply_to) }
+
it 'does not notify' do
expect { subject }.to_not change(Notification, :count)
end
@@ -77,10 +78,11 @@ RSpec.describe NotifyService, type: :service do
context 'when the message chain is initiated by recipient, but without a mention to the sender, even if the sender sends multiple messages in a row' do
let(:reply_to) { Fabricate(:status, account: recipient) }
- let!(:mention) { Fabricate(:mention, account: sender, status: reply_to) }
let(:dummy_reply) { Fabricate(:status, account: sender, visibility: :direct, thread: reply_to) }
let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: dummy_reply)) }
+ before { Fabricate(:mention, account: sender, status: reply_to) }
+
it 'does not notify' do
expect { subject }.to_not change(Notification, :count)
end
@@ -88,9 +90,10 @@ RSpec.describe NotifyService, type: :service do
context 'when the message chain is initiated by the recipient with a mention to the sender' do
let(:reply_to) { Fabricate(:status, account: recipient, visibility: :direct) }
- let!(:mention) { Fabricate(:mention, account: sender, status: reply_to) }
let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: reply_to)) }
+ before { Fabricate(:mention, account: sender, status: reply_to) }
+
it 'does notify' do
expect { subject }.to change(Notification, :count)
end
diff --git a/spec/services/remove_status_service_spec.rb b/spec/services/remove_status_service_spec.rb
index 7754ae8004..08ce0b0456 100644
--- a/spec/services/remove_status_service_spec.rb
+++ b/spec/services/remove_status_service_spec.rb
@@ -12,15 +12,15 @@ RSpec.describe RemoveStatusService, type: :service do
let!(:bill) { Fabricate(:account, username: 'bill', protocol: :activitypub, domain: 'example2.com', inbox_url: 'http://example2.com/inbox') }
before do
- stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
- stub_request(:post, 'http://example2.com/inbox').to_return(status: 200)
+ stub_request(:post, hank.inbox_url).to_return(status: 200)
+ stub_request(:post, bill.inbox_url).to_return(status: 200)
jeff.follow!(alice)
hank.follow!(alice)
end
context 'when removed status is not a reblog' do
- let!(:status) { PostStatusService.new.call(alice, text: 'Hello @bob@example.com ThisIsASecret') }
+ let!(:status) { PostStatusService.new.call(alice, text: "Hello @#{bob.pretty_acct} ThisIsASecret") }
before do
FavouriteService.new.call(jeff, status)
@@ -39,7 +39,7 @@ RSpec.describe RemoveStatusService, type: :service do
it 'sends Delete activity to followers' do
subject.call(status)
- expect(a_request(:post, 'http://example.com/inbox').with(
+ expect(a_request(:post, hank.inbox_url).with(
body: hash_including({
'type' => 'Delete',
'object' => {
@@ -53,7 +53,7 @@ RSpec.describe RemoveStatusService, type: :service do
it 'sends Delete activity to rebloggers' do
subject.call(status)
- expect(a_request(:post, 'http://example2.com/inbox').with(
+ expect(a_request(:post, bill.inbox_url).with(
body: hash_including({
'type' => 'Delete',
'object' => {
@@ -78,7 +78,7 @@ RSpec.describe RemoveStatusService, type: :service do
it 'sends Undo activity to followers' do
subject.call(status)
- expect(a_request(:post, 'http://example.com/inbox').with(
+ expect(a_request(:post, hank.inbox_url).with(
body: hash_including({
'type' => 'Undo',
'object' => hash_including({
@@ -96,7 +96,7 @@ RSpec.describe RemoveStatusService, type: :service do
it 'sends Undo activity to followers' do
subject.call(status)
- expect(a_request(:post, 'http://example.com/inbox').with(
+ expect(a_request(:post, hank.inbox_url).with(
body: hash_including({
'type' => 'Undo',
'object' => hash_including({
diff --git a/spec/services/report_service_spec.rb b/spec/services/report_service_spec.rb
index d3bcd5d31c..2a919f6405 100644
--- a/spec/services/report_service_spec.rb
+++ b/spec/services/report_service_spec.rb
@@ -156,9 +156,8 @@ RSpec.describe ReportService, type: :service do
-> { described_class.new.call(source_account, target_account) }
end
- let!(:other_report) { Fabricate(:report, target_account: target_account) }
-
before do
+ Fabricate(:report, target_account: target_account)
ActionMailer::Base.deliveries.clear
source_account.user.settings['notification_emails.report'] = true
source_account.user.save
diff --git a/spec/services/resolve_account_service_spec.rb b/spec/services/resolve_account_service_spec.rb
index 9f5fdca297..3c4b182e29 100644
--- a/spec/services/resolve_account_service_spec.rb
+++ b/spec/services/resolve_account_service_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe ResolveAccountService, type: :service do
let!(:remote_account) { Fabricate(:account, username: 'foo', domain: 'ap.example.com', protocol: 'activitypub') }
context 'when domain is banned' do
- let!(:domain_block) { Fabricate(:domain_block, domain: 'ap.example.com', severity: :suspend) }
+ before { Fabricate(:domain_block, domain: 'ap.example.com', severity: :suspend) }
it 'does not return an account' do
expect(subject.call('foo@ap.example.com', skip_webfinger: true)).to be_nil
@@ -214,6 +214,7 @@ RSpec.describe ResolveAccountService, type: :service do
expect(account.domain).to eq 'ap.example.com'
expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox'
expect(account.uri).to eq 'https://ap.example.com/users/foo'
+ expect(status.reload.account).to eq(account)
end
end
diff --git a/spec/services/suspend_account_service_spec.rb b/spec/services/suspend_account_service_spec.rb
index c258995b7e..b309ce511e 100644
--- a/spec/services/suspend_account_service_spec.rb
+++ b/spec/services/suspend_account_service_spec.rb
@@ -46,9 +46,9 @@ RSpec.describe SuspendAccountService, type: :service do
let!(:account) { Fabricate(:account) }
let!(:remote_follower) { Fabricate(:account, uri: 'https://alice.com', inbox_url: 'https://alice.com/inbox', protocol: :activitypub, domain: 'alice.com') }
let!(:remote_reporter) { Fabricate(:account, uri: 'https://bob.com', inbox_url: 'https://bob.com/inbox', protocol: :activitypub, domain: 'bob.com') }
- let!(:report) { Fabricate(:report, account: remote_reporter, target_account: account) }
before do
+ Fabricate(:report, account: remote_reporter, target_account: account)
remote_follower.follow!(account)
end
diff --git a/spec/services/unallow_domain_service_spec.rb b/spec/services/unallow_domain_service_spec.rb
index 19d40e7e86..d96e354187 100644
--- a/spec/services/unallow_domain_service_spec.rb
+++ b/spec/services/unallow_domain_service_spec.rb
@@ -27,6 +27,7 @@ RSpec.describe UnallowDomainService, type: :service do
end
it 'removes remote accounts from that domain' do
+ expect { already_banned_account.reload }.to raise_error(ActiveRecord::RecordNotFound)
expect(Account.where(domain: 'evil.org').exists?).to be false
end
diff --git a/spec/services/unsuspend_account_service_spec.rb b/spec/services/unsuspend_account_service_spec.rb
index 2f737c6215..211c39e6c7 100644
--- a/spec/services/unsuspend_account_service_spec.rb
+++ b/spec/services/unsuspend_account_service_spec.rb
@@ -39,9 +39,9 @@ RSpec.describe UnsuspendAccountService, type: :service do
let!(:account) { Fabricate(:account) }
let!(:remote_follower) { Fabricate(:account, uri: 'https://alice.com', inbox_url: 'https://alice.com/inbox', protocol: :activitypub, domain: 'alice.com') }
let!(:remote_reporter) { Fabricate(:account, uri: 'https://bob.com', inbox_url: 'https://bob.com/inbox', protocol: :activitypub, domain: 'bob.com') }
- let!(:report) { Fabricate(:report, account: remote_reporter, target_account: account) }
before do
+ Fabricate(:report, account: remote_reporter, target_account: account)
remote_follower.follow!(account)
end
diff --git a/spec/workers/scheduler/follow_recommendations_scheduler_spec.rb b/spec/workers/scheduler/follow_recommendations_scheduler_spec.rb
index 18d5260e42..246012e128 100644
--- a/spec/workers/scheduler/follow_recommendations_scheduler_spec.rb
+++ b/spec/workers/scheduler/follow_recommendations_scheduler_spec.rb
@@ -29,14 +29,12 @@ describe Scheduler::FollowRecommendationsScheduler do
it 'creates recommendations' do
expect { scheduled_run }.to change(FollowRecommendation, :count).from(0).to(target_accounts.size)
- expect(redis.zrange('follow_recommendations:en', 0, -1)).to match_array(target_accounts.pluck(:id).map(&:to_s))
end
end
context 'when there are no accounts to recommend' do
it 'does not create follow recommendations' do
expect { scheduled_run }.to_not change(FollowRecommendation, :count)
- expect(redis.zrange('follow_recommendations:en', 0, -1)).to be_empty
end
end
end
diff --git a/spec/workers/scheduler/user_cleanup_scheduler_spec.rb b/spec/workers/scheduler/user_cleanup_scheduler_spec.rb
index 9909795008..8fda246ba8 100644
--- a/spec/workers/scheduler/user_cleanup_scheduler_spec.rb
+++ b/spec/workers/scheduler/user_cleanup_scheduler_spec.rb
@@ -18,12 +18,11 @@ describe Scheduler::UserCleanupScheduler do
confirmed_user.update!(confirmed_at: 1.day.ago)
end
- it 'deletes the old unconfirmed user' do
- expect { subject.perform }.to change { User.exists?(old_unconfirmed_user.id) }.from(true).to(false)
- end
-
- it "deletes the old unconfirmed user's account" do
- expect { subject.perform }.to change { Account.exists?(old_unconfirmed_user.account_id) }.from(true).to(false)
+ it 'deletes the old unconfirmed user, their account, and the moderation note' do
+ expect { subject.perform }
+ .to change { User.exists?(old_unconfirmed_user.id) }.from(true).to(false)
+ .and change { Account.exists?(old_unconfirmed_user.account_id) }.from(true).to(false)
+ expect { moderation_note.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
it 'does not delete the new unconfirmed user or their account' do
diff --git a/yarn.lock b/yarn.lock
index aaebc8c319..e4c5d0f463 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -14520,8 +14520,8 @@ __metadata:
linkType: hard
"sass-loader@npm:^10.2.0":
- version: 10.5.0
- resolution: "sass-loader@npm:10.5.0"
+ version: 10.5.1
+ resolution: "sass-loader@npm:10.5.1"
dependencies:
klona: "npm:^2.0.4"
loader-utils: "npm:^2.0.0"
@@ -14540,7 +14540,7 @@ __metadata:
optional: true
sass:
optional: true
- checksum: be5da7784fd21c4f526cc3afaa1a765ba44cdc2f9798ecbac87b296ab44184ac5ba9bbda68a7a86f8cdcb6130acceefeb8912260fca14bdfc97f9dad02658400
+ checksum: 841448d02045b0c65595eab4cb701384b01a2adcb3594beacbb767b0cee5bd9d444027f4fc3a10acef3fe1c7eb6510fccffdee72a20e9877777789a5e349cb49
languageName: node
linkType: hard