mirror of
https://github.com/glitch-soc/mastodon.git
synced 2025-12-14 16:28:59 +00:00
Compare commits
13 Commits
compose-re
...
fix-column
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac686d5a5d | ||
|
|
ec620ae486 | ||
|
|
3b016342c6 | ||
|
|
3c18964256 | ||
|
|
c61dd918a2 | ||
|
|
02ba03d6db | ||
|
|
3bee0996c5 | ||
|
|
89daeb43a8 | ||
|
|
7d4f4f9aab | ||
|
|
256c2b1de0 | ||
|
|
02e3e1ec09 | ||
|
|
ff924f95bb | ||
|
|
c10f4bdb03 |
@@ -299,13 +299,11 @@ GEM
|
||||
sidekiq (>= 3.5.0)
|
||||
statsd-ruby (~> 1.2.0)
|
||||
oj (3.3.9)
|
||||
openssl (2.0.6)
|
||||
orm_adapter (0.5.0)
|
||||
ostatus2 (2.0.1)
|
||||
ostatus2 (2.0.2)
|
||||
addressable (~> 2.4)
|
||||
http (~> 2.0)
|
||||
nokogiri (~> 1.6)
|
||||
openssl (~> 2.0)
|
||||
ox (2.8.2)
|
||||
paperclip (5.1.0)
|
||||
activemodel (>= 4.2.0)
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
class AccountsController < ApplicationController
|
||||
include AccountControllerConcern
|
||||
include SignatureVerification
|
||||
|
||||
before_action :set_cache_headers
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
@@ -26,10 +27,11 @@ class AccountsController < ApplicationController
|
||||
end
|
||||
|
||||
format.json do
|
||||
render json: @account,
|
||||
serializer: ActivityPub::ActorSerializer,
|
||||
adapter: ActivityPub::Adapter,
|
||||
content_type: 'application/activity+json'
|
||||
skip_session!
|
||||
|
||||
render_cached_json(['activitypub', 'actor', @account.cache_key], content_type: 'application/activity+json') do
|
||||
ActiveModelSerializers::SerializableResource.new(@account, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,15 +4,19 @@ class ActivityPub::FollowsController < Api::BaseController
|
||||
include SignatureVerification
|
||||
|
||||
def show
|
||||
render(
|
||||
json: FollowRequest.includes(:account).references(:account).find_by!(
|
||||
id: params.require(:id),
|
||||
accounts: { domain: nil, username: params.require(:account_username) },
|
||||
target_account: signed_request_account
|
||||
),
|
||||
serializer: ActivityPub::FollowSerializer,
|
||||
adapter: ActivityPub::Adapter,
|
||||
content_type: 'application/activity+json'
|
||||
render json: follow_request,
|
||||
serializer: ActivityPub::FollowSerializer,
|
||||
adapter: ActivityPub::Adapter,
|
||||
content_type: 'application/activity+json'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def follow_request
|
||||
FollowRequest.includes(:account).references(:account).find_by!(
|
||||
id: params.require(:id),
|
||||
accounts: { domain: nil, username: params.require(:account_username) },
|
||||
target_account: signed_request_account
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -123,11 +123,24 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
|
||||
def render_cached_json(cache_key, **options)
|
||||
options[:expires_in] ||= 3.minutes
|
||||
options[:public] ||= true
|
||||
cache_key = cache_key.join(':') if cache_key.is_a?(Enumerable)
|
||||
content_type = options.delete(:content_type) || 'application/json'
|
||||
|
||||
data = Rails.cache.fetch(cache_key, { raw: true }.merge(options)) do
|
||||
yield.to_json
|
||||
end
|
||||
|
||||
expires_in options[:expires_in], public: true
|
||||
render json: data
|
||||
expires_in options[:expires_in], public: options[:public]
|
||||
render json: data, content_type: content_type
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Vary'] = 'Accept'
|
||||
end
|
||||
|
||||
def skip_session!
|
||||
request.session_options[:skip] = true
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
|
||||
class EmojisController < ApplicationController
|
||||
before_action :set_emoji
|
||||
before_action :set_cache_headers
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: @emoji,
|
||||
serializer: ActivityPub::EmojiSerializer,
|
||||
adapter: ActivityPub::Adapter,
|
||||
content_type: 'application/activity+json'
|
||||
skip_session!
|
||||
|
||||
render_cached_json(['activitypub', 'emoji', @emoji.cache_key], content_type: 'application/activity+json') do
|
||||
ActiveModelSerializers::SerializableResource.new(@emoji, serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,7 +10,7 @@ class StatusesController < ApplicationController
|
||||
before_action :set_link_headers
|
||||
before_action :check_account_suspension
|
||||
before_action :redirect_to_original, only: [:show]
|
||||
before_action { response.headers['Vary'] = 'Accept' }
|
||||
before_action :set_cache_headers
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
@@ -22,25 +22,21 @@ class StatusesController < ApplicationController
|
||||
end
|
||||
|
||||
format.json do
|
||||
render json: @status,
|
||||
serializer: ActivityPub::NoteSerializer,
|
||||
adapter: ActivityPub::Adapter,
|
||||
content_type: 'application/activity+json'
|
||||
skip_session! unless @stream_entry.hidden?
|
||||
|
||||
# Allow HTTP caching for 3 minutes if the status is public
|
||||
unless @stream_entry.hidden?
|
||||
request.session_options[:skip] = true
|
||||
expires_in(3.minutes, public: true)
|
||||
render_cached_json(['activitypub', 'note', @status.cache_key], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
|
||||
ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def activity
|
||||
render json: @status,
|
||||
serializer: ActivityPub::ActivitySerializer,
|
||||
adapter: ActivityPub::Adapter,
|
||||
content_type: 'application/activity+json'
|
||||
skip_session!
|
||||
|
||||
render_cached_json(['activitypub', 'activity', @status.cache_key], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
|
||||
ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter)
|
||||
end
|
||||
end
|
||||
|
||||
def embed
|
||||
|
||||
@@ -34,7 +34,7 @@ module Admin::ActionLogsHelper
|
||||
link_to attributes['domain'], "https://#{attributes['domain']}"
|
||||
when 'Status'
|
||||
tmp_status = Status.new(attributes)
|
||||
link_to tmp_status.account.acct, TagManager.instance.url_for(tmp_status)
|
||||
link_to tmp_status.account&.acct || "##{tmp_status.account_id}", TagManager.instance.url_for(tmp_status)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ module RoutingHelper
|
||||
extend ActiveSupport::Concern
|
||||
include Rails.application.routes.url_helpers
|
||||
include ActionView::Helpers::AssetTagHelper
|
||||
include Webpacker::Helper
|
||||
|
||||
included do
|
||||
def default_url_options
|
||||
@@ -17,6 +18,10 @@ module RoutingHelper
|
||||
URI.join(root_url, source).to_s
|
||||
end
|
||||
|
||||
def full_pack_url(source, **options)
|
||||
full_asset_url(asset_pack_path(source, options))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def use_storage?
|
||||
|
||||
@@ -23,7 +23,6 @@ export default class ColumnHeader extends React.PureComponent {
|
||||
icon: PropTypes.string.isRequired,
|
||||
active: PropTypes.bool,
|
||||
multiColumn: PropTypes.bool,
|
||||
focusable: PropTypes.bool,
|
||||
showBackButton: PropTypes.bool,
|
||||
children: PropTypes.node,
|
||||
pinned: PropTypes.bool,
|
||||
@@ -32,10 +31,6 @@ export default class ColumnHeader extends React.PureComponent {
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
focusable: true,
|
||||
}
|
||||
|
||||
state = {
|
||||
collapsed: true,
|
||||
animating: false,
|
||||
@@ -68,7 +63,7 @@ export default class ColumnHeader extends React.PureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { title, icon, active, children, pinned, onPin, multiColumn, focusable, showBackButton, intl: { formatMessage } } = this.props;
|
||||
const { title, icon, active, children, pinned, onPin, multiColumn, showBackButton, intl: { formatMessage } } = this.props;
|
||||
const { collapsed, animating } = this.state;
|
||||
|
||||
const wrapperClassName = classNames('column-header__wrapper', {
|
||||
@@ -135,11 +130,13 @@ export default class ColumnHeader extends React.PureComponent {
|
||||
|
||||
return (
|
||||
<div className={wrapperClassName}>
|
||||
<h1 tabIndex={focusable ? 0 : null} role='button' className={buttonClassName} aria-label={title} onClick={this.handleTitleClick}>
|
||||
<i className={`fa fa-fw fa-${icon} column-header__icon`} />
|
||||
<span className='column-header__title'>
|
||||
{title}
|
||||
</span>
|
||||
<h1 className={buttonClassName}>
|
||||
<button onClick={this.handleTitleClick}>
|
||||
<i className={`fa fa-fw fa-${icon} column-header__icon`} />
|
||||
<span className='column-header__title'>
|
||||
{title}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div className='column-header__buttons'>
|
||||
{backButton}
|
||||
|
||||
@@ -70,30 +70,28 @@ export default class GettingStarted extends ImmutablePureComponent {
|
||||
|
||||
navItems.push(
|
||||
<ColumnLink key='4' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />,
|
||||
<ColumnLink key='5' icon='thumb-tack' text={intl.formatMessage(messages.pins)} to='/pinned' />,
|
||||
<ColumnLink key='6' icon='bars' text={intl.formatMessage(messages.lists)} to='/lists' />
|
||||
<ColumnLink key='5' icon='bars' text={intl.formatMessage(messages.lists)} to='/lists' />
|
||||
);
|
||||
|
||||
if (myAccount.get('locked')) {
|
||||
navItems.push(<ColumnLink key='7' icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />);
|
||||
navItems.push(<ColumnLink key='6' icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />);
|
||||
}
|
||||
|
||||
navItems.push(
|
||||
<ColumnLink key='8' icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />,
|
||||
<ColumnLink key='9' icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />
|
||||
);
|
||||
|
||||
if (multiColumn) {
|
||||
navItems.push(<ColumnLink key='10' icon='question' text={intl.formatMessage(messages.keyboard_shortcuts)} to='/keyboard-shortcuts' />);
|
||||
navItems.push(<ColumnLink key='7' icon='question' text={intl.formatMessage(messages.keyboard_shortcuts)} to='/keyboard-shortcuts' />);
|
||||
}
|
||||
|
||||
navItems.push(<ColumnLink key='8' icon='book' text={intl.formatMessage(messages.info)} href='/about/more' />);
|
||||
|
||||
return (
|
||||
<Column icon='asterisk' heading={intl.formatMessage(messages.heading)} hideHeadingOnMobile>
|
||||
<div className='getting-started__wrapper'>
|
||||
<ColumnSubheading text={intl.formatMessage(messages.navigation_subheading)} />
|
||||
{navItems}
|
||||
<ColumnSubheading text={intl.formatMessage(messages.settings_subheading)} />
|
||||
<ColumnLink icon='book' text={intl.formatMessage(messages.info)} href='/about/more' />
|
||||
<ColumnLink icon='thumb-tack' text={intl.formatMessage(messages.pins)} to='/pinned' />
|
||||
<ColumnLink icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />
|
||||
<ColumnLink icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />
|
||||
<ColumnLink icon='cog' text={intl.formatMessage(messages.preferences)} href='/settings/preferences' />
|
||||
<ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' />
|
||||
</div>
|
||||
|
||||
@@ -64,8 +64,8 @@
|
||||
"confirmations.block.message": "你確定要封鎖 {name} ?",
|
||||
"confirmations.delete.confirm": "刪除",
|
||||
"confirmations.delete.message": "你確定要刪除這個狀態?",
|
||||
"confirmations.delete_list.confirm": "Delete",
|
||||
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
|
||||
"confirmations.delete_list.confirm": "刪除",
|
||||
"confirmations.delete_list.message": "確定要永久性地刪除這個名單嗎?",
|
||||
"confirmations.domain_block.confirm": "隱藏整個網域",
|
||||
"confirmations.domain_block.message": "你真的真的確定要隱藏整個 {domain} ?多數情況下,比較推薦封鎖或消音幾個特定目標就好。",
|
||||
"confirmations.mute.confirm": "消音",
|
||||
@@ -128,14 +128,14 @@
|
||||
"lightbox.close": "關閉",
|
||||
"lightbox.next": "繼續",
|
||||
"lightbox.previous": "回退",
|
||||
"lists.account.add": "Add to list",
|
||||
"lists.account.remove": "Remove from list",
|
||||
"lists.delete": "Delete list",
|
||||
"lists.edit": "Edit list",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.search": "Search among people you follow",
|
||||
"lists.subheading": "Your lists",
|
||||
"lists.account.add": "加到名單裡",
|
||||
"lists.account.remove": "從名單中移除",
|
||||
"lists.delete": "刪除名單",
|
||||
"lists.edit": "修改名單",
|
||||
"lists.new.create": "新增名單",
|
||||
"lists.new.title_placeholder": "名單名稱",
|
||||
"lists.search": "搜尋您關注的使用者",
|
||||
"lists.subheading": "您的名單",
|
||||
"loading_indicator.label": "讀取中...",
|
||||
"media_gallery.toggle_visible": "切換可見性",
|
||||
"missing_indicator.label": "找不到",
|
||||
@@ -146,8 +146,8 @@
|
||||
"navigation_bar.favourites": "最愛",
|
||||
"navigation_bar.follow_requests": "關注請求",
|
||||
"navigation_bar.info": "關於本站",
|
||||
"navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
|
||||
"navigation_bar.lists": "Lists",
|
||||
"navigation_bar.keyboard_shortcuts": "快速鍵",
|
||||
"navigation_bar.lists": "名單",
|
||||
"navigation_bar.logout": "登出",
|
||||
"navigation_bar.mutes": "消音的使用者",
|
||||
"navigation_bar.pins": "置頂貼文",
|
||||
|
||||
@@ -2350,6 +2350,19 @@
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
outline: 0;
|
||||
overflow: hidden;
|
||||
|
||||
& > button {
|
||||
display: flex;
|
||||
flex: auto;
|
||||
margin: 0;
|
||||
border: none;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
background: transparent;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&.active {
|
||||
box-shadow: 0 1px 0 rgba($ui-highlight-color, 0.3);
|
||||
|
||||
22
app/serializers/activitypub/delete_actor_serializer.rb
Normal file
22
app/serializers/activitypub/delete_actor_serializer.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::DeleteActorSerializer < ActiveModel::Serializer
|
||||
attributes :id, :type, :actor
|
||||
attribute :virtual_object, key: :object
|
||||
|
||||
def id
|
||||
[ActivityPub::TagManager.instance.uri_for(object), '#delete'].join
|
||||
end
|
||||
|
||||
def type
|
||||
'Delete'
|
||||
end
|
||||
|
||||
def actor
|
||||
ActivityPub::TagManager.instance.uri_for(object)
|
||||
end
|
||||
|
||||
def virtual_object
|
||||
actor
|
||||
end
|
||||
end
|
||||
@@ -27,7 +27,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
|
||||
end
|
||||
|
||||
def thumbnail
|
||||
full_asset_url(instance_presenter.thumbnail.file.url) if instance_presenter.thumbnail
|
||||
instance_presenter.thumbnail ? full_asset_url(instance_presenter.thumbnail.file.url) : full_pack_url('preview.jpg')
|
||||
end
|
||||
|
||||
def stats
|
||||
|
||||
@@ -17,9 +17,7 @@ class BatchedRemoveStatusService < BaseService
|
||||
|
||||
@stream_entry_batches = []
|
||||
@salmon_batches = []
|
||||
@activity_json_batches = []
|
||||
@json_payloads = statuses.map { |s| [s.id, Oj.dump(event: :delete, payload: s.id.to_s)] }.to_h
|
||||
@activity_json = {}
|
||||
@activity_xml = {}
|
||||
|
||||
# Ensure that rendered XML reflects destroyed state
|
||||
@@ -32,10 +30,7 @@ class BatchedRemoveStatusService < BaseService
|
||||
unpush_from_home_timelines(account, account_statuses)
|
||||
unpush_from_list_timelines(account, account_statuses)
|
||||
|
||||
if account.local?
|
||||
batch_stream_entries(account, account_statuses)
|
||||
batch_activity_json(account, account_statuses)
|
||||
end
|
||||
batch_stream_entries(account, account_statuses) if account.local?
|
||||
end
|
||||
|
||||
# Cannot be batched
|
||||
@@ -46,7 +41,6 @@ class BatchedRemoveStatusService < BaseService
|
||||
|
||||
Pubsubhubbub::RawDistributionWorker.push_bulk(@stream_entry_batches) { |batch| batch }
|
||||
NotificationWorker.push_bulk(@salmon_batches) { |batch| batch }
|
||||
ActivityPub::DeliveryWorker.push_bulk(@activity_json_batches) { |batch| batch }
|
||||
end
|
||||
|
||||
private
|
||||
@@ -57,22 +51,6 @@ class BatchedRemoveStatusService < BaseService
|
||||
end
|
||||
end
|
||||
|
||||
def batch_activity_json(account, statuses)
|
||||
account.followers.inboxes.each do |inbox_url|
|
||||
statuses.each do |status|
|
||||
@activity_json_batches << [build_json(status), account.id, inbox_url]
|
||||
end
|
||||
end
|
||||
|
||||
statuses.each do |status|
|
||||
other_recipients = (status.mentions + status.reblogs).map(&:account).reject(&:local?).select(&:activitypub?).uniq(&:id)
|
||||
|
||||
other_recipients.each do |target_account|
|
||||
@activity_json_batches << [build_json(status), account.id, target_account.inbox_url]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unpush_from_home_timelines(account, statuses)
|
||||
recipients = account.followers.local.to_a
|
||||
|
||||
@@ -123,23 +101,9 @@ class BatchedRemoveStatusService < BaseService
|
||||
Redis.current
|
||||
end
|
||||
|
||||
def build_json(status)
|
||||
return @activity_json[status.id] if @activity_json.key?(status.id)
|
||||
|
||||
@activity_json[status.id] = sign_json(status, ActiveModelSerializers::SerializableResource.new(
|
||||
status,
|
||||
serializer: status.reblog? ? ActivityPub::UndoAnnounceSerializer : ActivityPub::DeleteSerializer,
|
||||
adapter: ActivityPub::Adapter
|
||||
).as_json)
|
||||
end
|
||||
|
||||
def build_xml(stream_entry)
|
||||
return @activity_xml[stream_entry.id] if @activity_xml.key?(stream_entry.id)
|
||||
|
||||
@activity_xml[stream_entry.id] = stream_entry_to_xml(stream_entry)
|
||||
end
|
||||
|
||||
def sign_json(status, json)
|
||||
Oj.dump(ActivityPub::LinkedDataSignature.new(json).sign!(status.account))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -50,7 +50,7 @@ class FetchAtomService < BaseService
|
||||
@unsupported_activity = true
|
||||
nil
|
||||
end
|
||||
elsif @response['Link'] && !terminal
|
||||
elsif @response['Link'] && !terminal && link_header.find_link(%w(rel alternate))
|
||||
process_headers
|
||||
elsif @response.mime_type == 'text/html' && !terminal
|
||||
process_html
|
||||
@@ -70,8 +70,6 @@ class FetchAtomService < BaseService
|
||||
end
|
||||
|
||||
def process_headers
|
||||
link_header = LinkHeader.parse(@response['Link'].is_a?(Array) ? @response['Link'].first : @response['Link'])
|
||||
|
||||
json_link = link_header.find_link(%w(rel alternate), %w(type application/activity+json)) || link_header.find_link(%w(rel alternate), ['type', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'])
|
||||
atom_link = link_header.find_link(%w(rel alternate), %w(type application/atom+xml))
|
||||
|
||||
@@ -80,4 +78,8 @@ class FetchAtomService < BaseService
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def link_header
|
||||
@link_header ||= LinkHeader.parse(@response['Link'].is_a?(Array) ? @response['Link'].first : @response['Link'])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -22,6 +22,8 @@ class SuspendAccountService < BaseService
|
||||
end
|
||||
|
||||
def purge_content!
|
||||
ActivityPub::RawDistributionWorker.perform_async(delete_actor_json, @account.id) if @account.local?
|
||||
|
||||
@account.statuses.reorder(nil).find_in_batches do |statuses|
|
||||
BatchedRemoveStatusService.new.call(statuses)
|
||||
end
|
||||
@@ -54,4 +56,14 @@ class SuspendAccountService < BaseService
|
||||
def destroy_all(association)
|
||||
association.in_batches.destroy_all
|
||||
end
|
||||
|
||||
def delete_actor_json
|
||||
payload = ActiveModelSerializers::SerializableResource.new(
|
||||
@account,
|
||||
serializer: ActivityPub::DeleteActorSerializer,
|
||||
adapter: ActivityPub::Adapter
|
||||
).as_json
|
||||
|
||||
Oj.dump(ActivityPub::LinkedDataSignature.new(payload).sign!(@account))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -160,6 +160,7 @@ pl:
|
||||
update_status: "%{name} zaktualizował wpis użytkownika %{target}"
|
||||
title: Dziennik działań administracyjnych
|
||||
custom_emojis:
|
||||
by_domain: Według domeny
|
||||
copied_msg: Pomyślnie utworzono lokalną kopię emoji
|
||||
copy: Kopiuj
|
||||
copy_failed_msg: Nie udało się utworzyć lokalnej kopii emoji
|
||||
@@ -612,6 +613,7 @@ pl:
|
||||
private: Nie możesz przypiąć niepublicznego wpisu
|
||||
reblog: Nie możesz przypiąć podbicia wpisu
|
||||
show_more: Pokaż więcej
|
||||
title: '%{name}: "%{quote}"'
|
||||
visibilities:
|
||||
private: Tylko dla śledzących
|
||||
private_long: Widoczne tylko dla osób, które Cię śledzą
|
||||
|
||||
@@ -21,7 +21,7 @@ zh-TW:
|
||||
data: 資料
|
||||
display_name: 顯示名稱
|
||||
email: 電子信箱
|
||||
filtered_languages: 封鎖下面语言的文章
|
||||
filtered_languages: 封鎖下面語言的文章
|
||||
header: 個人頁面頂部
|
||||
locale: 語言
|
||||
locked: 將帳號轉為「私密」
|
||||
@@ -29,7 +29,16 @@ zh-TW:
|
||||
note: 簡介
|
||||
otp_attempt: 雙因子驗證碼
|
||||
password: 密碼
|
||||
setting_auto_play_gif: 自動播放 GIFs
|
||||
setting_boost_modal: 轉推前跳出確認視窗
|
||||
setting_default_privacy: 文章預設隱私度
|
||||
setting_default_sensitive: 預設我的內容為敏感內容
|
||||
setting_delete_modal: 刪推前跳出確認視窗
|
||||
setting_noindex: 不被搜尋引擎檢索
|
||||
setting_reduce_motion: 減低動畫效果
|
||||
setting_system_font_ui: 使用系統預設字體
|
||||
setting_theme: 網站主題
|
||||
setting_unfollow_modal: 取消關注前跳出確認視窗
|
||||
type: 匯入資料類型
|
||||
username: 使用者名稱
|
||||
interactions:
|
||||
|
||||
@@ -55,7 +55,7 @@ zh-TW:
|
||||
perform_full_suspension: 進行停權
|
||||
profile_url: 個人檔案網址
|
||||
public: 公開
|
||||
push_subscription_expires: PuSH 訂閱逾期
|
||||
push_subscription_expires: 推播訂閱過期
|
||||
salmon_url: Salmon URL
|
||||
silence: 靜音
|
||||
statuses: 狀態
|
||||
@@ -133,12 +133,14 @@ zh-TW:
|
||||
forgot_password: 忘記密碼?
|
||||
login: 登入
|
||||
logout: 登出
|
||||
migrate_account: 轉移到另一個帳號
|
||||
migrate_account_html: 想要將這個帳號指向另一個帳號可到<a href="%{path}">到這裡設定</a>。
|
||||
register: 註冊
|
||||
resend_confirmation: 重寄驗證信
|
||||
reset_password: 重設密碼
|
||||
set_new_password: 設定新密碼
|
||||
authorize_follow:
|
||||
error: 對不起,尋找這個跨站使用者的過程發生錯誤
|
||||
error: 對不起,搜尋遠端使用者出現錯誤
|
||||
follow: 關注
|
||||
title: 關注 %{acct}
|
||||
datetime:
|
||||
@@ -165,7 +167,16 @@ zh-TW:
|
||||
blocks: 您封鎖的使用者
|
||||
csv: CSV
|
||||
follows: 您關注的使用者
|
||||
mutes: 您靜音的使用者
|
||||
storage: 儲存空間大小
|
||||
followers:
|
||||
domain: 網域
|
||||
explanation_html: 為確保個人隱私,您必須知道有哪些使用者正關注你。<strong>您的私密內容會被發送到所有您有被關注的服務站上</strong>。如果您不信任這些服務站的管理者,您可以選擇檢查或刪除您的關注者。
|
||||
followers_count: 關注者數
|
||||
lock_link: 鎖住你的帳號
|
||||
purge: 移除關注者
|
||||
unlocked_warning_html: 所有人都可以關注並檢索你的隱藏狀態。%{lock_link}以檢查或拒絕關注。
|
||||
unlocked_warning_title: 你的帳號是公開的
|
||||
generic:
|
||||
changes_saved_msg: 已成功儲存修改
|
||||
powered_by: 網站由 %{link} 開發
|
||||
@@ -179,6 +190,7 @@ zh-TW:
|
||||
types:
|
||||
blocking: 您封鎖的使用者名單
|
||||
following: 您關注的使用者名單
|
||||
muting: 您靜音的使用者名單
|
||||
upload: 上傳
|
||||
landing_strip_html: "<strong>%{name}</strong> 是一個在 %{link_to_root_path} 的使用者。只要您有任何 Mastodon 服務站、或者聯盟網站的帳號,便可以跨站關注此站使用者,或者與他們互動。"
|
||||
landing_strip_signup_html: 如果您沒有這些帳號,歡迎在<a href="%{sign_up_path}">這裡註冊</a>。
|
||||
@@ -231,15 +243,26 @@ zh-TW:
|
||||
missing_resource: 無法找到資源
|
||||
proceed: 下一步
|
||||
prompt: 您希望關注︰
|
||||
sessions:
|
||||
activity: 最近活動
|
||||
browser: 瀏覽器
|
||||
current_session: 目前的 session
|
||||
description: "%{platform} 上的 %{browser}"
|
||||
explanation: 這些是現在正登入於你的 Mastodon 帳號的瀏覽器。
|
||||
revoke: 取消
|
||||
revoke_success: Session 取消成功。
|
||||
settings:
|
||||
authorized_apps: 已授權應用程式
|
||||
back: 回到 Mastodon
|
||||
development: 開發
|
||||
edit_profile: 修改個人資料
|
||||
export: 匯出
|
||||
followers: 授權追蹤者
|
||||
import: 匯入
|
||||
notifications: 通知
|
||||
preferences: 偏好設定
|
||||
settings: 設定
|
||||
two_factor_authentication: 雙因子認證
|
||||
two_factor_authentication: 兩階段認證
|
||||
statuses:
|
||||
open_in_web: 以網頁開啟
|
||||
over_character_limit: 超過了 %{max} 字的限制
|
||||
@@ -257,14 +280,14 @@ zh-TW:
|
||||
default: "%Y年%-m月%d日 %H:%M"
|
||||
two_factor_authentication:
|
||||
code_hint: 請輸入您認證器產生的代碼,以進行認證
|
||||
description_html: 當您啟用<strong>雙因子認證</strong>後,您登入時將需要使您手機、或其他種類認證器產生的代碼。
|
||||
description_html: 啟用<strong>兩階段認證</strong>後,登入時將需要使手機、或其他種類認證器產生的代碼。
|
||||
disable: 停用
|
||||
enable: 啟用
|
||||
enabled_success: 已成功啟用雙因子認證
|
||||
instructions_html: "<strong>請用您手機的認證器應用程式(如 Google Authenticator、Authy),掃描這裡的 QR 圖形碼</strong>。在雙因子認證啟用後,您登入時將須要使用此應用程式產生的認證碼。"
|
||||
enabled_success: 已成功啟用兩階段認證
|
||||
instructions_html: "<strong>請用您手機的認證器應用程式(如 Google Authenticator、Authy),掃描這裡的 QR 圖形碼</strong>。在兩階段認證啟用後,您登入時將須要使用此應用程式產生的認證碼。"
|
||||
manual_instructions: 如果您無法掃描 QR 圖形碼,請手動輸入︰
|
||||
setup: 設定
|
||||
wrong_code: 您輸入的認證碼並不正確!可能伺服器時間和您手機不一致,請檢查您手機的時間,或與本站管理員聯絡。
|
||||
users:
|
||||
invalid_email: 信箱地址格式不正確
|
||||
invalid_otp_token: 雙因子認證碼不正確
|
||||
invalid_otp_token: 兩階段認證碼不正確
|
||||
|
||||
Reference in New Issue
Block a user