Merge branch 'main' into glitch-soc/merge-upstream

Conflicts:
- `app/lib/formatter.rb`:
  Upstream completely refactored the formatting code and removed that file,
  while glitch-soc had code for Markdown and HTML toots.
  Took upstream code, glitch-soc changes will be re-implemented on top of the
  refactored classes in a later commit.
- `app/models/status.rb`:
  Upstream refactored status edit handling and moved code to
  `app/models/concerns/status_snapshot_concern.rb`.
  Applied glitch-soc's changes to that file.
- `app/serializers/activitypub/note_serializer.rb`:
  Not really a conflict, just a line added too close to one modified by
  glitch-soc.
  Applied upstream changes while keeping the glitch-soc-modified one.
- `app/services/update_status_service.rb`:
  Not really a conflict, upstream modified a line adjacent to one added by
  glitch-soc.
  Applied upstream changes while keeping the glitch-soc line.
- `app/views/statuses/_simple_status.html.haml`:
  Upstream refactored formatting, glitch-soc changed the markup slightly.
  Applied upstream changes.
- `spec/lib/formatter_spec.rb`:
  Upstream completely refactored the formatting code and removed that file,
  while glitch-soc had code for Markdown and HTML toots.
  Took upstream code, glitch-soc changes will be re-implemented on top of the
  refactored classes in a later commit.
main
Claire 2022-03-26 19:18:55 +01:00
commit aaa9ec340b
103 changed files with 1586 additions and 1458 deletions

View File

@ -81,6 +81,7 @@ All notable changes to this project will be documented in this file.
- Add lazy loading for emoji picker in web UI ([mashirozx](https://github.com/mastodon/mastodon/pull/16907), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17011)) - Add lazy loading for emoji picker in web UI ([mashirozx](https://github.com/mastodon/mastodon/pull/16907), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17011))
- Add single option votes tooltip in polls in web UI ([Brawaru](https://github.com/mastodon/mastodon/pull/16849)) - Add single option votes tooltip in polls in web UI ([Brawaru](https://github.com/mastodon/mastodon/pull/16849))
- Add confirmation modal when closing media edit modal with unsaved changes in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16518)) - Add confirmation modal when closing media edit modal with unsaved changes in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16518))
- Add hint about missing media attachment description in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/17845))
- Add support for fetching Create and Announce activities by URI in ActivityPub ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16383)) - Add support for fetching Create and Announce activities by URI in ActivityPub ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16383))
- Add `S3_FORCE_SINGLE_REQUEST` environment variable ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16866)) - Add `S3_FORCE_SINGLE_REQUEST` environment variable ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16866))
- Add `OMNIAUTH_ONLY` environment variable ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17288), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17345)) - Add `OMNIAUTH_ONLY` environment variable ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17288), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17345))
@ -130,6 +131,11 @@ All notable changes to this project will be documented in this file.
### Fixed ### Fixed
- Fix IDN domains not being rendered correctly in a few left-over places ([Gargron](https://github.com/mastodon/mastodon/pull/17848))
- Fix Sanskrit translation not being used in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17820))
- Fix Kurdish languages having the wrong language codes ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17812))
- Fix pghero making database schema suggestions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17807))
- Fix encoding glitch in the OpenGraph description of a profile page ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17821))
- Fix web manifest not permitting PWA usage from alternate domains ([HolgerHuo](https://github.com/mastodon/mastodon/pull/16714)) - Fix web manifest not permitting PWA usage from alternate domains ([HolgerHuo](https://github.com/mastodon/mastodon/pull/16714))
- Fix not being able to edit media attachments for scheduled posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17690)) - Fix not being able to edit media attachments for scheduled posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17690))
- Fix subscribed relay activities being recorded as boosts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17571)) - Fix subscribed relay activities being recorded as boosts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17571))

View File

@ -57,7 +57,7 @@ class StatusesIndex < Chewy::Index
field :id, type: 'long' field :id, type: 'long'
field :account_id, type: 'long' field :account_id, type: 'long'
field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].concat(status.ordered_media_attachments.map(&:description)).concat(status.preloadable_poll ? status.preloadable_poll.options : []).join("\n\n") } do field :text, type: 'text', value: ->(status) { [status.spoiler_text, PlainTextFormatter.new(status.text, status.local?).to_s].concat(status.ordered_media_attachments.map(&:description)).concat(status.preloadable_poll ? status.preloadable_poll.options : []).join("\n\n") } do
field :stemmed, type: 'text', analyzer: 'content' field :stemmed, type: 'text', analyzer: 'content'
end end

View File

@ -3,6 +3,10 @@
class Api::V1::Trends::LinksController < Api::BaseController class Api::V1::Trends::LinksController < Api::BaseController
before_action :set_links before_action :set_links
after_action :insert_pagination_headers
DEFAULT_LINKS_LIMIT = 10
def index def index
render json: @links, each_serializer: REST::Trends::LinkSerializer render json: @links, each_serializer: REST::Trends::LinkSerializer
end end
@ -20,6 +24,26 @@ class Api::V1::Trends::LinksController < Api::BaseController
end end
def links_from_trends def links_from_trends
Trends.links.query.allowed.in_locale(content_locale).limit(limit_param(10)) Trends.links.query.allowed.in_locale(content_locale).offset(offset_param).limit(limit_param(DEFAULT_LINKS_LIMIT))
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def next_path
api_v1_trends_links_url pagination_params(offset: offset_param + limit_param(DEFAULT_LINKS_LIMIT))
end
def prev_path
api_v1_trends_links_url pagination_params(offset: offset_param - limit_param(DEFAULT_LINKS_LIMIT)) if offset_param > limit_param(DEFAULT_LINKS_LIMIT)
end
def offset_param
params[:offset].to_i
end end
end end

View File

@ -3,6 +3,8 @@
class Api::V1::Trends::StatusesController < Api::BaseController class Api::V1::Trends::StatusesController < Api::BaseController
before_action :set_statuses before_action :set_statuses
after_action :insert_pagination_headers
def index def index
render json: @statuses, each_serializer: REST::StatusSerializer render json: @statuses, each_serializer: REST::StatusSerializer
end end
@ -22,6 +24,26 @@ class Api::V1::Trends::StatusesController < Api::BaseController
def statuses_from_trends def statuses_from_trends
scope = Trends.statuses.query.allowed.in_locale(content_locale) scope = Trends.statuses.query.allowed.in_locale(content_locale)
scope = scope.filtered_for(current_account) if user_signed_in? scope = scope.filtered_for(current_account) if user_signed_in?
scope.limit(limit_param(DEFAULT_STATUSES_LIMIT)) scope.offset(offset_param).limit(limit_param(DEFAULT_STATUSES_LIMIT))
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def next_path
api_v1_trends_statuses_url pagination_params(offset: offset_param + limit_param(DEFAULT_STATUSES_LIMIT))
end
def prev_path
api_v1_trends_statuses_url pagination_params(offset: offset_param - limit_param(DEFAULT_STATUSES_LIMIT)) if offset_param > limit_param(DEFAULT_STATUSES_LIMIT)
end
def offset_param
params[:offset].to_i
end end
end end

View File

@ -3,6 +3,10 @@
class Api::V1::Trends::TagsController < Api::BaseController class Api::V1::Trends::TagsController < Api::BaseController
before_action :set_tags before_action :set_tags
after_action :insert_pagination_headers
DEFAULT_TAGS_LIMIT = 10
def index def index
render json: @tags, each_serializer: REST::TagSerializer render json: @tags, each_serializer: REST::TagSerializer
end end
@ -12,10 +16,30 @@ class Api::V1::Trends::TagsController < Api::BaseController
def set_tags def set_tags
@tags = begin @tags = begin
if Setting.trends if Setting.trends
Trends.tags.query.allowed.limit(limit_param(10)) Trends.tags.query.allowed.limit(limit_param(DEFAULT_TAGS_LIMIT))
else else
[] []
end end
end end
end end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def next_path
api_v1_trends_tags_url pagination_params(offset: offset_param + limit_param(DEFAULT_TAGS_LIMIT))
end
def prev_path
api_v1_trends_tags_url pagination_params(offset: offset_param - limit_param(DEFAULT_TAGS_LIMIT)) if offset_param > limit_param(DEFAULT_TAGS_LIMIT)
end
def offset_param
params[:offset].to_i
end
end end

View File

@ -15,7 +15,7 @@ class Api::Web::EmbedsController < Api::Web::BaseController
return not_found if oembed.nil? return not_found if oembed.nil?
begin begin
oembed[:html] = Formatter.instance.sanitize(oembed[:html], Sanitize::Config::MASTODON_OEMBED) oembed[:html] = Sanitize.fragment(oembed[:html], Sanitize::Config::MASTODON_OEMBED)
rescue ArgumentError rescue ArgumentError
return not_found return not_found
end end

View File

@ -2,10 +2,12 @@
module AccountsHelper module AccountsHelper
def display_name(account, **options) def display_name(account, **options)
str = account.display_name.presence || account.username
if options[:custom_emojify] if options[:custom_emojify]
Formatter.instance.format_display_name(account, **options) prerender_custom_emojis(h(str), account.emojis)
else else
account.display_name.presence || account.username str
end end
end end

View File

@ -12,9 +12,6 @@ module Admin::Trends::StatusesHelper
return '' if text.blank? return '' if text.blank?
html = Formatter.instance.send(:encode, text) prerender_custom_emojis(h(text), status.emojis)
html = Formatter.instance.send(:encode_custom_emojis, html, status.emojis, prefers_autoplay?)
html.html_safe # rubocop:disable Rails/OutputSafety
end end
end end

View File

@ -240,4 +240,8 @@ module ApplicationHelper
end end
end.values end.values
end end
def prerender_custom_emojis(html, custom_emojis)
EmojiFormatter.new(html, custom_emojis, animate: prefers_autoplay?).to_s
end
end end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
module FormattingHelper
def html_aware_format(text, local, options = {})
HtmlAwareFormatter.new(text, local, options).to_s
end
def linkify(text, options = {})
TextFormatter.new(text, options).to_s
end
def extract_plain_text(text, local)
PlainTextFormatter.new(text, local).to_s
end
def status_content_format(status)
html_aware_format(status.text, status.local?, preloaded_accounts: [status.account] + (status.respond_to?(:active_mentions) ? status.active_mentions.map(&:account) : []))
end
end

View File

@ -2,6 +2,7 @@
module RoutingHelper module RoutingHelper
extend ActiveSupport::Concern extend ActiveSupport::Concern
include Rails.application.routes.url_helpers include Rails.application.routes.url_helpers
include ActionView::Helpers::AssetTagHelper include ActionView::Helpers::AssetTagHelper
include Webpacker::Helper include Webpacker::Helper
@ -22,8 +23,6 @@ module RoutingHelper
full_asset_url(asset_pack_path(source, **options)) full_asset_url(asset_pack_path(source, **options))
end end
private
def use_storage? def use_storage?
Rails.configuration.x.use_s3 || Rails.configuration.x.use_swift Rails.configuration.x.use_s3 || Rails.configuration.x.use_swift
end end

View File

@ -113,20 +113,6 @@ module StatusesHelper
end end
end end
private
def simplified_text(text)
text.dup.tap do |new_text|
URI.extract(new_text).each do |url|
new_text.gsub!(url, '')
end
new_text.gsub!(Account::MENTION_RE, '')
new_text.gsub!(Tag::HASHTAG_RE, '')
new_text.gsub!(/\s+/, '')
end
end
def embedded_view? def embedded_view?
params[:controller] == EMBEDDED_CONTROLLER && params[:action] == EMBEDDED_ACTION params[:controller] == EMBEDDED_CONTROLLER && params[:action] == EMBEDDED_ACTION
end end

View File

@ -33,6 +33,7 @@ export default class Counter extends React.PureComponent {
label: PropTypes.string.isRequired, label: PropTypes.string.isRequired,
href: PropTypes.string, href: PropTypes.string,
params: PropTypes.object, params: PropTypes.object,
target: PropTypes.string,
}; };
state = { state = {
@ -54,7 +55,7 @@ export default class Counter extends React.PureComponent {
} }
render () { render () {
const { label, href } = this.props; const { label, href, target } = this.props;
const { loading, data } = this.state; const { loading, data } = this.state;
let content; let content;
@ -100,7 +101,7 @@ export default class Counter extends React.PureComponent {
if (href) { if (href) {
return ( return (
<a href={href} className='sparkline'> <a href={href} className='sparkline' target={target}>
{inner} {inner}
</a> </a>
); );

View File

@ -3,16 +3,16 @@
"account.add_or_remove_from_list": "افزودن یا برداشتن از سیاهه‌ها", "account.add_or_remove_from_list": "افزودن یا برداشتن از سیاهه‌ها",
"account.badges.bot": "روبات", "account.badges.bot": "روبات",
"account.badges.group": "گروه", "account.badges.group": "گروه",
"account.block": "مسدود کردن @{name}", "account.block": "مسدود کردن @{name}",
"account.block_domain": "مسدود کردن دامنهٔ {domain}", "account.block_domain": "مسدود کردن دامنهٔ {domain}",
"account.blocked": "مسدود", "account.blocked": "مسدود",
"account.browse_more_on_origin_server": "مرور بیش‌تر روی نمایهٔ اصلی", "account.browse_more_on_origin_server": "مرور بیش‌تر روی نمایهٔ اصلی",
"account.cancel_follow_request": "لغو درخواست پی‌گیری", "account.cancel_follow_request": "لغو درخواست پی‌گیری",
"account.direct": "پیام مستقیم به @{name}", "account.direct": "پیام مستقیم به @{name}",
"account.disable_notifications": "آگاهی به من هنگام فرستادن‌های @{name} پایان یابد", "account.disable_notifications": "آگاهی به من هنگام فرستادن‌های @{name} پایان یابد",
"account.domain_blocked": "دامنه مسدود شد", "account.domain_blocked": "دامنه مسدود شد",
"account.edit_profile": "ویرایش نمایه", "account.edit_profile": "ویرایش نمایه",
"account.enable_notifications": "هنگام فرسته‌های @{name} مرا آگاه کن", "account.enable_notifications": "هنگام فرسته‌های @{name} مرا آگاه کن",
"account.endorse": "معرّفی در نمایه", "account.endorse": "معرّفی در نمایه",
"account.follow": "پی‌گیری", "account.follow": "پی‌گیری",
"account.followers": "پی‌گیرندگان", "account.followers": "پی‌گیرندگان",
@ -22,34 +22,34 @@
"account.following_counter": "{count, plural, one {{counter} پی‌گرفته} other {{counter} پی‌گرفته}}", "account.following_counter": "{count, plural, one {{counter} پی‌گرفته} other {{counter} پی‌گرفته}}",
"account.follows.empty": "این کاربر هنوز پی‌گیر کسی نیست.", "account.follows.empty": "این کاربر هنوز پی‌گیر کسی نیست.",
"account.follows_you": "پی می‌گیردتان", "account.follows_you": "پی می‌گیردتان",
"account.hide_reblogs": "نهفتن تقویت‌های @{name}", "account.hide_reblogs": "نهفتن تقویت‌های @{name}",
"account.joined": "پیوسته از {date}", "account.joined": "پیوسته از {date}",
"account.link_verified_on": "مالکیت این پیوند در {date} بررسی شد", "account.link_verified_on": "مالکیت این پیوند در {date} بررسی شد",
"account.locked_info": "این حساب خصوصی است. صاحبش تصمیم می‌گیرد که چه کسی پی‌گیرش باشد.", "account.locked_info": "این حساب خصوصی است. صاحبش تصمیم می‌گیرد که چه کسی پی‌گیرش باشد.",
"account.media": "رسانه", "account.media": "رسانه",
"account.mention": "نام‌بردن از @{name}", "account.mention": "نام‌بردن از @{name}",
"account.moved_to": "{name} منتقل شده به:", "account.moved_to": "{name} منتقل شده به:",
"account.mute": "خموشاندن @{name}", "account.mute": "خموشاندن @{name}",
"account.mute_notifications": "خموشاندن آگاهی‌ها از @{name}", "account.mute_notifications": "خموشاندن آگاهی‌های @{name}",
"account.muted": "خموش", "account.muted": "خموش",
"account.posts": "فرسته", "account.posts": "فرسته",
"account.posts_with_replies": "فرسته‌ها و پاسخ‌ها", "account.posts_with_replies": "فرسته‌ها و پاسخ‌ها",
"account.report": "گزارش @{name}", "account.report": "گزارش @{name}",
"account.requested": "منتظر پذیرش است. برای لغو درخواست پی‌گیری کلیک کنید", "account.requested": "منتظر پذیرش است. برای لغو درخواست پی‌گیری کلیک کنید",
"account.share": "هم‌رسانی نمایهٔ @{name}", "account.share": "هم‌رسانی نمایهٔ @{name}",
"account.show_reblogs": "نمایش تقویت‌های @{name}", "account.show_reblogs": "نمایش تقویت‌های @{name}",
"account.statuses_counter": "{count, plural, one {{counter} فرسته} other {{counter} فرسته}}", "account.statuses_counter": "{count, plural, one {{counter} فرسته} other {{counter} فرسته}}",
"account.unblock": "رفع مسدودیت @{name}", "account.unblock": "رفع مسدودیت @{name}",
"account.unblock_domain": "رفع مسدودیت دامنهٔ {domain}", "account.unblock_domain": "رفع مسدودیت دامنهٔ {domain}",
"account.unblock_short": "رفع مسدودیت", "account.unblock_short": "رفع مسدودیت",
"account.unendorse": "معرّفی نکردن در نمایه", "account.unendorse": "معرّفی نکردن در نمایه",
"account.unfollow": "ناپی‌گیری", "account.unfollow": "ناپی‌گیری",
"account.unmute": "ناخموشی @{name}", "account.unmute": "ناخموشی @{name}",
"account.unmute_notifications": "ناخموشی آگاهی‌ها از @{name}", "account.unmute_notifications": "ناخموشی آگاهی‌های @{name}",
"account.unmute_short": "ناخموشی", "account.unmute_short": "ناخموشی",
"account_note.placeholder": "برای افزودن یادداشت کلیک کنید", "account_note.placeholder": "برای افزودن یادداشت کلیک کنید",
"admin.dashboard.daily_retention": "User retention rate by day after sign-up", "admin.dashboard.daily_retention": "نرخ حفظ کاربر در روز پس از ثبت نام",
"admin.dashboard.monthly_retention": "User retention rate by month after sign-up", "admin.dashboard.monthly_retention": "نرخ حفظ کاربر در ماه پس از ثبت نام",
"admin.dashboard.retention.average": "میانگین", "admin.dashboard.retention.average": "میانگین",
"admin.dashboard.retention.cohort": "ماه ثبت‌نام", "admin.dashboard.retention.cohort": "ماه ثبت‌نام",
"admin.dashboard.retention.cohort_size": "کاربران جدید", "admin.dashboard.retention.cohort_size": "کاربران جدید",
@ -79,13 +79,13 @@
"column.lists": "سیاهه‌ها", "column.lists": "سیاهه‌ها",
"column.mutes": "کاربران خموش", "column.mutes": "کاربران خموش",
"column.notifications": "آگاهی‌ها", "column.notifications": "آگاهی‌ها",
"column.pins": "فرسته‌های سنجاقشده", "column.pins": "فرسته‌های سنجاق شده",
"column.public": "خط زمانی همگانی", "column.public": "خط زمانی همگانی",
"column_back_button.label": "بازگشت", "column_back_button.label": "بازگشت",
"column_header.hide_settings": "نهفتن تنظیمات", "column_header.hide_settings": "نهفتن تنظیمات",
"column_header.moveLeft_settings": "جابه‌جایی ستون به چپ", "column_header.moveLeft_settings": "جابه‌جایی ستون به چپ",
"column_header.moveRight_settings": "جابه‌جایی ستون به راست", "column_header.moveRight_settings": "جابه‌جایی ستون به راست",
"column_header.pin": "سنجاقکردن", "column_header.pin": "سنجاق کردن",
"column_header.show_settings": "نمایش تنظیمات", "column_header.show_settings": "نمایش تنظیمات",
"column_header.unpin": "برداشتن سنجاق", "column_header.unpin": "برداشتن سنجاق",
"column_subheading.settings": "تنظیمات", "column_subheading.settings": "تنظیمات",
@ -94,7 +94,7 @@
"community.column_settings.remote_only": "تنها دوردست", "community.column_settings.remote_only": "تنها دوردست",
"compose_form.direct_message_warning": "این فرسته تنها به کاربرانی که از آن‌ها نام برده شده فرستاده خواهد شد.", "compose_form.direct_message_warning": "این فرسته تنها به کاربرانی که از آن‌ها نام برده شده فرستاده خواهد شد.",
"compose_form.direct_message_warning_learn_more": "بیشتر بدانید", "compose_form.direct_message_warning_learn_more": "بیشتر بدانید",
"compose_form.hashtag_warning": "از آن‌جا که این فرسته فهرست‌نشده است، در نتایج جست‌وجوی برچسبها پیدا نخواهد شد. تنها فرسته‌های عمومی را می‌توان با جست‌وجوی برچسب یافت.", "compose_form.hashtag_warning": "از آن‌جا که این فرسته فهرست نشده است، در نتایج جست‌وجوی هشتگها پیدا نخواهد شد. تنها فرسته‌های عمومی را می‌توان با جست‌وجوی هشتگ یافت.",
"compose_form.lock_disclaimer": "حسابتان {locked} نیست. هر کسی می‌تواند پی‌گیرتان شده و فرسته‌های ویژهٔ پی‌گیرانتان را ببیند.", "compose_form.lock_disclaimer": "حسابتان {locked} نیست. هر کسی می‌تواند پی‌گیرتان شده و فرسته‌های ویژهٔ پی‌گیرانتان را ببیند.",
"compose_form.lock_disclaimer.lock": "قفل‌شده", "compose_form.lock_disclaimer.lock": "قفل‌شده",
"compose_form.placeholder": "تازه چه خبر؟", "compose_form.placeholder": "تازه چه خبر؟",
@ -144,7 +144,7 @@
"directory.local": "تنها از {domain}", "directory.local": "تنها از {domain}",
"directory.new_arrivals": "تازه‌واردان", "directory.new_arrivals": "تازه‌واردان",
"directory.recently_active": "کاربران فعال اخیر", "directory.recently_active": "کاربران فعال اخیر",
"embed.instructions": "برای جاگذاری این فرسته در سایت خودتان، کد زیر را کپی کنید.", "embed.instructions": "برای جاسازی این فرسته در سایت خودتان، کد زیر را رونوشت کنید.",
"embed.preview": "این گونه دیده خواهد شد:", "embed.preview": "این گونه دیده خواهد شد:",
"emoji_button.activity": "فعالیت", "emoji_button.activity": "فعالیت",
"emoji_button.custom": "سفارشی", "emoji_button.custom": "سفارشی",
@ -164,11 +164,11 @@
"empty_column.account_timeline": "هیچ فرسته‌ای این‌جا نیست!", "empty_column.account_timeline": "هیچ فرسته‌ای این‌جا نیست!",
"empty_column.account_unavailable": "نمایهٔ موجود نیست", "empty_column.account_unavailable": "نمایهٔ موجود نیست",
"empty_column.blocks": "هنوز کسی را مسدود نکرده‌اید.", "empty_column.blocks": "هنوز کسی را مسدود نکرده‌اید.",
"empty_column.bookmarked_statuses": "هنوز هیچ فرستهٔ نشان‌شده‌ای ندارید. هنگامی که فرسته‌ای را نشان‌کنید، این‌جا نشان داده خواهد شد.", "empty_column.bookmarked_statuses": "هنوز هیچ فرستهٔ نشانهگذاری شده‌ای ندارید. هنگامی که فرسته‌ای را نشانهگذاری کنید، این‌جا نشان داده خواهد شد.",
"empty_column.community": "خط زمانی محلّی خالی است. چیزی بنویسید تا چرخش بچرخد!", "empty_column.community": "خط زمانی محلّی خالی است. چیزی بنویسید تا چرخش بچرخد!",
"empty_column.direct": "هنوز هیچ پیام مستقیمی ندارید. هنگامی که چنین پیامی بگیرید یا بفرستید این‌جا نشان داده خواهد شد.", "empty_column.direct": "هنوز هیچ پیام مستقیمی ندارید. هنگامی که چنین پیامی بگیرید یا بفرستید این‌جا نشان داده خواهد شد.",
"empty_column.domain_blocks": "هنوز هیچ دامنه‌ای مسدود نشده است.", "empty_column.domain_blocks": "هنوز هیچ دامنه‌ای مسدود نشده است.",
"empty_column.explore_statuses": "Nothing is trending right now. Check back later!", "empty_column.explore_statuses": "الآن چیزی پرطرفدار نیست. بعداً دوباره بررسی کنید!",
"empty_column.favourited_statuses": "شما هنوز هیچ فرسته‌ای را نپسندیده‌اید. هنگامی که فرسته‌ای را بپسندید، این‌جا نشان داده خواهد شد.", "empty_column.favourited_statuses": "شما هنوز هیچ فرسته‌ای را نپسندیده‌اید. هنگامی که فرسته‌ای را بپسندید، این‌جا نشان داده خواهد شد.",
"empty_column.favourites": "هنوز هیچ کسی این فرسته را نپسندیده است. هنگامی که کسی آن را بپسندد، این‌جا نشان داده خواهد شد.", "empty_column.favourites": "هنوز هیچ کسی این فرسته را نپسندیده است. هنگامی که کسی آن را بپسندد، این‌جا نشان داده خواهد شد.",
"empty_column.follow_recommendations": "ظاهرا هیچ پیشنهادی برای شما نمی‌توانیم تولید کنیم. می‌توانید از امکان جست‌وجو برای یافتن افرادی که ممکن است بشناسید و یا کاوش میان برچسب‌های داغ استفاده کنید.", "empty_column.follow_recommendations": "ظاهرا هیچ پیشنهادی برای شما نمی‌توانیم تولید کنیم. می‌توانید از امکان جست‌وجو برای یافتن افرادی که ممکن است بشناسید و یا کاوش میان برچسب‌های داغ استفاده کنید.",
@ -247,7 +247,7 @@
"keyboard_shortcuts.my_profile": "گشودن نمایه‌تان", "keyboard_shortcuts.my_profile": "گشودن نمایه‌تان",
"keyboard_shortcuts.notifications": "گشودن ستون آگاهی‌ها", "keyboard_shortcuts.notifications": "گشودن ستون آگاهی‌ها",
"keyboard_shortcuts.open_media": "گشودن رسانه", "keyboard_shortcuts.open_media": "گشودن رسانه",
"keyboard_shortcuts.pinned": "گشودن سیاههٔ فرسته‌های سنجاق شده", "keyboard_shortcuts.pinned": "گشودن فهرست فرسته‌های سنجاق شده",
"keyboard_shortcuts.profile": "گشودن نمایهٔ نویسنده", "keyboard_shortcuts.profile": "گشودن نمایهٔ نویسنده",
"keyboard_shortcuts.reply": "پاسخ به فرسته", "keyboard_shortcuts.reply": "پاسخ به فرسته",
"keyboard_shortcuts.requests": "گشودن سیاههٔ درخواست‌های پی‌گیری", "keyboard_shortcuts.requests": "گشودن سیاههٔ درخواست‌های پی‌گیری",
@ -305,7 +305,7 @@
"navigation_bar.logout": "خروج", "navigation_bar.logout": "خروج",
"navigation_bar.mutes": "کاربران خموشانده", "navigation_bar.mutes": "کاربران خموشانده",
"navigation_bar.personal": "شخصی", "navigation_bar.personal": "شخصی",
"navigation_bar.pins": "فرسته‌های سنجاقشده", "navigation_bar.pins": "فرسته‌های سنجاق شده",
"navigation_bar.preferences": "ترجیحات", "navigation_bar.preferences": "ترجیحات",
"navigation_bar.public_timeline": "خط زمانی همگانی", "navigation_bar.public_timeline": "خط زمانی همگانی",
"navigation_bar.security": "امنیت", "navigation_bar.security": "امنیت",
@ -392,40 +392,40 @@
"report.block": "مسدود کردن", "report.block": "مسدود کردن",
"report.block_explanation": "شما فرسته‌هایشان را نخواهید دید. آن‌ها نمی‌توانند فرسته‌هایتان را ببینند یا شما را پی‌بگیرند. آنها می‌توانند بگویند که مسدود شده‌اند.", "report.block_explanation": "شما فرسته‌هایشان را نخواهید دید. آن‌ها نمی‌توانند فرسته‌هایتان را ببینند یا شما را پی‌بگیرند. آنها می‌توانند بگویند که مسدود شده‌اند.",
"report.categories.other": "غیره", "report.categories.other": "غیره",
"report.categories.spam": "Spam", "report.categories.spam": "هرزنامه",
"report.categories.violation": "Content violates one or more server rules", "report.categories.violation": "محتوا یک یا چند قانون کارساز را نقض می‌کند",
"report.category.subtitle": "Choose the best match", "report.category.subtitle": "منطبق‌ترین را انتخاب کنید",
"report.category.title": "Tell us what's going on with this {type}", "report.category.title": "به ما بگویید با این {type} چه مشکلی دارید",
"report.category.title_account": "profile", "report.category.title_account": "نمایه",
"report.category.title_status": "post", "report.category.title_status": "فرسته",
"report.close": "Done", "report.close": "انجام شد",
"report.comment.title": "Is there anything else you think we should know?", "report.comment.title": "آیا چیز دیگری هست که فکر می‌کنید باید بدانیم؟",
"report.forward": "فرستادن به {target}", "report.forward": "فرستادن به {target}",
"report.forward_hint": "این حساب در کارساز دیگری ثبت شده. آیا می‌خواهید رونوشتی ناشناس از این گزارش به آن‌جا هم فرستاده شود؟", "report.forward_hint": "این حساب در کارساز دیگری ثبت شده. آیا می‌خواهید رونوشتی ناشناس از این گزارش به آن‌جا هم فرستاده شود؟",
"report.mute": "Mute", "report.mute": "خموش",
"report.mute_explanation": "You will not see their posts. They can still follow you and see your posts and will not know that they are muted.", "report.mute_explanation": "شما فرسته‌های آن‌ها را نخواهید دید. آن‌ها همچنان می‌توانند شما را پی‌بگیرند و فرسته‌هایتان را ببینند و نمی‌دانند که خموش شده‌اند.",
"report.next": "Next", "report.next": "بعدی",
"report.placeholder": "توضیحات اضافه", "report.placeholder": "توضیحات اضافه",
"report.reasons.dislike": "I don't like it", "report.reasons.dislike": "من آن را دوست ندارم",
"report.reasons.dislike_description": "It is not something you want to see", "report.reasons.dislike_description": "این چیزی نیست که بخواهید ببینید",
"report.reasons.other": "It's something else", "report.reasons.other": "بخواطر چیز دیگری است",
"report.reasons.other_description": "The issue does not fit into other categories", "report.reasons.other_description": "این موضوع در دسته‌بندی‌های دیگر نمی‌گنجد",
"report.reasons.spam": "It's spam", "report.reasons.spam": "این هرزنامه است",
"report.reasons.spam_description": "Malicious links, fake engagement, or repetitive replies", "report.reasons.spam_description": "پیوندهای مخرب، تعامل جعلی یا پاسخ‌های تکراری",
"report.reasons.violation": "It violates server rules", "report.reasons.violation": "قوانین کارساز را نقض می‌کند",
"report.reasons.violation_description": "You are aware that it breaks specific rules", "report.reasons.violation_description": "شما آگاه هستید که قوانین خاصی را زیر پا می‌گذارد",
"report.rules.subtitle": "Select all that apply", "report.rules.subtitle": "همهٔ موارد انجام شده را برگزینید",
"report.rules.title": "Which rules are being violated?", "report.rules.title": "کدام قوانین نقض شده‌اند؟",
"report.statuses.subtitle": "Select all that apply", "report.statuses.subtitle": "همهٔ موارد انجام شده را برگزینید",
"report.statuses.title": "Are there any posts that back up this report?", "report.statuses.title": "آیا فرسته‌ای وجود دارد که از این گزارش پشتیبانی کند؟",
"report.submit": "فرستادن", "report.submit": "فرستادن",
"report.target": "در حال گزارش {target}", "report.target": "در حال گزارش {target}",
"report.thanks.take_action": "Here are your options for controlling what you see on Mastodon:", "report.thanks.take_action": "در اینجا گزینه‌هایی برای کنترل آنچه در ماستودون میبینید، وجود دارد:",
"report.thanks.take_action_actionable": "While we review this, you can take action against @{name}:", "report.thanks.take_action_actionable": "در حالی که ما این مورد را بررسی می‌کنیم، می‌توانید علیه @{name} اقدام کنید:",
"report.thanks.title": "Don't want to see this?", "report.thanks.title": "نمی‌خواهید این را ببینید؟",
"report.thanks.title_actionable": "Thanks for reporting, we'll look into this.", "report.thanks.title_actionable": "ممنون بابت گزارش، ما آن را بررسی خواهیم کرد.",
"report.unfollow": "Unfollow @{name}", "report.unfollow": "ناپی‌گیری @{name}",
"report.unfollow_explanation": "You are following this account. To not see their posts in your home feed anymore, unfollow them.", "report.unfollow_explanation": "شما این حساب را پی‌گرفته‌اید، برای اینکه دیگر فرسته‌هایش را در خوراک خانه‌تان نبینید؛ آن را پی‌نگیرید.",
"search.placeholder": "جست‌وجو", "search.placeholder": "جست‌وجو",
"search_popout.search_format": "راهنمای جست‌وجوی پیشرفته", "search_popout.search_format": "راهنمای جست‌وجوی پیشرفته",
"search_popout.tips.full_text": "جست‌وجوی متنی ساده فرسته‌هایی که نوشته، پسندیده، تقویت‌کرده یا در آن‌ها نام‌برده شده‌اید را به علاوهٔ نام‌های کاربری، نام‌های نمایشی و برچسب‌ها برمی‌گرداند.", "search_popout.tips.full_text": "جست‌وجوی متنی ساده فرسته‌هایی که نوشته، پسندیده، تقویت‌کرده یا در آن‌ها نام‌برده شده‌اید را به علاوهٔ نام‌های کاربری، نام‌های نمایشی و برچسب‌ها برمی‌گرداند.",
@ -434,39 +434,39 @@
"search_popout.tips.text": "جست‌وجوی متنی ساده برای نام‌ها، نام‌های کاربری، و برچسب‌ها", "search_popout.tips.text": "جست‌وجوی متنی ساده برای نام‌ها، نام‌های کاربری، و برچسب‌ها",
"search_popout.tips.user": "کاربر", "search_popout.tips.user": "کاربر",
"search_results.accounts": "افراد", "search_results.accounts": "افراد",
"search_results.all": "All", "search_results.all": "همه",
"search_results.hashtags": "برچسب‌ها", "search_results.hashtags": "برچسب‌ها",
"search_results.nothing_found": "Could not find anything for these search terms", "search_results.nothing_found": "چیزی برای این عبارت جست‌وجو یافت نشد",
"search_results.statuses": "فرسته‌ها", "search_results.statuses": "فرسته‌ها",
"search_results.statuses_fts_disabled": "جست‌وجوی محتوای فرسته‌ها در این کارساز ماستودون فعال نشده است.", "search_results.statuses_fts_disabled": "جست‌وجوی محتوای فرسته‌ها در این کارساز ماستودون به کار انداخته نشده است.",
"search_results.total": "{count, number} {count, plural, one {نتیجه} other {نتیجه}}", "search_results.total": "{count, number} {count, plural, one {نتیجه} other {نتیجه}}",
"status.admin_account": "گشودن واسط مدیریت برای @{name}", "status.admin_account": "گشودن واسط مدیریت برای @{name}",
"status.admin_status": "گشودن این فرسته در واسط مدیریت", "status.admin_status": "گشودن این فرسته در واسط مدیریت",
"status.block": "مسدود کردن @{name}", "status.block": "مسدود کردن @{name}",
"status.bookmark": "نشانک", "status.bookmark": "نشانک",
"status.cancel_reblog_private": "لغو تقویت", "status.cancel_reblog_private": "لغو تقویت",
"status.cannot_reblog": "این فرسته قابل تقویت نیست", "status.cannot_reblog": "این فرسته قابل تقویت نیست",
"status.copy": "رونویسی از نشانی فرسته", "status.copy": "رونوشت پیوند فرسته",
"status.delete": "حذف", "status.delete": "حذف",
"status.detailed_status": "نمایش کامل گفتگو", "status.detailed_status": "نمایش کامل گفتگو",
"status.direct": "پیام مستقیم به @{name}", "status.direct": "پیام مستقیم به @{name}",
"status.edit": "Edit", "status.edit": "ویرایش",
"status.edited": "Edited {date}", "status.edited": "ویرایش شده در {date}",
"status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}", "status.edited_x_times": "{count, plural, one {{count} مرتبه} other {{count} مرتبه}} ویرایش شد",
"status.embed": "جاگذاری", "status.embed": "جاسازی",
"status.favourite": "پسندیدن", "status.favourite": "پسندیدن",
"status.filtered": "پالوده", "status.filtered": "پالوده",
"status.history.created": "{name} created {date}", "status.history.created": "توسط {name} در {date} ایجاد شد",
"status.history.edited": "{name} edited {date}", "status.history.edited": "توسط {name} در {date} ویرایش شد",
"status.load_more": "بار کردن بیش‌تر", "status.load_more": "بار کردن بیش‌تر",
"status.media_hidden": "رسانهٔ نهفته", "status.media_hidden": "رسانهٔ نهفته",
"status.mention": "نام‌بردن از @{name}", "status.mention": "نام‌بردن از @{name}",
"status.more": "بیشتر", "status.more": "بیشتر",
"status.mute": "خموشاندن @{name}", "status.mute": "خموشاندن @{name}",
"status.mute_conversation": "خموشاندن گفت‌وگو", "status.mute_conversation": "خموشاندن گفت‌وگو",
"status.open": "گسترش این فرسته", "status.open": "گسترش این فرسته",
"status.pin": "سنجاقکردن در نمایه", "status.pin": "سنجاق کردن در نمایه",
"status.pinned": "فرستهٔ سنجاقشده", "status.pinned": "فرستهٔ سنجاق شده",
"status.read_more": "بیشتر بخوانید", "status.read_more": "بیشتر بخوانید",
"status.reblog": "تقویت", "status.reblog": "تقویت",
"status.reblog_private": "تقویت برای مخاطبان نخستین", "status.reblog_private": "تقویت برای مخاطبان نخستین",
@ -476,7 +476,7 @@
"status.remove_bookmark": "برداشتن نشانک", "status.remove_bookmark": "برداشتن نشانک",
"status.reply": "پاسخ", "status.reply": "پاسخ",
"status.replyAll": "پاسخ به رشته", "status.replyAll": "پاسخ به رشته",
"status.report": "گزارش @{name}", "status.report": "گزارش @{name}",
"status.sensitive_warning": "محتوای حساس", "status.sensitive_warning": "محتوای حساس",
"status.share": "هم‌رسانی", "status.share": "هم‌رسانی",
"status.show_less": "نمایش کمتر", "status.show_less": "نمایش کمتر",

View File

@ -192,7 +192,7 @@
"errors.unexpected_crash.copy_stacktrace": "スタックトレースをクリップボードにコピー", "errors.unexpected_crash.copy_stacktrace": "スタックトレースをクリップボードにコピー",
"errors.unexpected_crash.report_issue": "問題を報告", "errors.unexpected_crash.report_issue": "問題を報告",
"explore.search_results": "検索結果", "explore.search_results": "検索結果",
"explore.suggested_follows": "あなたに", "explore.suggested_follows": "おすすめ",
"explore.title": "エクスプローラー", "explore.title": "エクスプローラー",
"explore.trending_links": "ニュース", "explore.trending_links": "ニュース",
"explore.trending_statuses": "投稿", "explore.trending_statuses": "投稿",

View File

@ -342,7 +342,7 @@
"notifications.filter.all": "Hemû", "notifications.filter.all": "Hemû",
"notifications.filter.boosts": "Bilindkirî", "notifications.filter.boosts": "Bilindkirî",
"notifications.filter.favourites": "Bijarte", "notifications.filter.favourites": "Bijarte",
"notifications.filter.follows": "Şopîner", "notifications.filter.follows": "Dişopîne",
"notifications.filter.mentions": "Qalkirin", "notifications.filter.mentions": "Qalkirin",
"notifications.filter.polls": "Encamên rapirsiyê", "notifications.filter.polls": "Encamên rapirsiyê",
"notifications.filter.statuses": "Ji kesên tu dişopînî re rojanekirin", "notifications.filter.statuses": "Ji kesên tu dişopînî re rojanekirin",
@ -501,7 +501,7 @@
"time_remaining.seconds": "{number, plural, one {# çirke} other {# çirke}} maye", "time_remaining.seconds": "{number, plural, one {# çirke} other {# çirke}} maye",
"timeline_hint.remote_resource_not_displayed": "{resource} Ji rajekerên din nayê dîtin.", "timeline_hint.remote_resource_not_displayed": "{resource} Ji rajekerên din nayê dîtin.",
"timeline_hint.resources.followers": "Şopîner", "timeline_hint.resources.followers": "Şopîner",
"timeline_hint.resources.follows": "Şopîner", "timeline_hint.resources.follows": "Dişopîne",
"timeline_hint.resources.statuses": "Şandiyên kevn", "timeline_hint.resources.statuses": "Şandiyên kevn",
"trends.counter_by_accounts": "{count, plural, one {{counter} kes} other {{counter} kes}} diaxivin", "trends.counter_by_accounts": "{count, plural, one {{counter} kes} other {{counter} kes}} diaxivin",
"trends.trending_now": "Rojev", "trends.trending_now": "Rojev",

View File

@ -9,44 +9,44 @@
"account.browse_more_on_origin_server": "Meer op het originele profiel bekijken", "account.browse_more_on_origin_server": "Meer op het originele profiel bekijken",
"account.cancel_follow_request": "Volgverzoek annuleren", "account.cancel_follow_request": "Volgverzoek annuleren",
"account.direct": "@{name} een direct bericht sturen", "account.direct": "@{name} een direct bericht sturen",
"account.disable_notifications": "Geef geen melding meer wanneer @{name} toot", "account.disable_notifications": "Geef geen melding meer wanneer @{name} een bericht plaatst",
"account.domain_blocked": "Domein geblokkeerd", "account.domain_blocked": "Domein geblokkeerd",
"account.edit_profile": "Profiel bewerken", "account.edit_profile": "Profiel bewerken",
"account.enable_notifications": "Geef een melding wanneer @{name} toot", "account.enable_notifications": "Geef een melding wanneer @{name} een bericht plaatst",
"account.endorse": "Op profiel weergeven", "account.endorse": "Op profiel weergeven",
"account.follow": "Volgen", "account.follow": "Volgen",
"account.followers": "Volgers", "account.followers": "Volgers",
"account.followers.empty": "Niemand volgt nog deze gebruiker.", "account.followers.empty": "Niemand volgt nog deze gebruiker.",
"account.followers_counter": "{count, plural, one {{counter} volger} other {{counter} volgers}}", "account.followers_counter": "{count, plural, one {{counter} volger} other {{counter} volgers}}",
"account.following": "Following", "account.following": "Volgend",
"account.following_counter": "{count, plural, one {{counter} volgend} other {{counter} volgend}}", "account.following_counter": "{count, plural, one {{counter} volgend} other {{counter} volgend}}",
"account.follows.empty": "Deze gebruiker volgt nog niemand.", "account.follows.empty": "Deze gebruiker volgt nog niemand.",
"account.follows_you": "Volgt jou", "account.follows_you": "Volgt jou",
"account.hide_reblogs": "Boosts van @{name} verbergen", "account.hide_reblogs": "Boosts van @{name} verbergen",
"account.joined": "Geregistreerd in {date}", "account.joined": "Geregistreerd in {date}",
"account.link_verified_on": "Eigendom van deze link is gecontroleerd op {date}", "account.link_verified_on": "Eigendom van deze link is gecontroleerd op {date}",
"account.locked_info": "De privacystatus van dit account is op besloten gezet. De eigenaar bepaalt handmatig wie hen kan volgen.", "account.locked_info": "De privacystatus van dit account is op besloten gezet. De eigenaar bepaalt handmatig wie diegene kan volgen.",
"account.media": "Media", "account.media": "Media",
"account.mention": "@{name} vermelden", "account.mention": "@{name} vermelden",
"account.moved_to": "{name} is verhuisd naar:", "account.moved_to": "{name} is verhuisd naar:",
"account.mute": "@{name} negeren", "account.mute": "@{name} negeren",
"account.mute_notifications": "Meldingen van @{name} negeren", "account.mute_notifications": "Meldingen van @{name} negeren",
"account.muted": "Genegeerd", "account.muted": "Genegeerd",
"account.posts": "Toots", "account.posts": "Berichten",
"account.posts_with_replies": "Toots en reacties", "account.posts_with_replies": "Berichten en reacties",
"account.report": "@{name} rapporteren", "account.report": "@{name} rapporteren",
"account.requested": "Wacht op goedkeuring. Klik om het volgverzoek te annuleren", "account.requested": "Wacht op goedkeuring. Klik om het volgverzoek te annuleren",
"account.share": "Profiel van @{name} delen", "account.share": "Profiel van @{name} delen",
"account.show_reblogs": "Boosts van @{name} tonen", "account.show_reblogs": "Boosts van @{name} tonen",
"account.statuses_counter": "{count, plural, one {{counter} toot} other {{counter} toots}}", "account.statuses_counter": "{count, plural, one {{counter} bericht} other {{counter} berichten}}",
"account.unblock": "@{name} deblokkeren", "account.unblock": "@{name} deblokkeren",
"account.unblock_domain": "{domain} niet langer verbergen", "account.unblock_domain": "{domain} niet langer verbergen",
"account.unblock_short": "Unblock", "account.unblock_short": "Deblokkeren",
"account.unendorse": "Niet op profiel weergeven", "account.unendorse": "Niet op profiel weergeven",
"account.unfollow": "Ontvolgen", "account.unfollow": "Ontvolgen",
"account.unmute": "@{name} niet langer negeren", "account.unmute": "@{name} niet langer negeren",
"account.unmute_notifications": "Meldingen van @{name} niet langer negeren", "account.unmute_notifications": "Meldingen van @{name} niet langer negeren",
"account.unmute_short": "Unmute", "account.unmute_short": "Niet langer negeren",
"account_note.placeholder": "Klik om een opmerking toe te voegen", "account_note.placeholder": "Klik om een opmerking toe te voegen",
"admin.dashboard.daily_retention": "User retention rate by day after sign-up", "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
"admin.dashboard.monthly_retention": "User retention rate by month after sign-up", "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
@ -79,7 +79,7 @@
"column.lists": "Lijsten", "column.lists": "Lijsten",
"column.mutes": "Genegeerde gebruikers", "column.mutes": "Genegeerde gebruikers",
"column.notifications": "Meldingen", "column.notifications": "Meldingen",
"column.pins": "Vastgezette toots", "column.pins": "Vastgezette berichten",
"column.public": "Globale tijdlijn", "column.public": "Globale tijdlijn",
"column_back_button.label": "Terug", "column_back_button.label": "Terug",
"column_header.hide_settings": "Instellingen verbergen", "column_header.hide_settings": "Instellingen verbergen",
@ -92,10 +92,10 @@
"community.column_settings.local_only": "Alleen lokaal", "community.column_settings.local_only": "Alleen lokaal",
"community.column_settings.media_only": "Alleen media", "community.column_settings.media_only": "Alleen media",
"community.column_settings.remote_only": "Alleen andere servers", "community.column_settings.remote_only": "Alleen andere servers",
"compose_form.direct_message_warning": "Deze toot wordt alleen naar vermelde gebruikers verstuurd.", "compose_form.direct_message_warning": "Dit bericht wordt alleen naar vermelde gebruikers verstuurd.",
"compose_form.direct_message_warning_learn_more": "Meer leren", "compose_form.direct_message_warning_learn_more": "Meer leren",
"compose_form.hashtag_warning": "Deze toot valt niet onder een hashtag te bekijken, omdat deze niet op openbare tijdlijnen wordt getoond. Alleen openbare toots kunnen via hashtags gevonden worden.", "compose_form.hashtag_warning": "Dit bericht valt niet onder een hashtag te bekijken, omdat deze niet op openbare tijdlijnen wordt getoond. Alleen openbare berichten kunnen via hashtags gevonden worden.",
"compose_form.lock_disclaimer": "Jouw account is niet {locked}. Iedereen kan jou volgen en kan de toots zien die je alleen aan jouw volgers hebt gericht.", "compose_form.lock_disclaimer": "Jouw account is niet {locked}. Iedereen kan jou volgen en kan de berichten zien die je alleen aan jouw volgers hebt gericht.",
"compose_form.lock_disclaimer.lock": "besloten", "compose_form.lock_disclaimer.lock": "besloten",
"compose_form.placeholder": "Wat wil je kwijt?", "compose_form.placeholder": "Wat wil je kwijt?",
"compose_form.poll.add_option": "Keuze toevoegen", "compose_form.poll.add_option": "Keuze toevoegen",
@ -106,7 +106,7 @@
"compose_form.poll.switch_to_single": "Poll wijzigen om een enkele keuze toe te staan", "compose_form.poll.switch_to_single": "Poll wijzigen om een enkele keuze toe te staan",
"compose_form.publish": "Toot", "compose_form.publish": "Toot",
"compose_form.publish_loud": "{publish}!", "compose_form.publish_loud": "{publish}!",
"compose_form.save_changes": "Save changes", "compose_form.save_changes": "Wijzigingen opslaan",
"compose_form.sensitive.hide": "{count, plural, one {Media als gevoelig markeren} other {Media als gevoelig markeren}}", "compose_form.sensitive.hide": "{count, plural, one {Media als gevoelig markeren} other {Media als gevoelig markeren}}",
"compose_form.sensitive.marked": "{count, plural, one {Media is als gevoelig gemarkeerd} other {Media is als gevoelig gemarkeerd}}", "compose_form.sensitive.marked": "{count, plural, one {Media is als gevoelig gemarkeerd} other {Media is als gevoelig gemarkeerd}}",
"compose_form.sensitive.unmarked": "{count, plural, one {Media is niet als gevoelig gemarkeerd} other {Media is niet als gevoelig gemarkeerd}}", "compose_form.sensitive.unmarked": "{count, plural, one {Media is niet als gevoelig gemarkeerd} other {Media is niet als gevoelig gemarkeerd}}",
@ -118,22 +118,22 @@
"confirmations.block.confirm": "Blokkeren", "confirmations.block.confirm": "Blokkeren",
"confirmations.block.message": "Weet je het zeker dat je {name} wilt blokkeren?", "confirmations.block.message": "Weet je het zeker dat je {name} wilt blokkeren?",
"confirmations.delete.confirm": "Verwijderen", "confirmations.delete.confirm": "Verwijderen",
"confirmations.delete.message": "Weet je het zeker dat je deze toot wilt verwijderen?", "confirmations.delete.message": "Weet je het zeker dat je dit bericht wilt verwijderen?",
"confirmations.delete_list.confirm": "Verwijderen", "confirmations.delete_list.confirm": "Verwijderen",
"confirmations.delete_list.message": "Weet je zeker dat je deze lijst definitief wilt verwijderen?", "confirmations.delete_list.message": "Weet je zeker dat je deze lijst definitief wilt verwijderen?",
"confirmations.discard_edit_media.confirm": "Weggooien", "confirmations.discard_edit_media.confirm": "Weggooien",
"confirmations.discard_edit_media.message": "Je hebt niet-opgeslagen wijzigingen in de mediabeschrijving of voorvertonning, wil je deze toch weggooien?", "confirmations.discard_edit_media.message": "Je hebt niet-opgeslagen wijzigingen in de mediabeschrijving of voorvertonning, wil je deze toch weggooien?",
"confirmations.domain_block.confirm": "Verberg alles van deze server", "confirmations.domain_block.confirm": "Verberg alles van deze server",
"confirmations.domain_block.message": "Weet je het echt heel erg zeker dat je alles van {domain} wilt negeren? In de meeste gevallen is het blokkeren of negeren van een paar specifieke personen voldoende en beter. Je zult geen toots van deze server op openbare tijdlijnen zien of in jouw meldingen. Jouw volgers van deze server worden verwijderd.", "confirmations.domain_block.message": "Weet je het echt heel erg zeker dat je alles van {domain} wilt negeren? In de meeste gevallen is het blokkeren of negeren van een paar specifieke personen voldoende en beter. Je zult geen berichten van deze server op openbare tijdlijnen zien of in jouw meldingen. Jouw volgers van deze server worden verwijderd.",
"confirmations.logout.confirm": "Uitloggen", "confirmations.logout.confirm": "Uitloggen",
"confirmations.logout.message": "Weet je zeker dat je wilt uitloggen?", "confirmations.logout.message": "Weet je zeker dat je wilt uitloggen?",
"confirmations.mute.confirm": "Negeren", "confirmations.mute.confirm": "Negeren",
"confirmations.mute.explanation": "Dit verbergt toots van hen en toots waar hen in wordt vermeld, maar hen kan nog steeds jouw toots bekijken en jou volgen.", "confirmations.mute.explanation": "Dit verbergt diens berichten en berichten waar diegene in wordt vermeld, maar diegene kan nog steeds jouw berichten bekijken en jou volgen.",
"confirmations.mute.message": "Weet je het zeker dat je {name} wilt negeren?", "confirmations.mute.message": "Weet je het zeker dat je {name} wilt negeren?",
"confirmations.redraft.confirm": "Verwijderen en herschrijven", "confirmations.redraft.confirm": "Verwijderen en herschrijven",
"confirmations.redraft.message": "Weet je zeker dat je deze toot wilt verwijderen en herschrijven? Je verliest wel de boosts en favorieten, en de reacties op de originele toot zitten niet meer aan de nieuwe toot vast.", "confirmations.redraft.message": "Weet je zeker dat je dit bericht wilt verwijderen en herschrijven? Je verliest wel de boosts en favorieten, en de reacties op het originele bericht raak je kwijt.",
"confirmations.reply.confirm": "Reageren", "confirmations.reply.confirm": "Reageren",
"confirmations.reply.message": "Door nu te reageren overschrijf je de toot die je op dit moment aan het schrijven bent. Weet je zeker dat je verder wil gaan?", "confirmations.reply.message": "Door nu te reageren overschrijf je het bericht dat je op dit moment aan het schrijven bent. Weet je zeker dat je verder wil gaan?",
"confirmations.unfollow.confirm": "Ontvolgen", "confirmations.unfollow.confirm": "Ontvolgen",
"confirmations.unfollow.message": "Weet je het zeker dat je {name} wilt ontvolgen?", "confirmations.unfollow.message": "Weet je het zeker dat je {name} wilt ontvolgen?",
"conversation.delete": "Gesprek verwijderen", "conversation.delete": "Gesprek verwijderen",
@ -144,7 +144,7 @@
"directory.local": "Alleen {domain}", "directory.local": "Alleen {domain}",
"directory.new_arrivals": "Nieuwe accounts", "directory.new_arrivals": "Nieuwe accounts",
"directory.recently_active": "Onlangs actief", "directory.recently_active": "Onlangs actief",
"embed.instructions": "Embed deze toot op jouw website, door de onderstaande code te kopiëren.", "embed.instructions": "Embed dit bericht op jouw website door de onderstaande code te kopiëren.",
"embed.preview": "Zo komt het eruit te zien:", "embed.preview": "Zo komt het eruit te zien:",
"emoji_button.activity": "Activiteiten", "emoji_button.activity": "Activiteiten",
"emoji_button.custom": "Lokale emojis", "emoji_button.custom": "Lokale emojis",
@ -161,41 +161,41 @@
"emoji_button.symbols": "Symbolen", "emoji_button.symbols": "Symbolen",
"emoji_button.travel": "Reizen en locaties", "emoji_button.travel": "Reizen en locaties",
"empty_column.account_suspended": "Account opgeschort", "empty_column.account_suspended": "Account opgeschort",
"empty_column.account_timeline": "Hier zijn geen toots!", "empty_column.account_timeline": "Hier zijn geen berichten!",
"empty_column.account_unavailable": "Profiel is niet beschikbaar", "empty_column.account_unavailable": "Profiel is niet beschikbaar",
"empty_column.blocks": "Jij hebt nog geen enkele gebruiker geblokkeerd.", "empty_column.blocks": "Jij hebt nog geen enkele gebruiker geblokkeerd.",
"empty_column.bookmarked_statuses": "Jij hebt nog geen toots aan je bladwijzers toegevoegd. Wanneer je er een aan jouw bladwijzers toevoegt, valt deze hier te zien.", "empty_column.bookmarked_statuses": "Jij hebt nog geen berichten aan je bladwijzers toegevoegd. Wanneer je er een aan jouw bladwijzers toevoegt, valt deze hier te zien.",
"empty_column.community": "De lokale tijdlijn is nog leeg. Toot iets in het openbaar om de spits af te bijten!", "empty_column.community": "De lokale tijdlijn is nog leeg. Plaats een openbaar bericht om de spits af te bijten!",
"empty_column.direct": "Je hebt nog geen directe berichten. Wanneer je er een verzend of ontvangt, zijn deze hier te zien.", "empty_column.direct": "Je hebt nog geen directe berichten. Wanneer je er een verzend of ontvangt, zijn deze hier te zien.",
"empty_column.domain_blocks": "Er zijn nog geen geblokkeerde domeinen.", "empty_column.domain_blocks": "Er zijn nog geen geblokkeerde domeinen.",
"empty_column.explore_statuses": "Nothing is trending right now. Check back later!", "empty_column.explore_statuses": "Momenteel zijn er geen trends. Kom later terug!",
"empty_column.favourited_statuses": "Jij hebt nog geen favoriete toots. Wanneer je er een aan jouw favorieten toevoegt, valt deze hier te zien.", "empty_column.favourited_statuses": "Jij hebt nog geen favoriete berichten. Wanneer je er een aan jouw favorieten toevoegt, valt deze hier te zien.",
"empty_column.favourites": "Niemand heeft deze toot nog aan hun favorieten toegevoegd. Wanneer iemand dit doet, valt dat hier te zien.", "empty_column.favourites": "Niemand heeft dit bericht nog aan diens favorieten toegevoegd. Wanneer iemand dit doet, valt dat hier te zien.",
"empty_column.follow_recommendations": "Het lijkt er op dat er geen aanbevelingen voor jou aangemaakt kunnen worden. Je kunt proberen te zoeken naar mensen die je wellicht kent, zoeken op hashtags, de lokale en globale tijdlijnen bekijken of de gebruikersgids doorbladeren.", "empty_column.follow_recommendations": "Het lijkt er op dat er geen aanbevelingen voor jou aangemaakt kunnen worden. Je kunt proberen te zoeken naar mensen die je wellicht kent, zoeken op hashtags, de lokale en globale tijdlijnen bekijken of de gebruikersgids doorbladeren.",
"empty_column.follow_requests": "Jij hebt nog enkel volgverzoek ontvangen. Wanneer je er eentje ontvangt, valt dat hier te zien.", "empty_column.follow_requests": "Jij hebt nog enkel volgverzoek ontvangen. Wanneer je er eentje ontvangt, valt dat hier te zien.",
"empty_column.hashtag": "Er is nog niks te vinden onder deze hashtag.", "empty_column.hashtag": "Er is nog niks te vinden onder deze hashtag.",
"empty_column.home": "Deze tijdlijn is leeg! Volg meer mensen om het te vullen. {suggestions}", "empty_column.home": "Deze tijdlijn is leeg! Volg meer mensen om het te vullen. {suggestions}",
"empty_column.home.suggestions": "Enkele aanbevelingen bekijken", "empty_column.home.suggestions": "Enkele aanbevelingen bekijken",
"empty_column.list": "Er is nog niks te zien in deze lijst. Wanneer lijstleden nieuwe toots publiceren, zijn deze hier te zien.", "empty_column.list": "Er is nog niks te zien in deze lijst. Wanneer lijstleden nieuwe berichten plaatsen, zijn deze hier te zien.",
"empty_column.lists": "Jij hebt nog geen enkele lijst. Wanneer je er eentje hebt aangemaakt, valt deze hier te zien.", "empty_column.lists": "Jij hebt nog geen enkele lijst. Wanneer je er eentje hebt aangemaakt, valt deze hier te zien.",
"empty_column.mutes": "Jij hebt nog geen gebruikers genegeerd.", "empty_column.mutes": "Jij hebt nog geen gebruikers genegeerd.",
"empty_column.notifications": "Je hebt nog geen meldingen. Begin met iemand een gesprek.", "empty_column.notifications": "Je hebt nog geen meldingen. Begin met iemand een gesprek.",
"empty_column.public": "Er is hier helemaal niks! Toot iets in het openbaar of volg mensen van andere servers om het te vullen", "empty_column.public": "Er is hier helemaal niks! Plaatst een openbaar bericht of volg mensen van andere servers om het te vullen",
"error.unexpected_crash.explanation": "Als gevolg van een bug in onze broncode of als gevolg van een compatibiliteitsprobleem met jouw webbrowser, kan deze pagina niet goed worden weergegeven.", "error.unexpected_crash.explanation": "Als gevolg van een bug in onze broncode of als gevolg van een compatibiliteitsprobleem met jouw webbrowser, kan deze pagina niet goed worden weergegeven.",
"error.unexpected_crash.explanation_addons": "Deze pagina kon niet correct geladen worden. Deze fout wordt waarschijnlijk door een browser-add-on of een automatische vertalingshulpmiddel veroorzaakt.", "error.unexpected_crash.explanation_addons": "Deze pagina kon niet correct geladen worden. Deze fout wordt waarschijnlijk door een browser-add-on of een automatische vertalingshulpmiddel veroorzaakt.",
"error.unexpected_crash.next_steps": "Probeer deze pagina te vernieuwen. Wanneer dit niet helpt is het nog steeds mogelijk om Mastodon in een andere webbrowser of mobiele app te gebruiken.", "error.unexpected_crash.next_steps": "Probeer deze pagina te vernieuwen. Wanneer dit niet helpt is het nog steeds mogelijk om Mastodon in een andere webbrowser of mobiele app te gebruiken.",
"error.unexpected_crash.next_steps_addons": "Probeer deze uit te schakelen en de pagina te verversen. Wanneer dat niet helpt, kun je Mastodon nog altijd met een andere webbrowser of mobiele app gebruiken.", "error.unexpected_crash.next_steps_addons": "Probeer deze uit te schakelen en de pagina te verversen. Wanneer dat niet helpt, kun je Mastodon nog altijd met een andere webbrowser of mobiele app gebruiken.",
"errors.unexpected_crash.copy_stacktrace": "Stacktrace naar klembord kopiëren", "errors.unexpected_crash.copy_stacktrace": "Stacktrace naar klembord kopiëren",
"errors.unexpected_crash.report_issue": "Technisch probleem melden", "errors.unexpected_crash.report_issue": "Technisch probleem melden",
"explore.search_results": "Search results", "explore.search_results": "Zoekresultaten",
"explore.suggested_follows": "For you", "explore.suggested_follows": "Voor jou",
"explore.title": "Explore", "explore.title": "Verkennen",
"explore.trending_links": "News", "explore.trending_links": "Nieuws",
"explore.trending_statuses": "Posts", "explore.trending_statuses": "Berichten",
"explore.trending_tags": "Hashtags", "explore.trending_tags": "Hashtags",
"follow_recommendations.done": "Klaar", "follow_recommendations.done": "Klaar",
"follow_recommendations.heading": "Volg mensen waarvan je graag toots wil zien! Hier zijn enkele aanbevelingen.", "follow_recommendations.heading": "Volg mensen waarvan je graag berichten wil zien! Hier zijn enkele aanbevelingen.",
"follow_recommendations.lead": "Toots van mensen die je volgt zullen in chronologische volgorde onder start verschijnen. Wees niet bang om hierin fouten te maken, want je kunt mensen op elk moment net zo eenvoudig ontvolgen!", "follow_recommendations.lead": "Berichten van mensen die je volgt zullen in chronologische volgorde onder start verschijnen. Wees niet bang om hierin fouten te maken, want je kunt mensen op elk moment net zo eenvoudig ontvolgen!",
"follow_request.authorize": "Goedkeuren", "follow_request.authorize": "Goedkeuren",
"follow_request.reject": "Afkeuren", "follow_request.reject": "Afkeuren",
"follow_requests.unlocked_explanation": "Ook al is jouw account niet besloten, de medewerkers van {domain} denken dat jij misschien de volgende volgverzoeken handmatig wil controleren.", "follow_requests.unlocked_explanation": "Ook al is jouw account niet besloten, de medewerkers van {domain} denken dat jij misschien de volgende volgverzoeken handmatig wil controleren.",
@ -227,13 +227,13 @@
"intervals.full.minutes": "{number, plural, one {# minuut} other {# minuten}}", "intervals.full.minutes": "{number, plural, one {# minuut} other {# minuten}}",
"keyboard_shortcuts.back": "Ga terug", "keyboard_shortcuts.back": "Ga terug",
"keyboard_shortcuts.blocked": "Geblokkeerde gebruikers tonen", "keyboard_shortcuts.blocked": "Geblokkeerde gebruikers tonen",
"keyboard_shortcuts.boost": "Toot boosten", "keyboard_shortcuts.boost": "Bericht boosten",
"keyboard_shortcuts.column": "Op één van de kolommen focussen", "keyboard_shortcuts.column": "Op één van de kolommen focussen",
"keyboard_shortcuts.compose": "Tekstveld voor toots focussen", "keyboard_shortcuts.compose": "Tekstveld om een bericht te schrijven focussen",
"keyboard_shortcuts.description": "Omschrijving", "keyboard_shortcuts.description": "Omschrijving",
"keyboard_shortcuts.direct": "Jouw directe berichten tonen", "keyboard_shortcuts.direct": "Jouw directe berichten tonen",
"keyboard_shortcuts.down": "Naar beneden in de lijst bewegen", "keyboard_shortcuts.down": "Naar beneden in de lijst bewegen",
"keyboard_shortcuts.enter": "Toot volledig tonen", "keyboard_shortcuts.enter": "Volledig bericht tonen",
"keyboard_shortcuts.favourite": "Aan jouw favorieten toevoegen", "keyboard_shortcuts.favourite": "Aan jouw favorieten toevoegen",
"keyboard_shortcuts.favourites": "Favorieten tonen", "keyboard_shortcuts.favourites": "Favorieten tonen",
"keyboard_shortcuts.federated": "Globale tijdlijn tonen", "keyboard_shortcuts.federated": "Globale tijdlijn tonen",
@ -247,7 +247,7 @@
"keyboard_shortcuts.my_profile": "Jouw profiel tonen", "keyboard_shortcuts.my_profile": "Jouw profiel tonen",
"keyboard_shortcuts.notifications": "Meldingen tonen", "keyboard_shortcuts.notifications": "Meldingen tonen",
"keyboard_shortcuts.open_media": "Media openen", "keyboard_shortcuts.open_media": "Media openen",
"keyboard_shortcuts.pinned": "Jouw vastgezette toots tonen", "keyboard_shortcuts.pinned": "Jouw vastgemaakte berichten tonen",
"keyboard_shortcuts.profile": "Gebruikersprofiel auteur openen", "keyboard_shortcuts.profile": "Gebruikersprofiel auteur openen",
"keyboard_shortcuts.reply": "Reageren", "keyboard_shortcuts.reply": "Reageren",
"keyboard_shortcuts.requests": "Jouw volgverzoeken tonen", "keyboard_shortcuts.requests": "Jouw volgverzoeken tonen",
@ -256,7 +256,7 @@
"keyboard_shortcuts.start": "\"Aan de slag\" tonen", "keyboard_shortcuts.start": "\"Aan de slag\" tonen",
"keyboard_shortcuts.toggle_hidden": "Inhoudswaarschuwing tonen/verbergen", "keyboard_shortcuts.toggle_hidden": "Inhoudswaarschuwing tonen/verbergen",
"keyboard_shortcuts.toggle_sensitivity": "Media tonen/verbergen", "keyboard_shortcuts.toggle_sensitivity": "Media tonen/verbergen",
"keyboard_shortcuts.toot": "Nieuwe toot schrijven", "keyboard_shortcuts.toot": "Nieuw bericht schrijven",
"keyboard_shortcuts.unfocus": "Tekst- en zoekveld ontfocussen", "keyboard_shortcuts.unfocus": "Tekst- en zoekveld ontfocussen",
"keyboard_shortcuts.up": "Naar boven in de lijst bewegen", "keyboard_shortcuts.up": "Naar boven in de lijst bewegen",
"lightbox.close": "Sluiten", "lightbox.close": "Sluiten",
@ -289,12 +289,12 @@
"navigation_bar.blocks": "Geblokkeerde gebruikers", "navigation_bar.blocks": "Geblokkeerde gebruikers",
"navigation_bar.bookmarks": "Bladwijzers", "navigation_bar.bookmarks": "Bladwijzers",
"navigation_bar.community_timeline": "Lokale tijdlijn", "navigation_bar.community_timeline": "Lokale tijdlijn",
"navigation_bar.compose": "Nieuw toot schrijven", "navigation_bar.compose": "Nieuw bericht schrijven",
"navigation_bar.direct": "Directe berichten", "navigation_bar.direct": "Directe berichten",
"navigation_bar.discover": "Ontdekken", "navigation_bar.discover": "Ontdekken",
"navigation_bar.domain_blocks": "Geblokkeerde domeinen", "navigation_bar.domain_blocks": "Geblokkeerde domeinen",
"navigation_bar.edit_profile": "Profiel bewerken", "navigation_bar.edit_profile": "Profiel bewerken",
"navigation_bar.explore": "Explore", "navigation_bar.explore": "Verkennen",
"navigation_bar.favourites": "Favorieten", "navigation_bar.favourites": "Favorieten",
"navigation_bar.filters": "Filters", "navigation_bar.filters": "Filters",
"navigation_bar.follow_requests": "Volgverzoeken", "navigation_bar.follow_requests": "Volgverzoeken",
@ -305,23 +305,23 @@
"navigation_bar.logout": "Uitloggen", "navigation_bar.logout": "Uitloggen",
"navigation_bar.mutes": "Genegeerde gebruikers", "navigation_bar.mutes": "Genegeerde gebruikers",
"navigation_bar.personal": "Persoonlijk", "navigation_bar.personal": "Persoonlijk",
"navigation_bar.pins": "Vastgezette toots", "navigation_bar.pins": "Vastgemaakte berichten",
"navigation_bar.preferences": "Instellingen", "navigation_bar.preferences": "Instellingen",
"navigation_bar.public_timeline": "Globale tijdlijn", "navigation_bar.public_timeline": "Globale tijdlijn",
"navigation_bar.security": "Beveiliging", "navigation_bar.security": "Beveiliging",
"notification.admin.sign_up": "{name} signed up", "notification.admin.sign_up": "{name} heeft zich aangemeld",
"notification.favourite": "{name} voegde jouw toot als favoriet toe", "notification.favourite": "{name} voegde jouw bericht als favoriet toe",
"notification.follow": "{name} volgt jou nu", "notification.follow": "{name} volgt jou nu",
"notification.follow_request": "{name} wil jou graag volgen", "notification.follow_request": "{name} wil jou graag volgen",
"notification.mention": "{name} vermeldde jou", "notification.mention": "{name} vermeldde jou",
"notification.own_poll": "Jouw poll is beëindigd", "notification.own_poll": "Jouw poll is beëindigd",
"notification.poll": "Een poll waaraan jij hebt meegedaan is beëindigd", "notification.poll": "Een poll waaraan jij hebt meegedaan is beëindigd",
"notification.reblog": "{name} boostte jouw toot", "notification.reblog": "{name} boostte jouw bericht",
"notification.status": "{name} heeft zojuist een toot geplaatst", "notification.status": "{name} heeft zojuist een bericht geplaatst",
"notification.update": "{name} edited a post", "notification.update": "{name} heeft een bericht bewerkt",
"notifications.clear": "Meldingen verwijderen", "notifications.clear": "Meldingen verwijderen",
"notifications.clear_confirmation": "Weet je het zeker dat je al jouw meldingen wilt verwijderen?", "notifications.clear_confirmation": "Weet je het zeker dat je al jouw meldingen wilt verwijderen?",
"notifications.column_settings.admin.sign_up": "New sign-ups:", "notifications.column_settings.admin.sign_up": "Nieuwe aanmeldingen:",
"notifications.column_settings.alert": "Desktopmeldingen", "notifications.column_settings.alert": "Desktopmeldingen",
"notifications.column_settings.favourite": "Favorieten:", "notifications.column_settings.favourite": "Favorieten:",
"notifications.column_settings.filter_bar.advanced": "Alle categorieën tonen", "notifications.column_settings.filter_bar.advanced": "Alle categorieën tonen",
@ -335,10 +335,10 @@
"notifications.column_settings.reblog": "Boosts:", "notifications.column_settings.reblog": "Boosts:",
"notifications.column_settings.show": "In kolom tonen", "notifications.column_settings.show": "In kolom tonen",
"notifications.column_settings.sound": "Geluid afspelen", "notifications.column_settings.sound": "Geluid afspelen",
"notifications.column_settings.status": "Nieuwe toots:", "notifications.column_settings.status": "Nieuwe berichten:",
"notifications.column_settings.unread_notifications.category": "Ongelezen meldingen", "notifications.column_settings.unread_notifications.category": "Ongelezen meldingen",
"notifications.column_settings.unread_notifications.highlight": "Ongelezen meldingen markeren", "notifications.column_settings.unread_notifications.highlight": "Ongelezen meldingen markeren",
"notifications.column_settings.update": "Edits:", "notifications.column_settings.update": "Bewerkingen:",
"notifications.filter.all": "Alles", "notifications.filter.all": "Alles",
"notifications.filter.boosts": "Boosts", "notifications.filter.boosts": "Boosts",
"notifications.filter.favourites": "Favorieten", "notifications.filter.favourites": "Favorieten",
@ -365,7 +365,7 @@
"poll.votes": "{votes, plural, one {# stem} other {# stemmen}}", "poll.votes": "{votes, plural, one {# stem} other {# stemmen}}",
"poll_button.add_poll": "Poll toevoegen", "poll_button.add_poll": "Poll toevoegen",
"poll_button.remove_poll": "Poll verwijderen", "poll_button.remove_poll": "Poll verwijderen",
"privacy.change": "Zichtbaarheid van toot aanpassen", "privacy.change": "Zichtbaarheid van bericht aanpassen",
"privacy.direct.long": "Alleen aan vermelde gebruikers tonen", "privacy.direct.long": "Alleen aan vermelde gebruikers tonen",
"privacy.direct.short": "Direct", "privacy.direct.short": "Direct",
"privacy.private.long": "Alleen aan volgers tonen", "privacy.private.long": "Alleen aan volgers tonen",
@ -378,100 +378,100 @@
"regeneration_indicator.label": "Aan het laden…", "regeneration_indicator.label": "Aan het laden…",
"regeneration_indicator.sublabel": "Jouw tijdlijn wordt aangemaakt!", "regeneration_indicator.sublabel": "Jouw tijdlijn wordt aangemaakt!",
"relative_time.days": "{number}d", "relative_time.days": "{number}d",
"relative_time.full.days": "{number, plural, one {# day} other {# days}} ago", "relative_time.full.days": "{number, plural, one {# dag} other {# dagen}} geleden",
"relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago", "relative_time.full.hours": "{number, plural, one {# uur} other {# uur}} geleden",
"relative_time.full.just_now": "just now", "relative_time.full.just_now": "zojuist",
"relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago", "relative_time.full.minutes": "{number, plural, one {# minuut} other {# minuten}} geleden",
"relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago", "relative_time.full.seconds": "{number, plural, one {# seconde} other {# seconden}} geleden",
"relative_time.hours": "{number}u", "relative_time.hours": "{number}u",
"relative_time.just_now": "nu", "relative_time.just_now": "nu",
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"relative_time.today": "vandaag", "relative_time.today": "vandaag",
"reply_indicator.cancel": "Annuleren", "reply_indicator.cancel": "Annuleren",
"report.block": "Block", "report.block": "Blokkeren",
"report.block_explanation": "You will not see their posts. They will not be able to see your posts or follow you. They will be able to tell that they are blocked.", "report.block_explanation": "Je kunt diens berichten niet zien. Je kunt door diegene niet gevolgd worden en jouw berichten zijn onzichtbaar. Diegene kan zien dat die door jou is geblokkeerd.",
"report.categories.other": "Other", "report.categories.other": "Overig",
"report.categories.spam": "Spam", "report.categories.spam": "Spam",
"report.categories.violation": "Content violates one or more server rules", "report.categories.violation": "De inhoud overtreedt een of meerdere serverregels",
"report.category.subtitle": "Choose the best match", "report.category.subtitle": "Kies wat het meeste overeenkomt",
"report.category.title": "Tell us what's going on with this {type}", "report.category.title": "Vertel ons wat er met dit {type} aan de hand is",
"report.category.title_account": "profile", "report.category.title_account": "profiel",
"report.category.title_status": "post", "report.category.title_status": "bericht",
"report.close": "Done", "report.close": "Klaar",
"report.comment.title": "Is there anything else you think we should know?", "report.comment.title": "Zijn er nog andere dingen waarvan je denkt dat wij dat moeten weten?",
"report.forward": "Naar {target} doorsturen", "report.forward": "Naar {target} doorsturen",
"report.forward_hint": "Het account bevindt zich op een andere server. Wil je daar eveneens een geanonimiseerde kopie van deze rapportage naar toe sturen?", "report.forward_hint": "Het account bevindt zich op een andere server. Wil je daar eveneens een geanonimiseerde kopie van deze rapportage naar toe sturen?",
"report.mute": "Mute", "report.mute": "Negeren",
"report.mute_explanation": "You will not see their posts. They can still follow you and see your posts and will not know that they are muted.", "report.mute_explanation": "Je kunt diens berichten niet zien. Je kunt nog wel gevolgd worden en jouw berichten zijn nog zichtbaar, maar diegene kan niet zien dat die wordt genegeerd.",
"report.next": "Next", "report.next": "Volgende",
"report.placeholder": "Extra opmerkingen", "report.placeholder": "Extra opmerkingen",
"report.reasons.dislike": "I don't like it", "report.reasons.dislike": "Ik vind het niet leuk",
"report.reasons.dislike_description": "It is not something you want to see", "report.reasons.dislike_description": "Het is iets wat je niet wilt zien",
"report.reasons.other": "It's something else", "report.reasons.other": "Het is iets anders",
"report.reasons.other_description": "The issue does not fit into other categories", "report.reasons.other_description": "Het probleem past niet in een andere categorie",
"report.reasons.spam": "It's spam", "report.reasons.spam": "Het is spam",
"report.reasons.spam_description": "Malicious links, fake engagement, or repetitive replies", "report.reasons.spam_description": "Schadelijke links, reclame, misleiding of herhalende antwoorden",
"report.reasons.violation": "It violates server rules", "report.reasons.violation": "Het schendt de serverregels",
"report.reasons.violation_description": "You are aware that it breaks specific rules", "report.reasons.violation_description": "Je weet dat het specifieke regels schendt",
"report.rules.subtitle": "Select all that apply", "report.rules.subtitle": "Selecteer wat van toepassing is",
"report.rules.title": "Which rules are being violated?", "report.rules.title": "Welke regels worden geschonden?",
"report.statuses.subtitle": "Select all that apply", "report.statuses.subtitle": "Selecteer wat van toepassing is",
"report.statuses.title": "Are there any posts that back up this report?", "report.statuses.title": "Zijn er berichten die deze rapportage ondersteunen?",
"report.submit": "Verzenden", "report.submit": "Verzenden",
"report.target": "{target} rapporteren", "report.target": "{target} rapporteren",
"report.thanks.take_action": "Here are your options for controlling what you see on Mastodon:", "report.thanks.take_action": "Hier zijn jouw opties waarmee je kunt bepalen wat je in Mastodon wilt zien:",
"report.thanks.take_action_actionable": "While we review this, you can take action against @{name}:", "report.thanks.take_action_actionable": "Terwijl wij jouw rapportage beroordelen, kun je deze acties ondernemen tegen @{name}:",
"report.thanks.title": "Don't want to see this?", "report.thanks.title": "Wil je dit niet zien?",
"report.thanks.title_actionable": "Thanks for reporting, we'll look into this.", "report.thanks.title_actionable": "Dank je voor het rapporteren. Wij gaan er naar kijken.",
"report.unfollow": "Unfollow @{name}", "report.unfollow": "@{name} ontvolgen",
"report.unfollow_explanation": "You are following this account. To not see their posts in your home feed anymore, unfollow them.", "report.unfollow_explanation": "Je volgt dit account. Om diens berichten niet meer op jouw starttijdlijn te zien, kun je diegene ontvolgen.",
"search.placeholder": "Zoeken", "search.placeholder": "Zoeken",
"search_popout.search_format": "Geavanceerd zoeken", "search_popout.search_format": "Geavanceerd zoeken",
"search_popout.tips.full_text": "Gebruik gewone tekst om te zoeken in jouw toots, gebooste toots, favorieten en in toots waarin je bent vermeldt, en tevens naar gebruikersnamen, weergavenamen en hashtags.", "search_popout.tips.full_text": "Gebruik gewone tekst om te zoeken in jouw berichten, gebooste berichten, favorieten en in berichten waarin je bent vermeldt, en tevens naar gebruikersnamen, weergavenamen en hashtags.",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "toot", "search_popout.tips.status": "bericht",
"search_popout.tips.text": "Gebruik gewone tekst om te zoeken op weergavenamen, gebruikersnamen en hashtags", "search_popout.tips.text": "Gebruik gewone tekst om te zoeken op weergavenamen, gebruikersnamen en hashtags",
"search_popout.tips.user": "gebruiker", "search_popout.tips.user": "gebruiker",
"search_results.accounts": "Gebruikers", "search_results.accounts": "Gebruikers",
"search_results.all": "All", "search_results.all": "Alles",
"search_results.hashtags": "Hashtags", "search_results.hashtags": "Hashtags",
"search_results.nothing_found": "Could not find anything for these search terms", "search_results.nothing_found": "Deze zoektermen leveren geen resultaat op",
"search_results.statuses": "Toots", "search_results.statuses": "Berichten",
"search_results.statuses_fts_disabled": "Het zoeken in toots is op deze Mastodon-server niet ingeschakeld.", "search_results.statuses_fts_disabled": "Het zoeken in berichten is op deze Mastodon-server niet ingeschakeld.",
"search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}", "search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}",
"status.admin_account": "Moderatie-omgeving van @{name} openen", "status.admin_account": "Moderatie-omgeving van @{name} openen",
"status.admin_status": "Deze toot in de moderatie-omgeving openen", "status.admin_status": "Dit bericht in de moderatie-omgeving openen",
"status.block": "@{name} blokkeren", "status.block": "@{name} blokkeren",
"status.bookmark": "Bladwijzer toevoegen", "status.bookmark": "Bladwijzer toevoegen",
"status.cancel_reblog_private": "Niet langer boosten", "status.cancel_reblog_private": "Niet langer boosten",
"status.cannot_reblog": "Deze toot kan niet geboost worden", "status.cannot_reblog": "Dit bericht kan niet geboost worden",
"status.copy": "Link naar toot kopiëren", "status.copy": "Link naar bericht kopiëren",
"status.delete": "Verwijderen", "status.delete": "Verwijderen",
"status.detailed_status": "Uitgebreide gespreksweergave", "status.detailed_status": "Uitgebreide gespreksweergave",
"status.direct": "@{name} een direct bericht sturen", "status.direct": "@{name} een direct bericht sturen",
"status.edit": "Edit", "status.edit": "Bewerken",
"status.edited": "Edited {date}", "status.edited": "Bewerkt op {date}",
"status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}", "status.edited_x_times": "{count, plural, one {{count} keer} other {{count} keer}} bewerkt",
"status.embed": "Insluiten", "status.embed": "Insluiten",
"status.favourite": "Favoriet", "status.favourite": "Favoriet",
"status.filtered": "Gefilterd", "status.filtered": "Gefilterd",
"status.history.created": "{name} created {date}", "status.history.created": "{name} plaatste dit {date}",
"status.history.edited": "{name} edited {date}", "status.history.edited": "{name} bewerkte dit {date}",
"status.load_more": "Meer laden", "status.load_more": "Meer laden",
"status.media_hidden": "Media verborgen", "status.media_hidden": "Media verborgen",
"status.mention": "@{name} vermelden", "status.mention": "@{name} vermelden",
"status.more": "Meer", "status.more": "Meer",
"status.mute": "@{name} negeren", "status.mute": "@{name} negeren",
"status.mute_conversation": "Negeer gesprek", "status.mute_conversation": "Negeer gesprek",
"status.open": "Volledige toot tonen", "status.open": "Volledig bericht tonen",
"status.pin": "Aan profielpagina vastmaken", "status.pin": "Aan profielpagina vastmaken",
"status.pinned": "Vastgemaakte toot", "status.pinned": "Vastgemaakt bericht",
"status.read_more": "Meer lezen", "status.read_more": "Meer lezen",
"status.reblog": "Boosten", "status.reblog": "Boosten",
"status.reblog_private": "Boost naar oorspronkelijke ontvangers", "status.reblog_private": "Boost naar oorspronkelijke ontvangers",
"status.reblogged_by": "{name} boostte", "status.reblogged_by": "{name} boostte",
"status.reblogs.empty": "Niemand heeft deze toot nog geboost. Wanneer iemand dit doet, valt dat hier te zien.", "status.reblogs.empty": "Niemand heeft dit bericht nog geboost. Wanneer iemand dit doet, valt dat hier te zien.",
"status.redraft": "Verwijderen en herschrijven", "status.redraft": "Verwijderen en herschrijven",
"status.remove_bookmark": "Bladwijzer verwijderen", "status.remove_bookmark": "Bladwijzer verwijderen",
"status.reply": "Reageren", "status.reply": "Reageren",
@ -502,7 +502,7 @@
"timeline_hint.remote_resource_not_displayed": "{resource} van andere servers worden niet getoond.", "timeline_hint.remote_resource_not_displayed": "{resource} van andere servers worden niet getoond.",
"timeline_hint.resources.followers": "Volgers", "timeline_hint.resources.followers": "Volgers",
"timeline_hint.resources.follows": "Volgend", "timeline_hint.resources.follows": "Volgend",
"timeline_hint.resources.statuses": "Oudere toots", "timeline_hint.resources.statuses": "Oudere berichten",
"trends.counter_by_accounts": "{count, plural, one {{counter} persoon} other {{counter} personen}} zijn aan het praten", "trends.counter_by_accounts": "{count, plural, one {{counter} persoon} other {{counter} personen}} zijn aan het praten",
"trends.trending_now": "Huidige trends", "trends.trending_now": "Huidige trends",
"ui.beforeunload": "Je concept gaat verloren wanneer je Mastodon verlaat.", "ui.beforeunload": "Je concept gaat verloren wanneer je Mastodon verlaat.",

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class ActivityPub::Activity::Create < ActivityPub::Activity class ActivityPub::Activity::Create < ActivityPub::Activity
include FormattingHelper
def perform def perform
dereference_object! dereference_object!
@ -367,7 +369,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
end end
def converted_text def converted_text
Formatter.instance.linkify([@status_parser.title.presence, @status_parser.spoiler_text.presence, @status_parser.url || @status_parser.uri].compact.join("\n\n")) linkify([@status_parser.title.presence, @status_parser.spoiler_text.presence, @status_parser.url || @status_parser.uri].compact.join("\n\n"))
end end
def unsupported_media_type?(mime_type) def unsupported_media_type?(mime_type)

View File

@ -27,7 +27,9 @@ class ActivityPub::Parser::MediaAttachmentParser
end end
def description def description
@json['summary'].presence || @json['name'].presence str = @json['summary'].presence || @json['name'].presence
str = str.strip[0...MediaAttachment::MAX_DESCRIPTION_LENGTH] if str.present?
str
end end
def focus def focus

View File

@ -5,6 +5,7 @@ class Admin::SystemCheck
Admin::SystemCheck::DatabaseSchemaCheck, Admin::SystemCheck::DatabaseSchemaCheck,
Admin::SystemCheck::SidekiqProcessCheck, Admin::SystemCheck::SidekiqProcessCheck,
Admin::SystemCheck::RulesCheck, Admin::SystemCheck::RulesCheck,
Admin::SystemCheck::ElasticsearchCheck,
].freeze ].freeze
def self.perform def self.perform

View File

@ -0,0 +1,39 @@
# frozen_string_literal: true
class Admin::SystemCheck::ElasticsearchCheck < Admin::SystemCheck::BaseCheck
def pass?
return true unless Chewy.enabled?
running_version.present? && compatible_version?
end
def message
if running_version.present?
Admin::SystemCheck::Message.new(:elasticsearch_version_check, I18n.t('admin.system_checks.elasticsearch_version_check.version_comparison', running_version: running_version, required_version: required_version))
else
Admin::SystemCheck::Message.new(:elasticsearch_running_check)
end
end
private
def running_version
@running_version ||= begin
Chewy.client.info['version']['number']
rescue Faraday::ConnectionFailed
nil
end
end
def required_version
'7.x'
end
def compatible_version?
Gem::Version.new(running_version) >= Gem::Version.new(required_version)
end
def missing_queues
@missing_queues ||= Sidekiq::ProcessSet.new.reduce(SIDEKIQ_QUEUES) { |queues, process| queues - process['queues'] }
end
end

View File

@ -0,0 +1,98 @@
# frozen_string_literal: true
class EmojiFormatter
include RoutingHelper
DISALLOWED_BOUNDING_REGEX = /[[:alnum:]:]/.freeze
attr_reader :html, :custom_emojis, :options
# @param [ActiveSupport::SafeBuffer] html
# @param [Array<CustomEmoji>] custom_emojis
# @param [Hash] options
# @option options [Boolean] :animate
def initialize(html, custom_emojis, options = {})
raise ArgumentError unless html.html_safe?
@html = html
@custom_emojis = custom_emojis
@options = options
end
def to_s
return html if custom_emojis.empty? || html.blank?
i = -1
tag_open_index = nil
inside_shortname = false
shortname_start_index = -1
invisible_depth = 0
last_index = 0
result = ''.dup
while i + 1 < html.size
i += 1
if invisible_depth.zero? && inside_shortname && html[i] == ':'
inside_shortname = false
shortcode = html[shortname_start_index + 1..i - 1]
char_after = html[i + 1]
next unless (char_after.nil? || !DISALLOWED_BOUNDING_REGEX.match?(char_after)) && (emoji = emoji_map[shortcode])
result << html[last_index..shortname_start_index - 1] if shortname_start_index.positive?
result << image_for_emoji(shortcode, emoji)
last_index = i + 1
elsif tag_open_index && html[i] == '>'
tag = html[tag_open_index..i]
tag_open_index = nil
if invisible_depth.positive?
invisible_depth += count_tag_nesting(tag)
elsif tag == '<span class="invisible">'
invisible_depth = 1
end
elsif html[i] == '<'
tag_open_index = i
inside_shortname = false
elsif !tag_open_index && html[i] == ':' && (i.zero? || !DISALLOWED_BOUNDING_REGEX.match?(html[i - 1]))
inside_shortname = true
shortname_start_index = i
end
end
result << html[last_index..-1]
result.html_safe # rubocop:disable Rails/OutputSafety
end
private
def emoji_map
@emoji_map ||= custom_emojis.each_with_object({}) { |e, h| h[e.shortcode] = [full_asset_url(e.image.url), full_asset_url(e.image.url(:static))] }
end
def count_tag_nesting(tag)
if tag[1] == '/'
-1
elsif tag[-2] == '/'
0
else
1
end
end
def image_for_emoji(shortcode, emoji)
original_url, static_url = emoji
if animate?
image_tag(original_url, draggable: false, class: 'emojione', alt: ":#{shortcode}:", title: ":#{shortcode}:")
else
image_tag(original_url, draggable: false, class: 'emojione custom-emoji', alt: ":#{shortcode}:", title: ":#{shortcode}:", data: { original: original_url, static: static_url })
end
end
def animate?
@options[:animate]
end
end

View File

@ -5,18 +5,34 @@ module Extractor
module_function module_function
# :yields: username, list_slug, start, end def extract_entities_with_indices(text, options = {}, &block)
entities = begin
extract_urls_with_indices(text, options) +
extract_hashtags_with_indices(text, check_url_overlap: false) +
extract_mentions_or_lists_with_indices(text) +
extract_extra_uris_with_indices(text)
end
return [] if entities.empty?
entities = remove_overlapping_entities(entities)
entities.each(&block) if block_given?
entities
end
def extract_mentions_or_lists_with_indices(text) def extract_mentions_or_lists_with_indices(text)
return [] unless Twitter::TwitterText::Regex[:at_signs].match?(text) return [] unless text && Twitter::TwitterText::Regex[:at_signs].match?(text)
possible_entries = [] possible_entries = []
text.to_s.scan(Account::MENTION_RE) do |screen_name, _| text.scan(Account::MENTION_RE) do |screen_name, _|
match_data = $LAST_MATCH_INFO match_data = $LAST_MATCH_INFO
after = $' after = $'
unless Twitter::TwitterText::Regex[:end_mention_match].match?(after) unless Twitter::TwitterText::Regex[:end_mention_match].match?(after)
start_position = match_data.char_begin(1) - 1 start_position = match_data.char_begin(1) - 1
end_position = match_data.char_end(1) end_position = match_data.char_end(1)
possible_entries << { possible_entries << {
screen_name: screen_name, screen_name: screen_name,
indices: [start_position, end_position], indices: [start_position, end_position],
@ -29,18 +45,21 @@ module Extractor
yield mention[:screen_name], mention[:indices].first, mention[:indices].last yield mention[:screen_name], mention[:indices].first, mention[:indices].last
end end
end end
possible_entries possible_entries
end end
def extract_hashtags_with_indices(text, **) def extract_hashtags_with_indices(text, _options = {})
return [] unless /#/.match?(text) return [] unless text&.index('#')
possible_entries = []
tags = []
text.scan(Tag::HASHTAG_RE) do |hash_text, _| text.scan(Tag::HASHTAG_RE) do |hash_text, _|
match_data = $LAST_MATCH_INFO match_data = $LAST_MATCH_INFO
start_position = match_data.char_begin(1) - 1 start_position = match_data.char_begin(1) - 1
end_position = match_data.char_end(1) end_position = match_data.char_end(1)
after = $' after = $'
if %r{\A://}.match?(after) if %r{\A://}.match?(after)
hash_text.match(/(.+)(https?\Z)/) do |matched| hash_text.match(/(.+)(https?\Z)/) do |matched|
hash_text = matched[1] hash_text = matched[1]
@ -48,17 +67,48 @@ module Extractor
end end
end end
tags << { possible_entries << {
hashtag: hash_text, hashtag: hash_text,
indices: [start_position, end_position], indices: [start_position, end_position],
} }
end end
tags.each { |tag| yield tag[:hashtag], tag[:indices].first, tag[:indices].last } if block_given? if block_given?
tags possible_entries.each do |tag|
yield tag[:hashtag], tag[:indices].first, tag[:indices].last
end
end
possible_entries
end end
def extract_cashtags_with_indices(_text) def extract_cashtags_with_indices(_text)
[] # always returns empty array []
end
def extract_extra_uris_with_indices(text)
return [] unless text&.index(':')
possible_entries = []
text.scan(Twitter::TwitterText::Regex[:valid_extended_uri]) do
valid_uri_match_data = $LAST_MATCH_INFO
start_position = valid_uri_match_data.char_begin(3)
end_position = valid_uri_match_data.char_end(3)
possible_entries << {
url: valid_uri_match_data[3],
indices: [start_position, end_position],
}
end
if block_given?
possible_entries.each do |url|
yield url[:url], url[:indices].first, url[:indices].last
end
end
possible_entries
end end
end end

View File

@ -5,6 +5,7 @@ require 'singleton'
class FeedManager class FeedManager
include Singleton include Singleton
include Redisable include Redisable
include FormattingHelper
# Maximum number of items stored in a single feed # Maximum number of items stored in a single feed
MAX_ITEMS = 400 MAX_ITEMS = 400
@ -503,7 +504,7 @@ class FeedManager
status = status.reblog if status.reblog? status = status.reblog if status.reblog?
combined_text = [ combined_text = [
Formatter.instance.plaintext(status), extract_plain_text(status.text, status.local?),
status.spoiler_text, status.spoiler_text,
status.preloadable_poll ? status.preloadable_poll.options.join("\n\n") : nil, status.preloadable_poll ? status.preloadable_poll.options.join("\n\n") : nil,
status.ordered_media_attachments.map(&:description).join("\n\n"), status.ordered_media_attachments.map(&:description).join("\n\n"),

View File

@ -1,382 +0,0 @@
# frozen_string_literal: true
require 'singleton'
class HTMLRenderer < Redcarpet::Render::HTML
def block_code(code, language)
"<pre><code>#{encode(code).gsub("\n", "<br/>")}</code></pre>"
end
def autolink(link, link_type)
return link if link_type == :email
Formatter.instance.link_url(link)
rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
encode(link)
end
private
def html_entities
@html_entities ||= HTMLEntities.new
end
def encode(html)
html_entities.encode(html)
end
end
class Formatter
include Singleton
include RoutingHelper
include ActionView::Helpers::TextHelper
def format(status, **options)
if status.respond_to?(:reblog?) && status.reblog?
prepend_reblog = status.reblog.account.acct
status = status.proper
else
prepend_reblog = false
end
raw_content = status.text
if options[:inline_poll_options] && status.preloadable_poll
raw_content = raw_content + "\n\n" + status.preloadable_poll.options.map { |title| "[ ] #{title}" }.join("\n")
end
return '' if raw_content.blank?
unless status.local?
html = reformat(raw_content)
html = encode_custom_emojis(html, status.emojis, options[:autoplay]) if options[:custom_emojify]
return html.html_safe # rubocop:disable Rails/OutputSafety
end
linkable_accounts = status.respond_to?(:active_mentions) ? status.active_mentions.map(&:account) : []
linkable_accounts << status.account
html = raw_content
html = "RT @#{prepend_reblog} #{html}" if prepend_reblog
html = format_markdown(html) if status.content_type == 'text/markdown'
html = encode_and_link_urls(html, linkable_accounts, keep_html: %w(text/markdown text/html).include?(status.content_type))
html = reformat(html, true) if %w(text/markdown text/html).include?(status.content_type)
html = encode_custom_emojis(html, status.emojis, options[:autoplay]) if options[:custom_emojify]
unless %w(text/markdown text/html).include?(status.content_type)
html = simple_format(html, {}, sanitize: false)
html = html.delete("\n")
end
html.html_safe # rubocop:disable Rails/OutputSafety
end
def format_markdown(html)
html = markdown_formatter.render(html)
html.delete("\r").delete("\n")
end
def reformat(html, outgoing = false)
sanitize(html, Sanitize::Config::MASTODON_STRICT.merge(outgoing: outgoing))
rescue ArgumentError
''
end
def plaintext(status)
return status.text if status.local?
text = status.text.gsub(/(<br \/>|<br>|<\/p>)+/) { |match| "#{match}\n" }
strip_tags(text)
end
def simplified_format(account, **options)
return '' if account.note.blank?
html = account.local? ? linkify(account.note) : reformat(account.note)
html = encode_custom_emojis(html, account.emojis, options[:autoplay]) if options[:custom_emojify]
html.html_safe # rubocop:disable Rails/OutputSafety
end
def sanitize(html, config)
Sanitize.fragment(html, config)
end
def format_spoiler(status, **options)
html = encode(status.spoiler_text)
html = encode_custom_emojis(html, status.emojis, options[:autoplay])
html.html_safe # rubocop:disable Rails/OutputSafety
end
def format_poll_option(status, option, **options)
html = encode(option.title)
html = encode_custom_emojis(html, status.emojis, options[:autoplay])
html.html_safe # rubocop:disable Rails/OutputSafety
end
def format_display_name(account, **options)
html = encode(account.display_name.presence || account.username)
html = encode_custom_emojis(html, account.emojis, options[:autoplay]) if options[:custom_emojify]
html.html_safe # rubocop:disable Rails/OutputSafety
end
def format_field(account, str, **options)
html = account.local? ? encode_and_link_urls(str, me: true, with_domain: true) : reformat(str)
html = encode_custom_emojis(html, account.emojis, options[:autoplay]) if options[:custom_emojify]
html.html_safe # rubocop:disable Rails/OutputSafety
end
def linkify(text)
html = encode_and_link_urls(text)
html = simple_format(html, {}, sanitize: false)
html = html.delete("\n")
html.html_safe # rubocop:disable Rails/OutputSafety
end
def link_url(url)
"<a href=\"#{encode(url)}\" target=\"blank\" rel=\"nofollow noopener noreferrer\">#{link_html(url)}</a>"
end
private
def markdown_formatter
extensions = {
autolink: true,
no_intra_emphasis: true,
fenced_code_blocks: true,
disable_indented_code_blocks: true,
strikethrough: true,
lax_spacing: true,
space_after_headers: true,
superscript: true,
underline: true,
highlight: true,
footnotes: false,
}
renderer = HTMLRenderer.new({
filter_html: false,
escape_html: false,
no_images: true,
no_styles: true,
safe_links_only: true,
hard_wrap: true,
link_attributes: { target: '_blank', rel: 'nofollow noopener' },
})
Redcarpet::Markdown.new(renderer, extensions)
end
def html_entities
@html_entities ||= HTMLEntities.new
end
def encode(html)
html_entities.encode(html)
end
def encode_and_link_urls(html, accounts = nil, options = {})
if accounts.is_a?(Hash)
options = accounts
accounts = nil
end
entities = options[:keep_html] ? html_friendly_extractor(html) : utf8_friendly_extractor(html, extract_url_without_protocol: false)
rewrite(html.dup, entities, options[:keep_html]) do |entity|
if entity[:url]
link_to_url(entity, options)
elsif entity[:hashtag]
link_to_hashtag(entity)
elsif entity[:screen_name]
link_to_mention(entity, accounts, options)
end
end
end
def count_tag_nesting(tag)
if tag[1] == '/' then -1
elsif tag[-2] == '/' then 0
else 1
end
end
# rubocop:disable Metrics/BlockNesting
def encode_custom_emojis(html, emojis, animate = false)
return html if emojis.empty?
emoji_map = emojis.each_with_object({}) { |e, h| h[e.shortcode] = [full_asset_url(e.image.url), full_asset_url(e.image.url(:static))] }
i = -1
tag_open_index = nil
inside_shortname = false
shortname_start_index = -1
invisible_depth = 0
while i + 1 < html.size
i += 1
if invisible_depth.zero? && inside_shortname && html[i] == ':'
shortcode = html[shortname_start_index + 1..i - 1]
emoji = emoji_map[shortcode]
if emoji
original_url, static_url = emoji
replacement = begin
if animate
image_tag(original_url, draggable: false, class: 'emojione', alt: ":#{shortcode}:", title: ":#{shortcode}:")
else
image_tag(original_url, draggable: false, class: 'emojione custom-emoji', alt: ":#{shortcode}:", title: ":#{shortcode}:", data: { original: original_url, static: static_url })
end
end
before_html = shortname_start_index.positive? ? html[0..shortname_start_index - 1] : ''
html = before_html + replacement + html[i + 1..-1]
i += replacement.size - (shortcode.size + 2) - 1
else
i -= 1
end
inside_shortname = false
elsif tag_open_index && html[i] == '>'
tag = html[tag_open_index..i]
tag_open_index = nil
if invisible_depth.positive?
invisible_depth += count_tag_nesting(tag)
elsif tag == '<span class="invisible">'
invisible_depth = 1
end
elsif html[i] == '<'
tag_open_index = i
inside_shortname = false
elsif !tag_open_index && html[i] == ':'
inside_shortname = true
shortname_start_index = i
end
end
html
end
# rubocop:enable Metrics/BlockNesting
def rewrite(text, entities, keep_html = false)
text = text.to_s
# Sort by start index
entities = entities.sort_by do |entity|
indices = entity.respond_to?(:indices) ? entity.indices : entity[:indices]
indices.first
end
result = []
last_index = entities.reduce(0) do |index, entity|
indices = entity.respond_to?(:indices) ? entity.indices : entity[:indices]
result << (keep_html ? text[index...indices.first] : encode(text[index...indices.first]))
result << yield(entity)
indices.last
end
result << (keep_html ? text[last_index..-1] : encode(text[last_index..-1]))
result.flatten.join
end
def utf8_friendly_extractor(text, options = {})
# Note: I couldn't obtain list_slug with @user/list-name format
# for mention so this requires additional check
special = Extractor.extract_urls_with_indices(text, options)
standard = Extractor.extract_entities_with_indices(text, options)
extra = Extractor.extract_extra_uris_with_indices(text, options)
Extractor.remove_overlapping_entities(special + standard + extra)
end
def html_friendly_extractor(html, options = {})
gaps = []
total_offset = 0
escaped = html.gsub(/<[^>]*>|&#[0-9]+;/) do |match|
total_offset += match.length - 1
end_offset = Regexp.last_match.end(0)
gaps << [end_offset - total_offset, total_offset]
"\u200b"
end
entities = Extractor.extract_hashtags_with_indices(escaped, :check_url_overlap => false) +
Extractor.extract_mentions_or_lists_with_indices(escaped)
Extractor.remove_overlapping_entities(entities).map do |extract|
pos = extract[:indices].first
offset_idx = gaps.rindex { |gap| gap.first <= pos }
offset = offset_idx.nil? ? 0 : gaps[offset_idx].last
next extract.merge(
:indices => [extract[:indices].first + offset, extract[:indices].last + offset]
)
end
end
def link_to_url(entity, options = {})
url = Addressable::URI.parse(entity[:url])
html_attrs = { target: '_blank', rel: 'nofollow noopener noreferrer' }
html_attrs[:rel] = "me #{html_attrs[:rel]}" if options[:me]
Twitter::TwitterText::Autolink.send(:link_to_text, entity, link_html(entity[:url]), url, html_attrs)
rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
encode(entity[:url])
end
def link_to_mention(entity, linkable_accounts, options = {})
acct = entity[:screen_name]
return link_to_account(acct, options) unless linkable_accounts
same_username_hits = 0
account = nil
username, domain = acct.split('@')
domain = nil if TagManager.instance.local_domain?(domain)
linkable_accounts.each do |item|
same_username = item.username.casecmp(username).zero?
same_domain = item.domain.nil? ? domain.nil? : item.domain.casecmp(domain)&.zero?
if same_username && !same_domain
same_username_hits += 1
elsif same_username && same_domain
account = item
end
end
account ? mention_html(account, with_domain: same_username_hits.positive? || options[:with_domain]) : "@#{encode(acct)}"
end
def link_to_account(acct, options = {})
username, domain = acct.split('@')
domain = nil if TagManager.instance.local_domain?(domain)
account = EntityCache.instance.mention(username, domain)
account ? mention_html(account, with_domain: options[:with_domain]) : "@#{encode(acct)}"
end
def link_to_hashtag(entity)
hashtag_html(entity[:hashtag])
end
def link_html(url)
url = Addressable::URI.parse(url).to_s
prefix = url.match(/\A(https?:\/\/(www\.)?|xmpp:)/).to_s
text = url[prefix.length, 30]
suffix = url[prefix.length + 30..-1]
cutoff = url[prefix.length..-1].length > 30
"<span class=\"invisible\">#{encode(prefix)}</span><span class=\"#{cutoff ? 'ellipsis' : ''}\">#{encode(text)}</span><span class=\"invisible\">#{encode(suffix)}</span>"
end
def hashtag_html(tag)
"<a href=\"#{encode(tag_url(tag))}\" class=\"mention hashtag\" rel=\"tag\">#<span>#{encode(tag)}</span></a>"
end
def mention_html(account, with_domain: false)
"<span class=\"h-card\"><a href=\"#{encode(ActivityPub::TagManager.instance.url_for(account))}\" class=\"u-url mention\">@<span>#{encode(with_domain ? account.pretty_acct : account.username)}</span></a></span>"
end
end

View File

@ -0,0 +1,38 @@
# frozen_string_literal: true
class HtmlAwareFormatter
attr_reader :text, :local, :options
alias local? local
# @param [String] text
# @param [Boolean] local
# @param [Hash] options
def initialize(text, local, options = {})
@text = text
@local = local
@options = options
end
def to_s
return ''.html_safe if text.blank?
if local?
linkify
else
reformat.html_safe # rubocop:disable Rails/OutputSafety
end
rescue ArgumentError
''.html_safe
end
private
def reformat
Sanitize.fragment(text, Sanitize::Config::MASTODON_STRICT)
end
def linkify
TextFormatter.new(text, options).to_s
end
end

View File

@ -208,7 +208,7 @@ class LinkDetailsExtractor
end end
def valid_url_or_nil(str, same_origin_only: false) def valid_url_or_nil(str, same_origin_only: false)
return if str.blank? return if str.blank? || str == 'null'
url = @original_url + Addressable::URI.parse(str) url = @original_url + Addressable::URI.parse(str)

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
class PlainTextFormatter
include ActionView::Helpers::TextHelper
NEWLINE_TAGS_RE = /(<br \/>|<br>|<\/p>)+/.freeze
attr_reader :text, :local
alias local? local
def initialize(text, local)
@text = text
@local = local
end
def to_s
if local?
text
else
strip_tags(insert_newlines).chomp
end
end
private
def insert_newlines
text.gsub(NEWLINE_TAGS_RE) { |match| "#{match}\n" }
end
end

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class RSS::Serializer class RSS::Serializer
include FormattingHelper
private private
def render_statuses(builder, statuses) def render_statuses(builder, statuses)
@ -9,7 +11,7 @@ class RSS::Serializer
item.title(status_title(status)) item.title(status_title(status))
.link(ActivityPub::TagManager.instance.url_for(status)) .link(ActivityPub::TagManager.instance.url_for(status))
.pub_date(status.created_at) .pub_date(status.created_at)
.description(status.spoiler_text.presence || Formatter.instance.format(status, inline_poll_options: true).to_str) .description(status_description(status))
status.ordered_media_attachments.each do |media| status.ordered_media_attachments.each do |media|
item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, media.file.size) item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, media.file.size)
@ -19,9 +21,8 @@ class RSS::Serializer
end end
def status_title(status) def status_title(status)
return "#{status.account.acct} deleted status" if status.destroyed?
preview = status.proper.spoiler_text.presence || status.proper.text preview = status.proper.spoiler_text.presence || status.proper.text
if preview.length > 30 || preview[0, 30].include?("\n") if preview.length > 30 || preview[0, 30].include?("\n")
preview = preview[0, 30] preview = preview[0, 30]
preview = preview[0, preview.index("\n").presence || 30] + '…' preview = preview[0, preview.index("\n").presence || 30] + '…'
@ -35,4 +36,20 @@ class RSS::Serializer
"#{status.account.acct}: #{preview}" "#{status.account.acct}: #{preview}"
end end
end end
def status_description(status)
if status.proper.spoiler_text?
status.proper.spoiler_text
else
html = status_content_format(status.proper).to_str
after_html = ''
if status.proper.preloadable_poll
poll_options_html = status.proper.preloadable_poll.options.map { |o| "[ ] #{o}" }.join('<br />')
after_html = "<p>#{poll_options_html}</p>"
end
"#{html}#{after_html}"
end
end
end end

158
app/lib/text_formatter.rb Normal file
View File

@ -0,0 +1,158 @@
# frozen_string_literal: true
class TextFormatter
include ActionView::Helpers::TextHelper
include ERB::Util
include RoutingHelper
URL_PREFIX_REGEX = /\A(https?:\/\/(www\.)?|xmpp:)/.freeze
DEFAULT_REL = %w(nofollow noopener noreferrer).freeze
DEFAULT_OPTIONS = {
multiline: true,
}.freeze
attr_reader :text, :options
# @param [String] text
# @param [Hash] options
# @option options [Boolean] :multiline
# @option options [Boolean] :with_domains
# @option options [Boolean] :with_rel_me
# @option options [Array<Account>] :preloaded_accounts
def initialize(text, options = {})
@text = text
@options = DEFAULT_OPTIONS.merge(options)
end
def entities
@entities ||= Extractor.extract_entities_with_indices(text, extract_url_without_protocol: false)
end
def to_s
return ''.html_safe if text.blank?
html = rewrite do |entity|
if entity[:url]
link_to_url(entity)
elsif entity[:hashtag]
link_to_hashtag(entity)
elsif entity[:screen_name]
link_to_mention(entity)
end
end
html = simple_format(html, {}, sanitize: false).delete("\n") if multiline?
html.html_safe # rubocop:disable Rails/OutputSafety
end
private
def rewrite
entities.sort_by! do |entity|
entity[:indices].first
end
result = ''.dup
last_index = entities.reduce(0) do |index, entity|
indices = entity[:indices]
result << h(text[index...indices.first])
result << yield(entity)
indices.last
end
result << h(text[last_index..-1])
result
end
def link_to_url(entity)
url = Addressable::URI.parse(entity[:url]).to_s
rel = with_rel_me? ? (DEFAULT_REL + %w(me)) : DEFAULT_REL
prefix = url.match(URL_PREFIX_REGEX).to_s
display_url = url[prefix.length, 30]
suffix = url[prefix.length + 30..-1]
cutoff = url[prefix.length..-1].length > 30
<<~HTML.squish
<a href="#{h(url)}" target="_blank" rel="#{rel.join(' ')}"><span class="invisible">#{h(prefix)}</span><span class="#{cutoff ? 'ellipsis' : ''}">#{h(display_url)}</span><span class="invisible">#{h(suffix)}</span></a>
HTML
rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
h(entity[:url])
end
def link_to_hashtag(entity)
hashtag = entity[:hashtag]
url = tag_url(hashtag)
<<~HTML.squish
<a href="#{h(url)}" class="mention hashtag" rel="tag">#<span>#{h(hashtag)}</span></a>
HTML
end
def link_to_mention(entity)
username, domain = entity[:screen_name].split('@')
domain = nil if local_domain?(domain)
account = nil
if preloaded_accounts?
same_username_hits = 0
preloaded_accounts.each do |other_account|
same_username = other_account.username.casecmp(username).zero?
same_domain = other_account.domain.nil? ? domain.nil? : other_account.domain.casecmp(domain)&.zero?
if same_username && !same_domain
same_username_hits += 1
elsif same_username && same_domain
account = other_account
end
end
else
account = entity_cache.mention(username, domain)
end
return "@#{h(entity[:screen_name])}" if account.nil?
url = ActivityPub::TagManager.instance.url_for(account)
display_username = same_username_hits&.positive? || with_domains? ? account.pretty_acct : account.username
<<~HTML.squish
<span class="h-card"><a href="#{h(url)}" class="u-url mention">@<span>#{h(display_username)}</span></a></span>
HTML
end
def entity_cache
@entity_cache ||= EntityCache.instance
end
def tag_manager
@tag_manager ||= TagManager.instance
end
delegate :local_domain?, to: :tag_manager
def multiline?
options[:multiline]
end
def with_domains?
options[:with_domains]
end
def with_rel_me?
options[:with_rel_me]
end
def preloaded_accounts
options[:preloaded_accounts]
end
def preloaded_accounts?
preloaded_accounts.present?
end
end

View File

@ -5,6 +5,7 @@ class ApplicationMailer < ActionMailer::Base
helper :application helper :application
helper :instance helper :instance
helper :formatting
protected protected

View File

@ -132,13 +132,13 @@ class Account < ApplicationRecord
:approved?, :approved?,
:pending?, :pending?,
:disabled?, :disabled?,
:unconfirmed?,
:unconfirmed_or_pending?, :unconfirmed_or_pending?,
:role, :role,
:admin?, :admin?,
:moderator?, :moderator?,
:staff?, :staff?,
:locale, :locale,
:hides_network?,
:shows_application?, :shows_application?,
to: :user, to: :user,
prefix: true, prefix: true,

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
module StatusSnapshotConcern
extend ActiveSupport::Concern
included do
has_many :edits, class_name: 'StatusEdit', inverse_of: :status, dependent: :destroy
end
def edited?
edited_at.present?
end
def build_snapshot(account_id: nil, at_time: nil, rate_limit: true)
# We don't use `edits#new` here to avoid it having saved when the
# status is saved, since we want to control that manually
StatusEdit.new(
status_id: id,
text: text,
spoiler_text: spoiler_text,
sensitive: sensitive,
ordered_media_attachment_ids: ordered_media_attachment_ids&.dup || media_attachments.pluck(:id),
media_descriptions: ordered_media_attachments.map(&:description),
poll_options: preloadable_poll&.options&.dup,
account_id: account_id || self.account_id,
content_type: content_type,
created_at: at_time || edited_at,
rate_limit: rate_limit
)
end
def snapshot!(**options)
build_snapshot(**options).save!
end
end

View File

@ -185,7 +185,7 @@ class MediaAttachment < ApplicationRecord
remotable_attachment :thumbnail, IMAGE_LIMIT, suppress_errors: true, download_on_assign: false remotable_attachment :thumbnail, IMAGE_LIMIT, suppress_errors: true, download_on_assign: false
validates :account, presence: true validates :account, presence: true
validates :description, length: { maximum: MAX_DESCRIPTION_LENGTH }, if: :local? validates :description, length: { maximum: MAX_DESCRIPTION_LENGTH }
validates :file, presence: true, if: :local? validates :file, presence: true, if: :local?
validates :thumbnail, absence: true, if: -> { local? && !audio_or_video? } validates :thumbnail, absence: true, if: -> { local? && !audio_or_video? }
@ -258,7 +258,6 @@ class MediaAttachment < ApplicationRecord
after_commit :enqueue_processing, on: :create after_commit :enqueue_processing, on: :create
after_commit :reset_parent_cache, on: :update after_commit :reset_parent_cache, on: :update
before_create :prepare_description, unless: :local?
before_create :set_unknown_type before_create :set_unknown_type
before_create :set_processing before_create :set_processing
@ -306,10 +305,6 @@ class MediaAttachment < ApplicationRecord
self.type = :unknown if file.blank? && !type_changed? self.type = :unknown if file.blank? && !type_changed?
end end
def prepare_description
self.description = description.strip[0...MAX_DESCRIPTION_LENGTH] unless description.nil?
end
def set_type_and_extension def set_type_and_extension
self.type = begin self.type = begin
if VIDEO_MIME_TYPES.include?(file_content_type) if VIDEO_MIME_TYPES.include?(file_content_type)

View File

@ -37,6 +37,7 @@ class Status < ApplicationRecord
include Paginable include Paginable
include Cacheable include Cacheable
include StatusThreadingConcern include StatusThreadingConcern
include StatusSnapshotConcern
include RateLimitable include RateLimitable
rate_limit by: :account, family: :statuses rate_limit by: :account, family: :statuses
@ -61,8 +62,6 @@ class Status < ApplicationRecord
belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :replies, optional: true belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :replies, optional: true
belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblogs, optional: true belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblogs, optional: true
has_many :edits, class_name: 'StatusEdit', inverse_of: :status, dependent: :destroy
has_many :favourites, inverse_of: :status, dependent: :destroy has_many :favourites, inverse_of: :status, dependent: :destroy
has_many :bookmarks, inverse_of: :status, dependent: :destroy has_many :bookmarks, inverse_of: :status, dependent: :destroy
has_many :reblogs, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblog, dependent: :destroy has_many :reblogs, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblog, dependent: :destroy
@ -217,25 +216,6 @@ class Status < ApplicationRecord
public_visibility? || unlisted_visibility? public_visibility? || unlisted_visibility?
end end
def snapshot!(account_id: nil, at_time: nil, rate_limit: true)
edits.create!(
text: text,
spoiler_text: spoiler_text,
sensitive: sensitive,
ordered_media_attachment_ids: ordered_media_attachment_ids || media_attachments.pluck(:id),
media_descriptions: ordered_media_attachments.map(&:description),
poll_options: preloadable_poll&.options,
account_id: account_id || self.account_id,
content_type: content_type,
created_at: at_time || edited_at,
rate_limit: rate_limit
)
end
def edited?
edited_at.present?
end
alias sign? distributable? alias sign? distributable?
def with_media? def with_media?

View File

@ -37,7 +37,7 @@ class Trends::Query
end end
def offset!(value) def offset!(value)
@offset = value @offset = value.to_i
self self
end end
@ -46,7 +46,7 @@ class Trends::Query
end end
def limit!(value) def limit!(value)
@limit = value @limit = value.to_i
self self
end end

View File

@ -208,8 +208,12 @@ class User < ApplicationRecord
confirmed? && approved? && !disabled? && !account.suspended? && !account.memorial? confirmed? && approved? && !disabled? && !account.suspended? && !account.memorial?
end end
def unconfirmed?
!confirmed?
end
def unconfirmed_or_pending? def unconfirmed_or_pending?
!(confirmed? && approved?) unconfirmed? || pending?
end end
def inactive_message def inactive_message

View File

@ -2,6 +2,7 @@
class ActivityPub::ActorSerializer < ActivityPub::Serializer class ActivityPub::ActorSerializer < ActivityPub::Serializer
include RoutingHelper include RoutingHelper
include FormattingHelper
context :security context :security
@ -102,7 +103,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
end end
def summary def summary
object.suspended? ? '' : Formatter.instance.simplified_format(object) object.suspended? ? '' : html_aware_format(object.note, object.local?)
end end
def icon def icon
@ -185,6 +186,8 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
end end
class Account::FieldSerializer < ActivityPub::Serializer class Account::FieldSerializer < ActivityPub::Serializer
include FormattingHelper
attributes :type, :name, :value attributes :type, :name, :value
def type def type
@ -192,7 +195,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
end end
def value def value
Formatter.instance.format_field(object.account, object.value) html_aware_format(object.value, object.account.local?, with_rel_me: true, with_domains: true, multiline: false)
end end
end end

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class ActivityPub::NoteSerializer < ActivityPub::Serializer class ActivityPub::NoteSerializer < ActivityPub::Serializer
include FormattingHelper
context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :direct_message context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :direct_message
attributes :id, :type, :summary, attributes :id, :type, :summary,
@ -50,11 +52,11 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
end end
def content def content
Formatter.instance.format(object) status_content_format(object)
end end
def content_map def content_map
{ object.language => Formatter.instance.format(object) } { object.language => content }
end end
def replies def replies

View File

@ -2,6 +2,7 @@
class REST::AccountSerializer < ActiveModel::Serializer class REST::AccountSerializer < ActiveModel::Serializer
include RoutingHelper include RoutingHelper
include FormattingHelper
attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :group, :created_at, attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :group, :created_at,
:note, :url, :avatar, :avatar_static, :header, :header_static, :note, :url, :avatar, :avatar_static, :header, :header_static,
@ -14,10 +15,12 @@ class REST::AccountSerializer < ActiveModel::Serializer
attribute :suspended, if: :suspended? attribute :suspended, if: :suspended?
class FieldSerializer < ActiveModel::Serializer class FieldSerializer < ActiveModel::Serializer
include FormattingHelper
attributes :name, :value, :verified_at attributes :name, :value, :verified_at
def value def value
Formatter.instance.format_field(object.account, object.value) html_aware_format(object.value, object.account.local?, with_rel_me: true, with_domains: true, multiline: false)
end end
end end
@ -32,7 +35,7 @@ class REST::AccountSerializer < ActiveModel::Serializer
end end
def note def note
object.suspended? ? '' : Formatter.instance.simplified_format(object) object.suspended? ? '' : html_aware_format(object.note, object.local?)
end end
def url def url

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class REST::AnnouncementSerializer < ActiveModel::Serializer class REST::AnnouncementSerializer < ActiveModel::Serializer
include FormattingHelper
attributes :id, :content, :starts_at, :ends_at, :all_day, attributes :id, :content, :starts_at, :ends_at, :all_day,
:published_at, :updated_at :published_at, :updated_at
@ -25,7 +27,7 @@ class REST::AnnouncementSerializer < ActiveModel::Serializer
end end
def content def content
Formatter.instance.linkify(object.text) linkify(object.text)
end end
def reactions def reactions

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class REST::StatusEditSerializer < ActiveModel::Serializer class REST::StatusEditSerializer < ActiveModel::Serializer
include FormattingHelper
has_one :account, serializer: REST::AccountSerializer has_one :account, serializer: REST::AccountSerializer
attributes :content, :spoiler_text, :sensitive, :created_at attributes :content, :spoiler_text, :sensitive, :created_at
@ -11,7 +13,7 @@ class REST::StatusEditSerializer < ActiveModel::Serializer
attribute :poll, if: -> { object.poll_options.present? } attribute :poll, if: -> { object.poll_options.present? }
def content def content
Formatter.instance.format(object) status_content_format(object)
end end
def poll def poll

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class REST::StatusSerializer < ActiveModel::Serializer class REST::StatusSerializer < ActiveModel::Serializer
include FormattingHelper
attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id, attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id,
:sensitive, :spoiler_text, :visibility, :language, :sensitive, :spoiler_text, :visibility, :language,
:uri, :url, :replies_count, :reblogs_count, :uri, :url, :replies_count, :reblogs_count,
@ -73,7 +75,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
end end
def content def content
Formatter.instance.format(object) status_content_format(object)
end end
def url def url

View File

@ -4,6 +4,8 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
include JsonLdHelper include JsonLdHelper
def call(status, json) def call(status, json)
raise ArgumentError, 'Status has unsaved changes' if status.changed?
@json = json @json = json
@status_parser = ActivityPub::Parser::StatusParser.new(@json) @status_parser = ActivityPub::Parser::StatusParser.new(@json)
@uri = @status_parser.uri @uri = @status_parser.uri
@ -17,16 +19,19 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
last_edit_date = status.edited_at.presence || status.created_at last_edit_date = status.edited_at.presence || status.created_at
# Since we rely on tracking of previous changes, ensure clean slate
status.clear_changes_information
# Only allow processing one create/update per status at a time # Only allow processing one create/update per status at a time
RedisLock.acquire(lock_options) do |lock| RedisLock.acquire(lock_options) do |lock|
if lock.acquired? if lock.acquired?
Status.transaction do Status.transaction do
create_previous_edit! record_previous_edit!
update_media_attachments! update_media_attachments!
update_poll! update_poll!
update_immediate_attributes! update_immediate_attributes!
update_metadata! update_metadata!
create_edit! create_edits!
end end
queue_poll_notifications! queue_poll_notifications!
@ -216,19 +221,14 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
{ redis: Redis.current, key: "create:#{@uri}", autorelease: 15.minutes.seconds } { redis: Redis.current, key: "create:#{@uri}", autorelease: 15.minutes.seconds }
end end
def create_previous_edit! def record_previous_edit!
# We only need to create a previous edit when no previous edits exist, e.g. @previous_edit = @status.build_snapshot(at_time: @status.created_at, rate_limit: false) if @status.edits.empty?
# when the status has never been edited. For other cases, we always create
# an edit, so the step can be skipped
return if @status.edits.any?
@status.snapshot!(at_time: @status.created_at, rate_limit: false)
end end
def create_edit! def create_edits!
return unless significant_changes? return unless significant_changes?
@previous_edit&.save!
@status.snapshot!(account_id: @account.id, rate_limit: false) @status.snapshot!(account_id: @account.id, rate_limit: false)
end end

View File

@ -134,7 +134,7 @@ class FetchLinkCardService < BaseService
when 'video' when 'video'
@card.width = embed[:width].presence || 0 @card.width = embed[:width].presence || 0
@card.height = embed[:height].presence || 0 @card.height = embed[:height].presence || 0
@card.html = Formatter.instance.sanitize(embed[:html], Sanitize::Config::MASTODON_OEMBED) @card.html = Sanitize.fragment(embed[:html], Sanitize::Config::MASTODON_OEMBED)
@card.image_remote_url = (url + embed[:thumbnail_url]).to_s if embed[:thumbnail_url].present? @card.image_remote_url = (url + embed[:thumbnail_url]).to_s if embed[:thumbnail_url].present?
when 'rich' when 'rich'
# Most providers rely on <script> tags, which is a no-no # Most providers rely on <script> tags, which is a no-no

View File

@ -4,6 +4,8 @@ class UpdateStatusService < BaseService
include Redisable include Redisable
include LanguagesHelper include LanguagesHelper
class NoChangesSubmittedError < StandardError; end
# @param [Status] status # @param [Status] status
# @param [Integer] account_id # @param [Integer] account_id
# @param [Hash] options # @param [Hash] options
@ -18,6 +20,8 @@ class UpdateStatusService < BaseService
@status = status @status = status
@options = options @options = options
@account_id = account_id @account_id = account_id
@media_attachments_changed = false
@poll_changed = false
Status.transaction do Status.transaction do
create_previous_edit! create_previous_edit!
@ -33,18 +37,24 @@ class UpdateStatusService < BaseService
broadcast_updates! broadcast_updates!
@status @status
rescue NoChangesSubmittedError
# For calls that result in no changes, swallow the error
# but get back to the original state
@status.reload
end end
private private
def update_media_attachments! def update_media_attachments!
previous_media_attachments = @status.media_attachments.to_a previous_media_attachments = @status.ordered_media_attachments.to_a
next_media_attachments = validate_media! next_media_attachments = validate_media!
added_media_attachments = next_media_attachments - previous_media_attachments added_media_attachments = next_media_attachments - previous_media_attachments
MediaAttachment.where(id: added_media_attachments.map(&:id)).update_all(status_id: @status.id) MediaAttachment.where(id: added_media_attachments.map(&:id)).update_all(status_id: @status.id)
@status.ordered_media_attachment_ids = (@options[:media_ids] || []).map(&:to_i) & next_media_attachments.map(&:id) @status.ordered_media_attachment_ids = (@options[:media_ids] || []).map(&:to_i) & next_media_attachments.map(&:id)
@media_attachments_changed = previous_media_attachments.map(&:id) != @status.ordered_media_attachment_ids
@status.media_attachments.reload @status.media_attachments.reload
end end
@ -70,20 +80,23 @@ class UpdateStatusService < BaseService
# If for some reasons the options were changed, it invalidates all previous # If for some reasons the options were changed, it invalidates all previous
# votes, so we need to remove them # votes, so we need to remove them
poll_changed = true if @options[:poll][:options] != poll.options || ActiveModel::Type::Boolean.new.cast(@options[:poll][:multiple]) != poll.multiple @poll_changed = true if @options[:poll][:options] != poll.options || ActiveModel::Type::Boolean.new.cast(@options[:poll][:multiple]) != poll.multiple
poll.options = @options[:poll][:options] poll.options = @options[:poll][:options]
poll.hide_totals = @options[:poll][:hide_totals] || false poll.hide_totals = @options[:poll][:hide_totals] || false
poll.multiple = @options[:poll][:multiple] || false poll.multiple = @options[:poll][:multiple] || false
poll.expires_in = @options[:poll][:expires_in] poll.expires_in = @options[:poll][:expires_in]
poll.reset_votes! if poll_changed poll.reset_votes! if @poll_changed
poll.save! poll.save!
@status.poll_id = poll.id @status.poll_id = poll.id
elsif previous_poll.present? elsif previous_poll.present?
previous_poll.destroy previous_poll.destroy
@poll_changed = true
@status.poll_id = nil @status.poll_id = nil
end end
@poll_changed = true if @previous_expires_at != @status.preloadable_poll&.expires_at
end end
def update_immediate_attributes! def update_immediate_attributes!
@ -92,8 +105,11 @@ class UpdateStatusService < BaseService
@status.sensitive = @options[:sensitive] || @options[:spoiler_text].present? if @options.key?(:sensitive) || @options.key?(:spoiler_text) @status.sensitive = @options[:sensitive] || @options[:spoiler_text].present? if @options.key?(:sensitive) || @options.key?(:spoiler_text)
@status.language = valid_locale_cascade(@options[:language], @status.language, @status.account.user&.preferred_posting_language, I18n.default_locale) @status.language = valid_locale_cascade(@options[:language], @status.language, @status.account.user&.preferred_posting_language, I18n.default_locale)
@status.content_type = @options[:content_type] || @status.content_type @status.content_type = @options[:content_type] || @status.content_type
@status.edited_at = Time.now.utc
# We raise here to rollback the entire transaction
raise NoChangesSubmittedError unless significant_changes?
@status.edited_at = Time.now.utc
@status.save! @status.save!
end end
@ -139,4 +155,8 @@ class UpdateStatusService < BaseService
def create_edit! def create_edit!
@status.snapshot!(account_id: @account_id) @status.snapshot!(account_id: @account_id)
end end
def significant_changes?
@status.changed? || @poll_changed || @media_attachments_changed
end
end end

View File

@ -5,17 +5,17 @@
.account__header__fields .account__header__fields
- fields.each do |field| - fields.each do |field|
%dl %dl
%dt.emojify{ title: field.name }= Formatter.instance.format_field(account, field.name, custom_emojify: true) %dt.emojify{ title: field.name }= prerender_custom_emojis(h(field.name), account.emojis)
%dd{ title: field.value, class: custom_field_classes(field) } %dd{ title: field.value, class: custom_field_classes(field) }
- if field.verified? - if field.verified?
%span.verified__mark{ title: t('accounts.link_verified_on', date: l(field.verified_at)) } %span.verified__mark{ title: t('accounts.link_verified_on', date: l(field.verified_at)) }
= fa_icon 'check' = fa_icon 'check'
= Formatter.instance.format_field(account, field.value, custom_emojify: true) = prerender_custom_emojis(html_aware_format(field.value, account.local?, with_rel_me: true, with_domains: true, multiline: false), account.emojis)
= account_badge(account) = account_badge(account)
- if account.note.present? - if account.note.present?
.account__header__content.emojify= Formatter.instance.simplified_format(account, custom_emojify: true) .account__header__content.emojify= prerender_custom_emojis(html_aware_format(account.note, account.local?), account.emojis)
.public-account-bio__extra .public-account-bio__extra
= t 'accounts.joined', date: l(account.created_at, format: :month) = t 'accounts.joined', date: l(account.created_at, format: :month)

View File

@ -1,4 +1,4 @@
.batch-table__row{ class: [!account.suspended? && account.user_pending? && 'batch-table__row--attention', account.suspended? && 'batch-table__row--muted'] } .batch-table__row{ class: [!account.suspended? && account.user_pending? && 'batch-table__row--attention', (account.suspended? || account.user_unconfirmed?) && 'batch-table__row--muted'] }
%label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox
= f.check_box :account_ids, { multiple: true, include_hidden: false }, account.id = f.check_box :account_ids, { multiple: true, include_hidden: false }, account.id
.batch-table__row__content.batch-table__row__content--unpadded .batch-table__row__content.batch-table__row__content--unpadded

View File

@ -16,16 +16,16 @@
.account__header__fields .account__header__fields
- fields.each do |field| - fields.each do |field|
%dl %dl
%dt.emojify{ title: field.name }= Formatter.instance.format_field(account, field.name, custom_emojify: true) %dt.emojify{ title: field.name }= prerender_custom_emojis(h(field.name), account.emojis)
%dd{ title: field.value, class: custom_field_classes(field) } %dd{ title: field.value, class: custom_field_classes(field) }
- if field.verified? - if field.verified?
%span.verified__mark{ title: t('accounts.link_verified_on', date: l(field.verified_at)) } %span.verified__mark{ title: t('accounts.link_verified_on', date: l(field.verified_at)) }
= fa_icon 'check' = fa_icon 'check'
= Formatter.instance.format_field(account, field.value, custom_emojify: true) = prerender_custom_emojis(html_aware_format(field.value, account.local?, with_rel_me: true, with_domains: true, multiline: false), account.emojis)
- if account.note.present? - if account.note.present?
%div %div
.account__header__content.emojify= Formatter.instance.simplified_format(account, custom_emojify: true) .account__header__content.emojify= prerender_custom_emojis(html_aware_format(account.note, account.local?), account.emojis)
.dashboard__counters.admin-account-counters .dashboard__counters.admin-account-counters
%div %div

View File

@ -84,7 +84,8 @@
- else - else
%span.negative-hint %span.negative-hint
= t('admin.instances.availability.failures_recorded', count: @instance.delivery_failure_tracker.days) = t('admin.instances.availability.failures_recorded', count: @instance.delivery_failure_tracker.days)
= link_to t('admin.instances.delivery.clear'), clear_delivery_errors_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post } unless @instance.exhausted_deliveries_days.empty? %span= link_to t('admin.instances.delivery.clear'), clear_delivery_errors_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post } unless @instance.exhausted_deliveries_days.empty?
%span= link_to t('admin.instances.delivery.stop'), stop_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }
- if @instance.purgeable? - if @instance.purgeable?
%p= t('admin.instances.purge_description_html') %p= t('admin.instances.purge_description_html')

View File

@ -4,12 +4,12 @@
.batch-table__row__content .batch-table__row__content
.status__content>< .status__content><
- if status.proper.spoiler_text.blank? - if status.proper.spoiler_text.blank?
= Formatter.instance.format(status.proper, custom_emojify: true) = prerender_custom_emojis(status_content_format(status.proper), status.proper.emojis)
- else - else
%details< %details<
%summary>< %summary><
%strong> Content warning: #{Formatter.instance.format_spoiler(status.proper)} %strong> Content warning: #{prerender_custom_emojis(h(status.proper.spoiler_text), status.proper.emojis)}
= Formatter.instance.format(status.proper, custom_emojify: true) = prerender_custom_emojis(status_content_format(status.proper), status.proper.emojis)
- unless status.proper.ordered_media_attachments.empty? - unless status.proper.ordered_media_attachments.empty?
- if status.proper.ordered_media_attachments.first.video? - if status.proper.ordered_media_attachments.first.video?

View File

@ -23,7 +23,7 @@
= fa_icon('lock') if @report.target_account.locked? = fa_icon('lock') if @report.target_account.locked?
- if @report.target_account.note.present? - if @report.target_account.note.present?
.account-card__bio.emojify .account-card__bio.emojify
= Formatter.instance.simplified_format(@report.target_account, custom_emojify: true) = prerender_custom_emojis(html_aware_format(@report.target_account.note, @report.target_account.local?), @report.target_account.emojis)
.account-card__actions .account-card__actions
.account-card__counters .account-card__counters
.account-card__counters__item .account-card__counters__item

View File

@ -8,7 +8,7 @@
.dashboard .dashboard
.dashboard__item .dashboard__item
= react_admin_component :counter, measure: 'tag_accounts', start_at: @time_period.first, end_at: @time_period.last, params: { id: @tag.id }, label: t('admin.trends.tags.dashboard.tag_accounts_measure') = react_admin_component :counter, measure: 'tag_accounts', start_at: @time_period.first, end_at: @time_period.last, params: { id: @tag.id }, label: t('admin.trends.tags.dashboard.tag_accounts_measure'), href: tag_url(@tag), target: '_blank'
.dashboard__item .dashboard__item
= react_admin_component :counter, measure: 'tag_uses', start_at: @time_period.first, end_at: @time_period.last, params: { id: @tag.id }, label: t('admin.trends.tags.dashboard.tag_uses_measure') = react_admin_component :counter, measure: 'tag_uses', start_at: @time_period.first, end_at: @time_period.last, params: { id: @tag.id }, label: t('admin.trends.tags.dashboard.tag_uses_measure')
.dashboard__item .dashboard__item

View File

@ -34,7 +34,7 @@
= fa_icon('lock') if account.locked? = fa_icon('lock') if account.locked?
- if account.note.present? - if account.note.present?
.account-card__bio.emojify .account-card__bio.emojify
= Formatter.instance.simplified_format(account, custom_emojify: true) = prerender_custom_emojis(html_aware_format(account.note, account.local?), account.emojis)
- else - else
.flex-spacer .flex-spacer
.account-card__actions .account-card__actions

View File

@ -26,7 +26,7 @@
%p= t "user_mailer.warning.explanation.#{@strike.action}", instance: Rails.configuration.x.local_domain %p= t "user_mailer.warning.explanation.#{@strike.action}", instance: Rails.configuration.x.local_domain
- unless @strike.text.blank? - unless @strike.text.blank?
= Formatter.instance.linkify(@strike.text) = linkify(@strike.text)
- if @strike.report && !@strike.report.other? - if @strike.report && !@strike.report.other?
%p %p

View File

@ -28,10 +28,10 @@
- if status.spoiler_text? - if status.spoiler_text?
%div.auto-dir %div.auto-dir
%p %p
= Formatter.instance.format_spoiler(status) = status.spoiler_text
%div.auto-dir %div.auto-dir
= Formatter.instance.format(status) = status_content_format(status)
- if status.ordered_media_attachments.size > 0 - if status.ordered_media_attachments.size > 0
%p %p

View File

@ -3,6 +3,6 @@
> ---- > ----
> >
<% end %> <% end %>
> <%= raw word_wrap(Formatter.instance.plaintext(status), break_sequence: "\n> ") %> > <%= raw word_wrap(extract_plain_text(status.text, status.local?), break_sequence: "\n> ") %>
<%= raw t('application_mailer.view')%> <%= web_url("statuses/#{status.id}") %> <%= raw t('application_mailer.view')%> <%= web_url("statuses/#{status.id}") %>

View File

@ -5,7 +5,7 @@
* <%= raw t('notification_mailer.digest.mention', name: notification.from_account.pretty_acct) %> * <%= raw t('notification_mailer.digest.mention', name: notification.from_account.pretty_acct) %>
<%= raw Formatter.instance.plaintext(notification.target_status) %> <%= raw extract_plain_text(notification.target_status.text, notification.target_status.local?) %>
<%= raw t('application_mailer.view')%> <%= web_url("statuses/#{notification.target_status.id}") %> <%= raw t('application_mailer.view')%> <%= web_url("statuses/#{notification.target_status.id}") %>
<% end %> <% end %>

View File

@ -18,10 +18,11 @@
.status__content.emojify{ :data => ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }< .status__content.emojify{ :data => ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }<
- if status.spoiler_text? - if status.spoiler_text?
%p< %p<
%span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: prefers_autoplay?)}&nbsp; %span.p-summary> #{prerender_custom_emojis(h(status.spoiler_text), status.emojis)}&nbsp;
%button.status__content__spoiler-link= t('statuses.show_more') %button.status__content__spoiler-link= t('statuses.show_more')
.e-content .e-content
= Formatter.instance.format(status, custom_emojify: true, autoplay: prefers_autoplay?) = prerender_custom_emojis(status_content_format(status), status.emojis)
- if status.preloadable_poll - if status.preloadable_poll
= render_poll_component(status) = render_poll_component(status)

View File

@ -12,7 +12,7 @@
%span.poll__number>< %span.poll__number><
= "#{percent.round}%" = "#{percent.round}%"
%span.poll__option__text %span.poll__option__text
= Formatter.instance.format_poll_option(status, option, autoplay: prefers_autoplay?) = prerender_custom_emojis(h(option.title), status.emojis)
- if own_votes.include?(index) - if own_votes.include?(index)
%span.poll__voted %span.poll__voted
%i.poll__voted__mark.fa.fa-check %i.poll__voted__mark.fa.fa-check
@ -23,7 +23,7 @@
%label.poll__option>< %label.poll__option><
%span.poll__input{ class: poll.multiple? ? 'checkbox' : nil}>< %span.poll__input{ class: poll.multiple? ? 'checkbox' : nil}><
%span.poll__option__text %span.poll__option__text
= Formatter.instance.format_poll_option(status, option, autoplay: prefers_autoplay?) = prerender_custom_emojis(h(option.title), status.emojis)
.poll__footer .poll__footer
- unless show_results - unless show_results
%button.button.button-secondary{ disabled: true } %button.button.button-secondary{ disabled: true }

View File

@ -30,10 +30,11 @@
.status__content.emojify{ :data => ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }< .status__content.emojify{ :data => ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }<
- if status.spoiler_text? - if status.spoiler_text?
%p< %p<
%span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: prefers_autoplay?)}&nbsp; %span.p-summary> #{prerender_custom_emojis(h(status.spoiler_text), status.emojis)}&nbsp;
%button.status__content__spoiler-link= t('statuses.show_more') %button.status__content__spoiler-link= t('statuses.show_more')
.e-content< .e-content<
= Formatter.instance.format(status, custom_emojify: true, autoplay: prefers_autoplay?) = prerender_custom_emojis(status_content_format(status), status.emojis)
- if status.preloadable_poll - if status.preloadable_poll
= render_poll_component(status) = render_poll_component(status)

View File

@ -40,7 +40,7 @@
%p= t "user_mailer.warning.explanation.#{@warning.action}", instance: @instance %p= t "user_mailer.warning.explanation.#{@warning.action}", instance: @instance
- unless @warning.text.blank? - unless @warning.text.blank?
= Formatter.instance.linkify(@warning.text) = linkify(@warning.text)
- if @warning.report && !@warning.report.other? - if @warning.report && !@warning.report.other?
%p %p

View File

@ -75,30 +75,4 @@ module Twitter::TwitterText
) )
}iox }iox
end end
module Extractor
# Extracts a list of all XMPP and magnet URIs included in the Toot <tt>text</tt> along
# with the indices. If the <tt>text</tt> is <tt>nil</tt> or contains no
# XMPP or magnet URIs an empty array will be returned.
#
# If a block is given then it will be called for each XMPP URI.
def extract_extra_uris_with_indices(text, _options = {}) # :yields: uri, start, end
return [] unless text && text.index(":")
urls = []
text.to_s.scan(Twitter::TwitterText::Regex[:valid_extended_uri]) do
valid_uri_match_data = $~
start_position = valid_uri_match_data.char_begin(3)
end_position = valid_uri_match_data.char_end(3)
urls << {
:url => valid_uri_match_data[3],
:indices => [start_position, end_position]
}
end
urls.each{|url| yield url[:url], url[:indices].first, url[:indices].last} if block_given?
urls
end
end
end end

View File

@ -24,7 +24,7 @@ nl:
status: status:
attributes: attributes:
reblog: reblog:
taken: van toot bestaat al taken: van bericht bestaat al
user: user:
attributes: attributes:
email: email:

View File

@ -490,6 +490,7 @@ ca:
other: Intents fallits en %{count} diferents dies. other: Intents fallits en %{count} diferents dies.
no_failures_recorded: Sense errors registrats. no_failures_recorded: Sense errors registrats.
title: Disponibilitat title: Disponibilitat
warning: El darrer intent de connectar a aquest servidor no ha tingut èxit
back_to_all: Totes back_to_all: Totes
back_to_limited: Limitades back_to_limited: Limitades
back_to_warning: Avís back_to_warning: Avís

View File

@ -490,6 +490,7 @@ da:
other: Mislykkede forsøg på %{count} forskellige dage. other: Mislykkede forsøg på %{count} forskellige dage.
no_failures_recorded: Ingen fejl noteret. no_failures_recorded: Ingen fejl noteret.
title: Tilgængelighed title: Tilgængelighed
warning: Seneste forsøg på at oprette forbindelse til denne server mislykkedes
back_to_all: Alle back_to_all: Alle
back_to_limited: Begrænset back_to_limited: Begrænset
back_to_warning: Advarsel back_to_warning: Advarsel

View File

@ -373,6 +373,7 @@ de:
enable: Aktivieren enable: Aktivieren
enabled: Aktiviert enabled: Aktiviert
enabled_msg: Das Emoji wurde aktiviert enabled_msg: Das Emoji wurde aktiviert
image_hint: PNG oder GIF bis %{size}
list: Liste list: Liste
listed: Gelistet listed: Gelistet
new: new:
@ -489,6 +490,7 @@ de:
other: Fehlgeschlagener Versuch am %{count}. Tag. other: Fehlgeschlagener Versuch am %{count}. Tag.
no_failures_recorded: Keine Fehler bei der Aufzeichnung. no_failures_recorded: Keine Fehler bei der Aufzeichnung.
title: Verfügbarkeit title: Verfügbarkeit
warning: Der letzte Versuch, sich mit diesem Server zu verbinden, war nicht erfolgreich
back_to_all: Alle back_to_all: Alle
back_to_limited: Beschränkt back_to_limited: Beschränkt
back_to_warning: Warnung back_to_warning: Warnung
@ -1436,6 +1438,7 @@ de:
disallowed_hashtags: disallowed_hashtags:
one: 'enthält einen verbotenen Hashtag: %{tags}' one: 'enthält einen verbotenen Hashtag: %{tags}'
other: 'enthält verbotene Hashtags: %{tags}' other: 'enthält verbotene Hashtags: %{tags}'
edited_at_html: Bearbeitet %{date}
errors: errors:
in_reply_not_found: Der Beitrag, auf den du antworten möchtest, scheint nicht zu existieren. in_reply_not_found: Der Beitrag, auf den du antworten möchtest, scheint nicht zu existieren.
open_in_web: Im Web öffnen open_in_web: Im Web öffnen

View File

@ -73,6 +73,10 @@ id:
index: index:
authorized_at: Diberi hak otorisasi pada %{date} authorized_at: Diberi hak otorisasi pada %{date}
description_html: Ini adalah aplikasi yang dapat mengakses akun Anda menggunakan API. Jika ada aplikasi yang tidak Anda kenal di sini, atau aplikasi yang berperilaku aneh, Anda dapat mencabut hak aksesnya. description_html: Ini adalah aplikasi yang dapat mengakses akun Anda menggunakan API. Jika ada aplikasi yang tidak Anda kenal di sini, atau aplikasi yang berperilaku aneh, Anda dapat mencabut hak aksesnya.
last_used_at: Terakhir dipakai pada %{date}
never_used: Tidak pernah dipakai
scopes: Hak akses
superapp: Internal
title: Aplikasi yang anda izinkan title: Aplikasi yang anda izinkan
errors: errors:
messages: messages:
@ -108,6 +112,33 @@ id:
authorized_applications: authorized_applications:
destroy: destroy:
notice: Aplikasi dicabut. notice: Aplikasi dicabut.
grouped_scopes:
access:
read: Akses baca-saja
read/write: Akses baca dan tulis
write: Akses tulis-saja
title:
accounts: Akun
admin/accounts: Administrasi akun
admin/all: Semua fungsi administratif
admin/reports: Administrasi laporan
all: Segalanya
blocks: Blokir
bookmarks: Markah
conversations: Percakapan
crypto: Enkripsi end-to-end
favourites: Favorit
filters: Saringan
follow: Hubungan
follows: Mengikuti
lists: Daftar
media: Lampiran media
mutes: Bisukan
notifications: Notifikasi
push: Notifikasi dorong
reports: Laporan
search: Pencarian
statuses: Kiriman
layouts: layouts:
admin: admin:
nav: nav:
@ -122,6 +153,7 @@ id:
admin:write: ubah semua data di server admin:write: ubah semua data di server
admin:write:accounts: lakukan aksi moderasi akun admin:write:accounts: lakukan aksi moderasi akun
admin:write:reports: lakukan aksi moderasi laporan admin:write:reports: lakukan aksi moderasi laporan
crypto: menggunakan enkripsi end-to-end
follow: mengikuti, blokir, menghapus blokir, dan berhenti mengikuti akun follow: mengikuti, blokir, menghapus blokir, dan berhenti mengikuti akun
push: terima notifikasi dorong push: terima notifikasi dorong
read: membaca data pada akun anda read: membaca data pada akun anda
@ -141,6 +173,7 @@ id:
write:accounts: ubah profil Anda write:accounts: ubah profil Anda
write:blocks: blokir akun dan domain write:blocks: blokir akun dan domain
write:bookmarks: status markah write:bookmarks: status markah
write:conversations: bisukan dan hapus percakapan
write:favourites: status favorit write:favourites: status favorit
write:filters: buat saringan write:filters: buat saringan
write:follows: ikuti orang write:follows: ikuti orang

View File

@ -130,7 +130,7 @@ ku:
favourites: Bijarte favourites: Bijarte
filters: Parzûn filters: Parzûn
follow: Pêwendî follow: Pêwendî
follows: Şopîner follows: Dişopîne
lists: Rêzok lists: Rêzok
media: Pêvekên medya media: Pêvekên medya
mutes: Bêdengkirin mutes: Bêdengkirin
@ -162,7 +162,7 @@ ku:
read:bookmarks: şûnpelên xwe bibîne read:bookmarks: şûnpelên xwe bibîne
read:favourites: bijarteyên xwe bibîne read:favourites: bijarteyên xwe bibîne
read:filters: parzûnûn xwe bibîne read:filters: parzûnûn xwe bibîne
read:follows: şopînerên xwe bibîne read:follows: ên tu dişopînî bibîne
read:lists: rêzoka xwe bibîne read:lists: rêzoka xwe bibîne
read:mutes: ajimêrên bêdeng kirî bibîne read:mutes: ajimêrên bêdeng kirî bibîne
read:notifications: agahdariyên xwe bibîne read:notifications: agahdariyên xwe bibîne

View File

@ -60,6 +60,8 @@ nl:
error: error:
title: Er is een fout opgetreden title: Er is een fout opgetreden
new: new:
prompt_html: "%{client_name} heeft toestemming nodig om toegang te krijgen tot jouw account. Het betreft een third-party-toepassing.<strong>Als je dit niet vertrouwt, moet je geen toestemming verlenen.</strong>"
review_permissions: Toestemmingen beoordelen
title: Autorisatie vereist title: Autorisatie vereist
show: show:
title: Kopieer deze autorisatiecode en plak het in de toepassing. title: Kopieer deze autorisatiecode en plak het in de toepassing.
@ -69,6 +71,11 @@ nl:
confirmations: confirmations:
revoke: Weet je het zeker? revoke: Weet je het zeker?
index: index:
authorized_at: Toestemming verleent op %{date}
last_used_at: Voor het laatst gebruikt op %{date}
never_used: Nooit gebruikt
scopes: Toestemmingen
superapp: Intern
title: Jouw geautoriseerde toepassingen title: Jouw geautoriseerde toepassingen
errors: errors:
messages: messages:
@ -104,6 +111,33 @@ nl:
authorized_applications: authorized_applications:
destroy: destroy:
notice: Toepassing ingetrokken. notice: Toepassing ingetrokken.
grouped_scopes:
access:
read: Alleen leestoegang
read/write: Lees- en schrijftoegang
write: Alleen schrijftoegang
title:
accounts: Accounts
admin/accounts: Accountbeheer
admin/all: Alle beheerfuncties
admin/reports: Rapportagebeheer
all: Alles
blocks: Blokkeren
bookmarks: Bladwijzers
conversations: Gesprekken
crypto: End-to-end-encryptie
favourites: Favorieten
filters: Filters
follow: Relaties
follows: Volgend
lists: Lijsten
media: Mediabijlagen
mutes: Negeren
notifications: Meldingen
push: Pushmeldingen
reports: Rapportages
search: Zoeken
statuses: Berichten
layouts: layouts:
admin: admin:
nav: nav:
@ -118,6 +152,7 @@ nl:
admin:write: wijzig alle gegevens op de server admin:write: wijzig alle gegevens op de server
admin:write:accounts: moderatieacties op accounts uitvoeren admin:write:accounts: moderatieacties op accounts uitvoeren
admin:write:reports: moderatieacties op rapportages uitvoeren admin:write:reports: moderatieacties op rapportages uitvoeren
crypto: end-to-end-encryptie gebruiken
follow: relaties tussen accounts bewerken follow: relaties tussen accounts bewerken
push: jouw pushmeldingen ontvangen push: jouw pushmeldingen ontvangen
read: alle gegevens van jouw account lezen read: alle gegevens van jouw account lezen
@ -130,14 +165,15 @@ nl:
read:lists: jouw lijsten bekijken read:lists: jouw lijsten bekijken
read:mutes: jouw genegeerde gebruikers bekijken read:mutes: jouw genegeerde gebruikers bekijken
read:notifications: jouw meldingen bekijken read:notifications: jouw meldingen bekijken
read:reports: jouw gerapporteerde toots bekijken read:reports: jouw gerapporteerde berichten bekijken
read:search: namens jou zoeken read:search: namens jou zoeken
read:statuses: alle toots bekijken read:statuses: alle berichten bekijken
write: alle gegevens van jouw account bewerken write: alle gegevens van jouw account bewerken
write:accounts: jouw profiel bewerken write:accounts: jouw profiel bewerken
write:blocks: accounts en domeinen blokkeren write:blocks: accounts en domeinen blokkeren
write:bookmarks: toots aan bladwijzers toevoegen write:bookmarks: berichten aan bladwijzers toevoegen
write:favourites: toots als favoriet markeren write:conversations: gespreken negeren en verwijderen
write:favourites: berichten als favoriet markeren
write:filters: filters aanmaken write:filters: filters aanmaken
write:follows: mensen volgen write:follows: mensen volgen
write:lists: lijsten aanmaken write:lists: lijsten aanmaken
@ -145,4 +181,4 @@ nl:
write:mutes: mensen en gesprekken negeren write:mutes: mensen en gesprekken negeren
write:notifications: meldingen verwijderen write:notifications: meldingen verwijderen
write:reports: andere mensen rapporteren write:reports: andere mensen rapporteren
write:statuses: toots publiceren write:statuses: berichten plaatsen

View File

@ -775,6 +775,11 @@ en:
system_checks: system_checks:
database_schema_check: database_schema_check:
message_html: There are pending database migrations. Please run them to ensure the application behaves as expected message_html: There are pending database migrations. Please run them to ensure the application behaves as expected
elasticsearch_running_check:
message_html: Could not connect to Elasticsearch. Please check that it is running, or disable full-text search
elasticsearch_version_check:
message_html: 'Incompatible Elasticsearch version: %{value}'
version_comparison: Elasticsearch %{running_version} is running while %{required_version} is required
rules_check: rules_check:
action: Manage server rules action: Manage server rules
message_html: You haven't defined any server rules. message_html: You haven't defined any server rules.

View File

@ -490,6 +490,7 @@ es-AR:
other: Intentos fallidos en %{count} días. other: Intentos fallidos en %{count} días.
no_failures_recorded: No hay fallos en el registro. no_failures_recorded: No hay fallos en el registro.
title: Disponibilidad title: Disponibilidad
warning: El último intento de conexión a este servidor no fue exitoso
back_to_all: Todos back_to_all: Todos
back_to_limited: Limitados back_to_limited: Limitados
back_to_warning: Advertencia back_to_warning: Advertencia

View File

@ -490,6 +490,7 @@ es:
other: Intentos fallidos en %{count} días diferentes. other: Intentos fallidos en %{count} días diferentes.
no_failures_recorded: No hay fallos en el registro. no_failures_recorded: No hay fallos en el registro.
title: Disponibilidad title: Disponibilidad
warning: El último intento de conexión a este servidor no ha tenido éxito
back_to_all: Todos back_to_all: Todos
back_to_limited: Limitados back_to_limited: Limitados
back_to_warning: Advertencia back_to_warning: Advertencia

View File

@ -490,6 +490,7 @@ gl:
other: Intentos fallidos durante %{count} días distintos. other: Intentos fallidos durante %{count} días distintos.
no_failures_recorded: Non hai fallos rexistrados. no_failures_recorded: Non hai fallos rexistrados.
title: Dispoñibilidade title: Dispoñibilidade
warning: Fallou o último intento de conectar con este servidor
back_to_all: Todo back_to_all: Todo
back_to_limited: Limitado back_to_limited: Limitado
back_to_warning: Aviso back_to_warning: Aviso

View File

@ -492,6 +492,7 @@ hu:
other: Sikertelen próbálkozás %{count} különböző napon. other: Sikertelen próbálkozás %{count} különböző napon.
no_failures_recorded: Nem rögzítettünk hibát. no_failures_recorded: Nem rögzítettünk hibát.
title: Elérhetőség title: Elérhetőség
warning: Sikertelen volt az utolsó csatlakozási próbálkozás ehhez a szerverhez
back_to_all: Mind back_to_all: Mind
back_to_limited: Korlátozott back_to_limited: Korlátozott
back_to_warning: Figyelmeztetés back_to_warning: Figyelmeztetés

View File

@ -477,6 +477,7 @@ id:
other: Upaya gagal dalam %{count} hari berbeda. other: Upaya gagal dalam %{count} hari berbeda.
no_failures_recorded: Tidak ada kegagalan tercatat. no_failures_recorded: Tidak ada kegagalan tercatat.
title: Ketersediaan title: Ketersediaan
warning: Upaya terakhir untuk menyambung ke server ini tidak berhasil
back_to_all: Semua back_to_all: Semua
back_to_limited: Terbatas back_to_limited: Terbatas
back_to_warning: Peringatan back_to_warning: Peringatan

View File

@ -490,6 +490,7 @@ is:
other: Misheppnaðar tilraunir á %{count} mismunandi dögum. other: Misheppnaðar tilraunir á %{count} mismunandi dögum.
no_failures_recorded: Engar misheppnaðar tilraunir á skrá. no_failures_recorded: Engar misheppnaðar tilraunir á skrá.
title: Tiltækileiki title: Tiltækileiki
warning: Síðasta tilraun til að tengjast þessum netþjóni mistókst
back_to_all: Allt back_to_all: Allt
back_to_limited: Takmarkað back_to_limited: Takmarkað
back_to_warning: Aðvörun back_to_warning: Aðvörun

View File

@ -490,6 +490,7 @@ it:
other: Tentativo fallito %{count} giorni differenti. other: Tentativo fallito %{count} giorni differenti.
no_failures_recorded: Nessun fallimento registrato. no_failures_recorded: Nessun fallimento registrato.
title: Disponibilità title: Disponibilità
warning: L'ultimo tentativo di connessione a questo server non è riuscito
back_to_all: Tutto back_to_all: Tutto
back_to_limited: Limitato back_to_limited: Limitato
back_to_warning: Avviso back_to_warning: Avviso

View File

@ -481,6 +481,7 @@ ko:
other: 실패한 전달 시도 총 %{count}일. other: 실패한 전달 시도 총 %{count}일.
no_failures_recorded: 실패 기록이 없습니다. no_failures_recorded: 실패 기록이 없습니다.
title: 가용성 title: 가용성
warning: 이 서버에 대한 마지막 연결 시도가 성공적이지 않았습니다
back_to_all: 전체 back_to_all: 전체
back_to_limited: 제한됨 back_to_limited: 제한됨
back_to_warning: 경고 back_to_warning: 경고

View File

@ -133,7 +133,7 @@ ku:
enabled: Çalakkirî enabled: Çalakkirî
enabled_msg: Ajimêra %{username} bi serkeftî hat çalak kirin enabled_msg: Ajimêra %{username} bi serkeftî hat çalak kirin
followers: Şopîner followers: Şopîner
follows: Dişopînê follows: Dişopîne
header: Jormalper header: Jormalper
inbox_url: Peyamên hatî URl inbox_url: Peyamên hatî URl
invite_request_text: Sedemên tevlêbûnê invite_request_text: Sedemên tevlêbûnê
@ -492,6 +492,7 @@ ku:
other: Hewldanên têkçûyî di %{count} rojên cuda de. other: Hewldanên têkçûyî di %{count} rojên cuda de.
no_failures_recorded: Di tomarê de têkçûn tune. no_failures_recorded: Di tomarê de têkçûn tune.
title: Berdestbûnî title: Berdestbûnî
warning: Hewldana dawî ji bo girêdana bi vê rajekarê re bi ser neket
back_to_all: Hemû back_to_all: Hemû
back_to_limited: Sînorkirî back_to_limited: Sînorkirî
back_to_warning: Hişyarî back_to_warning: Hişyarî
@ -543,7 +544,7 @@ ku:
title: Giştî title: Giştî
total_blocked_by_us: Ji aliyê me ve hatiye astengkirin total_blocked_by_us: Ji aliyê me ve hatiye astengkirin
total_followed_by_them: Ji aliyê wan ve hatiye şopandin total_followed_by_them: Ji aliyê wan ve hatiye şopandin
total_followed_by_us: Ji aliyê ve me hate şopandin total_followed_by_us: Ji aliyê me ve hatiye şopandin
total_reported: Giliyên derheqê wan de total_reported: Giliyên derheqê wan de
total_storage: Pêvekên medyayê total_storage: Pêvekên medyayê
totals_time_period_hint_html: Tevahiyên ku li jêr têne xuyakirin daneyên hemû deman dihewîne. totals_time_period_hint_html: Tevahiyên ku li jêr têne xuyakirin daneyên hemû deman dihewîne.
@ -975,7 +976,7 @@ ku:
close: An jî, tu dikarî tenê ev çarçoveyê bigirî. close: An jî, tu dikarî tenê ev çarçoveyê bigirî.
return: Profîla vê bikarhênerê nîşan bike return: Profîla vê bikarhênerê nîşan bike
web: Biçe tevneyê web: Biçe tevneyê
title: Bişopîne %{acct} title: "%{acct} bişopîne"
challenge: challenge:
confirm: Bidomîne confirm: Bidomîne
hint_html: "<strong>Nîşe:</strong>Ji bo demjimêreke din em ê pêborîna te careke din ji te nexwazin." hint_html: "<strong>Nîşe:</strong>Ji bo demjimêreke din em ê pêborîna te careke din ji te nexwazin."
@ -1247,7 +1248,7 @@ ku:
follow: follow:
body: "%{name} niha te dişopîne!" body: "%{name} niha te dişopîne!"
subject: "%{name} niha te dişopîne" subject: "%{name} niha te dişopîne"
title: Şopînereke title: Şopînera
follow_request: follow_request:
action: Daxwazên şopandinê bi rê ve bibe action: Daxwazên şopandinê bi rê ve bibe
body: "%{name} daxwaza şopandina te kir" body: "%{name} daxwaza şopandina te kir"
@ -1416,7 +1417,7 @@ ku:
notifications: Agahdarî notifications: Agahdarî
preferences: Hilbijarte preferences: Hilbijarte
profile: Profîl profile: Profîl
relationships: Yên tê şopandin û şopîner relationships: Şopandin û şopîner
statuses_cleanup: Bi xweberî ve jêbirina şandiya statuses_cleanup: Bi xweberî ve jêbirina şandiya
strikes: Binpêkirinên çavdêriyê strikes: Binpêkirinên çavdêriyê
two_factor_authentication: Piştrastkirinê du-faktorî two_factor_authentication: Piştrastkirinê du-faktorî

View File

@ -501,6 +501,7 @@ lv:
zero: Neizdevušies mēģinājumi %{count} dienās. zero: Neizdevušies mēģinājumi %{count} dienās.
no_failures_recorded: Nav reģistrētu kļūdu. no_failures_recorded: Nav reģistrētu kļūdu.
title: Pieejamība title: Pieejamība
warning: Pēdējais mēģinājums izveidot savienojumu ar šo serveri ir bijis neveiksmīgs
back_to_all: Visas back_to_all: Visas
back_to_limited: Ierobežotās back_to_limited: Ierobežotās
back_to_warning: Brīdinājums back_to_warning: Brīdinājums

View File

@ -1,7 +1,7 @@
--- ---
nl: nl:
about: about:
about_hashtag_html: Dit zijn openbare toots die getagged zijn met <strong>#%{hashtag}</strong>. Je kunt er op reageren of iets anders mee doen als je op Mastodon (of ergens anders in de fediverse) een account hebt. about_hashtag_html: Dit zijn openbare berichten die getagged zijn met <strong>#%{hashtag}</strong>. Je kunt er op reageren of iets anders mee doen als je op Mastodon (of ergens anders in de fediverse) een account hebt.
about_mastodon_html: Mastodon is een sociaal netwerk dat gebruikt maakt van open webprotocollen en vrije software. Het is net zoals e-mail gedecentraliseerd. about_mastodon_html: Mastodon is een sociaal netwerk dat gebruikt maakt van open webprotocollen en vrije software. Het is net zoals e-mail gedecentraliseerd.
about_this: Over deze server about_this: Over deze server
active_count_after: actief active_count_after: actief
@ -31,7 +31,7 @@ nl:
source_code: Broncode source_code: Broncode
status_count_after: status_count_after:
one: toot one: toot
other: toots other: berichten
status_count_before: Zij schreven status_count_before: Zij schreven
tagline: Vrienden volgen en nieuwe ontdekken tagline: Vrienden volgen en nieuwe ontdekken
terms: Gebruiksvoorwaarden terms: Gebruiksvoorwaarden
@ -41,7 +41,7 @@ nl:
reason: 'Reden:' reason: 'Reden:'
rejecting_media: 'Mediabestanden van deze server worden niet verwerkt en er worden geen thumbnails getoond. Je moet handmatig naar deze server doorklikken om de mediabestanden te kunnen bekijken:' rejecting_media: 'Mediabestanden van deze server worden niet verwerkt en er worden geen thumbnails getoond. Je moet handmatig naar deze server doorklikken om de mediabestanden te kunnen bekijken:'
rejecting_media_title: Mediabestanden geweigerd rejecting_media_title: Mediabestanden geweigerd
silenced: Toots van deze server worden nergens weergegeven, behalve op jouw eigen starttijdlijn wanneer je het account volgt. silenced: Berichten van deze server worden nergens weergegeven, behalve op jouw eigen starttijdlijn wanneer je het account volgt.
silenced_title: Beperkte servers silenced_title: Beperkte servers
suspended: Je bent niet in staat om iemand van deze server te volgen, en er worden geen gegevens van deze server verwerkt of opgeslagen, en met deze server uitgewisseld. suspended: Je bent niet in staat om iemand van deze server te volgen, en er worden geen gegevens van deze server verwerkt of opgeslagen, en met deze server uitgewisseld.
suspended_title: Opgeschorte servers suspended_title: Opgeschorte servers
@ -74,9 +74,9 @@ nl:
following: Je moet dit account wel al volgen, alvorens je het kan aanbevelen following: Je moet dit account wel al volgen, alvorens je het kan aanbevelen
posts: posts:
one: Toot one: Toot
other: Toots other: Berichten
posts_tab_heading: Toots posts_tab_heading: Berichten
posts_with_replies: Toots en reacties posts_with_replies: Berichten en reacties
roles: roles:
admin: Beheerder admin: Beheerder
bot: Bot bot: Bot
@ -193,7 +193,7 @@ nl:
targeted_reports: Door anderen gerapporteerd targeted_reports: Door anderen gerapporteerd
silence: Beperken silence: Beperken
silenced: Beperkt silenced: Beperkt
statuses: Toots statuses: Berichten
subscribe: Abonneren subscribe: Abonneren
suspended: Opgeschort suspended: Opgeschort
suspension_irreversible: De gegevens van dit account zijn onomkeerbaar verwijderd. Je kunt het opschorten van dit account ongedaan maken zodat het weer valt te gebruiken, maar de verwijderde gegevens worden hiermee niet hersteld. suspension_irreversible: De gegevens van dit account zijn onomkeerbaar verwijderd. Je kunt het opschorten van dit account ongedaan maken zodat het weer valt te gebruiken, maar de verwijderde gegevens worden hiermee niet hersteld.
@ -229,7 +229,7 @@ nl:
destroy_custom_emoji: Lokale emoji verwijderen destroy_custom_emoji: Lokale emoji verwijderen
destroy_domain_allow: Domeingoedkeuring verwijderen destroy_domain_allow: Domeingoedkeuring verwijderen
destroy_domain_block: Domeinblokkade verwijderen destroy_domain_block: Domeinblokkade verwijderen
destroy_email_domain_block: E-maildomeinblokkade verwijderen destroy_email_domain_block: Blokkade van e-maildomein verwijderen
destroy_ip_block: IP-regel verwijderen destroy_ip_block: IP-regel verwijderen
destroy_status: Toot verwijderen destroy_status: Toot verwijderen
destroy_unavailable_domain: Niet beschikbaar domein verwijderen destroy_unavailable_domain: Niet beschikbaar domein verwijderen
@ -245,16 +245,16 @@ nl:
reset_password_user: Wachtwoord opnieuw instellen reset_password_user: Wachtwoord opnieuw instellen
resolve_report: Rapportage oplossen resolve_report: Rapportage oplossen
sensitive_account: De media in jouw account als gevoelig markeren sensitive_account: De media in jouw account als gevoelig markeren
silence_account: Account negeren silence_account: Account beperken
suspend_account: Account opschorten suspend_account: Account opschorten
unassigned_report: Rapportage niet langer toewijzen unassigned_report: Rapportage niet langer toewijzen
unsensitive_account: De media in jouw account niet langer als gevoelig markeren unsensitive_account: De media in jouw account niet langer als gevoelig markeren
unsilence_account: Account niet langer negeren unsilence_account: Account niet langer beperken
unsuspend_account: Account niet langer opschorten unsuspend_account: Account niet langer opschorten
update_announcement: Mededeling bijwerken update_announcement: Mededeling bijwerken
update_custom_emoji: Lokale emoji bijwerken update_custom_emoji: Lokale emoji bijwerken
update_domain_block: Domeinblokkade bijwerken update_domain_block: Domeinblokkade bijwerken
update_status: Toot bijwerken update_status: Bericht bijwerken
actions: actions:
assigned_to_self_report_html: "%{name} heeft rapportage %{target} aan zichzelf toegewezen" assigned_to_self_report_html: "%{name} heeft rapportage %{target} aan zichzelf toegewezen"
change_email_user_html: "%{name} veranderde het e-mailadres van gebruiker %{target}" change_email_user_html: "%{name} veranderde het e-mailadres van gebruiker %{target}"
@ -274,7 +274,7 @@ nl:
destroy_domain_block_html: Domein %{target} is door %{name} gedeblokkeerd destroy_domain_block_html: Domein %{target} is door %{name} gedeblokkeerd
destroy_email_domain_block_html: "%{name} heeft het e-maildomein %{target} gedeblokkeerd" destroy_email_domain_block_html: "%{name} heeft het e-maildomein %{target} gedeblokkeerd"
destroy_ip_block_html: "%{name} verwijderde regel voor IP %{target}" destroy_ip_block_html: "%{name} verwijderde regel voor IP %{target}"
destroy_status_html: Toot van %{target} is door %{name} verwijderd destroy_status_html: Bericht van %{target} is door %{name} verwijderd
destroy_unavailable_domain_html: "%{name} heeft de bezorging voor domein %{target} hervat" destroy_unavailable_domain_html: "%{name} heeft de bezorging voor domein %{target} hervat"
disable_2fa_user_html: De vereiste tweestapsverificatie voor %{target} is door %{name} uitgeschakeld disable_2fa_user_html: De vereiste tweestapsverificatie voor %{target} is door %{name} uitgeschakeld
disable_custom_emoji_html: Emoji %{target} is door %{name} uitgeschakeld disable_custom_emoji_html: Emoji %{target} is door %{name} uitgeschakeld
@ -297,8 +297,8 @@ nl:
update_announcement_html: "%{name} heeft de mededeling %{target} bijgewerkt" update_announcement_html: "%{name} heeft de mededeling %{target} bijgewerkt"
update_custom_emoji_html: Emoji %{target} is door %{name} bijgewerkt update_custom_emoji_html: Emoji %{target} is door %{name} bijgewerkt
update_domain_block_html: "%{name} heeft de domeinblokkade bijgewerkt voor %{target}" update_domain_block_html: "%{name} heeft de domeinblokkade bijgewerkt voor %{target}"
update_status_html: "%{name} heeft de toots van %{target} bijgewerkt" update_status_html: "%{name} heeft de berichten van %{target} bijgewerkt"
deleted_status: "(verwijderde toot}" deleted_status: "(verwijderd bericht}"
empty: Geen logs gevonden. empty: Geen logs gevonden.
filter_by_action: Op actie filteren filter_by_action: Op actie filteren
filter_by_user: Op gebruiker filteren filter_by_user: Op gebruiker filteren
@ -461,11 +461,11 @@ nl:
relays: relays:
add_new: Nieuwe relayserver toevoegen add_new: Nieuwe relayserver toevoegen
delete: Verwijderen delete: Verwijderen
description_html: Een <strong>federatierelay</strong> is een tussenliggende server die grote hoeveelheden openbare toots uitwisselt tussen servers die zich hierop hebben geabonneerd. <strong>Het kan kleine en middelgrote servers helpen om content uit de fediverse te ontdekken</strong>, waarvoor anders lokale gebruikers handmatig mensen van externe servers moeten volgen. description_html: Een <strong>federatierelay</strong> is een tussenliggende server die grote hoeveelheden openbare berichten uitwisselt tussen servers die zich hierop hebben geabonneerd. <strong>Het kan kleine en middelgrote servers helpen om content van de fediverse te ontdekken</strong>, waarvoor anders lokale gebruikers handmatig mensen van externe servers moeten volgen.
disable: Uitschakelen disable: Uitschakelen
disabled: Uitgeschakeld disabled: Uitgeschakeld
enable: Inschakelen enable: Inschakelen
enable_hint: Eenmaal ingeschakeld gaat jouw server zich op alle openbare toots van deze relayserver abonneren en stuurt het de openbare toots van jouw server naar de relayserver. enable_hint: Eenmaal ingeschakeld gaat jouw server zich op alle openbare berichten van deze relayserver abonneren en stuurt het de openbare berichten van jouw server naar de relayserver.
enabled: Ingeschakeld enabled: Ingeschakeld
inbox_url: Relay-URL inbox_url: Relay-URL
pending: Aan het wachten op toestemming van de relayserver pending: Aan het wachten op toestemming van de relayserver
@ -506,7 +506,7 @@ nl:
reported_by: Gerapporteerd door reported_by: Gerapporteerd door
resolved: Opgelost resolved: Opgelost
resolved_msg: Rapportage succesvol opgelost! resolved_msg: Rapportage succesvol opgelost!
status: Toot status: Bericht
title: Rapportages title: Rapportages
unassign: Niet langer toewijzen unassign: Niet langer toewijzen
unresolved: Onopgelost unresolved: Onopgelost
@ -520,7 +520,7 @@ nl:
title: Serverregels title: Serverregels
settings: settings:
activity_api_enabled: activity_api_enabled:
desc_html: Wekelijks overzicht van de hoeveelheid lokale toots, actieve gebruikers en nieuwe registraties desc_html: Wekelijks overzicht van de hoeveelheid lokale berichten, actieve gebruikers en nieuwe registraties
title: Statistieken over gebruikersactiviteit via de API publiceren title: Statistieken over gebruikersactiviteit via de API publiceren
bootstrap_timeline_accounts: bootstrap_timeline_accounts:
desc_html: Meerdere gebruikersnamen met komma's scheiden. Deze accounts worden in ieder geval aan nieuwe gebruikers aanbevolen desc_html: Meerdere gebruikersnamen met komma's scheiden. Deze accounts worden in ieder geval aan nieuwe gebruikers aanbevolen
@ -533,7 +533,7 @@ nl:
title: Aangepaste CSS title: Aangepaste CSS
default_noindex: default_noindex:
desc_html: Heeft invloed op alle gebruikers die deze instelling niet zelf hebben veranderd desc_html: Heeft invloed op alle gebruikers die deze instelling niet zelf hebben veranderd
title: Toots van gebruikers standaard niet door zoekmachines laten indexeren title: Berichten van gebruikers standaard niet door zoekmachines laten indexeren
domain_blocks: domain_blocks:
all: Aan iedereen all: Aan iedereen
disabled: Aan niemand disabled: Aan niemand
@ -561,7 +561,7 @@ nl:
desc_html: Wordt op de voorpagina weergegeven wanneer registratie van nieuwe accounts is uitgeschakeld<br>En ook hier kan je HTML gebruiken desc_html: Wordt op de voorpagina weergegeven wanneer registratie van nieuwe accounts is uitgeschakeld<br>En ook hier kan je HTML gebruiken
title: Bericht wanneer registratie is uitgeschakeld title: Bericht wanneer registratie is uitgeschakeld
deletion: deletion:
desc_html: Toestaan dat iedereen hun eigen account kan verwijderen desc_html: Toestaan dat iedereen diens eigen account kan verwijderen
title: Verwijderen account toestaan title: Verwijderen account toestaan
min_invite_role: min_invite_role:
disabled: Niemand disabled: Niemand
@ -606,7 +606,7 @@ nl:
title: Hashtags toestaan om trending te worden zonder voorafgaande beoordeling title: Hashtags toestaan om trending te worden zonder voorafgaande beoordeling
trends: trends:
desc_html: Eerder beoordeelde hashtags die op dit moment trending zijn openbaar tonen desc_html: Eerder beoordeelde hashtags die op dit moment trending zijn openbaar tonen
title: Trending hashtags title: Trends
site_uploads: site_uploads:
delete: Geüpload bestand verwijderen delete: Geüpload bestand verwijderen
destroyed_msg: Verwijderen website-upload geslaagd! destroyed_msg: Verwijderen website-upload geslaagd!
@ -615,8 +615,8 @@ nl:
deleted: Verwijderd deleted: Verwijderd
media: media:
title: Media title: Media
no_status_selected: Er werden geen toots gewijzigd, omdat er geen enkele werd geselecteerd no_status_selected: Er werden geen berichten gewijzigd, omdat er geen enkele werd geselecteerd
title: Toots van account title: Berichten van account
with_media: Met media with_media: Met media
system_checks: system_checks:
database_schema_check: database_schema_check:
@ -662,14 +662,14 @@ nl:
guide_link: https://crowdin.com/project/mastodon/nl guide_link: https://crowdin.com/project/mastodon/nl
guide_link_text: Iedereen kan bijdragen. guide_link_text: Iedereen kan bijdragen.
sensitive_content: Gevoelige inhoud sensitive_content: Gevoelige inhoud
toot_layout: Lay-out van toots toot_layout: Lay-out van berichten
application_mailer: application_mailer:
notification_preferences: E-mailvoorkeuren wijzigen notification_preferences: E-mailvoorkeuren wijzigen
salutation: "%{name}," salutation: "%{name},"
settings: 'E-mailvoorkeuren wijzigen: %{link}' settings: 'E-mailvoorkeuren wijzigen: %{link}'
view: 'Bekijk:' view: 'Bekijk:'
view_profile: Profiel bekijken view_profile: Profiel bekijken
view_status: Toot bekijken view_status: Bericht bekijken
applications: applications:
created: Aanmaken toepassing geslaagd created: Aanmaken toepassing geslaagd
destroyed: Verwijderen toepassing geslaagd destroyed: Verwijderen toepassing geslaagd
@ -768,8 +768,8 @@ nl:
success_msg: Jouw account is succesvol verwijderd success_msg: Jouw account is succesvol verwijderd
warning: warning:
before: 'Lees deze tekst zorgvuldig voordat je verder gaat:' before: 'Lees deze tekst zorgvuldig voordat je verder gaat:'
caches: Toots en media die op andere servers zijn opgeslagen kunnen daar achterblijven caches: Berichten en media die op andere servers zijn opgeslagen kunnen daar achterblijven
data_removal: Jouw toots en andere gegevens worden permanent verwijderd data_removal: Jouw berichten en andere gegevens worden permanent verwijderd
email_change_html: Je kunt <a href="%{path}">je e-mailadres wijzigen</a> zonder dat je jouw account hoeft te verwijderen email_change_html: Je kunt <a href="%{path}">je e-mailadres wijzigen</a> zonder dat je jouw account hoeft te verwijderen
email_contact_html: Wanneer het nog steeds niet aankomt, kun je voor hulp e-mailen naar <a href="mailto:%{email}">%{email}</a> email_contact_html: Wanneer het nog steeds niet aankomt, kun je voor hulp e-mailen naar <a href="mailto:%{email}">%{email}</a>
email_reconfirmation_html: Wanneer je de bevestigingsmail niet hebt ontvangen, kun je deze <a href="%{path}">opnieuw aanvragen</a> email_reconfirmation_html: Wanneer je de bevestigingsmail niet hebt ontvangen, kun je deze <a href="%{path}">opnieuw aanvragen</a>
@ -805,7 +805,7 @@ nl:
archive_takeout: archive_takeout:
date: Datum date: Datum
download: Jouw archief downloaden download: Jouw archief downloaden
hint_html: Je kunt een archief opvragen van jouw <strong>toots en geüploade media</strong>. De geëxporteerde gegevens zijn in het ActivityPub-formaat, dat door hiervoor geschikte software valt uit te lezen. Je kunt elke 7 dagen een kopie van je archief aanvragen. hint_html: Je kunt een archief opvragen van jouw <strong>berichten en geüploade media</strong>. De geëxporteerde gegevens zijn in het ActivityPub-formaat, dat door hiervoor geschikte software valt uit te lezen. Je kunt elke 7 dagen een kopie van je archief aanvragen.
in_progress: Jouw archief wordt samengesteld... in_progress: Jouw archief wordt samengesteld...
request: Jouw archief opvragen request: Jouw archief opvragen
size: Omvang size: Omvang
@ -820,7 +820,7 @@ nl:
add_new: Nieuwe toevoegen add_new: Nieuwe toevoegen
errors: errors:
limit: Je hebt al het maximaal aantal hashtags uitgelicht limit: Je hebt al het maximaal aantal hashtags uitgelicht
hint_html: "<strong>Wat zijn uitgelichte hashtags?</strong> Deze worden prominent op jouw openbare profiel getoond en stelt mensen in staat om jouw openbare toots per hashtag te bekijken. Het zijn een goed hulpmiddel om creatieve werkzaamheden of langetermijnprojecten bij te houden." hint_html: "<strong>Wat zijn uitgelichte hashtags?</strong> Deze worden prominent op jouw openbare profiel getoond en stelt mensen in staat om jouw openbare berichten per hashtag te bekijken. Het zijn een goed hulpmiddel om creatieve werkzaamheden of langetermijnprojecten bij te houden."
filters: filters:
contexts: contexts:
account: Profielen account: Profielen
@ -901,7 +901,7 @@ nl:
limit: Je hebt het maximaal aantal lijsten bereikt limit: Je hebt het maximaal aantal lijsten bereikt
media_attachments: media_attachments:
validations: validations:
images_and_video: Een video kan niet aan een toot met afbeeldingen worden gekoppeld images_and_video: Een video kan niet aan een bericht met afbeeldingen worden gekoppeld
not_ready: Kan geen bestanden toevoegen die nog niet zijn verwerkt. Probeer het later opnieuw! not_ready: Kan geen bestanden toevoegen die nog niet zijn verwerkt. Probeer het later opnieuw!
too_many: Er kunnen niet meer dan 4 afbeeldingen toegevoegd worden too_many: Er kunnen niet meer dan 4 afbeeldingen toegevoegd worden
migrations: migrations:
@ -954,8 +954,8 @@ nl:
other: "%{count} nieuwe meldingen sinds jouw laatste bezoek \U0001F418" other: "%{count} nieuwe meldingen sinds jouw laatste bezoek \U0001F418"
title: Tijdens jouw afwezigheid... title: Tijdens jouw afwezigheid...
favourite: favourite:
body: 'Jouw toot werd door %{name} aan hun favorieten toegevoegd:' body: 'Jouw bericht werd door %{name} aan diens favorieten toegevoegd:'
subject: "%{name} voegde jouw toot als favoriet toe" subject: "%{name} voegde jouw bericht als favoriet toe"
title: Nieuwe favoriet title: Nieuwe favoriet
follow: follow:
body: "%{name} volgt jou nu!" body: "%{name} volgt jou nu!"
@ -974,11 +974,11 @@ nl:
poll: poll:
subject: Een poll van %{name} is beëindigd subject: Een poll van %{name} is beëindigd
reblog: reblog:
body: 'Jouw toot werd door %{name} geboost:' body: 'Jouw bericht werd door %{name} geboost:'
subject: "%{name} boostte jouw toot" subject: "%{name} boostte jouw bericht"
title: Nieuwe boost title: Nieuwe boost
status: status:
subject: "%{name} heeft zojuist een toot geplaatst" subject: "%{name} heeft zojuist een bericht geplaatst"
notifications: notifications:
email_events: E-mailmeldingen voor gebeurtenissen email_events: E-mailmeldingen voor gebeurtenissen
email_events_hint: 'Selecteer gebeurtenissen waarvoor je meldingen wilt ontvangen:' email_events_hint: 'Selecteer gebeurtenissen waarvoor je meldingen wilt ontvangen:'
@ -997,7 +997,7 @@ nl:
code_hint: Voer de code in die door de authenticatie-app werd gegenereerd code_hint: Voer de code in die door de authenticatie-app werd gegenereerd
description_html: Na het instellen van <strong>tweestapsverificatie</strong> met een authenticatie-app, kun je alleen inloggen als je jouw mobiele telefoon bij je hebt. Hiermee genereer je namelijk de in te voeren toegangscode. description_html: Na het instellen van <strong>tweestapsverificatie</strong> met een authenticatie-app, kun je alleen inloggen als je jouw mobiele telefoon bij je hebt. Hiermee genereer je namelijk de in te voeren toegangscode.
enable: Inschakelen enable: Inschakelen
instructions_html: "<strong>Scan deze QR-code in Google Authenticator of een soortgelijke app op jouw mobiele telefoon</strong>. Van nu af aan genereert deze app toegangscodes die je bij het inloggen moet invoeren." instructions_html: "<strong>Scan deze QR-code in Google Authenticator of een soortgelijke app op jouw mobiele telefoon</strong>. Vanaf nu genereert deze app toegangscodes die je bij het inloggen moet invoeren."
manual_instructions: 'Voor het geval je de QR-code niet kunt scannen en het handmatig moet invoeren, vind je hieronder de geheime code in platte tekst:' manual_instructions: 'Voor het geval je de QR-code niet kunt scannen en het handmatig moet invoeren, vind je hieronder de geheime code in platte tekst:'
setup: Instellen setup: Instellen
wrong_code: De ingevoerde code is ongeldig! Klopt de systeemtijd van de server en die van jouw apparaat? wrong_code: De ingevoerde code is ongeldig! Klopt de systeemtijd van de server en die van jouw apparaat?
@ -1053,17 +1053,17 @@ nl:
remote_interaction: remote_interaction:
favourite: favourite:
proceed: Doorgaan met toevoegen aan jouw favorieten proceed: Doorgaan met toevoegen aan jouw favorieten
prompt: 'Je wilt de volgende toot aan jouw favorieten toevoegen:' prompt: 'Je wilt het volgende bericht aan jouw favorieten toevoegen:'
reblog: reblog:
proceed: Doorgaan met boosten proceed: Doorgaan met boosten
prompt: 'Je wilt de volgende toot boosten:' prompt: 'Je wilt het volgende bericht boosten:'
reply: reply:
proceed: Doorgaan met reageren proceed: Doorgaan met reageren
prompt: 'Je wilt op de volgende toot reageren:' prompt: 'Je wilt op het volgende bericht reageren:'
scheduled_statuses: scheduled_statuses:
over_daily_limit: Je hebt de limiet van %{limit} in te plannen toots voor die dag overschreden over_daily_limit: Je hebt de limiet van %{limit} in te plannen berichten voor vandaag overschreden
over_total_limit: Je hebt de limiet van %{limit} in te plannen toots overschreden over_total_limit: Je hebt de limiet van %{limit} in te plannen berichten overschreden
too_soon: De datum voor de ingeplande toot moet in de toekomst liggen too_soon: De datum voor het ingeplande bericht moet in de toekomst liggen
sessions: sessions:
activity: Laatst actief activity: Laatst actief
browser: Webbrowser browser: Webbrowser
@ -1093,7 +1093,7 @@ nl:
adobe_air: Adobe Air adobe_air: Adobe Air
android: Android android: Android
blackberry: Blackberry blackberry: Blackberry
chrome_os: ChromeOS chrome_os: Chrome OS
firefox_os: Firefox OS firefox_os: Firefox OS
ios: iOS ios: iOS
linux: Linux linux: Linux
@ -1144,12 +1144,12 @@ nl:
one: 'bevatte een niet toegestane hashtag: %{tags}' one: 'bevatte een niet toegestane hashtag: %{tags}'
other: 'bevatte niet toegestane hashtags: %{tags}' other: 'bevatte niet toegestane hashtags: %{tags}'
errors: errors:
in_reply_not_found: De toot waarop je probeert te reageren lijkt niet te bestaan. in_reply_not_found: Het bericht waarop je probeert te reageren lijkt niet te bestaan.
open_in_web: In de webapp openen open_in_web: In de webapp openen
over_character_limit: Limiet van %{max} tekens overschreden over_character_limit: Limiet van %{max} tekens overschreden
pin_errors: pin_errors:
limit: Je hebt het maximaal aantal toots al vastgezet limit: Je hebt het maximaal aantal bericht al vastgemaakt
ownership: Een toot van iemand anders kan niet worden vastgezet ownership: Een bericht van iemand anders kan niet worden vastgemaakt
reblog: Een boost kan niet worden vastgezet reblog: Een boost kan niet worden vastgezet
poll: poll:
total_people: total_people:
@ -1174,7 +1174,7 @@ nl:
unlisted: Minder openbaar unlisted: Minder openbaar
unlisted_long: Aan iedereen tonen, maar niet op openbare tijdlijnen unlisted_long: Aan iedereen tonen, maar niet op openbare tijdlijnen
stream_entries: stream_entries:
pinned: Vastgemaakte toot pinned: Vastgemaakt bericht
reblogged: boostte reblogged: boostte
sensitive_content: Gevoelige inhoud sensitive_content: Gevoelige inhoud
tags: tags:
@ -1332,7 +1332,7 @@ nl:
otp_lost_help_html: Als je toegang tot beiden kwijt bent geraakt, neem dan contact op via %{email} otp_lost_help_html: Als je toegang tot beiden kwijt bent geraakt, neem dan contact op via %{email}
seamless_external_login: Je bent ingelogd via een externe dienst, daarom zijn wachtwoorden en e-mailinstellingen niet beschikbaar. seamless_external_login: Je bent ingelogd via een externe dienst, daarom zijn wachtwoorden en e-mailinstellingen niet beschikbaar.
signed_in_as: 'Ingelogd als:' signed_in_as: 'Ingelogd als:'
suspicious_sign_in_confirmation: Het lijkt er op dat je nog niet eerder op dit apparaat bent ingelogd, en je bent een tijdje niet ingelogd, dus sturen we een beveiligingscode naar je e-mailadres om te bevestigen dat jij het bent. suspicious_sign_in_confirmation: Het lijkt er op dat je nog niet eerder op dit apparaat bent ingelogd, dus sturen we een beveiligingscode naar jouw e-mailadres om te bevestigen dat jij het bent.
verification: verification:
explanation_html: 'Je kunt <strong>jezelf verifiëren als de eigenaar van de links in de metadata van jouw profiel</strong>. Hiervoor moet op de gelinkte website een link terug naar jouw Mastodonprofiel staan. Deze link <strong>moet</strong> het <code>rel="me"</code>-attribuut bevatten. De omschrijving van de link maakt niet uit. Hier is een voorbeeld:' explanation_html: 'Je kunt <strong>jezelf verifiëren als de eigenaar van de links in de metadata van jouw profiel</strong>. Hiervoor moet op de gelinkte website een link terug naar jouw Mastodonprofiel staan. Deze link <strong>moet</strong> het <code>rel="me"</code>-attribuut bevatten. De omschrijving van de link maakt niet uit. Hier is een voorbeeld:'
verification: Verificatie verification: Verificatie

View File

@ -490,6 +490,7 @@ pt-PT:
other: Tentativas em %{count} dias diferentes. other: Tentativas em %{count} dias diferentes.
no_failures_recorded: Sem falhas registadas. no_failures_recorded: Sem falhas registadas.
title: Disponibilidade title: Disponibilidade
warning: A última tentativa de conectar a este servidor não foi bem sucedida
back_to_all: Todas back_to_all: Todas
back_to_limited: Limitadas back_to_limited: Limitadas
back_to_warning: Aviso back_to_warning: Aviso

View File

@ -480,6 +480,7 @@ ru:
availability: availability:
no_failures_recorded: Сбоев в записи нет. no_failures_recorded: Сбоев в записи нет.
title: Доступность title: Доступность
warning: Последняя попытка подключения к этому серверу не удалась
back_to_all: Все узлы back_to_all: Все узлы
back_to_limited: Все ограниченные узлы back_to_limited: Все ограниченные узлы
back_to_warning: Все узлы требующие внимания back_to_warning: Все узлы требующие внимания
@ -742,6 +743,7 @@ ru:
none: "%{name} отправил(а) предупреждение %{target}" none: "%{name} отправил(а) предупреждение %{target}"
sensitive: "%{name} отметил(а) учетную запись %{target} как деликатную" sensitive: "%{name} отметил(а) учетную запись %{target} как деликатную"
silence: "%{name} ограничил(а) учетную запись %{target}" silence: "%{name} ограничил(а) учетную запись %{target}"
appeal_approved: Обжаловано
appeal_pending: Обжалование в обработке appeal_pending: Обжалование в обработке
system_checks: system_checks:
database_schema_check: database_schema_check:
@ -806,6 +808,8 @@ ru:
empty: Вы еще не определили пресеты предупреждений. empty: Вы еще не определили пресеты предупреждений.
title: Управление шаблонами предупреждений title: Управление шаблонами предупреждений
admin_mailer: admin_mailer:
new_appeal:
subject: "%{username} обжалует решение модерации на %{instance}"
new_pending_account: new_pending_account:
body: Ниже указана информация учётной записи. Вы можете одобрить или отклонить заявку. body: Ниже указана информация учётной записи. Вы можете одобрить или отклонить заявку.
subject: Новая учётная запись для рассмотрения на %{instance} (%{username}) subject: Новая учётная запись для рассмотрения на %{instance} (%{username})

View File

@ -27,6 +27,8 @@ fa:
scheduled_at: برای انتشار فوری اعلامیه، خالی بگذارید scheduled_at: برای انتشار فوری اعلامیه، خالی بگذارید
starts_at: اختیاری. در صورتی که اعلامیه‌تان محدود به بازهٔ زمانی خاصی است starts_at: اختیاری. در صورتی که اعلامیه‌تان محدود به بازهٔ زمانی خاصی است
text: می‌توانید مانند یک بوق‌ معمولی بنویسید. یادتان باشد که اعلامیهٔ شما فضای صفحهٔ کاربران را اشغال خواهد کرد text: می‌توانید مانند یک بوق‌ معمولی بنویسید. یادتان باشد که اعلامیهٔ شما فضای صفحهٔ کاربران را اشغال خواهد کرد
appeal:
text: فقط یک بار می‌توانید برای اخطار اعتراض کنید
defaults: defaults:
autofollow: کسانی که از راه دعوت‌نامه عضو می‌شوند به طور خودکار پیگیر شما خواهند شد autofollow: کسانی که از راه دعوت‌نامه عضو می‌شوند به طور خودکار پیگیر شما خواهند شد
avatar: یکی از قالب‌های PNG یا GIF یا JPG. بیشترین اندازه %{size}. تصویر به اندازهٔ %{dimensions} پیکسل تبدیل خواهد شد avatar: یکی از قالب‌های PNG یا GIF یا JPG. بیشترین اندازه %{size}. تصویر به اندازهٔ %{dimensions} پیکسل تبدیل خواهد شد
@ -35,6 +37,7 @@ fa:
current_password: به دلایل امنیتی لطفاً رمز این حساب را وارد کنید current_password: به دلایل امنیتی لطفاً رمز این حساب را وارد کنید
current_username: برای تأیید، لطفاً نام کاربری حساب فعلی را وارد کنید current_username: برای تأیید، لطفاً نام کاربری حساب فعلی را وارد کنید
digest: تنها وقتی فرستاده می‌شود که مدتی طولانی فعالیتی نداشته باشید و در این مدت برای شما پیغام خصوصی‌ای نوشته شده باشد digest: تنها وقتی فرستاده می‌شود که مدتی طولانی فعالیتی نداشته باشید و در این مدت برای شما پیغام خصوصی‌ای نوشته شده باشد
discoverable: اجازه دهید حساب‌تان از طریق پیشنهادها، پرطرفدارها و سایر قابلیت‌ها، توسط افراد غریبه قابل کشف باشد
email: به شما ایمیل تأییدی فرستاده خواهد شد email: به شما ایمیل تأییدی فرستاده خواهد شد
fields: شما می‌توانید تا چهار مورد را در یک جدول در نمایهٔ خود نمایش دهید fields: شما می‌توانید تا چهار مورد را در یک جدول در نمایهٔ خود نمایش دهید
header: یکی از قالب‌های PNG یا GIF یا JPG. بیشترین اندازه %{size}. تصویر به اندازهٔ %{dimensions} پیکسل تبدیل خواهد شد header: یکی از قالب‌های PNG یا GIF یا JPG. بیشترین اندازه %{size}. تصویر به اندازهٔ %{dimensions} پیکسل تبدیل خواهد شد
@ -60,6 +63,7 @@ fa:
domain_allow: domain_allow:
domain: این دامین خواهد توانست داده‌ها از این سرور را دریافت کند و داده‌های از این دامین در این‌جا پردازش و ذخیره خواهند شد domain: این دامین خواهد توانست داده‌ها از این سرور را دریافت کند و داده‌های از این دامین در این‌جا پردازش و ذخیره خواهند شد
email_domain_block: email_domain_block:
domain: این می‌تواند نام دامنه‌ای باشد که در نشانی رایانامه یا رکورد MX استفاده می‌شود. پس از ثبت نام بررسی خواهند شد.
with_dns_records: تلاشی برای resolve کردن رکوردهای ساناد دامنهٔ داده‌شده انجام شده و نتیجه نیز مسدود خواهد شد with_dns_records: تلاشی برای resolve کردن رکوردهای ساناد دامنهٔ داده‌شده انجام شده و نتیجه نیز مسدود خواهد شد
featured_tag: featured_tag:
name: 'شاید بخواهید چنین چیزهایی را به کار ببرید:' name: 'شاید بخواهید چنین چیزهایی را به کار ببرید:'
@ -116,6 +120,8 @@ fa:
scheduled_at: زمان‌بندی انتشار scheduled_at: زمان‌بندی انتشار
starts_at: آغاز رویداد starts_at: آغاز رویداد
text: اعلامیه text: اعلامیه
appeal:
text: توضیح دهید که چرا این تصمیم باید معکوس شود
defaults: defaults:
autofollow: دعوت از دیگران برای عضو شدن و پیگیری حساب شما autofollow: دعوت از دیگران برای عضو شدن و پیگیری حساب شما
avatar: تصویر نمایه avatar: تصویر نمایه
@ -194,6 +200,7 @@ fa:
sign_up_requires_approval: محدود کردن ثبت نام‌ها sign_up_requires_approval: محدود کردن ثبت نام‌ها
severity: قانون severity: قانون
notification_emails: notification_emails:
appeal: شخصی به تصمیم ناظر اعتراض کرد
digest: فرستادن رایانامه‌های خلاصه digest: فرستادن رایانامه‌های خلاصه
favourite: وقتی کسی نوشتهٔ شما را پسندید ایمیل بفرست favourite: وقتی کسی نوشتهٔ شما را پسندید ایمیل بفرست
follow: وقتی کسی پیگیر شما شد ایمیل بفرست follow: وقتی کسی پیگیر شما شد ایمیل بفرست
@ -201,6 +208,8 @@ fa:
mention: وقتی کسی از شما نام برد ایمیل بفرست mention: وقتی کسی از شما نام برد ایمیل بفرست
pending_account: وقتی حساب تازه‌ای نیاز به بازبینی داشت ایمیل بفرست pending_account: وقتی حساب تازه‌ای نیاز به بازبینی داشت ایمیل بفرست
reblog: وقتی کسی نوشتهٔ شما را بازبوقید ایمیل بفرست reblog: وقتی کسی نوشتهٔ شما را بازبوقید ایمیل بفرست
report: گزارش جدیدی فرستاده شد
trending_tag: روند جدیدی نیازمند بازبینی است
rule: rule:
text: قانون text: قانون
tag: tag:

View File

@ -144,7 +144,7 @@ ku:
inbox_url: URLya guhêzkera wergirtî inbox_url: URLya guhêzkera wergirtî
irreversible: Li şûna veşartinê jê bibe irreversible: Li şûna veşartinê jê bibe
locale: Zimanê navrûyê locale: Zimanê navrûyê
locked: Ajimêr qefl bike locked: Ajimêr kilît bike
max_uses: Hejmara bikaranîna herî zêde max_uses: Hejmara bikaranîna herî zêde
new_password: Pêborîna nû new_password: Pêborîna nû
note: Jiyanname note: Jiyanname

View File

@ -7,18 +7,18 @@ nl:
account_migration: account_migration:
acct: Vul de gebruikersnaam@domein van het account in, waarnaartoe je wilt verhuizen acct: Vul de gebruikersnaam@domein van het account in, waarnaartoe je wilt verhuizen
account_warning_preset: account_warning_preset:
text: Je kunt voor toots specifieke tekst gebruiken, zoals URL's, hashtags en vermeldingen text: Je kunt specifieke tekst voor berichten gebruiken, zoals URL's, hashtags en vermeldingen
title: Optioneel. Niet zichtbaar voor de ontvanger title: Optioneel. Niet zichtbaar voor de ontvanger
admin_account_action: admin_account_action:
include_statuses: De gebruiker ziet welke toots verantwoordelijk zijn voor de moderatieactie of waarschuwing include_statuses: De gebruiker ziet welke berichten verantwoordelijk zijn voor de moderatieactie of waarschuwing
send_email_notification: De gebruiker ontvangt een uitleg over wat er met hun account is gebeurd send_email_notification: De gebruiker ontvangt een uitleg over wat er met diens account is gebeurd
text_html: Optioneel. Je kunt voor toots specifieke tekst gebruiken. Om tijd te besparen kun je <a href="%{path}">presets voor waarschuwingen toevoegen</a> text_html: Optioneel. Je kunt specifieke tekst voor berichten gebruiken. Om tijd te besparen kun je <a href="%{path}">presets voor waarschuwingen toevoegen</a>
type_html: Kies wat er met <strong>%{acct}</strong> moet gebeuren type_html: Kies wat er met <strong>%{acct}</strong> moet gebeuren
types: types:
disable: Voorkom dat de gebruiker hun account gebruikt, maar verwijder of verberg de inhoud niet. disable: Voorkom dat de gebruiker diens account gebruikt, maar verwijder of verberg de inhoud niet.
none: Gebruik dit om een waarschuwing naar de gebruiker te sturen, zonder dat nog een andere actie wordt uitgevoerd. none: Gebruik dit om een waarschuwing naar de gebruiker te sturen, zonder dat nog een andere actie wordt uitgevoerd.
sensitive: Forceer dat alle mediabijlagen van deze gebruiker als gevoelig worden gemarkeerd. sensitive: Forceer dat alle mediabijlagen van deze gebruiker als gevoelig worden gemarkeerd.
silence: Voorkom dat de gebruiker openbare toots kan versturen, verberg hun toots en meldingen voor mensen die hen niet volgen. silence: Voorkom dat de gebruiker openbare berichten kan versturen, verberg diens berichten en meldingen voor mensen die diegene niet volgen.
suspend: Alle interacties van en met dit account blokkeren en de inhoud verwijderen. Dit kan binnen dertig dagen worden teruggedraaid. suspend: Alle interacties van en met dit account blokkeren en de inhoud verwijderen. Dit kan binnen dertig dagen worden teruggedraaid.
warning_preset_id: Optioneel. Je kunt nog steeds handmatig tekst toevoegen aan het eind van de voorinstelling warning_preset_id: Optioneel. Je kunt nog steeds handmatig tekst toevoegen aan het eind van de voorinstelling
announcement: announcement:
@ -26,7 +26,7 @@ nl:
ends_at: Optioneel. De publicatie van de mededeling wordt op dit tijdstip automatisch beëindigd ends_at: Optioneel. De publicatie van de mededeling wordt op dit tijdstip automatisch beëindigd
scheduled_at: Laat leeg om de mededeling meteen te publiceren scheduled_at: Laat leeg om de mededeling meteen te publiceren
starts_at: Optioneel. In het geval dat jouw mededeling aan een bepaald tijdvak is gebonden starts_at: Optioneel. In het geval dat jouw mededeling aan een bepaald tijdvak is gebonden
text: Je kunt voor toots specifieke tekst gebruiken. Let op de ruimte die de mededeling op het scherm van de gebruiker inneemt text: Je kunt specifieke tekst voor berichten gebruiken. Let op de ruimte die de mededeling op het scherm van de gebruiker inneemt
defaults: defaults:
autofollow: Mensen die zich via de uitnodiging hebben geregistreerd, volgen jou automatisch autofollow: Mensen die zich via de uitnodiging hebben geregistreerd, volgen jou automatisch
avatar: PNG, GIF of JPG. Maximaal %{size}. Wordt teruggeschaald naar %{dimensions}px avatar: PNG, GIF of JPG. Maximaal %{size}. Wordt teruggeschaald naar %{dimensions}px
@ -39,20 +39,20 @@ nl:
fields: Je kan maximaal 4 items als een tabel op je profiel weergeven fields: Je kan maximaal 4 items als een tabel op je profiel weergeven
header: PNG, GIF of JPG. Maximaal %{size}. Wordt teruggeschaald naar %{dimensions}px header: PNG, GIF of JPG. Maximaal %{size}. Wordt teruggeschaald naar %{dimensions}px
inbox_url: Kopieer de URL van de voorpagina van de relayserver die je wil gebruiken inbox_url: Kopieer de URL van de voorpagina van de relayserver die je wil gebruiken
irreversible: Gefilterde toots verdwijnen onomkeerbaar, zelfs als de filter later wordt verwijderd irreversible: Gefilterde berichten verdwijnen onomkeerbaar, zelfs als de filter later wordt verwijderd
locale: De taal van de gebruikersomgeving, e-mails en pushmeldingen locale: De taal van de gebruikersomgeving, e-mails en pushmeldingen
locked: Door het goedkeuren van volgers handmatig bepalen wie jou mag volgen locked: Door het goedkeuren van volgers handmatig bepalen wie jou mag volgen
password: Gebruik tenminste 8 tekens password: Gebruik tenminste 8 tekens
phrase: Komt overeen ongeacht hoofd-/kleine letters of een inhoudswaarschuwing phrase: Komt overeen ongeacht hoofd-/kleine letters of een inhoudswaarschuwing
scopes: Tot welke API's heeft de toepassing toegang. Wanneer je een toestemming van het bovenste niveau kiest, hoef je geen individuele toestemmingen meer te kiezen. scopes: Tot welke API's heeft de toepassing toegang. Wanneer je een toestemming van het bovenste niveau kiest, hoef je geen individuele toestemmingen meer te kiezen.
setting_aggregate_reblogs: Geen nieuwe boosts tonen voor toots die recentelijk nog zijn geboost (heeft alleen effect op nieuw ontvangen boosts) setting_aggregate_reblogs: Geen nieuwe boosts tonen voor berichten die recentelijk nog zijn geboost (heeft alleen effect op nieuw ontvangen boosts)
setting_default_sensitive: Gevoelige media wordt standaard verborgen en kan met één klik worden getoond setting_default_sensitive: Gevoelige media wordt standaard verborgen en kan met één klik worden getoond
setting_display_media_default: Als gevoelig gemarkeerde media verbergen setting_display_media_default: Als gevoelig gemarkeerde media verbergen
setting_display_media_hide_all: Media altijd verbergen setting_display_media_hide_all: Media altijd verbergen
setting_display_media_show_all: Media altijd tonen setting_display_media_show_all: Media altijd tonen
setting_hide_network: Wie jij volgt en wie jou volgen wordt niet op jouw profiel getoond setting_hide_network: Wie jij volgt en wie jou volgen wordt niet op jouw profiel getoond
setting_noindex: Heeft invloed op jouw openbare profiel en toots setting_noindex: Heeft invloed op jouw openbare profiel en pagina's met berichten
setting_show_application: De toepassing de je gebruikt om te tooten wordt in de gedetailleerde weergave van de toot getoond setting_show_application: De toepassing de je gebruikt om berichten te plaatsen wordt in de gedetailleerde weergave van het bericht getoond
setting_use_blurhash: Wazige kleurovergangen zijn gebaseerd op de kleuren van de verborgen media, waarmee elk detail verdwijnt setting_use_blurhash: Wazige kleurovergangen zijn gebaseerd op de kleuren van de verborgen media, waarmee elk detail verdwijnt
setting_use_pending_items: De tijdlijn wordt bijgewerkt door op het aantal nieuwe items te klikken, in plaats van dat deze automatisch wordt bijgewerkt setting_use_pending_items: De tijdlijn wordt bijgewerkt door op het aantal nieuwe items te klikken, in plaats van dat deze automatisch wordt bijgewerkt
username: Jouw gebruikersnaam is uniek op %{domain} username: Jouw gebruikersnaam is uniek op %{domain}
@ -85,7 +85,7 @@ nl:
tag: tag:
name: Je kunt elk woord met een hoofdletter beginnen, om zo bijvoorbeeld de tekst leesbaarder te maken name: Je kunt elk woord met een hoofdletter beginnen, om zo bijvoorbeeld de tekst leesbaarder te maken
user: user:
chosen_languages: Alleen toots in de aangevinkte talen worden op de openbare tijdlijnen getoond chosen_languages: Alleen berichten in de aangevinkte talen worden op de openbare tijdlijnen getoond
labels: labels:
account: account:
fields: fields:
@ -99,7 +99,7 @@ nl:
text: Tekst van preset text: Tekst van preset
title: Titel title: Titel
admin_account_action: admin_account_action:
include_statuses: Gerapporteerde toots aan de e-mail toevoegen include_statuses: Gerapporteerde berichten aan de e-mail toevoegen
send_email_notification: Meld dit per e-mail aan de gebruiker send_email_notification: Meld dit per e-mail aan de gebruiker
text: Aangepaste waarschuwing text: Aangepaste waarschuwing
type: Actie type: Actie
@ -146,22 +146,22 @@ nl:
setting_advanced_layout: Geavanceerde webomgeving inschakelen setting_advanced_layout: Geavanceerde webomgeving inschakelen
setting_aggregate_reblogs: Boosts in tijdlijnen groeperen setting_aggregate_reblogs: Boosts in tijdlijnen groeperen
setting_auto_play_gif: Speel geanimeerde GIF's automatisch af setting_auto_play_gif: Speel geanimeerde GIF's automatisch af
setting_boost_modal: Vraag voor het boosten van een toot een bevestiging setting_boost_modal: Vraag voor het boosten van een bericht een bevestiging
setting_crop_images: Afbeeldingen bijsnijden tot 16x9 in toots op tijdlijnen setting_crop_images: Afbeeldingen bijsnijden tot 16x9 in berichten op tijdlijnen
setting_default_language: Taal van jouw toots setting_default_language: Taal van jouw berichten
setting_default_privacy: Standaardzichtbaarheid van jouw toots setting_default_privacy: Zichtbaarheid van nieuwe berichten
setting_default_sensitive: Media altijd als gevoelig markeren setting_default_sensitive: Media altijd als gevoelig markeren
setting_delete_modal: Vraag voor het verwijderen van een toot een bevestiging setting_delete_modal: Vraag voor het verwijderen van een bericht een bevestiging
setting_disable_swiping: Swipebewegingen uitschakelen setting_disable_swiping: Swipebewegingen uitschakelen
setting_display_media: Mediaweergave setting_display_media: Mediaweergave
setting_display_media_default: Standaard setting_display_media_default: Standaard
setting_display_media_hide_all: Alles verbergen setting_display_media_hide_all: Alles verbergen
setting_display_media_show_all: Alles tonen setting_display_media_show_all: Alles tonen
setting_expand_spoilers: Altijd toots met inhoudswaarschuwingen uitklappen setting_expand_spoilers: Altijd berichten met inhoudswaarschuwingen uitklappen
setting_hide_network: Jouw volgers en wie je volgt verbergen setting_hide_network: Jouw volgers en wie je volgt verbergen
setting_noindex: Jouw toots niet door zoekmachines laten indexeren setting_noindex: Jouw berichten niet door zoekmachines laten indexeren
setting_reduce_motion: Langzamere animaties setting_reduce_motion: Langzamere animaties
setting_show_application: Toepassing onthullen die je voor het verzenden van toots gebruikt setting_show_application: Toepassing onthullen die je voor het verzenden van berichten gebruikt
setting_system_font_ui: Standaardlettertype van jouw systeem gebruiken setting_system_font_ui: Standaardlettertype van jouw systeem gebruiken
setting_theme: Thema website setting_theme: Thema website
setting_trends: Trends van vandaag tonen setting_trends: Trends van vandaag tonen
@ -195,19 +195,19 @@ nl:
severity: Regel severity: Regel
notification_emails: notification_emails:
digest: Periodiek e-mails met een samenvatting versturen digest: Periodiek e-mails met een samenvatting versturen
favourite: Wanneer iemand jouw toot aan hun favorieten heeft toegevoegd favourite: Wanneer iemand jouw bericht aan diens favorieten heeft toegevoegd
follow: Wanneer iemand jou is gaan volgen follow: Wanneer iemand jou is gaan volgen
follow_request: Wanneer iemand jou wil volgen follow_request: Wanneer iemand jou wil volgen
mention: Wanneer iemand jou heeft vermeld mention: Wanneer iemand jou heeft vermeld
pending_account: Wanneer een nieuw account moet worden beoordeeld pending_account: Wanneer een nieuw account moet worden beoordeeld
reblog: Wanneer iemand jouw toot heeft geboost reblog: Wanneer iemand jouw bericht heeft geboost
rule: rule:
text: Regel text: Regel
tag: tag:
listable: Toestaan dat deze hashtag in zoekopdrachten en aanbevelingen te zien valt listable: Toestaan dat deze hashtag in zoekopdrachten en aanbevelingen te zien valt
name: Hashtag name: Hashtag
trendable: Toestaan dat deze hashtag onder trends te zien valt trendable: Toestaan dat deze hashtag onder trends te zien valt
usable: Toestaan dat deze hashtag in toots gebruikt mag worden usable: Toestaan dat deze hashtag in berichten gebruikt mag worden
'no': Nee 'no': Nee
recommended: Aanbevolen recommended: Aanbevolen
required: required:

View File

@ -74,7 +74,7 @@ th:
text: นี่จะช่วยให้เราตรวจทานใบสมัครของคุณ text: นี่จะช่วยให้เราตรวจทานใบสมัครของคุณ
ip_block: ip_block:
comment: ไม่จำเป็น จดจำเหตุผลที่คุณเพิ่มกฎนี้ comment: ไม่จำเป็น จดจำเหตุผลที่คุณเพิ่มกฎนี้
ip: ป้อนที่อยู่ IPv4 หรือ IPv6 คุณสามารถปิดกั้นทั้งช่วงได้โดยใช้ไวยากรณ์ CIDR ระวังอย่าล็อคตัวเองออก! ip: ป้อนที่อยู่ IPv4 หรือ IPv6 คุณสามารถปิดกั้นทั้งช่วงได้โดยใช้ไวยากรณ์ CIDR ระวังอย่าล็อคตัวคุณเองออก!
severities: severities:
no_access: ปิดกั้นการเข้าถึงทรัพยากรทั้งหมด no_access: ปิดกั้นการเข้าถึงทรัพยากรทั้งหมด
sign_up_requires_approval: การลงทะเบียนใหม่จะต้องมีการอนุมัติของคุณ sign_up_requires_approval: การลงทะเบียนใหม่จะต้องมีการอนุมัติของคุณ

View File

@ -404,6 +404,8 @@ sv:
status: Status status: Status
title: Följ rekommendationer title: Följ rekommendationer
instances: instances:
availability:
warning: Det senaste försöket att ansluta till denna värddator har misslyckats
back_to_all: Alla back_to_all: Alla
back_to_limited: Begränsat back_to_limited: Begränsat
back_to_warning: Varning back_to_warning: Varning

View File

@ -556,6 +556,9 @@ th:
other: "%{count} หมายเหตุ" other: "%{count} หมายเหตุ"
action_log: รายการบันทึกการตรวจสอบ action_log: รายการบันทึกการตรวจสอบ
action_taken_by: ใช้การกระทำโดย action_taken_by: ใช้การกระทำโดย
actions:
resolve_description_html: จะไม่ใช้การกระทำกับบัญชีที่รายงาน ไม่มีการบันทึกการดำเนินการ และจะปิดรายงาน
actions_description_html: ตัดสินใจว่าการกระทำใดที่จะใช้เพื่อแก้ปัญหารายงานนี้ หากคุณใช้การกระทำที่เป็นการลงโทษกับบัญชีที่รายงาน จะส่งการแจ้งเตือนอีเมลถึงเขา ยกเว้นเมื่อมีการเลือกหมวดหมู่ <strong>สแปม</strong>
are_you_sure: คุณแน่ใจหรือไม่? are_you_sure: คุณแน่ใจหรือไม่?
assign_to_self: มอบหมายให้ฉัน assign_to_self: มอบหมายให้ฉัน
assigned: ผู้ควบคุมที่ได้รับมอบหมาย assigned: ผู้ควบคุมที่ได้รับมอบหมาย
@ -883,6 +886,7 @@ th:
confirming: กำลังรอการยืนยันอีเมลให้เสร็จสมบูรณ์ confirming: กำลังรอการยืนยันอีเมลให้เสร็จสมบูรณ์
functional: บัญชีของคุณทำงานได้อย่างเต็มที่ functional: บัญชีของคุณทำงานได้อย่างเต็มที่
pending: ใบสมัครของคุณกำลังรอดำเนินการตรวจทานโดยพนักงานของเรา นี่อาจใช้เวลาสักครู่ คุณจะได้รับอีเมลหากใบสมัครของคุณได้รับการอนุมัติ pending: ใบสมัครของคุณกำลังรอดำเนินการตรวจทานโดยพนักงานของเรา นี่อาจใช้เวลาสักครู่ คุณจะได้รับอีเมลหากใบสมัครของคุณได้รับการอนุมัติ
view_strikes: ดูการดำเนินการที่ผ่านมากับบัญชีของคุณ
too_fast: ส่งแบบฟอร์มเร็วเกินไป ลองอีกครั้ง too_fast: ส่งแบบฟอร์มเร็วเกินไป ลองอีกครั้ง
trouble_logging_in: มีปัญหาในการเข้าสู่ระบบ? trouble_logging_in: มีปัญหาในการเข้าสู่ระบบ?
use_security_key: ใช้กุญแจความปลอดภัย use_security_key: ใช้กุญแจความปลอดภัย
@ -954,6 +958,7 @@ th:
submit: ส่งการอุทธรณ์ submit: ส่งการอุทธรณ์
associated_report: รายงานที่เกี่ยวข้อง associated_report: รายงานที่เกี่ยวข้อง
created_at: ลงวันที่ created_at: ลงวันที่
description_html: นี่คือการกระทำที่ใช้กับบัญชีของคุณและคำเตือนที่ส่งถึงคุณโดยพนักงานของ %{instance}
recipient: ส่งถึง recipient: ส่งถึง
status: 'โพสต์ #%{id}' status: 'โพสต์ #%{id}'
title: "%{action} จาก %{date}" title: "%{action} จาก %{date}"
@ -1410,9 +1415,11 @@ th:
user_mailer: user_mailer:
appeal_approved: appeal_approved:
action: ไปยังบัญชีของคุณ action: ไปยังบัญชีของคุณ
explanation: อนุมัติการอุทธรณ์การดำเนินการกับบัญชีของคุณเมื่อ %{strike_date} ที่คุณได้ส่งเมื่อ %{appeal_date} แล้ว บัญชีของคุณอยู่ในสถานะที่ดีอีกครั้งหนึ่ง
subject: อนุมัติการอุทธรณ์ของคุณจาก %{date} แล้ว subject: อนุมัติการอุทธรณ์ของคุณจาก %{date} แล้ว
title: อนุมัติการอุทธรณ์แล้ว title: อนุมัติการอุทธรณ์แล้ว
appeal_rejected: appeal_rejected:
explanation: ปฏิเสธการอุทธรณ์การดำเนินการกับบัญชีของคุณเมื่อ %{strike_date} ที่คุณได้ส่งเมื่อ %{appeal_date} แล้ว
subject: ปฏิเสธการอุทธรณ์ของคุณจาก %{date} แล้ว subject: ปฏิเสธการอุทธรณ์ของคุณจาก %{date} แล้ว
title: ปฏิเสธการอุทธรณ์แล้ว title: ปฏิเสธการอุทธรณ์แล้ว
backup_ready: backup_ready:
@ -1431,6 +1438,12 @@ th:
categories: categories:
spam: สแปม spam: สแปม
violation: เนื้อหาละเมิดหลักเกณฑ์ชุมชนดังต่อไปนี้ violation: เนื้อหาละเมิดหลักเกณฑ์ชุมชนดังต่อไปนี้
explanation:
delete_statuses: มีการพบว่าบางโพสต์ของคุณละเมิดหนึ่งหลักเกณฑ์ชุมชนหรือมากกว่าและได้รับการเอาออกโดยผู้ควบคุมของ %{instance} ในเวลาต่อมา
disable: คุณไม่สามารถใช้บัญชีของคุณได้อีกต่อไป แต่โปรไฟล์และข้อมูลอื่น ๆ ของคุณยังคงอยู่ในสภาพเดิม คุณสามารถขอข้อมูลสำรองของข้อมูลของคุณ เปลี่ยนการตั้งค่าบัญชี หรือลบบัญชีของคุณ
mark_statuses_as_sensitive: ทำเครื่องหมายบางโพสต์ของคุณว่าละเอียดอ่อนโดยผู้ควบคุมของ %{instance} แล้ว นี่หมายความว่าผู้คนจะต้องแตะสื่อในโพสต์ก่อนที่จะแสดงตัวอย่าง คุณสามารถทำเครื่องหมายสื่อว่าละเอียดอ่อนด้วยตัวคุณเองเมื่อโพสต์ในอนาคต
sensitive: จากนี้ไป จะทำเครื่องหมายไฟล์สื่อที่อัปโหลดทั้งหมดของคุณว่าละเอียดอ่อนและซ่อนอยู่หลังการคลิกไปยังคำเตือน
silence: คุณยังคงสามารถใช้บัญชีของคุณแต่เฉพาะผู้คนที่กำลังติดตามคุณอยู่แล้วเท่านั้นที่จะเห็นโพสต์ของคุณในเซิร์ฟเวอร์นี้ และอาจแยกคุณออกจากคุณลักษณะการค้นพบต่าง ๆ อย่างไรก็ตาม ผู้อื่นอาจยังติดตามคุณด้วยตนเอง
reason: 'เหตุผล:' reason: 'เหตุผล:'
statuses: 'โพสต์ที่อ้างถึง:' statuses: 'โพสต์ที่อ้างถึง:'
subject: subject:

View File

@ -490,6 +490,7 @@ tr:
other: "%{count} farklı gün başarısız girişim." other: "%{count} farklı gün başarısız girişim."
no_failures_recorded: Kayıtlı başarısızlık yok. no_failures_recorded: Kayıtlı başarısızlık yok.
title: Ulaşılabilirlik title: Ulaşılabilirlik
warning: Bu sunucuya önceki bağlanma denemesi başarısız olmuştu
back_to_all: Tümü back_to_all: Tümü
back_to_limited: Sınırlı back_to_limited: Sınırlı
back_to_warning: Uyarı back_to_warning: Uyarı

View File

@ -479,6 +479,7 @@ zh-CN:
other: 在 %{count} 天中尝试失败。 other: 在 %{count} 天中尝试失败。
no_failures_recorded: 没有失败记录。 no_failures_recorded: 没有失败记录。
title: 可用性 title: 可用性
warning: 上一次连接到此服务器的尝试失败了
back_to_all: 全部 back_to_all: 全部
back_to_limited: 受限 back_to_limited: 受限
back_to_warning: 警告 back_to_warning: 警告

View File

@ -479,6 +479,7 @@ zh-TW:
other: 錯誤嘗試於 %{count} 天。 other: 錯誤嘗試於 %{count} 天。
no_failures_recorded: 報告中沒有錯誤。 no_failures_recorded: 報告中沒有錯誤。
title: 可用狀態 title: 可用狀態
warning: 上一次嘗試連線至本伺服器失敗
back_to_all: 所有 back_to_all: 所有
back_to_limited: 受限制的 back_to_limited: 受限制的
back_to_warning: 警告 back_to_warning: 警告

View File

@ -17,7 +17,7 @@ module Mastodon
end end
def flags def flags
'rc1' 'rc3'
end end
def suffix def suffix

View File

@ -0,0 +1,55 @@
require 'rails_helper'
RSpec.describe EmojiFormatter do
let!(:emoji) { Fabricate(:custom_emoji, shortcode: 'coolcat') }
def preformat_text(str)
TextFormatter.new(str).to_s
end
describe '#to_s' do
subject { described_class.new(text, emojis).to_s }
let(:emojis) { [emoji] }
context 'given text that is not marked as html-safe' do
let(:text) { 'Foo' }
it 'raises an argument error' do
expect { subject }.to raise_error ArgumentError
end
end
context 'given text with an emoji shortcode at the start' do
let(:text) { preformat_text(':coolcat: Beep boop') }
it 'converts the shortcode to an image tag' do
is_expected.to match(/<img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
end
end
context 'given text with an emoji shortcode in the middle' do
let(:text) { preformat_text('Beep :coolcat: boop') }
it 'converts the shortcode to an image tag' do
is_expected.to match(/Beep <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
end
end
context 'given text with concatenated emoji shortcodes' do
let(:text) { preformat_text(':coolcat::coolcat:') }
it 'does not touch the shortcodes' do
is_expected.to match(/:coolcat::coolcat:/)
end
end
context 'given text with an emoji shortcode at the end' do
let(:text) { preformat_text('Beep boop :coolcat:') }
it 'converts the shortcode to an image tag' do
is_expected.to match(/boop <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
end
end
end
end

View File

@ -1,638 +0,0 @@
require 'rails_helper'
RSpec.describe Formatter do
let(:local_account) { Fabricate(:account, domain: nil, username: 'alice') }
let(:remote_account) { Fabricate(:account, domain: 'remote.test', username: 'bob', url: 'https://remote.test/') }
shared_examples 'encode and link URLs' do
context 'given a stand-alone medium URL' do
let(:text) { 'https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4' }
it 'matches the full URL' do
is_expected.to include 'href="https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4"'
end
end
context 'given a stand-alone google URL' do
let(:text) { 'http://google.com' }
it 'matches the full URL' do
is_expected.to include 'href="http://google.com"'
end
end
context 'given a stand-alone URL with a newer TLD' do
let(:text) { 'http://example.gay' }
it 'matches the full URL' do
is_expected.to include 'href="http://example.gay"'
end
end
context 'given a stand-alone IDN URL' do
let(:text) { 'https://nic.みんな/' }
it 'matches the full URL' do
is_expected.to include 'href="https://nic.みんな/"'
end
it 'has display URL' do
is_expected.to include '<span class="">nic.みんな/</span>'
end
end
context 'given a URL with a trailing period' do
let(:text) { 'http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona. ' }
it 'matches the full URL but not the period' do
is_expected.to include 'href="http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona"'
end
end
context 'given a URL enclosed with parentheses' do
let(:text) { '(http://google.com/)' }
it 'matches the full URL but not the parentheses' do
is_expected.to include 'href="http://google.com/"'
end
end
context 'given a URL with a trailing exclamation point' do
let(:text) { 'http://www.google.com!' }
it 'matches the full URL but not the exclamation point' do
is_expected.to include 'href="http://www.google.com"'
end
end
context 'given a URL with a trailing single quote' do
let(:text) { "http://www.google.com'" }
it 'matches the full URL but not the single quote' do
is_expected.to include 'href="http://www.google.com"'
end
end
context 'given a URL with a trailing angle bracket' do
let(:text) { 'http://www.google.com>' }
it 'matches the full URL but not the angle bracket' do
is_expected.to include 'href="http://www.google.com"'
end
end
context 'given a URL with a query string' do
context 'with escaped unicode character' do
let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink' }
it 'matches the full URL' do
is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&amp;q=autolink"'
end
end
context 'with unicode character' do
let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓&q=autolink' }
it 'matches the full URL' do
is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=✓&amp;q=autolink"'
end
end
context 'with unicode character at the end' do
let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓' }
it 'matches the full URL' do
is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=✓"'
end
end
context 'with escaped and not escaped unicode characters' do
let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&utf81=✓&q=autolink' }
it 'preserves escaped unicode characters' do
is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&amp;utf81=✓&amp;q=autolink"'
end
end
end
context 'given a URL with parentheses in it' do
let(:text) { 'https://en.wikipedia.org/wiki/Diaspora_(software)' }
it 'matches the full URL' do
is_expected.to include 'href="https://en.wikipedia.org/wiki/Diaspora_(software)"'
end
end
context 'given a URL in quotation marks' do
let(:text) { '"https://example.com/"' }
it 'does not match the quotation marks' do
is_expected.to include 'href="https://example.com/"'
end
end
context 'given a URL in angle brackets' do
let(:text) { '<https://example.com/>' }
it 'does not match the angle brackets' do
is_expected.to include 'href="https://example.com/"'
end
end
context 'given a URL with Japanese path string' do
let(:text) { 'https://ja.wikipedia.org/wiki/日本' }
it 'matches the full URL' do
is_expected.to include 'href="https://ja.wikipedia.org/wiki/日本"'
end
end
context 'given a URL with Korean path string' do
let(:text) { 'https://ko.wikipedia.org/wiki/대한민국' }
it 'matches the full URL' do
is_expected.to include 'href="https://ko.wikipedia.org/wiki/대한민국"'
end
end
context 'given a URL with a full-width space' do
let(:text) { 'https://example.com/ abc123' }
it 'does not match the full-width space' do
is_expected.to include 'href="https://example.com/"'
end
end
context 'given a URL in Japanese quotation marks' do
let(:text) { '「[https://example.org/」' }
it 'does not match the quotation marks' do
is_expected.to include 'href="https://example.org/"'
end
end
context 'given a URL with Simplified Chinese path string' do
let(:text) { 'https://baike.baidu.com/item/中华人民共和国' }
it 'matches the full URL' do
is_expected.to include 'href="https://baike.baidu.com/item/中华人民共和国"'
end
end
context 'given a URL with Traditional Chinese path string' do
let(:text) { 'https://zh.wikipedia.org/wiki/臺灣' }
it 'matches the full URL' do
is_expected.to include 'href="https://zh.wikipedia.org/wiki/臺灣"'
end
end
context 'given a URL containing unsafe code (XSS attack, visible part)' do
let(:text) { %q{http://example.com/b<del>b</del>} }
it 'does not include the HTML in the URL' do
is_expected.to include '"http://example.com/b"'
end
it 'escapes the HTML' do
is_expected.to include '&lt;del&gt;b&lt;/del&gt;'
end
end
context 'given a URL containing unsafe code (XSS attack, invisible part)' do
let(:text) { %q{http://example.com/blahblahblahblah/a<script>alert("Hello")</script>} }
it 'does not include the HTML in the URL' do
is_expected.to include '"http://example.com/blahblahblahblah/a"'
end
it 'escapes the HTML' do
is_expected.to include '&lt;script&gt;alert(&quot;Hello&quot;)&lt;/script&gt;'
end
end
context 'given text containing HTML code (script tag)' do
let(:text) { '<script>alert("Hello")</script>' }
it 'escapes the HTML' do
is_expected.to include '<p>&lt;script&gt;alert(&quot;Hello&quot;)&lt;/script&gt;</p>'
end
end
context 'given text containing HTML (XSS attack)' do
let(:text) { %q{<img src="javascript:alert('XSS');">} }
it 'escapes the HTML' do
is_expected.to include '<p>&lt;img src=&quot;javascript:alert(&apos;XSS&apos;);&quot;&gt;</p>'
end
end
context 'given an invalid URL' do
let(:text) { 'http://www\.google\.com' }
it 'outputs the raw URL' do
is_expected.to eq '<p>http://www\.google\.com</p>'
end
end
context 'given text containing a hashtag' do
let(:text) { '#hashtag' }
it 'creates a hashtag link' do
is_expected.to include '/tags/hashtag" class="mention hashtag" rel="tag">#<span>hashtag</span></a>'
end
end
context 'given text containing a hashtag with Unicode chars' do
let(:text) { '#hashtagタグ' }
it 'creates a hashtag link' do
is_expected.to include '/tags/hashtag%E3%82%BF%E3%82%B0" class="mention hashtag" rel="tag">#<span>hashtagタグ</span></a>'
end
end
context 'given a stand-alone xmpp: URI' do
let(:text) { 'xmpp:user@instance.com' }
it 'matches the full URI' do
is_expected.to include 'href="xmpp:user@instance.com"'
end
end
context 'given a an xmpp: URI with a query-string' do
let(:text) { 'please join xmpp:muc@instance.com?join right now' }
it 'matches the full URI' do
is_expected.to include 'href="xmpp:muc@instance.com?join"'
end
end
context 'given text containing a magnet: URI' do
let(:text) { 'wikipedia gives this example of a magnet uri: magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a' }
it 'matches the full URI' do
is_expected.to include 'href="magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a"'
end
end
end
describe '#format_spoiler' do
subject { Formatter.instance.format_spoiler(status) }
context 'given a post containing plain text' do
let(:status) { Fabricate(:status, text: 'text', spoiler_text: 'Secret!', uri: nil) }
it 'Returns the spoiler text' do
is_expected.to eq 'Secret!'
end
end
context 'given a post with an emoji shortcode at the start' do
let!(:emoji) { Fabricate(:custom_emoji) }
let(:status) { Fabricate(:status, text: 'text', spoiler_text: ':coolcat: Secret!', uri: nil) }
let(:text) { ':coolcat: Beep boop' }
it 'converts the shortcode to an image tag' do
is_expected.to match(/<img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
end
end
end
describe '#format' do
subject { Formatter.instance.format(status) }
context 'given a post with local status' do
context 'given a reblogged post' do
let(:reblog) { Fabricate(:status, account: local_account, text: 'Hello world', uri: nil) }
let(:status) { Fabricate(:status, reblog: reblog) }
it 'returns original status with credit to its author' do
is_expected.to include 'RT <span class="h-card"><a href="https://cb6e6126.ngrok.io/@alice" class="u-url mention">@<span>alice</span></a></span> Hello world'
end
end
context 'given a post containing plain text' do
let(:status) { Fabricate(:status, text: 'text', uri: nil) }
it 'paragraphizes the text' do
is_expected.to eq '<p>text</p>'
end
end
context 'given a post containing line feeds' do
let(:status) { Fabricate(:status, text: "line\nfeed", uri: nil) }
it 'removes line feeds' do
is_expected.not_to include "\n"
end
end
context 'given a post containing linkable mentions' do
let(:status) { Fabricate(:status, mentions: [ Fabricate(:mention, account: local_account) ], text: '@alice') }
it 'creates a mention link' do
is_expected.to include '<a href="https://cb6e6126.ngrok.io/@alice" class="u-url mention">@<span>alice</span></a></span>'
end
end
context 'given a post containing unlinkable mentions' do
let(:status) { Fabricate(:status, text: '@alice', uri: nil) }
it 'does not create a mention link' do
is_expected.to include '@alice'
end
end
context do
let(:content_type) { 'text/plain' }
subject do
status = Fabricate(:status, text: text, content_type: content_type, uri: nil)
Formatter.instance.format(status)
end
context 'given an invalid URL (invalid port)' do
let(:text) { 'https://foo.bar:X/' }
let(:content_type) { 'text/markdown' }
it 'outputs the raw URL' do
is_expected.to eq '<p>https://foo.bar:X/</p>'
end
end
include_examples 'encode and link URLs'
end
context 'given a post with custom_emojify option' do
let!(:emoji) { Fabricate(:custom_emoji) }
let(:status) { Fabricate(:status, account: local_account, text: text) }
subject { Formatter.instance.format(status, custom_emojify: true) }
context 'given a post with an emoji shortcode at the start' do
let(:text) { ':coolcat: Beep boop' }
it 'converts the shortcode to an image tag' do
is_expected.to match(/<p><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
end
end
context 'given a post with an emoji shortcode in the middle' do
let(:text) { 'Beep :coolcat: boop' }
it 'converts the shortcode to an image tag' do
is_expected.to match(/Beep <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
end
end
context 'given a post with concatenated emoji shortcodes' do
let(:text) { ':coolcat::coolcat:' }
it 'does not touch the shortcodes' do
is_expected.to match(/:coolcat::coolcat:/)
end
end
context 'given a post with an emoji shortcode at the end' do
let(:text) { 'Beep boop :coolcat:' }
it 'converts the shortcode to an image tag' do
is_expected.to match(/boop <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
end
end
end
end
context 'given a post with remote status' do
let(:status) { Fabricate(:status, account: remote_account, text: 'Beep boop') }
it 'reformats the post' do
is_expected.to eq 'Beep boop'
end
context 'given a post with custom_emojify option' do
let!(:emoji) { Fabricate(:custom_emoji, domain: remote_account.domain) }
let(:status) { Fabricate(:status, account: remote_account, text: text) }
subject { Formatter.instance.format(status, custom_emojify: true) }
context 'given a post with an emoji shortcode at the start' do
let(:text) { '<p>:coolcat: Beep boop<br />' }
it 'converts the shortcode to an image tag' do
is_expected.to match(/<p><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
end
end
context 'given a post with an emoji shortcode in the middle' do
let(:text) { '<p>Beep :coolcat: boop</p>' }
it 'converts the shortcode to an image tag' do
is_expected.to match(/Beep <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
end
end
context 'given a post with concatenated emoji' do
let(:text) { '<p>:coolcat::coolcat:</p>' }
it 'does not touch the shortcodes' do
is_expected.to match(/<p>:coolcat::coolcat:<\/p>/)
end
end
context 'given a post with an emoji shortcode at the end' do
let(:text) { '<p>Beep boop<br />:coolcat:</p>' }
it 'converts the shortcode to an image tag' do
is_expected.to match(/<br><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
end
end
end
end
end
describe '#reformat' do
subject { Formatter.instance.reformat(text) }
context 'given a post containing plain text' do
let(:text) { 'Beep boop' }
it 'keeps the plain text' do
is_expected.to include 'Beep boop'
end
end
context 'given a post containing script tags' do
let(:text) { '<script>alert("Hello")</script>' }
it 'strips the scripts' do
is_expected.to_not include '<script>alert("Hello")</script>'
end
end
context 'given a post containing malicious classes' do
let(:text) { '<span class="mention status__content__spoiler-link">Show more</span>' }
it 'strips the malicious classes' do
is_expected.to_not include 'status__content__spoiler-link'
end
end
end
describe '#plaintext' do
subject { Formatter.instance.plaintext(status) }
context 'given a post with local status' do
let(:status) { Fabricate(:status, text: '<p>a text by a nerd who uses an HTML tag in text</p>', content_type: content_type, uri: nil) }
let(:content_type) { 'text/plain' }
it 'returns the raw text' do
is_expected.to eq '<p>a text by a nerd who uses an HTML tag in text</p>'
end
end
context 'given a post with remote status' do
let(:status) { Fabricate(:status, account: remote_account, text: '<script>alert("Hello")</script>') }
it 'returns tag-stripped text' do
is_expected.to eq ''
end
end
end
describe '#simplified_format' do
subject { Formatter.instance.simplified_format(account) }
context 'given a post with local status' do
let(:account) { Fabricate(:account, domain: nil, note: text) }
context 'given a post containing linkable mentions for local accounts' do
let(:text) { '@alice' }
before { local_account }
it 'creates a mention link' do
is_expected.to eq '<p><span class="h-card"><a href="https://cb6e6126.ngrok.io/@alice" class="u-url mention">@<span>alice</span></a></span></p>'
end
end
context 'given a post containing linkable mentions for remote accounts' do
let(:text) { '@bob@remote.test' }
before { remote_account }
it 'creates a mention link' do
is_expected.to eq '<p><span class="h-card"><a href="https://remote.test/" class="u-url mention">@<span>bob</span></a></span></p>'
end
end
context 'given a post containing unlinkable mentions' do
let(:text) { '@alice' }
it 'does not create a mention link' do
is_expected.to eq '<p>@alice</p>'
end
end
context 'given a post with custom_emojify option' do
let!(:emoji) { Fabricate(:custom_emoji) }
before { account.note = text }
subject { Formatter.instance.simplified_format(account, custom_emojify: true) }
context 'given a post with an emoji shortcode at the start' do
let(:text) { ':coolcat: Beep boop' }
it 'converts the shortcode to an image tag' do
is_expected.to match(/<p><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
end
end
context 'given a post with an emoji shortcode in the middle' do
let(:text) { 'Beep :coolcat: boop' }
it 'converts the shortcode to an image tag' do
is_expected.to match(/Beep <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
end
end
context 'given a post with concatenated emoji shortcodes' do
let(:text) { ':coolcat::coolcat:' }
it 'does not touch the shortcodes' do
is_expected.to match(/:coolcat::coolcat:/)
end
end
context 'given a post with an emoji shortcode at the end' do
let(:text) { 'Beep boop :coolcat:' }
it 'converts the shortcode to an image tag' do
is_expected.to match(/boop <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
end
end
end
include_examples 'encode and link URLs'
end
context 'given a post with remote status' do
let(:text) { '<script>alert("Hello")</script>' }
let(:account) { Fabricate(:account, domain: 'remote', note: text) }
it 'reformats' do
is_expected.to_not include '<script>alert("Hello")</script>'
end
context 'with custom_emojify option' do
let!(:emoji) { Fabricate(:custom_emoji, domain: remote_account.domain) }
before { remote_account.note = text }
subject { Formatter.instance.simplified_format(remote_account, custom_emojify: true) }
context 'given a post with an emoji shortcode at the start' do
let(:text) { '<p>:coolcat: Beep boop<br />' }
it 'converts shortcode to image tag' do
is_expected.to match(/<p><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
end
end
context 'given a post with an emoji shortcode in the middle' do
let(:text) { '<p>Beep :coolcat: boop</p>' }
it 'converts shortcode to image tag' do
is_expected.to match(/Beep <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
end
end
context 'given a post with concatenated emoji shortcodes' do
let(:text) { '<p>:coolcat::coolcat:</p>' }
it 'does not touch the shortcodes' do
is_expected.to match(/<p>:coolcat::coolcat:<\/p>/)
end
end
context 'given a post with an emoji shortcode at the end' do
let(:text) { '<p>Beep boop<br />:coolcat:</p>' }
it 'converts shortcode to image tag' do
is_expected.to match(/<br><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
end
end
end
end
end
describe '#sanitize' do
let(:html) { '<script>alert("Hello")</script>' }
subject { Formatter.instance.sanitize(html, Sanitize::Config::MASTODON_STRICT) }
it 'sanitizes' do
is_expected.to eq ''
end
end
end

View File

@ -0,0 +1,44 @@
require 'rails_helper'
RSpec.describe HtmlAwareFormatter do
describe '#to_s' do
subject { described_class.new(text, local).to_s }
context 'when local' do
let(:local) { true }
let(:text) { 'Foo bar' }
it 'returns formatted text' do
is_expected.to eq '<p>Foo bar</p>'
end
end
context 'when remote' do
let(:local) { false }
context 'given plain text' do
let(:text) { 'Beep boop' }
it 'keeps the plain text' do
is_expected.to include 'Beep boop'
end
end
context 'given text containing script tags' do
let(:text) { '<script>alert("Hello")</script>' }
it 'strips the scripts' do
is_expected.to_not include '<script>alert("Hello")</script>'
end
end
context 'given text containing malicious classes' do
let(:text) { '<span class="mention status__content__spoiler-link">Show more</span>' }
it 'strips the malicious classes' do
is_expected.to_not include 'status__content__spoiler-link'
end
end
end
end
end

View File

@ -25,6 +25,14 @@ RSpec.describe LinkDetailsExtractor do
expect(subject.canonical_url).to eq 'https://foo.com/article' expect(subject.canonical_url).to eq 'https://foo.com/article'
end end
end end
context 'when canonical URL is set to "null"' do
let(:html) { '<!doctype html><link rel="canonical" href="null" />' }
it 'ignores the canonical URLs' do
expect(subject.canonical_url).to eq original_url
end
end
end end
context 'when structured data is present' do context 'when structured data is present' do

View File

@ -0,0 +1,24 @@
require 'rails_helper'
RSpec.describe PlainTextFormatter do
describe '#to_s' do
subject { described_class.new(status.text, status.local?).to_s }
context 'given a post with local status' do
let(:status) { Fabricate(:status, text: '<p>a text by a nerd who uses an HTML tag in text</p>', uri: nil) }
it 'returns the raw text' do
is_expected.to eq '<p>a text by a nerd who uses an HTML tag in text</p>'
end
end
context 'given a post with remote status' do
let(:remote_account) { Fabricate(:account, domain: 'remote.test', username: 'bob', url: 'https://remote.test/') }
let(:status) { Fabricate(:status, account: remote_account, text: '<p>Hello</p><script>alert("Hello")</script>') }
it 'returns tag-stripped text' do
is_expected.to eq 'Hello'
end
end
end
end

View File

@ -0,0 +1,313 @@
require 'rails_helper'
RSpec.describe TextFormatter do
describe '#to_s' do
let(:preloaded_accounts) { nil }
subject { described_class.new(text, preloaded_accounts: preloaded_accounts).to_s }
context 'given text containing plain text' do
let(:text) { 'text' }
it 'paragraphizes the text' do
is_expected.to eq '<p>text</p>'
end
end
context 'given text containing line feeds' do
let(:text) { "line\nfeed" }
it 'removes line feeds' do
is_expected.not_to include "\n"
end
end
context 'given text containing linkable mentions' do
let(:preloaded_accounts) { [Fabricate(:account, username: 'alice')] }
let(:text) { '@alice' }
it 'creates a mention link' do
is_expected.to include '<a href="https://cb6e6126.ngrok.io/@alice" class="u-url mention">@<span>alice</span></a></span>'
end
end
context 'given text containing unlinkable mentions' do
let(:preloaded_accounts) { [] }
let(:text) { '@alice' }
it 'does not create a mention link' do
is_expected.to include '@alice'
end
end
context 'given a stand-alone medium URL' do
let(:text) { 'https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4' }
it 'matches the full URL' do
is_expected.to include 'href="https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4"'
end
end
context 'given a stand-alone google URL' do
let(:text) { 'http://google.com' }
it 'matches the full URL' do
is_expected.to include 'href="http://google.com"'
end
end
context 'given a stand-alone URL with a newer TLD' do
let(:text) { 'http://example.gay' }
it 'matches the full URL' do
is_expected.to include 'href="http://example.gay"'
end
end
context 'given a stand-alone IDN URL' do
let(:text) { 'https://nic.みんな/' }
it 'matches the full URL' do
is_expected.to include 'href="https://nic.みんな/"'
end
it 'has display URL' do
is_expected.to include '<span class="">nic.みんな/</span>'
end
end
context 'given a URL with a trailing period' do
let(:text) { 'http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona. ' }
it 'matches the full URL but not the period' do
is_expected.to include 'href="http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona"'
end
end
context 'given a URL enclosed with parentheses' do
let(:text) { '(http://google.com/)' }
it 'matches the full URL but not the parentheses' do
is_expected.to include 'href="http://google.com/"'
end
end
context 'given a URL with a trailing exclamation point' do
let(:text) { 'http://www.google.com!' }
it 'matches the full URL but not the exclamation point' do
is_expected.to include 'href="http://www.google.com"'
end
end
context 'given a URL with a trailing single quote' do
let(:text) { "http://www.google.com'" }
it 'matches the full URL but not the single quote' do
is_expected.to include 'href="http://www.google.com"'
end
end
context 'given a URL with a trailing angle bracket' do
let(:text) { 'http://www.google.com>' }
it 'matches the full URL but not the angle bracket' do
is_expected.to include 'href="http://www.google.com"'
end
end
context 'given a URL with a query string' do
context 'with escaped unicode character' do
let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink' }
it 'matches the full URL' do
is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&amp;q=autolink"'
end
end
context 'with unicode character' do
let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓&q=autolink' }
it 'matches the full URL' do
is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=✓&amp;q=autolink"'
end
end
context 'with unicode character at the end' do
let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓' }
it 'matches the full URL' do
is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=✓"'
end
end
context 'with escaped and not escaped unicode characters' do
let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&utf81=✓&q=autolink' }
it 'preserves escaped unicode characters' do
is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&amp;utf81=✓&amp;q=autolink"'
end
end
end
context 'given a URL with parentheses in it' do
let(:text) { 'https://en.wikipedia.org/wiki/Diaspora_(software)' }
it 'matches the full URL' do
is_expected.to include 'href="https://en.wikipedia.org/wiki/Diaspora_(software)"'
end
end
context 'given a URL in quotation marks' do
let(:text) { '"https://example.com/"' }
it 'does not match the quotation marks' do
is_expected.to include 'href="https://example.com/"'
end
end
context 'given a URL in angle brackets' do
let(:text) { '<https://example.com/>' }
it 'does not match the angle brackets' do
is_expected.to include 'href="https://example.com/"'
end
end
context 'given a URL with Japanese path string' do
let(:text) { 'https://ja.wikipedia.org/wiki/日本' }
it 'matches the full URL' do
is_expected.to include 'href="https://ja.wikipedia.org/wiki/日本"'
end
end
context 'given a URL with Korean path string' do
let(:text) { 'https://ko.wikipedia.org/wiki/대한민국' }
it 'matches the full URL' do
is_expected.to include 'href="https://ko.wikipedia.org/wiki/대한민국"'
end
end
context 'given a URL with a full-width space' do
let(:text) { 'https://example.com/ abc123' }
it 'does not match the full-width space' do
is_expected.to include 'href="https://example.com/"'
end
end
context 'given a URL in Japanese quotation marks' do
let(:text) { '「[https://example.org/」' }
it 'does not match the quotation marks' do
is_expected.to include 'href="https://example.org/"'
end
end
context 'given a URL with Simplified Chinese path string' do
let(:text) { 'https://baike.baidu.com/item/中华人民共和国' }
it 'matches the full URL' do
is_expected.to include 'href="https://baike.baidu.com/item/中华人民共和国"'
end
end
context 'given a URL with Traditional Chinese path string' do
let(:text) { 'https://zh.wikipedia.org/wiki/臺灣' }
it 'matches the full URL' do
is_expected.to include 'href="https://zh.wikipedia.org/wiki/臺灣"'
end
end
context 'given a URL containing unsafe code (XSS attack, visible part)' do
let(:text) { %q{http://example.com/b<del>b</del>} }
it 'does not include the HTML in the URL' do
is_expected.to include '"http://example.com/b"'
end
it 'escapes the HTML' do
is_expected.to include '&lt;del&gt;b&lt;/del&gt;'
end
end
context 'given a URL containing unsafe code (XSS attack, invisible part)' do
let(:text) { %q{http://example.com/blahblahblahblah/a<script>alert("Hello")</script>} }
it 'does not include the HTML in the URL' do
is_expected.to include '"http://example.com/blahblahblahblah/a"'
end
it 'escapes the HTML' do
is_expected.to include '&lt;script&gt;alert(&quot;Hello&quot;)&lt;/script&gt;'
end
end
context 'given text containing HTML code (script tag)' do
let(:text) { '<script>alert("Hello")</script>' }
it 'escapes the HTML' do
is_expected.to include '<p>&lt;script&gt;alert(&quot;Hello&quot;)&lt;/script&gt;</p>'
end
end
context 'given text containing HTML (XSS attack)' do
let(:text) { %q{<img src="javascript:alert('XSS');">} }
it 'escapes the HTML' do
is_expected.to include '<p>&lt;img src=&quot;javascript:alert(&#39;XSS&#39;);&quot;&gt;</p>'
end
end
context 'given an invalid URL' do
let(:text) { 'http://www\.google\.com' }
it 'outputs the raw URL' do
is_expected.to eq '<p>http://www\.google\.com</p>'
end
end
context 'given text containing a hashtag' do
let(:text) { '#hashtag' }
it 'creates a hashtag link' do
is_expected.to include '/tags/hashtag" class="mention hashtag" rel="tag">#<span>hashtag</span></a>'
end
end
context 'given text containing a hashtag with Unicode chars' do
let(:text) { '#hashtagタグ' }
it 'creates a hashtag link' do
is_expected.to include '/tags/hashtag%E3%82%BF%E3%82%B0" class="mention hashtag" rel="tag">#<span>hashtagタグ</span></a>'
end
end
context 'given text with a stand-alone xmpp: URI' do
let(:text) { 'xmpp:user@instance.com' }
it 'matches the full URI' do
is_expected.to include 'href="xmpp:user@instance.com"'
end
end
context 'given text with an xmpp: URI with a query-string' do
let(:text) { 'please join xmpp:muc@instance.com?join right now' }
it 'matches the full URI' do
is_expected.to include 'href="xmpp:muc@instance.com?join"'
end
end
context 'given text containing a magnet: URI' do
let(:text) { 'wikipedia gives this example of a magnet uri: magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a' }
it 'matches the full URI' do
is_expected.to include 'href="magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a"'
end
end
end
end

Some files were not shown because too many files have changed in this diff Show More