Add notification policies and notification requests (#29366)

pull/2674/head
Eugen Rochko 2024-03-07 15:53:37 +01:00 committed by GitHub
parent 653ce43abe
commit 50b17f7e10
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
104 changed files with 1096 additions and 247 deletions

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
class Api::V1::Notifications::PoliciesController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:notifications' }, only: :show
before_action -> { doorkeeper_authorize! :write, :'write:notifications' }, only: :update
before_action :require_user!
before_action :set_policy
def show
render json: @policy, serializer: REST::NotificationPolicySerializer
end
def update
@policy.update!(resource_params)
render json: @policy, serializer: REST::NotificationPolicySerializer
end
private
def set_policy
@policy = NotificationPolicy.find_or_initialize_by(account: current_account)
with_read_replica do
@policy.summarize!
end
end
def resource_params
params.permit(
:filter_not_following,
:filter_not_followers,
:filter_new_accounts,
:filter_private_mentions
)
end
end

View File

@ -0,0 +1,75 @@
# frozen_string_literal: true
class Api::V1::Notifications::RequestsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:notifications' }, only: :index
before_action -> { doorkeeper_authorize! :write, :'write:notifications' }, except: :index
before_action :require_user!
before_action :set_request, except: :index
after_action :insert_pagination_headers, only: :index
def index
with_read_replica do
@requests = load_requests
@relationships = relationships
end
render json: @requests, each_serializer: REST::NotificationRequestSerializer, relationships: @relationships
end
def accept
AcceptNotificationRequestService.new.call(@request)
render_empty
end
def dismiss
@request.update!(dismissed: true)
render_empty
end
private
def load_requests
requests = NotificationRequest.where(account: current_account).where(dismissed: truthy_param?(:dismissed)).includes(:last_status, from_account: [:account_stat, :user]).to_a_paginated_by_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT),
params_slice(:max_id, :since_id, :min_id)
)
NotificationRequest.preload_cache_collection(requests) do |statuses|
cache_collection(statuses, Status)
end
end
def relationships
StatusRelationshipsPresenter.new(@requests.map(&:last_status), current_user&.account_id)
end
def set_request
@request = NotificationRequest.where(account: current_account).find(params[:id])
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def next_path
api_v1_notifications_requests_url pagination_params(max_id: pagination_max_id) unless @requests.empty?
end
def prev_path
api_v1_notifications_requests_url pagination_params(min_id: pagination_since_id) unless @requests.empty?
end
def pagination_max_id
@requests.last.id
end
def pagination_since_id
@requests.first.id
end
def pagination_params(core_params)
params.slice(:dismissed).permit(:dismissed).merge(core_params)
end
end

View File

@ -49,7 +49,8 @@ class Api::V1::NotificationsController < Api::BaseController
current_account.notifications.without_suspended.browserable(
types: Array(browserable_params[:types]),
exclude_types: Array(browserable_params[:exclude_types]),
from_account_id: browserable_params[:account_id]
from_account_id: browserable_params[:account_id],
include_filtered: truthy_param?(:include_filtered)
)
end
@ -78,10 +79,10 @@ class Api::V1::NotificationsController < Api::BaseController
end
def browserable_params
params.permit(:account_id, types: [], exclude_types: [])
params.permit(:account_id, :include_filtered, types: [], exclude_types: [])
end
def pagination_params(core_params)
params.slice(:limit, :account_id, :types, :exclude_types).permit(:limit, :account_id, types: [], exclude_types: []).merge(core_params)
params.slice(:limit, :account_id, :types, :exclude_types, :include_filtered).permit(:limit, :account_id, :include_filtered, types: [], exclude_types: []).merge(core_params)
end
end

View File

@ -15,10 +15,15 @@ module Account::Associations
has_many :favourites, inverse_of: :account, dependent: :destroy
has_many :bookmarks, inverse_of: :account, dependent: :destroy
has_many :mentions, inverse_of: :account, dependent: :destroy
has_many :notifications, inverse_of: :account, dependent: :destroy
has_many :conversations, class_name: 'AccountConversation', dependent: :destroy, inverse_of: :account
has_many :scheduled_statuses, inverse_of: :account, dependent: :destroy
# Notifications
has_many :notifications, inverse_of: :account, dependent: :destroy
has_one :notification_policy, inverse_of: :account, dependent: :destroy
has_many :notification_permissions, inverse_of: :account, dependent: :destroy
has_many :notification_requests, inverse_of: :account, dependent: :destroy
# Pinned statuses
has_many :status_pins, inverse_of: :account, dependent: :destroy
has_many :pinned_statuses, -> { reorder('status_pins.created_at DESC') }, through: :status_pins, class_name: 'Status', source: :status

View File

@ -12,6 +12,7 @@
# account_id :bigint(8) not null
# from_account_id :bigint(8) not null
# type :string
# filtered :boolean default(FALSE), not null
#
class Notification < ApplicationRecord
@ -89,7 +90,7 @@ class Notification < ApplicationRecord
end
class << self
def browserable(types: [], exclude_types: [], from_account_id: nil)
def browserable(types: [], exclude_types: [], from_account_id: nil, include_filtered: false)
requested_types = if types.empty?
TYPES
else
@ -99,6 +100,7 @@ class Notification < ApplicationRecord
requested_types -= exclude_types.map(&:to_sym)
all.tap do |scope|
scope.merge!(where(filtered: false)) unless include_filtered || from_account_id.present?
scope.merge!(where(from_account_id: from_account_id)) if from_account_id.present?
scope.merge!(where(type: requested_types)) unless requested_types.size == TYPES.size
end
@ -144,6 +146,8 @@ class Notification < ApplicationRecord
after_initialize :set_from_account
before_validation :set_from_account
after_destroy :remove_from_notification_request
private
def set_from_account
@ -158,4 +162,9 @@ class Notification < ApplicationRecord
self.from_account_id = activity&.id
end
end
def remove_from_notification_request
notification_request = NotificationRequest.find_by(account_id: account_id, from_account_id: from_account_id)
notification_request&.reconsider_existence!
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: notification_permissions
#
# id :bigint(8) not null, primary key
# account_id :bigint(8) not null
# from_account_id :bigint(8) not null
# created_at :datetime not null
# updated_at :datetime not null
#
class NotificationPermission < ApplicationRecord
belongs_to :account
belongs_to :from_account, class_name: 'Account'
end

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: notification_policies
#
# id :bigint(8) not null, primary key
# account_id :bigint(8) not null
# filter_not_following :boolean default(FALSE), not null
# filter_not_followers :boolean default(FALSE), not null
# filter_new_accounts :boolean default(FALSE), not null
# filter_private_mentions :boolean default(TRUE), not null
# created_at :datetime not null
# updated_at :datetime not null
#
class NotificationPolicy < ApplicationRecord
belongs_to :account
has_many :notification_requests, primary_key: :account_id, foreign_key: :account_id, dependent: nil, inverse_of: false
attr_reader :pending_requests_count, :pending_notifications_count
MAX_MEANINGFUL_COUNT = 100
def summarize!
@pending_requests_count = pending_notification_requests.first
@pending_notifications_count = pending_notification_requests.last
end
private
def pending_notification_requests
@pending_notification_requests ||= notification_requests.where(dismissed: false).limit(MAX_MEANINGFUL_COUNT).pick(Arel.sql('count(*), coalesce(sum(notifications_count), 0)::bigint'))
end
end

View File

@ -0,0 +1,53 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: notification_requests
#
# id :bigint(8) not null, primary key
# account_id :bigint(8) not null
# from_account_id :bigint(8) not null
# last_status_id :bigint(8) not null
# notifications_count :bigint(8) default(0), not null
# dismissed :boolean default(FALSE), not null
# created_at :datetime not null
# updated_at :datetime not null
#
class NotificationRequest < ApplicationRecord
include Paginable
MAX_MEANINGFUL_COUNT = 100
belongs_to :account
belongs_to :from_account, class_name: 'Account'
belongs_to :last_status, class_name: 'Status'
before_save :prepare_notifications_count
def self.preload_cache_collection(requests)
cached_statuses_by_id = yield(requests.filter_map(&:last_status)).index_by(&:id) # Call cache_collection in block
requests.each do |request|
request.last_status = cached_statuses_by_id[request.last_status_id] unless request.last_status_id.nil?
end
end
def reconsider_existence!
return if dismissed?
prepare_notifications_count
if notifications_count.positive?
save
else
destroy
end
end
private
def prepare_notifications_count
self.notifications_count = Notification.where(account: account, from_account: from_account).limit(MAX_MEANINGFUL_COUNT).count
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class REST::NotificationPolicySerializer < ActiveModel::Serializer
attributes :filter_not_following,
:filter_not_followers,
:filter_new_accounts,
:filter_private_mentions,
:summary
def summary
{
pending_requests_count: object.pending_requests_count.to_s,
pending_notifications_count: object.pending_notifications_count.to_s,
}
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class REST::NotificationRequestSerializer < ActiveModel::Serializer
attributes :id, :created_at, :updated_at, :notifications_count
belongs_to :from_account, key: :account, serializer: REST::AccountSerializer
belongs_to :last_status, serializer: REST::StatusSerializer
def id
object.id.to_s
end
def notifications_count
object.notifications_count.to_s
end
end

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
class AcceptNotificationRequestService < BaseService
def call(request)
NotificationPermission.create!(account: request.account, from_account: request.from_account)
UnfilterNotificationsWorker.perform_async(request.id)
end
end

View File

@ -11,126 +11,195 @@ class NotifyService < BaseService
status
).freeze
class DismissCondition
def initialize(notification)
@recipient = notification.account
@sender = notification.from_account
@notification = notification
end
def dismiss?
blocked = @recipient.unavailable?
blocked ||= from_self? && @notification.type != :poll
return blocked if message? && from_staff?
blocked ||= domain_blocking?
blocked ||= @recipient.blocking?(@sender)
blocked ||= @recipient.muting_notifications?(@sender)
blocked ||= conversation_muted?
blocked ||= blocked_mention? if message?
blocked
end
private
def blocked_mention?
FeedManager.instance.filter?(:mentions, @notification.target_status, @recipient)
end
def message?
@notification.type == :mention
end
def from_staff?
@sender.local? && @sender.user.present? && @sender.user_role&.overrides?(@recipient.user_role)
end
def from_self?
@recipient.id == @sender.id
end
def domain_blocking?
@recipient.domain_blocking?(@sender.domain) && !following_sender?
end
def conversation_muted?
@notification.target_status && @recipient.muting_conversation?(@notification.target_status.conversation)
end
def following_sender?
@recipient.following?(@sender)
end
end
class FilterCondition
NEW_ACCOUNT_THRESHOLD = 30.days.freeze
NEW_FOLLOWER_THRESHOLD = 3.days.freeze
def initialize(notification)
@notification = notification
@recipient = notification.account
@sender = notification.from_account
@policy = NotificationPolicy.find_or_initialize_by(account: @recipient)
end
def filter?
return false if override_for_sender?
from_limited? ||
filtered_by_not_following_policy? ||
filtered_by_not_followers_policy? ||
filtered_by_new_accounts_policy? ||
filtered_by_private_mentions_policy?
end
private
def filtered_by_not_following_policy?
@policy.filter_not_following? && not_following?
end
def filtered_by_not_followers_policy?
@policy.filter_not_followers? && not_follower?
end
def filtered_by_new_accounts_policy?
@policy.filter_new_accounts? && new_account?
end
def filtered_by_private_mentions_policy?
@policy.filter_private_mentions? && not_following? && private_mention_not_in_response?
end
def not_following?
!@recipient.following?(@sender)
end
def not_follower?
follow = Follow.find_by(account: @sender, target_account: @recipient)
follow.nil? || follow.created_at > NEW_FOLLOWER_THRESHOLD.ago
end
def new_account?
@sender.created_at > NEW_ACCOUNT_THRESHOLD.ago
end
def override_for_sender?
NotificationPermission.exists?(account: @recipient, from_account: @sender)
end
def from_limited?
@sender.silenced? && not_following?
end
def private_mention_not_in_response?
@notification.type == :mention && @notification.target_status.direct_visibility? && !response_to_recipient?
end
def response_to_recipient?
return false if @notification.target_status.in_reply_to_id.nil?
statuses_that_mention_sender.positive?
end
def statuses_that_mention_sender
Status.count_by_sql([<<-SQL.squish, id: @notification.target_status.in_reply_to_id, recipient_id: @recipient.id, sender_id: @sender.id, depth_limit: 100])
WITH RECURSIVE ancestors(id, in_reply_to_id, mention_id, path, depth) AS (
SELECT s.id, s.in_reply_to_id, m.id, ARRAY[s.id], 0
FROM statuses s
LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id
WHERE s.id = :id
UNION ALL
SELECT s.id, s.in_reply_to_id, m.id, st.path || s.id, st.depth + 1
FROM ancestors st
JOIN statuses s ON s.id = st.in_reply_to_id
LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id
WHERE st.mention_id IS NULL AND NOT s.id = ANY(path) AND st.depth < :depth_limit
)
SELECT COUNT(*)
FROM ancestors st
JOIN statuses s ON s.id = st.id
WHERE st.mention_id IS NOT NULL AND s.visibility = 3
SQL
end
end
def call(recipient, type, activity)
return if recipient.user.nil?
@recipient = recipient
@activity = activity
@notification = Notification.new(account: @recipient, type: type, activity: @activity)
return if recipient.user.nil? || blocked?
# For certain conditions we don't need to create a notification at all
return if dismiss?
@notification.filtered = filter?
@notification.save!
# It's possible the underlying activity has been deleted
# between the save call and now
return if @notification.activity.nil?
push_notification!
push_to_conversation! if direct_message?
send_email! if email_needed?
if @notification.filtered?
update_notification_request!
else
push_notification!
push_to_conversation! if direct_message?
send_email! if email_needed?
end
rescue ActiveRecord::RecordInvalid
nil
end
private
def blocked_mention?
FeedManager.instance.filter?(:mentions, @notification.mention.status, @recipient)
def dismiss?
DismissCondition.new(@notification).dismiss?
end
def following_sender?
return @following_sender if defined?(@following_sender)
@following_sender = @recipient.following?(@notification.from_account) || @recipient.requested?(@notification.from_account)
def filter?
FilterCondition.new(@notification).filter?
end
def optional_non_follower?
@recipient.user.settings['interactions.must_be_follower'] && !@notification.from_account.following?(@recipient)
end
def update_notification_request!
return unless @notification.type == :mention
def optional_non_following?
@recipient.user.settings['interactions.must_be_following'] && !following_sender?
end
def message?
@notification.type == :mention
end
def direct_message?
message? && @notification.target_status.direct_visibility?
end
# Returns true if the sender has been mentioned by the recipient up the thread
def response_to_recipient?
return false if @notification.target_status.in_reply_to_id.nil?
# Using an SQL CTE to avoid unneeded back-and-forth with SQL server in case of long threads
!Status.count_by_sql([<<-SQL.squish, id: @notification.target_status.in_reply_to_id, recipient_id: @recipient.id, sender_id: @notification.from_account.id, depth_limit: 100]).zero?
WITH RECURSIVE ancestors(id, in_reply_to_id, mention_id, path, depth) AS (
SELECT s.id, s.in_reply_to_id, m.id, ARRAY[s.id], 0
FROM statuses s
LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id
WHERE s.id = :id
UNION ALL
SELECT s.id, s.in_reply_to_id, m.id, st.path || s.id, st.depth + 1
FROM ancestors st
JOIN statuses s ON s.id = st.in_reply_to_id
LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id
WHERE st.mention_id IS NULL AND NOT s.id = ANY(path) AND st.depth < :depth_limit
)
SELECT COUNT(*)
FROM ancestors st
JOIN statuses s ON s.id = st.id
WHERE st.mention_id IS NOT NULL AND s.visibility = 3
SQL
end
def from_staff?
@notification.from_account.local? && @notification.from_account.user.present? && @notification.from_account.user_role&.overrides?(@recipient.user_role)
end
def optional_non_following_and_direct?
direct_message? &&
@recipient.user.settings['interactions.must_be_following_dm'] &&
!following_sender? &&
!response_to_recipient?
end
def hellbanned?
@notification.from_account.silenced? && !following_sender?
end
def from_self?
@recipient.id == @notification.from_account.id
end
def domain_blocking?
@recipient.domain_blocking?(@notification.from_account.domain) && !following_sender?
end
def blocked?
blocked = @recipient.unavailable?
blocked ||= from_self? && @notification.type != :poll
return blocked if message? && from_staff?
blocked ||= domain_blocking?
blocked ||= @recipient.blocking?(@notification.from_account)
blocked ||= @recipient.muting_notifications?(@notification.from_account)
blocked ||= hellbanned?
blocked ||= optional_non_follower?
blocked ||= optional_non_following?
blocked ||= optional_non_following_and_direct?
blocked ||= conversation_muted?
blocked ||= blocked_mention? if @notification.type == :mention
blocked
end
def conversation_muted?
if @notification.target_status
@recipient.muting_conversation?(@notification.target_status.conversation)
else
false
end
notification_request = NotificationRequest.find_or_initialize_by(account_id: @recipient.id, from_account_id: @notification.from_account_id)
notification_request.last_status_id = @notification.target_status.id
notification_request.save
end
def push_notification!
@ -150,6 +219,10 @@ class NotifyService < BaseService
AccountConversation.add_status(@recipient, @notification.target_status)
end
def direct_message?
@notification.type == :mention && @notification.target_status.direct_visibility?
end
def push_to_web_push_subscriptions!
::Web::PushNotificationWorker.push_bulk(web_push_subscriptions.select { |subscription| subscription.pushable?(@notification) }) { |subscription| [subscription.id, @notification.id] }
end

View File

@ -40,11 +40,3 @@
label_method: ->(setting) { I18n.t("simple_form.labels.notification_emails.software_updates.#{setting}") },
label: I18n.t('simple_form.labels.notification_emails.software_updates.label'),
wrapper: :with_label
%h4= t 'notifications.other_settings'
.fields-group
= f.simple_fields_for :settings, current_user.settings do |ff|
= ff.input :'interactions.must_be_follower', wrapper: :with_label, label: I18n.t('simple_form.labels.interactions.must_be_follower')
= ff.input :'interactions.must_be_following', wrapper: :with_label, label: I18n.t('simple_form.labels.interactions.must_be_following')
= ff.input :'interactions.must_be_following_dm', wrapper: :with_label, label: I18n.t('simple_form.labels.interactions.must_be_following_dm')

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
class UnfilterNotificationsWorker
include Sidekiq::Worker
def perform(notification_request_id)
@notification_request = NotificationRequest.find(notification_request_id)
push_to_conversations!
unfilter_notifications!
remove_request!
rescue ActiveRecord::RecordNotFound
true
end
private
def push_to_conversations!
notifications_with_private_mentions.find_each { |notification| AccountConversation.add_status(@notification_request.account, notification.target_status) }
end
def unfilter_notifications!
filtered_notifications.in_batches.update_all(filtered: false)
end
def remove_request!
@notification_request.destroy!
end
def filtered_notifications
Notification.where(account: @notification_request.account, from_account: @notification_request.from_account, filtered: true)
end
def notifications_with_private_mentions
filtered_notifications.joins(mention: :status).merge(Status.where(visibility: :direct)).includes(mention: :status)
end
end

View File

@ -1288,7 +1288,6 @@ an:
notifications:
email_events: Eventos pa notificacions per correu electronico
email_events_hint: 'Tría los eventos pa los quals deseyas recibir notificacions:'
other_settings: Atros achustes de notificacions
number:
human:
decimal_units:

View File

@ -1594,7 +1594,6 @@ ar:
administration_emails: إشعارات البريد الإلكتروني الإدارية
email_events: الأحداث للإشعارات عبر البريد الإلكتروني
email_events_hint: 'اختر الأحداث التي تريد أن تصِلَك اشعارات عنها:'
other_settings: إعدادات أخرى للإشعارات
number:
human:
decimal_units:

View File

@ -695,7 +695,6 @@ ast:
notifications:
email_events: Unviu d'avisos per corréu electrónicu
email_events_hint: 'Seleiciona los eventos de los que quies recibir avisos:'
other_settings: Configuración d'otros avisos
number:
human:
decimal_units:

View File

@ -1547,7 +1547,6 @@ be:
administration_emails: Апавяшчэнні эл. пошты для адміністратара
email_events: Падзеі для апавяшчэнняў эл. пошты
email_events_hint: 'Выберыце падзеі, аб якіх вы хочаце атрымліваць апавяшчэнні:'
other_settings: Іншыя налады апавяшчэнняў
number:
human:
decimal_units:

View File

@ -1490,7 +1490,6 @@ bg:
administration_emails: Администраторски известия по имейла
email_events: Събития за известия по имейл
email_events_hint: 'Изберете събития, за които искате да получавате известия:'
other_settings: Настройки за други известия
number:
human:
decimal_units:

View File

@ -1495,7 +1495,6 @@ ca:
administration_emails: Notificacions per correu-e de l'Admin
email_events: Esdeveniments per a notificacions de correu electrònic
email_events_hint: 'Selecciona els esdeveniments per als quals vols rebre notificacions:'
other_settings: Altres opcions de notificació
number:
human:
decimal_units:

View File

@ -840,7 +840,6 @@ ckb:
notifications:
email_events: رووداوەکان بۆ ئاگاداری ئیمەیلی
email_events_hint: 'ئەو ڕووداوانە دیاریبکە کە دەتەوێت ئاگانامەکان وەربگری بۆ:'
other_settings: ڕێکبەندەکانی ئاگانامەکانی تر
otp_authentication:
code_hint: کۆدێک داخڵ بکە کە دروست کراوە لەلایەن ئەپی ڕەسەنایەتیەوە بۆ دڵنیابوون
description_html: ئەگەر تۆ <strong> هاتنەژوورەوەی دوو قۆناغی</strong> بە یارمەتی ئەپێکی پەسەندکردن چالاک بکەن، پێویستە بۆ چوونەژوورەوە ، بە تەلەفۆنەکەتان کە کۆدیکتان بۆ دروستدەکات دەستپێگەیشتنتان هەبێت.

View File

@ -809,7 +809,6 @@ co:
notifications:
email_events: Avvenimenti da nutificà cù l'e-mail
email_events_hint: 'Selezziunate l''avvenimenti per quelli vulete riceve nutificazione:'
other_settings: Altri parametri di nutificazione
number:
human:
decimal_units:

View File

@ -1547,7 +1547,6 @@ cs:
administration_emails: E-mailová oznámení administrátora
email_events: Události pro e-mailová oznámení
email_events_hint: 'Vyberte události, pro které chcete dostávat oznámení:'
other_settings: Další nastavení oznámení
number:
human:
decimal_units:

View File

@ -1599,7 +1599,6 @@ cy:
administration_emails: Hysbysiadau e-bost gweinyddol
email_events: Digwyddiadau ar gyfer hysbysiadau e-bost
email_events_hint: 'Dewiswch ddigwyddiadau yr ydych am dderbyn hysbysiadau ar eu cyfer:'
other_settings: Gosodiadau hysbysiadau arall
number:
human:
decimal_units:

View File

@ -1495,7 +1495,6 @@ da:
administration_emails: Admin e-mailnotifikationer
email_events: Begivenheder for e-mailnotifikationer
email_events_hint: 'Vælg begivenheder, for hvilke notifikationer skal modtages:'
other_settings: Andre notifikationsindstillinger
number:
human:
decimal_units:

View File

@ -1495,7 +1495,6 @@ de:
administration_emails: Admin-E-Mail-Benachrichtigungen
email_events: Benachrichtigungen per E-Mail
email_events_hint: 'Bitte die Ereignisse auswählen, für die du Benachrichtigungen per E-Mail erhalten möchtest:'
other_settings: Weitere Benachrichtigungseinstellungen
number:
human:
decimal_units:

View File

@ -1383,7 +1383,6 @@ el:
notifications:
email_events: Συμβάντα για ειδοποιήσεις μέσω email
email_events_hint: 'Επέλεξε συμβάντα για τα οποία θέλεις να λαμβάνεις ειδοποιήσεις μέσω email:'
other_settings: Άλλες ρυθμίσεις ειδοποιήσεων
number:
human:
decimal_units:

View File

@ -1490,7 +1490,6 @@ en-GB:
administration_emails: Admin e-mail notifications
email_events: Events for e-mail notifications
email_events_hint: 'Select events that you want to receive notifications for:'
other_settings: Other notifications settings
number:
human:
decimal_units:

View File

@ -1495,7 +1495,6 @@ en:
administration_emails: Admin e-mail notifications
email_events: Events for e-mail notifications
email_events_hint: 'Select events that you want to receive notifications for:'
other_settings: Other notifications settings
number:
human:
decimal_units:

View File

@ -1425,7 +1425,6 @@ eo:
administration_emails: Admin retpoŝtaj sciigoj
email_events: Eventoj por retpoŝtaj sciigoj
email_events_hint: 'Elekti la eventojn pri kioj vi volas ricevi sciigojn:'
other_settings: Aliaj agordoj de sciigoj
number:
human:
decimal_units:

View File

@ -1495,7 +1495,6 @@ es-AR:
administration_emails: Notificaciones de administración por correo electrónico
email_events: Eventos para notificaciones por correo electrónico
email_events_hint: 'Seleccioná los eventos para los que querés recibir notificaciones:'
other_settings: Configuración de otras notificaciones
number:
human:
decimal_units:

View File

@ -1495,7 +1495,6 @@ es-MX:
administration_emails: Notificaciones de administración por correo electrónico
email_events: Eventos para notificaciones por correo electrónico
email_events_hint: 'Selecciona los eventos para los que deseas recibir notificaciones:'
other_settings: Otros ajustes de notificaciones
number:
human:
decimal_units:

View File

@ -1495,7 +1495,6 @@ es:
administration_emails: Notificaciones de administración por correo electrónico
email_events: Eventos para notificaciones por correo electrónico
email_events_hint: 'Selecciona los eventos para los que deseas recibir notificaciones:'
other_settings: Otros ajustes de notificaciones
number:
human:
decimal_units:

View File

@ -1490,7 +1490,6 @@ et:
administration_emails: Admini e-postiteavitused
email_events: E-posti teadete sündmused
email_events_hint: 'Vali sündmused, mille kohta soovid teavitusi:'
other_settings: Muud teadete sätted
number:
human:
decimal_units:

View File

@ -1496,7 +1496,6 @@ eu:
administration_emails: Administratzailearen posta elektroniko bidezko jakinarazpenak
email_events: E-mail jakinarazpenentzako gertaerak
email_events_hint: 'Hautatu jaso nahi dituzun gertaeren jakinarazpenak:'
other_settings: Bezte jakinarazpen konfigurazioak
number:
human:
decimal_units:

View File

@ -1271,7 +1271,6 @@ fa:
notifications:
email_events: رویدادها برای آگاهی‌های رایانامه‌ای
email_events_hint: 'گزینش رویدادهایی که می‌خواهید برایشان آگاهی دریافت کنید:'
other_settings: سایر تنظیمات آگاهی‌ها
number:
human:
decimal_units:

View File

@ -1495,7 +1495,6 @@ fi:
administration_emails: Ylläpitäjän sähköposti-ilmoitukset
email_events: Sähköposti-ilmoitusten tapahtumat
email_events_hint: 'Valitse tapahtumat, joista haluat saada ilmoituksia:'
other_settings: Muut ilmoitusasetukset
number:
human:
decimal_units:

View File

@ -1495,7 +1495,6 @@ fo:
administration_emails: Fráboðanir um teldupost til umsitarar
email_events: Hendingar fyri teldupostfráboðanir
email_events_hint: 'Vel hendingar, sum tú vil hava fráboðanir um:'
other_settings: Aðrar fráboðanarstillingar
number:
human:
decimal_units:

View File

@ -1490,7 +1490,6 @@ fr-CA:
administration_emails: Notifications par e-mail de ladmin
email_events: Événements pour les notifications par courriel
email_events_hint: 'Sélectionnez les événements pour lesquels vous souhaitez recevoir des notifications :'
other_settings: Autres paramètres de notifications
number:
human:
decimal_units:

View File

@ -1490,7 +1490,6 @@ fr:
administration_emails: Notifications par e-mail de ladmin
email_events: Événements pour les notifications par courriel
email_events_hint: 'Sélectionnez les événements pour lesquels vous souhaitez recevoir des notifications :'
other_settings: Autres paramètres de notifications
number:
human:
decimal_units:

View File

@ -1490,7 +1490,6 @@ fy:
administration_emails: E-mailmeldingen behearder
email_events: E-mailmeldingen foar eveneminten
email_events_hint: 'Selektearje eveneminten wêrfoart jo meldingen ûntfange wolle:'
other_settings: Oare meldingsynstellingen
number:
human:
decimal_units:

View File

@ -1532,7 +1532,6 @@ gd:
administration_emails: Brathan puist-d na rianachd
email_events: Tachartasan nam brathan puist-d
email_events_hint: 'Tagh na tachartasan dhan a bheil thu airson brathan fhaighinn:'
other_settings: Roghainnean eile nam brathan
number:
human:
decimal_units:

View File

@ -1495,7 +1495,6 @@ gl:
administration_emails: Notificacións de Admin por correo electrónico
email_events: Eventos para os correos de notificación
email_events_hint: 'Escolle os eventos sobre os que queres recibir notificacións:'
other_settings: Outros axustes das notificacións
number:
human:
decimal_units:

View File

@ -1547,7 +1547,6 @@ he:
administration_emails: התראות לדוא"ל חשבון מנהל
email_events: ארועים להתראות דוא"ל
email_events_hint: 'בחר/י ארועים עבורים תרצה/י לקבל התראות:'
other_settings: הגדרות התראות אחרות
number:
human:
decimal_units:

View File

@ -1495,7 +1495,6 @@ hu:
administration_emails: Adminisztrátori e-mail-értesítések
email_events: Események email értesítésekhez
email_events_hint: 'Válaszd ki azokat az eseményeket, melyekről értesítést szeretnél:'
other_settings: Értesítések egyéb beállításai
number:
human:
decimal_units:

View File

@ -672,7 +672,6 @@ hy:
subject: "%{name}-ը փոխել է գրառումը"
notifications:
email_events_hint: Ընտրիր իրադարձութիւնները, որոնց վերաբերեալ ցանկանում ես ստանալ ծանուցումներ․
other_settings: Ծանուցումների այլ կարգաւորումներ
number:
human:
decimal_units:

View File

@ -1257,7 +1257,6 @@ id:
notifications:
email_events: Event untuk notifikasi email
email_events_hint: 'Pilih event yang ingin Anda terima notifikasinya:'
other_settings: Pengaturan notifikasi lain
number:
human:
decimal_units:

View File

@ -1495,7 +1495,6 @@ ie:
administration_emails: Email-notificationes pri administration
email_events: Evenimentes por email-notificationes
email_events_hint: 'Selecte li evenimentes pri queles tu vole reciver notificationes:'
other_settings: Parametres pri altri notificationes
number:
human:
decimal_units:

View File

@ -1472,7 +1472,6 @@ io:
administration_emails: Jerala retpostonotifiki
email_events: Eventi por retpostoavizi
email_events_hint: 'Selektez eventi quon vu volas ganar avizi:'
other_settings: Altra avizopcioni
number:
human:
decimal_units:

View File

@ -1499,7 +1499,6 @@ is:
administration_emails: Kerfisstjórnunartilkynningar í tölvupósti
email_events: Atburðir fyrir tilkynningar í tölvupósti
email_events_hint: 'Veldu þá atburði sem þú vilt fá tilkynningar í tölvupósti þegar þeir koma upp:'
other_settings: Aðrar stillingar varðandi tilkynningar
number:
human:
decimal_units:

View File

@ -1497,7 +1497,6 @@ it:
administration_emails: Notifiche email amministratore
email_events: Eventi per notifiche via email
email_events_hint: 'Seleziona gli eventi per i quali vuoi ricevere le notifiche:'
other_settings: Altre impostazioni delle notifiche
number:
human:
decimal_units:

View File

@ -1469,7 +1469,6 @@ ja:
administration_emails: 管理にかかわるメール通知
email_events: メールによる通知
email_events_hint: '受信する通知を選択:'
other_settings: その他の通知設定
number:
human:
decimal_units:

View File

@ -626,8 +626,6 @@ kab:
subject: Yuder-ik·ikem-id %{name}
reblog:
subject: "%{name} yesselha addad-ik·im"
notifications:
other_settings: Iɣewwaṛen nniḍen n yilɣa
number:
human:
decimal_units:

View File

@ -539,7 +539,6 @@ kk:
notifications:
email_events: E-mail ескертпелеріне шаралар
email_events_hint: 'Ескертпе болып келетін шараларды таңда:'
other_settings: Ескертпелердің басқа баптаулары
number:
human:
decimal_units:

View File

@ -1471,7 +1471,6 @@ ko:
administration_emails: 관리자 이메일 알림
email_events: 이메일 알림에 대한 이벤트
email_events_hint: '알림 받을 이벤트를 선택해주세요:'
other_settings: 기타 알림 설정
number:
human:
decimal_units:

View File

@ -1285,7 +1285,6 @@ ku:
notifications:
email_events: Bûyer bo agahdariyên e-nameyê
email_events_hint: 'Bûyera ku tu dixwazî agahdariyan jê wergerî hilbijêre:'
other_settings: Sazkariya agahdariyên din
number:
human:
decimal_units:

View File

@ -1495,7 +1495,6 @@ lad:
administration_emails: Avizos de administrasyon por posta
email_events: Evenimyentos para avizos por posta
email_events_hint: 'Eskoje los evenimientos para los kualos keres risivir avizos:'
other_settings: Otras preferensyas de avizos
number:
human:
decimal_units:

View File

@ -1513,7 +1513,6 @@ lv:
administration_emails: Administrators e-pasta paziņojumi
email_events: E-pasta paziņojumu notikumi
email_events_hint: 'Atlasi notikumus, par kuriem vēlies saņemt paziņojumus:'
other_settings: Citu paziņojumu iestatījumi
number:
human:
decimal_units:

View File

@ -1442,7 +1442,6 @@ ms:
administration_emails: Notifikasi e-mel pentadbir
email_events: Acara untuk pemberitahuan e-mel
email_events_hint: 'Pilih acara yang ingin anda terima pemberitahuan:'
other_settings: Tetapan notifikasi lain
number:
human:
decimal_units:

View File

@ -1445,7 +1445,6 @@ my:
administration_emails: စီမံခန့်ခွဲသူ အီးမေးလ် အသိပေးချက်များ
email_events: အီးမေးလ်သတိပေးချက်များအတွက်အကြောင်းအရာများ
email_events_hint: အသိပေးချက်များရယူမည့် အစီအစဉ်များကို ရွေးပါ -
other_settings: အခြားအသိပေးချက်များ၏ သတ်မှတ်ချက်များ
number:
human:
decimal_units: