Refactor Web::PushSubscription, remove welcome message (#4524)
* Refactor Web::PushSubscription, remove welcome message * Add missing helper * Use locale of the receiver on push notifications (#4519) * Remove unused translations * Fix dir on notifications
This commit is contained in:
parent
504737e860
commit
74437c6bff
|
@ -31,8 +31,8 @@ const notify = options =>
|
||||||
const group = cloneNotification(notifications[0]);
|
const group = cloneNotification(notifications[0]);
|
||||||
|
|
||||||
group.title = formatGroupTitle(group.data.message, group.data.count + 1);
|
group.title = formatGroupTitle(group.data.message, group.data.count + 1);
|
||||||
group.body = `${options.title}\n${group.body}`;
|
group.body = `${options.title}\n${group.body}`;
|
||||||
group.data = { ...group.data, count: group.data.count + 1 };
|
group.data = { ...group.data, count: group.data.count + 1 };
|
||||||
|
|
||||||
return self.registration.showNotification(group.title, group);
|
return self.registration.showNotification(group.title, group);
|
||||||
}
|
}
|
||||||
|
@ -43,18 +43,18 @@ const notify = options =>
|
||||||
const handlePush = (event) => {
|
const handlePush = (event) => {
|
||||||
const options = event.data.json();
|
const options = event.data.json();
|
||||||
|
|
||||||
options.body = options.data.nsfw || options.data.content;
|
options.body = options.data.nsfw || options.data.content;
|
||||||
options.image = options.image || undefined; // Null results in a network request (404)
|
options.dir = options.data.dir;
|
||||||
|
options.image = options.image || undefined; // Null results in a network request (404)
|
||||||
options.timestamp = options.timestamp && new Date(options.timestamp);
|
options.timestamp = options.timestamp && new Date(options.timestamp);
|
||||||
|
|
||||||
const expandAction = options.data.actions.find(action => action.todo === 'expand');
|
const expandAction = options.data.actions.find(action => action.todo === 'expand');
|
||||||
|
|
||||||
if (expandAction) {
|
if (expandAction) {
|
||||||
options.actions = [expandAction];
|
options.actions = [expandAction];
|
||||||
options.hiddenActions = options.data.actions.filter(action => action !== expandAction);
|
options.hiddenActions = options.data.actions.filter(action => action !== expandAction);
|
||||||
|
|
||||||
options.data.hiddenImage = options.image;
|
options.data.hiddenImage = options.image;
|
||||||
options.image = undefined;
|
options.image = undefined;
|
||||||
} else {
|
} else {
|
||||||
options.actions = options.data.actions;
|
options.actions = options.data.actions;
|
||||||
}
|
}
|
||||||
|
@ -75,8 +75,8 @@ const cloneNotification = (notification) => {
|
||||||
const expandNotification = (notification) => {
|
const expandNotification = (notification) => {
|
||||||
const nextNotification = cloneNotification(notification);
|
const nextNotification = cloneNotification(notification);
|
||||||
|
|
||||||
nextNotification.body = notification.data.content;
|
nextNotification.body = notification.data.content;
|
||||||
nextNotification.image = notification.data.hiddenImage;
|
nextNotification.image = notification.data.hiddenImage;
|
||||||
nextNotification.actions = notification.data.actions.filter(action => action.todo !== 'expand');
|
nextNotification.actions = notification.data.actions.filter(action => action.todo !== 'expand');
|
||||||
|
|
||||||
return self.registration.showNotification(nextNotification.title, nextNotification);
|
return self.registration.showNotification(nextNotification.title, nextNotification);
|
||||||
|
@ -105,8 +105,7 @@ const openUrl = url =>
|
||||||
const webClients = clientList.filter(client => /\/web\//.test(client.url));
|
const webClients = clientList.filter(client => /\/web\//.test(client.url));
|
||||||
|
|
||||||
if (webClients.length !== 0) {
|
if (webClients.length !== 0) {
|
||||||
const client = findBestClient(webClients);
|
const client = findBestClient(webClients);
|
||||||
|
|
||||||
const { pathname } = new URL(url);
|
const { pathname } = new URL(url);
|
||||||
|
|
||||||
if (pathname.startsWith('/web/')) {
|
if (pathname.startsWith('/web/')) {
|
||||||
|
@ -126,8 +125,7 @@ const openUrl = url =>
|
||||||
});
|
});
|
||||||
|
|
||||||
const removeActionFromNotification = (notification, action) => {
|
const removeActionFromNotification = (notification, action) => {
|
||||||
const actions = notification.actions.filter(act => act.action !== action.action);
|
const actions = notification.actions.filter(act => act.action !== action.action);
|
||||||
|
|
||||||
const nextNotification = cloneNotification(notification);
|
const nextNotification = cloneNotification(notification);
|
||||||
|
|
||||||
nextNotification.actions = actions;
|
nextNotification.actions = actions;
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
class SessionActivation < ApplicationRecord
|
class SessionActivation < ApplicationRecord
|
||||||
|
belongs_to :user, inverse_of: :session_activations, required: true
|
||||||
belongs_to :access_token, class_name: 'Doorkeeper::AccessToken', dependent: :destroy
|
belongs_to :access_token, class_name: 'Doorkeeper::AccessToken', dependent: :destroy
|
||||||
belongs_to :web_push_subscription, class_name: 'Web::PushSubscription', dependent: :destroy
|
belongs_to :web_push_subscription, class_name: 'Web::PushSubscription', dependent: :destroy
|
||||||
|
|
||||||
|
|
|
@ -13,59 +13,14 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
require 'webpush'
|
require 'webpush'
|
||||||
require_relative '../../models/setting'
|
|
||||||
|
|
||||||
class Web::PushSubscription < ApplicationRecord
|
class Web::PushSubscription < ApplicationRecord
|
||||||
include RoutingHelper
|
|
||||||
include StreamEntriesHelper
|
|
||||||
include ActionView::Helpers::TranslationHelper
|
|
||||||
include ActionView::Helpers::SanitizeHelper
|
|
||||||
|
|
||||||
has_one :session_activation
|
has_one :session_activation
|
||||||
|
|
||||||
before_create :send_welcome_notification
|
|
||||||
|
|
||||||
def push(notification)
|
def push(notification)
|
||||||
name = display_name notification.from_account
|
I18n.with_locale(session_activation.user.locale || I18n.default_locale) do
|
||||||
title = title_str(name, notification)
|
push_payload(message_from(notification), 48.hours.seconds)
|
||||||
body = body_str notification
|
end
|
||||||
dir = dir_str body
|
|
||||||
url = url_str notification
|
|
||||||
image = image_str notification
|
|
||||||
actions = actions_arr notification
|
|
||||||
|
|
||||||
access_token = actions.empty? ? nil : find_or_create_access_token(notification).token
|
|
||||||
nsfw = notification.target_status.nil? || notification.target_status.spoiler_text.empty? ? nil : notification.target_status.spoiler_text
|
|
||||||
|
|
||||||
# TODO: Make sure that the payload does not exceed 4KB - Webpush::PayloadTooLarge
|
|
||||||
Webpush.payload_send(
|
|
||||||
message: JSON.generate(
|
|
||||||
title: title,
|
|
||||||
dir: dir,
|
|
||||||
image: image,
|
|
||||||
badge: full_asset_url('badge.png', skip_pipeline: true),
|
|
||||||
tag: notification.id,
|
|
||||||
timestamp: notification.created_at,
|
|
||||||
icon: notification.from_account.avatar_static_url,
|
|
||||||
data: {
|
|
||||||
content: decoder.decode(strip_tags(body)),
|
|
||||||
nsfw: nsfw.nil? ? nil : decoder.decode(strip_tags(nsfw)),
|
|
||||||
url: url,
|
|
||||||
actions: actions,
|
|
||||||
access_token: access_token,
|
|
||||||
message: translate('push_notifications.group.title'), # Do not pass count, will be formatted in the ServiceWorker
|
|
||||||
}
|
|
||||||
),
|
|
||||||
endpoint: endpoint,
|
|
||||||
p256dh: key_p256dh,
|
|
||||||
auth: key_auth,
|
|
||||||
vapid: {
|
|
||||||
subject: "mailto:#{Setting.site_contact_email}",
|
|
||||||
private_key: Rails.configuration.x.vapid_private_key,
|
|
||||||
public_key: Rails.configuration.x.vapid_public_key,
|
|
||||||
},
|
|
||||||
ttl: 40 * 60 * 60 # 48 hours
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def pushable?(notification)
|
def pushable?(notification)
|
||||||
|
@ -73,120 +28,47 @@ class Web::PushSubscription < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def as_payload
|
def as_payload
|
||||||
payload = {
|
payload = { id: id, endpoint: endpoint }
|
||||||
id: id,
|
|
||||||
endpoint: endpoint,
|
|
||||||
}
|
|
||||||
|
|
||||||
payload[:alerts] = data['alerts'] if data && data.key?('alerts')
|
payload[:alerts] = data['alerts'] if data && data.key?('alerts')
|
||||||
|
|
||||||
payload
|
payload
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def access_token
|
||||||
|
find_or_create_access_token.token
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def title_str(name, notification)
|
def push_payload(message, ttl = 5.minutes.seconds)
|
||||||
case notification.type
|
# TODO: Make sure that the payload does not
|
||||||
when :mention then translate('push_notifications.mention.title', name: name)
|
# exceed 4KB - Webpush::PayloadTooLarge
|
||||||
when :follow then translate('push_notifications.follow.title', name: name)
|
|
||||||
when :favourite then translate('push_notifications.favourite.title', name: name)
|
|
||||||
when :reblog then translate('push_notifications.reblog.title', name: name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def body_str(notification)
|
|
||||||
case notification.type
|
|
||||||
when :mention then notification.target_status.text
|
|
||||||
when :follow then notification.from_account.note
|
|
||||||
when :favourite then notification.target_status.text
|
|
||||||
when :reblog then notification.target_status.text
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def url_str(notification)
|
|
||||||
case notification.type
|
|
||||||
when :mention then web_url("statuses/#{notification.target_status.id}")
|
|
||||||
when :follow then web_url("accounts/#{notification.from_account.id}")
|
|
||||||
when :favourite then web_url("statuses/#{notification.target_status.id}")
|
|
||||||
when :reblog then web_url("statuses/#{notification.target_status.id}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def actions_arr(notification)
|
|
||||||
actions =
|
|
||||||
case notification.type
|
|
||||||
when :mention then [
|
|
||||||
{
|
|
||||||
title: translate('push_notifications.mention.action_favourite'),
|
|
||||||
icon: full_asset_url('web-push-icon_favourite.png', skip_pipeline: true),
|
|
||||||
todo: 'request',
|
|
||||||
method: 'POST',
|
|
||||||
action: "/api/v1/statuses/#{notification.target_status.id}/favourite",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
else []
|
|
||||||
end
|
|
||||||
|
|
||||||
should_hide = notification.type.equal?(:mention) && !notification.target_status.nil? && (notification.target_status.sensitive || !notification.target_status.spoiler_text.empty?)
|
|
||||||
can_boost = notification.type.equal?(:mention) && !notification.target_status.nil? && !notification.target_status.hidden?
|
|
||||||
|
|
||||||
if should_hide
|
|
||||||
actions.insert(0, title: translate('push_notifications.mention.action_expand'), icon: full_asset_url('web-push-icon_expand.png', skip_pipeline: true), todo: 'expand', action: 'expand')
|
|
||||||
end
|
|
||||||
|
|
||||||
if can_boost
|
|
||||||
actions << { title: translate('push_notifications.mention.action_boost'), icon: full_asset_url('web-push-icon_reblog.png', skip_pipeline: true), todo: 'request', method: 'POST', action: "/api/v1/statuses/#{notification.target_status.id}/reblog" }
|
|
||||||
end
|
|
||||||
|
|
||||||
actions
|
|
||||||
end
|
|
||||||
|
|
||||||
def image_str(notification)
|
|
||||||
return nil if notification.target_status.nil? || notification.target_status.media_attachments.empty?
|
|
||||||
|
|
||||||
full_asset_url(notification.target_status.media_attachments.first.file.url(:small))
|
|
||||||
end
|
|
||||||
|
|
||||||
def dir_str(body)
|
|
||||||
rtl?(body) ? 'rtl' : 'ltr'
|
|
||||||
end
|
|
||||||
|
|
||||||
def send_welcome_notification
|
|
||||||
Webpush.payload_send(
|
Webpush.payload_send(
|
||||||
message: JSON.generate(
|
message: Oj.dump(message),
|
||||||
title: translate('push_notifications.subscribed.title'),
|
|
||||||
icon: full_asset_url('android-chrome-192x192.png', skip_pipeline: true),
|
|
||||||
badge: full_asset_url('badge.png', skip_pipeline: true),
|
|
||||||
data: {
|
|
||||||
content: translate('push_notifications.subscribed.body'),
|
|
||||||
actions: [],
|
|
||||||
url: web_url('notifications'),
|
|
||||||
message: translate('push_notifications.group.title'), # Do not pass count, will be formatted in the ServiceWorker
|
|
||||||
}
|
|
||||||
),
|
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
p256dh: key_p256dh,
|
p256dh: key_p256dh,
|
||||||
auth: key_auth,
|
auth: key_auth,
|
||||||
|
ttl: ttl,
|
||||||
vapid: {
|
vapid: {
|
||||||
subject: "mailto:#{Setting.site_contact_email}",
|
subject: "mailto:#{Setting.site_contact_email}",
|
||||||
private_key: Rails.configuration.x.vapid_private_key,
|
private_key: Rails.configuration.x.vapid_private_key,
|
||||||
public_key: Rails.configuration.x.vapid_public_key,
|
public_key: Rails.configuration.x.vapid_public_key,
|
||||||
},
|
}
|
||||||
ttl: 5 * 60 # 5 minutes
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_or_create_access_token(notification)
|
def message_from(notification)
|
||||||
|
serializable_resource = ActiveModelSerializers::SerializableResource.new(notification, serializer: Web::NotificationSerializer, scope: self, scope_name: :current_push_subscription)
|
||||||
|
serializable_resource.as_json
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_or_create_access_token
|
||||||
Doorkeeper::AccessToken.find_or_create_for(
|
Doorkeeper::AccessToken.find_or_create_for(
|
||||||
Doorkeeper::Application.find_by(superapp: true),
|
Doorkeeper::Application.find_by(superapp: true),
|
||||||
notification.account.user.id,
|
session_activation.user_id,
|
||||||
Doorkeeper::OAuth::Scopes.from_string('read write follow'),
|
Doorkeeper::OAuth::Scopes.from_string('read write follow'),
|
||||||
Doorkeeper.configuration.access_token_expires_in,
|
Doorkeeper.configuration.access_token_expires_in,
|
||||||
Doorkeeper.configuration.refresh_token_enabled?
|
Doorkeeper.configuration.refresh_token_enabled?
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def decoder
|
|
||||||
@decoder ||= HTMLEntities.new
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Web::NotificationSerializer < ActiveModel::Serializer
|
||||||
|
include StreamEntriesHelper
|
||||||
|
|
||||||
|
class DataSerializer < ActiveModel::Serializer
|
||||||
|
include RoutingHelper
|
||||||
|
include StreamEntriesHelper
|
||||||
|
include ActionView::Helpers::SanitizeHelper
|
||||||
|
|
||||||
|
attributes :content, :nsfw, :url, :actions,
|
||||||
|
:access_token, :message
|
||||||
|
|
||||||
|
def content
|
||||||
|
decoder.decode(strip_tags(body))
|
||||||
|
end
|
||||||
|
|
||||||
|
def dir
|
||||||
|
rtl?(body) ? 'rtl' : 'ltr'
|
||||||
|
end
|
||||||
|
|
||||||
|
def nsfw
|
||||||
|
return if object.target_status.nil?
|
||||||
|
object.target_status.spoiler_text.presence
|
||||||
|
end
|
||||||
|
|
||||||
|
def url
|
||||||
|
case object.type
|
||||||
|
when :mention
|
||||||
|
web_url("statuses/#{object.target_status.id}")
|
||||||
|
when :follow
|
||||||
|
web_url("accounts/#{object.from_account.id}")
|
||||||
|
when :favourite
|
||||||
|
web_url("statuses/#{object.target_status.id}")
|
||||||
|
when :reblog
|
||||||
|
web_url("statuses/#{object.target_status.id}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def actions
|
||||||
|
return @actions if defined?(@actions)
|
||||||
|
|
||||||
|
@actions = []
|
||||||
|
|
||||||
|
if object.type == :mention
|
||||||
|
@actions << expand_action if collapsed?
|
||||||
|
@actions << favourite_action
|
||||||
|
@actions << reblog_action if rebloggable?
|
||||||
|
end
|
||||||
|
|
||||||
|
@actions
|
||||||
|
end
|
||||||
|
|
||||||
|
def access_token
|
||||||
|
return if actions.empty?
|
||||||
|
current_push_subscription.access_token
|
||||||
|
end
|
||||||
|
|
||||||
|
def message
|
||||||
|
I18n.t('push_notifications.group.title')
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def body
|
||||||
|
case object.type
|
||||||
|
when :mention
|
||||||
|
object.target_status.text
|
||||||
|
when :follow
|
||||||
|
object.from_account.note
|
||||||
|
when :favourite
|
||||||
|
object.target_status.text
|
||||||
|
when :reblog
|
||||||
|
object.target_status.text
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def decoder
|
||||||
|
@decoder ||= HTMLEntities.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def expand_action
|
||||||
|
{
|
||||||
|
title: I18n.t('push_notifications.mention.action_expand'),
|
||||||
|
icon: full_asset_url('web-push-icon_expand.png', skip_pipeline: true),
|
||||||
|
todo: 'expand',
|
||||||
|
action: 'expand',
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def favourite_action
|
||||||
|
{
|
||||||
|
title: I18n.t('push_notifications.mention.action_favourite'),
|
||||||
|
icon: full_asset_url('web-push-icon_favourite.png', skip_pipeline: true),
|
||||||
|
todo: 'request',
|
||||||
|
method: 'POST',
|
||||||
|
action: "/api/v1/statuses/#{object.target_status.id}/favourite",
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def reblog_action
|
||||||
|
{
|
||||||
|
title: I18n.t('push_notifications.mention.action_boost'),
|
||||||
|
icon: full_asset_url('web-push-icon_reblog.png', skip_pipeline: true),
|
||||||
|
todo: 'request',
|
||||||
|
method: 'POST',
|
||||||
|
action: "/api/v1/statuses/#{object.target_status.id}/reblog",
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def collapsed?
|
||||||
|
!object.target_status.nil? && (object.target_status.sensitive? || object.target_status.spoiler_text.present?)
|
||||||
|
end
|
||||||
|
|
||||||
|
def rebloggable?
|
||||||
|
!object.target_status.nil? && !object.target_status.hidden?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
attributes :title, :dir, :image, :badge, :tag,
|
||||||
|
:timestamp, :icon
|
||||||
|
|
||||||
|
has_one :data
|
||||||
|
|
||||||
|
def title
|
||||||
|
case object.type
|
||||||
|
when :mention
|
||||||
|
I18n.t('push_notifications.mention.title', name: name)
|
||||||
|
when :follow
|
||||||
|
I18n.t('push_notifications.follow.title', name: name)
|
||||||
|
when :favourite
|
||||||
|
I18n.t('push_notifications.favourite.title', name: name)
|
||||||
|
when :reblog
|
||||||
|
I18n.t('push_notifications.reblog.title', name: name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def image
|
||||||
|
return if object.target_status.nil? || object.target_status.media_attachments.empty?
|
||||||
|
full_asset_url(object.target_status.media_attachments.first.file.url(:small))
|
||||||
|
end
|
||||||
|
|
||||||
|
def badge
|
||||||
|
full_asset_url('badge.png', skip_pipeline: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def tag
|
||||||
|
object.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def timestamp
|
||||||
|
object.created_at
|
||||||
|
end
|
||||||
|
|
||||||
|
def icon
|
||||||
|
object.from_account.avatar_static_url
|
||||||
|
end
|
||||||
|
|
||||||
|
def data
|
||||||
|
object
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def name
|
||||||
|
display_name(object.from_account)
|
||||||
|
end
|
||||||
|
end
|
File diff suppressed because one or more lines are too long
|
@ -382,9 +382,6 @@ en:
|
||||||
title: "%{name} mentioned you"
|
title: "%{name} mentioned you"
|
||||||
reblog:
|
reblog:
|
||||||
title: "%{name} boosted your status"
|
title: "%{name} boosted your status"
|
||||||
subscribed:
|
|
||||||
body: You can now receive push notifications.
|
|
||||||
title: Subscription registered!
|
|
||||||
remote_follow:
|
remote_follow:
|
||||||
acct: Enter your username@domain you want to follow from
|
acct: Enter your username@domain you want to follow from
|
||||||
missing_resource: Could not find the required redirect URL for your account
|
missing_resource: Could not find the required redirect URL for your account
|
||||||
|
|
|
@ -357,9 +357,6 @@ fa:
|
||||||
title: "%{name} از شما نام برد"
|
title: "%{name} از شما نام برد"
|
||||||
reblog:
|
reblog:
|
||||||
title: "%{name} نوشتهٔ شما را بازبوقید"
|
title: "%{name} نوشتهٔ شما را بازبوقید"
|
||||||
subscribed:
|
|
||||||
body: از این به بعد سرور میتواندبه شما اعلانهای تازه بفرستد .
|
|
||||||
title: عضویت ثبت شد!
|
|
||||||
remote_follow:
|
remote_follow:
|
||||||
acct: نشانی حساب username@domain خود را اینجا بنویسید
|
acct: نشانی حساب username@domain خود را اینجا بنویسید
|
||||||
missing_resource: نشانی اینترنتی برای رسیدن به حساب شما پیدا نشد
|
missing_resource: نشانی اینترنتی برای رسیدن به حساب شما پیدا نشد
|
||||||
|
|
|
@ -353,9 +353,6 @@ fr:
|
||||||
title: "%{name} vous a mentionné·e"
|
title: "%{name} vous a mentionné·e"
|
||||||
reblog:
|
reblog:
|
||||||
title: "%{name} a partagé⋅e votre statut"
|
title: "%{name} a partagé⋅e votre statut"
|
||||||
subscribed:
|
|
||||||
body: Vous pouvez désormais recevoir des notifications push.
|
|
||||||
title: Abonnements aux notifications push
|
|
||||||
remote_follow:
|
remote_follow:
|
||||||
acct: Entrez votre pseudo@instance depuis lequel vous voulez suivre ce⋅tte utilisateur⋅rice
|
acct: Entrez votre pseudo@instance depuis lequel vous voulez suivre ce⋅tte utilisateur⋅rice
|
||||||
missing_resource: L’URL de redirection n’a pas pu être trouvée
|
missing_resource: L’URL de redirection n’a pas pu être trouvée
|
||||||
|
|
|
@ -381,9 +381,6 @@ ja:
|
||||||
title: "%{name} さんから返信がありました"
|
title: "%{name} さんから返信がありました"
|
||||||
reblog:
|
reblog:
|
||||||
title: あなたのトゥートが %{name} さんにブーストされました
|
title: あなたのトゥートが %{name} さんにブーストされました
|
||||||
subscribed:
|
|
||||||
body: あなたはプッシュ通知を受け取ることが出来ます
|
|
||||||
title: Subscription が登録されました
|
|
||||||
remote_follow:
|
remote_follow:
|
||||||
acct: あなたの ユーザー名@ドメイン を入力してください
|
acct: あなたの ユーザー名@ドメイン を入力してください
|
||||||
missing_resource: リダイレクト先が見つかりませんでした
|
missing_resource: リダイレクト先が見つかりませんでした
|
||||||
|
|
|
@ -353,9 +353,6 @@ nl:
|
||||||
title: "%{name} vermeldde jou"
|
title: "%{name} vermeldde jou"
|
||||||
reblog:
|
reblog:
|
||||||
title: "%{name} boostte jouw toot"
|
title: "%{name} boostte jouw toot"
|
||||||
subscribed:
|
|
||||||
body: Je kan nu pushmeldingen ontvangen.
|
|
||||||
title: Aanmelding bevestigd!
|
|
||||||
remote_follow:
|
remote_follow:
|
||||||
acct: Geef jouw account@domein.tld op waarvandaan je wilt volgen
|
acct: Geef jouw account@domein.tld op waarvandaan je wilt volgen
|
||||||
missing_resource: Kon vereiste doorverwijzings-URL voor jouw account niet vinden
|
missing_resource: Kon vereiste doorverwijzings-URL voor jouw account niet vinden
|
||||||
|
|
|
@ -448,9 +448,6 @@ oc:
|
||||||
title: "%{name} vos a mencionat"
|
title: "%{name} vos a mencionat"
|
||||||
reblog:
|
reblog:
|
||||||
title: "%{name} a partejat vòstre estatut"
|
title: "%{name} a partejat vòstre estatut"
|
||||||
subscribed:
|
|
||||||
body: Podètz ara recebre las notificacions push.
|
|
||||||
title: Abonament enregistrat !
|
|
||||||
remote_follow:
|
remote_follow:
|
||||||
acct: Picatz vòstre utilizaire@instància que cal utilizar per sègre aqueste utilizaire
|
acct: Picatz vòstre utilizaire@instància que cal utilizar per sègre aqueste utilizaire
|
||||||
missing_resource: URL de redireccion pas trobada
|
missing_resource: URL de redireccion pas trobada
|
||||||
|
|
|
@ -224,8 +224,6 @@ pl:
|
||||||
settings: 'Zmień ustawienia powiadamiania: %{link}'
|
settings: 'Zmień ustawienia powiadamiania: %{link}'
|
||||||
signature: Powiadomienie Mastodona z instancji %{instance}
|
signature: Powiadomienie Mastodona z instancji %{instance}
|
||||||
view: 'Zobacz:'
|
view: 'Zobacz:'
|
||||||
applications:
|
|
||||||
invalid_url: Ten URL jest nieprawidłowy
|
|
||||||
applications:
|
applications:
|
||||||
created: Pomyślnie utworzono aplikację
|
created: Pomyślnie utworzono aplikację
|
||||||
destroyed: Pomyślnie usunięto aplikację
|
destroyed: Pomyślnie usunięto aplikację
|
||||||
|
@ -375,9 +373,6 @@ pl:
|
||||||
title: "%{name} wspomniał o Tobie"
|
title: "%{name} wspomniał o Tobie"
|
||||||
reblog:
|
reblog:
|
||||||
title: "%{name} podbił Twój status"
|
title: "%{name} podbił Twój status"
|
||||||
subscribed:
|
|
||||||
body: Otrzymujesz teraz powiadomienia push.
|
|
||||||
title: Zarejestrowano subskrypcję!
|
|
||||||
remote_follow:
|
remote_follow:
|
||||||
acct: Podaj swój adres (nazwa@domena), z którego chcesz śledzić
|
acct: Podaj swój adres (nazwa@domena), z którego chcesz śledzić
|
||||||
missing_resource: Nie udało się znaleźć adresu przekierowania z Twojej domeny
|
missing_resource: Nie udało się znaleźć adresu przekierowania z Twojej domeny
|
||||||
|
|
|
@ -278,9 +278,6 @@ ru:
|
||||||
title: Вас упомянул(а) %{name}
|
title: Вас упомянул(а) %{name}
|
||||||
reblog:
|
reblog:
|
||||||
title: "%{name} продвинул(а) Ваш статус"
|
title: "%{name} продвинул(а) Ваш статус"
|
||||||
subscribed:
|
|
||||||
body: Теперь Вы можете получать push-уведомления.
|
|
||||||
title: Подписка зарегистрирована!
|
|
||||||
remote_follow:
|
remote_follow:
|
||||||
acct: Введите username@domain, откуда Вы хотите подписаться
|
acct: Введите username@domain, откуда Вы хотите подписаться
|
||||||
missing_resource: Поиск требуемого перенаправления URL для Вашего аккаунта завершился неудачей
|
missing_resource: Поиск требуемого перенаправления URL для Вашего аккаунта завершился неудачей
|
||||||
|
|
Loading…
Reference in New Issue