Merge remote-tracking branch 'parent/main' into upstream-20241107
|
@ -1 +1 @@
|
|||
3.3.5
|
||||
3.3.6
|
||||
|
|
|
@ -12,7 +12,7 @@ ARG BUILDPLATFORM=${BUILDPLATFORM}
|
|||
|
||||
# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.3.x"]
|
||||
# renovate: datasource=docker depName=docker.io/ruby
|
||||
ARG RUBY_VERSION="3.3.5"
|
||||
ARG RUBY_VERSION="3.3.6"
|
||||
# # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"]
|
||||
# renovate: datasource=node-version depName=node
|
||||
ARG NODE_MAJOR_VERSION="22"
|
||||
|
|
5
Gemfile
|
@ -25,7 +25,7 @@ gem 'ruby-vips', '~> 2.2', require: false
|
|||
gem 'active_model_serializers', '~> 0.10'
|
||||
gem 'addressable', '~> 2.8'
|
||||
gem 'bootsnap', '~> 1.18.0', require: false
|
||||
gem 'browser', '< 6' # https://github.com/fnando/browser/issues/543
|
||||
gem 'browser'
|
||||
gem 'charlock_holmes', '~> 0.7.7'
|
||||
gem 'chewy', '~> 7.3'
|
||||
gem 'devise', '~> 4.9'
|
||||
|
@ -47,13 +47,14 @@ gem 'color_diff', '~> 0.1'
|
|||
gem 'csv', '~> 3.2'
|
||||
gem 'discard', '~> 1.2'
|
||||
gem 'doorkeeper', '~> 5.6'
|
||||
gem 'faraday-httpclient'
|
||||
gem 'fast_blank', '~> 1.0'
|
||||
gem 'fastimage'
|
||||
gem 'hiredis', '~> 0.6'
|
||||
gem 'htmlentities', '~> 4.3'
|
||||
gem 'http', '~> 5.2.0'
|
||||
gem 'http_accept_language', '~> 2.1'
|
||||
gem 'httplog', '~> 1.7.0'
|
||||
gem 'httplog', '~> 1.7.0', require: false
|
||||
gem 'i18n'
|
||||
gem 'idn-ruby', require: 'idn'
|
||||
gem 'inline_svg'
|
||||
|
|
46
Gemfile.lock
|
@ -133,7 +133,7 @@ GEM
|
|||
msgpack (~> 1.2)
|
||||
brakeman (6.2.2)
|
||||
racc
|
||||
browser (5.3.1)
|
||||
browser (6.0.0)
|
||||
brpoplpush-redis_script (0.1.3)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.5)
|
||||
redis (>= 1.0, < 6)
|
||||
|
@ -200,8 +200,8 @@ GEM
|
|||
devise (>= 4.0.0)
|
||||
rpam2 (~> 4.0)
|
||||
diff-lcs (1.5.1)
|
||||
discard (1.3.0)
|
||||
activerecord (>= 4.2, < 8)
|
||||
discard (1.4.0)
|
||||
activerecord (>= 4.2, < 9.0)
|
||||
docile (1.4.1)
|
||||
domain_name (0.6.20240107)
|
||||
doorkeeper (5.7.1)
|
||||
|
@ -229,29 +229,14 @@ GEM
|
|||
fabrication (2.31.0)
|
||||
faker (3.5.1)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
faraday (1.10.3)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0)
|
||||
faraday-multipart (~> 1.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.0)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (1.0.2)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday (2.12.0)
|
||||
faraday-net_http (>= 2.0, < 3.4)
|
||||
json
|
||||
logger
|
||||
faraday-httpclient (2.0.1)
|
||||
httpclient (>= 2.2)
|
||||
faraday-net_http (3.3.0)
|
||||
net-http
|
||||
fast_blank (1.0.1)
|
||||
fastimage (2.3.1)
|
||||
ffi (1.17.0)
|
||||
|
@ -362,7 +347,7 @@ GEM
|
|||
rack (>= 2.2, < 4)
|
||||
rdf (~> 3.3)
|
||||
rexml (~> 3.2)
|
||||
json-ld-preloaded (3.3.0)
|
||||
json-ld-preloaded (3.3.1)
|
||||
json-ld (~> 3.3)
|
||||
rdf (~> 3.3)
|
||||
json-schema (5.0.1)
|
||||
|
@ -430,7 +415,6 @@ GEM
|
|||
minitest (5.25.1)
|
||||
msgpack (1.7.3)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.4.1)
|
||||
mutex_m (0.2.0)
|
||||
net-http (0.4.1)
|
||||
uri
|
||||
|
@ -448,7 +432,7 @@ GEM
|
|||
nokogiri (1.16.7)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
oj (3.16.6)
|
||||
oj (3.16.7)
|
||||
bigdecimal (>= 3.0)
|
||||
ostruct (>= 0.2)
|
||||
omniauth (2.1.2)
|
||||
|
@ -760,7 +744,6 @@ GEM
|
|||
ruby-vips (2.2.2)
|
||||
ffi (~> 1.12)
|
||||
logger
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
rufus-scheduler (3.9.1)
|
||||
fugit (~> 1.1, >= 1.1.6)
|
||||
|
@ -908,7 +891,7 @@ DEPENDENCIES
|
|||
blurhash (~> 0.1)
|
||||
bootsnap (~> 1.18.0)
|
||||
brakeman (~> 6.0)
|
||||
browser (< 6)
|
||||
browser
|
||||
bundler-audit (~> 0.9)
|
||||
capybara (~> 3.39)
|
||||
charlock_holmes (~> 0.7.7)
|
||||
|
@ -930,6 +913,7 @@ DEPENDENCIES
|
|||
email_spec
|
||||
fabrication (~> 2.30)
|
||||
faker (~> 3.2)
|
||||
faraday-httpclient
|
||||
fast_blank (~> 1.0)
|
||||
fastimage
|
||||
flatware-rspec
|
||||
|
|
|
@ -17,6 +17,17 @@ class Api::V1::AnnualReportsController < Api::BaseController
|
|||
relationships: @relationships
|
||||
end
|
||||
|
||||
def show
|
||||
with_read_replica do
|
||||
@presenter = AnnualReportsPresenter.new([@annual_report])
|
||||
@relationships = StatusRelationshipsPresenter.new(@presenter.statuses, current_account.id)
|
||||
end
|
||||
|
||||
render json: @presenter,
|
||||
serializer: REST::AnnualReportsSerializer,
|
||||
relationships: @relationships
|
||||
end
|
||||
|
||||
def read
|
||||
@annual_report.view!
|
||||
render_empty
|
||||
|
|
BIN
app/javascript/images/archetypes/booster.png
Executable file
After Width: | Height: | Size: 620 KiB |
BIN
app/javascript/images/archetypes/lurker.png
Executable file
After Width: | Height: | Size: 1 MiB |
BIN
app/javascript/images/archetypes/oracle.png
Executable file
After Width: | Height: | Size: 1.2 MiB |
BIN
app/javascript/images/archetypes/pollster.png
Executable file
After Width: | Height: | Size: 710 KiB |
BIN
app/javascript/images/archetypes/replier.png
Executable file
After Width: | Height: | Size: 786 KiB |
|
@ -24,6 +24,7 @@ export const allNotificationTypes = [
|
|||
'admin.report',
|
||||
'moderation_warning',
|
||||
'severed_relationships',
|
||||
'annual_report',
|
||||
];
|
||||
|
||||
export type NotificationWithStatusType =
|
||||
|
@ -44,7 +45,8 @@ export type NotificationType =
|
|||
| 'moderation_warning'
|
||||
| 'severed_relationships'
|
||||
| 'admin.sign_up'
|
||||
| 'admin.report';
|
||||
| 'admin.report'
|
||||
| 'annual_report';
|
||||
|
||||
export interface NotifyEmojiReactionJSON {
|
||||
name: string;
|
||||
|
@ -158,6 +160,15 @@ interface AccountRelationshipSeveranceNotificationJSON
|
|||
event: ApiAccountRelationshipSeveranceEventJSON;
|
||||
}
|
||||
|
||||
export interface ApiAnnualReportEventJSON {
|
||||
year: string;
|
||||
}
|
||||
|
||||
interface AnnualReportNotificationGroupJSON extends BaseNotificationGroupJSON {
|
||||
type: 'annual_report';
|
||||
annual_report: ApiAnnualReportEventJSON;
|
||||
}
|
||||
|
||||
export type ApiNotificationJSON =
|
||||
| SimpleNotificationJSON
|
||||
| ReportNotificationJSON
|
||||
|
@ -170,7 +181,8 @@ export type ApiNotificationGroupJSON =
|
|||
| ReportNotificationGroupJSON
|
||||
| AccountRelationshipSeveranceNotificationGroupJSON
|
||||
| NotificationGroupWithStatusJSON
|
||||
| ModerationWarningNotificationGroupJSON;
|
||||
| ModerationWarningNotificationGroupJSON
|
||||
| AnnualReportNotificationGroupJSON;
|
||||
|
||||
export interface ApiNotificationGroupsResultJSON {
|
||||
accounts: ApiAccountJSON[];
|
||||
|
|
|
@ -113,12 +113,12 @@ class Item extends PureComponent {
|
|||
width = 25;
|
||||
}
|
||||
|
||||
if (attachment.get('description')?.length > 0) {
|
||||
badges.push(<AltTextBadge key='alt' description={attachment.get('description')} />);
|
||||
}
|
||||
|
||||
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
|
||||
|
||||
if (description?.length > 0) {
|
||||
badges.push(<AltTextBadge key='alt' description={description} />);
|
||||
}
|
||||
|
||||
if (attachment.get('type') === 'unknown') {
|
||||
return (
|
||||
<div className={classNames('media-gallery__item', { standalone, 'media-gallery__item--tall': height === 100, 'media-gallery__item--wide': width === 100 })} key={attachment.get('id')}>
|
||||
|
|
|
@ -13,11 +13,14 @@ class ModalRoot extends PureComponent {
|
|||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
backgroundColor: PropTypes.shape({
|
||||
r: PropTypes.number,
|
||||
g: PropTypes.number,
|
||||
b: PropTypes.number,
|
||||
}),
|
||||
backgroundColor: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.shape({
|
||||
r: PropTypes.number,
|
||||
g: PropTypes.number,
|
||||
b: PropTypes.number,
|
||||
}),
|
||||
]),
|
||||
ignoreFocus: PropTypes.bool,
|
||||
...WithOptionalRouterPropTypes,
|
||||
};
|
||||
|
@ -141,14 +144,17 @@ class ModalRoot extends PureComponent {
|
|||
|
||||
let backgroundColor = null;
|
||||
|
||||
if (this.props.backgroundColor) {
|
||||
backgroundColor = multiply({ ...this.props.backgroundColor, a: 1 }, { r: 0, g: 0, b: 0, a: 0.7 });
|
||||
if (this.props.backgroundColor && typeof this.props.backgroundColor === 'string') {
|
||||
backgroundColor = this.props.backgroundColor;
|
||||
} else if (this.props.backgroundColor) {
|
||||
const darkenedColor = multiply({ ...this.props.backgroundColor, a: 1 }, { r: 0, g: 0, b: 0, a: 0.7 });
|
||||
backgroundColor = `rgb(${darkenedColor.r}, ${darkenedColor.g}, ${darkenedColor.b})`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='modal-root' ref={this.setRef}>
|
||||
<div style={{ pointerEvents: visible ? 'auto' : 'none' }}>
|
||||
<div role='presentation' className='modal-root__overlay' onClick={onClose} style={{ backgroundColor: backgroundColor ? `rgba(${backgroundColor.r}, ${backgroundColor.g}, ${backgroundColor.b}, 0.9)` : null }} />
|
||||
<div role='presentation' className='modal-root__overlay' onClick={onClose} style={{ backgroundColor }} />
|
||||
<div role='dialog' className='modal-root__container'>{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -405,13 +405,16 @@ class Status extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
let media, statusAvatar, prepend, rebloggedByText;
|
||||
const matchedFilters = status.get('matched_filters');
|
||||
const expanded = (!matchedFilters || this.state.showDespiteFilter) && (!status.get('hidden') || status.get('spoiler_text').length === 0);
|
||||
|
||||
if (hidden) {
|
||||
return (
|
||||
<HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}>
|
||||
<div ref={this.handleRef} className={classNames('status__wrapper', { focusable: !this.props.muted })} tabIndex={unfocusable ? null : 0}>
|
||||
<span>{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}</span>
|
||||
<span>{status.get('content')}</span>
|
||||
{status.get('spoiler_text').length > 0 && (<span>{status.get('spoiler_text')}</span>)}
|
||||
{expanded && <span>{status.get('content')}</span>}
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
|
@ -420,7 +423,6 @@ class Status extends ImmutablePureComponent {
|
|||
const connectUp = previousId && previousId === status.get('in_reply_to_id');
|
||||
const connectToRoot = rootId && rootId === status.get('in_reply_to_id');
|
||||
const connectReply = nextInReplyToId && nextInReplyToId === status.get('id');
|
||||
const matchedFilters = status.get('matched_filters');
|
||||
|
||||
let visibilityName = status.get('limited_scope') || status.get('visibility_ex') || status.get('visibility');
|
||||
|
||||
|
@ -578,7 +580,6 @@ class Status extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
|
||||
const expanded = (!matchedFilters || this.state.showDespiteFilter) && (!status.get('hidden') || status.get('spoiler_text').length === 0);
|
||||
|
||||
const withLimited = status.get('visibility_ex') === 'limited' && status.get('limited_scope') ? <span className='status__visibility-icon'><Icon id='get-pocket' icon={LimitedIcon} title={intl.formatMessage(messages.limited_short)} /></span> : null;
|
||||
const withQuote = status.get('quote_id') ? <span className='status__visibility-icon'><Icon id='quote-right' icon={QuoteIcon} title='Quote' /></span> : null;
|
||||
|
|
69
app/javascript/mastodon/features/annual_report/archetype.tsx
Normal file
|
@ -0,0 +1,69 @@
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import booster from '@/images/archetypes/booster.png';
|
||||
import lurker from '@/images/archetypes/lurker.png';
|
||||
import oracle from '@/images/archetypes/oracle.png';
|
||||
import pollster from '@/images/archetypes/pollster.png';
|
||||
import replier from '@/images/archetypes/replier.png';
|
||||
import type { Archetype as ArchetypeData } from 'mastodon/models/annual_report';
|
||||
|
||||
export const Archetype: React.FC<{
|
||||
data: ArchetypeData;
|
||||
}> = ({ data }) => {
|
||||
let illustration, label;
|
||||
|
||||
switch (data) {
|
||||
case 'booster':
|
||||
illustration = booster;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.archetype.booster'
|
||||
defaultMessage='The cool-hunter'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'replier':
|
||||
illustration = replier;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.archetype.replier'
|
||||
defaultMessage='The social butterfly'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'pollster':
|
||||
illustration = pollster;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.archetype.pollster'
|
||||
defaultMessage='The pollster'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'lurker':
|
||||
illustration = lurker;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.archetype.lurker'
|
||||
defaultMessage='The lurker'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'oracle':
|
||||
illustration = oracle;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.archetype.oracle'
|
||||
defaultMessage='The oracle'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__archetype'>
|
||||
<div className='annual-report__summary__archetype__label'>{label}</div>
|
||||
<img src={illustration} alt='' />
|
||||
</div>
|
||||
);
|
||||
};
|
69
app/javascript/mastodon/features/annual_report/followers.tsx
Normal file
|
@ -0,0 +1,69 @@
|
|||
import { FormattedMessage, FormattedNumber } from 'react-intl';
|
||||
|
||||
import { Sparklines, SparklinesCurve } from 'react-sparklines';
|
||||
|
||||
import { ShortNumber } from 'mastodon/components/short_number';
|
||||
import type { TimeSeriesMonth } from 'mastodon/models/annual_report';
|
||||
|
||||
export const Followers: React.FC<{
|
||||
data: TimeSeriesMonth[];
|
||||
total?: number;
|
||||
}> = ({ data, total }) => {
|
||||
const change = data.reduce((sum, item) => sum + item.followers, 0);
|
||||
|
||||
const cumulativeGraph = data.reduce(
|
||||
(newData, item) => [
|
||||
...newData,
|
||||
item.followers + (newData[newData.length - 1] ?? 0),
|
||||
],
|
||||
[0],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__followers'>
|
||||
<Sparklines data={cumulativeGraph} margin={0}>
|
||||
<svg>
|
||||
<defs>
|
||||
<linearGradient id='gradient' x1='0%' y1='0%' x2='0%' y2='100%'>
|
||||
<stop
|
||||
offset='0%'
|
||||
stopColor='var(--sparkline-gradient-top)'
|
||||
stopOpacity='1'
|
||||
/>
|
||||
<stop
|
||||
offset='100%'
|
||||
stopColor='var(--sparkline-gradient-bottom)'
|
||||
stopOpacity='0'
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<SparklinesCurve style={{ fill: 'none' }} />
|
||||
</Sparklines>
|
||||
|
||||
<div className='annual-report__summary__followers__foreground'>
|
||||
<div className='annual-report__summary__followers__number'>
|
||||
{change > -1 ? '+' : '-'}
|
||||
<FormattedNumber value={change} />
|
||||
</div>
|
||||
|
||||
<div className='annual-report__summary__followers__label'>
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.followers.followers'
|
||||
defaultMessage='followers'
|
||||
/>
|
||||
</span>
|
||||
<div className='annual-report__summary__followers__footnote'>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.followers.total'
|
||||
defaultMessage='{count} total'
|
||||
values={{ count: <ShortNumber value={total ?? 0} /> }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,105 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-return,
|
||||
@typescript-eslint/no-explicit-any,
|
||||
@typescript-eslint/no-unsafe-assignment */
|
||||
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { toggleStatusSpoilers } from 'mastodon/actions/statuses';
|
||||
import { DetailedStatus } from 'mastodon/features/status/components/detailed_status';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import type { TopStatuses } from 'mastodon/models/annual_report';
|
||||
import { makeGetStatus, makeGetPictureInPicture } from 'mastodon/selectors';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
const getStatus = makeGetStatus() as unknown as (arg0: any, arg1: any) => any;
|
||||
const getPictureInPicture = makeGetPictureInPicture() as unknown as (
|
||||
arg0: any,
|
||||
arg1: any,
|
||||
) => any;
|
||||
|
||||
export const HighlightedPost: React.FC<{
|
||||
data: TopStatuses;
|
||||
}> = ({ data }) => {
|
||||
let statusId, label;
|
||||
|
||||
if (data.by_reblogs) {
|
||||
statusId = data.by_reblogs;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.highlighted_post.by_reblogs'
|
||||
defaultMessage='most boosted post'
|
||||
/>
|
||||
);
|
||||
} else if (data.by_favourites) {
|
||||
statusId = data.by_favourites;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.highlighted_post.by_favourites'
|
||||
defaultMessage='most favourited post'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
statusId = data.by_replies;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.highlighted_post.by_replies'
|
||||
defaultMessage='post with the most replies'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const domain = useAppSelector((state) => state.meta.get('domain'));
|
||||
const status = useAppSelector((state) =>
|
||||
statusId ? getStatus(state, { id: statusId }) : undefined,
|
||||
);
|
||||
const pictureInPicture = useAppSelector((state) =>
|
||||
statusId ? getPictureInPicture(state, { id: statusId }) : undefined,
|
||||
);
|
||||
const account = useAppSelector((state) =>
|
||||
me ? state.accounts.get(me) : undefined,
|
||||
);
|
||||
|
||||
const handleToggleHidden = useCallback(() => {
|
||||
dispatch(toggleStatusSpoilers(statusId));
|
||||
}, [dispatch, statusId]);
|
||||
|
||||
if (!status) {
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__most-boosted-post' />
|
||||
);
|
||||
}
|
||||
|
||||
const displayName = (
|
||||
<span className='display-name'>
|
||||
<strong className='display-name__html'>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.highlighted_post.possessive'
|
||||
defaultMessage="{name}'s"
|
||||
values={{
|
||||
name: account && (
|
||||
<bdi
|
||||
dangerouslySetInnerHTML={{ __html: account.display_name_html }}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</strong>
|
||||
<span className='display-name__account'>{label}</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__most-boosted-post'>
|
||||
<DetailedStatus
|
||||
status={status}
|
||||
pictureInPicture={pictureInPicture}
|
||||
domain={domain}
|
||||
onToggleHidden={handleToggleHidden}
|
||||
overrideDisplayName={displayName}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
99
app/javascript/mastodon/features/annual_report/index.tsx
Normal file
|
@ -0,0 +1,99 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import {
|
||||
importFetchedStatuses,
|
||||
importFetchedAccounts,
|
||||
} from 'mastodon/actions/importer';
|
||||
import { apiRequestGet, apiRequestPost } from 'mastodon/api';
|
||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import type { Account } from 'mastodon/models/account';
|
||||
import type { AnnualReport as AnnualReportData } from 'mastodon/models/annual_report';
|
||||
import type { Status } from 'mastodon/models/status';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { Archetype } from './archetype';
|
||||
import { Followers } from './followers';
|
||||
import { HighlightedPost } from './highlighted_post';
|
||||
import { MostUsedHashtag } from './most_used_hashtag';
|
||||
import { NewPosts } from './new_posts';
|
||||
import { Percentile } from './percentile';
|
||||
|
||||
interface AnnualReportResponse {
|
||||
annual_reports: AnnualReportData[];
|
||||
accounts: Account[];
|
||||
statuses: Status[];
|
||||
}
|
||||
|
||||
export const AnnualReport: React.FC<{
|
||||
year: string;
|
||||
}> = ({ year }) => {
|
||||
const [response, setResponse] = useState<AnnualReportResponse | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const currentAccount = useAppSelector((state) =>
|
||||
me ? state.accounts.get(me) : undefined,
|
||||
);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
|
||||
apiRequestGet<AnnualReportResponse>(`v1/annual_reports/${year}`)
|
||||
.then((data) => {
|
||||
dispatch(importFetchedStatuses(data.statuses));
|
||||
dispatch(importFetchedAccounts(data.accounts));
|
||||
|
||||
setResponse(data);
|
||||
setLoading(false);
|
||||
|
||||
return apiRequestPost(`v1/annual_reports/${year}/read`);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, [dispatch, year, setResponse, setLoading]);
|
||||
|
||||
if (loading) {
|
||||
return <LoadingIndicator />;
|
||||
}
|
||||
|
||||
const report = response?.annual_reports[0];
|
||||
|
||||
if (!report) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='annual-report'>
|
||||
<div className='annual-report__header'>
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.thanks'
|
||||
defaultMessage='Thanks for being part of Mastodon!'
|
||||
/>
|
||||
</h1>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.here_it_is'
|
||||
defaultMessage='Here is your {year} in review:'
|
||||
values={{ year: report.year }}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='annual-report__bento annual-report__summary'>
|
||||
<Archetype data={report.data.archetype} />
|
||||
<HighlightedPost data={report.data.top_statuses} />
|
||||
<Followers
|
||||
data={report.data.time_series}
|
||||
total={currentAccount?.followers_count}
|
||||
/>
|
||||
<MostUsedHashtag data={report.data.top_hashtags} />
|
||||
<Percentile data={report.data.percentiles} />
|
||||
<NewPosts data={report.data.time_series} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import type { NameAndCount } from 'mastodon/models/annual_report';
|
||||
|
||||
export const MostUsedApp: React.FC<{
|
||||
data: NameAndCount[];
|
||||
}> = ({ data }) => {
|
||||
const app = data[0];
|
||||
|
||||
if (!app) {
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__most-used-app' />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__most-used-app'>
|
||||
<div className='annual-report__summary__most-used-app__icon'>
|
||||
{app.name}
|
||||
</div>
|
||||
<div className='annual-report__summary__most-used-app__label'>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.most_used_app.most_used_app'
|
||||
defaultMessage='most used app'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import type { NameAndCount } from 'mastodon/models/annual_report';
|
||||
|
||||
export const MostUsedHashtag: React.FC<{
|
||||
data: NameAndCount[];
|
||||
}> = ({ data }) => {
|
||||
const hashtag = data[0];
|
||||
|
||||
if (!hashtag) {
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__most-used-hashtag' />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__most-used-hashtag'>
|
||||
<div className='annual-report__summary__most-used-hashtag__hashtag'>
|
||||
#{hashtag.name}
|
||||
</div>
|
||||
<div className='annual-report__summary__most-used-hashtag__label'>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.most_used_hashtag.most_used_hashtag'
|
||||
defaultMessage='most used hashtag'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
53
app/javascript/mastodon/features/annual_report/new_posts.tsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { FormattedNumber, FormattedMessage } from 'react-intl';
|
||||
|
||||
import ChatBubbleIcon from '@/material-icons/400-24px/chat_bubble.svg?react';
|
||||
import type { TimeSeriesMonth } from 'mastodon/models/annual_report';
|
||||
|
||||
export const NewPosts: React.FC<{
|
||||
data: TimeSeriesMonth[];
|
||||
}> = ({ data }) => {
|
||||
const posts = data.reduce((sum, item) => sum + item.statuses, 0);
|
||||
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__new-posts'>
|
||||
<svg width={500} height={500}>
|
||||
<defs>
|
||||
<pattern
|
||||
id='posts'
|
||||
x='0'
|
||||
y='0'
|
||||
width='32'
|
||||
height='35'
|
||||
patternUnits='userSpaceOnUse'
|
||||
>
|
||||
<circle cx='12' cy='12' r='12' fill='var(--lime)' />
|
||||
<ChatBubbleIcon
|
||||
fill='var(--indigo-1)'
|
||||
x='4'
|
||||
y='4'
|
||||
width='16'
|
||||
height='16'
|
||||
/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect
|
||||
width={500}
|
||||
height={500}
|
||||
fill='url(#posts)'
|
||||
style={{ opacity: 0.2 }}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<div className='annual-report__summary__new-posts__number'>
|
||||
<FormattedNumber value={posts} />
|
||||
</div>
|
||||
<div className='annual-report__summary__new-posts__label'>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.new_posts.new_posts'
|
||||
defaultMessage='new posts'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,53 @@
|
|||
/* eslint-disable react/jsx-no-useless-fragment */
|
||||
import { FormattedMessage, FormattedNumber } from 'react-intl';
|
||||
|
||||
import type { Percentiles } from 'mastodon/models/annual_report';
|
||||
|
||||
export const Percentile: React.FC<{
|
||||
data: Percentiles;
|
||||
}> = ({ data }) => {
|
||||
const percentile = data.statuses;
|
||||
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__percentile'>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.percentile.text'
|
||||
defaultMessage='<topLabel>That puts you in the top</topLabel><percentage></percentage><bottomLabel>of Mastodon users.</bottomLabel>'
|
||||
values={{
|
||||
topLabel: (str) => (
|
||||
<div className='annual-report__summary__percentile__label'>
|
||||
{str}
|
||||
</div>
|
||||
),
|
||||
percentage: () => (
|
||||
<div className='annual-report__summary__percentile__number'>
|
||||
<FormattedNumber
|
||||
value={percentile / 100}
|
||||
style='percent'
|
||||
maximumFractionDigits={1}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
bottomLabel: (str) => (
|
||||
<div>
|
||||
<div className='annual-report__summary__percentile__label'>
|
||||
{str}
|
||||
</div>
|
||||
|
||||
{percentile < 6 && (
|
||||
<div className='annual-report__summary__percentile__footnote'>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.percentile.we_wont_tell_bernie'
|
||||
defaultMessage="We won't tell Bernie."
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{(message) => <>{message}</>}
|
||||
</FormattedMessage>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,59 @@
|
|||
import { useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import CelebrationIcon from '@/material-icons/400-24px/celebration.svg?react';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import type { NotificationGroupAnnualReport } from 'mastodon/models/notification_group';
|
||||
import { useAppDispatch } from 'mastodon/store';
|
||||
|
||||
export const NotificationAnnualReport: React.FC<{
|
||||
notification: NotificationGroupAnnualReport;
|
||||
unread: boolean;
|
||||
}> = ({ notification: { annualReport }, unread }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const year = annualReport.year;
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
dispatch(
|
||||
openModal({
|
||||
modalType: 'ANNUAL_REPORT',
|
||||
modalProps: { year },
|
||||
}),
|
||||
);
|
||||
}, [dispatch, year]);
|
||||
|
||||
return (
|
||||
<div
|
||||
role='button'
|
||||
className={classNames(
|
||||
'notification-group notification-group--link notification-group--annual-report focusable',
|
||||
{ 'notification-group--unread': unread },
|
||||
)}
|
||||
tabIndex={0}
|
||||
>
|
||||
<div className='notification-group__icon'>
|
||||
<Icon id='celebration' icon={CelebrationIcon} />
|
||||
</div>
|
||||
|
||||
<div className='notification-group__main'>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='notification.annual_report.message'
|
||||
defaultMessage="Your {year} #Wrapstodon awaits! Unveil your year's highlights and memorable moments on Mastodon!"
|
||||
values={{ year }}
|
||||
/>
|
||||
</p>
|
||||
<button onClick={handleClick} className='link-button'>
|
||||
<FormattedMessage
|
||||
id='notification.annual_report.view'
|
||||
defaultMessage='View #Wrapstodon'
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -9,6 +9,7 @@ import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
|||
|
||||
import { NotificationAdminReport } from './notification_admin_report';
|
||||
import { NotificationAdminSignUp } from './notification_admin_sign_up';
|
||||
import { NotificationAnnualReport } from './notification_annual_report';
|
||||
import { NotificationEmojiReaction } from './notification_emoji_reaction';
|
||||
import { NotificationFavourite } from './notification_favourite';
|
||||
import { NotificationFollow } from './notification_follow';
|
||||
|
@ -170,6 +171,14 @@ export const NotificationGroup: React.FC<{
|
|||
/>
|
||||
);
|
||||
break;
|
||||
case 'annual_report':
|
||||
content = (
|
||||
<NotificationAnnualReport
|
||||
unread={unread}
|
||||
notification={notificationGroup}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ export const DetailedStatus: React.FC<{
|
|||
domain: string;
|
||||
showMedia?: boolean;
|
||||
withLogo?: boolean;
|
||||
overrideDisplayName?: React.ReactNode;
|
||||
pictureInPicture: any;
|
||||
onToggleHidden?: (status: any) => void;
|
||||
onToggleMediaVisibility?: () => void;
|
||||
|
@ -69,6 +70,7 @@ export const DetailedStatus: React.FC<{
|
|||
domain,
|
||||
showMedia,
|
||||
withLogo,
|
||||
overrideDisplayName,
|
||||
pictureInPicture,
|
||||
onToggleMediaVisibility,
|
||||
onToggleHidden,
|
||||
|
@ -403,7 +405,11 @@ export const DetailedStatus: React.FC<{
|
|||
<div className='detailed-status__display-avatar'>
|
||||
<Avatar account={status.get('account')} size={46} />
|
||||
</div>
|
||||
<DisplayName account={status.get('account')} localDomain={domain} />
|
||||
|
||||
{overrideDisplayName ?? (
|
||||
<DisplayName account={status.get('account')} localDomain={domain} />
|
||||
)}
|
||||
|
||||
{withLogo && (
|
||||
<>
|
||||
<div className='spacer' />
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import { useEffect } from 'react';
|
||||
|
||||
import { AnnualReport } from 'mastodon/features/annual_report';
|
||||
|
||||
const AnnualReportModal: React.FC<{
|
||||
year: string;
|
||||
onChangeBackgroundColor: (arg0: string) => void;
|
||||
}> = ({ year, onChangeBackgroundColor }) => {
|
||||
useEffect(() => {
|
||||
onChangeBackgroundColor('var(--indigo-1)');
|
||||
}, [onChangeBackgroundColor]);
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal annual-report-modal'>
|
||||
<AnnualReport year={year} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default AnnualReportModal;
|
|
@ -23,6 +23,7 @@ import {
|
|||
SubscribedLanguagesModal,
|
||||
ClosedRegistrationsModal,
|
||||
IgnoreNotificationsModal,
|
||||
AnnualReportModal,
|
||||
} from 'mastodon/features/ui/util/async-components';
|
||||
import { getScrollbarWidth } from 'mastodon/utils/scrollbar';
|
||||
|
||||
|
@ -82,6 +83,7 @@ export const MODAL_COMPONENTS = {
|
|||
'INTERACTION': InteractionModal,
|
||||
'CLOSED_REGISTRATIONS': ClosedRegistrationsModal,
|
||||
'IGNORE_NOTIFICATIONS': IgnoreNotificationsModal,
|
||||
'ANNUAL_REPORT': AnnualReportModal,
|
||||
};
|
||||
|
||||
export default class ModalRoot extends PureComponent {
|
||||
|
|
|
@ -285,3 +285,7 @@ export function NotificationRequest () {
|
|||
export function LinkTimeline () {
|
||||
return import(/*webpackChunkName: "features/link_timeline" */'../../link_timeline');
|
||||
}
|
||||
|
||||
export function AnnualReportModal () {
|
||||
return import(/*webpackChunkName: "modals/annual_report_modal" */'../components/annual_report_modal');
|
||||
}
|
||||
|
|
|
@ -82,6 +82,8 @@
|
|||
"alert.unexpected.message": "Ur fazi dic'hortozet zo degouezhet.",
|
||||
"alert.unexpected.title": "Hopala !",
|
||||
"announcement.announcement": "Kemennad",
|
||||
"annual_report.summary.followers.followers": "heulier",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name}",
|
||||
"attachments_list.unprocessed": "(ket meret)",
|
||||
"audio.hide": "Kuzhat ar c'hleved",
|
||||
"block_modal.show_less": "Diskouez nebeutoc'h",
|
||||
|
|
|
@ -87,6 +87,18 @@
|
|||
"alert.unexpected.title": "Vaja!",
|
||||
"alt_text_badge.title": "Text alternatiu",
|
||||
"announcement.announcement": "Anunci",
|
||||
"annual_report.summary.archetype.oracle": "L'Oracle",
|
||||
"annual_report.summary.followers.followers": "seguidors",
|
||||
"annual_report.summary.followers.total": "{count} en total",
|
||||
"annual_report.summary.here_it_is": "El repàs del vostre {year}:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "la publicació més afavorida",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "la publicació més impulsada",
|
||||
"annual_report.summary.highlighted_post.by_replies": "la publicació amb més respostes",
|
||||
"annual_report.summary.highlighted_post.possessive": "de {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "l'aplicació més utilitzada",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "l'etiqueta més utilitzada",
|
||||
"annual_report.summary.new_posts.new_posts": "publicacions noves",
|
||||
"annual_report.summary.thanks": "Gràcies per formar part de Mastodon!",
|
||||
"attachments_list.unprocessed": "(sense processar)",
|
||||
"audio.hide": "Amaga l'àudio",
|
||||
"block_modal.remote_users_caveat": "Li demanarem al servidor {domain} que respecti la vostra decisió, tot i que no podem garantir-ho, ja que alguns servidors gestionen de forma diferent els blocatges. És possible que els usuaris no autenticats puguin veure les publicacions públiques.",
|
||||
|
|
|
@ -87,6 +87,24 @@
|
|||
"alert.unexpected.title": "Oha!",
|
||||
"alt_text_badge.title": "Bildbeschreibung",
|
||||
"announcement.announcement": "Ankündigung",
|
||||
"annual_report.summary.archetype.booster": "Trendjäger*in",
|
||||
"annual_report.summary.archetype.lurker": "Beobachter*in",
|
||||
"annual_report.summary.archetype.oracle": "Orakel",
|
||||
"annual_report.summary.archetype.pollster": "Meinungsforscher*in",
|
||||
"annual_report.summary.archetype.replier": "Geselliger Schmetterling",
|
||||
"annual_report.summary.followers.followers": "Follower",
|
||||
"annual_report.summary.followers.total": "{count} insgesamt",
|
||||
"annual_report.summary.here_it_is": "Dein Jahresrückblick für {year}:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "am häufigsten favorisierter Beitrag",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "am häufigsten geteilter Beitrag",
|
||||
"annual_report.summary.highlighted_post.by_replies": "Beitrag mit den meisten Antworten",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "am häufigsten verwendete App",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "am häufigsten verwendeter Hashtag",
|
||||
"annual_report.summary.new_posts.new_posts": "neue Beiträge",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Damit gehörst du zu den obersten</topLabel><percentage></percentage><bottomLabel>der Mastodon-Nutzer*innen.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Wir werden Bernie nichts verraten.",
|
||||
"annual_report.summary.thanks": "Danke, dass du Teil von Mastodon bist!",
|
||||
"attachments_list.unprocessed": "(ausstehend)",
|
||||
"audio.hide": "Audio ausblenden",
|
||||
"block_modal.remote_users_caveat": "Wir werden den Server {domain} bitten, deine Entscheidung zu respektieren. Allerdings kann nicht garantiert werden, dass sie eingehalten wird, weil einige Server Blockierungen unterschiedlich handhaben können. Öffentliche Beiträge können für nicht angemeldete Nutzer*innen weiterhin sichtbar sein.",
|
||||
|
@ -508,6 +526,8 @@
|
|||
"notification.admin.report_statuses_other": "{name} meldete {target}",
|
||||
"notification.admin.sign_up": "{name} registrierte sich",
|
||||
"notification.admin.sign_up.name_and_others": "{name} und {count, plural, one {# weitere Person} other {# weitere Personen}} registrierten sich",
|
||||
"notification.annual_report.message": "Dein {year} #Wrapstodon erwartet dich! Lass deine Highlights und unvergesslichen Momente auf Mastodon erneut aufleben!",
|
||||
"notification.annual_report.view": "#Wrapstodon ansehen",
|
||||
"notification.favourite": "{name} favorisierte deinen Beitrag",
|
||||
"notification.favourite.name_and_others_with_link": "{name} und <a>{count, plural, one {# weitere Person} other {# weitere Personen}}</a> favorisierten deinen Beitrag",
|
||||
"notification.follow": "{name} folgt dir",
|
||||
|
|
|
@ -100,6 +100,24 @@
|
|||
"alert.unexpected.title": "Oops!",
|
||||
"alt_text_badge.title": "Alt text",
|
||||
"announcement.announcement": "Announcement",
|
||||
"annual_report.summary.archetype.booster": "The cool-hunter",
|
||||
"annual_report.summary.archetype.lurker": "The lurker",
|
||||
"annual_report.summary.archetype.oracle": "The oracle",
|
||||
"annual_report.summary.archetype.pollster": "The pollster",
|
||||
"annual_report.summary.archetype.replier": "The social butterfly",
|
||||
"annual_report.summary.followers.followers": "followers",
|
||||
"annual_report.summary.followers.total": "{count} total",
|
||||
"annual_report.summary.here_it_is": "Here is your {year} in review:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "most favourited post",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "most boosted post",
|
||||
"annual_report.summary.highlighted_post.by_replies": "post with the most replies",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name}'s",
|
||||
"annual_report.summary.most_used_app.most_used_app": "most used app",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "most used hashtag",
|
||||
"annual_report.summary.new_posts.new_posts": "new posts",
|
||||
"annual_report.summary.percentile.text": "<topLabel>That puts you in the top</topLabel><percentage></percentage><bottomLabel>of Mastodon users.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "We won't tell Bernie.",
|
||||
"annual_report.summary.thanks": "Thanks for being part of Mastodon!",
|
||||
"antennas.account.add": "Add to antenna",
|
||||
"antennas.account.remove": "Remove from antenna",
|
||||
"antennas.accounts": "{count} accounts",
|
||||
|
@ -623,6 +641,8 @@
|
|||
"notification.admin.report_statuses_other": "{name} reported {target}",
|
||||
"notification.admin.sign_up": "{name} signed up",
|
||||
"notification.admin.sign_up.name_and_others": "{name} and {count, plural, one {# other} other {# others}} signed up",
|
||||
"notification.annual_report.message": "Your {year} #Wrapstodon awaits! Unveil your year's highlights and memorable moments on Mastodon!",
|
||||
"notification.annual_report.view": "View #Wrapstodon",
|
||||
"notification.emoji_reaction": "{name} reacted your post with emoji",
|
||||
"notification.emoji_reaction.name_and_others_with_link": "{name} and <a>{count, plural, one {# other} other {# others}}</a> reacted your post with emoji",
|
||||
"notification.favourite": "{name} favorited your post",
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
"account.languages": "Ŝanĝi la abonitajn lingvojn",
|
||||
"account.link_verified_on": "Propreco de tiu ligilo estis konfirmita je {date}",
|
||||
"account.locked_info": "Tiu konto estas privatigita. La posedanto mane akceptas tiun, kiu povas sekvi rin.",
|
||||
"account.media": "Plurmedioj",
|
||||
"account.media": "Plurmedio",
|
||||
"account.mention": "Mencii @{name}",
|
||||
"account.moved_to": "{name} indikis, ke ria nova konto estas nun:",
|
||||
"account.mute": "Silentigi @{name}",
|
||||
|
@ -87,6 +87,8 @@
|
|||
"alert.unexpected.title": "Aj!",
|
||||
"alt_text_badge.title": "Alt-teksto",
|
||||
"announcement.announcement": "Anonco",
|
||||
"annual_report.summary.new_posts.new_posts": "novaj afiŝoj",
|
||||
"annual_report.summary.thanks": "Dankon pro esti parto de Mastodon!",
|
||||
"attachments_list.unprocessed": "(neprilaborita)",
|
||||
"audio.hide": "Kaŝi aŭdion",
|
||||
"block_modal.remote_users_caveat": "Ni petos al la servilo {domain} respekti vian elekton. Tamen, plenumo ne estas garantiita ĉar iuj serviloj eble manipulas blokojn malsame. Publikaj afiŝoj eble ankoraŭ estas videbla por ne-ensalutintaj uzantoj.",
|
||||
|
@ -142,7 +144,7 @@
|
|||
"column_header.unpin": "Malfiksi",
|
||||
"column_subheading.settings": "Agordoj",
|
||||
"community.column_settings.local_only": "Nur loka",
|
||||
"community.column_settings.media_only": "Nur plurmedioj",
|
||||
"community.column_settings.media_only": "Nur plurmedio",
|
||||
"community.column_settings.remote_only": "Nur fora",
|
||||
"compose.language.change": "Ŝanĝi lingvon",
|
||||
"compose.language.search": "Serĉi lingvojn...",
|
||||
|
|
|
@ -87,6 +87,15 @@
|
|||
"alert.unexpected.title": "¡Epa!",
|
||||
"alt_text_badge.title": "Texto alternativo",
|
||||
"announcement.announcement": "Anuncio",
|
||||
"annual_report.summary.followers.followers": "seguidores",
|
||||
"annual_report.summary.here_it_is": "Aquí está tu resumen de {year}:",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "publicación más impulsada",
|
||||
"annual_report.summary.highlighted_post.by_replies": "publicación con más respuestas",
|
||||
"annual_report.summary.highlighted_post.possessive": "de {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "aplicación más usada",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "etiqueta más usada",
|
||||
"annual_report.summary.new_posts.new_posts": "nuevas publicaciones",
|
||||
"annual_report.summary.thanks": "¡Gracias por ser parte de Mastodon!",
|
||||
"attachments_list.unprocessed": "[sin procesar]",
|
||||
"audio.hide": "Ocultar audio",
|
||||
"block_modal.remote_users_caveat": "Le pediremos al servidor {domain} que respete tu decisión. Sin embargo, el cumplimiento no está garantizado, ya que algunos servidores pueden manejar los bloqueos de forma diferente. Los mensajes públicos todavía podrían estar visibles para los usuarios no conectados.",
|
||||
|
|
|
@ -87,6 +87,15 @@
|
|||
"alert.unexpected.title": "¡Ups!",
|
||||
"alt_text_badge.title": "Texto alternativo",
|
||||
"announcement.announcement": "Anuncio",
|
||||
"annual_report.summary.followers.followers": "seguidores",
|
||||
"annual_report.summary.here_it_is": "Aquí está tu resumen de {year}:",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "publicación más impulsada",
|
||||
"annual_report.summary.highlighted_post.by_replies": "publicación con más respuestas",
|
||||
"annual_report.summary.highlighted_post.possessive": "de {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "aplicación más usada",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "etiqueta más usada",
|
||||
"annual_report.summary.new_posts.new_posts": "nuevas publicaciones",
|
||||
"annual_report.summary.thanks": "¡Gracias por ser parte de Mastodon!",
|
||||
"attachments_list.unprocessed": "(sin procesar)",
|
||||
"audio.hide": "Ocultar audio",
|
||||
"block_modal.remote_users_caveat": "Le pediremos al servidor {domain} que respete tu decisión. Sin embargo, el cumplimiento no está garantizado ya que algunos servidores pueden manejar bloques de forma diferente. Las publicaciones públicas pueden ser todavía visibles para los usuarios no conectados.",
|
||||
|
|
|
@ -87,6 +87,15 @@
|
|||
"alert.unexpected.title": "¡Ups!",
|
||||
"alt_text_badge.title": "Texto alternativo",
|
||||
"announcement.announcement": "Anuncio",
|
||||
"annual_report.summary.followers.followers": "seguidores",
|
||||
"annual_report.summary.here_it_is": "Aquí está tu resumen de {year}:",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "publicación más impulsada",
|
||||
"annual_report.summary.highlighted_post.by_replies": "publicación con más respuestas",
|
||||
"annual_report.summary.highlighted_post.possessive": "de {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "aplicación más usada",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "etiqueta más usada",
|
||||
"annual_report.summary.new_posts.new_posts": "nuevas publicaciones",
|
||||
"annual_report.summary.thanks": "¡Gracias por ser parte de Mastodon!",
|
||||
"attachments_list.unprocessed": "(sin procesar)",
|
||||
"audio.hide": "Ocultar audio",
|
||||
"block_modal.remote_users_caveat": "Le pediremos al servidor {domain} que respete tu decisión. Sin embargo, el cumplimiento no está garantizado, ya que algunos servidores pueden manejar bloqueos de forma distinta. Los mensajes públicos pueden ser todavía visibles para los usuarios que no hayan iniciado sesión.",
|
||||
|
|
|
@ -87,6 +87,11 @@
|
|||
"alert.unexpected.title": "Hups!",
|
||||
"alt_text_badge.title": "Vaihtoehtoinen teksti",
|
||||
"announcement.announcement": "Tiedote",
|
||||
"annual_report.summary.archetype.replier": "Sosiaalinen perhonen",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "tehostetuin julkaisu",
|
||||
"annual_report.summary.most_used_app.most_used_app": "käytetyin sovellus",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "käytetyin aihetunniste",
|
||||
"annual_report.summary.thanks": "Kiitos, että olet osa Mastodonia!",
|
||||
"attachments_list.unprocessed": "(käsittelemätön)",
|
||||
"audio.hide": "Piilota ääni",
|
||||
"block_modal.remote_users_caveat": "Pyydämme palvelinta {domain} kunnioittamaan päätöstäsi. Myötämielisyyttä ei kuitenkaan taata, koska jotkin palvelimet voivat käsitellä estoja eri tavalla. Julkiset julkaisut voivat silti näkyä kirjautumattomille käyttäjille.",
|
||||
|
@ -158,7 +163,7 @@
|
|||
"compose_form.poll.duration": "Äänestyksen kesto",
|
||||
"compose_form.poll.multiple": "Monivalinta",
|
||||
"compose_form.poll.option_placeholder": "Vaihtoehto {number}",
|
||||
"compose_form.poll.single": "Yksi vaihtoehto",
|
||||
"compose_form.poll.single": "Yksittäisvalinta",
|
||||
"compose_form.poll.switch_to_multiple": "Muuta äänestys monivalinnaksi",
|
||||
"compose_form.poll.switch_to_single": "Muuta äänestys yksittäisvalinnaksi",
|
||||
"compose_form.poll.type": "Tyyli",
|
||||
|
@ -386,7 +391,7 @@
|
|||
"interaction_modal.description.follow": "Mastodon-tilillä voit seurata käyttäjää {name} saadaksesi hänen julkaisunsa kotisyötteeseesi.",
|
||||
"interaction_modal.description.reblog": "Mastodon-tilillä voit tehostaa tätä julkaisua jakaaksesi sen seuraajiesi kanssa.",
|
||||
"interaction_modal.description.reply": "Mastodon-tilillä voit vastata tähän julkaisuun.",
|
||||
"interaction_modal.description.vote": "Osallistuminen äänestykseen onnistuu Mastodon-tilillä.",
|
||||
"interaction_modal.description.vote": "Mastodon-tilillä voit osallistua tähän äänestykseen.",
|
||||
"interaction_modal.login.action": "Siirry kotiin",
|
||||
"interaction_modal.login.prompt": "Kotipalvelimesi verkkotunnus, kuten mastodon.social",
|
||||
"interaction_modal.no_account_yet": "Etkö ole vielä Mastodonissa?",
|
||||
|
@ -508,6 +513,7 @@
|
|||
"notification.admin.report_statuses_other": "{name} raportoi käyttäjän {target}",
|
||||
"notification.admin.sign_up": "{name} rekisteröityi",
|
||||
"notification.admin.sign_up.name_and_others": "{name} ja {count, plural, one {# muu} other {# muuta}} rekisteröityivät",
|
||||
"notification.annual_report.view": "Näytä #Wrapstodon",
|
||||
"notification.favourite": "{name} lisäsi julkaisusi suosikkeihinsa",
|
||||
"notification.favourite.name_and_others_with_link": "{name} ja <a>{count, plural, one {# muu} other {# muuta}}</a> lisäsivät julkaisusi suosikkeihinsa",
|
||||
"notification.follow": "{name} seurasi sinua",
|
||||
|
|
|
@ -158,6 +158,7 @@
|
|||
"compose_form.poll.duration": "Doer fan de enkête",
|
||||
"compose_form.poll.multiple": "Mearkar",
|
||||
"compose_form.poll.option_placeholder": "Opsje {number}",
|
||||
"compose_form.poll.single": "Inkelde kar",
|
||||
"compose_form.poll.switch_to_multiple": "Enkête wizigje om meardere karren ta te stean",
|
||||
"compose_form.poll.switch_to_single": "Enkête wizigje om in inkelde kar ta te stean",
|
||||
"compose_form.poll.type": "Styl",
|
||||
|
@ -196,6 +197,7 @@
|
|||
"confirmations.unfollow.title": "Brûker net mear folgje?",
|
||||
"content_warning.hide": "Berjocht ferstopje",
|
||||
"content_warning.show": "Dochs toane",
|
||||
"content_warning.show_more": "Mear toane",
|
||||
"conversation.delete": "Petear fuortsmite",
|
||||
"conversation.mark_as_read": "As lêzen markearje",
|
||||
"conversation.open": "Petear toane",
|
||||
|
@ -304,6 +306,7 @@
|
|||
"filter_modal.select_filter.subtitle": "In besteande kategory brûke of in nije oanmeitsje",
|
||||
"filter_modal.select_filter.title": "Dit berjocht filterje",
|
||||
"filter_modal.title.status": "In berjocht filterje",
|
||||
"filter_warning.matches_filter": "Komt oerien mei filter ‘<span>{title}</span>’",
|
||||
"filtered_notifications_banner.pending_requests": "Fan {count, plural, =0 {net ien} one {ien persoan} other {# persoanen}} dy’t jo mooglik kinne",
|
||||
"filtered_notifications_banner.title": "Filtere meldingen",
|
||||
"firehose.all": "Alles",
|
||||
|
@ -383,6 +386,7 @@
|
|||
"interaction_modal.description.follow": "Jo kinne mei in Mastodon-account {name} folgje, om sa harren berjochten op jo starttiidline te ûntfangen.",
|
||||
"interaction_modal.description.reblog": "Jo kinne mei in Mastodon-account dit berjocht booste, om it sa mei jo folgers te dielen.",
|
||||
"interaction_modal.description.reply": "Jo kinne mei in Mastodon-account op dit berjocht reagearje.",
|
||||
"interaction_modal.description.vote": "Mei in Mastodon-account kinne jo yn dizze enkête stimme.",
|
||||
"interaction_modal.login.action": "Gean nei start",
|
||||
"interaction_modal.login.prompt": "Domein fan jo server, byg. mastodon.social",
|
||||
"interaction_modal.no_account_yet": "Net op Mastodon?",
|
||||
|
@ -394,6 +398,7 @@
|
|||
"interaction_modal.title.follow": "{name} folgje",
|
||||
"interaction_modal.title.reblog": "Berjocht fan {name} booste",
|
||||
"interaction_modal.title.reply": "Op it berjocht fan {name} reagearje",
|
||||
"interaction_modal.title.vote": "Stimme yn {name}’s peiling",
|
||||
"intervals.full.days": "{number, plural, one {# dei} other {# dagen}} lyn",
|
||||
"intervals.full.hours": "{number, plural, one {# oere} other {# oeren}} lyn",
|
||||
"intervals.full.minutes": "{number, plural, one {# minút} other {# minuten}} lyn",
|
||||
|
@ -506,6 +511,7 @@
|
|||
"notification.favourite": "{name} hat jo berjocht as favoryt markearre",
|
||||
"notification.favourite.name_and_others_with_link": "{name} en <a>{count, plural, one {# oar} other {# oaren}}</a> hawwe jo berjocht as favoryt markearre",
|
||||
"notification.follow": "{name} folget dy",
|
||||
"notification.follow.name_and_others": "{name} en <a>{count, plural, one {# oar persoan} other {# oare persoanen}}</a> folgje jo no",
|
||||
"notification.follow_request": "{name} hat dy in folchfersyk stjoerd",
|
||||
"notification.follow_request.name_and_others": "{name} en {count, plural, one {# oar} other {# oaren}} hawwe frege om jo te folgjen",
|
||||
"notification.label.mention": "Fermelding",
|
||||
|
@ -564,6 +570,7 @@
|
|||
"notifications.column_settings.filter_bar.category": "Flugge filterbalke",
|
||||
"notifications.column_settings.follow": "Nije folgers:",
|
||||
"notifications.column_settings.follow_request": "Nij folchfersyk:",
|
||||
"notifications.column_settings.group": "Groepearje",
|
||||
"notifications.column_settings.mention": "Fermeldingen:",
|
||||
"notifications.column_settings.poll": "Enkêteresultaten:",
|
||||
"notifications.column_settings.push": "Pushmeldingen",
|
||||
|
|
|
@ -87,6 +87,24 @@
|
|||
"alert.unexpected.title": "Hiúps!",
|
||||
"alt_text_badge.title": "Téacs alt",
|
||||
"announcement.announcement": "Fógra",
|
||||
"annual_report.summary.archetype.booster": "An sealgair fionnuar",
|
||||
"annual_report.summary.archetype.lurker": "An lurker",
|
||||
"annual_report.summary.archetype.oracle": "An oracal",
|
||||
"annual_report.summary.archetype.pollster": "An pollaire",
|
||||
"annual_report.summary.archetype.replier": "An féileacán sóisialta",
|
||||
"annual_report.summary.followers.followers": "leanúna",
|
||||
"annual_report.summary.followers.total": "{count} san iomlán",
|
||||
"annual_report.summary.here_it_is": "Seo do {year} faoi athbhreithniú:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "post is fearr leat",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "post is treisithe",
|
||||
"annual_report.summary.highlighted_post.by_replies": "post leis an líon is mó freagraí",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name}'s",
|
||||
"annual_report.summary.most_used_app.most_used_app": "aip is mó a úsáidtear",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag is mó a úsáidtear",
|
||||
"annual_report.summary.new_posts.new_posts": "postanna nua",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Cuireann sé sin i mbarr</topLabel><percentage></percentage><bottomLabel> úsáideoirí Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Ní inseoidh muid do Bernie.",
|
||||
"annual_report.summary.thanks": "Go raibh maith agat as a bheith mar chuid de Mastodon!",
|
||||
"attachments_list.unprocessed": "(neamhphróiseáilte)",
|
||||
"audio.hide": "Cuir fuaim i bhfolach",
|
||||
"block_modal.remote_users_caveat": "Iarrfaimid ar an bhfreastalaí {domain} meas a bheith agat ar do chinneadh. Mar sin féin, ní ráthaítear comhlíonadh toisc go bhféadfadh roinnt freastalaithe bloic a láimhseáil ar bhealach difriúil. Seans go mbeidh postálacha poiblí fós le feiceáil ag úsáideoirí nach bhfuil logáilte isteach.",
|
||||
|
@ -386,6 +404,7 @@
|
|||
"interaction_modal.description.follow": "Le cuntas ar Mastodon, is féidir leat {name} a leanúint chun a gcuid postálacha a fháil i do fhotha baile.",
|
||||
"interaction_modal.description.reblog": "Le cuntas ar Mastodon, is féidir leat an postáil seo a threisiú chun é a roinnt le do leantóirí féin.",
|
||||
"interaction_modal.description.reply": "Le cuntas ar Mastodon, is féidir leat freagra a thabhairt ar an bpostáil seo.",
|
||||
"interaction_modal.description.vote": "Le cuntas ar Mastodon, is féidir leat vótáil sa vótaíocht seo.",
|
||||
"interaction_modal.login.action": "Thabhairt dom abhaile",
|
||||
"interaction_modal.login.prompt": "Fearann do fhreastalaí baile, e.g. mastodon.sóisialta",
|
||||
"interaction_modal.no_account_yet": "Ní ar Mastodon?",
|
||||
|
@ -397,6 +416,7 @@
|
|||
"interaction_modal.title.follow": "Lean {name}",
|
||||
"interaction_modal.title.reblog": "Mol postáil de chuid {name}",
|
||||
"interaction_modal.title.reply": "Freagair postáil {name}",
|
||||
"interaction_modal.title.vote": "Vótáil i vótaíocht {name}",
|
||||
"intervals.full.days": "{number, plural, one {# lá} other {# lá}}",
|
||||
"intervals.full.hours": "{number, plural, one {# uair} other {# uair}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# nóiméad} other {# nóiméad}}",
|
||||
|
@ -506,6 +526,8 @@
|
|||
"notification.admin.report_statuses_other": "{name} tuairiscithe {target}",
|
||||
"notification.admin.sign_up": "Chláraigh {name}",
|
||||
"notification.admin.sign_up.name_and_others": "{name} agus {count, plural, one {# duine eile} two {# daoine eile} few {# daoine eile} many {# daoine eile} other {# daoine eile}} a chláraigh",
|
||||
"notification.annual_report.message": "Tá do {year} #Wrapstodon ag fanacht! Nocht buaicphointí na bliana agus chuimhneacháin i gcuimhne ar Mastodon!",
|
||||
"notification.annual_report.view": "Amharc #Wrapstodon",
|
||||
"notification.favourite": "Is fearr le {name} do phostáil",
|
||||
"notification.favourite.name_and_others_with_link": "{name} agus <a>{count, plural, one {# duine eile} other {# daoine eile}}</a> thaitin le do phost",
|
||||
"notification.follow": "Lean {name} thú",
|
||||
|
|
|
@ -87,6 +87,16 @@
|
|||
"alert.unexpected.title": "Vaites!",
|
||||
"alt_text_badge.title": "Texto Alt",
|
||||
"announcement.announcement": "Anuncio",
|
||||
"annual_report.summary.followers.followers": "seguidoras",
|
||||
"annual_report.summary.followers.total": "{count} en total",
|
||||
"annual_report.summary.here_it_is": "Este é o resumo do teu {year}:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "a publicación mais favorecida",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "a publicación con mais promocións",
|
||||
"annual_report.summary.highlighted_post.by_replies": "a publicación con mais respostas",
|
||||
"annual_report.summary.highlighted_post.possessive": "de {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "app que mais usaches",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "o cancelo mais utilizado",
|
||||
"annual_report.summary.new_posts.new_posts": "novas publicacións",
|
||||
"attachments_list.unprocessed": "(sen procesar)",
|
||||
"audio.hide": "Agochar audio",
|
||||
"block_modal.remote_users_caveat": "Ímoslle pedir ao servidor {domain} que respecte a túa decisión. Emporiso, non hai garantía de que atenda a petición xa que os servidores xestionan os bloqueos de formas diferentes. As publicacións públicas poderían aínda ser visibles para usuarias que non iniciaron sesión.",
|
||||
|
|
|
@ -87,6 +87,24 @@
|
|||
"alert.unexpected.title": "Úbbs!",
|
||||
"alt_text_badge.title": "Hjálpartexti mynda",
|
||||
"announcement.announcement": "Auglýsing",
|
||||
"annual_report.summary.archetype.booster": "Svali gaurinn",
|
||||
"annual_report.summary.archetype.lurker": "Lurkurinn",
|
||||
"annual_report.summary.archetype.oracle": "Völvan",
|
||||
"annual_report.summary.archetype.pollster": "Kannanafíkillinn",
|
||||
"annual_report.summary.archetype.replier": "Félagsveran",
|
||||
"annual_report.summary.followers.followers": "fylgjendur",
|
||||
"annual_report.summary.followers.total": "{count} alls",
|
||||
"annual_report.summary.here_it_is": "Hér er yfirlitið þitt fyrir {year}:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "færsla sett oftast í eftirlæti",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "færsla oftast endurbirt",
|
||||
"annual_report.summary.highlighted_post.by_replies": "færsla með flestum svörum",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "mest notaða forrit",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "mest notaða myllumerki",
|
||||
"annual_report.summary.new_posts.new_posts": "nýjar færslur",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Það setur þig á meðal efstu</topLabel><percentage></percentage><bottomLabel>notenda Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Við förum ekkert að raupa um þetta.",
|
||||
"annual_report.summary.thanks": "Takk fyrir að vera hluti af Mastodon-samfélaginu!",
|
||||
"attachments_list.unprocessed": "(óunnið)",
|
||||
"audio.hide": "Fela hljóð",
|
||||
"block_modal.remote_users_caveat": "Við munum biðja {domain} netþjóninn um að virða ákvörðun þína. Hitt er svo annað mál hvort hann fari eftir þessu, ekki er hægt að tryggja eftirfylgni því sumir netþjónar meðhöndla útilokanir á sinn hátt. Opinberar færslur gætu verið sýnilegar notendum sem ekki eru skráðir inn.",
|
||||
|
@ -508,6 +526,8 @@
|
|||
"notification.admin.report_statuses_other": "{name} kærði {target}",
|
||||
"notification.admin.sign_up": "{name} skráði sig",
|
||||
"notification.admin.sign_up.name_and_others": "{name} og {count, plural, one {# í viðbót hefur} other {# í viðbót hafa}} skráð sig",
|
||||
"notification.annual_report.message": "{year} á #Wrapstodon bíður! Afhjúpaðu hvað bar hæst á árinu og minnistæðustu augnablikin á Mastodon!",
|
||||
"notification.annual_report.view": "Skoða #Wrapstodon",
|
||||
"notification.favourite": "{name} setti færsluna þína í eftirlæti",
|
||||
"notification.favourite.name_and_others_with_link": "{name} og <a>{count, plural, one {# í viðbót hefur} other {# í viðbót hafa}}</a> sett færsluna þína í eftirlæti",
|
||||
"notification.follow": "{name} fylgist með þér",
|
||||
|
|
|
@ -113,7 +113,7 @@
|
|||
"bundle_modal_error.message": "컴포넌트를 불러오는 중 문제가 발생했습니다.",
|
||||
"bundle_modal_error.retry": "다시 시도",
|
||||
"closed_registrations.other_server_instructions": "마스토돈은 분산화 되어 있기 때문에, 다른 서버에서 계정을 만들더라도 이 서버와 상호작용 할 수 있습니다.",
|
||||
"closed_registrations_modal.description": "{domain}은 현재 가입이 막혀있는 상태입니다, 마스토돈을 이용하기 위해 꼭 {domain}을 사용할 필요는 없다는 사실을 인지해 두세요.",
|
||||
"closed_registrations_modal.description": "{domain}은 현재 가입이 불가능합니다. 하지만 마스토돈을 이용하기 위해 꼭 {domain}을 사용할 필요는 없다는 사실을 인지해 두세요.",
|
||||
"closed_registrations_modal.find_another_server": "다른 서버 찾기",
|
||||
"closed_registrations_modal.preamble": "마스토돈은 분산화 되어 있습니다, 그렇기 때문에 어디에서 계정을 생성하든, 이 서버에 있는 누구와도 팔로우와 상호작용을 할 수 있습니다. 심지어는 스스로 서버를 만드는 것도 가능합니다!",
|
||||
"closed_registrations_modal.title": "마스토돈에서 가입",
|
||||
|
@ -386,6 +386,7 @@
|
|||
"interaction_modal.description.follow": "마스토돈 계정을 통해, {name} 님을 팔로우 하고 그의 게시물을 홈 피드에서 받아 볼 수 있습니다.",
|
||||
"interaction_modal.description.reblog": "마스토돈 계정을 통해, 이 게시물을 부스트 하고 자신의 팔로워들에게 공유할 수 있습니다.",
|
||||
"interaction_modal.description.reply": "마스토돈 계정을 통해, 이 게시물에 응답할 수 있습니다.",
|
||||
"interaction_modal.description.vote": "마스토돈 계정을 통해, 이 투표에 참여할 수 있습니다.",
|
||||
"interaction_modal.login.action": "홈 서버로 가기",
|
||||
"interaction_modal.login.prompt": "내 홈 서버의 도메인. 예시: mastodon.social",
|
||||
"interaction_modal.no_account_yet": "Mastodon 계정이 없나요?",
|
||||
|
@ -397,6 +398,7 @@
|
|||
"interaction_modal.title.follow": "{name} 님을 팔로우",
|
||||
"interaction_modal.title.reblog": "{name} 님의 게시물을 부스트",
|
||||
"interaction_modal.title.reply": "{name} 님의 게시물에 답글",
|
||||
"interaction_modal.title.vote": "{name} 님의 투표에 참여",
|
||||
"intervals.full.days": "{number} 일",
|
||||
"intervals.full.hours": "{number} 시간",
|
||||
"intervals.full.minutes": "{number} 분",
|
||||
|
|
|
@ -87,6 +87,24 @@
|
|||
"alert.unexpected.title": "Ups!",
|
||||
"alt_text_badge.title": "Alternatyvus tekstas",
|
||||
"announcement.announcement": "Skelbimas",
|
||||
"annual_report.summary.archetype.booster": "Šaunus medžiotojas",
|
||||
"annual_report.summary.archetype.lurker": "Stebėtojas",
|
||||
"annual_report.summary.archetype.oracle": "Vydūnas",
|
||||
"annual_report.summary.archetype.pollster": "Apklausos rengėjas",
|
||||
"annual_report.summary.archetype.replier": "Socialinis drugelis",
|
||||
"annual_report.summary.followers.followers": "sekėjai (-ų)",
|
||||
"annual_report.summary.followers.total": "iš viso {count}",
|
||||
"annual_report.summary.here_it_is": "Štai jūsų {year} apžvalga:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "labiausiai pamėgtas įrašas",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "labiausiai pasidalintas įrašas",
|
||||
"annual_report.summary.highlighted_post.by_replies": "įrašas su daugiausiai atsakymų",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "labiausiai naudota programa",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "labiausiai naudotas saitažodis",
|
||||
"annual_report.summary.new_posts.new_posts": "nauji įrašai",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Tai reiškia, kad esate tarp</topLabel><percentage></percentage><bottomLabel>populiariausių „Mastodon“ naudotojų.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Mes nesakysime Bernie.",
|
||||
"annual_report.summary.thanks": "Dėkojame, kad esate „Mastodon“ dalis!",
|
||||
"attachments_list.unprocessed": "(neapdorotas)",
|
||||
"audio.hide": "Slėpti garsą",
|
||||
"block_modal.remote_users_caveat": "Paprašysime serverio {domain} gerbti tavo sprendimą. Tačiau atitiktis negarantuojama, nes kai kurie serveriai gali skirtingai tvarkyti blokavimus. Vieši įrašai vis tiek gali būti matomi neprisijungusiems naudotojams.",
|
||||
|
@ -507,6 +525,8 @@
|
|||
"notification.admin.report_statuses": "{name} pranešė {target} kategorijai {category}",
|
||||
"notification.admin.report_statuses_other": "{name} pranešė {target}",
|
||||
"notification.admin.sign_up": "{name} užsiregistravo",
|
||||
"notification.annual_report.message": "Jūsų laukia {year} #Wrapstodon! Atskleiskite savo metų svarbiausius įvykius ir įsimintinas akimirkas platformoje „Mastodon“.",
|
||||
"notification.annual_report.view": "Peržiūrėti #Wrapstodon",
|
||||
"notification.favourite": "{name} pamėgo tavo įrašą",
|
||||
"notification.follow": "{name} seka tave",
|
||||
"notification.follow.name_and_others": "{name} ir <a>{count, plural, one {# kitas} few {# kiti} many {# kito} other {# kitų}}</a> seka tave",
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
"account.followers.empty": "Šim lietotājam vēl nav sekotāju.",
|
||||
"account.followers_counter": "{count, plural, zero {{count} sekotāju} one {{count} sekotājs} other {{count} sekotāji}}",
|
||||
"account.following": "Seko",
|
||||
"account.following_counter": "{count, plural, one {seko {counter}} other {seko {counter}}}",
|
||||
"account.follows.empty": "Šis lietotājs pagaidām nevienam neseko.",
|
||||
"account.go_to_profile": "Doties uz profilu",
|
||||
"account.hide_reblogs": "Paslēpt @{name} pastiprinātos ierakstus",
|
||||
|
@ -61,6 +62,7 @@
|
|||
"account.requested_follow": "{name} nosūtīja Tev sekošanas pieprasījumu",
|
||||
"account.share": "Dalīties ar @{name} profilu",
|
||||
"account.show_reblogs": "Parādīt @{name} pastiprinātos ierakstus",
|
||||
"account.statuses_counter": "{count, plural, zero {{counter} ierakstu} one {{counter} ieraksts} other {{counter} ieraksti}}",
|
||||
"account.unblock": "Atbloķēt @{name}",
|
||||
"account.unblock_domain": "Atbloķēt domēnu {domain}",
|
||||
"account.unblock_short": "Atbloķēt",
|
||||
|
@ -191,6 +193,7 @@
|
|||
"confirmations.unfollow.title": "Pārtraukt sekošanu lietotājam?",
|
||||
"content_warning.hide": "Paslēpt ierakstu",
|
||||
"content_warning.show": "Tomēr rādīt",
|
||||
"content_warning.show_more": "Rādīt vairāk",
|
||||
"conversation.delete": "Dzēst sarunu",
|
||||
"conversation.mark_as_read": "Atzīmēt kā izlasītu",
|
||||
"conversation.open": "Skatīt sarunu",
|
||||
|
@ -211,6 +214,7 @@
|
|||
"dismissable_banner.explore_tags": "Šie ir tēmturi, kas šodien gūst uzmanību sabiedriskajā tīmeklī. Tēmturi, kurus izmanto vairāk dažādu cilvēku, tiek vērtēti augstāk.",
|
||||
"dismissable_banner.public_timeline": "Šie ir jaunākie publiskie ieraksti no lietotājiem sociālajā tīmeklī, kuriem {domain} seko cilvēki.",
|
||||
"domain_block_modal.block": "Bloķēt serveri",
|
||||
"domain_block_modal.block_account_instead": "Tā vietā liegt @{name}",
|
||||
"domain_block_modal.they_cant_follow": "Neviens šajā serverī nevar Tev sekot.",
|
||||
"domain_block_modal.they_wont_know": "Viņi nezinās, ka tikuši bloķēti.",
|
||||
"domain_block_modal.title": "Bloķēt domēnu?",
|
||||
|
@ -329,6 +333,7 @@
|
|||
"home.pending_critical_update.link": "Skatīt jauninājumus",
|
||||
"home.pending_critical_update.title": "Ir pieejams būtisks drošības atjauninājums.",
|
||||
"home.show_announcements": "Rādīt paziņojumus",
|
||||
"ignore_notifications_modal.ignore": "Neņemt vērā paziņojumus",
|
||||
"interaction_modal.description.favourite": "Ar Mastodon kontu tu vari pievienot šo ziņu izlasei, lai informētu autoru, ka to novērtē, un saglabātu to vēlākai lasīšanai.",
|
||||
"interaction_modal.description.follow": "Ar Mastodon kontu Tu vari sekot {name}, lai saņemtu lietotāja ierakstus savā mājas plūsmā.",
|
||||
"interaction_modal.description.reblog": "Ar Mastodon kontu Tu vari izvirzīt šo ierakstu, lai kopīgotu to ar saviem sekotājiem.",
|
||||
|
@ -505,10 +510,10 @@
|
|||
"onboarding.action.back": "Aizved mani atpakaļ",
|
||||
"onboarding.actions.back": "Aizved mani atpakaļ",
|
||||
"onboarding.actions.go_to_explore": "Skatīt tendences",
|
||||
"onboarding.actions.go_to_home": "Dodieties uz manu mājas plūsmu",
|
||||
"onboarding.actions.go_to_home": "Doties uz manu sākuma plūsmu",
|
||||
"onboarding.compose.template": "Sveiki, #Mastodon!",
|
||||
"onboarding.follows.empty": "Diemžēl pašlaik nevar parādīt rezultātus. Vari mēģināt izmantot meklēšanu vai pārlūkot izpētes lapu, lai atrastu cilvēkus, kuriem sekot, vai vēlāk mēģināt vēlreiz.",
|
||||
"onboarding.follows.lead": "Tava mājas plūsma ir galvenais veids, kā pieredzēt Mastodon. Jo vairāk cilvēkiem sekosi, jo dzīvīgāka un aizraujošāka tā būs. Lai sāktu, šeit ir daži ieteikumi:",
|
||||
"onboarding.follows.lead": "Tava sākuma plūsma ir galvenais veids, kā pieredzēt Mastodon. Jo vairāk cilvēkiem sekosi, jo dzīvīgāka un aizraujošāka tā būs. Lai sāktu, šeit ir daži ieteikumi:",
|
||||
"onboarding.follows.title": "Pielāgo savu mājas barotni",
|
||||
"onboarding.profile.discoverable": "Padarīt manu profilu atklājamu",
|
||||
"onboarding.profile.display_name": "Attēlojamais vārds",
|
||||
|
@ -526,7 +531,7 @@
|
|||
"onboarding.start.lead": "Tagad Tu esi daļa no Mastodon — vienreizējas, decentralizētas sociālās mediju platformas, kurā Tu, nevis algoritms, veido Tavu pieredzi. Sāksim darbu šajā jaunajā sociālajā jomā:",
|
||||
"onboarding.start.skip": "Nav nepieciešama palīdzība darba sākšanai?",
|
||||
"onboarding.start.title": "Tev tas izdevās!",
|
||||
"onboarding.steps.follow_people.body": "Tu pats veido savu plūsmu. Piepildīsim to ar interesantiem cilvēkiem.",
|
||||
"onboarding.steps.follow_people.body": "Sekošana aizraujošiem cilvēkiem ir tas, par ko ir Mastodon.",
|
||||
"onboarding.steps.follow_people.title": "Pielāgo savu mājas barotni",
|
||||
"onboarding.steps.publish_status.body": "Pasveicini pasauli ar tekstu, attēliem, video vai aptaujām {emoji}",
|
||||
"onboarding.steps.publish_status.title": "Izveido savu pirmo ziņu",
|
||||
|
|
|
@ -87,6 +87,24 @@
|
|||
"alert.unexpected.title": "Oeps!",
|
||||
"alt_text_badge.title": "Alt-tekst",
|
||||
"announcement.announcement": "Mededeling",
|
||||
"annual_report.summary.archetype.booster": "De cool-hunter",
|
||||
"annual_report.summary.archetype.lurker": "De lurker",
|
||||
"annual_report.summary.archetype.oracle": "Het orakel",
|
||||
"annual_report.summary.archetype.pollster": "De opiniepeiler",
|
||||
"annual_report.summary.archetype.replier": "De sociale vlinder",
|
||||
"annual_report.summary.followers.followers": "volgers",
|
||||
"annual_report.summary.followers.total": "totaal {count}",
|
||||
"annual_report.summary.here_it_is": "Hier is jouw terugblik op {year}:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "bericht met de meeste favorieten",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "bericht met de meeste boosts",
|
||||
"annual_report.summary.highlighted_post.by_replies": "bericht met de meeste reacties",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name}'s",
|
||||
"annual_report.summary.most_used_app.most_used_app": "meest gebruikte app",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "meest gebruikte hashtag",
|
||||
"annual_report.summary.new_posts.new_posts": "nieuwe berichten",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Dat zet je in de top</topLabel><percentage></percentage><bottomLabel>van Mastodon-gebruikers.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "We zullen Bernie niets vertellen.",
|
||||
"annual_report.summary.thanks": "Bedankt dat je deel uitmaakt van Mastodon!",
|
||||
"attachments_list.unprocessed": "(niet verwerkt)",
|
||||
"audio.hide": "Audio verbergen",
|
||||
"block_modal.remote_users_caveat": "We vragen de server {domain} om je besluit te respecteren. Het naleven hiervan is echter niet gegarandeerd, omdat sommige servers blokkades anders kunnen interpreteren. Openbare berichten zijn mogelijk nog steeds zichtbaar voor niet-ingelogde gebruikers.",
|
||||
|
@ -508,6 +526,8 @@
|
|||
"notification.admin.report_statuses_other": "{name} rapporteerde {target}",
|
||||
"notification.admin.sign_up": "{name} heeft zich geregistreerd",
|
||||
"notification.admin.sign_up.name_and_others": "{name} en {count, plural, one {# ander persoon} other {# andere personen}} hebben zich geregistreerd",
|
||||
"notification.annual_report.message": "Jouw {year} #Wrapstodon staat klaar! Laat de hoogtepunten en memorabele momenten van jouw jaar zien op Mastodon!",
|
||||
"notification.annual_report.view": "#Wrapstodon bekijken",
|
||||
"notification.favourite": "{name} markeerde jouw bericht als favoriet",
|
||||
"notification.favourite.name_and_others_with_link": "{name} en <a>{count, plural, one {# ander persoon} other {# andere personen}}</a> hebben jouw bericht als favoriet gemarkeerd",
|
||||
"notification.follow": "{name} volgt jou nu",
|
||||
|
|
|
@ -87,6 +87,24 @@
|
|||
"alert.unexpected.title": "Eita!",
|
||||
"alt_text_badge.title": "Texto alternativo",
|
||||
"announcement.announcement": "Comunicados",
|
||||
"annual_report.summary.archetype.booster": "Caçador legal",
|
||||
"annual_report.summary.archetype.lurker": "O espreitador",
|
||||
"annual_report.summary.archetype.oracle": "O oráculo",
|
||||
"annual_report.summary.archetype.pollster": "O pesquisador",
|
||||
"annual_report.summary.archetype.replier": "A borboleta social",
|
||||
"annual_report.summary.followers.followers": "seguidores",
|
||||
"annual_report.summary.followers.total": "{count} total",
|
||||
"annual_report.summary.here_it_is": "Aqui está seu {year} em revisão:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "publicação mais favoritada",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "publicação mais impulsionada",
|
||||
"annual_report.summary.highlighted_post.by_replies": "publicação com mais respostas",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "aplicativo mais usado",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag mais usada",
|
||||
"annual_report.summary.new_posts.new_posts": "novas publicações",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Isso o coloca no topo</topLabel></percentage><bottomLabel>dos usuários de Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Não contaremos à Bernie.",
|
||||
"annual_report.summary.thanks": "Obrigada por fazer parte do Mastodon!",
|
||||
"attachments_list.unprocessed": "(não processado)",
|
||||
"audio.hide": "Ocultar áudio",
|
||||
"block_modal.remote_users_caveat": "Pediremos ao servidor {domínio} que respeite sua decisão. No entanto, a conformidade não é garantida pois alguns servidores podem lidar com os blocos de maneira diferente. As postagens públicas ainda podem estar visíveis para usuários não logados.",
|
||||
|
@ -508,6 +526,8 @@
|
|||
"notification.admin.report_statuses_other": "{name} denunciou {target}",
|
||||
"notification.admin.sign_up": "{name} se inscreveu",
|
||||
"notification.admin.sign_up.name_and_others": "{name} e {count, plural, one {# other} other {# outros}}",
|
||||
"notification.annual_report.message": "O #Wrapstodon do seu {year} está esperando! Desvende seus destaques do ano e momentos memoráveis no Mastodon!",
|
||||
"notification.annual_report.view": "Ver #Wrapstodon",
|
||||
"notification.favourite": "{name} favoritou sua publicação",
|
||||
"notification.favourite.name_and_others_with_link": "{name} e <a>{count, plural, one {# outro} other {# others}}</a> favoritaram a publicação",
|
||||
"notification.follow": "{name} te seguiu",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"alert.rate_limited.message": "Пожалуйста, повторите после {retry_time, time, medium}.",
|
||||
"alert.rate_limited.title": "Ограничение количества запросов",
|
||||
"alert.unexpected.message": "Произошла непредвиденная ошибка.",
|
||||
"alert.unexpected.title": "Упс!",
|
||||
"alert.unexpected.title": "Ой!",
|
||||
"alt_text_badge.title": "Альтернативный текст",
|
||||
"announcement.announcement": "Объявление",
|
||||
"attachments_list.unprocessed": "(не обработан)",
|
||||
|
@ -95,11 +95,11 @@
|
|||
"block_modal.they_cant_mention": "Он не может упоминать или подписываться на вас.",
|
||||
"block_modal.they_cant_see_posts": "Он не может видеть ваши сообщения, и вы не увидите его.",
|
||||
"block_modal.they_will_know": "Он может видеть, что он заблокирован.",
|
||||
"block_modal.title": "Заблокировать пользователя?",
|
||||
"block_modal.title": "Заблокируем пользователя?",
|
||||
"block_modal.you_wont_see_mentions": "Вы не увидите записи, которые упоминают его.",
|
||||
"boost_modal.combo": "{combo}, чтобы пропустить это в следующий раз",
|
||||
"boost_modal.reblog": "Повысить пост?",
|
||||
"boost_modal.undo_reblog": "Разгрузить пост?",
|
||||
"boost_modal.reblog": "Репостнуть?",
|
||||
"boost_modal.undo_reblog": "Убрать репост?",
|
||||
"bundle_column_error.copy_stacktrace": "Скопировать отчет об ошибке",
|
||||
"bundle_column_error.error.body": "Запрошенная страница не может быть отображена. Это может быть вызвано ошибкой в нашем коде или проблемой совместимости браузера.",
|
||||
"bundle_column_error.error.title": "О нет!",
|
||||
|
@ -144,7 +144,7 @@
|
|||
"community.column_settings.local_only": "Только локальные",
|
||||
"community.column_settings.media_only": "Только с медиафайлами",
|
||||
"community.column_settings.remote_only": "Только удалённые",
|
||||
"compose.language.change": "Изменить язык",
|
||||
"compose.language.change": "Сменить язык",
|
||||
"compose.language.search": "Поиск языков...",
|
||||
"compose.published.body": "Запись опубликована.",
|
||||
"compose.published.open": "Открыть",
|
||||
|
@ -158,6 +158,7 @@
|
|||
"compose_form.poll.duration": "Продолжительность опроса",
|
||||
"compose_form.poll.multiple": "Несколько вариантов ответа",
|
||||
"compose_form.poll.option_placeholder": "Вариант {number}",
|
||||
"compose_form.poll.single": "Один вариант ответа",
|
||||
"compose_form.poll.switch_to_multiple": "Разрешить выбор нескольких вариантов",
|
||||
"compose_form.poll.switch_to_single": "Переключить в режим выбора одного ответа",
|
||||
"compose_form.poll.type": "Стиль",
|
||||
|
@ -180,7 +181,7 @@
|
|||
"confirmations.discard_edit_media.message": "У вас есть несохранённые изменения описания мультимедиа или предпросмотра, отменить их?",
|
||||
"confirmations.edit.confirm": "Редактировать",
|
||||
"confirmations.edit.message": "В данный момент, редактирование перезапишет составляемое вами сообщение. Вы уверены, что хотите продолжить?",
|
||||
"confirmations.edit.title": "Переписать сообщение?",
|
||||
"confirmations.edit.title": "Перепишем сообщение?",
|
||||
"confirmations.logout.confirm": "Выйти",
|
||||
"confirmations.logout.message": "Вы уверены, что хотите выйти?",
|
||||
"confirmations.logout.title": "Выйти?",
|
||||
|
@ -196,6 +197,7 @@
|
|||
"confirmations.unfollow.title": "Отписаться?",
|
||||
"content_warning.hide": "Скрыть пост",
|
||||
"content_warning.show": "Всё равно показать",
|
||||
"content_warning.show_more": "Покажи ещё",
|
||||
"conversation.delete": "Удалить беседу",
|
||||
"conversation.mark_as_read": "Отметить как прочитанное",
|
||||
"conversation.open": "Просмотр беседы",
|
||||
|
@ -221,6 +223,7 @@
|
|||
"domain_block_modal.they_cant_follow": "Никто из этого сервера не может подписываться на вас.",
|
||||
"domain_block_modal.they_wont_know": "Он не будет знать, что его заблокировали.",
|
||||
"domain_block_modal.title": "Заблокировать домен?",
|
||||
"domain_block_modal.you_will_lose_num_followers": "Вы потеряете {followersCount, plural, one {{followersCountDisplay} подписчика} other {{followersCountDisplay} подписчиков}} и {followingCount, plural, one {{followingCountDisplay} подписку} other {{followingCountDisplay} подписок}}.",
|
||||
"domain_block_modal.you_will_lose_relationships": "Вы потеряете всех подписчиков и людей, на которых вы подписаны, на этом сервере.",
|
||||
"domain_block_modal.you_wont_see_posts": "Вы не будете видеть записи или уведомления от пользователей на этом сервере.",
|
||||
"domain_pill.activitypub_lets_connect": "Это позволяет вам общаться и взаимодействовать с людьми не только на Mastodon, но и в различных социальных приложениях.",
|
||||
|
@ -303,6 +306,7 @@
|
|||
"filter_modal.select_filter.subtitle": "Используйте существующую категорию или создайте новую",
|
||||
"filter_modal.select_filter.title": "Фильтровать этот пост",
|
||||
"filter_modal.title.status": "Фильтровать пост",
|
||||
"filter_warning.matches_filter": "Соответствует фильтру \"<span>{title}</span>\"",
|
||||
"filtered_notifications_banner.pending_requests": "Вы можете знать {count, plural, =0 {ни одного человека} one {одного человека} other {# человек}}",
|
||||
"filtered_notifications_banner.title": "Отфильтрованные уведомления",
|
||||
"firehose.all": "Все",
|
||||
|
@ -382,6 +386,7 @@
|
|||
"interaction_modal.description.follow": "С учётной записью Mastodon вы можете подписаться на {name}, чтобы получать их посты в своей домашней ленте.",
|
||||
"interaction_modal.description.reblog": "С учётной записью Mastodon, вы можете продвинуть этот пост, чтобы поделиться им со своими подписчиками.",
|
||||
"interaction_modal.description.reply": "Вы можете ответить на этот пост с учётной записью Mastodon.",
|
||||
"interaction_modal.description.vote": "Вы сможете проголосовать тут имея аккаунт Mastodon.",
|
||||
"interaction_modal.login.action": "Перейти на домашнюю страницу",
|
||||
"interaction_modal.login.prompt": "Домен вашего домашнего сервера, например, mastodon.social",
|
||||
"interaction_modal.no_account_yet": "Еще не на Mastodon?",
|
||||
|
@ -393,12 +398,13 @@
|
|||
"interaction_modal.title.follow": "Подписаться на {name}",
|
||||
"interaction_modal.title.reblog": "Продвинуть публикацию {name}",
|
||||
"interaction_modal.title.reply": "Ответить на пост {name}",
|
||||
"interaction_modal.title.vote": "Голосовать в опросе {name}",
|
||||
"intervals.full.days": "{number, plural, one {# день} few {# дня} other {# дней}}",
|
||||
"intervals.full.hours": "{number, plural, one {# час} few {# часа} other {# часов}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# минута} few {# минуты} other {# минут}}",
|
||||
"keyboard_shortcuts.back": "перейти назад",
|
||||
"keyboard_shortcuts.blocked": "чтобы открыть список заблокированных",
|
||||
"keyboard_shortcuts.boost": "продвинуть пост",
|
||||
"keyboard_shortcuts.boost": "Репостнуть",
|
||||
"keyboard_shortcuts.column": "фокус на одном из столбцов",
|
||||
"keyboard_shortcuts.compose": "фокус на поле ввода",
|
||||
"keyboard_shortcuts.description": "Описание",
|
||||
|
@ -464,7 +470,7 @@
|
|||
"mute_modal.show_options": "Показать опции",
|
||||
"mute_modal.they_can_mention_and_follow": "Они могут упоминать и следить за вами, но вы не будете их видеть.",
|
||||
"mute_modal.they_wont_know": "Они не будут знать, что их заглушили.",
|
||||
"mute_modal.title": "Заглушить пользователя?",
|
||||
"mute_modal.title": "Заткнуть пользователя?",
|
||||
"mute_modal.you_wont_see_mentions": "Вы не увидите постов, которые их упоминают.",
|
||||
"mute_modal.you_wont_see_posts": "Они по-прежнему смогут видеть ваши посты, но вы не сможете видеть их посты.",
|
||||
"navigation_bar.about": "О проекте",
|
||||
|
@ -503,8 +509,9 @@
|
|||
"notification.admin.sign_up": "{name} зарегистрирован",
|
||||
"notification.admin.sign_up.name_and_others": "{name} и {count, plural, one {# другой} other {# другие}} подписались",
|
||||
"notification.favourite": "{name} добавил(а) ваш пост в избранное",
|
||||
"notification.favourite.name_and_others_with_link": "{name} и <a>{count, plural, one {# другие} other {# другие}}</a> отдали предпочтение вашему посту",
|
||||
"notification.favourite.name_and_others_with_link": "{name} и ещё <a>{count, plural, one {# пользователь} few {# пользователя} other {# пользователей}}</a> добавили ваш пост в избранное",
|
||||
"notification.follow": "{name} подписался (-лась) на вас",
|
||||
"notification.follow.name_and_others": "{name} и <a>{count, plural, one {# other} few {# others} many {# others} other {# others}}</a> подписались на вас",
|
||||
"notification.follow_request": "{name} отправил запрос на подписку",
|
||||
"notification.follow_request.name_and_others": "{name} и ещё {count, plural, one {#} other {# других}} подписались на вас",
|
||||
"notification.label.mention": "Упоминание",
|
||||
|
@ -512,7 +519,7 @@
|
|||
"notification.label.private_reply": "Частный ответ",
|
||||
"notification.label.reply": "Ответить",
|
||||
"notification.mention": "Упоминание",
|
||||
"notification.mentioned_you": "{name} упомянул(а) вас",
|
||||
"notification.mentioned_you": "{name} упоминает вас",
|
||||
"notification.moderation-warning.learn_more": "Узнать больше",
|
||||
"notification.moderation_warning": "Вы получили предупреждение от модерации",
|
||||
"notification.moderation_warning.action_delete_statuses": "Некоторые из ваших публикаций были удалены.",
|
||||
|
@ -525,7 +532,7 @@
|
|||
"notification.own_poll": "Ваш опрос закончился",
|
||||
"notification.poll": "Голосование, в котором вы приняли участие, завершилось",
|
||||
"notification.reblog": "{name} продвинул(а) ваш пост",
|
||||
"notification.reblog.name_and_others_with_link": "{name} и <a>{count, plural, one {# other} other {# others}}</a> увеличили ваш пост",
|
||||
"notification.reblog.name_and_others_with_link": "{name} и ещё <a>{count, plural, one {# пользователь} few {# пользователя} other {# пользователей}}</a> продвинули ваш пост",
|
||||
"notification.relationships_severance_event": "Потеряно соединение с {name}",
|
||||
"notification.relationships_severance_event.account_suspension": "Администратор {from} заблокировал {target}, что означает, что вы больше не сможете получать обновления от них или взаймодествовать с ними.",
|
||||
"notification.relationships_severance_event.domain_block": "Администратор {from} заблокировал {target} включая {followersCount} ваших подписчиков и {followingCount, plural, one {# аккаунт} few {# аккаунта} other {# аккаунтов}}, на которые вы подписаны.",
|
||||
|
@ -534,10 +541,15 @@
|
|||
"notification.status": "{name} только что запостил",
|
||||
"notification.update": "{name} изменил(а) пост",
|
||||
"notification_requests.accept": "Принять",
|
||||
"notification_requests.accept_multiple": "{count, plural, one {Принять # запрос…} few {Принять # запроса…} other {Принять # запросов…}}",
|
||||
"notification_requests.confirm_accept_multiple.button": "{count, plural, one {Принять запрос} other {Принять запросы}}",
|
||||
"notification_requests.confirm_accept_multiple.message": "Вы собираетесь принять {count, plural, one {# запрос на показ уведомлений} few {# запроса на показ уведомлений} other {# запросов на показ уведомлений}}. Продолжить?",
|
||||
"notification_requests.confirm_accept_multiple.title": "Принимать запросы на уведомления?",
|
||||
"notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Отклонить запрос} other {Отклонить запросы}}",
|
||||
"notification_requests.confirm_dismiss_multiple.message": "Вы собираетесь отклонить {count, plural, one {# запрос на показ уведомлений} few {# запроса на показ уведомлений} other {# запросов на показ уведомлений}}. Вы не сможете просмотреть {count, plural, other {их}} потом. Продолжить?",
|
||||
"notification_requests.confirm_dismiss_multiple.title": "Отклонять запросы на уведомления?",
|
||||
"notification_requests.dismiss": "Отклонить",
|
||||
"notification_requests.dismiss_multiple": "{count, plural, one {Отклонить # запрос…} few {Отклонить # запроса…} other {Отклонить # запросов…}}",
|
||||
"notification_requests.edit_selection": "Редактировать",
|
||||
"notification_requests.exit_selection": "Готово",
|
||||
"notification_requests.explainer_for_limited_account": "Уведомления от этой учетной записи были отфильтрованы, поскольку учетная запись была ограничена модератором.",
|
||||
|
@ -558,6 +570,7 @@
|
|||
"notifications.column_settings.filter_bar.category": "Панель сортировки",
|
||||
"notifications.column_settings.follow": "У вас новый подписчик:",
|
||||
"notifications.column_settings.follow_request": "Новые запросы на подписку:",
|
||||
"notifications.column_settings.group": "Группа",
|
||||
"notifications.column_settings.mention": "Вас упомянули в посте:",
|
||||
"notifications.column_settings.poll": "Опрос, в котором вы приняли участие, завершился:",
|
||||
"notifications.column_settings.push": "Пуш-уведомления",
|
||||
|
@ -591,6 +604,7 @@
|
|||
"notifications.policy.filter_limited_accounts_title": "Модерируемые аккаунты",
|
||||
"notifications.policy.filter_new_accounts.hint": "Создано в течение последних {days, plural, one {один день} few {# дней} many {# дней} other {# дня}}",
|
||||
"notifications.policy.filter_new_accounts_title": "Новые учётные записи",
|
||||
"notifications.policy.filter_not_followers_hint": "Включая людей, которые подписаны на вас меньше чем {days, plural, one {# день} few {# дня} other {# дней}}",
|
||||
"notifications.policy.filter_not_followers_title": "Люди, не подписанные на вас",
|
||||
"notifications.policy.filter_not_following_hint": "Пока вы не одобрите их вручную",
|
||||
"notifications.policy.filter_not_following_title": "Люди, на которых вы не подписаны",
|
||||
|
@ -600,8 +614,8 @@
|
|||
"notifications_permission_banner.enable": "Включить уведомления",
|
||||
"notifications_permission_banner.how_to_control": "Получайте уведомления даже когда Mastodon закрыт, включив уведомления на рабочем столе. А чтобы лишний шум не отвлекал, вы можете настроить какие уведомления вы хотите получать, нажав на кнопку {icon} выше.",
|
||||
"notifications_permission_banner.title": "Будьте в курсе происходящего",
|
||||
"onboarding.action.back": "Вернуть меня",
|
||||
"onboarding.actions.back": "Вернуть меня",
|
||||
"onboarding.action.back": "Верните меня",
|
||||
"onboarding.actions.back": "Верните меня",
|
||||
"onboarding.actions.go_to_explore": "Посмотреть, что актуально",
|
||||
"onboarding.actions.go_to_home": "Перейти к домашней ленте новостей",
|
||||
"onboarding.compose.template": "Привет, #Mastodon!",
|
||||
|
@ -619,7 +633,7 @@
|
|||
"onboarding.profile.title": "Настройка профиля",
|
||||
"onboarding.profile.upload_avatar": "Загрузить фотографию профиля",
|
||||
"onboarding.profile.upload_header": "Загрузить заголовок профиля",
|
||||
"onboarding.share.lead": "Расскажите людям, как они могут найти вас на Mastodon!",
|
||||
"onboarding.share.lead": "Расскажите людям, как найти вас на Mastodon!",
|
||||
"onboarding.share.message": "Я {username} на #Mastodon! Следуйте за мной по адресу {url}",
|
||||
"onboarding.share.next_steps": "Возможные дальнейшие шаги:",
|
||||
"onboarding.share.title": "Поделиться вашим профилем",
|
||||
|
@ -634,7 +648,7 @@
|
|||
"onboarding.steps.setup_profile.title": "Настройте свой профиль",
|
||||
"onboarding.steps.share_profile.body": "Расскажите своим друзьям как найти вас на Mastodon!",
|
||||
"onboarding.steps.share_profile.title": "Поделитесь вашим профилем",
|
||||
"onboarding.tips.2fa": "<strong>Знаете ли вы? </strong> Вы можете защитить свой аккаунт, настроив двухфакторную аутентификацию в настройках аккаунта. Она работает с любым приложением TOTP по вашему выбору, номер телефона не требуется!",
|
||||
"onboarding.tips.2fa": "<strong>А вы знали? </strong> Можно защитить свой аккаунт, настроив двухфакторную аутентификацию в настройках аккаунта. Она работает с любым приложением TOTP по вашему выбору, номер телефона не нужен!",
|
||||
"onboarding.tips.accounts_from_other_servers": "<strong>Знали ли вы? </strong> Поскольку Mastodon децентрализован, некоторые профили, с которыми вы столкнетесь, будут размещены на серверах, отличных от вашего. И все же вы можете взаимодействовать с ними без проблем! Их сервер находится во второй половине имени пользователя!",
|
||||
"onboarding.tips.migration": "<strong>Знаете ли вы? </strong> Если вы чувствуете, что {domain} не подходит вам в качестве сервера в будущем, вы можете переехать на другой сервер Mastodon без потери своих подписчиков. Вы даже можете разместить свой собственный сервер!",
|
||||
"onboarding.tips.verification": "<strong>Знали ли вы? </strong> Вы можете подтвердить свою учетную запись, разместив ссылку на свой профиль Mastodon на собственном сайте и добавив сайт в свой профиль. Никаких сборов или документов не требуется!",
|
||||
|
@ -665,7 +679,7 @@
|
|||
"privacy_policy.title": "Политика конфиденциальности",
|
||||
"recommended": "Рекомендуется",
|
||||
"refresh": "Обновить",
|
||||
"regeneration_indicator.label": "Загрузка…",
|
||||
"regeneration_indicator.label": "Грузим…",
|
||||
"regeneration_indicator.sublabel": "Один момент, мы подготавливаем вашу ленту!",
|
||||
"relative_time.days": "{number} д",
|
||||
"relative_time.full.days": "{number, plural, one {# день} many {# дней} other {# дня}} назад",
|
||||
|
@ -782,6 +796,7 @@
|
|||
"status.edited_x_times": "{count, plural, one {{count} изменение} many {{count} изменений} other {{count} изменения}}",
|
||||
"status.embed": "Получить код для встраивания",
|
||||
"status.favourite": "Избранное",
|
||||
"status.favourites": "{count, plural, one {в избранном} other {в избранном}}",
|
||||
"status.filter": "Фильтровать этот пост",
|
||||
"status.history.created": "{name} создал {date}",
|
||||
"status.history.edited": "{name} отредактировал(а) {date}",
|
||||
|
@ -800,6 +815,7 @@
|
|||
"status.reblog": "Продвинуть",
|
||||
"status.reblog_private": "Продвинуть для своей аудитории",
|
||||
"status.reblogged_by": "{name} продвинул(а)",
|
||||
"status.reblogs": "{count, plural, one {boost} few {boosts} many {boosts} other {boosts}}",
|
||||
"status.reblogs.empty": "Никто ещё не продвинул этот пост. Как только кто-то это сделает, они появятся здесь.",
|
||||
"status.redraft": "Создать заново",
|
||||
"status.remove_bookmark": "Убрать из закладок",
|
||||
|
@ -856,11 +872,11 @@
|
|||
"upload_modal.detect_text": "Найти текст на картинке",
|
||||
"upload_modal.edit_media": "Изменить файл",
|
||||
"upload_modal.hint": "Нажмите и перетащите круг в предпросмотре в точку фокуса, которая всегда будет видна на эскизах.",
|
||||
"upload_modal.preparing_ocr": "Подготовка распознования…",
|
||||
"upload_modal.preparing_ocr": "Подготовка распознавания…",
|
||||
"upload_modal.preview_label": "Предпросмотр ({ratio})",
|
||||
"upload_progress.label": "Загрузка...",
|
||||
"upload_progress.processing": "Обработка…",
|
||||
"username.taken": "Данное имя пользователя уже занято. Выберите другое.",
|
||||
"username.taken": "Это имя пользователя уже занято. Выберите другое",
|
||||
"video.close": "Закрыть видео",
|
||||
"video.download": "Загрузить файл",
|
||||
"video.exit_fullscreen": "Покинуть полноэкранный режим",
|
||||
|
|
|
@ -194,6 +194,7 @@
|
|||
"confirmations.unfollow.title": "Prestať sledovať užívateľa?",
|
||||
"content_warning.hide": "Skryť príspevok",
|
||||
"content_warning.show": "Aj tak zobraziť",
|
||||
"content_warning.show_more": "Ukázať viac",
|
||||
"conversation.delete": "Vymazať konverzáciu",
|
||||
"conversation.mark_as_read": "Označiť ako prečítanú",
|
||||
"conversation.open": "Zobraziť konverzáciu",
|
||||
|
@ -484,7 +485,7 @@
|
|||
"notification_requests.edit_selection": "Uprav",
|
||||
"notification_requests.exit_selection": "Hotovo",
|
||||
"notification_requests.notifications_from": "Oboznámenia od {name}",
|
||||
"notification_requests.title": "Filtrované oboznámenia",
|
||||
"notification_requests.title": "Filtrované oznámenia",
|
||||
"notification_requests.view": "Zobraz upozornenia",
|
||||
"notifications.clear": "Vyčistiť upozornenia",
|
||||
"notifications.clear_confirmation": "Určite chcete nenávratne odstrániť všetky svoje upozornenia?",
|
||||
|
|
|
@ -87,6 +87,19 @@
|
|||
"alert.unexpected.title": "Hëm!",
|
||||
"alt_text_badge.title": "Tekst alternativ",
|
||||
"announcement.announcement": "Lajmërim",
|
||||
"annual_report.summary.followers.followers": "ndjekës",
|
||||
"annual_report.summary.followers.total": "{count} gjithsej",
|
||||
"annual_report.summary.here_it_is": "Ja {year} juaj e shqyrtuar:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "potimi më i parapëlqyer",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "postimi me më shumë përforcime",
|
||||
"annual_report.summary.highlighted_post.by_replies": "postimi me më tepër përgjigje",
|
||||
"annual_report.summary.highlighted_post.possessive": "nga {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "aplikacioni më i përdorur",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag-u më i përdorur",
|
||||
"annual_report.summary.new_posts.new_posts": "postime të reja",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Kjo ju vë në krye</topLabel><percentage></percentage><bottomLabel>të përdoruesve të Mastodon-it.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Nuk do t’ia themi Bernit.",
|
||||
"annual_report.summary.thanks": "Faleminderit që jeni pjesë e Mastodon-it!",
|
||||
"attachments_list.unprocessed": "(e papërpunuar)",
|
||||
"audio.hide": "Fshihe audion",
|
||||
"block_modal.remote_users_caveat": "Do t’i kërkojmë shërbyesit {domain} të respektojë vendimin tuaj. Por, pajtimi s’është i garantuar, ngaqë disa shërbyes mund t’i trajtojnë ndryshe bllokimet. Psotimet publike mundet të jenë ende të dukshme për përdorues pa bërë hyrje në llogari.",
|
||||
|
@ -508,6 +521,8 @@
|
|||
"notification.admin.report_statuses_other": "{name} raportoi {target}",
|
||||
"notification.admin.sign_up": "{name} u regjistrua",
|
||||
"notification.admin.sign_up.name_and_others": "U regjistrua {name} dhe {count, plural, one {# tjetër} other {# të tjerë}}",
|
||||
"notification.annual_report.message": "#Wrapstodon juaj për {year} pret! Zbuloni pikat e theksuara dhe çastet e paharrueshëm të këtij viti për ju në Mastodon!",
|
||||
"notification.annual_report.view": "Shihni #Wrapstodon",
|
||||
"notification.favourite": "{name} i vuri shenjë postimit tuaj si të parapëlqyer",
|
||||
"notification.favourite.name_and_others_with_link": "{name} dhe <a>{count, plural, one {# tjetër} other {# të tjerë}}</a> i vunë shenjë postimit tuaj si të parapëlqyer",
|
||||
"notification.follow": "{name} zuri t’ju ndjekë",
|
||||
|
|
|
@ -82,11 +82,17 @@
|
|||
"admin.impact_report.instance_follows": "Följare som deras användare skulle förlora",
|
||||
"admin.impact_report.title": "Sammanfattning av påverkan",
|
||||
"alert.rate_limited.message": "Vänligen försök igen efter {retry_time, time, medium}.",
|
||||
"alert.rate_limited.title": "Mängd begränsad",
|
||||
"alert.rate_limited.title": "Hastighetsbegränsad",
|
||||
"alert.unexpected.message": "Ett oväntat fel uppstod.",
|
||||
"alert.unexpected.title": "Hoppsan!",
|
||||
"alt_text_badge.title": "Alt-Text",
|
||||
"announcement.announcement": "Meddelande",
|
||||
"annual_report.summary.followers.followers": "följare",
|
||||
"annual_report.summary.followers.total": "{count} totalt",
|
||||
"annual_report.summary.highlighted_post.by_replies": "inlägg med flest svar",
|
||||
"annual_report.summary.new_posts.new_posts": "nya inlägg",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Vi berättar inte för Bernie.",
|
||||
"annual_report.summary.thanks": "Tack för att du är en del av Mastodon!",
|
||||
"attachments_list.unprocessed": "(obehandlad)",
|
||||
"audio.hide": "Dölj audio",
|
||||
"block_modal.remote_users_caveat": "Vi kommer att be servern {domain} att respektera ditt beslut. Dock garanteras inte efterlevnad eftersom vissa servrar kan hantera blockeringar på olika sätt. Offentliga inlägg kan fortfarande vara synliga för icke-inloggade användare.",
|
||||
|
@ -271,7 +277,7 @@
|
|||
"empty_column.follow_requests": "Du har inga följarförfrågningar än. När du får en kommer den visas här.",
|
||||
"empty_column.followed_tags": "Du följer inga hashtaggar ännu. När du gör det kommer de att dyka upp här.",
|
||||
"empty_column.hashtag": "Det finns inget i denna hashtag ännu.",
|
||||
"empty_column.home": "Din hemma-tidslinje är tom! Följ fler användare för att fylla den. {suggestions}",
|
||||
"empty_column.home": "Din hemma-tidslinje är tom! Följ fler användare för att fylla den.",
|
||||
"empty_column.list": "Det finns inget i denna lista än. När listmedlemmar publicerar nya inlägg kommer de synas här.",
|
||||
"empty_column.lists": "Du har inga listor än. När skapar en kommer den dyka upp här.",
|
||||
"empty_column.mutes": "Du har ännu inte tystat några användare.",
|
||||
|
@ -383,7 +389,7 @@
|
|||
"ignore_notifications_modal.not_following_title": "Vill du blockera aviseringar från personer som du inte följer dig?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Vill du ignorera aviseringar från oönskade privata omnämningar?",
|
||||
"interaction_modal.description.favourite": "Med ett Mastodon-konto kan du favoritmarkera detta inlägg för att visa författaren att du gillar det och för att spara det till senare.",
|
||||
"interaction_modal.description.follow": "Med ett Mastodon-konto kan du följa {name} för att se hens inlägg i ditt hemflöde.",
|
||||
"interaction_modal.description.follow": "Med ett Mastodon-konto kan du följa {name} för att se deras inlägg i ditt hemflöde.",
|
||||
"interaction_modal.description.reblog": "Med ett Mastodon-konto kan du boosta detta inlägg för att dela den med dina egna följare.",
|
||||
"interaction_modal.description.reply": "Med ett Mastodon-konto kan du svara på detta inlägg.",
|
||||
"interaction_modal.description.vote": "Med ett konto på Mastodon kan du delta i denna omröstning.",
|
||||
|
@ -405,11 +411,11 @@
|
|||
"keyboard_shortcuts.back": "Gå bakåt",
|
||||
"keyboard_shortcuts.blocked": "Öppna listan över blockerade användare",
|
||||
"keyboard_shortcuts.boost": "Boosta inlägg",
|
||||
"keyboard_shortcuts.column": "för att fokusera en status i en av kolumnerna",
|
||||
"keyboard_shortcuts.column": "Fokusera kolumn",
|
||||
"keyboard_shortcuts.compose": "för att fokusera skrivfältet",
|
||||
"keyboard_shortcuts.description": "Beskrivning",
|
||||
"keyboard_shortcuts.direct": "för att öppna privata nämningskolumnen",
|
||||
"keyboard_shortcuts.down": "för att flytta nedåt i listan",
|
||||
"keyboard_shortcuts.down": "Flytta ner i listan",
|
||||
"keyboard_shortcuts.enter": "Öppna inlägg",
|
||||
"keyboard_shortcuts.favourite": "Favoritmarkera inlägg",
|
||||
"keyboard_shortcuts.favourites": "Öppna favoritlistan",
|
||||
|
@ -429,13 +435,13 @@
|
|||
"keyboard_shortcuts.reply": "Svara på inlägg",
|
||||
"keyboard_shortcuts.requests": "för att öppna Följförfrågningar",
|
||||
"keyboard_shortcuts.search": "för att fokusera sökfältet",
|
||||
"keyboard_shortcuts.spoilers": "visa/dölja CW-fält",
|
||||
"keyboard_shortcuts.start": "för att öppna \"Kom igång\"-kolumnen",
|
||||
"keyboard_shortcuts.toggle_hidden": "för att visa/gömma text bakom CW",
|
||||
"keyboard_shortcuts.toggle_sensitivity": "för att visa/gömma media",
|
||||
"keyboard_shortcuts.spoilers": "Visa/dölja CW-fält",
|
||||
"keyboard_shortcuts.start": "Öppna \"Kom igång\"-kolumnen",
|
||||
"keyboard_shortcuts.toggle_hidden": "Visa/gömma text bakom CW",
|
||||
"keyboard_shortcuts.toggle_sensitivity": "Visa/gömma media",
|
||||
"keyboard_shortcuts.toot": "Starta nytt inlägg",
|
||||
"keyboard_shortcuts.unfocus": "för att avfokusera skrivfält/sökfält",
|
||||
"keyboard_shortcuts.up": "för att flytta uppåt i listan",
|
||||
"keyboard_shortcuts.unfocus": "Avfokusera skrivfält/sökfält",
|
||||
"keyboard_shortcuts.up": "Flytta uppåt i listan",
|
||||
"lightbox.close": "Stäng",
|
||||
"lightbox.next": "Nästa",
|
||||
"lightbox.previous": "Tidigare",
|
||||
|
@ -508,6 +514,7 @@
|
|||
"notification.admin.report_statuses_other": "{name} rapporterade {target}",
|
||||
"notification.admin.sign_up": "{name} registrerade sig",
|
||||
"notification.admin.sign_up.name_and_others": "{name} och {count, plural, one {# en annan} other {# andra}} har registrerat sig",
|
||||
"notification.annual_report.view": "Visa #Wrapstodon",
|
||||
"notification.favourite": "{name} favoritmarkerade ditt inlägg",
|
||||
"notification.favourite.name_and_others_with_link": "{name} och <a>{count, plural, one {# annan} other {# andra}}</a> har favoritmarkerat ditt inlägg",
|
||||
"notification.follow": "{name} följer dig",
|
||||
|
@ -617,11 +624,11 @@
|
|||
"onboarding.action.back": "Ta mig tillbaka",
|
||||
"onboarding.actions.back": "Ta mig tillbaka",
|
||||
"onboarding.actions.go_to_explore": "See what's trending",
|
||||
"onboarding.actions.go_to_home": "Go to your home feed",
|
||||
"onboarding.actions.go_to_home": "Ta mig till mitt hemflöde",
|
||||
"onboarding.compose.template": "Hallå #Mastodon!",
|
||||
"onboarding.follows.empty": "Tyvärr kan inga resultat visas just nu. Du kan prova att använda sökfunktionen eller utforska sidan för att hitta personer att följa, eller försök igen senare.",
|
||||
"onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
|
||||
"onboarding.follows.title": "Popular on Mastodon",
|
||||
"onboarding.follows.lead": "Ditt hemflöde är det primära sättet att uppleva Mastodon. Ju fler människor du följer, desto mer aktiv och intressant blir det. För att komma igång, är här några förslag:",
|
||||
"onboarding.follows.title": "Anpassa ditt hemflöde",
|
||||
"onboarding.profile.discoverable": "Gör min profil upptäckbar",
|
||||
"onboarding.profile.discoverable_hint": "När du väljer att vara upptäckbar på Mastodon kan dina inlägg visas i sök- och trendresultat, och din profil kan föreslås för personer med liknande intressen som du.",
|
||||
"onboarding.profile.display_name": "Visningsnamn",
|
||||
|
@ -642,7 +649,7 @@
|
|||
"onboarding.start.title": "Du klarade det!",
|
||||
"onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
|
||||
"onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
|
||||
"onboarding.steps.publish_status.body": "Say hello to the world.",
|
||||
"onboarding.steps.publish_status.body": "Säg hej till världen med text, foton, videor eller omröstningar {emoji}",
|
||||
"onboarding.steps.publish_status.title": "Gör ditt första inlägg",
|
||||
"onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
|
||||
"onboarding.steps.setup_profile.title": "Customize your profile",
|
||||
|
@ -733,8 +740,8 @@
|
|||
"report.thanks.take_action_actionable": "Medan vi granskar detta kan du vidta åtgärder mot {name}:",
|
||||
"report.thanks.title": "Vill du inte se det här?",
|
||||
"report.thanks.title_actionable": "Tack för att du rapporterar, vi kommer att titta på detta.",
|
||||
"report.unfollow": "Sluta följ @{username}",
|
||||
"report.unfollow_explanation": "Du följer detta konto. Avfölj hen för att inte se hens inlägg i ditt hemflöde.",
|
||||
"report.unfollow": "Sluta följ @{name}",
|
||||
"report.unfollow_explanation": "Du följer detta konto. Avfölj det för att inte se dess inlägg i ditt hemflöde.",
|
||||
"report_notification.attached_statuses": "bifogade {count, plural, one {{count} inlägg} other {{count} inlägg}}",
|
||||
"report_notification.categories.legal": "Rättsligt",
|
||||
"report_notification.categories.legal_sentence": "olagligt innehåll",
|
||||
|
@ -829,7 +836,7 @@
|
|||
"status.show_less_all": "Visa mindre för alla",
|
||||
"status.show_more_all": "Visa mer för alla",
|
||||
"status.show_original": "Visa original",
|
||||
"status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}",
|
||||
"status.title.with_attachments": "{user} lade upp {attachmentCount, plural, one {en bilaga} other {{attachmentCount} bilagor}}",
|
||||
"status.translate": "Översätt",
|
||||
"status.translated_from_with": "Översatt från {lang} med {provider}",
|
||||
"status.uncached_media_warning": "Förhandsvisning inte tillgänglig",
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
"account.joined_short": "เข้าร่วมเมื่อ",
|
||||
"account.languages": "เปลี่ยนภาษาที่บอกรับ",
|
||||
"account.link_verified_on": "ตรวจสอบความเป็นเจ้าของของลิงก์นี้เมื่อ {date}",
|
||||
"account.locked_info": "สถานะความเป็นส่วนตัวของบัญชีนี้ถูกตั้งค่าเป็นล็อค เจ้าของตรวจสอบด้วยตนเองว่าใครสามารถติดตามพวกเขาได้",
|
||||
"account.locked_info": "มีการตั้งสถานะความเป็นส่วนตัวของบัญชีนี้เป็นล็อคอยู่ เจ้าของตรวจทานผู้ที่สามารถติดตามเขาด้วยตนเอง",
|
||||
"account.media": "สื่อ",
|
||||
"account.mention": "กล่าวถึง @{name}",
|
||||
"account.moved_to": "{name} ได้ระบุว่าบัญชีใหม่ของเขาในตอนนี้คือ:",
|
||||
|
@ -97,7 +97,7 @@
|
|||
"block_modal.they_will_know": "เขาสามารถเห็นว่ามีการปิดกั้นเขา",
|
||||
"block_modal.title": "ปิดกั้นผู้ใช้?",
|
||||
"block_modal.you_wont_see_mentions": "คุณจะไม่เห็นโพสต์ที่กล่าวถึงเขา",
|
||||
"boost_modal.combo": "คุณสามารถกด {combo} เพื่อข้ามสิ่งนี้ในครั้งถัดไป",
|
||||
"boost_modal.combo": "คุณสามารถกดปุ่ม {combo} เพื่อข้ามสิ่งนี้ในครั้งถัดไป",
|
||||
"boost_modal.reblog": "ดันโพสต์?",
|
||||
"boost_modal.undo_reblog": "เลิกดันโพสต์?",
|
||||
"bundle_column_error.copy_stacktrace": "คัดลอกรายงานข้อผิดพลาด",
|
||||
|
@ -386,6 +386,7 @@
|
|||
"interaction_modal.description.follow": "ด้วยบัญชีใน Mastodon คุณสามารถติดตาม {name} เพื่อรับโพสต์ของเขาในฟีดหน้าแรกของคุณ",
|
||||
"interaction_modal.description.reblog": "ด้วยบัญชีใน Mastodon คุณสามารถดันโพสต์นี้เพื่อแชร์โพสต์กับผู้ติดตามของคุณเอง",
|
||||
"interaction_modal.description.reply": "ด้วยบัญชีใน Mastodon คุณสามารถตอบกลับโพสต์นี้",
|
||||
"interaction_modal.description.vote": "ด้วยบัญชีใน Mastodon คุณสามารถลงคะแนนในการสำรวจความคิดเห็นนี้",
|
||||
"interaction_modal.login.action": "นำฉันกลับบ้าน",
|
||||
"interaction_modal.login.prompt": "โดเมนของเซิร์ฟเวอร์บ้านของคุณ เช่น mastodon.social",
|
||||
"interaction_modal.no_account_yet": "ไม่ได้อยู่ใน Mastodon?",
|
||||
|
@ -397,6 +398,7 @@
|
|||
"interaction_modal.title.follow": "ติดตาม {name}",
|
||||
"interaction_modal.title.reblog": "ดันโพสต์ของ {name}",
|
||||
"interaction_modal.title.reply": "ตอบกลับโพสต์ของ {name}",
|
||||
"interaction_modal.title.vote": "ลงคะแนนในการสำรวจความคิดเห็นของ {name}",
|
||||
"intervals.full.days": "{number, plural, other {# วัน}}",
|
||||
"intervals.full.hours": "{number, plural, other {# ชั่วโมง}}",
|
||||
"intervals.full.minutes": "{number, plural, other {# นาที}}",
|
||||
|
@ -855,11 +857,11 @@
|
|||
"upload_error.poll": "ไม่อนุญาตการอัปโหลดไฟล์โดยมีการสำรวจความคิดเห็น",
|
||||
"upload_form.audio_description": "อธิบายสำหรับผู้ที่สูญเสียการได้ยิน",
|
||||
"upload_form.description": "อธิบายสำหรับผู้คนที่พิการทางการมองเห็นหรือมีสายตาเลือนราง",
|
||||
"upload_form.drag_and_drop.instructions": "หากต้องการเลือกไฟล์สื่อ ให้กดปุ่มที Space หรือ Enter บนแป้นพิมพ์ เมื่อเลือกไฟล์ได้แล้ว ก็สามารถใช้ปุ่มลูกศรเพื่อเลื่อนไฟล์ไปในทิศทางที่ต้องการได้ เมื่อต้องการวางไฟล์ในตำแหน่งใหม่ ให้กดปุ่ม Space หรือ Enter ดูอีกครั้ง หรือหากต้องการยกเลิกการเลือก ให้กดปุ่ม Esc ได้",
|
||||
"upload_form.drag_and_drop.on_drag_cancel": "การลากไฟล์นั้นหยุดชะงัก ไฟล์ที่กำลังแนบมาถูกยกเลิก {item} ได้ถูกลบทิ้งแล้ว",
|
||||
"upload_form.drag_and_drop.on_drag_end": "ไฟล์แนบ {item} ได้ถูกยกเลิกแล้ว",
|
||||
"upload_form.drag_and_drop.on_drag_over": "ไฟล์แนบ {item} ได้ถูกย้ายไปแล้ว",
|
||||
"upload_form.drag_and_drop.on_drag_start": "ได้รับไฟล์แนบเรียบร้อยแล้ว {item}.",
|
||||
"upload_form.drag_and_drop.instructions": "เพื่อหยิบไฟล์แนบสื่อ กดปุ่มเว้นวรรคหรือขึ้นบรรทัดใหม่ ขณะลาก ใช้ปุ่มลูกศรเพื่อย้ายไฟล์แนบสื่อในทิศทางใดก็ตามที่กำหนด กดปุ่มเว้นวรรคหรือขึ้นบรรทัดใหม่อีกครั้งเพื่อปล่อยไฟล์แนบสื่อในตำแหน่งใหม่ หรือกดปุ่ม Escape เพื่อยกเลิก",
|
||||
"upload_form.drag_and_drop.on_drag_cancel": "ยกเลิกการลากแล้ว ปล่อยไฟล์แนบสื่อ {item} แล้ว",
|
||||
"upload_form.drag_and_drop.on_drag_end": "ปล่อยไฟล์แนบสื่อ {item} แล้ว",
|
||||
"upload_form.drag_and_drop.on_drag_over": "ย้ายไฟล์แนบสื่อ {item} แล้ว",
|
||||
"upload_form.drag_and_drop.on_drag_start": "หยิบไฟล์แนบสื่อ {item} แล้ว",
|
||||
"upload_form.edit": "แก้ไข",
|
||||
"upload_form.thumbnail": "เปลี่ยนภาพขนาดย่อ",
|
||||
"upload_form.video_description": "อธิบายสำหรับผู้คนที่พิการทางการได้ยิน ได้ยินไม่ชัด พิการทางการมองเห็น หรือมีสายตาเลือนราง",
|
||||
|
|
|
@ -87,6 +87,24 @@
|
|||
"alert.unexpected.title": "哎呀!",
|
||||
"alt_text_badge.title": "替代文本",
|
||||
"announcement.announcement": "公告",
|
||||
"annual_report.summary.archetype.booster": "潮流捕手",
|
||||
"annual_report.summary.archetype.lurker": "吃瓜群众",
|
||||
"annual_report.summary.archetype.oracle": "预言家",
|
||||
"annual_report.summary.archetype.pollster": "投票狂魔",
|
||||
"annual_report.summary.archetype.replier": "评论区原住民",
|
||||
"annual_report.summary.followers.followers": "关注者",
|
||||
"annual_report.summary.followers.total": "{count} 人",
|
||||
"annual_report.summary.here_it_is": "你的 {year} 年度回顾在此:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "最受欢迎嘟嘟",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "传播最广嘟嘟",
|
||||
"annual_report.summary.highlighted_post.by_replies": "最热闹嘟嘟",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name} 的",
|
||||
"annual_report.summary.most_used_app.most_used_app": "最常用的应用",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "最常用的话题",
|
||||
"annual_report.summary.new_posts.new_posts": "新发嘟",
|
||||
"annual_report.summary.percentile.text": "<topLabel>这使你跻身 Mastodon 用户的前</topLabel><percentage></percentage><bottomLabel></bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "我们打死也不会告诉Bernie。",
|
||||
"annual_report.summary.thanks": "感谢你这一年与 Mastodon 一路同行!",
|
||||
"attachments_list.unprocessed": "(未处理)",
|
||||
"audio.hide": "隐藏音频",
|
||||
"block_modal.remote_users_caveat": "我们将要求服务器 {domain} 尊重您的决定。然而,我们无法保证对方一定遵从,因为某些服务器可能会以不同的方案处理屏蔽操作。公开嘟文仍然可能对未登录的用户可见。",
|
||||
|
@ -508,6 +526,8 @@
|
|||
"notification.admin.report_statuses_other": "{name} 举报了 {target}",
|
||||
"notification.admin.sign_up": "{name} 注册了",
|
||||
"notification.admin.sign_up.name_and_others": "{name} 和 {count, plural, other {另外 # 人}}注册了",
|
||||
"notification.annual_report.message": "你的 {year} #Wrapstodon 年度回顾来啦!快来看看这一年你在 Mastodon 上的精彩瞬间!",
|
||||
"notification.annual_report.view": "查看 #Wrapstodon",
|
||||
"notification.favourite": "{name} 喜欢了你的嘟文",
|
||||
"notification.favourite.name_and_others_with_link": "{name} 和 <a>{count, plural, other {另外 # 人}}</a> 喜欢了你的嘟文",
|
||||
"notification.follow": "{name} 开始关注你",
|
||||
|
|
|
@ -87,6 +87,23 @@
|
|||
"alert.unexpected.title": "哎呀!",
|
||||
"alt_text_badge.title": "ALT 說明文字",
|
||||
"announcement.announcement": "公告",
|
||||
"annual_report.summary.archetype.booster": "酷炫的獵人",
|
||||
"annual_report.summary.archetype.lurker": "潛伏者",
|
||||
"annual_report.summary.archetype.oracle": "先知",
|
||||
"annual_report.summary.archetype.pollster": "民調人員",
|
||||
"annual_report.summary.archetype.replier": "社交菁英",
|
||||
"annual_report.summary.followers.followers": "跟隨者",
|
||||
"annual_report.summary.followers.total": "總共 {count}",
|
||||
"annual_report.summary.here_it_is": "以下是您的{year}年度回顧:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "最愛的嘟文",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "最多轉嘟的嘟文",
|
||||
"annual_report.summary.highlighted_post.by_replies": "最多回覆的嘟文",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name} 的",
|
||||
"annual_report.summary.most_used_app.most_used_app": "最常使用的應用程式",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "最常使用的主題標籤",
|
||||
"annual_report.summary.new_posts.new_posts": "新嘟文",
|
||||
"annual_report.summary.percentile.text": "<topLabel>這讓您成為前</topLabel><percentage></percentage><bottomLabel>Mastodon 的使用者。</bottomLabel>",
|
||||
"annual_report.summary.thanks": "感謝您成為 Mastodon 的一員!",
|
||||
"attachments_list.unprocessed": "(未經處理)",
|
||||
"audio.hide": "隱藏音訊",
|
||||
"block_modal.remote_users_caveat": "我們會要求 {domain} 伺服器尊重您的決定。然而,我們無法保證所有伺服器皆會遵守,某些伺服器可能以不同方式處理封鎖。未登入之使用者仍可能看見您的公開嘟文。",
|
||||
|
|
44
app/javascript/mastodon/models/annual_report.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
export interface Percentiles {
|
||||
followers: number;
|
||||
statuses: number;
|
||||
}
|
||||
|
||||
export interface NameAndCount {
|
||||
name: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface TimeSeriesMonth {
|
||||
month: number;
|
||||
statuses: number;
|
||||
following: number;
|
||||
followers: number;
|
||||
}
|
||||
|
||||
export interface TopStatuses {
|
||||
by_reblogs: number;
|
||||
by_favourites: number;
|
||||
by_replies: number;
|
||||
}
|
||||
|
||||
export type Archetype =
|
||||
| 'lurker'
|
||||
| 'booster'
|
||||
| 'pollster'
|
||||
| 'replier'
|
||||
| 'oracle';
|
||||
|
||||
interface AnnualReportV1 {
|
||||
most_used_apps: NameAndCount[];
|
||||
percentiles: Percentiles;
|
||||
top_hashtags: NameAndCount[];
|
||||
top_statuses: TopStatuses;
|
||||
time_series: TimeSeriesMonth[];
|
||||
archetype: Archetype;
|
||||
}
|
||||
|
||||
export interface AnnualReport {
|
||||
year: number;
|
||||
schema_version: number;
|
||||
data: AnnualReportV1;
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import type {
|
||||
ApiAccountRelationshipSeveranceEventJSON,
|
||||
ApiAccountWarningJSON,
|
||||
ApiAnnualReportEventJSON,
|
||||
BaseNotificationGroupJSON,
|
||||
ApiNotificationGroupJSON,
|
||||
ApiNotificationJSON,
|
||||
|
@ -91,6 +92,12 @@ export interface NotificationGroupSeveredRelationships
|
|||
event: AccountRelationshipSeveranceEvent;
|
||||
}
|
||||
|
||||
type AnnualReportEvent = ApiAnnualReportEventJSON;
|
||||
export interface NotificationGroupAnnualReport
|
||||
extends BaseNotification<'annual_report'> {
|
||||
annualReport: AnnualReportEvent;
|
||||
}
|
||||
|
||||
interface Report extends Omit<ApiReportJSON, 'target_account'> {
|
||||
targetAccountId: string;
|
||||
}
|
||||
|
@ -115,7 +122,8 @@ export type NotificationGroup =
|
|||
| NotificationGroupModerationWarning
|
||||
| NotificationGroupSeveredRelationships
|
||||
| NotificationGroupAdminSignUp
|
||||
| NotificationGroupAdminReport;
|
||||
| NotificationGroupAdminReport
|
||||
| NotificationGroupAnnualReport;
|
||||
|
||||
function createReportFromJSON(reportJSON: ApiReportJSON): Report {
|
||||
const { target_account, ...report } = reportJSON;
|
||||
|
@ -155,6 +163,12 @@ function createEmojiReactionGroupsFromJSON(
|
|||
];
|
||||
}
|
||||
|
||||
function createAnnualReportEventFromJSON(
|
||||
eventJson: ApiAnnualReportEventJSON,
|
||||
): AnnualReportEvent {
|
||||
return eventJson;
|
||||
}
|
||||
|
||||
export function createNotificationGroupFromJSON(
|
||||
groupJson: ApiNotificationGroupJSON,
|
||||
): NotificationGroup {
|
||||
|
@ -213,7 +227,6 @@ export function createNotificationGroupFromJSON(
|
|||
event: createAccountRelationshipSeveranceEventFromJSON(group.event),
|
||||
sampleAccountIds,
|
||||
};
|
||||
|
||||
case 'moderation_warning': {
|
||||
const { moderation_warning, ...groupWithoutModerationWarning } = group;
|
||||
return {
|
||||
|
@ -222,6 +235,14 @@ export function createNotificationGroupFromJSON(
|
|||
sampleAccountIds,
|
||||
};
|
||||
}
|
||||
case 'annual_report': {
|
||||
const { annual_report, ...groupWithoutAnnualReport } = group;
|
||||
return {
|
||||
...groupWithoutAnnualReport,
|
||||
annualReport: createAnnualReportEventFromJSON(annual_report),
|
||||
sampleAccountIds,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return {
|
||||
sampleAccountIds,
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m80-80 200-560 360 360L80-80Zm502-378-42-42 224-224q32-32 77-32t77 32l24 24-42 42-24-24q-14-14-35-14t-35 14L582-458ZM422-618l-42-42 24-24q14-14 14-34t-14-34l-26-26 42-42 26 26q32 32 32 76t-32 76l-24 24Zm80 80-42-42 144-144q14-14 14-35t-14-35l-64-64 42-42 64 64q32 32 32 77t-32 77L502-538Zm160 160-42-42 64-64q32-32 77-32t77 32l64 64-42 42-64-64q-14-14-35-14t-35 14l-64 64Z"/></svg>
|
After Width: | Height: | Size: 478 B |
1
app/javascript/material-icons/400-24px/celebration.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m80-80 200-560 360 360L80-80Zm132-132 282-100-182-182-100 282Zm370-246-42-42 224-224q32-32 77-32t77 32l24 24-42 42-24-24q-14-14-35-14t-35 14L582-458ZM422-618l-42-42 24-24q14-14 14-34t-14-34l-26-26 42-42 26 26q32 32 32 76t-32 76l-24 24Zm80 80-42-42 144-144q14-14 14-35t-14-35l-64-64 42-42 64 64q32 32 32 77t-32 77L502-538Zm160 160-42-42 64-64q32-32 77-32t77 32l64 64-42 42-64-64q-14-14-35-14t-35 14l-64 64ZM212-212Z"/></svg>
|
After Width: | Height: | Size: 520 B |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m260-260 300-140 140-300-300 140-140 300Zm220-180q-17 0-28.5-11.5T440-480q0-17 11.5-28.5T480-520q17 0 28.5 11.5T520-480q0 17-11.5 28.5T480-440Zm0 360q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m300-300 280-80 80-280-280 80-80 280Zm180-120q-25 0-42.5-17.5T420-480q0-25 17.5-42.5T480-540q25 0 42.5 17.5T540-480q0 25-17.5 42.5T480-420Zm0 340q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Z"/></svg>
|
Before Width: | Height: | Size: 437 B After Width: | Height: | Size: 433 B |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m260-260 300-140 140-300-300 140-140 300Zm220-180q-17 0-28.5-11.5T440-480q0-17 11.5-28.5T480-520q17 0 28.5 11.5T520-480q0 17-11.5 28.5T480-440Zm0 360q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m300-300 280-80 80-280-280 80-80 280Zm180-120q-25 0-42.5-17.5T420-480q0-25 17.5-42.5T480-540q25 0 42.5 17.5T540-480q0 25-17.5 42.5T480-420Zm0 340q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q133 0 226.5-93.5T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 133 93.5 226.5T480-160Zm0-320Z"/></svg>
|
Before Width: | Height: | Size: 533 B After Width: | Height: | Size: 547 B |
|
@ -15,6 +15,7 @@
|
|||
@import 'mastodon/polls';
|
||||
@import 'mastodon/modal';
|
||||
@import 'mastodon/emoji_picker';
|
||||
@import 'mastodon/annual_reports';
|
||||
@import 'mastodon/about';
|
||||
@import 'mastodon/tables';
|
||||
@import 'mastodon/admin';
|
||||
|
|
335
app/javascript/styles/mastodon/annual_reports.scss
Normal file
|
@ -0,0 +1,335 @@
|
|||
:root {
|
||||
--indigo-1: #17063b;
|
||||
--indigo-2: #2f0c7a;
|
||||
--indigo-3: #562cfc;
|
||||
--indigo-5: #858afa;
|
||||
--indigo-6: #cccfff;
|
||||
--lime: #baff3b;
|
||||
--goldenrod-2: #ffc954;
|
||||
}
|
||||
|
||||
.annual-report {
|
||||
flex: 0 0 auto;
|
||||
background: var(--indigo-1);
|
||||
padding: 24px;
|
||||
|
||||
&__header {
|
||||
margin-bottom: 16px;
|
||||
|
||||
h1 {
|
||||
font-size: 25px;
|
||||
font-weight: 600;
|
||||
line-height: 30px;
|
||||
color: var(--lime);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
color: var(--indigo-6);
|
||||
}
|
||||
}
|
||||
|
||||
&__bento {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
|
||||
grid-template-rows: minmax(0, auto) minmax(0, 1fr) minmax(0, auto) minmax(
|
||||
0,
|
||||
auto
|
||||
);
|
||||
|
||||
&__box {
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
background: var(--indigo-2);
|
||||
color: var(--indigo-5);
|
||||
}
|
||||
}
|
||||
|
||||
&__summary {
|
||||
&__most-boosted-post {
|
||||
grid-column: span 2;
|
||||
grid-row: span 2;
|
||||
padding: 0;
|
||||
|
||||
.status__content,
|
||||
.content-warning {
|
||||
color: var(--indigo-6);
|
||||
}
|
||||
|
||||
.detailed-status {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.content-warning {
|
||||
border: 0;
|
||||
background: var(--indigo-1);
|
||||
|
||||
.link-button {
|
||||
color: var(--indigo-5);
|
||||
}
|
||||
}
|
||||
|
||||
.detailed-status__meta__line {
|
||||
border-bottom-color: var(--indigo-3);
|
||||
}
|
||||
|
||||
.detailed-status__meta {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.detailed-status__meta,
|
||||
.poll__footer,
|
||||
.poll__link,
|
||||
.detailed-status .logo,
|
||||
.detailed-status__display-name {
|
||||
color: var(--indigo-5);
|
||||
}
|
||||
|
||||
.detailed-status__meta .animated-number,
|
||||
.detailed-status__display-name strong {
|
||||
color: var(--indigo-6);
|
||||
}
|
||||
|
||||
.poll__chart {
|
||||
background-color: var(--indigo-3);
|
||||
|
||||
&.leading {
|
||||
background-color: var(--goldenrod-2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__followers {
|
||||
grid-column: span 1;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding-block-start: 24px;
|
||||
padding-block-end: 24px;
|
||||
|
||||
--sparkline-gradient-top: rgba(86, 44, 252, 50%);
|
||||
--sparkline-gradient-bottom: rgba(86, 44, 252, 0%);
|
||||
|
||||
&__foreground {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&__number {
|
||||
font-size: 31px;
|
||||
font-weight: 600;
|
||||
line-height: 37px;
|
||||
color: var(--lime);
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 17px;
|
||||
color: var(--indigo-6);
|
||||
}
|
||||
|
||||
&__footnote {
|
||||
display: block;
|
||||
font-weight: 400;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
inset-inline-end: 0;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
height: 70%;
|
||||
width: auto;
|
||||
|
||||
path:first-child {
|
||||
fill: url('#gradient') !important;
|
||||
fill-opacity: 1 !important;
|
||||
}
|
||||
|
||||
path:last-child {
|
||||
stroke: var(--indigo-3) !important;
|
||||
fill: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__archetype {
|
||||
grid-column: span 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
gap: 8px;
|
||||
padding: 0;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
padding: 16px;
|
||||
padding-bottom: 8px;
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
font-weight: 600;
|
||||
color: var(--lime);
|
||||
}
|
||||
}
|
||||
|
||||
&__most-used-app {
|
||||
grid-column: span 1;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&__label {
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
font-weight: 600;
|
||||
color: var(--indigo-6);
|
||||
}
|
||||
|
||||
&__icon {
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
font-weight: 600;
|
||||
color: var(--goldenrod-2);
|
||||
}
|
||||
}
|
||||
|
||||
&__percentile {
|
||||
grid-row: span 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
text-align: center;
|
||||
text-wrap: balance;
|
||||
padding: 16px 8px;
|
||||
|
||||
&__label {
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
}
|
||||
|
||||
&__number {
|
||||
font-size: 61px;
|
||||
font-weight: 600;
|
||||
line-height: 73px;
|
||||
color: var(--goldenrod-2);
|
||||
}
|
||||
|
||||
&__footnote {
|
||||
font-size: 11px;
|
||||
line-height: 14px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
&__new-posts {
|
||||
grid-column: span 2;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&__label {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
color: var(--indigo-6);
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__number {
|
||||
font-size: 76px;
|
||||
font-weight: 600;
|
||||
line-height: 91px;
|
||||
color: var(--goldenrod-2);
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
inset-inline-start: -7px;
|
||||
top: -4px;
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__most-used-hashtag {
|
||||
grid-column: span 2;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
|
||||
&__hashtag {
|
||||
font-size: 42px;
|
||||
font-weight: 600;
|
||||
line-height: 58px;
|
||||
color: var(--indigo-6);
|
||||
margin-inline-start: -100%;
|
||||
margin-inline-end: -100%;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 17px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.annual-report-modal {
|
||||
max-width: 480px;
|
||||
background: var(--indigo-1);
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
|
||||
.loading-indicator .circular-progress {
|
||||
color: var(--lime);
|
||||
}
|
||||
|
||||
@media screen and (max-width: $no-columns-breakpoint) {
|
||||
border-bottom: 0;
|
||||
border-radius: 16px 16px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.notification-group--annual-report {
|
||||
.notification-group__icon {
|
||||
color: var(--lime);
|
||||
}
|
||||
|
||||
.notification-group__main .link-button {
|
||||
font-weight: 500;
|
||||
color: var(--lime);
|
||||
}
|
||||
}
|
|
@ -1706,7 +1706,8 @@ body > [data-popper-placement] {
|
|||
|
||||
.status__wrapper-direct,
|
||||
.notification-ungrouped--direct,
|
||||
.notification-group--direct {
|
||||
.notification-group--direct,
|
||||
.notification-group--annual-report {
|
||||
background: rgba($ui-highlight-color, 0.05);
|
||||
|
||||
&:focus {
|
||||
|
@ -6066,7 +6067,8 @@ a.status-card {
|
|||
inset-inline-start: 0;
|
||||
inset-inline-end: 0;
|
||||
bottom: 0;
|
||||
background: rgba($base-overlay-background, 0.7);
|
||||
opacity: 0.9;
|
||||
background: $base-overlay-background;
|
||||
transition: background 0.5s;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,11 +17,21 @@ class AnnualReport
|
|||
|
||||
SCHEMA = 1
|
||||
|
||||
def self.table_name_prefix
|
||||
'annual_report_'
|
||||
end
|
||||
|
||||
def initialize(account, year)
|
||||
@account = account
|
||||
@year = year
|
||||
end
|
||||
|
||||
def self.prepare(year)
|
||||
SOURCES.each do |klass|
|
||||
klass.prepare(year)
|
||||
end
|
||||
end
|
||||
|
||||
def generate
|
||||
return if GeneratedAnnualReport.exists?(account: @account, year: @year)
|
||||
|
||||
|
|
|
@ -1,62 +1,37 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AnnualReport::Percentiles < AnnualReport::Source
|
||||
def self.prepare(year)
|
||||
AnnualReport::StatusesPerAccountCount.connection.exec_query(<<~SQL.squish, nil, [year, Mastodon::Snowflake.id_at(DateTime.new(year).beginning_of_year), Mastodon::Snowflake.id_at(DateTime.new(year).end_of_year)])
|
||||
INSERT INTO annual_report_statuses_per_account_counts (year, account_id, statuses_count)
|
||||
SELECT $1, account_id, count(*)
|
||||
FROM statuses
|
||||
WHERE id BETWEEN $2 AND $3
|
||||
AND (local OR uri IS NULL)
|
||||
GROUP BY account_id
|
||||
ON CONFLICT (year, account_id) DO NOTHING
|
||||
SQL
|
||||
end
|
||||
|
||||
def generate
|
||||
{
|
||||
percentiles: {
|
||||
followers: (total_with_fewer_followers / (total_with_any_followers + 1.0)) * 100,
|
||||
statuses: (total_with_fewer_statuses / (total_with_any_statuses + 1.0)) * 100,
|
||||
statuses: 100.0 - ((total_with_fewer_statuses / (total_with_any_statuses + 1.0)) * 100),
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def followers_gained
|
||||
@followers_gained ||= @account.passive_relationships.where("date_part('year', follows.created_at) = ?", @year).count
|
||||
end
|
||||
|
||||
def statuses_created
|
||||
@statuses_created ||= report_statuses.count
|
||||
end
|
||||
|
||||
def total_with_fewer_followers
|
||||
@total_with_fewer_followers ||= Follow.find_by_sql([<<~SQL.squish, { year: @year, comparison: followers_gained }]).first.total
|
||||
WITH tmp0 AS (
|
||||
SELECT follows.target_account_id
|
||||
FROM follows
|
||||
INNER JOIN accounts ON accounts.id = follows.target_account_id
|
||||
WHERE date_part('year', follows.created_at) = :year
|
||||
AND accounts.domain IS NULL
|
||||
GROUP BY follows.target_account_id
|
||||
HAVING COUNT(*) < :comparison
|
||||
)
|
||||
SELECT count(*) AS total
|
||||
FROM tmp0
|
||||
SQL
|
||||
end
|
||||
|
||||
def total_with_fewer_statuses
|
||||
@total_with_fewer_statuses ||= Status.find_by_sql([<<~SQL.squish, { comparison: statuses_created, min_id: year_as_snowflake_range.first, max_id: year_as_snowflake_range.last }]).first.total
|
||||
WITH tmp0 AS (
|
||||
SELECT statuses.account_id
|
||||
FROM statuses
|
||||
INNER JOIN accounts ON accounts.id = statuses.account_id
|
||||
WHERE statuses.id BETWEEN :min_id AND :max_id
|
||||
AND accounts.domain IS NULL
|
||||
GROUP BY statuses.account_id
|
||||
HAVING count(*) < :comparison
|
||||
)
|
||||
SELECT count(*) AS total
|
||||
FROM tmp0
|
||||
SQL
|
||||
end
|
||||
|
||||
def total_with_any_followers
|
||||
@total_with_any_followers ||= Follow.where("date_part('year', follows.created_at) = ?", @year).joins(:target_account).merge(Account.local).count('distinct follows.target_account_id')
|
||||
@total_with_fewer_statuses ||= AnnualReport::StatusesPerAccountCount.where(year: year).where(statuses_count: ...statuses_created).count
|
||||
end
|
||||
|
||||
def total_with_any_statuses
|
||||
@total_with_any_statuses ||= Status.where(id: year_as_snowflake_range).joins(:account).merge(Account.local).count('distinct statuses.account_id')
|
||||
@total_with_any_statuses ||= AnnualReport::StatusesPerAccountCount.where(year: year).count
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,6 +8,14 @@ class AnnualReport::Source
|
|||
@year = year
|
||||
end
|
||||
|
||||
def self.prepare(_year)
|
||||
# Use this method if any pre-calculations must be made before individual annual reports are generated
|
||||
end
|
||||
|
||||
def generate
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def report_statuses
|
||||
|
|
15
app/models/annual_report/statuses_per_account_count.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: annual_report_statuses_per_account_counts
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# year :integer not null
|
||||
# account_id :bigint(8) not null
|
||||
# statuses_count :bigint(8) not null
|
||||
#
|
||||
|
||||
class AnnualReport::StatusesPerAccountCount < ApplicationRecord
|
||||
# This table facilitates percentile calculations
|
||||
end
|
|
@ -44,8 +44,16 @@ class FeaturedTag < ApplicationRecord
|
|||
update(statuses_count: statuses_count + 1, last_status_at: timestamp)
|
||||
end
|
||||
|
||||
def decrement(deleted_status_id)
|
||||
update(statuses_count: [0, statuses_count - 1].max, last_status_at: visible_tagged_account_statuses.where.not(id: deleted_status_id).pick(:created_at))
|
||||
def decrement(deleted_status)
|
||||
if statuses_count <= 1
|
||||
update(statuses_count: 0, last_status_at: nil)
|
||||
elsif last_status_at > deleted_status.created_at
|
||||
update(statuses_count: statuses_count - 1)
|
||||
else
|
||||
# Fetching the latest status creation time can be expensive, so only perform it
|
||||
# if we know we are deleting the latest status using this tag
|
||||
update(statuses_count: statuses_count - 1, last_status_at: visible_tagged_account_statuses.where(id: ...deleted_status.id).pick(:created_at))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -83,6 +83,9 @@ class Notification < ApplicationRecord
|
|||
moderation_warning: {
|
||||
filterable: false,
|
||||
}.freeze,
|
||||
annual_report: {
|
||||
filterable: false,
|
||||
}.freeze,
|
||||
'admin.sign_up': {
|
||||
filterable: false,
|
||||
}.freeze,
|
||||
|
@ -124,6 +127,7 @@ class Notification < ApplicationRecord
|
|||
belongs_to :report, inverse_of: false
|
||||
belongs_to :account_warning, inverse_of: false
|
||||
belongs_to :account_relationship_severance_event, inverse_of: false
|
||||
belongs_to :generated_annual_report, inverse_of: false
|
||||
end
|
||||
|
||||
validates :type, inclusion: { in: TYPES }
|
||||
|
@ -344,7 +348,7 @@ class Notification < ApplicationRecord
|
|||
self.from_account_id = activity&.status&.account_id
|
||||
when 'Account'
|
||||
self.from_account_id = activity&.id
|
||||
when 'AccountRelationshipSeveranceEvent', 'AccountWarning'
|
||||
when 'AccountRelationshipSeveranceEvent', 'AccountWarning', 'GeneratedAnnualReport'
|
||||
# These do not really have an originating account, but this is mandatory
|
||||
# in the data model, and the recipient's account will by definition
|
||||
# always exist
|
||||
|
|
|
@ -67,6 +67,7 @@ class NotificationGroup < ActiveModelSerializers::Model
|
|||
:report,
|
||||
:account_relationship_severance_event,
|
||||
:account_warning,
|
||||
:generated_annual_report,
|
||||
to: :notification, prefix: false
|
||||
|
||||
def self.convert_emoji_reaction_pair(activity_ids)
|
||||
|
|
9
app/serializers/rest/annual_report_event_serializer.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::AnnualReportEventSerializer < ActiveModel::Serializer
|
||||
attributes :year
|
||||
|
||||
def year
|
||||
object.year.to_s
|
||||
end
|
||||
end
|
|
@ -14,6 +14,7 @@ class REST::NotificationGroupSerializer < ActiveModel::Serializer
|
|||
belongs_to :account_relationship_severance_event, key: :event, if: :relationship_severance_event?, serializer: REST::AccountRelationshipSeveranceEventSerializer
|
||||
belongs_to :account_warning, key: :moderation_warning, if: :moderation_warning_event?, serializer: REST::AccountWarningSerializer
|
||||
has_one :list, if: :list_status_type?, serializer: REST::ListSerializer
|
||||
belongs_to :generated_annual_report, key: :annual_report, if: :annual_report_event?, serializer: REST::AnnualReportEventSerializer
|
||||
|
||||
def sample_account_ids
|
||||
object.sample_accounts.pluck(:id).map(&:to_s)
|
||||
|
@ -47,6 +48,10 @@ class REST::NotificationGroupSerializer < ActiveModel::Serializer
|
|||
object.type == :moderation_warning
|
||||
end
|
||||
|
||||
def annual_report_event?
|
||||
object.type == :annual_report
|
||||
end
|
||||
|
||||
def page_min_id
|
||||
object.pagination_data[:min_id].to_s
|
||||
end
|
||||
|
|
|
@ -50,14 +50,6 @@ class REST::V1::InstanceSerializer < ActiveModel::Serializer
|
|||
{ streaming_api: Rails.configuration.x.streaming_api_base_url }
|
||||
end
|
||||
|
||||
def usage
|
||||
{
|
||||
users: {
|
||||
active_month: instance_presenter.active_user_count(4),
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
def configuration
|
||||
{
|
||||
accounts: {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
class NotifyService < BaseService
|
||||
include Redisable
|
||||
|
||||
# TODO: the severed_relationships type probably warrants email notifications
|
||||
# TODO: the severed_relationships and annual_report types probably warrants email notifications
|
||||
NON_EMAIL_TYPES = %i(
|
||||
admin.report
|
||||
admin.sign_up
|
||||
|
@ -15,6 +15,7 @@ class NotifyService < BaseService
|
|||
list_status
|
||||
moderation_warning
|
||||
severed_relationships
|
||||
annual_report
|
||||
).freeze
|
||||
|
||||
class BaseCondition
|
||||
|
@ -28,6 +29,7 @@ class NotifyService < BaseService
|
|||
poll
|
||||
update
|
||||
account_warning
|
||||
annual_report
|
||||
).freeze
|
||||
|
||||
def initialize(notification)
|
||||
|
@ -103,7 +105,7 @@ class NotifyService < BaseService
|
|||
class DropCondition < BaseCondition
|
||||
def drop?
|
||||
blocked = @recipient.unavailable?
|
||||
blocked ||= from_self? && %i(poll severed_relationships moderation_warning).exclude?(@notification.type)
|
||||
blocked ||= from_self? && %i(poll severed_relationships moderation_warning annual_report).exclude?(@notification.type)
|
||||
|
||||
return blocked if message? && from_staff?
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ class ProcessHashtagsService < BaseService
|
|||
|
||||
unless removed_tags.empty?
|
||||
@account.featured_tags.where(tag_id: removed_tags.map(&:id)).find_each do |featured_tag|
|
||||
featured_tag.decrement(@status.id)
|
||||
featured_tag.decrement(@status)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -146,7 +146,7 @@ class RemoveStatusService < BaseService
|
|||
|
||||
def remove_from_hashtags
|
||||
@account.featured_tags.where(tag_id: @status.tags.map(&:id)).find_each do |featured_tag|
|
||||
featured_tag.decrement(@status.id)
|
||||
featured_tag.decrement(@status)
|
||||
end
|
||||
|
||||
return unless %i(public public_unlisted login).include?(@visibility) || (@status.unlisted_visibility? && %i(public public_unlisted).include?(@searchability))
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# TODO: remove this file some time after 4.3.0
|
||||
|
||||
Rails.application.config.after_initialize do
|
||||
Rails.application.config.action_dispatch.cookies_rotations.tap do |cookies|
|
||||
authenticated_encrypted_cookie_salt = Rails.application.config.action_dispatch.authenticated_encrypted_cookie_salt
|
||||
signed_cookie_salt = Rails.application.config.action_dispatch.signed_cookie_salt
|
||||
|
||||
secret_key_base = Rails.application.secret_key_base
|
||||
|
||||
key_generator = ActiveSupport::KeyGenerator.new(
|
||||
secret_key_base, iterations: 1000, hash_digest_class: OpenSSL::Digest::SHA1
|
||||
)
|
||||
key_len = ActiveSupport::MessageEncryptor.key_len
|
||||
|
||||
old_encrypted_secret = key_generator.generate_key(authenticated_encrypted_cookie_salt, key_len)
|
||||
old_signed_secret = key_generator.generate_key(signed_cookie_salt)
|
||||
|
||||
cookies.rotate :encrypted, old_encrypted_secret
|
||||
cookies.rotate :signed, old_signed_secret
|
||||
end
|
||||
end
|
|
@ -1,7 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
HttpLog.configure do |config|
|
||||
config.logger = Rails.logger
|
||||
config.color = { color: :yellow }
|
||||
config.compact_log = true
|
||||
# Disable httplog in production unless log_level is `debug`
|
||||
if !Rails.env.production? || Rails.configuration.log_level == :debug
|
||||
require 'httplog'
|
||||
|
||||
HttpLog.configure do |config|
|
||||
config.logger = Rails.logger
|
||||
config.color = { color: :yellow }
|
||||
config.compact_log = true
|
||||
end
|
||||
end
|
||||
|
|
|
@ -60,6 +60,7 @@ ru:
|
|||
error:
|
||||
title: Произошла ошибка
|
||||
new:
|
||||
prompt_html: "%{client_name} хочет получить доступ к вашему аккаунту. <strong>Принимайте запрос только в том случае, если узнаёте, откуда он, и доверяете источнику.</strong>"
|
||||
review_permissions: Просмотр разрешений
|
||||
title: Требуется авторизация
|
||||
show:
|
||||
|
|
|
@ -60,7 +60,7 @@ th:
|
|||
error:
|
||||
title: เกิดข้อผิดพลาด
|
||||
new:
|
||||
prompt_html: "%{client_name} ร้องขอสิทธิ์ในการเข้าถึงข้อมูลในบัญชีของคุณ <strong>อนุมัติคำขอนี้ได้ก็ต่อเมื่อคุณมั่นใจและเชื่อถือในแหล่งที่มาของข้อมูลนี้</strong>"
|
||||
prompt_html: "%{client_name} ต้องการสิทธิอนุญาตเพื่อเข้าถึงบัญชีของคุณ <strong>อนุมัติคำขอนี้เฉพาะหากคุณรู้จักและเชื่อถือแหล่งที่มานี้เท่านั้น</strong>"
|
||||
review_permissions: ตรวจทานสิทธิอนุญาต
|
||||
title: ต้องการการอนุญาต
|
||||
show:
|
||||
|
|
|
@ -21,6 +21,7 @@ fy:
|
|||
one: Toot
|
||||
other: Berjochten
|
||||
posts_tab_heading: Berjochten
|
||||
self_follow_error: It folgjen fan dyn eigen account is net tastien
|
||||
admin:
|
||||
account_actions:
|
||||
action: Aksje útfiere
|
||||
|
@ -1165,8 +1166,11 @@ fy:
|
|||
use_security_key: Befeiligingskaai brûke
|
||||
author_attribution:
|
||||
example_title: Faorbyldtekst
|
||||
hint_html: Skriuwe jo nijs- of blogartikelen bûten Mastodon? Bepaal hoe’t jo oahelle wurde as dizze dield wurde op Mastodon.
|
||||
instructions: 'Soargj derfoar dat dizze koade yn de HTML fan jo artikel sit:'
|
||||
more_from_html: Mear fan %{name}
|
||||
s_blog: Weblog fan %{name}
|
||||
then_instructions: Foegje dernei de domeinnamme fan de publikaasje yn it ûndersteande fjild ta.
|
||||
title: Auteur-attribúsje
|
||||
challenge:
|
||||
confirm: Trochgean
|
||||
|
|
|
@ -262,7 +262,7 @@ gd:
|
|||
destroy_domain_allow_html: Dì-cheadaich %{name} co-nasgadh leis an àrainn %{target}
|
||||
destroy_domain_block_html: Dì-bhac %{name} an àrainn %{target}
|
||||
destroy_email_domain_block_html: Dì-bhac %{name} an àrainn puist-d %{target}
|
||||
destroy_instance_html: Purgaidich %{name} an àrainn %{target}
|
||||
destroy_instance_html: Phurgaidich %{name} an àrainn %{target}
|
||||
destroy_ip_block_html: Sguab %{name} às riaghailt dhan IP %{target}
|
||||
destroy_status_html: Thug %{name} post aig %{target} air falbh
|
||||
destroy_unavailable_domain_html: Lean %{name} air adhart leis an lìbhrigeadh dhan àrainn %{target}
|
||||
|
|
|
@ -25,6 +25,7 @@ ru:
|
|||
one: Пост
|
||||
other: статусов
|
||||
posts_tab_heading: Посты
|
||||
self_follow_error: Нельзя подписаться на самого себя
|
||||
admin:
|
||||
account_actions:
|
||||
action: Выполнить действие
|
||||
|
@ -462,6 +463,7 @@ ru:
|
|||
title: Блокировка нового почтового домена
|
||||
no_email_domain_block_selected: Блоки почтовых доменов не были изменены, так как ни один из них не был выбран
|
||||
not_permitted: Не разрешено
|
||||
resolved_dns_records_hint_html: Доменное имя указывает на следующие MX-домены, которые в конечном итоге отвечают за прием электронной почты. Блокировка MX-домена будет блокировать регистрации с любого адреса электронной почты, который использует тот же MX-домен, даже если видимое доменное имя отличается от него. <strong>Будьте осторожны, чтобы не заблокировать основных провайдеров электронной почты</strong>
|
||||
resolved_through_html: Разрешено через %{domain}
|
||||
title: Заблокированные e-mail домены
|
||||
export_domain_allows:
|
||||
|
@ -625,6 +627,7 @@ ru:
|
|||
resolve_description_html: Никаких действий не будет выполнено относительно доложенного содержимого. Жалоба будет закрыта.
|
||||
silence_description_html: Учетная запись будет видна только тем пользователям, которые уже подписаны на неё, либо открыли его вручную. Это действие можно отменить в любой момент, и отменяет все жалобы против аккаунта.
|
||||
suspend_description_html: Аккаунт и все его содержимое будут недоступны и в конечном итоге удалены, и взаимодействие с ним будет невозможно. Это действие можно отменить в течение 30 дней. Отменяет все жалобы против этого аккаунта.
|
||||
actions_description_html: Выберите действие, чтобы разрешить данную жалобу. Если вы примете меры модерации против аккаунта, его владелец получит уведомление по электронной почте, кроме тех случаев, когда выбрана категория <strong>Спам</strong>.
|
||||
actions_description_remote_html: Решите вопрос о том, какие меры необходимо принять для урегулирования этой жалобы. Это повлияет только на то, как <strong>ваш</strong> сервер взаимодействует с этим удаленным аккаунтом и обрабатывает его содержимое.
|
||||
actions_no_posts: У этого отчета нет связанных с ним сообщений для удаления
|
||||
add_to_report: Прикрепить ещё
|
||||
|
@ -902,6 +905,7 @@ ru:
|
|||
sidekiq_process_check:
|
||||
message_html: Ни один Sidekiq не запущен для %{value} очереди(-ей). Пожалуйста, просмотрите настройки Sidekiq
|
||||
software_version_check:
|
||||
action: Посмотреть доступные обновления
|
||||
message_html: Доступно обновление для Mastodon.
|
||||
software_version_critical_check:
|
||||
action: Посмотреть доступные обновления
|
||||
|
@ -1198,8 +1202,11 @@ ru:
|
|||
use_security_key: Использовать ключ безопасности
|
||||
author_attribution:
|
||||
example_title: Образец текста
|
||||
hint_html: Публикуете ли вы свои статьи где-либо ещё кроме Mastodon? Если да, то ваше авторство может быть упомянуто, когда ими делятся в Mastodon.
|
||||
instructions: 'Добавьте код ниже в HTML ваших статей:'
|
||||
more_from_html: Больше от %{name}
|
||||
s_blog: "%{name}'S Блог"
|
||||
then_instructions: Затем добавьте доменное имя сайта, где вы публикуетесь, в поле ниже.
|
||||
title: Авторская атрибуция
|
||||
challenge:
|
||||
confirm: Продолжить
|
||||
|
@ -1411,6 +1418,27 @@ ru:
|
|||
merge_long: Сохранить имеющиеся данные и добавить новые.
|
||||
overwrite: Перезаписать
|
||||
overwrite_long: Перезаписать имеющиеся данные новыми.
|
||||
overwrite_preambles:
|
||||
blocking_html:
|
||||
few: Вы собираетесь <strong>заменить свой список блокировки</strong>, в котором сейчас <strong>%{count} аккаунта</strong>, из файла <strong>%{filename}</strong>.
|
||||
many: Вы собираетесь <strong>заменить свой список блокировки</strong>, в котором сейчас <strong>%{count} аккаунов</strong>, из файла <strong>%{filename}</strong>.
|
||||
one: Вы собираетесь <strong>заменить свой список блокировки</strong>, в котором сейчас <strong>%{count} аккаунт</strong>, из файла <strong>%{filename}</strong>.
|
||||
other: Вы собираетесь <strong>заменить свой список блокировки</strong>, в котором сейчас <strong>%{count} аккаунтов</strong>, из файла <strong>%{filename}</strong>.
|
||||
bookmarks_html:
|
||||
few: Вы собираетесь <strong>заменить свои закладки</strong>, в которых сейчас <strong>%{count} поста</strong>, из файла <strong>%{filename}</strong>.
|
||||
many: Вы собираетесь <strong>заменить свои закладки</strong>, в которых сейчас <strong>%{count} постов</strong>, из файла <strong>%{filename}</strong>.
|
||||
one: Вы собираетесь <strong>заменить свои закладки</strong>, в которых сейчас <strong>%{count} пост</strong>, из файла <strong>%{filename}</strong>.
|
||||
other: Вы собираетесь <strong>заменить свои закладки</strong>, в которых сейчас <strong>%{count} постов</strong>, из файла <strong>%{filename}</strong>.
|
||||
domain_blocking_html:
|
||||
few: Вы собираетесь <strong>заменить свой список доменных блокировок</strong>, в котором сейчас <strong>%{count} домена</strong>, из файла <strong>%{filename}</strong>.
|
||||
many: Вы собираетесь <strong>заменить свой список доменных блокировок</strong>, в котором сейчас <strong>%{count} доменов</strong>, из файла <strong>%{filename}</strong>.
|
||||
one: Вы собираетесь <strong>заменить свой список доменных блокировок</strong>, в котором сейчас <strong>%{count} домен</strong>, из файла <strong>%{filename}</strong>.
|
||||
other: Вы собираетесь <strong>заменить свой список доменных блокировок</strong>, в котором сейчас <strong>%{count} доменов</strong>, из файла <strong>%{filename}</strong>.
|
||||
following_html:
|
||||
few: Вы собираетесь <strong>подписаться</strong> на <strong>%{count} аккаунта</strong> из файла <strong>%{filename}</strong> и <strong>отписаться от всех прочих</strong>.
|
||||
many: Вы собираетесь <strong>подписаться</strong> на <strong>%{count} аккаунтов</strong> из файла <strong>%{filename}</strong> и <strong>отписаться от всех прочих</strong>.
|
||||
one: Вы собираетесь <strong>подписаться</strong> на <strong>%{count} аккаунт</strong> из файла <strong>%{filename}</strong> и <strong>отписаться от всех прочих</strong>.
|
||||
other: Вы собираетесь <strong>подписаться</strong> на <strong>%{count} аккаунтов</strong> из файла <strong>%{filename}</strong> и <strong>отписаться от всех прочих</strong>.
|
||||
preface: Вы можете загрузить некоторые данные, например, списки людей, на которых Вы подписаны или которых блокируете, в Вашу учётную запись на этом узле из файлов, экспортированных с другого узла.
|
||||
recent_imports: Недавно импортированное
|
||||
states:
|
||||
|
@ -1728,6 +1756,7 @@ ru:
|
|||
delete: Удаление учётной записи
|
||||
development: Разработчикам
|
||||
edit_profile: Изменить профиль
|
||||
export: Экспорт
|
||||
featured_tags: Избранные хэштеги
|
||||
import: Импорт
|
||||
import_and_export: Импорт и экспорт
|
||||
|
|
|
@ -3,6 +3,7 @@ fy:
|
|||
simple_form:
|
||||
hints:
|
||||
account:
|
||||
attribution_domains_as_text: Ien per rigel. Beskermet tsjin falske attribúsjes.
|
||||
discoverable: Jo iepenbiere berjochten kinne útljochte wurde op ferskate plakken binnen Mastodon en jo account kin oanrekommandearre wurde oan oare brûkers.
|
||||
display_name: Jo folsleine namme of in aardige bynamme.
|
||||
fields: Jo website, persoanlike foarnammewurden, leeftiid, alles wat jo mar kwyt wolle.
|
||||
|
@ -143,6 +144,7 @@ fy:
|
|||
url: Wêr’t eveneminten nei ta stjoerd wurde
|
||||
labels:
|
||||
account:
|
||||
attribution_domains_as_text: Websites dy’t jo wurdearring jaan meie
|
||||
discoverable: Profyl en bydragen yn sykalgoritmen opnimme litte
|
||||
fields:
|
||||
name: Label
|
||||
|
|
|
@ -32,9 +32,9 @@ ko:
|
|||
warning_preset_id: 선택사항. 틀의 마지막에 임의의 텍스트를 추가 할 수 있습니다
|
||||
announcement:
|
||||
all_day: 체크 되었을 경우, 그 시간에 속한 날짜들에만 표시됩니다
|
||||
ends_at: 옵션입니다. 공지사항이 이 시간에 자동으로 발행 중지 됩니다
|
||||
ends_at: 선택사항. 공지사항이 이 시간에 자동으로 발행 중지 됩니다
|
||||
scheduled_at: 공백으로 두면 공지사항이 곧바로 발행 됩니다
|
||||
starts_at: 공지사항이 특정한 시간에 종속 될 때를 위한 옵션입니다
|
||||
starts_at: 선택사항. 공지사항이 특정한 시간에 종속 될 때를 위한 옵션입니다
|
||||
text: 게시물 문법을 사용할 수 있습니다. 공지사항은 사용자의 화면 상단 공간을 차지한다는 것을 명심하세요
|
||||
appeal:
|
||||
text: 처벌에 대해 단 한 번만 이의제기를 할 수 있습니다
|
||||
|
@ -110,7 +110,7 @@ ko:
|
|||
invite_request:
|
||||
text: 이 정보는 신청을 검토하는데 도움을 줄 수 있습니다.
|
||||
ip_block:
|
||||
comment: 필수 아님. 왜 이 규칙을 추가했는지 기억하세요.
|
||||
comment: 선택사항. 왜 이 규칙을 추가했는지 기억하세요.
|
||||
expires_in: IP 주소는 한정된 자원입니다, 이것들은 가끔 공유 되거나 자주 소유자가 바뀌기도 합니다. 이런 이유로 인해, IP 차단을 영구히 유지하는 것은 추천하지 않습니다.
|
||||
ip: IPv4 또는 IPv6 주소를 입력하세요. CIDR 문법을 사용해서 모든 범위를 차단할 수도 있습니다. 자기 자신을 잠가버리지 않도록 주의하세요!
|
||||
severities:
|
||||
|
@ -119,7 +119,7 @@ ko:
|
|||
sign_up_requires_approval: 새 가입이 승인을 필요로 하도록 합니다
|
||||
severity: 해당 IP로부터의 요청에 대해 무엇이 일어나게 할 지 고르세요
|
||||
rule:
|
||||
hint: 옵션사항. 규칙에 대한 더 상세한 정보를 제공하세요
|
||||
hint: 선택사항. 규칙에 대한 더 상세한 정보를 제공하세요
|
||||
text: 이 서버 사용자들이 지켜야 할 규칙과 요구사항을 설명해주세요. 짧고 간단하게 작성해주세요
|
||||
sessions:
|
||||
otp: '휴대전화에서 생성된 이중 인증 코드를 입력하거나, 복구 코드 중 하나를 사용하세요:'
|
||||
|
|
|
@ -61,7 +61,7 @@ lv:
|
|||
setting_display_media_hide_all: Vienmēr slēpt multividi
|
||||
setting_display_media_show_all: Vienmēr rādīt multividi
|
||||
setting_use_blurhash: Pāreju pamatā ir paslēpto uzskatāmo līdzekļu krāsas, bet saturs tiek padarīts neskaidrs
|
||||
setting_use_pending_items: Paslēpt laika skalas atjauninājumus aiz klikšķa, nevis automātiski ritini plūsmu
|
||||
setting_use_pending_items: Paslēpt laika skalas atjauninājumus aiz klikšķa, nevis ar automātisku plūsmas ritināšanu
|
||||
username: Tu vari lietot burtus, ciparus un zemsvītras
|
||||
whole_word: Ja atslēgvārds vai frāze ir tikai burtciparu, tas tiks lietots tikai tad, ja tas atbilst visam vārdam
|
||||
domain_allow:
|
||||
|
|
|
@ -3,7 +3,7 @@ nl:
|
|||
simple_form:
|
||||
hints:
|
||||
account:
|
||||
attribution_domains_as_text: Eén per regel. Beschermt tegen valse attribueringen.
|
||||
attribution_domains_as_text: Eén per regel. Beschermt tegen valse toeschrijvingen.
|
||||
discoverable: Jouw openbare berichten kunnen worden uitgelicht op verschillende plekken binnen Mastodon en jouw account kan worden aanbevolen aan andere gebruikers.
|
||||
display_name: Jouw volledige naam of een leuke bijnaam.
|
||||
fields: Jouw website, persoonlijke voornaamwoorden, leeftijd, alles wat je maar kwijt wilt.
|
||||
|
|
|
@ -3,6 +3,7 @@ ru:
|
|||
simple_form:
|
||||
hints:
|
||||
account:
|
||||
attribution_domains_as_text: По одному на строку. Защищает от ложных атрибуций.
|
||||
discoverable: Ваши публичные сообщения и профиль могут быть показаны или рекомендованы в различных разделах Mastodon, и ваш профиль может быть предложен другим пользователям.
|
||||
display_name: Ваше полное имя или псевдоним.
|
||||
fields: Ваша домашняя страница, местоимения, возраст - все, что угодно.
|
||||
|
@ -143,6 +144,7 @@ ru:
|
|||
url: Куда события будут отправляться
|
||||
labels:
|
||||
account:
|
||||
attribution_domains_as_text: Веб-сайты, которым разрешено ссылаться на вас
|
||||
discoverable: Профиль и сообщения в алгоритмах обнаружения
|
||||
fields:
|
||||
name: Пункт
|
||||
|
|
|
@ -74,7 +74,7 @@ sv:
|
|||
filters:
|
||||
action: Välj vilken åtgärd som ska utföras när ett inlägg matchar filtret
|
||||
actions:
|
||||
hide: Dölj det filtrerade innehållet helt (beter sig som om det inte fanns)
|
||||
hide: Dölj det filtrerade innehållet helt, beter sig som om det inte fanns
|
||||
warn: Dölj det filtrerade innehållet bakom en varning som visar filtrets rubrik
|
||||
form_admin_settings:
|
||||
activity_api_enabled: Antalet lokalt publicerade inlägg, aktiva användare och nya registrerade konton per vecka
|
||||
|
|
|
@ -59,7 +59,7 @@ sv:
|
|||
delete: Radera data
|
||||
deleted: Raderad
|
||||
demote: Degradera
|
||||
destroyed_msg: "%{username}'s data har nu lagts till kön för att raderas omedelbart"
|
||||
destroyed_msg: "%{username}s data har nu lagts till kön för att raderas omedelbart"
|
||||
disable: inaktivera
|
||||
disable_sign_in_token_auth: Inaktivera autentisering med pollett via e-post
|
||||
disable_two_factor_authentication: Inaktivera 2FA
|
||||
|
@ -72,7 +72,7 @@ sv:
|
|||
enable: Aktivera
|
||||
enable_sign_in_token_auth: Aktivera autentisering med pollett via e-post
|
||||
enabled: Aktiverad
|
||||
enabled_msg: Uppfrysningen av %{username}'s konto lyckades
|
||||
enabled_msg: Uppfrysningen av %{username}s konto lyckades
|
||||
followers: Följare
|
||||
follows: Följs
|
||||
header: Rubrik
|
||||
|
@ -268,21 +268,21 @@ sv:
|
|||
enable_custom_emoji_html: "%{name} aktiverade emoji %{target}"
|
||||
enable_sign_in_token_auth_user_html: "%{name} aktiverade e-posttokenautentisering för %{target}"
|
||||
enable_user_html: "%{name} aktiverade inloggning för användaren %{target}"
|
||||
memorialize_account_html: "%{name} gjorde %{target}'s konto till en minnessida"
|
||||
memorialize_account_html: "%{name} gjorde %{target}s konto till en minnessida"
|
||||
promote_user_html: "%{name} befordrade användaren %{target}"
|
||||
reject_appeal_html: "%{name} avvisade överklagande av modereringsbeslut från %{target}"
|
||||
reject_user_html: "%{name} avvisade registrering från %{target}"
|
||||
remove_avatar_user_html: "%{name} tog bort %{target}'s avatar"
|
||||
remove_avatar_user_html: "%{name} tog bort %{target}s avatar"
|
||||
reopen_report_html: "%{name} öppnade rapporten igen %{target}"
|
||||
resend_user_html: "%{name} skickade bekräftelsemail för %{target} på nytt"
|
||||
reset_password_user_html: "%{name} återställ användarens lösenord %{target}"
|
||||
resolve_report_html: "%{name} löste rapporten %{target}"
|
||||
sensitive_account_html: "%{name} markerade %{target}'s media som känsligt"
|
||||
silence_account_html: "%{name} begränsade %{target}'s konto"
|
||||
sensitive_account_html: "%{name} markerade %{target}s media som känsligt"
|
||||
silence_account_html: "%{name} begränsade %{target}s konto"
|
||||
suspend_account_html: "%{name} stängde av %{target}s konto"
|
||||
unassigned_report_html: "%{name} tog bort tilldelning av rapporten %{target}"
|
||||
unblock_email_account_html: "%{name} avblockerade %{target}s e-postadress"
|
||||
unsensitive_account_html: "%{name} avmarkerade %{target}'s media som känsligt"
|
||||
unsensitive_account_html: "%{name} avmarkerade %{target}s media som känsligt"
|
||||
unsilence_account_html: "%{name} tog bort begränsning av %{target}s konto"
|
||||
unsuspend_account_html: "%{name} ångrade avstängningen av %{target}s konto"
|
||||
update_announcement_html: "%{name} uppdaterade kungörelsen %{target}"
|
||||
|
@ -569,7 +569,7 @@ sv:
|
|||
no_ip_block_selected: Inga IP-regler ändrades då inga var valda
|
||||
title: IP-regler
|
||||
relationships:
|
||||
title: "%{acct}'s relationer"
|
||||
title: "%{acct}s relationer"
|
||||
relays:
|
||||
add_new: Lägg till nytt relä
|
||||
delete: Radera
|
||||
|
@ -899,7 +899,7 @@ sv:
|
|||
review_requested: Granskning begärd
|
||||
reviewed: Granskat
|
||||
title: Status
|
||||
trendable:
|
||||
trendable: Kan trenda
|
||||
unreviewed: Ogranskad
|
||||
usable: Användbar
|
||||
name: Namn
|
||||
|
@ -1826,8 +1826,8 @@ sv:
|
|||
tags:
|
||||
does_not_match_previous_name: matchar inte det föregående namnet
|
||||
themes:
|
||||
contrast: Hög kontrast
|
||||
default: Mastodon
|
||||
contrast: Mastodon (Hög kontrast)
|
||||
default: Mastodon (Mörk)
|
||||
mastodon-light: Mastodon (ljust)
|
||||
system: Automatisk (använd systemtema)
|
||||
time:
|
||||
|
|
|
@ -1148,11 +1148,11 @@ th:
|
|||
use_security_key: ใช้กุญแจความปลอดภัย
|
||||
author_attribution:
|
||||
example_title: ข้อความตัวอย่าง
|
||||
hint_html: คุณเขียนข่าวหรือลงบทความที่อื่นๆที่นอกจาก Mastodon บ้างไหม? ถ้าหากคุณเขียนอยู่ล่ะก็ คุณสามารถบอกได้นะว่าอยากให้คนอื่นให้เครดิตคุณยังไงเวลาแชร์ไปที่ Mastodon นะ
|
||||
instructions: 'โปรดตรวจสอบให้แน่ใจว่าโค้ดนี้ได้มีอยู่ในบทความของคุณแล้ว HTML:'
|
||||
hint_html: คุณกำลังเขียนข่าวหรือบทความบล็อกภายนอก Mastodon หรือไม่? ควบคุมวิธีที่คุณได้รับเครดิตเมื่อมีการแบ่งปันข่าวหรือบทความบล็อกใน Mastodon
|
||||
instructions: 'ตรวจสอบให้แน่ใจว่าโค้ดนี้อยู่ใน HTML ของบทความของคุณ:'
|
||||
more_from_html: เพิ่มเติมจาก %{name}
|
||||
s_blog: บล็อกของ %{name}
|
||||
then_instructions: จากนั้น ให้เพิ่มใส่ชื่อเว็บไซต์ของสิ่งพิมพ์ลงไปในช่องว่างข้างล่าง
|
||||
then_instructions: จากนั้น เพิ่มชื่อโดเมนของการเผยแพร่ในช่องด้านล่าง
|
||||
title: การระบุแหล่งที่มาผู้สร้าง
|
||||
challenge:
|
||||
confirm: ดำเนินการต่อ
|
||||
|
|
|
@ -63,7 +63,7 @@ namespace :api, format: false do
|
|||
resources :scheduled_statuses, only: [:index, :show, :update, :destroy]
|
||||
resources :preferences, only: [:index]
|
||||
|
||||
resources :annual_reports, only: [:index] do
|
||||
resources :annual_reports, only: [:index, :show] do
|
||||
member do
|
||||
post :read
|
||||
end
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateAnnualReportStatusesPerAccountCounts < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
create_table :annual_report_statuses_per_account_counts do |t| # rubocop:disable Rails/CreateTableWithTimestamps
|
||||
t.integer :year, null: false
|
||||
t.bigint :account_id, null: false
|
||||
t.bigint :statuses_count, null: false
|
||||
end
|
||||
|
||||
add_index :annual_report_statuses_per_account_counts, [:year, :account_id], unique: true
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.1].define(version: 2024_10_22_214312) do
|
||||
ActiveRecord::Schema[7.1].define(version: 2024_11_04_082851) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
|
@ -267,6 +267,13 @@ ActiveRecord::Schema[7.1].define(version: 2024_10_22_214312) do
|
|||
t.bigint "status_ids", array: true
|
||||
end
|
||||
|
||||
create_table "annual_report_statuses_per_account_counts", force: :cascade do |t|
|
||||
t.integer "year", null: false
|
||||
t.bigint "account_id", null: false
|
||||
t.bigint "statuses_count", null: false
|
||||
t.index ["year", "account_id"], name: "idx_on_year_account_id_ff3e167cef", unique: true
|
||||
end
|
||||
|
||||
create_table "antenna_accounts", force: :cascade do |t|
|
||||
t.bigint "antenna_id", null: false
|
||||
t.bigint "account_id", null: false
|
||||
|
|
|
@ -6,6 +6,8 @@ module Mastodon::CLI
|
|||
class Media < Base
|
||||
include ActionView::Helpers::NumberHelper
|
||||
|
||||
class UnrecognizedOrphanType < StandardError; end
|
||||
|
||||
VALID_PATH_SEGMENTS_SIZE = [7, 10].freeze
|
||||
|
||||
option :days, type: :numeric, default: 7, aliases: [:d]
|
||||
|
@ -120,23 +122,10 @@ module Mastodon::CLI
|
|||
object.acl.put(acl: s3_permissions) if options[:fix_permissions] && !dry_run?
|
||||
|
||||
path_segments = object.key.split('/')
|
||||
path_segments.delete('cache')
|
||||
|
||||
unless VALID_PATH_SEGMENTS_SIZE.include?(path_segments.size)
|
||||
progress.log(pastel.yellow("Unrecognized file found: #{object.key}"))
|
||||
next
|
||||
end
|
||||
|
||||
model_name = path_segments.first.classify
|
||||
attachment_name = path_segments[1].singularize
|
||||
record_id = path_segments[2...-2].join.to_i
|
||||
file_name = path_segments.last
|
||||
record = record_map.dig(model_name, record_id)
|
||||
attachment = record&.public_send(attachment_name)
|
||||
|
||||
progress.increment
|
||||
|
||||
next unless attachment.blank? || !attachment.variant?(file_name)
|
||||
next unless orphaned_file?(path_segments, record_map)
|
||||
|
||||
begin
|
||||
object.delete unless dry_run?
|
||||
|
@ -148,6 +137,8 @@ module Mastodon::CLI
|
|||
rescue => e
|
||||
progress.log(pastel.red("Error processing #{object.key}: #{e}"))
|
||||
end
|
||||
rescue UnrecognizedOrphanType
|
||||
progress.log(pastel.yellow("Unrecognized file found: #{object.key}"))
|
||||
end
|
||||
end
|
||||
when :fog
|
||||
|
@ -165,26 +156,10 @@ module Mastodon::CLI
|
|||
key = path.gsub("#{root_path}#{File::SEPARATOR}", '')
|
||||
|
||||
path_segments = key.split(File::SEPARATOR)
|
||||
path_segments.delete('cache')
|
||||
|
||||
unless VALID_PATH_SEGMENTS_SIZE.include?(path_segments.size)
|
||||
progress.log(pastel.yellow("Unrecognized file found: #{key}"))
|
||||
next
|
||||
end
|
||||
|
||||
model_name = path_segments.first.classify
|
||||
record_id = path_segments[2...-2].join.to_i
|
||||
attachment_name = path_segments[1].singularize
|
||||
file_name = path_segments.last
|
||||
|
||||
next unless PRELOADED_MODELS.include?(model_name)
|
||||
|
||||
record = model_name.constantize.find_by(id: record_id)
|
||||
attachment = record&.public_send(attachment_name)
|
||||
|
||||
progress.increment
|
||||
|
||||
next unless attachment.blank? || !attachment.variant?(file_name)
|
||||
next unless orphaned_file?(path_segments)
|
||||
|
||||
begin
|
||||
size = File.size(path)
|
||||
|
@ -205,6 +180,8 @@ module Mastodon::CLI
|
|||
rescue => e
|
||||
progress.log(pastel.red("Error processing #{key}: #{e}"))
|
||||
end
|
||||
rescue UnrecognizedOrphanType
|
||||
progress.log(pastel.yellow("Unrecognized file found: #{path}"))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -364,5 +341,23 @@ module Mastodon::CLI
|
|||
model_map[model_name] = model_name.constantize.where(id: record_ids).index_by(&:id)
|
||||
end
|
||||
end
|
||||
|
||||
def orphaned_file?(path_segments, record_map = nil)
|
||||
path_segments.delete('cache')
|
||||
|
||||
raise UnrecognizedOrphanType unless VALID_PATH_SEGMENTS_SIZE.include?(path_segments.size)
|
||||
|
||||
model_name = path_segments.first.classify
|
||||
record_id = path_segments[2...-2].join.to_i
|
||||
attachment_name = path_segments[1].singularize
|
||||
file_name = path_segments.last
|
||||
|
||||
raise UnrecognizedOrphanType unless PRELOADED_MODELS.include?(model_name)
|
||||
|
||||
record = record_map.present? ? record_map.dig(model_name, record_id) : model_name.constantize.find_by(id: record_id)
|
||||
attachment = record&.public_send(attachment_name)
|
||||
|
||||
attachment.blank? || !attachment.variant?(file_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -57,36 +57,24 @@ RSpec.describe AccountStatusesFilter do
|
|||
end
|
||||
|
||||
shared_examples 'filter params' do
|
||||
context 'with only_media param' do
|
||||
let(:params) { { only_media: true } }
|
||||
it 'respects param options in results' do
|
||||
expect(results_for(only_media: true))
|
||||
.to all(satisfy(&:with_media?))
|
||||
|
||||
it 'returns only statuses with media' do
|
||||
expect(subject.all?(&:with_media?)).to be true
|
||||
end
|
||||
expect(results_for(tagged: tag.name))
|
||||
.to all(satisfy { |status| status.tags.include?(tag) })
|
||||
|
||||
expect(results_for(exclude_replies: true))
|
||||
.to all(satisfy { |status| !status.reply? })
|
||||
|
||||
expect(results_for(exclude_reblogs: true))
|
||||
.to all(satisfy { |status| !status.reblog? })
|
||||
end
|
||||
|
||||
context 'with tagged param' do
|
||||
let(:params) { { tagged: tag.name } }
|
||||
|
||||
it 'returns only statuses with tag' do
|
||||
expect(subject.all? { |s| s.tags.include?(tag) }).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'with exclude_replies param' do
|
||||
let(:params) { { exclude_replies: true } }
|
||||
|
||||
it 'returns only statuses that are not replies' do
|
||||
expect(subject.none?(&:reply?)).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'with exclude_reblogs param' do
|
||||
let(:params) { { exclude_reblogs: true } }
|
||||
|
||||
it 'returns only statuses that are not reblogs' do
|
||||
expect(subject.none?(&:reblog?)).to be true
|
||||
end
|
||||
def results_for(params)
|
||||
described_class
|
||||
.new(account, current_account, params)
|
||||
.results
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -4,17 +4,20 @@ require 'rails_helper'
|
|||
|
||||
RSpec.describe AnnualReport::Percentiles do
|
||||
describe '#generate' do
|
||||
subject { described_class.new(account, Time.zone.now.year) }
|
||||
subject { described_class.new(account, year) }
|
||||
|
||||
let(:year) { Time.zone.now.year }
|
||||
|
||||
context 'with an inactive account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
it 'builds a report for an account' do
|
||||
described_class.prepare(year)
|
||||
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
percentiles: include(
|
||||
followers: 0,
|
||||
statuses: 0
|
||||
statuses: 100
|
||||
)
|
||||
)
|
||||
end
|
||||
|
@ -25,16 +28,15 @@ RSpec.describe AnnualReport::Percentiles do
|
|||
|
||||
before do
|
||||
Fabricate.times 2, :status # Others as `account`
|
||||
Fabricate.times 2, :follow # Others as `target_account`
|
||||
Fabricate.times 2, :status, account: account
|
||||
Fabricate.times 2, :follow, target_account: account
|
||||
end
|
||||
|
||||
it 'builds a report for an account' do
|
||||
described_class.prepare(year)
|
||||
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
percentiles: include(
|
||||
followers: 50,
|
||||
statuses: 50
|
||||
)
|
||||
)
|
||||
|
|
|
@ -126,16 +126,54 @@ RSpec.describe FeaturedTag do
|
|||
end
|
||||
|
||||
describe '#decrement' do
|
||||
it 'decreases the count and updates the last_status_at timestamp' do
|
||||
tag = Fabricate :tag, name: 'test'
|
||||
status = Fabricate :status, visibility: :public, created_at: 10.days.ago
|
||||
status.tags << tag
|
||||
let(:tag) { Fabricate(:tag, name: 'test') }
|
||||
let(:account) { Fabricate(:account) }
|
||||
let(:featured_tag) { Fabricate(:featured_tag, name: 'test', account: account) }
|
||||
|
||||
featured_tag = Fabricate :featured_tag, name: 'test', account: status.account
|
||||
context 'when removing the last status using the tag' do
|
||||
let(:status) { Fabricate(:status, visibility: :public, account: account, created_at: 10.days.ago) }
|
||||
|
||||
expect { featured_tag.decrement(status.id) }
|
||||
.to change(featured_tag, :statuses_count).from(1).to(0)
|
||||
.and change(featured_tag, :last_status_at).to(nil)
|
||||
before do
|
||||
status.tags << tag
|
||||
end
|
||||
|
||||
it 'decreases the count and updates the last_status_at timestamp' do
|
||||
expect { featured_tag.decrement(status) }
|
||||
.to change(featured_tag, :statuses_count).from(1).to(0)
|
||||
.and change(featured_tag, :last_status_at).to(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when removing a previous status using the tag' do
|
||||
let(:previous_status) { Fabricate(:status, visibility: :public, account: account, created_at: 1.month.ago) }
|
||||
let(:status) { Fabricate(:status, visibility: :public, account: account, created_at: 10.days.ago) }
|
||||
|
||||
before do
|
||||
previous_status.tags << tag
|
||||
status.tags << tag
|
||||
end
|
||||
|
||||
it 'decreases the count and updates the last_status_at timestamp' do
|
||||
expect { featured_tag.decrement(previous_status) }
|
||||
.to change(featured_tag, :statuses_count).from(2).to(1)
|
||||
.and not_change(featured_tag, :last_status_at)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when removing the most recent use of the tag' do
|
||||
let(:previous_status) { Fabricate(:status, visibility: :public, account: account, created_at: 1.month.ago) }
|
||||
let(:status) { Fabricate(:status, visibility: :public, account: account, created_at: 10.days.ago) }
|
||||
|
||||
before do
|
||||
previous_status.tags << tag
|
||||
status.tags << tag
|
||||
end
|
||||
|
||||
it 'decreases the count and updates the last_status_at timestamp' do
|
||||
expect { featured_tag.decrement(status) }
|
||||
.to change(featured_tag, :statuses_count).from(2).to(1)
|
||||
.and change(featured_tag, :last_status_at)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|