From b5ac61b2c5cad94a680527b961def46aea0a1ad4 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 19 Dec 2023 11:59:43 +0100 Subject: [PATCH 01/31] Change algorithm of follow recommendations (#28314) Co-authored-by: Claire --- .github/renovate.json5 | 7 +- .../follow_recommendations_controller.rb | 2 +- .../api/v1/suggestions_controller.rb | 13 ++-- .../api/v2/suggestions_controller.rb | 12 +++- app/lib/potential_friendship_tracker.rb | 31 --------- app/models/account_domain_block.rb | 5 ++ app/models/account_suggestions.rb | 48 +++++++++---- .../friends_of_friends_source.rb | 37 ++++++++++ .../account_suggestions/global_source.rb | 34 ++-------- .../past_interactions_source.rb | 36 ---------- .../account_suggestions/setting_source.rb | 34 +++------- .../similar_profiles_source.rb | 67 +++++++++++++++++++ app/models/account_suggestions/source.rb | 30 ++------- app/models/block.rb | 9 ++- app/models/concerns/account/associations.rb | 1 + app/models/concerns/account/interactions.rb | 13 ---- app/models/concerns/account/search.rb | 1 + app/models/follow.rb | 8 ++- app/models/follow_recommendation_filter.rb | 7 +- app/models/follow_recommendation_mute.rb | 26 +++++++ .../follow_recommendation_suppression.rb | 14 ---- app/models/follow_request.rb | 5 ++ app/models/mute.rb | 9 ++- app/models/preview_cards_status.rb | 4 +- app/services/account_search_service.rb | 5 ++ app/services/favourite_service.rb | 7 +- app/services/post_status_service.rb | 3 - app/services/reblog_service.rb | 8 +-- .../follow_recommendations/show.html.haml | 2 + .../follow_recommendations_scheduler.rb | 50 -------------- ...4923_create_follow_recommendation_mutes.rb | 14 ++++ ...dd_languages_index_to_account_summaries.rb | 9 +++ db/schema.rb | 14 +++- spec/requests/api/v1/suggestions_spec.rb | 22 +++--- .../follow_recommendations_scheduler_spec.rb | 2 - 35 files changed, 297 insertions(+), 292 deletions(-) delete mode 100644 app/lib/potential_friendship_tracker.rb create mode 100644 app/models/account_suggestions/friends_of_friends_source.rb delete mode 100644 app/models/account_suggestions/past_interactions_source.rb create mode 100644 app/models/account_suggestions/similar_profiles_source.rb create mode 100644 app/models/follow_recommendation_mute.rb create mode 100644 db/migrate/20231211234923_create_follow_recommendation_mutes.rb create mode 100644 db/migrate/20231212073317_add_languages_index_to_account_summaries.rb diff --git a/.github/renovate.json5 b/.github/renovate.json5 index aca5228594..dab99829a1 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -102,9 +102,12 @@ { // Group actions/*-artifact in the same PR matchManagers: ['github-actions'], - matchPackageNames: ['actions/download-artifact', 'actions/upload-artifact'], + matchPackageNames: [ + 'actions/download-artifact', + 'actions/upload-artifact', + ], matchUpdateTypes: ['major'], - groupName: 'artifact actions (major)' + groupName: 'artifact actions (major)', }, { // Update @types/* packages every week, with one grouped PR 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/lib/potential_friendship_tracker.rb b/app/lib/potential_friendship_tracker.rb deleted file mode 100644 index f5bc203465..0000000000 --- a/app/lib/potential_friendship_tracker.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -class PotentialFriendshipTracker - EXPIRE_AFTER = 90.days.seconds - MAX_ITEMS = 80 - - WEIGHTS = { - reply: 1, - 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..77d4f635aa --- /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 follows.target_account_id NOT IN (SELECT * FROM first_degree) + 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 31902ae21a..2bb6fed5ad 100644 --- a/app/models/concerns/account/associations.rb +++ b/app/models/concerns/account/associations.rb @@ -64,6 +64,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 4ddec9bf49..351530c2f0 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 @@ -307,10 +300,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 ea27f374e7..8aa43ab245 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -178,9 +178,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/reblog_service.rb b/app/services/reblog_service.rb index 6ec0944744..960315ed8b 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) 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/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 a0062c8cea..126ed8785a 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 @@ -1209,6 +1218,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 @@ -1341,6 +1352,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/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/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 From 4075b4d7ee962642a50e0933882d769934c58760 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 19 Dec 2023 13:39:59 +0100 Subject: [PATCH 02/31] Update dependency node to 20.10 (#28040) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .nvmrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nvmrc b/.nvmrc index 48ef2c10ba..d4c3d320cc 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20.9 +20.10 From 5489d4c84c07b9fff56976d0de346ce24f344fda Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 19 Dec 2023 13:54:02 +0100 Subject: [PATCH 03/31] Update dependency aws-sdk-s3 to v1.141.0 (#28116) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 3b33402b22..15bdb9b4f8 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) From c7c727994a04b0ce959d149593a2c732707e7ea5 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 19 Dec 2023 08:10:33 -0500 Subject: [PATCH 04/31] Add spec for `CLI::Maintenance#fix_duplicates` (#28326) --- lib/mastodon/cli/maintenance.rb | 2 +- spec/lib/mastodon/cli/maintenance_spec.rb | 73 +++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/lib/mastodon/cli/maintenance.rb b/lib/mastodon/cli/maintenance.rb index 2e5243468b..98067c6e34 100644 --- a/lib/mastodon/cli/maintenance.rb +++ b/lib/mastodon/cli/maintenance.rb @@ -546,7 +546,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 diff --git a/spec/lib/mastodon/cli/maintenance_spec.rb b/spec/lib/mastodon/cli/maintenance_spec.rb index 02169b7a42..61331e6dba 100644 --- a/spec/lib/mastodon/cli/maintenance_spec.rb +++ b/spec/lib/mastodon/cli/maintenance_spec.rb @@ -52,5 +52,78 @@ 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 + 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', + 'Restoring index_accounts_on_username_and_domain_lower', + 'Reindexing textual indexes on accounts…', + 'Finished!' + ) + .and change(duplicate_accounts, :count).from(2).to(1) + end + + def duplicate_accounts + Account.where(username: duplicate_account_username, domain: duplicate_account_domain) + end + + def prepare_duplicate_data + ActiveRecord::Base.connection.remove_index :accounts, name: :index_accounts_on_username_and_domain_lower + Fabricate(:account, username: duplicate_account_username, domain: duplicate_account_domain) + Fabricate.build(:account, username: duplicate_account_username, domain: duplicate_account_domain).save(validate: false) + end + end + + context 'with duplicate users' 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 + + def agree_to_backup_warning + allow(cli.shell) + .to receive(:yes?) + .with('Continue? (Yes/No)') + .and_return(true) + .once + end + end end end From 6fed0fcbaa1ac5866443e6af6424a78a78b78688 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 19 Dec 2023 16:17:22 +0100 Subject: [PATCH 05/31] Remove unneeded settings cleanup from specs (#28425) --- .../settings/branding_controller_spec.rb | 8 ---- .../api/v1/trends/links_controller_spec.rb | 6 --- .../api/v1/trends/statuses_controller_spec.rb | 6 --- .../api/v1/trends/tags_controller_spec.rb | 6 --- .../auth/registrations_controller_spec.rb | 44 ------------------- .../account_controller_concern_spec.rb | 6 --- spec/helpers/application_helper_spec.rb | 12 ----- spec/helpers/instance_helper_spec.rb | 6 --- spec/models/user_spec.rb | 34 ++------------ spec/presenters/instance_presenter_spec.rb | 24 ---------- .../api/v1/instances/activity_spec.rb | 6 --- .../api/v1/instances/domain_blocks_spec.rb | 6 --- spec/requests/api/v1/instances/peers_spec.rb | 6 --- spec/requests/cache_spec.rb | 20 ++------- spec/services/app_sign_up_service_spec.rb | 7 +-- 15 files changed, 8 insertions(+), 189 deletions(-) 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/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/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/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/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 0a55770bad..ef1463e64c 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -292,12 +292,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) @@ -318,12 +312,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/models/user_spec.rb b/spec/models/user_spec.rb index a2f8d2ca44..9111fd7c73 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 diff --git a/spec/presenters/instance_presenter_spec.rb b/spec/presenters/instance_presenter_spec.rb index 2a1d668ceb..516c7e9896 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/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/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/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 From 961d65aba6432d83e70a5e8ee720034b87064d02 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 20 Dec 2023 03:54:04 -0500 Subject: [PATCH 06/31] Add coverage to CLI Maintenance for duplicate users on `confirmation_token` and `reset_password_token` values (#28434) --- spec/lib/mastodon/cli/maintenance_spec.rb | 60 ++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/spec/lib/mastodon/cli/maintenance_spec.rb b/spec/lib/mastodon/cli/maintenance_spec.rb index 61331e6dba..353bf08b68 100644 --- a/spec/lib/mastodon/cli/maintenance_spec.rb +++ b/spec/lib/mastodon/cli/maintenance_spec.rb @@ -89,7 +89,7 @@ describe Mastodon::CLI::Maintenance do end end - context 'with duplicate users' do + context 'with duplicate users on email' do before do prepare_duplicate_data end @@ -117,6 +117,64 @@ describe Mastodon::CLI::Maintenance do 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 + def agree_to_backup_warning allow(cli.shell) .to receive(:yes?) From 9a24b124168b65cf93ab9f8372e415d692f2c62f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 20 Dec 2023 09:54:21 +0100 Subject: [PATCH 07/31] Update dependency sass-loader to v10.5.1 (#28432) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index a11316260f..764faff27a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14485,8 +14485,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" @@ -14505,7 +14505,7 @@ __metadata: optional: true sass: optional: true - checksum: be5da7784fd21c4f526cc3afaa1a765ba44cdc2f9798ecbac87b296ab44184ac5ba9bbda68a7a86f8cdcb6130acceefeb8912260fca14bdfc97f9dad02658400 + checksum: 841448d02045b0c65595eab4cb701384b01a2adcb3594beacbb767b0cee5bd9d444027f4fc3a10acef3fe1c7eb6510fccffdee72a20e9877777789a5e349cb49 languageName: node linkType: hard From fecc07856ce3eeb6fa8dc15cb22283f4c60d10bb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 20 Dec 2023 10:15:30 +0100 Subject: [PATCH 08/31] New Crowdin Translations (automated) (#28439) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/ie.json | 37 + app/javascript/mastodon/locales/lad.json | 49 ++ config/locales/activerecord.lad.yml | 8 + config/locales/devise.ie.yml | 2 + config/locales/ie.yml | 561 ++++++++++++++- config/locales/lad.yml | 850 ++++++++++++++++++++++- config/locales/simple_form.ie.yml | 34 + config/locales/simple_form.lad.yml | 36 + 8 files changed, 1571 insertions(+), 6 deletions(-) diff --git a/app/javascript/mastodon/locales/ie.json b/app/javascript/mastodon/locales/ie.json index 966b272720..9f91bf2cef 100644 --- a/app/javascript/mastodon/locales/ie.json +++ b/app/javascript/mastodon/locales/ie.json @@ -140,6 +140,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 +198,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 +323,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 +354,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 +392,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 +410,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 +433,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 +460,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 +496,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 +627,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 +656,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 +697,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 +715,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/lad.json b/app/javascript/mastodon/locales/lad.json index ffdc044412..1b501c710a 100644 --- a/app/javascript/mastodon/locales/lad.json +++ b/app/javascript/mastodon/locales/lad.json @@ -294,18 +294,25 @@ "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_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.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.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.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.title.favourite": "Endika ke te plaze publikasyon de {name}", @@ -356,6 +363,7 @@ "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", @@ -377,6 +385,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", @@ -451,10 +460,25 @@ "notifications_permission_banner.title": "Nunka te piedres niente", "onboarding.action.back": "Va atras", "onboarding.actions.back": "Va atras", + "onboarding.compose.template": "Ke haber, #Mastodon?", + "onboarding.follows.title": "Personaliza tu linya prinsipala", "onboarding.profile.display_name": "Nombre amostrado", + "onboarding.profile.display_name_hint": "Tu nombre para amostrar.", + "onboarding.profile.note": "Tu deskripsyon", + "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.title": "Partaja tu profil", + "onboarding.start.skip": "No nesesitas ayudo para ampesar?", + "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 +497,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!", @@ -503,9 +528,11 @@ "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.other": "Es otra koza", "report.reasons.other_description": "El problem no es de las otras kategorias", "report.reasons.spam": "Es spam", @@ -537,8 +564,13 @@ "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.user": "utilizador", "search_results.accounts": "Profiles", "search_results.all": "Todos", "search_results.hashtags": "Etiketas", @@ -555,12 +587,29 @@ "sign_in_banner.create_account": "Kriya kuento", "sign_in_banner.sign_in": "Konektate", "sign_in_banner.sso_redirect": "Konektate o enrejistrate", + "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.direct": "Enmenta a @{name} en privado", + "status.direct_indicator": "Enmentadura privada", + "status.edit": "Edita", + "status.edited": "Editado {date}", + "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", 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..cee5357c24 100644 --- a/config/locales/devise.ie.yml +++ b/config/locales/devise.ie.yml @@ -26,3 +26,5 @@ ie: 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..94df7d1454 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 @@ -448,6 +450,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 @@ -465,9 +468,20 @@ 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 delivery: unavailable: Índisponibil delivery_error_hint: Si liveration ne es possibil durant %{count} dies, it va esser marcat automaticmen quam ínliverabil. @@ -586,37 +600,85 @@ ie: skip_to_actions: Ear rect al actiones status: Statu statuses: Contenete raportat + summary: + close_report: 'Marcar raporte #%{id} quam resoluet' title: Raportes roles: categories: + devops: DevOps + invites: Invitationes moderation: Moderation + special: Special + delete: Deleter 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 manage_announcements: Tractar proclamationes manage_announcements_description: Permisse usatores tractar proclamationes sur li servitor + manage_appeals: Gerer Apelles + manage_federation: Gerer Federation + manage_invites: Gerer Invitationes + manage_reports: Gerer Raportes + manage_roles: Gerer Roles + manage_taxonomies: Gerer Taxonomies 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 title: Roles rules: add_new: Adjunter un regule + title: Regules del servitor settings: about: title: Pri appearance: title: Aspecte + discovery: + profile_directory: Profilarium + public_timelines: Public témpor-lineas domain_blocks: users: A local usatores qui ha initiat session + registrations: + title: Registrationes + registrations_mode: + modes: + none: Nequi posse registrar se + open: Quicunc posse registrar se site_uploads: delete: Deleter cargat file destroyed_msg: Cargat file successosimen deletet! software_updates: - type: Tip + type: Specie version: Version statuses: account: Autor + batch: + remove_from_report: Remover de raporte + report: Raportar + deleted: Deletet + favourites: Favorites + in_reply_to: Respondent a language: Lingue metadata: Metadata + open: Aperter posta + original_status: Original posta visibility: Visibilitá + strikes: + actions: + 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 title: Administration trends: approved: Aprobat @@ -633,25 +695,137 @@ ie: webhooks: 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 + 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 +833,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,13 +1070,50 @@ 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: set_redirect: Configurar un redirection move_handler: @@ -754,13 +1126,34 @@ 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 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 +1170,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,6 +1299,7 @@ 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} pin_errors: @@ -839,25 +1328,87 @@ ie: 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 welcome: + explanation: Vi quelc suggestiones por que tu mey comensar subject: Benevenit a Mastodon 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.' verification: Verification diff --git a/config/locales/lad.yml b/config/locales/lad.yml index 626d2da7ec..afa6d2930c 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}" @@ -280,7 +330,61 @@ 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 + 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 + 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 +449,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 +694,38 @@ 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_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: + title: Aparensya + discovery: + follow_recommendations: Rekomendasyones de kuentos + profile_directory: Katalogo de profiles + public_timelines: Linyas de tiempo publikas + 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: @@ -542,6 +738,7 @@ lad: destroyed_msg: Dosya supremida kon sukseso! software_updates: documentation_link: Ambezate mas + title: Aktualizasyones desponivles type: Tipo version: Versyon statuses: @@ -579,7 +776,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: @@ -620,3 +817,654 @@ 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_usable: No se pueden uzar + 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_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_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 + appearance: + advanced_web_interface: Enterfaz web avanzada + sensitive_content: Kontenido sensivle + application_mailer: + 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 + confirmations: + login_link: konektate kon kuento + 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 + 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. + 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. + 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 + disputes: + strikes: + action_taken: Aksyon tomada + appeal: Apela + 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: + 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 + filters: + contexts: + account: Profiles + notifications: Avizos + public: Linyas de tiempo publikas + edit: + add_keyword: Adjusta biervo yave + keywords: Biervos yaves + statuses: Publikasyones individualas + index: + delete: Efasa + title: Filtros + new: + save: Guadra muevo filtro + title: Adjusta muevo filtro + statuses: + back_to_filter: Retorna al filtro + batch: + remove: Kita del filtro + generic: + all: Todos + cancel: Anula + confirm: Konfirma + copy: Kopia + delete: Efasa + none: Dinguno + save_changes: Guadra trokamientos + today: oy + imports: + errors: + 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. + states: + finished: Finalizado + scheduled: Programado + unconfirmed: Sin konfirmasyon + status: Estado + success: Tus datos se tienen kargado djustamente i seran prosesados pishin + types: + blocking: Lista de blokos + bookmarks: Markadores + lists: Listas + upload: Karga + invites: + delete: Dezaktiva + expires_in: + '1800': 30 minutos + '21600': 6 oras + '3600': 1 ora + '43200': 12 oras + '604800': 1 semana + '86400': 1 diya + expires_in_prompt: Nunkua + 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: + complete: Dezabonado + 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: + follow: + title: Muevo suivante + follow_request: + title: Mueva solisitud de segimiento + mention: + action: Arisponde + number: + human: + decimal_units: + format: "%n%u" + units: + billion: MM + million: M + thousand: K + 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 + 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 + status: Estado del kuento + rss: + content_warning: 'Avertensya de kontenido:' + sessions: + activity: Ultima aktivita + browsers: + alipay: Alipay + blackberry: BlackBerry + chrome: Chrome + edge: Microsoft Edge + electron: Electron + firefox: Firefox + 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 + weibo: Weibo + current_session: Sesyon aktuala + 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 + 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 + poll: + vote: Vota + show_more: Amostra mas + statuses_cleanup: + 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 + 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:' + title: + none: Avertensya + silence: Kuento limitado + suspend: Kuento suspendido + users: + signed_in_as: 'Konektado komo:' + verification: + verification: Verifikasyon + webauthn_credentials: + add: Adjusta mueva yave de sigurita + delete: Efasa + 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..a94903b662 100644 --- a/config/locales/simple_form.ie.yml +++ b/config/locales/simple_form.ie.yml @@ -4,15 +4,47 @@ 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. + 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. theme: Li dessine quel ínregistrat visitantes e nov usatores vide. @@ -43,5 +75,7 @@ ie: new_password: Nov passa-parol note: Biografie password: Passa-parol + form_admin_settings: + registrations_mode: Qui posse registrar se notification_emails: follow_request: Alqui petit sequer te diff --git a/config/locales/simple_form.lad.yml b/config/locales/simple_form.lad.yml index 4247487607..33fe877db1 100644 --- a/config/locales/simple_form.lad.yml +++ b/config/locales/simple_form.lad.yml @@ -168,6 +168,19 @@ lad: confirm_password: Konfirma kod context: Filtra kontekstos current_password: Kod aktual + display_name: Nombre amostrado + email: Adreso de posta elektronika + expires_in: Kaduka dempues de + header: Imaje de kavesera + new_password: Muevo kod + note: Deskripsyon + otp_attempt: Kodiche de dos pasos + password: Kod + 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_use_blurhash: Amostra gradientes koloridos para kontenido multimedia eskondido setting_use_pending_items: Modo lento severity: Severita @@ -228,3 +241,26 @@ lad: no_access: Bloka akseso sign_up_block: Bloka enrejistrasyones sign_up_requires_approval: Limita enrejistrasyones + severity: Regla + notification_emails: + follow: Alguno te ampeso a segir + follow_request: Alguno tiene solisitado segirte + mention: Alguno te enmento + reblog: Alguno repartajo tu publikasyon + rule: + text: Regla + tag: + name: Etiketa + user: + role: Rolo + user_role: + name: Nombre + permissions_as_keys: Permisos + position: Priorita + webhook: + events: Evenimientos kapasitados + 'no': 'No' + recommended: Rekomendado + required: + mark: "*" + 'yes': Si From ac04e62a0ea7467abc6c6552cac2113489bf6aee Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 20 Dec 2023 10:44:01 +0100 Subject: [PATCH 09/31] Remove unused route definitions (#28430) --- config/routes/api.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/config/routes/api.rb b/config/routes/api.rb index ebfa60a690..0fe9f69abc 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -301,10 +301,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 From d29b1cca2e1dd9f38a12086ca8010fafbb0cac33 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 20 Dec 2023 15:35:59 +0100 Subject: [PATCH 10/31] Fix `friends_of_friends` sometimes suggesting already-followed accounts (#28433) --- app/models/account_suggestions/friends_of_friends_source.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/account_suggestions/friends_of_friends_source.rb b/app/models/account_suggestions/friends_of_friends_source.rb index 77d4f635aa..28d0ab99b3 100644 --- a/app/models/account_suggestions/friends_of_friends_source.rb +++ b/app/models/account_suggestions/friends_of_friends_source.rb @@ -16,7 +16,7 @@ class AccountSuggestions::FriendsOfFriendsSource < AccountSuggestions::Source 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 follows.target_account_id NOT 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 From 01f0a6ca4fbb4d53c01f8be353a9ebfeb98f1739 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 21 Dec 2023 09:32:25 +0100 Subject: [PATCH 11/31] Fix profile setup showing default avatar in web UI (#28453) --- app/javascript/mastodon/features/onboarding/profile.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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); From 2463b53363a621c1e18223bda5c47a663707a22c Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 21 Dec 2023 03:51:03 -0500 Subject: [PATCH 12/31] More duplicates in cli maintenance spec, misc bug fixes (#28449) --- lib/mastodon/cli/maintenance.rb | 40 +- spec/lib/mastodon/cli/maintenance_spec.rb | 426 +++++++++++++++++++++- 2 files changed, 443 insertions(+), 23 deletions(-) diff --git a/lib/mastodon/cli/maintenance.rb b/lib/mastodon/cli/maintenance.rb index 98067c6e34..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) @@ -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/lib/mastodon/cli/maintenance_spec.rb b/spec/lib/mastodon/cli/maintenance_spec.rb index 353bf08b68..ca492bbf69 100644 --- a/spec/lib/mastodon/cli/maintenance_spec.rb +++ b/spec/lib/mastodon/cli/maintenance_spec.rb @@ -62,6 +62,7 @@ describe Mastodon::CLI::Maintenance do context 'with duplicate accounts' do before do prepare_duplicate_data + choose_local_account_to_keep end let(:duplicate_account_username) { 'username' } @@ -71,21 +72,37 @@ describe Mastodon::CLI::Maintenance 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_accounts, :count).from(2).to(1) + .and change(duplicate_remote_accounts, :count).from(2).to(1) + .and change(duplicate_local_accounts, :count).from(2).to(1) end - def duplicate_accounts + 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 - Fabricate(:account, username: duplicate_account_username, domain: duplicate_account_domain) - Fabricate.build(:account, username: duplicate_account_username, domain: duplicate_account_domain).save(validate: false) + _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 @@ -175,6 +192,407 @@ describe Mastodon::CLI::Maintenance do 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?) From 9e5ddc1745bf729888dc2b11300955c715e95008 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 21 Dec 2023 10:09:00 +0100 Subject: [PATCH 13/31] New Crowdin Translations (automated) (#28451) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/lad.json | 28 +++++ app/javascript/mastodon/locales/tr.json | 4 +- config/locales/devise.ie.yml | 10 ++ config/locales/ie.yml | 38 +++++++ config/locales/lad.yml | 135 +++++++++++++++++++++++ config/locales/simple_form.ie.yml | 18 +++ config/locales/simple_form.lad.yml | 9 ++ config/locales/tr.yml | 2 +- 8 files changed, 241 insertions(+), 3 deletions(-) diff --git a/app/javascript/mastodon/locales/lad.json b/app/javascript/mastodon/locales/lad.json index 1b501c710a..c776a91ee7 100644 --- a/app/javascript/mastodon/locales/lad.json +++ b/app/javascript/mastodon/locales/lad.json @@ -77,6 +77,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 +224,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,6 +299,8 @@ "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", @@ -303,18 +310,23 @@ "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}", @@ -369,6 +381,7 @@ "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", @@ -403,6 +416,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", @@ -460,17 +474,24 @@ "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.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", @@ -515,6 +536,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", @@ -552,6 +574,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", @@ -570,6 +593,7 @@ "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", @@ -596,10 +620,12 @@ "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", @@ -637,6 +663,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", @@ -685,6 +712,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/tr.json b/app/javascript/mastodon/locales/tr.json index 8eb09bb7cb..e6389b05a5 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -45,7 +45,7 @@ "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:", @@ -345,7 +345,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/config/locales/devise.ie.yml b/config/locales/devise.ie.yml index cee5357c24..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,6 +28,8 @@ 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. diff --git a/config/locales/ie.yml b/config/locales/ie.yml index 94df7d1454..c9eef75204 100644 --- a/config/locales/ie.yml +++ b/config/locales/ie.yml @@ -329,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 @@ -433,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: @@ -459,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 @@ -601,15 +604,36 @@ ie: status: Statu statuses: Contenete raportat 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:' close_report: 'Marcar raporte #%{id} quam resoluet' 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 @@ -618,26 +642,39 @@ ie: manage_announcements: Tractar proclamationes manage_announcements_description: Permisse usatores tractar proclamationes sur li servitor manage_appeals: Gerer Apelles + manage_blocks: Gerer Bloccas + manage_custom_emojis: Gerer Customisat Emojis manage_federation: Gerer Federation manage_invites: Gerer Invitationes manage_reports: Gerer Raportes manage_roles: Gerer Roles + manage_rules: Gerer Regules + manage_settings: Gerer Parametres manage_taxonomies: Gerer Taxonomies 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_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: title: Aspecte discovery: profile_directory: Profilarium public_timelines: Public témpor-lineas + title: Decovriment domain_blocks: + all: Ad omnes + disabled: A necun users: A local usatores qui ha initiat session registrations: title: Registrationes @@ -645,6 +682,7 @@ ie: modes: none: Nequi posse registrar se open: Quicunc posse registrar se + title: Parametres del servitor site_uploads: delete: Deleter cargat file destroyed_msg: Cargat file successosimen deletet! diff --git a/config/locales/lad.yml b/config/locales/lad.yml index afa6d2930c..36364feba2 100644 --- a/config/locales/lad.yml +++ b/config/locales/lad.yml @@ -306,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 @@ -331,6 +332,7 @@ lad: 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 @@ -380,6 +382,7 @@ lad: 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 @@ -702,6 +705,7 @@ lad: 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: @@ -715,10 +719,17 @@ lad: title: Sovre esto appearance: 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 @@ -737,7 +748,9 @@ 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 version: Versyon @@ -795,6 +808,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: @@ -852,7 +869,9 @@ lad: 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 @@ -901,6 +920,8 @@ lad: 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}) @@ -908,6 +929,9 @@ lad: 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: @@ -916,10 +940,20 @@ lad: 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 @@ -936,7 +970,11 @@ lad: 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 welcome_title: Bienvenido, %{name}! wrong_email_hint: Si este adreso de posta es inkorekto, puedes trokarlo en las preferensyas del kuento. @@ -962,6 +1000,7 @@ lad: progress: confirm: Konfirma posta details: Tus detalyos + review: Muestra revizyon rules: Acheta reglas providers: cas: CAS @@ -975,10 +1014,12 @@ lad: 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} @@ -1023,6 +1064,8 @@ lad: x_seconds: "%{count} s" deletes: proceed: Efasa kuento + warning: + username_unavailable: Tu nombre de utilizador no estara desponivle disputes: strikes: action_taken: Aksyon tomada @@ -1054,6 +1097,7 @@ lad: 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. @@ -1093,14 +1137,27 @@ lad: 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: delete: Efasa + empty: No tyenes filtros. + 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 @@ -1109,17 +1166,22 @@ lad: 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 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 @@ -1130,19 +1192,35 @@ lad: 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: + 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 @@ -1151,6 +1229,8 @@ lad: '604800': 1 semana '86400': 1 diya expires_in_prompt: Nunkua + invalid: Esta envitasyon no es valida + invited_by: 'Fuites envitado por:' max_uses: one: 1 uzo other: "%{count} uzos" @@ -1214,12 +1294,31 @@ lad: moderation: title: Moderasyon notification_mailer: + admin: + sign_up: + subject: "%{name} se enrejistro" + favourite: + title: Muevo favorito follow: + body: "%{name} te esta sigiendo!" + subject: "%{name} te esta sigiendo" title: Muevo suivante follow_request: + 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 + reblog: + title: Mueva repartajasyon + status: + subject: "%{name} publiko algo" + update: + subject: "%{name} edito una publikasyon" + notifications: + other_settings: Otras preferensyas de avizos number: human: decimal_units: @@ -1227,7 +1326,9 @@ lad: 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." @@ -1249,6 +1350,7 @@ lad: 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: @@ -1286,6 +1388,7 @@ lad: content_warning: 'Avertensya de kontenido:' sessions: activity: Ultima aktivita + browser: Navigador browsers: alipay: Alipay blackberry: BlackBerry @@ -1293,6 +1396,7 @@ lad: edge: Microsoft Edge electron: Electron firefox: Firefox + generic: Navigador deskonosido huawei_browser: Huawei Browser ie: Internet Explorer micro_messenger: MicroMessenger @@ -1303,6 +1407,7 @@ lad: qq: QQ Browser safari: Safari uc_browser: UC Browser + unknown_browser: Navigador deskonosido weibo: Weibo current_session: Sesyon aktuala ip: IP @@ -1374,9 +1479,30 @@ lad: limit: Ya tienes fiksado el numero maksimo de publikasyones ownership: La publikasyon de otra persona no puede fiksarse 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 min_age: '1209600': 2 semanas '15778476': 6 mezes @@ -1451,14 +1577,23 @@ lad: 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:' + subject: + none: Avertensya para %{acct} title: + delete_statuses: Publikasyones efasadas + mark_statuses_as_sensitive: Publikasyones markadas komo sensivles none: Avertensya silence: Kuento limitado suspend: Kuento suspendido + welcome: + final_action: Ampesa a publikar + subject: Bienvenido a Mastodon users: + 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 delete: Efasa diff --git a/config/locales/simple_form.ie.yml b/config/locales/simple_form.ie.yml index a94903b662..ff1587be8f 100644 --- a/config/locales/simple_form.ie.yml +++ b/config/locales/simple_form.ie.yml @@ -47,6 +47,7 @@ ie: 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. @@ -67,15 +68,32 @@ 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 notification_emails: follow_request: Alqui petit sequer te diff --git a/config/locales/simple_form.lad.yml b/config/locales/simple_form.lad.yml index 33fe877db1..e0b8eab876 100644 --- a/config/locales/simple_form.lad.yml +++ b/config/locales/simple_form.lad.yml @@ -168,14 +168,18 @@ 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 new_password: Muevo kod note: Deskripsyon otp_attempt: Kodiche de dos pasos password: Kod + phrase: Biervo yave o fraza setting_default_language: Lingua de publikasyones setting_default_privacy: Privasita de publikasyones setting_display_media_default: Predeterminado @@ -247,6 +251,8 @@ lad: follow_request: Alguno tiene solisitado segirte mention: Alguno te enmento reblog: Alguno repartajo tu publikasyon + software_updates: + label: Mueva version de Mastodon esta desponivle rule: text: Regla tag: @@ -259,8 +265,11 @@ lad: 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: From 87e2bd02ac3accd198f43d30723bc3027fb4bffd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 21 Dec 2023 10:13:10 +0100 Subject: [PATCH 14/31] Update dependency irb to v1.11.0 (#28442) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 15bdb9b4f8..cc262f8e1a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) From c753b1ad35ae84c21c0474bdb052833b2bd4463a Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 21 Dec 2023 04:18:38 -0500 Subject: [PATCH 15/31] Clean up of `RSpec/LetSetup` within `spec/models` (#28444) --- .rubocop_todo.yml | 3 --- spec/models/account_spec.rb | 1 + spec/models/canonical_email_block_spec.rb | 2 +- spec/models/user_spec.rb | 2 ++ 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 030f311101..bb2715f96e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -63,11 +63,8 @@ RSpec/LetSetup: - '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' 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/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/user_spec.rb b/spec/models/user_spec.rb index 9111fd7c73..ab5bd39b7b 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -422,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 @@ -430,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 From f32d672d2ffc33fc4b07a6e36f5d9dcbc3f26e4c Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 21 Dec 2023 04:28:41 -0500 Subject: [PATCH 16/31] Clean up of `RSpec/LetSetup` within `spec/controllers` (#28446) --- .rubocop_todo.yml | 5 ---- .../follower_accounts_controller_spec.rb | 7 +++++ .../following_accounts_controller_spec.rb | 7 +++++ ...authorized_applications_controller_spec.rb | 4 +++ .../oauth/tokens_controller_spec.rb | 4 +++ .../settings/imports_controller_spec.rb | 26 ++++++++++--------- 6 files changed, 36 insertions(+), 17 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index bb2715f96e..f834c55620 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -55,11 +55,6 @@ RSpec/LetSetup: - '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' 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" From 5976d3702ff067fb0ed972fbc5f01a0f46b01bd7 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 21 Dec 2023 10:44:09 +0100 Subject: [PATCH 17/31] Change "Follow" to "Follow back" and "Mutual" when appropriate in web UI (#28452) --- .../features/account/components/header.jsx | 20 +++++++++++++++---- app/javascript/mastodon/locales/en.json | 3 ++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/app/javascript/mastodon/features/account/components/header.jsx b/app/javascript/mastodon/features/account/components/header.jsx index 29b46cb43d..ab9526f66a 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,18 @@ const titleFromAccount = account => { return `${prefix} (@${acct})`; }; +const messageForFollowButton = relationship => { + 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 +267,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 +293,7 @@ class Header extends ImmutablePureComponent { } else if (account.getIn(['relationship', 'requested'])) { actionBtn =