Merge commit 'ce1680e6f979889ebd5c775353f3b82a8b2698bb' into glitch-soc/merge-upstream

Conflicts:
- `app/models/concerns/user/has_settings.rb`:
  Upstream added a new helper for a setting, where glitch-soc had extra ones.
  Not a real conflict, added upstream's new helper without removing glitch-soc's ones.
This commit is contained in:
Claire
2025-08-09 23:02:43 +02:00
45 changed files with 890 additions and 137 deletions

View File

@@ -315,7 +315,7 @@ GEM
http_accept_language (2.1.1) http_accept_language (2.1.1)
httpclient (2.9.0) httpclient (2.9.0)
mutex_m mutex_m
httplog (1.7.2) httplog (1.7.3)
rack (>= 2.0) rack (>= 2.0)
rainbow (>= 2.0.0) rainbow (>= 2.0.0)
i18n (1.14.7) i18n (1.14.7)

View File

@@ -12,6 +12,8 @@ class Auth::SessionsController < Devise::SessionsController
skip_before_action :require_functional! skip_before_action :require_functional!
skip_before_action :update_user_sign_in skip_before_action :update_user_sign_in
around_action :preserve_stored_location, only: :destroy, if: :continue_after?
prepend_before_action :check_suspicious!, only: [:create] prepend_before_action :check_suspicious!, only: [:create]
include Auth::TwoFactorAuthenticationConcern include Auth::TwoFactorAuthenticationConcern
@@ -31,11 +33,9 @@ class Auth::SessionsController < Devise::SessionsController
end end
def destroy def destroy
tmp_stored_location = stored_location_for(:user)
super super
session.delete(:challenge_passed_at) session.delete(:challenge_passed_at)
flash.delete(:notice) flash.delete(:notice)
store_location_for(:user, tmp_stored_location) if continue_after?
end end
def webauthn_options def webauthn_options
@@ -96,6 +96,12 @@ class Auth::SessionsController < Devise::SessionsController
private private
def preserve_stored_location
original_stored_location = stored_location_for(:user)
yield
store_location_for(:user, original_stored_location)
end
def check_suspicious! def check_suspicious!
user = find_user user = find_user
@login_is_suspicious = suspicious_sign_in?(user) unless user.nil? @login_is_suspicious = suspicious_sign_in?(user) unless user.nil?

View File

@@ -228,6 +228,8 @@ export function submitCompose() {
visibility: getState().getIn(['compose', 'privacy']), visibility: getState().getIn(['compose', 'privacy']),
poll: getState().getIn(['compose', 'poll'], null), poll: getState().getIn(['compose', 'poll'], null),
language: getState().getIn(['compose', 'language']), language: getState().getIn(['compose', 'language']),
quoted_status_id: getState().getIn(['compose', 'quoted_status_id']),
quote_approval_policy: getState().getIn(['compose', 'quote_policy']),
}, },
headers: { headers: {
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']), 'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),

View File

@@ -1,9 +1,18 @@
import { createAction } from '@reduxjs/toolkit';
import type { List as ImmutableList, Map as ImmutableMap } from 'immutable'; import type { List as ImmutableList, Map as ImmutableMap } from 'immutable';
import { apiUpdateMedia } from 'mastodon/api/compose'; import { apiUpdateMedia } from 'mastodon/api/compose';
import type { ApiMediaAttachmentJSON } from 'mastodon/api_types/media_attachments'; import type { ApiMediaAttachmentJSON } from 'mastodon/api_types/media_attachments';
import type { MediaAttachment } from 'mastodon/models/media_attachment'; import type { MediaAttachment } from 'mastodon/models/media_attachment';
import { createDataLoadingThunk } from 'mastodon/store/typed_functions'; import {
createDataLoadingThunk,
createAppThunk,
} from 'mastodon/store/typed_functions';
import type { ApiQuotePolicy } from '../api_types/quotes';
import type { Status } from '../models/status';
import { ensureComposeIsVisible } from './compose';
type SimulatedMediaAttachmentJSON = ApiMediaAttachmentJSON & { type SimulatedMediaAttachmentJSON = ApiMediaAttachmentJSON & {
unattached?: boolean; unattached?: boolean;
@@ -68,3 +77,26 @@ export const changeUploadCompose = createDataLoadingThunk(
useLoadingBar: false, useLoadingBar: false,
}, },
); );
export const quoteComposeByStatus = createAppThunk(
'compose/quoteComposeStatus',
(status: Status, { getState }) => {
ensureComposeIsVisible(getState);
return status;
},
);
export const quoteComposeById = createAppThunk(
(statusId: string, { dispatch, getState }) => {
const status = getState().statuses.get(statusId);
if (status) {
dispatch(quoteComposeByStatus(status));
}
},
);
export const quoteComposeCancel = createAction('compose/quoteComposeCancel');
export const setQuotePolicy = createAction<ApiQuotePolicy>(
'compose/setQuotePolicy',
);

View File

@@ -0,0 +1,23 @@
import type { ApiStatusJSON } from './statuses';
export type ApiQuoteState = 'accepted' | 'pending' | 'revoked' | 'unauthorized';
export type ApiQuotePolicy = 'public' | 'followers' | 'nobody';
interface ApiQuoteEmptyJSON {
state: Exclude<ApiQuoteState, 'accepted'>;
quoted_status: null;
}
interface ApiNestedQuoteJSON {
state: 'accepted';
quoted_status_id: string;
}
interface ApiQuoteAcceptedJSON {
state: 'accepted';
quoted_status: Omit<ApiStatusJSON, 'quote'> & {
quote: ApiNestedQuoteJSON | ApiQuoteEmptyJSON;
};
}
export type ApiQuoteJSON = ApiQuoteAcceptedJSON | ApiQuoteEmptyJSON;

View File

@@ -4,6 +4,7 @@ import type { ApiAccountJSON } from './accounts';
import type { ApiCustomEmojiJSON } from './custom_emoji'; import type { ApiCustomEmojiJSON } from './custom_emoji';
import type { ApiMediaAttachmentJSON } from './media_attachments'; import type { ApiMediaAttachmentJSON } from './media_attachments';
import type { ApiPollJSON } from './polls'; import type { ApiPollJSON } from './polls';
import type { ApiQuoteJSON } from './quotes';
// See app/modals/status.rb // See app/modals/status.rb
export type StatusVisibility = export type StatusVisibility =
@@ -118,6 +119,7 @@ export interface ApiStatusJSON {
card?: ApiPreviewCardJSON; card?: ApiPreviewCardJSON;
poll?: ApiPollJSON; poll?: ApiPollJSON;
quote?: ApiQuoteJSON;
} }
export interface ApiContextJSON { export interface ApiContextJSON {

View File

@@ -43,7 +43,7 @@
"account.followers": "Падпісчыкі", "account.followers": "Падпісчыкі",
"account.followers.empty": "Ніхто пакуль не падпісаны на гэтага карыстальніка.", "account.followers.empty": "Ніхто пакуль не падпісаны на гэтага карыстальніка.",
"account.followers_counter": "{count, plural, one {{counter} падпісчык} few {{counter} падпісчыкі} many {{counter} падпісчыкаў} other {{counter} падпісчыка}}", "account.followers_counter": "{count, plural, one {{counter} падпісчык} few {{counter} падпісчыкі} many {{counter} падпісчыкаў} other {{counter} падпісчыка}}",
"account.followers_you_know_counter": "{count, one {{counter}, знаёмы вам} other {{counter}, знаёмых вам}}", "account.followers_you_know_counter": "{count, plural, one {{counter}, знаёмы вам} other {{counter}, знаёмых вам}}",
"account.following": "Падпіскі", "account.following": "Падпіскі",
"account.following_counter": "{count, plural, one {{counter} падпіска} few {{counter} падпіскі} many {{counter} падпісак} other {{counter} падпіскі}}", "account.following_counter": "{count, plural, one {{counter} падпіска} few {{counter} падпіскі} many {{counter} падпісак} other {{counter} падпіскі}}",
"account.follows.empty": "Карыстальнік ні на каго не падпісаны.", "account.follows.empty": "Карыстальнік ні на каго не падпісаны.",
@@ -62,6 +62,7 @@
"account.mute_notifications_short": "Не апавяшчаць", "account.mute_notifications_short": "Не апавяшчаць",
"account.mute_short": "Ігнараваць", "account.mute_short": "Ігнараваць",
"account.muted": "Ігнаруецца", "account.muted": "Ігнаруецца",
"account.muting": "Ігнараванне",
"account.mutual": "Вы падпісаны адно на аднаго", "account.mutual": "Вы падпісаны адно на аднаго",
"account.no_bio": "Апісанне адсутнічае.", "account.no_bio": "Апісанне адсутнічае.",
"account.open_original_page": "Адкрыць арыгінальную старонку", "account.open_original_page": "Адкрыць арыгінальную старонку",
@@ -103,6 +104,8 @@
"alt_text_modal.add_text_from_image": "Дадаць тэкст з відарыса", "alt_text_modal.add_text_from_image": "Дадаць тэкст з відарыса",
"alt_text_modal.cancel": "Скасаваць", "alt_text_modal.cancel": "Скасаваць",
"alt_text_modal.change_thumbnail": "Змяніць мініяцюру", "alt_text_modal.change_thumbnail": "Змяніць мініяцюру",
"alt_text_modal.describe_for_people_with_hearing_impairments": "Апішыце гэта людзям з праблемамі са слыхам…",
"alt_text_modal.describe_for_people_with_visual_impairments": "Апішыце гэта людзям з праблемамі са зрокам…",
"alt_text_modal.done": "Гатова", "alt_text_modal.done": "Гатова",
"announcement.announcement": "Аб'ява", "announcement.announcement": "Аб'ява",
"annual_report.summary.archetype.booster": "Паляўнічы на трэнды", "annual_report.summary.archetype.booster": "Паляўнічы на трэнды",
@@ -216,7 +219,13 @@
"confirmations.delete_list.confirm": "Выдаліць", "confirmations.delete_list.confirm": "Выдаліць",
"confirmations.delete_list.message": "Вы ўпэўненыя, што хочаце беззваротна выдаліць гэты чарнавік?", "confirmations.delete_list.message": "Вы ўпэўненыя, што хочаце беззваротна выдаліць гэты чарнавік?",
"confirmations.delete_list.title": "Выдаліць спіс?", "confirmations.delete_list.title": "Выдаліць спіс?",
"confirmations.discard_draft.confirm": "Адмовіцца і працягнуць",
"confirmations.discard_draft.edit.cancel": "Працягнуць рэдагаванне", "confirmations.discard_draft.edit.cancel": "Працягнуць рэдагаванне",
"confirmations.discard_draft.edit.message": "Калі працягнуць, то ўсе змены, што Вы зрабілі ў гэтым допісе, будуць адмененыя.",
"confirmations.discard_draft.edit.title": "Адмовіцца ад змен у Вашым допісе?",
"confirmations.discard_draft.post.cancel": "Працягнуць чарнавік",
"confirmations.discard_draft.post.message": "Калі працягнуць, то допіс, які Вы зараз пішаце, не будзе апублікаваны.",
"confirmations.discard_draft.post.title": "Выдаліць чарнавік?",
"confirmations.discard_edit_media.confirm": "Адмяніць", "confirmations.discard_edit_media.confirm": "Адмяніць",
"confirmations.discard_edit_media.message": "У вас ёсць незахаваныя змены ў апісанні або прэв'ю, усе роўна скасаваць іх?", "confirmations.discard_edit_media.message": "У вас ёсць незахаваныя змены ў апісанні або прэв'ю, усе роўна скасаваць іх?",
"confirmations.follow_to_list.confirm": "Падпісацца і дадаць у спіс", "confirmations.follow_to_list.confirm": "Падпісацца і дадаць у спіс",
@@ -226,6 +235,7 @@
"confirmations.logout.message": "Вы ўпэўненыя, што хочаце выйсці?", "confirmations.logout.message": "Вы ўпэўненыя, што хочаце выйсці?",
"confirmations.logout.title": "Выйсці?", "confirmations.logout.title": "Выйсці?",
"confirmations.missing_alt_text.confirm": "Дадаць альтэрнатыўны тэкст", "confirmations.missing_alt_text.confirm": "Дадаць альтэрнатыўны тэкст",
"confirmations.missing_alt_text.message": "У Вашым допісе ёсць медыя без альтэрнатыўнага тэксту. Дадаванне апісання дапамагае зрабіць Ваш допіс даступным для большай колькасці людзей.",
"confirmations.missing_alt_text.secondary": "Усё адно апублікаваць", "confirmations.missing_alt_text.secondary": "Усё адно апублікаваць",
"confirmations.missing_alt_text.title": "Дадаць альтэрнатыўны тэкст?", "confirmations.missing_alt_text.title": "Дадаць альтэрнатыўны тэкст?",
"confirmations.mute.confirm": "Ігнараваць", "confirmations.mute.confirm": "Ігнараваць",
@@ -233,7 +243,11 @@
"confirmations.redraft.message": "Вы ўпэўнены, што хочаце выдаліць допіс і перапісаць яго? Упадабанні і пашырэнні згубяцца, а адказы да арыгінальнага допісу асірацеюць.", "confirmations.redraft.message": "Вы ўпэўнены, што хочаце выдаліць допіс і перапісаць яго? Упадабанні і пашырэнні згубяцца, а адказы да арыгінальнага допісу асірацеюць.",
"confirmations.redraft.title": "Выдаліць і перапісаць допіс?", "confirmations.redraft.title": "Выдаліць і перапісаць допіс?",
"confirmations.remove_from_followers.confirm": "Выдаліць падпісчыка", "confirmations.remove_from_followers.confirm": "Выдаліць падпісчыка",
"confirmations.remove_from_followers.message": "Карыстальнік {name} больш не будзе падпісаны на Вас. Упэўненыя, што хочаце працягнуць?",
"confirmations.remove_from_followers.title": "Выдаліць падпісчыка?", "confirmations.remove_from_followers.title": "Выдаліць падпісчыка?",
"confirmations.revoke_quote.confirm": "Выдаліць допіс",
"confirmations.revoke_quote.message": "Гэтае дзеянне немагчыма адмяніць.",
"confirmations.revoke_quote.title": "Выдаліць допіс?",
"confirmations.unfollow.confirm": "Адпісацца", "confirmations.unfollow.confirm": "Адпісацца",
"confirmations.unfollow.message": "Вы ўпэўненыя, што хочаце адпісацца ад {name}?", "confirmations.unfollow.message": "Вы ўпэўненыя, што хочаце адпісацца ад {name}?",
"confirmations.unfollow.title": "Адпісацца ад карыстальніка?", "confirmations.unfollow.title": "Адпісацца ад карыстальніка?",
@@ -295,6 +309,9 @@
"emoji_button.search_results": "Вынікі пошуку", "emoji_button.search_results": "Вынікі пошуку",
"emoji_button.symbols": "Сімвалы", "emoji_button.symbols": "Сімвалы",
"emoji_button.travel": "Падарожжы і месцы", "emoji_button.travel": "Падарожжы і месцы",
"empty_column.account_featured.me": "Вы яшчэ нічога не паказалі. Ці ведалі Вы, што ў сваім профілі Вы можаце паказаць свае хэштэгі, якім найбольш карыстаецеся, і нават профілі сваіх сяброў?",
"empty_column.account_featured.other": "{acct} яшчэ нічога не паказаў. Ці ведалі Вы, што ў сваім профілі Вы можаце паказаць свае хэштэгі, якім найбольш карыстаецеся, і нават профілі сваіх сяброў?",
"empty_column.account_featured_other.unknown": "Гэты профіль яшчэ нічога не паказаў.",
"empty_column.account_hides_collections": "Гэты карыстальнік вырашыў схаваць гэтую інфармацыю", "empty_column.account_hides_collections": "Гэты карыстальнік вырашыў схаваць гэтую інфармацыю",
"empty_column.account_suspended": "Уліковы запіс прыпынены", "empty_column.account_suspended": "Уліковы запіс прыпынены",
"empty_column.account_timeline": "Тут няма допісаў!", "empty_column.account_timeline": "Тут няма допісаў!",
@@ -327,6 +344,7 @@
"explore.trending_links": "Навіны", "explore.trending_links": "Навіны",
"explore.trending_statuses": "Допісы", "explore.trending_statuses": "Допісы",
"explore.trending_tags": "Хэштэгі", "explore.trending_tags": "Хэштэгі",
"featured_carousel.header": "{count, plural,one {Замацаваны допіс} other {Замацаваныя допісы}}",
"featured_carousel.next": "Далей", "featured_carousel.next": "Далей",
"featured_carousel.post": "Допіс", "featured_carousel.post": "Допіс",
"featured_carousel.previous": "Назад", "featured_carousel.previous": "Назад",
@@ -383,6 +401,8 @@
"generic.saved": "Захавана", "generic.saved": "Захавана",
"getting_started.heading": "Пачатак працы", "getting_started.heading": "Пачатак працы",
"hashtag.admin_moderation": "Адкрыць інтэрфейс мадэратара для #{name}", "hashtag.admin_moderation": "Адкрыць інтэрфейс мадэратара для #{name}",
"hashtag.browse": "Праглядзець допісы ў #{hashtag}",
"hashtag.browse_from_account": "Праглядзець допісы ад @{name} у #{hashtag}",
"hashtag.column_header.tag_mode.all": "і {additional}", "hashtag.column_header.tag_mode.all": "і {additional}",
"hashtag.column_header.tag_mode.any": "або {additional}", "hashtag.column_header.tag_mode.any": "або {additional}",
"hashtag.column_header.tag_mode.none": "без {additional}", "hashtag.column_header.tag_mode.none": "без {additional}",
@@ -395,7 +415,10 @@
"hashtag.counter_by_accounts": "{count, plural, one {{counter} удзельнік} few {{counter} удзельніка} many {{counter} удзельнікаў} other {{counter} удзельніка}}", "hashtag.counter_by_accounts": "{count, plural, one {{counter} удзельнік} few {{counter} удзельніка} many {{counter} удзельнікаў} other {{counter} удзельніка}}",
"hashtag.counter_by_uses": "{count, plural, one {{counter} допіс} few {{counter} допісы} many {{counter} допісаў} other {{counter} допісу}}", "hashtag.counter_by_uses": "{count, plural, one {{counter} допіс} few {{counter} допісы} many {{counter} допісаў} other {{counter} допісу}}",
"hashtag.counter_by_uses_today": "{count, plural, one {{counter} допіс} few {{counter} допісы} many {{counter} допісаў} other {{counter} допісу}} за сёння", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} допіс} few {{counter} допісы} many {{counter} допісаў} other {{counter} допісу}} за сёння",
"hashtag.feature": "Паказваць у профілі",
"hashtag.follow": "Падпісацца на хэштэг", "hashtag.follow": "Падпісацца на хэштэг",
"hashtag.mute": "Ігнараваць #{hashtag}",
"hashtag.unfeature": "Не паказваць у профілі",
"hashtag.unfollow": "Адпісацца ад хэштэга", "hashtag.unfollow": "Адпісацца ад хэштэга",
"hashtags.and_other": "…і яшчэ {count, plural, other {#}}", "hashtags.and_other": "…і яшчэ {count, plural, other {#}}",
"hints.profiles.followers_may_be_missing": "Падпісчыкі гэтага профілю могуць адсутнічаць.", "hints.profiles.followers_may_be_missing": "Падпісчыкі гэтага профілю могуць адсутнічаць.",
@@ -424,8 +447,12 @@
"ignore_notifications_modal.not_following_title": "Ігнараваць апавяшчэнні ад людзей на якіх вы не падпісаны?", "ignore_notifications_modal.not_following_title": "Ігнараваць апавяшчэнні ад людзей на якіх вы не падпісаны?",
"ignore_notifications_modal.private_mentions_title": "Ігнараваць апавяшчэнні пра непажаданыя асабістыя згадванні?", "ignore_notifications_modal.private_mentions_title": "Ігнараваць апавяшчэнні пра непажаданыя асабістыя згадванні?",
"info_button.label": "Даведка", "info_button.label": "Даведка",
"info_button.what_is_alt_text": "<h1>Што такое альтэрнатыўны тэкст?</h1> <p>Альтэрнатыўны тэкст апісвае відарыс людзям з парушэннем зроку, павольным злучэннем або тым, каму патрэбны дадатковы кантэкст.</p> <p>Вы можаце зрабіць відарыс больш дасяжным і зразумелым для ўсіх, напісаўшы зразумелы, сціслы і аб'ектыўны альтэрнатыўны тэкст.</p> <ul><li>Ахоплівайце важныя элементы</li> <li>Тлумачце тэкст на відарысе</li> <li>Карыстайцеся звычайнымі сказамі</li> <li>Пазбягайце залішняй інфармацыі</li> <li>Засяроджвайцеся на тэндэнцыях і ключавых высновах у цяжкіх для разумення візуальных матэрыялах (напрыклад, дыяграмах або картах)</li></ul>",
"interaction_modal.action.favourite": "Каб працягнуць, вы мусіце ўпадабаць нешта са свайго ўліковага запісу.", "interaction_modal.action.favourite": "Каб працягнуць, вы мусіце ўпадабаць нешта са свайго ўліковага запісу.",
"interaction_modal.action.follow": "Каб працягнуць, вы мусіце падпісацца на некага са свайго ўліковага запісу.", "interaction_modal.action.follow": "Каб працягнуць, вы мусіце падпісацца на некага са свайго ўліковага запісу.",
"interaction_modal.action.reblog": "Каб працягнуць, Вам трэба пашырыць допіс са свайго профілю.",
"interaction_modal.action.reply": "Каб працягнуць, Вам трэба адказаць са свайго профілю.",
"interaction_modal.action.vote": "Каб працягнуць, Вам трэба прагаласаваць са свайго профілю.",
"interaction_modal.go": "Перайсці", "interaction_modal.go": "Перайсці",
"interaction_modal.no_account_yet": "Не маеце ўліковага запісу?", "interaction_modal.no_account_yet": "Не маеце ўліковага запісу?",
"interaction_modal.on_another_server": "На іншым серверы", "interaction_modal.on_another_server": "На іншым серверы",
@@ -434,6 +461,7 @@
"interaction_modal.title.follow": "Падпісацца на {name}", "interaction_modal.title.follow": "Падпісацца на {name}",
"interaction_modal.title.reblog": "Пашырыць допіс ад {name}", "interaction_modal.title.reblog": "Пашырыць допіс ад {name}",
"interaction_modal.title.reply": "Адказаць на допіс {name}", "interaction_modal.title.reply": "Адказаць на допіс {name}",
"interaction_modal.title.vote": "Прагаласуйце ў апытанні {name}",
"interaction_modal.username_prompt": "Напр., {example}", "interaction_modal.username_prompt": "Напр., {example}",
"intervals.full.days": "{number, plural, one {# дзень} few {# дні} many {# дзён} other {# дня}}", "intervals.full.days": "{number, plural, one {# дзень} few {# дні} many {# дзён} other {# дня}}",
"intervals.full.hours": "{number, plural, one {# гадзіна} few {# гадзіны} many {# гадзін} other {# гадзіны}}", "intervals.full.hours": "{number, plural, one {# гадзіна} few {# гадзіны} many {# гадзін} other {# гадзіны}}",
@@ -473,6 +501,8 @@
"keyboard_shortcuts.translate": "каб перакласці допіс", "keyboard_shortcuts.translate": "каб перакласці допіс",
"keyboard_shortcuts.unfocus": "Расфакусаваць тэкставую вобласць/пошукавы радок", "keyboard_shortcuts.unfocus": "Расфакусаваць тэкставую вобласць/пошукавы радок",
"keyboard_shortcuts.up": "Перамясціцца ўверх па спісе", "keyboard_shortcuts.up": "Перамясціцца ўверх па спісе",
"learn_more_link.got_it": "Зразумеў(-ла)",
"learn_more_link.learn_more": "Падрабязней",
"lightbox.close": "Закрыць", "lightbox.close": "Закрыць",
"lightbox.next": "Далей", "lightbox.next": "Далей",
"lightbox.previous": "Назад", "lightbox.previous": "Назад",
@@ -487,10 +517,15 @@
"lists.add_to_list": "Дадаць у спіс", "lists.add_to_list": "Дадаць у спіс",
"lists.add_to_lists": "Дадаць {name} у спісы", "lists.add_to_lists": "Дадаць {name} у спісы",
"lists.create": "Стварыць", "lists.create": "Стварыць",
"lists.create_a_list_to_organize": "Стварыце новы спіс, каб арганізаваць сваю Галоўную старонку",
"lists.create_list": "Стварыць спіс", "lists.create_list": "Стварыць спіс",
"lists.delete": "Выдаліць спіс", "lists.delete": "Выдаліць спіс",
"lists.done": "Гатова", "lists.done": "Гатова",
"lists.edit": "Рэдагаваць спіс", "lists.edit": "Рэдагаваць спіс",
"lists.exclusive": "Схаваць карыстальнікаў на Галоўнай старонцы",
"lists.exclusive_hint": "Калі ў гэтым спісе нехта ёсць, схавайце яго на сваёй Галоўнай старонцы, каб не бачыць яго допісы двойчы.",
"lists.find_users_to_add": "Знайсці карыстальнікаў, каб дадаць",
"lists.list_members_count": "{count, plural,one {# карыстальнік}other {# карыстальнікі}}",
"lists.list_name": "Назва спіса", "lists.list_name": "Назва спіса",
"lists.new_list_name": "Назва новага спіса", "lists.new_list_name": "Назва новага спіса",
"lists.no_lists_yet": "Пакуль няма спісаў.", "lists.no_lists_yet": "Пакуль няма спісаў.",
@@ -502,6 +537,7 @@
"lists.replies_policy.none": "Нікога", "lists.replies_policy.none": "Нікога",
"lists.save": "Захаваць", "lists.save": "Захаваць",
"lists.search": "Пошук", "lists.search": "Пошук",
"lists.show_replies_to": "Уключыць адказы ад карыстальнікаў са спіса",
"load_pending": "{count, plural, one {# новы элемент} few {# новыя элементы} many {# новых элементаў} other {# новых элементаў}}", "load_pending": "{count, plural, one {# новы элемент} few {# новыя элементы} many {# новых элементаў} other {# новых элементаў}}",
"loading_indicator.label": "Ідзе загрузка…", "loading_indicator.label": "Ідзе загрузка…",
"media_gallery.hide": "Схаваць", "media_gallery.hide": "Схаваць",
@@ -544,6 +580,8 @@
"navigation_bar.search_trends": "Пошук / Трэндавае", "navigation_bar.search_trends": "Пошук / Трэндавае",
"navigation_panel.collapse_followed_tags": "Згарнуць меню падпісак на хэштэгі", "navigation_panel.collapse_followed_tags": "Згарнуць меню падпісак на хэштэгі",
"navigation_panel.collapse_lists": "Згарнуць меню спісаў", "navigation_panel.collapse_lists": "Згарнуць меню спісаў",
"navigation_panel.expand_followed_tags": "Разгарнуць меню падпісак на хэштэгі",
"navigation_panel.expand_lists": "Разгарнуць меню спіса",
"not_signed_in_indicator.not_signed_in": "Вам трэба ўвайсці каб атрымаць доступ да гэтага рэсурсу.", "not_signed_in_indicator.not_signed_in": "Вам трэба ўвайсці каб атрымаць доступ да гэтага рэсурсу.",
"notification.admin.report": "{name} паскардзіўся на {target}", "notification.admin.report": "{name} паскардзіўся на {target}",
"notification.admin.report_account": "{name} паскардзіўся на {count, plural, one {# допіс} many {# допісаў} other {# допіса}} ад {target} з прычыны {category}", "notification.admin.report_account": "{name} паскардзіўся на {count, plural, one {# допіс} many {# допісаў} other {# допіса}} ад {target} з прычыны {category}",
@@ -551,16 +589,21 @@
"notification.admin.report_statuses": "{name} паскардзіўся на {target} з прычыны {category}", "notification.admin.report_statuses": "{name} паскардзіўся на {target} з прычыны {category}",
"notification.admin.report_statuses_other": "{name} паскардзіўся на {target}", "notification.admin.report_statuses_other": "{name} паскардзіўся на {target}",
"notification.admin.sign_up": "{name} зарэгістраваўся", "notification.admin.sign_up": "{name} зарэгістраваўся",
"notification.admin.sign_up.name_and_others": "{name} і {count, plural, one {# іншы} other {# іншых}} зарэгістраваліся",
"notification.annual_report.message": "Вас чакае Ваш #Wrapstodon нумар {year}! Падзяліцеся сваімі галоўнымі падзеямі і запамінальнымі момантамі ў Mastodon!",
"notification.annual_report.view": "Перайсці да #Wrapstodon", "notification.annual_report.view": "Перайсці да #Wrapstodon",
"notification.favourite": "Ваш допіс упадабаны {name}", "notification.favourite": "Карыстальнік {name} упадабаў Ваш допіс",
"notification.favourite.name_and_others_with_link": "{name} і <a>{count, plural, one {# іншы} other {# іншыя}}</a> ўпадабалі Ваш допіс",
"notification.favourite_pm": "Ваша асабістае згадванне ўпадабана {name}", "notification.favourite_pm": "Ваша асабістае згадванне ўпадабана {name}",
"notification.favourite_pm.name_and_others_with_link": "{name} і <a>{count, plural, one {# іншы} few {# іншыя} many {# іншых} other {# іншых}}</a> ўпадабалі ваша асабістае згадванне", "notification.favourite_pm.name_and_others_with_link": "{name} і <a>{count, plural, one {# іншы} few {# іншыя} many {# іншых} other {# іншых}}</a> ўпадабалі ваша асабістае згадванне",
"notification.follow": "{name} падпісаўся на вас", "notification.follow": "{name} падпісаўся на вас",
"notification.follow.name_and_others": "{name} і <a>{count, plural, one {# іншы} other {# іншыя}}</a> падпісаліся на Вас",
"notification.follow_request": "{name} адправіў запыт на падпіску", "notification.follow_request": "{name} адправіў запыт на падпіску",
"notification.follow_request.name_and_others": "{name} і {count, plural, one {# іншы} many {# іншых} other {# іншых}} запыталіся падпісацца на вас", "notification.follow_request.name_and_others": "{name} і {count, plural, one {# іншы} many {# іншых} other {# іншых}} запыталіся падпісацца на вас",
"notification.label.mention": "Згадванне", "notification.label.mention": "Згадванне",
"notification.label.private_mention": "Асабістае згадванне", "notification.label.private_mention": "Асабістае згадванне",
"notification.label.private_reply": "Асабісты адказ", "notification.label.private_reply": "Асабісты адказ",
"notification.label.quote": "Карыстальнік {name} цытаваў Ваш допіс",
"notification.label.reply": "Адказ", "notification.label.reply": "Адказ",
"notification.mention": "Згадванне", "notification.mention": "Згадванне",
"notification.mentioned_you": "{name} згадаў вас", "notification.mentioned_you": "{name} згадаў вас",
@@ -576,7 +619,7 @@
"notification.own_poll": "Ваша апытанне скончылася", "notification.own_poll": "Ваша апытанне скончылася",
"notification.poll": "Апытанне, дзе вы прынялі ўдзел, скончылася", "notification.poll": "Апытанне, дзе вы прынялі ўдзел, скончылася",
"notification.reblog": "{name} пашырыў ваш допіс", "notification.reblog": "{name} пашырыў ваш допіс",
"notification.reblog.name_and_others_with_link": "{name} і <a>{count, plural, one {# іншы} many {# іншых} other {# іншых}}</a> абагулілі ваш допіс", "notification.reblog.name_and_others_with_link": "{name} і <a>{count, plural, one {# іншы} many {# іншых} other {# іншых}}</a> пашырылі ваш допіс",
"notification.relationships_severance_event": "Страціў сувязь з {name}", "notification.relationships_severance_event": "Страціў сувязь з {name}",
"notification.relationships_severance_event.account_suspension": "Адміністратар з {from} прыпыніў працу {target}, што азначае, што вы больш не можаце атрымліваць ад іх абнаўлення ці ўзаемадзейнічаць з імі.", "notification.relationships_severance_event.account_suspension": "Адміністратар з {from} прыпыніў працу {target}, што азначае, што вы больш не можаце атрымліваць ад іх абнаўлення ці ўзаемадзейнічаць з імі.",
"notification.relationships_severance_event.domain_block": "Адміністратар з {from} заблакіраваў {target}, у тым ліку {followersCount} вашых падпісчыка(-аў) і {followingCount, plural, one {# уліковы запіс} few {# уліковыя запісы} many {# уліковых запісаў} other {# уліковых запісаў}}.", "notification.relationships_severance_event.domain_block": "Адміністратар з {from} заблакіраваў {target}, у тым ліку {followersCount} вашых падпісчыка(-аў) і {followingCount, plural, one {# уліковы запіс} few {# уліковыя запісы} many {# уліковых запісаў} other {# уліковых запісаў}}.",
@@ -585,11 +628,19 @@
"notification.status": "Новы допіс ад {name}", "notification.status": "Новы допіс ад {name}",
"notification.update": "Допіс {name} адрэдагаваны", "notification.update": "Допіс {name} адрэдагаваны",
"notification_requests.accept": "Прыняць", "notification_requests.accept": "Прыняць",
"notification_requests.accept_multiple": "{count, plural,one {Прыняць # запыт…} other {Прыняць # запытаў…}}",
"notification_requests.confirm_accept_multiple.button": "{count, plural,one {Прыняць запыт} other {Прыняць запыты}}",
"notification_requests.confirm_accept_multiple.message": "Вы збіраецеся прыняць {count, plural, one {адзін запыт на апавяшчэнне} other {# запытаў на апавяшчэнне}}. Упэўненыя, што хочаце працягнуць?",
"notification_requests.confirm_accept_multiple.title": "Прыняць запыты на апавяшчэнні?", "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 {аднаго запыту на апавяшчэнне} other {# запытаў на апавяшчэнне}}. Вы не зможаце зноў лёгка атрымаць доступ да {count, plural, one {яго} other {іх}}. Упэўненыя, што хочаце працягнуць?",
"notification_requests.confirm_dismiss_multiple.title": "Адхіліць запыты на апавяшчэнні?", "notification_requests.confirm_dismiss_multiple.title": "Адхіліць запыты на апавяшчэнні?",
"notification_requests.dismiss": "Адхіліць", "notification_requests.dismiss": "Адхіліць",
"notification_requests.dismiss_multiple": "{count, plural,one {Адмовіцца ад запыту…} other {Адмовіцца ад запытаў…}}",
"notification_requests.edit_selection": "Рэдагаваць", "notification_requests.edit_selection": "Рэдагаваць",
"notification_requests.exit_selection": "Гатова", "notification_requests.exit_selection": "Гатова",
"notification_requests.explainer_for_limited_account": "Апавяшчэнне з гэтага профілю было адфільтраванае, бо гэты профіль абмежаваў мадэратар.",
"notification_requests.explainer_for_limited_remote_account": "Апавяшчэнні з гэтага профілю былі адфільтраваныя, бо гэты профіль абмежаваў мадэратар.",
"notification_requests.maximize": "Разгарнуць", "notification_requests.maximize": "Разгарнуць",
"notification_requests.minimize_banner": "Згарнуць банер адфільтраваных апавяшчэнняў", "notification_requests.minimize_banner": "Згарнуць банер адфільтраваных апавяшчэнняў",
"notification_requests.notifications_from": "Апавяшчэнні ад {name}", "notification_requests.notifications_from": "Апавяшчэнні ад {name}",
@@ -610,6 +661,7 @@
"notifications.column_settings.mention": "Згадванні:", "notifications.column_settings.mention": "Згадванні:",
"notifications.column_settings.poll": "Вынікі апытання:", "notifications.column_settings.poll": "Вынікі апытання:",
"notifications.column_settings.push": "Push-апавяшчэнні", "notifications.column_settings.push": "Push-апавяшчэнні",
"notifications.column_settings.quote": "Цытаваныя допісы:",
"notifications.column_settings.reblog": "Пашырэнні:", "notifications.column_settings.reblog": "Пашырэнні:",
"notifications.column_settings.show": "Паказваць у слупку", "notifications.column_settings.show": "Паказваць у слупку",
"notifications.column_settings.sound": "Прайграваць гук", "notifications.column_settings.sound": "Прайграваць гук",
@@ -633,7 +685,10 @@
"notifications.policy.accept": "Прыняць", "notifications.policy.accept": "Прыняць",
"notifications.policy.accept_hint": "Паказваць у апавяшчэннях", "notifications.policy.accept_hint": "Паказваць у апавяшчэннях",
"notifications.policy.drop": "Iгнараваць", "notifications.policy.drop": "Iгнараваць",
"notifications.policy.drop_hint": "Адправіць у бездань, адкуль больш ніколі не ўбачыце",
"notifications.policy.filter": "Фільтраваць", "notifications.policy.filter": "Фільтраваць",
"notifications.policy.filter_hint": "Адправіць у скрыню адфільтраваных апавяшчэнняў",
"notifications.policy.filter_limited_accounts_hint": "Абмежавана мадэратарамі сервера",
"notifications.policy.filter_limited_accounts_title": "Уліковыя запісы пад мадэрацыяй", "notifications.policy.filter_limited_accounts_title": "Уліковыя запісы пад мадэрацыяй",
"notifications.policy.filter_new_accounts.hint": "Створаныя на працягу {days, plural, one {апошняга # дня} few {апошніх # дзён} many {апошніх # дзён} other {апошняй # дня}}", "notifications.policy.filter_new_accounts.hint": "Створаныя на працягу {days, plural, one {апошняга # дня} few {апошніх # дзён} many {апошніх # дзён} other {апошняй # дня}}",
"notifications.policy.filter_new_accounts_title": "Новыя ўліковыя запісы", "notifications.policy.filter_new_accounts_title": "Новыя ўліковыя запісы",
@@ -755,6 +810,7 @@
"report_notification.categories.violation": "Парушэнне правілаў", "report_notification.categories.violation": "Парушэнне правілаў",
"report_notification.categories.violation_sentence": "парушэнне правілаў", "report_notification.categories.violation_sentence": "парушэнне правілаў",
"report_notification.open": "Адкрыць скаргу", "report_notification.open": "Адкрыць скаргу",
"search.clear": "Ачысціць пошук",
"search.no_recent_searches": "Гісторыя пошуку пустая", "search.no_recent_searches": "Гісторыя пошуку пустая",
"search.placeholder": "Пошук", "search.placeholder": "Пошук",
"search.quick_action.account_search": "Супадзенне профіляў {x}", "search.quick_action.account_search": "Супадзенне профіляў {x}",
@@ -796,6 +852,8 @@
"status.bookmark": "Дадаць закладку", "status.bookmark": "Дадаць закладку",
"status.cancel_reblog_private": "Прыбраць", "status.cancel_reblog_private": "Прыбраць",
"status.cannot_reblog": "Гэты пост нельга пашырыць", "status.cannot_reblog": "Гэты пост нельга пашырыць",
"status.context.load_new_replies": "Даступныя новыя адказы",
"status.context.loading": "Правяраюцца новыя адказы",
"status.continued_thread": "Працяг ланцужка", "status.continued_thread": "Працяг ланцужка",
"status.copy": "Скапіраваць спасылку на допіс", "status.copy": "Скапіраваць спасылку на допіс",
"status.delete": "Выдаліць", "status.delete": "Выдаліць",
@@ -807,7 +865,7 @@
"status.edited_x_times": "Рэдагавана {count, plural, one {{count} раз} few {{count} разы} many {{count} разоў} other {{count} разу}}", "status.edited_x_times": "Рэдагавана {count, plural, one {{count} раз} few {{count} разы} many {{count} разоў} other {{count} разу}}",
"status.embed": "Атрымаць убудаваны код", "status.embed": "Атрымаць убудаваны код",
"status.favourite": "Упадабанае", "status.favourite": "Упадабанае",
"status.favourites": "{count, plural, one {# упадабанае} few {# упадабаныя} many {# упадабаных} other {# упадабанага}}", "status.favourites": "{count, plural, one {упадабанне} few {упадабанні} other {упадабанняў}}",
"status.filter": "Фільтраваць гэты допіс", "status.filter": "Фільтраваць гэты допіс",
"status.history.created": "Створана {name} {date}", "status.history.created": "Створана {name} {date}",
"status.history.edited": "Адрэдагавана {name} {date}", "status.history.edited": "Адрэдагавана {name} {date}",
@@ -821,19 +879,27 @@
"status.mute_conversation": "Ігнараваць размову", "status.mute_conversation": "Ігнараваць размову",
"status.open": "Разгарнуць гэты допіс", "status.open": "Разгарнуць гэты допіс",
"status.pin": "Замацаваць у профілі", "status.pin": "Замацаваць у профілі",
"status.quote_error.filtered": "Схавана адным з Вашых фільтраў",
"status.quote_error.not_available": "Допіс недаступны",
"status.quote_error.pending_approval": "Допіс чакае пацвярджэння",
"status.quote_error.pending_approval_popout.body": "Допісы, якія былі цытаваныя паміж серверамі Fediverse, могуць доўга загружацца, паколькі розныя серверы маюць розныя пратаколы.",
"status.quote_error.pending_approval_popout.title": "Цытаваны допіс чакае пацвярджэння? Захоўвайце спакой",
"status.quote_post_author": "Цытаваў допіс @{name}",
"status.read_more": "Чытаць болей", "status.read_more": "Чытаць болей",
"status.reblog": "Пашырыць", "status.reblog": "Пашырыць",
"status.reblog_private": "Пашырыць з першапачатковай бачнасцю", "status.reblog_private": "Пашырыць з першапачатковай бачнасцю",
"status.reblogged_by": "{name} пашырыў(-ла)", "status.reblogged_by": "Карыстальнік {name} пашырыў",
"status.reblogs": "{count, plural, one {# пашырэнне} few {# пашырэнні} many {# пашырэнняў} other {# пашырэння}}", "status.reblogs": "{count, plural, one {пашырэнне} few {пашырэнні} many {пашырэнняў} other {пашырэння}}",
"status.reblogs.empty": "Гэты допіс яшчэ ніхто не пашырыў. Калі гэта адбудзецца, гэтых людзей будзе бачна тут.", "status.reblogs.empty": "Гэты допіс яшчэ ніхто не пашырыў. Калі гэта адбудзецца, гэтых людзей будзе бачна тут.",
"status.redraft": "Выдаліць і паправіць", "status.redraft": "Выдаліць і перапісаць",
"status.remove_bookmark": "Выдаліць закладку", "status.remove_bookmark": "Выдаліць закладку",
"status.remove_favourite": "Выдаліць з упадабаных",
"status.replied_in_thread": "Адказаў у ланцужку", "status.replied_in_thread": "Адказаў у ланцужку",
"status.replied_to": "Адказаў {name}", "status.replied_to": "Адказаў {name}",
"status.reply": "Адказаць", "status.reply": "Адказаць",
"status.replyAll": "Адказаць у ланцугу", "status.replyAll": "Адказаць у ланцугу",
"status.report": "Паскардзіцца на @{name}", "status.report": "Паскардзіцца на @{name}",
"status.revoke_quote": "Выдаліць мой допіс з допісу @{name}",
"status.sensitive_warning": "Уражвальны змест", "status.sensitive_warning": "Уражвальны змест",
"status.share": "Абагуліць", "status.share": "Абагуліць",
"status.show_less_all": "Згарнуць усё", "status.show_less_all": "Згарнуць усё",
@@ -853,7 +919,9 @@
"tabs_bar.notifications": "Апавяшчэнні", "tabs_bar.notifications": "Апавяшчэнні",
"tabs_bar.publish": "Новы допіс", "tabs_bar.publish": "Новы допіс",
"tabs_bar.search": "Пошук", "tabs_bar.search": "Пошук",
"terms_of_service.effective_as_of": "Дзейнічае да {date}",
"terms_of_service.title": "Умовы выкарыстання", "terms_of_service.title": "Умовы выкарыстання",
"terms_of_service.upcoming_changes_on": "Змены, якія адбудуцца {date}",
"time_remaining.days": "{number, plural, one {застаўся # дзень} few {засталося # дні} many {засталося # дзён} other {засталося # дня}}", "time_remaining.days": "{number, plural, one {застаўся # дзень} few {засталося # дні} many {засталося # дзён} other {засталося # дня}}",
"time_remaining.hours": "{number, plural, one {засталася # гадзіна} few {засталося # гадзіны} many {засталося # гадзін} other {засталося # гадзіны}}", "time_remaining.hours": "{number, plural, one {засталася # гадзіна} few {засталося # гадзіны} many {засталося # гадзін} other {засталося # гадзіны}}",
"time_remaining.minutes": "{number, plural, one {засталася # хвіліна} few {засталося # хвіліны} many {засталося # хвілін} other {засталося # хвіліны}}", "time_remaining.minutes": "{number, plural, one {засталася # хвіліна} few {засталося # хвіліны} many {засталося # хвілін} other {засталося # хвіліны}}",
@@ -869,6 +937,11 @@
"upload_button.label": "Дадаць выяву, відэа- ці аўдыяфайл", "upload_button.label": "Дадаць выяву, відэа- ці аўдыяфайл",
"upload_error.limit": "Перавышана колькасць файлаў.", "upload_error.limit": "Перавышана колькасць файлаў.",
"upload_error.poll": "Немагчыма прымацаваць файл да апытання.", "upload_error.poll": "Немагчыма прымацаваць файл да апытання.",
"upload_form.drag_and_drop.instructions": "Каб абраць медыя далучэнне, націсніце прабел ці Enter. Падчас перасоўвання выкарыстоўвайце кнопкі са стрэлкамі, каб пасунуць медыя далучэнне ў любым напрамку. Націсніце прабел ці Enter зноў, каб перасунуць медыя далучэнне ў новае месца, або 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.edit": "Рэдагаваць",
"upload_progress.label": "Запампоўванне...", "upload_progress.label": "Запампоўванне...",
"upload_progress.processing": "Апрацоўка…", "upload_progress.processing": "Апрацоўка…",

View File

@@ -899,7 +899,7 @@
"status.reply": "Besvar", "status.reply": "Besvar",
"status.replyAll": "Svar alle", "status.replyAll": "Svar alle",
"status.report": "Anmeld @{name}", "status.report": "Anmeld @{name}",
"status.revoke_quote": "Fjern mit indlæg fra @{name}'s indlæg", "status.revoke_quote": "Fjern eget indlæg fra @{name}s indlæg",
"status.sensitive_warning": "Følsomt indhold", "status.sensitive_warning": "Følsomt indhold",
"status.share": "Del", "status.share": "Del",
"status.show_less_all": "Vis mindre for alle", "status.show_less_all": "Vis mindre for alle",

View File

@@ -245,6 +245,9 @@
"confirmations.remove_from_followers.confirm": "Αφαίρεση ακολούθου", "confirmations.remove_from_followers.confirm": "Αφαίρεση ακολούθου",
"confirmations.remove_from_followers.message": "Ο χρήστης {name} θα σταματήσει να σε ακολουθεί. Σίγουρα θες να συνεχίσεις;", "confirmations.remove_from_followers.message": "Ο χρήστης {name} θα σταματήσει να σε ακολουθεί. Σίγουρα θες να συνεχίσεις;",
"confirmations.remove_from_followers.title": "Αφαίρεση ακολούθου;", "confirmations.remove_from_followers.title": "Αφαίρεση ακολούθου;",
"confirmations.revoke_quote.confirm": "Αφαίρεση ανάρτησης",
"confirmations.revoke_quote.message": "Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.",
"confirmations.revoke_quote.title": "Αφαίρεση ανάρτησης;",
"confirmations.unfollow.confirm": "Άρση ακολούθησης", "confirmations.unfollow.confirm": "Άρση ακολούθησης",
"confirmations.unfollow.message": "Σίγουρα θες να πάψεις να ακολουθείς τον/την {name};", "confirmations.unfollow.message": "Σίγουρα θες να πάψεις να ακολουθείς τον/την {name};",
"confirmations.unfollow.title": "Άρση ακολούθησης;", "confirmations.unfollow.title": "Άρση ακολούθησης;",
@@ -896,6 +899,7 @@
"status.reply": "Απάντησε", "status.reply": "Απάντησε",
"status.replyAll": "Απάντησε στο νήμα συζήτησης", "status.replyAll": "Απάντησε στο νήμα συζήτησης",
"status.report": "Αναφορά @{name}", "status.report": "Αναφορά @{name}",
"status.revoke_quote": "Αφαίρεση της ανάρτησης μου από την ανάρτηση του/της @{name}",
"status.sensitive_warning": "Ευαίσθητο περιεχόμενο", "status.sensitive_warning": "Ευαίσθητο περιεχόμενο",
"status.share": "Κοινοποίηση", "status.share": "Κοινοποίηση",
"status.show_less_all": "Δείξε λιγότερο για όλες", "status.show_less_all": "Δείξε λιγότερο για όλες",

View File

@@ -245,6 +245,9 @@
"confirmations.remove_from_followers.confirm": "Poista seuraaja", "confirmations.remove_from_followers.confirm": "Poista seuraaja",
"confirmations.remove_from_followers.message": "{name} lakkaa seuraamasta sinua. Haluatko varmasti jatkaa?", "confirmations.remove_from_followers.message": "{name} lakkaa seuraamasta sinua. Haluatko varmasti jatkaa?",
"confirmations.remove_from_followers.title": "Poistetaanko seuraaja?", "confirmations.remove_from_followers.title": "Poistetaanko seuraaja?",
"confirmations.revoke_quote.confirm": "Poista julkaisu",
"confirmations.revoke_quote.message": "Tätä toimea ei voi peruuttaa.",
"confirmations.revoke_quote.title": "Poistetaanko julkaisu?",
"confirmations.unfollow.confirm": "Lopeta seuraaminen", "confirmations.unfollow.confirm": "Lopeta seuraaminen",
"confirmations.unfollow.message": "Haluatko varmasti lopettaa profiilin {name} seuraamisen?", "confirmations.unfollow.message": "Haluatko varmasti lopettaa profiilin {name} seuraamisen?",
"confirmations.unfollow.title": "Lopetetaanko käyttäjän seuraaminen?", "confirmations.unfollow.title": "Lopetetaanko käyttäjän seuraaminen?",
@@ -896,6 +899,7 @@
"status.reply": "Vastaa", "status.reply": "Vastaa",
"status.replyAll": "Vastaa ketjuun", "status.replyAll": "Vastaa ketjuun",
"status.report": "Raportoi @{name}", "status.report": "Raportoi @{name}",
"status.revoke_quote": "Poista julkaisuni käyttäjän @{name} julkaisusta",
"status.sensitive_warning": "Arkaluonteista sisältöä", "status.sensitive_warning": "Arkaluonteista sisältöä",
"status.share": "Jaa", "status.share": "Jaa",
"status.show_less_all": "Näytä kaikista vähemmän", "status.show_less_all": "Näytä kaikista vähemmän",

View File

@@ -245,6 +245,9 @@
"confirmations.remove_from_followers.confirm": "Bain leantóir", "confirmations.remove_from_followers.confirm": "Bain leantóir",
"confirmations.remove_from_followers.message": "Scoirfidh {name} de bheith ag leanúint leat. An bhfuil tú cinnte gur mian leat leanúint ar aghaidh?", "confirmations.remove_from_followers.message": "Scoirfidh {name} de bheith ag leanúint leat. An bhfuil tú cinnte gur mian leat leanúint ar aghaidh?",
"confirmations.remove_from_followers.title": "Bain an leantóir?", "confirmations.remove_from_followers.title": "Bain an leantóir?",
"confirmations.revoke_quote.confirm": "Bain postáil",
"confirmations.revoke_quote.message": "Ní féidir an gníomh seo a chealú.",
"confirmations.revoke_quote.title": "Bain postáil?",
"confirmations.unfollow.confirm": "Ná lean", "confirmations.unfollow.confirm": "Ná lean",
"confirmations.unfollow.message": "An bhfuil tú cinnte gur mhaith leat {name} a dhíleanúint?", "confirmations.unfollow.message": "An bhfuil tú cinnte gur mhaith leat {name} a dhíleanúint?",
"confirmations.unfollow.title": "Dílean an t-úsáideoir?", "confirmations.unfollow.title": "Dílean an t-úsáideoir?",
@@ -896,6 +899,7 @@
"status.reply": "Freagair", "status.reply": "Freagair",
"status.replyAll": "Freagair le snáithe", "status.replyAll": "Freagair le snáithe",
"status.report": "Tuairiscigh @{name}", "status.report": "Tuairiscigh @{name}",
"status.revoke_quote": "Bain mo phost ó phost @{name}",
"status.sensitive_warning": "Ábhar íogair", "status.sensitive_warning": "Ábhar íogair",
"status.share": "Comhroinn", "status.share": "Comhroinn",
"status.show_less_all": "Taispeáin níos lú d'uile", "status.show_less_all": "Taispeáin níos lú d'uile",

View File

@@ -245,6 +245,9 @@
"confirmations.remove_from_followers.confirm": "Követő eltávolítása", "confirmations.remove_from_followers.confirm": "Követő eltávolítása",
"confirmations.remove_from_followers.message": "{name} követ téged. Biztos, hogy folytatod?", "confirmations.remove_from_followers.message": "{name} követ téged. Biztos, hogy folytatod?",
"confirmations.remove_from_followers.title": "Követő eltávolítása?", "confirmations.remove_from_followers.title": "Követő eltávolítása?",
"confirmations.revoke_quote.confirm": "Bejegyzés eltávolítása",
"confirmations.revoke_quote.message": "Ez a művelet nem vonható vissza.",
"confirmations.revoke_quote.title": "Bejegyzés eltávolítása?",
"confirmations.unfollow.confirm": "Követés visszavonása", "confirmations.unfollow.confirm": "Követés visszavonása",
"confirmations.unfollow.message": "Biztos, hogy vissza szeretnéd vonni {name} követését?", "confirmations.unfollow.message": "Biztos, hogy vissza szeretnéd vonni {name} követését?",
"confirmations.unfollow.title": "Megszünteted a felhasználó követését?", "confirmations.unfollow.title": "Megszünteted a felhasználó követését?",
@@ -896,6 +899,7 @@
"status.reply": "Válasz", "status.reply": "Válasz",
"status.replyAll": "Válasz a beszélgetésre", "status.replyAll": "Válasz a beszélgetésre",
"status.report": "@{name} bejelentése", "status.report": "@{name} bejelentése",
"status.revoke_quote": "Saját bejegyzés eltávolítása @{name} bejegyzéséből",
"status.sensitive_warning": "Kényes tartalom", "status.sensitive_warning": "Kényes tartalom",
"status.share": "Megosztás", "status.share": "Megosztás",
"status.show_less_all": "Kevesebbet mindenhol", "status.show_less_all": "Kevesebbet mindenhol",

View File

@@ -85,9 +85,11 @@
"alt_text_modal.cancel": "Semmet", "alt_text_modal.cancel": "Semmet",
"alt_text_modal.done": "Immed", "alt_text_modal.done": "Immed",
"announcement.announcement": "Ulɣu", "announcement.announcement": "Ulɣu",
"annual_report.summary.followers.followers": "imeḍfaṛen",
"annual_report.summary.most_used_app.most_used_app": "asnas yettwasqedcen s waṭas", "annual_report.summary.most_used_app.most_used_app": "asnas yettwasqedcen s waṭas",
"annual_report.summary.most_used_hashtag.none": "Ula yiwen", "annual_report.summary.most_used_hashtag.none": "Ula yiwen",
"annual_report.summary.new_posts.new_posts": "tisuffaɣ timaynutin", "annual_report.summary.new_posts.new_posts": "tisuffaɣ timaynutin",
"annual_report.summary.percentile.we_wont_tell_bernie": "Ur as-neqqar i yiwen.",
"annual_report.summary.thanks": "Tanemmirt imi i tettekkiḍ deg Mastodon!", "annual_report.summary.thanks": "Tanemmirt imi i tettekkiḍ deg Mastodon!",
"audio.hide": "Ffer amesli", "audio.hide": "Ffer amesli",
"block_modal.show_less": "Ssken-d drus", "block_modal.show_less": "Ssken-d drus",
@@ -123,6 +125,7 @@
"column.firehose": "Isuddam usriden", "column.firehose": "Isuddam usriden",
"column.follow_requests": "Isuturen n teḍfeṛt", "column.follow_requests": "Isuturen n teḍfeṛt",
"column.home": "Agejdan", "column.home": "Agejdan",
"column.list_members": "Sefrek iεeggalen n tebdart",
"column.lists": "Tibdarin", "column.lists": "Tibdarin",
"column.mutes": "Imiḍanen yettwasgugmen", "column.mutes": "Imiḍanen yettwasgugmen",
"column.notifications": "Ilɣa", "column.notifications": "Ilɣa",
@@ -160,6 +163,7 @@
"compose_form.save_changes": "Leqqem", "compose_form.save_changes": "Leqqem",
"compose_form.spoiler.marked": "Kkes aḍris yettwaffren deffir n walɣu", "compose_form.spoiler.marked": "Kkes aḍris yettwaffren deffir n walɣu",
"compose_form.spoiler.unmarked": "Rnu aḍris yettwaffren deffir n walɣu", "compose_form.spoiler.unmarked": "Rnu aḍris yettwaffren deffir n walɣu",
"compose_form.spoiler_placeholder": "Alɣu n ugbur (afrayan)",
"confirmation_modal.cancel": "Sefsex", "confirmation_modal.cancel": "Sefsex",
"confirmations.block.confirm": "Sewḥel", "confirmations.block.confirm": "Sewḥel",
"confirmations.delete.confirm": "Kkes", "confirmations.delete.confirm": "Kkes",
@@ -168,8 +172,10 @@
"confirmations.delete_list.confirm": "Kkes", "confirmations.delete_list.confirm": "Kkes",
"confirmations.delete_list.message": "Tebɣiḍ s tidet ad tekkseḍ umuɣ-agi i lebda?", "confirmations.delete_list.message": "Tebɣiḍ s tidet ad tekkseḍ umuɣ-agi i lebda?",
"confirmations.delete_list.title": "Tukksa n tebdart?", "confirmations.delete_list.title": "Tukksa n tebdart?",
"confirmations.discard_draft.confirm": "Ttu-t u kemmel",
"confirmations.discard_edit_media.confirm": "Sefsex", "confirmations.discard_edit_media.confirm": "Sefsex",
"confirmations.follow_to_list.confirm": "Ḍfeṛ-it sakin rnu-t ɣer tebdart", "confirmations.follow_to_list.confirm": "Ḍfeṛ-it sakin rnu-t ɣer tebdart",
"confirmations.follow_to_list.title": "Ḍfer aseqdac?",
"confirmations.logout.confirm": "Ffeɣ", "confirmations.logout.confirm": "Ffeɣ",
"confirmations.logout.message": "D tidet tebɣiḍ ad teffɣeḍ?", "confirmations.logout.message": "D tidet tebɣiḍ ad teffɣeḍ?",
"confirmations.logout.title": "Tebɣiḍ ad teffɣeḍ ssya?", "confirmations.logout.title": "Tebɣiḍ ad teffɣeḍ ssya?",
@@ -178,6 +184,8 @@
"confirmations.missing_alt_text.title": "Rnu aḍris amlellay?", "confirmations.missing_alt_text.title": "Rnu aḍris amlellay?",
"confirmations.mute.confirm": "Sgugem", "confirmations.mute.confirm": "Sgugem",
"confirmations.redraft.confirm": "Kkes sakin ɛiwed tira", "confirmations.redraft.confirm": "Kkes sakin ɛiwed tira",
"confirmations.remove_from_followers.confirm": "Kkes aneḍfar",
"confirmations.revoke_quote.confirm": "Kkes tasuffeɣt",
"confirmations.unfollow.confirm": "Ur ḍḍafaṛ ara", "confirmations.unfollow.confirm": "Ur ḍḍafaṛ ara",
"confirmations.unfollow.message": "Tetḥeqqeḍ belli tebɣiḍ ur teṭafaṛeḍ ara {name}?", "confirmations.unfollow.message": "Tetḥeqqeḍ belli tebɣiḍ ur teṭafaṛeḍ ara {name}?",
"content_warning.hide": "Ffer tasuffeɣt", "content_warning.hide": "Ffer tasuffeɣt",
@@ -203,7 +211,12 @@
"domain_block_modal.you_wont_see_posts": "Ur tettuɣaleḍ ara ttwaliḍ tisuffaɣ neɣ ulɣuten n iseqdacen n uqeddac-a.", "domain_block_modal.you_wont_see_posts": "Ur tettuɣaleḍ ara ttwaliḍ tisuffaɣ neɣ ulɣuten n iseqdacen n uqeddac-a.",
"domain_pill.activitypub_like_language": "ActivityPub am tutlayt yettmeslay Mastodon d izeḍwan inmettiyen nniḍen.", "domain_pill.activitypub_like_language": "ActivityPub am tutlayt yettmeslay Mastodon d izeḍwan inmettiyen nniḍen.",
"domain_pill.server": "Aqeddac", "domain_pill.server": "Aqeddac",
"domain_pill.their_handle": "Asulay-is:",
"domain_pill.username": "Isem n useqdac", "domain_pill.username": "Isem n useqdac",
"domain_pill.whats_in_a_handle": "D acu i yellan deg usulay?",
"domain_pill.who_they_are": "Imi isulayen qqaren-d anwa i d yiwen d wanda yella, tzemreḍ ad temyigweḍ d yemdanen deg web anmetti yebnan s <button>tɣeṛɣaṛ yemmugen s ActivityPub</button>.",
"domain_pill.who_you_are": "Imi isulay-ik·im yeqqar-d anwa i d kečč·kemmi d wanda i telliḍ, zemren medden ad myigwen yid-k·m deg web anmetti yebnan s <button>tɣeṛɣaṛ yemmugen s ActivityPub</button>.",
"domain_pill.your_handle": "Asulay-ik·im:",
"domain_pill.your_server": "D axxam-inek·inem umḍin, anda i zedɣent akk tsuffaɣ-ik·im. Ur k·m-yeεǧib ara wa? Ssenfel-d iqeddacen melmi i ak·m-yehwa, awi-d daɣen ineḍfaren-ik·im yid-k·m.", "domain_pill.your_server": "D axxam-inek·inem umḍin, anda i zedɣent akk tsuffaɣ-ik·im. Ur k·m-yeεǧib ara wa? Ssenfel-d iqeddacen melmi i ak·m-yehwa, awi-d daɣen ineḍfaren-ik·im yid-k·m.",
"embed.instructions": "Ẓẓu addad-agi deg usmel-inek·inem s wenɣal n tangalt yellan sdaw-agi.", "embed.instructions": "Ẓẓu addad-agi deg usmel-inek·inem s wenɣal n tangalt yellan sdaw-agi.",
"embed.preview": "Akka ara d-iban:", "embed.preview": "Akka ara d-iban:",
@@ -264,6 +277,7 @@
"firehose.remote": "Iqeddacen nniḍen", "firehose.remote": "Iqeddacen nniḍen",
"follow_request.authorize": "Ssireg", "follow_request.authorize": "Ssireg",
"follow_request.reject": "Agi", "follow_request.reject": "Agi",
"follow_suggestions.curated_suggestion": "Yettwafren sɣur tarbaɛt",
"follow_suggestions.dismiss": "Dayen ur t-id-skan ara", "follow_suggestions.dismiss": "Dayen ur t-id-skan ara",
"follow_suggestions.featured_longer": "Yettwafraned s ufus sɣur agraw n {domain}", "follow_suggestions.featured_longer": "Yettwafraned s ufus sɣur agraw n {domain}",
"follow_suggestions.friends_of_friends_longer": "D aɣeṛfan ar wid i teṭṭafareḍ", "follow_suggestions.friends_of_friends_longer": "D aɣeṛfan ar wid i teṭṭafareḍ",
@@ -301,6 +315,7 @@
"hashtag.follow": "Ḍfeṛ ahacṭag", "hashtag.follow": "Ḍfeṛ ahacṭag",
"hashtag.mute": "Sgugem #{hashtag}", "hashtag.mute": "Sgugem #{hashtag}",
"hashtags.and_other": "…d {count, plural, one {}other {# nniḍen}}", "hashtags.and_other": "…d {count, plural, one {}other {# nniḍen}}",
"home.column_settings.show_quotes": "Sken-d tibdarin",
"home.column_settings.show_reblogs": "Ssken-d beṭṭu", "home.column_settings.show_reblogs": "Ssken-d beṭṭu",
"home.column_settings.show_replies": "Ssken-d tiririyin", "home.column_settings.show_replies": "Ssken-d tiririyin",
"home.hide_announcements": "Ffer ulɣuyen", "home.hide_announcements": "Ffer ulɣuyen",
@@ -354,6 +369,7 @@
"keyboard_shortcuts.toggle_hidden": "i uskan/tuffra n uḍris deffir CW", "keyboard_shortcuts.toggle_hidden": "i uskan/tuffra n uḍris deffir CW",
"keyboard_shortcuts.toggle_sensitivity": "i teskent/tuffra n yimidyaten", "keyboard_shortcuts.toggle_sensitivity": "i teskent/tuffra n yimidyaten",
"keyboard_shortcuts.toot": "i wakken attebdud tajewwaqt tamaynut", "keyboard_shortcuts.toot": "i wakken attebdud tajewwaqt tamaynut",
"keyboard_shortcuts.translate": "i usuqel n tsuffeɣt",
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
"keyboard_shortcuts.up": "i tulin ɣer d asawen n tebdart", "keyboard_shortcuts.up": "i tulin ɣer d asawen n tebdart",
"learn_more_link.got_it": "Gziɣ-t", "learn_more_link.got_it": "Gziɣ-t",
@@ -458,6 +474,7 @@
"notifications.column_settings.mention": "Abdar:", "notifications.column_settings.mention": "Abdar:",
"notifications.column_settings.poll": "Igemmaḍ n usenqed:", "notifications.column_settings.poll": "Igemmaḍ n usenqed:",
"notifications.column_settings.push": "Ilɣa yettudemmren", "notifications.column_settings.push": "Ilɣa yettudemmren",
"notifications.column_settings.quote": "Yebder-d:",
"notifications.column_settings.reblog": "Seǧhed:", "notifications.column_settings.reblog": "Seǧhed:",
"notifications.column_settings.show": "Ssken-d tilɣa deg ujgu", "notifications.column_settings.show": "Ssken-d tilɣa deg ujgu",
"notifications.column_settings.sound": "Rmed imesli", "notifications.column_settings.sound": "Rmed imesli",
@@ -631,6 +648,7 @@
"status.mute_conversation": "Sgugem adiwenni", "status.mute_conversation": "Sgugem adiwenni",
"status.open": "Semɣeṛ tasuffeɣt-ayi", "status.open": "Semɣeṛ tasuffeɣt-ayi",
"status.pin": "Senteḍ-itt deg umaɣnu", "status.pin": "Senteḍ-itt deg umaɣnu",
"status.quote_post_author": "Yebder-d tasuffeɣt sɣur @{name}",
"status.read_more": "Issin ugar", "status.read_more": "Issin ugar",
"status.reblog": "Bḍu", "status.reblog": "Bḍu",
"status.reblogged_by": "Yebḍa-tt {name}", "status.reblogged_by": "Yebḍa-tt {name}",
@@ -650,7 +668,7 @@
"status.show_original": "Sken aɣbalu", "status.show_original": "Sken aɣbalu",
"status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}", "status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}",
"status.translate": "Suqel", "status.translate": "Suqel",
"status.translated_from_with": "Yettwasuqel seg {lang} s {provider}", "status.translated_from_with": "Tettwasuqel seg {lang} s {provider}",
"status.uncached_media_warning": "Ulac taskant", "status.uncached_media_warning": "Ulac taskant",
"status.unmute_conversation": "Kkes asgugem n udiwenni", "status.unmute_conversation": "Kkes asgugem n udiwenni",
"status.unpin": "Kkes asenteḍ seg umaɣnu", "status.unpin": "Kkes asenteḍ seg umaɣnu",

View File

@@ -245,6 +245,9 @@
"confirmations.remove_from_followers.confirm": "팔로워 제거", "confirmations.remove_from_followers.confirm": "팔로워 제거",
"confirmations.remove_from_followers.message": "{name} 님이 나를 팔로우하지 않게 됩니다. 계속할까요?", "confirmations.remove_from_followers.message": "{name} 님이 나를 팔로우하지 않게 됩니다. 계속할까요?",
"confirmations.remove_from_followers.title": "팔로워를 제거할까요?", "confirmations.remove_from_followers.title": "팔로워를 제거할까요?",
"confirmations.revoke_quote.confirm": "게시물 삭제",
"confirmations.revoke_quote.message": "이 작업은 되돌릴 수 없습니다.",
"confirmations.revoke_quote.title": "게시물을 지울까요?",
"confirmations.unfollow.confirm": "팔로우 해제", "confirmations.unfollow.confirm": "팔로우 해제",
"confirmations.unfollow.message": "정말로 {name} 님을 팔로우 해제하시겠습니까?", "confirmations.unfollow.message": "정말로 {name} 님을 팔로우 해제하시겠습니까?",
"confirmations.unfollow.title": "사용자를 언팔로우 할까요?", "confirmations.unfollow.title": "사용자를 언팔로우 할까요?",
@@ -885,6 +888,7 @@
"status.reply": "답장", "status.reply": "답장",
"status.replyAll": "글타래에 답장", "status.replyAll": "글타래에 답장",
"status.report": "@{name} 신고하기", "status.report": "@{name} 신고하기",
"status.revoke_quote": "내 게시물을 @{name}의 게시물에서 삭제",
"status.sensitive_warning": "민감한 내용", "status.sensitive_warning": "민감한 내용",
"status.share": "공유", "status.share": "공유",
"status.show_less_all": "모두 접기", "status.show_less_all": "모두 접기",

View File

@@ -1,6 +1,7 @@
{ {
"about.blocks": "මැදිහත්කරණ සේවාදායක", "about.blocks": "මැදිහත්කරණ සේවාදායක",
"about.contact": "සබඳතාව:", "about.contact": "සබඳතාව:",
"about.default_locale": "Default",
"about.disclaimer": "මාස්ටඩන් යනු නිදහස් විවෘත මූලාශ්‍ර මෘදුකාංගයකි. එය මාස්ටඩන් gGmbH හි වෙළඳ නාමයකි.", "about.disclaimer": "මාස්ටඩන් යනු නිදහස් විවෘත මූලාශ්‍ර මෘදුකාංගයකි. එය මාස්ටඩන් gGmbH හි වෙළඳ නාමයකි.",
"about.domain_blocks.no_reason_available": "හේතුව ලබා ගත නොහැක.", "about.domain_blocks.no_reason_available": "හේතුව ලබා ගත නොහැක.",
"about.domain_blocks.preamble": "Mastodon සාමාන්‍යයෙන් ඔබට fediverse හි වෙනත් ඕනෑම සේවාදායකයකින් අන්තර්ගතයන් බැලීමට සහ පරිශීලකයින් සමඟ අන්තර් ක්‍රියා කිරීමට ඉඩ සලසයි. මෙම විශේෂිත සේවාදායකයේ සිදු කර ඇති ව්‍යතිරේක මේවාය.", "about.domain_blocks.preamble": "Mastodon සාමාන්‍යයෙන් ඔබට fediverse හි වෙනත් ඕනෑම සේවාදායකයකින් අන්තර්ගතයන් බැලීමට සහ පරිශීලකයින් සමඟ අන්තර් ක්‍රියා කිරීමට ඉඩ සලසයි. මෙම විශේෂිත සේවාදායකයේ සිදු කර ඇති ව්‍යතිරේක මේවාය.",

View File

@@ -1,6 +1,11 @@
import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable'; import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
import { changeUploadCompose } from 'mastodon/actions/compose_typed'; import {
changeUploadCompose,
quoteComposeByStatus,
quoteComposeCancel,
setQuotePolicy,
} from 'mastodon/actions/compose_typed';
import { timelineDelete } from 'mastodon/actions/timelines_typed'; import { timelineDelete } from 'mastodon/actions/timelines_typed';
import { import {
@@ -83,6 +88,11 @@ const initialState = ImmutableMap({
resetFileKey: Math.floor((Math.random() * 0x10000)), resetFileKey: Math.floor((Math.random() * 0x10000)),
idempotencyKey: null, idempotencyKey: null,
tagHistory: ImmutableList(), tagHistory: ImmutableList(),
// Quotes
quoted_status_id: null,
quote_policy: 'public',
default_quote_policy: 'public', // Set in hydration.
}); });
const initialPoll = ImmutableMap({ const initialPoll = ImmutableMap({
@@ -117,6 +127,8 @@ function clearAll(state) {
map.set('progress', 0); map.set('progress', 0);
map.set('poll', null); map.set('poll', null);
map.set('idempotencyKey', uuid()); map.set('idempotencyKey', uuid());
map.set('quoted_status_id', null);
map.set('quote_policy', state.get('default_quote_policy'));
}); });
} }
@@ -317,6 +329,15 @@ export const composeReducer = (state = initialState, action) => {
return state.set('is_changing_upload', true); return state.set('is_changing_upload', true);
} else if (changeUploadCompose.rejected.match(action)) { } else if (changeUploadCompose.rejected.match(action)) {
return state.set('is_changing_upload', false); return state.set('is_changing_upload', false);
} else if (quoteComposeByStatus.match(action)) {
const status = action.payload;
if (status.getIn(['quote_approval', 'current_user']) === 'automatic') {
return state.set('quoted_status_id', status.get('id'));
}
} else if (quoteComposeCancel.match(action)) {
return state.set('quoted_status_id', null);
} else if (setQuotePolicy.match(action)) {
return state.set('quote_policy', action.payload);
} }
switch(action.type) { switch(action.type) {

View File

@@ -1,5 +1,12 @@
import type { GetThunkAPI } from '@reduxjs/toolkit'; import type {
import { createAsyncThunk, createSelector } from '@reduxjs/toolkit'; ActionCreatorWithPreparedPayload,
GetThunkAPI,
} from '@reduxjs/toolkit';
import {
createAsyncThunk as rtkCreateAsyncThunk,
createSelector,
createAction,
} from '@reduxjs/toolkit';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports // eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
@@ -18,7 +25,7 @@ interface AppMeta {
useLoadingBar?: boolean; useLoadingBar?: boolean;
} }
export const createAppAsyncThunk = createAsyncThunk.withTypes<{ export const createAppAsyncThunk = rtkCreateAsyncThunk.withTypes<{
state: RootState; state: RootState;
dispatch: AppDispatch; dispatch: AppDispatch;
rejectValue: AsyncThunkRejectValue; rejectValue: AsyncThunkRejectValue;
@@ -43,9 +50,88 @@ interface AppThunkOptions<Arg> {
) => boolean; ) => boolean;
} }
const createBaseAsyncThunk = createAsyncThunk.withTypes<AppThunkConfig>(); // Type definitions for the sync thunks.
type AppThunk<Arg = void, Returned = void> = (
arg: Arg,
) => (dispatch: AppDispatch, getState: () => RootState) => Returned;
export function createThunk<Arg = void, Returned = void>( type AppThunkCreator<Arg = void, Returned = void, ExtraArg = unknown> = (
arg: Arg,
api: AppThunkApi,
extra?: ExtraArg,
) => Returned;
type AppThunkActionCreator<
Arg = void,
Returned = void,
> = ActionCreatorWithPreparedPayload<
[Returned, Arg],
Returned,
string,
never,
{ arg: Arg }
>;
// Version that does not dispatch it's own action.
export function createAppThunk<Arg = void, Returned = void, ExtraArg = unknown>(
creator: AppThunkCreator<Arg, Returned, ExtraArg>,
extra?: ExtraArg,
): AppThunk<Arg, Returned>;
// Version that dispatches an named action with the result of the creator callback.
export function createAppThunk<Arg = void, Returned = void, ExtraArg = unknown>(
name: string,
creator: AppThunkCreator<Arg, Returned, ExtraArg>,
extra?: ExtraArg,
): AppThunk<Arg, Returned> & AppThunkActionCreator<Arg, Returned>;
/** Creates a thunk that dispatches an action. */
export function createAppThunk<Arg = void, Returned = void, ExtraArg = unknown>(
nameOrCreator: string | AppThunkCreator<Arg, Returned, ExtraArg>,
maybeCreatorOrExtra?: AppThunkCreator<Arg, Returned, ExtraArg> | ExtraArg,
maybeExtra?: ExtraArg,
) {
const isDispatcher = typeof nameOrCreator === 'string';
const name = isDispatcher ? nameOrCreator : undefined;
const creator = isDispatcher
? (maybeCreatorOrExtra as AppThunkCreator<Arg, Returned, ExtraArg>)
: nameOrCreator;
const extra = isDispatcher ? maybeExtra : (maybeCreatorOrExtra as ExtraArg);
let action: null | AppThunkActionCreator<Arg, Returned> = null;
// Creates a thunk that dispatches the action with the result of the creator.
const actionCreator: AppThunk<Arg, Returned> = (arg) => {
return (dispatch, getState) => {
const result = creator(arg, { dispatch, getState }, extra);
if (action) {
// Dispatches the action with the result.
const actionObj = action(result, arg);
dispatch(actionObj);
}
return result;
};
};
// No action name provided, return the thunk directly.
if (!name) {
return actionCreator;
}
// Create the action and assign the action creator to it in order
// to have things like `toString` and `match` available.
action = createAction(name, (payload: Returned, arg: Arg) => ({
payload,
meta: {
arg,
},
}));
return Object.assign({}, action, actionCreator);
}
const createBaseAsyncThunk = rtkCreateAsyncThunk.withTypes<AppThunkConfig>();
export function createAsyncThunk<Arg = void, Returned = void>(
name: string, name: string,
creator: (arg: Arg, api: AppThunkApi) => Returned | Promise<Returned>, creator: (arg: Arg, api: AppThunkApi) => Returned | Promise<Returned>,
options: AppThunkOptions<Arg> = {}, options: AppThunkOptions<Arg> = {},
@@ -104,7 +190,7 @@ export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>(
name: string, name: string,
loadData: (args: Args) => Promise<LoadDataResult>, loadData: (args: Args) => Promise<LoadDataResult>,
thunkOptions?: AppThunkOptions<Args>, thunkOptions?: AppThunkOptions<Args>,
): ReturnType<typeof createThunk<Args, LoadDataResult>>; ): ReturnType<typeof createAsyncThunk<Args, LoadDataResult>>;
// Overload when the `onData` method returns discardLoadDataInPayload, then the payload is empty // Overload when the `onData` method returns discardLoadDataInPayload, then the payload is empty
export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>( export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>(
@@ -114,7 +200,7 @@ export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>(
| AppThunkOptions<Args> | AppThunkOptions<Args>
| OnData<Args, LoadDataResult, DiscardLoadData>, | OnData<Args, LoadDataResult, DiscardLoadData>,
thunkOptions?: AppThunkOptions<Args>, thunkOptions?: AppThunkOptions<Args>,
): ReturnType<typeof createThunk<Args, void>>; ): ReturnType<typeof createAsyncThunk<Args, void>>;
// Overload when the `onData` method returns nothing, then the mayload is the `onData` result // Overload when the `onData` method returns nothing, then the mayload is the `onData` result
export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>( export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>(
@@ -124,7 +210,7 @@ export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>(
| AppThunkOptions<Args> | AppThunkOptions<Args>
| OnData<Args, LoadDataResult, void>, | OnData<Args, LoadDataResult, void>,
thunkOptions?: AppThunkOptions<Args>, thunkOptions?: AppThunkOptions<Args>,
): ReturnType<typeof createThunk<Args, LoadDataResult>>; ): ReturnType<typeof createAsyncThunk<Args, LoadDataResult>>;
// Overload when there is an `onData` method returning something // Overload when there is an `onData` method returning something
export function createDataLoadingThunk< export function createDataLoadingThunk<
@@ -138,7 +224,7 @@ export function createDataLoadingThunk<
| AppThunkOptions<Args> | AppThunkOptions<Args>
| OnData<Args, LoadDataResult, Returned>, | OnData<Args, LoadDataResult, Returned>,
thunkOptions?: AppThunkOptions<Args>, thunkOptions?: AppThunkOptions<Args>,
): ReturnType<typeof createThunk<Args, Returned>>; ): ReturnType<typeof createAsyncThunk<Args, Returned>>;
/** /**
* This function creates a Redux Thunk that handles loading data asynchronously (usually from the API), dispatching `pending`, `fullfilled` and `rejected` actions. * This function creates a Redux Thunk that handles loading data asynchronously (usually from the API), dispatching `pending`, `fullfilled` and `rejected` actions.
@@ -189,7 +275,7 @@ export function createDataLoadingThunk<
thunkOptions = maybeThunkOptions; thunkOptions = maybeThunkOptions;
} }
return createThunk<Args, Returned>( return createAsyncThunk<Args, Returned>(
name, name,
async (arg, { getState, dispatch }) => { async (arg, { getState, dispatch }) => {
const data = await loadData(arg, { const data = await loadData(arg, {

View File

@@ -127,6 +127,10 @@ module User::HasSettings
settings['hide_followers_count'] settings['hide_followers_count']
end end
def setting_default_quote_policy
settings['default_quote_policy'] || 'public'
end
def allows_report_emails? def allows_report_emails?
settings['notification_emails.report'] settings['notification_emails.report']
end end

View File

@@ -9,6 +9,7 @@
# capabilities :jsonb not null # capabilities :jsonb not null
# confirmed :boolean default(FALSE), not null # confirmed :boolean default(FALSE), not null
# contact_email :string # contact_email :string
# delivery_last_failed_at :datetime
# fediverse_account :string # fediverse_account :string
# name :string not null # name :string not null
# privacy_policy :jsonb # privacy_policy :jsonb
@@ -22,6 +23,8 @@
class Fasp::Provider < ApplicationRecord class Fasp::Provider < ApplicationRecord
include DebugConcern include DebugConcern
RETRY_INTERVAL = 1.hour
has_many :fasp_backfill_requests, inverse_of: :fasp_provider, class_name: 'Fasp::BackfillRequest', dependent: :delete_all has_many :fasp_backfill_requests, inverse_of: :fasp_provider, class_name: 'Fasp::BackfillRequest', dependent: :delete_all
has_many :fasp_debug_callbacks, inverse_of: :fasp_provider, class_name: 'Fasp::DebugCallback', dependent: :delete_all has_many :fasp_debug_callbacks, inverse_of: :fasp_provider, class_name: 'Fasp::DebugCallback', dependent: :delete_all
has_many :fasp_subscriptions, inverse_of: :fasp_provider, class_name: 'Fasp::Subscription', dependent: :delete_all has_many :fasp_subscriptions, inverse_of: :fasp_provider, class_name: 'Fasp::Subscription', dependent: :delete_all
@@ -122,6 +125,16 @@ class Fasp::Provider < ApplicationRecord
@delivery_failure_tracker ||= DeliveryFailureTracker.new(base_url, resolution: :minutes) @delivery_failure_tracker ||= DeliveryFailureTracker.new(base_url, resolution: :minutes)
end end
def available?
delivery_failure_tracker.available? || retry_worthwile?
end
def update_availability!
self.delivery_last_failed_at = (Time.current unless delivery_failure_tracker.available?)
save!
end
private private
def create_keypair def create_keypair
@@ -148,4 +161,8 @@ class Fasp::Provider < ApplicationRecord
Fasp::Request.new(self).delete(path) Fasp::Request.new(self).delete(path)
end end
end end
def retry_worthwile?
delivery_last_failed_at && delivery_last_failed_at < RETRY_INTERVAL.ago
end
end end

View File

@@ -71,6 +71,7 @@ class InitialStateSerializer < ActiveModel::Serializer
store[:default_privacy] = object.visibility || object_account_user.setting_default_privacy store[:default_privacy] = object.visibility || object_account_user.setting_default_privacy
store[:default_sensitive] = object_account_user.setting_default_sensitive store[:default_sensitive] = object_account_user.setting_default_sensitive
store[:default_language] = object_account_user.preferred_posting_language store[:default_language] = object_account_user.preferred_posting_language
store[:default_quote_policy] = object_account_user.setting_default_quote_policy
end end
store[:text] = object.text if object.text store[:text] = object.text if object.text

View File

@@ -1,9 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Fasp::AccountSearchWorker class Fasp::AccountSearchWorker < Fasp::BaseWorker
include Sidekiq::Worker sidekiq_options retry: 0
sidekiq_options queue: 'fasp', retry: 0
def perform(query) def perform(query)
return unless Mastodon::Feature.fasp_enabled? return unless Mastodon::Feature.fasp_enabled?
@@ -17,11 +15,13 @@ class Fasp::AccountSearchWorker
fetch_service = ActivityPub::FetchRemoteActorService.new fetch_service = ActivityPub::FetchRemoteActorService.new
account_search_providers.each do |provider| account_search_providers.each do |provider|
Fasp::Request.new(provider).get("/account_search/v0/search?#{params}").each do |uri| with_provider(provider) do
next if Account.where(uri:).any? Fasp::Request.new(provider).get("/account_search/v0/search?#{params}").each do |uri|
next if Account.where(uri:).any?
account = fetch_service.call(uri) account = fetch_service.call(uri)
async_refresh.increment_result_count(by: 1) if account.present? async_refresh.increment_result_count(by: 1) if account.present?
end
end end
end end
ensure ensure

View File

@@ -1,13 +1,13 @@
# frozen_string_literal: true # frozen_string_literal: true
class Fasp::AnnounceAccountLifecycleEventWorker class Fasp::AnnounceAccountLifecycleEventWorker < Fasp::BaseWorker
include Sidekiq::Worker sidekiq_options retry: 5
sidekiq_options queue: 'fasp', retry: 5
def perform(uri, event_type) def perform(uri, event_type)
Fasp::Subscription.includes(:fasp_provider).category_account.lifecycle.each do |subscription| Fasp::Subscription.includes(:fasp_provider).category_account.lifecycle.each do |subscription|
announce(subscription, uri, event_type) with_provider(subscription.fasp_provider) do
announce(subscription, uri, event_type)
end
end end
end end

View File

@@ -1,13 +1,13 @@
# frozen_string_literal: true # frozen_string_literal: true
class Fasp::AnnounceContentLifecycleEventWorker class Fasp::AnnounceContentLifecycleEventWorker < Fasp::BaseWorker
include Sidekiq::Worker sidekiq_options retry: 5
sidekiq_options queue: 'fasp', retry: 5
def perform(uri, event_type) def perform(uri, event_type)
Fasp::Subscription.includes(:fasp_provider).category_content.lifecycle.each do |subscription| Fasp::Subscription.includes(:fasp_provider).category_content.lifecycle.each do |subscription|
announce(subscription, uri, event_type) with_provider(subscription.fasp_provider) do
announce(subscription, uri, event_type)
end
end end
end end

View File

@@ -1,16 +1,16 @@
# frozen_string_literal: true # frozen_string_literal: true
class Fasp::AnnounceTrendWorker class Fasp::AnnounceTrendWorker < Fasp::BaseWorker
include Sidekiq::Worker sidekiq_options retry: 5
sidekiq_options queue: 'fasp', retry: 5
def perform(status_id, trend_source) def perform(status_id, trend_source)
status = ::Status.includes(:account).find(status_id) status = ::Status.includes(:account).find(status_id)
return unless status.account.indexable? return unless status.account.indexable?
Fasp::Subscription.includes(:fasp_provider).category_content.trends.each do |subscription| Fasp::Subscription.includes(:fasp_provider).category_content.trends.each do |subscription|
announce(subscription, status.uri) if trending?(subscription, status, trend_source) with_provider(subscription.fasp_provider) do
announce(subscription, status.uri) if trending?(subscription, status, trend_source)
end
end end
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
# status might not exist anymore, in which case there is nothing to do # status might not exist anymore, in which case there is nothing to do

View File

@@ -1,16 +1,16 @@
# frozen_string_literal: true # frozen_string_literal: true
class Fasp::BackfillWorker class Fasp::BackfillWorker < Fasp::BaseWorker
include Sidekiq::Worker sidekiq_options retry: 5
sidekiq_options queue: 'fasp', retry: 5
def perform(backfill_request_id) def perform(backfill_request_id)
backfill_request = Fasp::BackfillRequest.find(backfill_request_id) backfill_request = Fasp::BackfillRequest.find(backfill_request_id)
announce(backfill_request) with_provider(backfill_request.fasp_provider) do
announce(backfill_request)
backfill_request.advance! backfill_request.advance!
end
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
# ignore missing backfill requests # ignore missing backfill requests
end end

View File

@@ -0,0 +1,19 @@
# frozen_string_literal: true
class Fasp::BaseWorker
include Sidekiq::Worker
sidekiq_options queue: 'fasp'
private
def with_provider(provider)
return unless provider.available?
yield
rescue *Mastodon::HTTP_CONNECTION_ERRORS
raise if provider.available?
ensure
provider.update_availability!
end
end

View File

@@ -1,9 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Fasp::FollowRecommendationWorker class Fasp::FollowRecommendationWorker < Fasp::BaseWorker
include Sidekiq::Worker sidekiq_options retry: 0
sidekiq_options queue: 'fasp', retry: 0
def perform(account_id) def perform(account_id)
return unless Mastodon::Feature.fasp_enabled? return unless Mastodon::Feature.fasp_enabled?
@@ -20,14 +18,16 @@ class Fasp::FollowRecommendationWorker
fetch_service = ActivityPub::FetchRemoteActorService.new fetch_service = ActivityPub::FetchRemoteActorService.new
follow_recommendation_providers.each do |provider| follow_recommendation_providers.each do |provider|
Fasp::Request.new(provider).get("/follow_recommendation/v0/accounts?#{params}").each do |uri| with_provider(provider) do
next if Account.where(uri:).any? Fasp::Request.new(provider).get("/follow_recommendation/v0/accounts?#{params}").each do |uri|
next if Account.where(uri:).any?
new_account = fetch_service.call(uri) new_account = fetch_service.call(uri)
if new_account.present? if new_account.present?
Fasp::FollowRecommendation.find_or_create_by(requesting_account: account, recommended_account: new_account) Fasp::FollowRecommendation.find_or_create_by(requesting_account: account, recommended_account: new_account)
async_refresh.increment_result_count(by: 1) async_refresh.increment_result_count(by: 1)
end
end end
end end
end end

View File

@@ -18,9 +18,13 @@ be:
attributes: attributes:
domain: domain:
invalid: не з’яўляецца сапраўдным даменным імем invalid: не з’яўляецца сапраўдным даменным імем
messages:
invalid_domain_on_line: "%{value} не пасуе для карэктнай назвы сервера"
models: models:
account: account:
attributes: attributes:
fields:
fields_with_values_missing_labels: утрымлівае значэнні без апісанняў
username: username:
invalid: павінна змяшчаць толькі літары, лічбы і ніжнія падкрэсліванні invalid: павінна змяшчаць толькі літары, лічбы і ніжнія падкрэсліванні
reserved: зарэзервавана reserved: зарэзервавана
@@ -45,8 +49,14 @@ be:
attributes: attributes:
reblog: reblog:
taken: гэтага допісу ўжо існуе taken: гэтага допісу ўжо існуе
terms_of_service:
attributes:
effective_date:
too_soon: занадта рана, мусіць быць пасля %{date}
user: user:
attributes: attributes:
date_of_birth:
below_limit: ніжэй за дазволены ўзрост
email: email:
blocked: выкарыстоўвае забароненую крыніцу электроннай пошты blocked: выкарыстоўвае забароненую крыніцу электроннай пошты
unreachable: не існуе unreachable: не існуе

View File

@@ -39,6 +39,8 @@ kab:
taken: n iddaden yellan yakan taken: n iddaden yellan yakan
user: user:
attributes: attributes:
date_of_birth:
below_limit: ddaw n talast n leɛmeṛ
email: email:
blocked: isseqdac asaǧǧaw n yimayl ur yettusirgen ara blocked: isseqdac asaǧǧaw n yimayl ur yettusirgen ara
unreachable: ur d-ttban ara d akken yella unreachable: ur d-ttban ara d akken yella

View File

@@ -25,6 +25,7 @@ be:
one: Допіс one: Допіс
other: Допісы other: Допісы
posts_tab_heading: Допісы posts_tab_heading: Допісы
self_follow_error: Нельга падпісацца на свой профіль
admin: admin:
account_actions: account_actions:
action: Выканаць дзеянне action: Выканаць дзеянне
@@ -51,6 +52,7 @@ be:
title: Змяніць адрас эл. пошты для %{username} title: Змяніць адрас эл. пошты для %{username}
change_role: change_role:
changed_msg: Роля паспяхова зменена! changed_msg: Роля паспяхова зменена!
edit_roles: Наладзіць ролі карыстальнікаў
label: Змяніць ролю label: Змяніць ролю
no_role: Няма ролі no_role: Няма ролі
title: Змяніць ролю для %{username} title: Змяніць ролю для %{username}
@@ -194,6 +196,7 @@ be:
create_relay: Стварыць рэтранслятар create_relay: Стварыць рэтранслятар
create_unavailable_domain: Стварыць недаступны Дамен create_unavailable_domain: Стварыць недаступны Дамен
create_user_role: Стварыць ролю create_user_role: Стварыць ролю
create_username_block: Стварыць правіла імя карыстальніка
demote_user: Панізіць карыстальніка demote_user: Панізіць карыстальніка
destroy_announcement: Выдаліць аб'яву destroy_announcement: Выдаліць аб'яву
destroy_canonical_email_block: Выдаліць блакіроўку электроннай пошты destroy_canonical_email_block: Выдаліць блакіроўку электроннай пошты
@@ -207,6 +210,7 @@ be:
destroy_status: Выдаліць допіс destroy_status: Выдаліць допіс
destroy_unavailable_domain: Выдаліць недаступны дамен destroy_unavailable_domain: Выдаліць недаступны дамен
destroy_user_role: Выдаліць ролю destroy_user_role: Выдаліць ролю
destroy_username_block: Выдаліць правіла імя карыстальніка
disable_2fa_user: Адключыць двухэтапнае спраўджанне disable_2fa_user: Адключыць двухэтапнае спраўджанне
disable_custom_emoji: Адключыць адвольныя эмодзі disable_custom_emoji: Адключыць адвольныя эмодзі
disable_relay: Выключыць рэтранслятар disable_relay: Выключыць рэтранслятар
@@ -241,6 +245,7 @@ be:
update_report: Абнавіць скаргу update_report: Абнавіць скаргу
update_status: Абнавіць допіс update_status: Абнавіць допіс
update_user_role: Абнавіць ролю update_user_role: Абнавіць ролю
update_username_block: Абнавіць правіла імя карыстальніка
actions: actions:
approve_appeal_html: Карыстальнік %{name} ухваліў запыт на мадэрацыю %{target} approve_appeal_html: Карыстальнік %{name} ухваліў запыт на мадэрацыю %{target}
approve_user_html: "%{name} пацвердзіў рэгістрацыю ад %{target}" approve_user_html: "%{name} пацвердзіў рэгістрацыю ад %{target}"
@@ -259,6 +264,7 @@ be:
create_relay_html: "%{name} стварыў(-ла) рэтранслятар %{target}" create_relay_html: "%{name} стварыў(-ла) рэтранслятар %{target}"
create_unavailable_domain_html: "%{name} прыпыніў дастаўку да дамена %{target}" create_unavailable_domain_html: "%{name} прыпыніў дастаўку да дамена %{target}"
create_user_role_html: "%{name} зрабіў ролю %{target}" create_user_role_html: "%{name} зрабіў ролю %{target}"
create_username_block_html: Адміністратар %{name} дадаў правіла для імён карыстальнікаў, у якіх %{target}
demote_user_html: "%{name} прыбраў карыстальніка %{target}" demote_user_html: "%{name} прыбраў карыстальніка %{target}"
destroy_announcement_html: "%{name} выдаліў аб'яву %{target}" destroy_announcement_html: "%{name} выдаліў аб'яву %{target}"
destroy_canonical_email_block_html: "%{name} разблакіраваў эл. пошту з хэшам %{target}" destroy_canonical_email_block_html: "%{name} разблакіраваў эл. пошту з хэшам %{target}"
@@ -272,6 +278,7 @@ be:
destroy_status_html: "%{name} выдаліў допіс %{target}" destroy_status_html: "%{name} выдаліў допіс %{target}"
destroy_unavailable_domain_html: "%{name} дазволіў працягнуць адпраўку на дамен %{target}" destroy_unavailable_domain_html: "%{name} дазволіў працягнуць адпраўку на дамен %{target}"
destroy_user_role_html: "%{name} выдаліў ролю %{target}" destroy_user_role_html: "%{name} выдаліў ролю %{target}"
destroy_username_block_html: Адміністратар %{name} прыбраў правіла для імён карыстальнікаў, у якіх %{target}
disable_2fa_user_html: "%{name} амяніў абавязковую двухфактарную верыфікацыю для карыстальніка %{target}" disable_2fa_user_html: "%{name} амяніў абавязковую двухфактарную верыфікацыю для карыстальніка %{target}"
disable_custom_emoji_html: "%{name} заблакіраваў эмодзі %{target}" disable_custom_emoji_html: "%{name} заблакіраваў эмодзі %{target}"
disable_relay_html: "%{name} выключыў(-ла) рэтранслятар %{target}" disable_relay_html: "%{name} выключыў(-ла) рэтранслятар %{target}"
@@ -306,6 +313,7 @@ be:
update_report_html: "%{name} абнавіў скаргу %{target}" update_report_html: "%{name} абнавіў скаргу %{target}"
update_status_html: "%{name} абнавіў допіс %{target}" update_status_html: "%{name} абнавіў допіс %{target}"
update_user_role_html: "%{name} змяніў ролю %{target}" update_user_role_html: "%{name} змяніў ролю %{target}"
update_username_block_html: Адміністратар %{name} змяніў правіла для імён карыстальнікаў, у якіх %{target}
deleted_account: выдалены ўліковы запіс deleted_account: выдалены ўліковы запіс
empty: Логі не знойдзены. empty: Логі не знойдзены.
filter_by_action: Фільтраваць па дзеянню filter_by_action: Фільтраваць па дзеянню
@@ -313,6 +321,7 @@ be:
title: Аўдыт title: Аўдыт
unavailable_instance: "(імя дамена недаступнае)" unavailable_instance: "(імя дамена недаступнае)"
announcements: announcements:
back: Вярнуцца да аб'яў
destroyed_msg: Аб’ява выдалена! destroyed_msg: Аб’ява выдалена!
edit: edit:
title: Рэдагаваць абвестку title: Рэдагаваць абвестку
@@ -321,6 +330,10 @@ be:
new: new:
create: Стварыць аб'яву create: Стварыць аб'яву
title: Новая аб'ява title: Новая аб'ява
preview:
disclaimer: Паколькі карыстальнікі не могуць адмовіцца ад іх, апавяшчэнні па электроннай пошце мусяць выкарыстоўвацца толькі для важных аб'яў, накшталт уцечкі асабістых дадзеных ці зачынення сервера.
explanation_html: 'Ліст будзе дасланы на электронную пошту <strong>%{display_count} карыстальнікам</strong>. У ім будзе наступнае:'
title: Перадпрагляд аб'явы апавяшчэння
publish: Апублікаваць publish: Апублікаваць
published_msg: Аб'ява паспяхова апублікавана! published_msg: Аб'ява паспяхова апублікавана!
scheduled_for: Запланавана на %{time} scheduled_for: Запланавана на %{time}
@@ -492,22 +505,32 @@ be:
fasp: fasp:
debug: debug:
callbacks: callbacks:
created_at: Створана
delete: Выдаліць delete: Выдаліць
ip: IP-адрас ip: IP-адрас
request_body: Запытаць цела
title: Зрабіць дэбаг зваротных выклікаў
providers: providers:
active: Актыўны active: Актыўны
base_url: Базавы URL-адрас base_url: Базавы URL-адрас
callback: Зваротны выклік
delete: Выдаліць delete: Выдаліць
edit: Рэдагаваць пастаўшчыка edit: Рэдагаваць пастаўшчыка
finish_registration: Завяршыць рэгістрацыю finish_registration: Завяршыць рэгістрацыю
name: Назва name: Назва
providers: Пастаўшчыкі providers: Пастаўшчыкі
public_key_fingerprint: Лічбавы адбітак публічнага ключа public_key_fingerprint: Лічбавы адбітак публічнага ключа
registration_requested: Патрабуюцца рэгістрацыя
registrations: registrations:
confirm: Пацвердзіць confirm: Пацвердзіць
description: Вы запыталі рэгістрацыю ад FASP. Адмоўцеся, калі Вы не рабілі гэтага. Калі ж Вы гэта зрабілі, то ўважліва параўнайце імя і ключ перад пацвярджэннем рэгістрацыі.
reject: Адхіліць reject: Адхіліць
title: Пацвердзіць рэгістрацыю ў FASP
save: Захаваць save: Захаваць
select_capabilities: Выбраць здольнасці
sign_in: Увайсці sign_in: Увайсці
status: Допіс
title: Дапаможныя серверы Fediverse
title: FASP title: FASP
follow_recommendations: follow_recommendations:
description_html: "<strong>Рэкамендацыі падпісак, дапамогаюць новым карыстальнікам хутка знайсці цікавы кантэнт</strong>. Калі карыстальнік недастаткова ўзаемадзейнічаў з іншымі, каб сфарміраваць персанальныя рэкамендацыі прытрымлівацца, замест гэтага рэкамендуюцца гэтыя ўліковыя запісы. Яны штодзённа пераразлічваюцца з сумесі ўліковых запісаў з самымі апошнімі ўзаемадзеяннямі і найбольшай колькасцю мясцовых падпісчыкаў для дадзенай мовы." description_html: "<strong>Рэкамендацыі падпісак, дапамогаюць новым карыстальнікам хутка знайсці цікавы кантэнт</strong>. Калі карыстальнік недастаткова ўзаемадзейнічаў з іншымі, каб сфарміраваць персанальныя рэкамендацыі прытрымлівацца, замест гэтага рэкамендуюцца гэтыя ўліковыя запісы. Яны штодзённа пераразлічваюцца з сумесі ўліковых запісаў з самымі апошнімі ўзаемадзеяннямі і найбольшай колькасцю мясцовых падпісчыкаў для дадзенай мовы."
@@ -586,7 +609,9 @@ be:
moderation_notes: moderation_notes:
create: Дадаць нататку мадэратара create: Дадаць нататку мадэратара
created_msg: Нататка мадэратара для экзэмпляра сервера створана! created_msg: Нататка мадэратара для экзэмпляра сервера створана!
description_html: Паглядзіце і пакіньце нататкі іншым мадэратарам або сабе ў будучыні
destroyed_msg: Нататка мадэратара экзэмпляра сервера выдалена! destroyed_msg: Нататка мадэратара экзэмпляра сервера выдалена!
placeholder: Інфармацыя пра гэты выпадак, прынятыя меры ці нешта яшчэ, што дапаможа Вам разабрацца з гэтым у будучыні.
title: Нататкі мадэратараў title: Нататкі мадэратараў
private_comment: Прыватны каментарый private_comment: Прыватны каментарый
public_comment: Публічны каментарый public_comment: Публічны каментарый
@@ -807,15 +832,21 @@ be:
description_html: Большасць сцвярджаюць, што прачыталі ўмовы абслугоўвання і згаджаюцца з імі, але звычайна людзі не чытаюць іх да канца, пакуль не ўзнікне праблема. <strong>Таму зрабіце правілы вашага сервера простымі з першага погляду, прадставіўшы іх у выглядзе маркіраванага спісу.</strong> Старайцеся рабіць правілы кароткімі і простымі, але не разбіваць іх на шмат асобных пунктаў. description_html: Большасць сцвярджаюць, што прачыталі ўмовы абслугоўвання і згаджаюцца з імі, але звычайна людзі не чытаюць іх да канца, пакуль не ўзнікне праблема. <strong>Таму зрабіце правілы вашага сервера простымі з першага погляду, прадставіўшы іх у выглядзе маркіраванага спісу.</strong> Старайцеся рабіць правілы кароткімі і простымі, але не разбіваць іх на шмат асобных пунктаў.
edit: Рэдагаваць правіла edit: Рэдагаваць правіла
empty: Правілы сервера яшчэ не вызначаны. empty: Правілы сервера яшчэ не вызначаны.
move_down: Перасунуць уніз
move_up: Перасунуць уверх
title: Правілы сервера title: Правілы сервера
translation: Пераклад translation: Пераклад
translations: Пераклады translations: Пераклады
translations_explanation: Вы можаце па жаданні дадаваць пераклады Вашых правіл. Калі перакладу няма, то пакажацца арыгінальная версія. Калі ласка, заўсёды правярайце, каб пераклад быў такім жа актуальным, як і арыгінал.
settings: settings:
about: about:
manage_rules: Кіраваць правіламі сервера manage_rules: Кіраваць правіламі сервера
preamble: Дайце падрабязную інфармацыю аб тым, як сервер працуе, мадэруецца, фінансуецца. preamble: Дайце падрабязную інфармацыю аб тым, як сервер працуе, мадэруецца, фінансуецца.
rules_hint: Існуе спецыяльная вобласць для правілаў, якіх вашы карыстальнікі павінны прытрымлівацца. rules_hint: Існуе спецыяльная вобласць для правілаў, якіх вашы карыстальнікі павінны прытрымлівацца.
title: Пра нас title: Пра нас
allow_referrer_origin:
desc: Калі Вашыя карыстальнікі націскаюць спасылкі на знешнія сайты, іх браўзер можа дасылаць адрас Вашага сервера Mastodon як крыніцу спасылкі. Адключыце гэту функцыю, калі яна ўнікальна ідэнтыфікуе Вашых карыстальнікаў (напрыклад, калі гэта персанальны сервер Mastodon).
title: Дазволіць знешнім сайтам бачыць Ваш сервер Mastodon як крыніцу трафіка
appearance: appearance:
preamble: Наладзьце вэб-інтэрфейс Mastodon. preamble: Наладзьце вэб-інтэрфейс Mastodon.
title: Выгляд title: Выгляд
@@ -922,6 +953,8 @@ be:
system_checks: system_checks:
database_schema_check: database_schema_check:
message_html: Ёсць незавершаныя міграцыі базы даных. Запусціце іх, каб пераканацца, што праграма паводзіць сябе належным чынам message_html: Ёсць незавершаныя міграцыі базы даных. Запусціце іх, каб пераканацца, што праграма паводзіць сябе належным чынам
elasticsearch_analysis_index_mismatch:
message_html: Налады аналізатара індэксаў Elasticsearch пратэрмінаваныя. Калі ласка, запусціце <code>tootctl search deploy --only-mapping --only=%{value}</code>
elasticsearch_health_red: elasticsearch_health_red:
message_html: Кластар Elasticsearch нездаровы (чырвоны статус), функцыі пошуку недаступныя message_html: Кластар Elasticsearch нездаровы (чырвоны статус), функцыі пошуку недаступныя
elasticsearch_health_yellow: elasticsearch_health_yellow:
@@ -990,9 +1023,25 @@ be:
generate: Выкарыстаць шаблон generate: Выкарыстаць шаблон
generates: generates:
action: Згенерыраваць action: Згенерыраваць
chance_to_review_html: "<strong>Аўтаматычна згенераваныя ўмовы карыстання не будуць аўтаматычна апублікаваныя.</strong> У Вас будзе магчымасць паглядзець на вынікі. Калі ласка, дайце неабходныя дэталі, каб працягнуць."
explanation_html: Узор умоў карыстання прадстаўлены выключна з інфармацыйнай мэтай і не павінен успрымацца як юрыдычная кансультацыя ні ў якім пытанні. Калі ласка, правядзіце размову з Вашым уласным юрыдычным кансультантам па Вашай сітуацыі і Вашых канкрэтных юрыдычных пытаннях.
title: Стварэнне ўмоў карыстання
going_live_on_html: Уступяць у сілу %{date}
history: Гісторыя history: Гісторыя
live: Дзейнічае live: Дзейнічае
no_history: Пакуль не заўважана ніякіх змен ва ўмовах пагаднення.
no_terms_of_service_html: У Вас пакуль няма ніякіх умоў карыстання. Умовы карыстання ствараюцца для яснасці і абароны ад патэнцыяльных абавязкаў у спрэчках з Вашымі карыстальнікамі.
notified_on_html: Карыстальнікам паведамяць %{date}
notify_users: Апавясціць карыстальнікаў notify_users: Апавясціць карыстальнікаў
preview:
explanation_html: 'Электронны ліст будзе дасланы <strong>%{display_count} карыстальнікам</strong>, якія зарэгістраваліся да %{date}. У лісце будзе наступны тэкст:'
send_preview: Адправіць на %{email} для перадпрагляду
send_to_all:
few: Адправіць %{display_count} электронныя лісты
many: Адправіць %{display_count} электронных лістоў
one: Адправіць %{display_count} электронны ліст
other: Адправіць %{display_count} электронных лістоў
title: Перадпрагляд апавяшчэння пра ўмовы карыстання
publish: Апублікаваць publish: Апублікаваць
published_on_html: Апублікавана %{date} published_on_html: Апублікавана %{date}
save_draft: Захаваць чарнавік save_draft: Захаваць чарнавік
@@ -1002,10 +1051,15 @@ be:
allow: Дазволіць allow: Дазволіць
approved: Пацверджаны approved: Пацверджаны
confirm_allow: Вы ўпэўнены, што хочаце дазволіць выбраныя тэгі? confirm_allow: Вы ўпэўнены, што хочаце дазволіць выбраныя тэгі?
confirm_disallow: Вы ўпэўненыя, што хочаце забараніць абраныя хэштэгі?
disallow: Забараніць disallow: Забараніць
links: links:
allow: Дазволіць спасылка allow: Дазволіць спасылка
allow_provider: Дазволіць выдаўца allow_provider: Дазволіць выдаўца
confirm_allow: Вы ўпэўненыя, што хочаце дазволіць абраныя спасылкі?
confirm_allow_provider: Вы ўпэўненыя, што хочаце дазволіць абраныя спасылкі?
confirm_disallow: Вы ўпэўненыя, што хочаце забараніць абраныя спасылкі?
confirm_disallow_provider: Вы ўпэўненыя, што хочаце забараніць абраныя серверы?
description_html: Гэта спасылкі, якія зараз часта распаўсюджваюцца ўліковымі запісамі, з якіх ваш сервер бачыць паведамленні. Гэта можа дапамагчы вашым карыстальнікам даведацца, што адбываецца ў свеце. Ніякія спасылкі не будуць паказвацца публічна, пакуль вы не зацвердзіце аўтара. Вы таксама можаце дазволіць або адхіліць асобныя спасылкі. description_html: Гэта спасылкі, якія зараз часта распаўсюджваюцца ўліковымі запісамі, з якіх ваш сервер бачыць паведамленні. Гэта можа дапамагчы вашым карыстальнікам даведацца, што адбываецца ў свеце. Ніякія спасылкі не будуць паказвацца публічна, пакуль вы не зацвердзіце аўтара. Вы таксама можаце дазволіць або адхіліць асобныя спасылкі.
disallow: Забараніць спасылку disallow: Забараніць спасылку
disallow_provider: Дазволіць выдаўца disallow_provider: Дазволіць выдаўца
@@ -1031,6 +1085,10 @@ be:
statuses: statuses:
allow: Дазволіць допіс allow: Дазволіць допіс
allow_account: Дазволіць аўтара allow_account: Дазволіць аўтара
confirm_allow: Вы ўпэўненыя, што хочаце дазволіць абраныя допісы?
confirm_allow_account: Вы ўпэўненыя, што хочаце дазволіць абраныя профілі?
confirm_disallow: Вы ўпэўненыя, што хочаце забараніць абраныя допісы?
confirm_disallow_account: Вы ўпэўненыя, што хочаце забараніць абраныя профілі?
description_html: Гэта допісы, пра якія ведае ваш сервер, што на дадзены момант часта абагульваюцца і падабаюцца людзям. Гэта можа дапамагчы вашым новым і пастаянным карыстальнікам знайсці больш людзей, на якіх можна падпісацца. Ніякія допісы не будуць паказвацца публічна, пакуль вы не зацвердзіце аўтара, а аўтар не дазволіць прапанаваць свой уліковы запіс іншым. Вы таксама можаце дазволіць або адхіліць асобныя допісы. description_html: Гэта допісы, пра якія ведае ваш сервер, што на дадзены момант часта абагульваюцца і падабаюцца людзям. Гэта можа дапамагчы вашым новым і пастаянным карыстальнікам знайсці больш людзей, на якіх можна падпісацца. Ніякія допісы не будуць паказвацца публічна, пакуль вы не зацвердзіце аўтара, а аўтар не дазволіць прапанаваць свой уліковы запіс іншым. Вы таксама можаце дазволіць або адхіліць асобныя допісы.
disallow: Забараніць допіс disallow: Забараніць допіс
disallow_account: Забараніць аўтара disallow_account: Забараніць аўтара
@@ -1069,6 +1127,25 @@ be:
other: Выкарысталі %{count} чалавек за апошні тыдзень other: Выкарысталі %{count} чалавек за апошні тыдзень
title: Рэкамендацыі і трэнды title: Рэкамендацыі і трэнды
trending: Трэндавае trending: Трэндавае
username_blocks:
add_new: Дадаць новае
block_registrations: Заблакіраваць рэгістрацыю
comparison:
contains: Мае
equals: Такое ж, як
contains_html: Мае %{string}
created_msg: Правіла імя карыстальніка паспяхова створанае
delete: Выдаліць
edit:
title: Змяніць правіла імя карыстальніка
matches_exactly_html: Такое ж, як %{string}
new:
create: Стварыць правіла
title: Стварыць новае правіла імя карыстальніка
no_username_block_selected: Аніводнае з правіл імён карыстальніка не было змененае, бо аніводнае не было абранае
not_permitted: Забаронена
title: Правілы імені карыстальніка
updated_msg: Правіла імя карыстальніка паспяхова абноўленае
warning_presets: warning_presets:
add_new: Дадаць новы add_new: Дадаць новы
delete: Выдаліць delete: Выдаліць
@@ -1227,6 +1304,7 @@ be:
set_new_password: Прызначыць новы пароль set_new_password: Прызначыць новы пароль
setup: setup:
email_below_hint_html: Праверце папку са спамам або зрабіце новы запыт. Вы можаце выправіць свой email, калі ён няправільны. email_below_hint_html: Праверце папку са спамам або зрабіце новы запыт. Вы можаце выправіць свой email, калі ён няправільны.
email_settings_hint_html: Націсніце на спасылку, якую мы адправілі на %{email}, каб пачаць карыстацца Mastodon. Мы пакуль пачакаем тут.
link_not_received: Не атрымалі спасылку? link_not_received: Не атрымалі спасылку?
new_confirmation_instructions_sent: Праз некалькі хвілін вы атрымаеце новы ліст на email са спасылкай для пацверджання! new_confirmation_instructions_sent: Праз некалькі хвілін вы атрымаеце новы ліст на email са спасылкай для пацверджання!
title: Праверце вашу пошту title: Праверце вашу пошту
@@ -1235,6 +1313,7 @@ be:
title: Уваход у %{domain} title: Уваход у %{domain}
sign_up: sign_up:
manual_review: Рэгістрацыі на %{domain} праходзяць ручную праверку нашымі мадэратарамі. Каб дапамагчы нам апрацаваць вашу рэгістрацыю, напішыце крыху пра сябе і чаму вы хочаце мець уліковы запіс на %{domain}. manual_review: Рэгістрацыі на %{domain} праходзяць ручную праверку нашымі мадэратарамі. Каб дапамагчы нам апрацаваць вашу рэгістрацыю, напішыце крыху пра сябе і чаму вы хочаце мець уліковы запіс на %{domain}.
preamble: З профілем на серверы Mastodon Вы зможаце падпісацца на любога чалавека ў fediverse, незалежна ад таго, на якім серверы знаходзіцца іх профіль.
title: Наладзьма вас на %{domain}. title: Наладзьма вас на %{domain}.
status: status:
account_status: Стан уліковага запісу account_status: Стан уліковага запісу
@@ -1246,9 +1325,15 @@ be:
view_strikes: Праглядзець мінулыя папярэджанні для вашага ўліковага запісу view_strikes: Праглядзець мінулыя папярэджанні для вашага ўліковага запісу
too_fast: Форма адпраўлена занадта хутка, паспрабуйце яшчэ раз. too_fast: Форма адпраўлена занадта хутка, паспрабуйце яшчэ раз.
use_security_key: Выкарыстаеце ключ бяспекі use_security_key: Выкарыстаеце ключ бяспекі
user_agreement_html: Я прачытаў і згаджаюся з <a href="%{terms_of_service_path}" target="_blank">умовамі карыстання</a> і <a href="%{privacy_policy_path}" target="_blank">палітыкай прыватнасці</a>
user_privacy_agreement_html: Я прачытаў і згаджаюся з <a href="%{privacy_policy_path}" target="_blank">палітыкай прыватнасці</a>
author_attribution: author_attribution:
example_title: Прыклад тэксту example_title: Прыклад тэксту
hint_html: Вы пішаце навіны ці артыкулы ў блогу па-за Mastodon? Кантралюйце, як пазначаецца Вашае аўтарства, калі імі дзеляцца ў Mastodon.
instructions: 'Упэўніцеся, што гэты код прысутнічае ў HTML-кодзе Вашага артыкула:'
more_from_html: Больш ад %{name}
s_blog: Блог %{name} s_blog: Блог %{name}
then_instructions: Пасля, дадайце назву сайта публікацыі ў полі знізу.
title: Пазначэнне аўтарства title: Пазначэнне аўтарства
challenge: challenge:
confirm: Працягнуць confirm: Працягнуць
@@ -1464,6 +1549,68 @@ be:
merge_long: Захаваць існуючыя запісы і дадаць новыя merge_long: Захаваць існуючыя запісы і дадаць новыя
overwrite: Перазапісаць overwrite: Перазапісаць
overwrite_long: Замяніць бягучыя запісы на новыя 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>.
lists_html:
few: Вы збіраецеся <strong>замяніць свае спісы</strong> змесцівам з <strong>%{filename}</strong>. <strong>%{count} профілі</strong> будуць дададзеныя ў новыя спісы.
many: Вы збіраецеся <strong>замяніць свае спісы</strong> змесцівам з <strong>%{filename}</strong>. <strong>%{count} профіляў</strong> будуць дададзеныя ў новыя спісы.
one: Вы збіраецеся <strong>замяніць свае спісы</strong> змесцівам з <strong>%{filename}</strong>. <strong>%{count} профіль</strong> будзе дададзены ў новыя спісы.
other: Вы збіраецеся <strong>замяніць свае спісы</strong> змесцівам з <strong>%{filename}</strong>. <strong>%{count} профіляў</strong> будуць дададзеныя ў новыя спісы.
muting_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>.
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>%{count} допісы</strong> з <strong>%{filename}</strong> у Вашыя <strong>закладкі</strong>.
many: Вы збіраецеся дадаць <strong>%{count} допісаў</strong> з <strong>%{filename}</strong> у Вашыя <strong>закладкі</strong>.
one: Вы збіраецеся дадаць <strong>%{count} допіс</strong> з <strong>%{filename}</strong> у Вашыя <strong>закладкі</strong>.
other: Вы збіраецеся дадаць <strong>%{count} допісаў</strong> з <strong>%{filename}</strong> у Вашыя <strong>закладкі</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>.
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>.
lists_html:
few: Вы збіраецеся дадаць <strong>%{count} профілі</strong> з <strong>%{filename}</strong> у Вашыя <strong>спісы</strong>. Калі спісаў няма, то будуць створаны новыя.
many: Вы збіраецеся дадаць <strong>%{count} профіляў</strong> з <strong>%{filename}</strong> у Вашыя <strong>спісы</strong>. Калі спісаў няма, то будуць створаны новыя.
one: Вы збіраецеся дадаць <strong>%{count} профіль</strong> з <strong>%{filename}</strong> у Вашыя <strong>спісы</strong>. Калі спісаў няма, то будуць створаны новыя.
other: Вы збіраецеся дадаць <strong>%{count} профіляў</strong> з <strong>%{filename}</strong> у Вашыя <strong>спісы</strong>. Калі спісаў няма, то будуць створаны новыя.
muting_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>.
preface: Вы можаце імпартаваць даныя, экспартаваныя вамі з іншага сервера, напрыклад, спіс людзей, на якіх вы падпісаны або якіх блакуеце. preface: Вы можаце імпартаваць даныя, экспартаваныя вамі з іншага сервера, напрыклад, спіс людзей, на якіх вы падпісаны або якіх блакуеце.
recent_imports: Нядаўнія імпарты recent_imports: Нядаўнія імпарты
states: states:
@@ -1550,6 +1697,7 @@ be:
media_attachments: media_attachments:
validations: validations:
images_and_video: Немагчыма далучыць відэа да допісу, які ўжо змяшчае выявы images_and_video: Немагчыма далучыць відэа да допісу, які ўжо змяшчае выявы
not_found: Файл %{ids} не знойдзены або ўжо далучаны да іншага допісу
not_ready: Няможна далучыць файлы, апрацоўка якіх яшчэ не скончылася. Паспрабуйце яшчэ раз праз хвілінку! not_ready: Няможна далучыць файлы, апрацоўка якіх яшчэ не скончылася. Паспрабуйце яшчэ раз праз хвілінку!
too_many: Немагчыма далучыць больш за 4 файлы too_many: Немагчыма далучыць больш за 4 файлы
migrations: migrations:
@@ -1617,6 +1765,10 @@ be:
title: Новае згадванне title: Новае згадванне
poll: poll:
subject: Апытанне ад %{name} скончылася subject: Апытанне ад %{name} скончылася
quote:
body: 'Ваш допіс працытаваў карыстальнік %{name}:'
subject: Карыстальнік %{name} працытаваў Ваш допіс
title: Цытаваць
reblog: reblog:
body: "%{name} пашырыў ваш пост:" body: "%{name} пашырыў ваш пост:"
subject: "%{name} пашырыў ваш допіс" subject: "%{name} пашырыў ваш допіс"
@@ -1696,7 +1848,7 @@ be:
follow_failure: Вы не можаце падпісацца на некаторыя акаўнты. follow_failure: Вы не можаце падпісацца на некаторыя акаўнты.
follow_selected_followers: Падпісацца на выбраных падпісчыкаў follow_selected_followers: Падпісацца на выбраных падпісчыкаў
followers: Падпісчыкі followers: Падпісчыкі
following: Падпісаны following: Падпіскі
invited: Запрошаны invited: Запрошаны
last_active: Апошняя актыўнасць last_active: Апошняя актыўнасць
most_recent: Даўнасць most_recent: Даўнасць
@@ -1835,6 +1987,7 @@ be:
edited_at_html: Адрэдагавана %{date} edited_at_html: Адрэдагавана %{date}
errors: errors:
in_reply_not_found: Здаецца, допіс, на які вы спрабуеце адказаць, не існуе. in_reply_not_found: Здаецца, допіс, на які вы спрабуеце адказаць, не існуе.
quoted_status_not_found: Выглядае, што допісу, які Вы спрабуеце цытаваць, не існуе.
over_character_limit: перавышаная колькасць сімвалаў у %{max} over_character_limit: перавышаная колькасць сімвалаў у %{max}
pin_errors: pin_errors:
direct: Допісы, бачныя толькі згаданым карыстальнікам, нельга замацаваць direct: Допісы, бачныя толькі згаданым карыстальнікам, нельга замацаваць
@@ -1842,6 +1995,8 @@ be:
ownership: Немагчыма замацаваць чужы допіс ownership: Немагчыма замацаваць чужы допіс
reblog: Немагчыма замацаваць пашырэнне reblog: Немагчыма замацаваць пашырэнне
quote_policies: quote_policies:
followers: Толькі Вашыя падпісчыкі
nobody: Ніхто
public: Усе public: Усе
title: '%{name}: "%{quote}"' title: '%{name}: "%{quote}"'
visibilities: visibilities:
@@ -1896,6 +2051,11 @@ be:
does_not_match_previous_name: не супадае з папярэднім імям does_not_match_previous_name: не супадае з папярэднім імям
terms_of_service: terms_of_service:
title: Умовы выкарыстання title: Умовы выкарыстання
terms_of_service_interstitial:
future_preamble_html: Мы ўносім праўкі ў нашыя ўмовы карыстання, якія пачнуць дзейнічаць <strong>%{date}</strong>. Мы раім Вам азнаёміцца з абноўленымі ўмовамі.
past_preamble_html: Пасля Вашага апошняга наведвання мы ўнеслі праўкі ў нашыя ўмовы карыстання. Мы раім Вам азнаёміцца з абноўленымі ўмовамі.
review_link: Прачытаць умовы карыстання
title: На %{domain} змяняюцца ўмовы карыстання
themes: themes:
contrast: Mastodon (высокі кантраст) contrast: Mastodon (высокі кантраст)
default: Mastodon (цёмная) default: Mastodon (цёмная)
@@ -1927,6 +2087,10 @@ be:
recovery_instructions_html: Калі раптам вы страціце доступ да свайго тэлефона, вы можаце скарыстаць адзін з кодаў аднаўлення ніжэй каб аднавіць доступ да свайго ўліковага запісу. <strong>Захоўвайце іх у бяспечным месцы</strong>. Напрыклад, вы можаце раздрукаваць іх і захоўваць разам з іншымі важнымі дакументамі. recovery_instructions_html: Калі раптам вы страціце доступ да свайго тэлефона, вы можаце скарыстаць адзін з кодаў аднаўлення ніжэй каб аднавіць доступ да свайго ўліковага запісу. <strong>Захоўвайце іх у бяспечным месцы</strong>. Напрыклад, вы можаце раздрукаваць іх і захоўваць разам з іншымі важнымі дакументамі.
webauthn: Ключы бяспекі webauthn: Ключы бяспекі
user_mailer: user_mailer:
announcement_published:
description: 'Аб''ява ад адміністратараў %{domain}:'
subject: Аб'ява сэрвісу
title: Аб'ява сэрвісу %{domain}
appeal_approved: appeal_approved:
action: Налады ўліковага запісу action: Налады ўліковага запісу
explanation: Апеляцыя на папярэджанне супраць вашага ўліковага запісу ад %{strike_date}, якую вы падалі %{appeal_date}, была ўхвалена. Ваш уліковы запіс зноў на добрым рахунку. explanation: Апеляцыя на папярэджанне супраць вашага ўліковага запісу ад %{strike_date}, якую вы падалі %{appeal_date}, была ўхвалена. Ваш уліковы запіс зноў на добрым рахунку.
@@ -1957,7 +2121,13 @@ be:
subject: У вас уліковы запіс зайшлі з новага IP-адрасу subject: У вас уліковы запіс зайшлі з новага IP-адрасу
title: Новы ўваход title: Новы ўваход
terms_of_service_changed: terms_of_service_changed:
agreement: Працягваючы карыстацца %{domain}, Вы пагаджаецеся з гэтымі ўмовамі. Калі Вы не згодныя з абноўленымі ўмовамі, то можаце ў любы момант адмовіцца ад пагаднення з %{domain}, выдаліўшы свой профіль.
changelog: 'Коратка пра тое, што значыць гэтае абнаўленне:'
description: 'Вы атрымалі дадзены ліст, бо мы ўносім праўкі ў нашыя ўмовы карыстання на %{domain}. Гэтыя абнаўленні ўступяць у сілу %{date}. Мы раім Вам цалкам азнаёміцца з абноўленымі ўмовамі тут:'
description_html: Вы атрымалі дадзены ліст, бо мы ўносім праўкі ў нашыя ўмовы карыстання на %{domain}. Гэтыя абнаўленні ўступяць у сілу <strong>%{date}</strong>. Мы раім Вам цалкам азнаёміцца з <a href="%{path}" target="_blank">абноўленымі ўмовамі тут</a>.
sign_off: Каманда %{domain} sign_off: Каманда %{domain}
subject: Абнаўленні ў нашых умовах карыстання
subtitle: Змяняюцца ўмовы карыстання на %{domain}
title: Важнае абнаўленне title: Важнае абнаўленне
warning: warning:
appeal: Падаць апеляцыю appeal: Падаць апеляцыю
@@ -2047,6 +2217,7 @@ be:
instructions_html: Скапіруйце прыведзены ніжэй код і ўстаўце ў HTML вашага сайта. Затым дадайце адрас вашага сайта ў адно з дадатковых палёў вашага профілю на ўкладцы «рэдагаваць профіль» і захавайце змены. instructions_html: Скапіруйце прыведзены ніжэй код і ўстаўце ў HTML вашага сайта. Затым дадайце адрас вашага сайта ў адно з дадатковых палёў вашага профілю на ўкладцы «рэдагаваць профіль» і захавайце змены.
verification: Верыфікацыя verification: Верыфікацыя
verified_links: Вашыя правераныя спасылкі verified_links: Вашыя правераныя спасылкі
website_verification: Пацвярджэнне сайта
webauthn_credentials: webauthn_credentials:
add: Дадаць новы ключ бяспекі add: Дадаць новы ключ бяспекі
create: create:

View File

@@ -60,6 +60,7 @@ be:
error: error:
title: Узнікла памылка title: Узнікла памылка
new: new:
prompt_html: "%{client_name} хоча атрымаць дазвол на доступ да Вашага профілю. <strong>Ухваляйце гэты запыт толькі калі Вы ведаеце гэту крыніцу і давяраеце ёй.</strong>"
review_permissions: Прагледзець дазволы review_permissions: Прагледзець дазволы
title: Патрабуецца аўтарызацыя title: Патрабуецца аўтарызацыя
show: show:

View File

@@ -567,6 +567,7 @@ kab:
accept: Qbel accept: Qbel
back: Tuɣalin back: Tuɣalin
invited_by: 'Tzemreḍ ad tkecmeḍ ɣer %{domain} s tanemmirt i tinnubga i d-teṭṭfeḍ sɣur :' invited_by: 'Tzemreḍ ad tkecmeḍ ɣer %{domain} s tanemmirt i tinnubga i d-teṭṭfeḍ sɣur :'
preamble: Tiyi ttwasemmant-d yerna ttwaḍemnent sɣur imḍebbren n %{domain}.
preamble_invited: Uqbel ad tkemmleḍ, ttxil-k·m ẓer ilugan i d-sbedden yimkariyen n %{domain}. preamble_invited: Uqbel ad tkemmleḍ, ttxil-k·m ẓer ilugan i d-sbedden yimkariyen n %{domain}.
title: Kra n yilugan igejdanen. title: Kra n yilugan igejdanen.
title_invited: Tettwaɛerḍeḍ. title_invited: Tettwaɛerḍeḍ.
@@ -578,6 +579,7 @@ kab:
preamble_html: Kcem ar <strong>%{domain}</strong> s inekcam-inek n tuqqna. Ma yella yezga-d umiḍan-ik deg uqeddac-nniḍen, ur tezmireḍ ara ad tkecmeḍ sya. preamble_html: Kcem ar <strong>%{domain}</strong> s inekcam-inek n tuqqna. Ma yella yezga-d umiḍan-ik deg uqeddac-nniḍen, ur tezmireḍ ara ad tkecmeḍ sya.
title: Akeččum ɣer %{domain} title: Akeččum ɣer %{domain}
sign_up: sign_up:
preamble: S umiḍan yellan deg uqeddac-a n Mastodon, ad tizimreḍ ad t-ḍefreḍ yal yiwen nniḍen i yellan deg fediverse, akken yebɣu yili wanda i yella umiḍan-nsen.
title: Iyya ad d-nessewjed tiɣawsiwin i %{domain}. title: Iyya ad d-nessewjed tiɣawsiwin i %{domain}.
status: status:
account_status: Addad n umiḍan account_status: Addad n umiḍan
@@ -744,6 +746,8 @@ kab:
action: Err action: Err
body: 'Yuder-ik·ikem-id %{name} deg:' body: 'Yuder-ik·ikem-id %{name} deg:'
subject: Yuder-ik·ikem-id %{name} subject: Yuder-ik·ikem-id %{name}
quote:
title: Tabdert tamaynut
reblog: reblog:
subject: "%{name} yesselha addad-ik·im" subject: "%{name} yesselha addad-ik·im"
title: Azuzer amaynut title: Azuzer amaynut

View File

@@ -56,7 +56,7 @@ da:
scopes: De API'er, som applikationen vil kunne tilgå. Vælges en topniveaudstrækning, vil detailvalg være unødvendige. scopes: De API'er, som applikationen vil kunne tilgå. Vælges en topniveaudstrækning, vil detailvalg være unødvendige.
setting_aggregate_reblogs: Vis ikke nye fremhævelser for nyligt fremhævede indlæg (påvirker kun nyligt modtagne fremhævelser) setting_aggregate_reblogs: Vis ikke nye fremhævelser for nyligt fremhævede indlæg (påvirker kun nyligt modtagne fremhævelser)
setting_always_send_emails: Normalt sendes ingen e-mailnotifikationer under aktivt brug af Mastodon setting_always_send_emails: Normalt sendes ingen e-mailnotifikationer under aktivt brug af Mastodon
setting_default_quote_policy: Denne indstilling træder kun i kraft for indlæg oprettet med den næste Mastodon-version, men egne præference kan vælges som forberedelse. setting_default_quote_policy: Denne indstilling træder kun i kraft for indlæg oprettet med den næste Mastodon-version, men egen præference kan vælges som forberedelse.
setting_default_sensitive: Sensitive medier er som standard skjult og kan vises med et klik setting_default_sensitive: Sensitive medier er som standard skjult og kan vises med et klik
setting_display_media_default: Skjul medier med sensitiv-markering setting_display_media_default: Skjul medier med sensitiv-markering
setting_display_media_hide_all: Skjul altid medier setting_display_media_hide_all: Skjul altid medier

View File

@@ -27,6 +27,8 @@ kab:
username: Tzemreḍ ad tesqedceḍ isekkilen, uṭṭunen akked yijerriden n wadda username: Tzemreḍ ad tesqedceḍ isekkilen, uṭṭunen akked yijerriden n wadda
featured_tag: featured_tag:
name: 'Ha-t-an kra seg ihacṭagen i tesseqdaceḍ ussan-a ineggura maḍi :' name: 'Ha-t-an kra seg ihacṭagen i tesseqdaceḍ ussan-a ineggura maḍi :'
form_admin_settings:
min_age: Ad ttwasutren yiseqdacen ad sentemen azemz-nsen n tlalit deg ujerred
form_challenge: form_challenge:
current_password: Tkecmeḍ ɣer temnaḍt taɣellsant current_password: Tkecmeḍ ɣer temnaḍt taɣellsant
imports: imports:
@@ -37,15 +39,19 @@ kab:
comment: D afrayan. Cfu ɣef wayɣer i terniḍ alugen-a. comment: D afrayan. Cfu ɣef wayɣer i terniḍ alugen-a.
severities: severities:
no_access: Sewḥel anekcu ɣer akk tiɣbula no_access: Sewḥel anekcu ɣer akk tiɣbula
user:
date_of_birth:
one: Ilaq ad neḍmen belli tesɛiḍ ma ulac %{count} akken ad tesqedceḍ %{domain}. Ur neḥrez ara aya.
other: Ilaq ad neḍmen belli tesɛiḍ ma ulac %{count} akken ad tesqedceḍ %{domain}. Ur neḥrez ara aya.
labels: labels:
account: account:
fields: fields:
name: Tabzimt name: Tabzimt
value: Agbur value: Agbur
account_alias: account_alias:
acct: Tansa n umiḍan aqbur acct: Asulay n umiḍan aqbur
account_migration: account_migration:
acct: Tansa n umiḍan amaynut acct: Asulay n umiḍan amaynut
account_warning_preset: account_warning_preset:
title: Azwel title: Azwel
admin_account_action: admin_account_action:
@@ -90,12 +96,14 @@ kab:
setting_always_send_emails: Dima ttazen-d ilɣa s yimayl setting_always_send_emails: Dima ttazen-d ilɣa s yimayl
setting_default_language: Tutlayt n usuffeɣ setting_default_language: Tutlayt n usuffeɣ
setting_default_privacy: Tabaḍnit n usuffeɣ setting_default_privacy: Tabaḍnit n usuffeɣ
setting_default_quote_policy: Anwa i izemren ad d-yebder
setting_display_media: Askanay n imidyaten setting_display_media: Askanay n imidyaten
setting_display_media_default: Akk-a kan setting_display_media_default: Akk-a kan
setting_display_media_hide_all: Ffer-iten akk setting_display_media_hide_all: Ffer-iten akk
setting_display_media_show_all: Sken-iten-id akk setting_display_media_show_all: Sken-iten-id akk
setting_hide_network: Ffer azetta-k·m inmetti setting_hide_network: Ffer azetta-k·m inmetti
setting_theme: Asental n wesmel setting_theme: Asental n wesmel
setting_trends: Sken-d inezzaɣ n wass-a
setting_use_pending_items: Askar aleɣwayan setting_use_pending_items: Askar aleɣwayan
sign_in_token_attempt: Tangalt n tɣellist sign_in_token_attempt: Tangalt n tɣellist
title: Azwel title: Azwel
@@ -116,6 +124,7 @@ kab:
status_page_url: URL n uusebter n waddaden status_page_url: URL n uusebter n waddaden
theme: Asentel amezwer theme: Asentel amezwer
thumbnail: Tanfult n uqeddac thumbnail: Tanfult n uqeddac
trends: Rmed inezzaɣ
interactions: interactions:
must_be_follower: Ssewḥel ilɣa sɣur wid akk d tid ur yellin ara d imeḍfaren-ik·im must_be_follower: Ssewḥel ilɣa sɣur wid akk d tid ur yellin ara d imeḍfaren-ik·im
must_be_following: Ssewḥel ilɣa sɣur wid akked tid ur tettḍafareḍ ara must_be_following: Ssewḥel ilɣa sɣur wid akked tid ur tettḍafareḍ ara

View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddDeliveryLastFailedAtToFaspProviders < ActiveRecord::Migration[8.0]
def change
add_column :fasp_providers, :delivery_last_failed_at, :datetime
end
end

View File

@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_07_17_003848) do ActiveRecord::Schema[8.0].define(version: 2025_08_05_075010) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql" enable_extension "pg_catalog.plpgsql"
@@ -488,6 +488,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_07_17_003848) do
t.string "fediverse_account" t.string "fediverse_account"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.datetime "delivery_last_failed_at"
t.index ["base_url"], name: "index_fasp_providers_on_base_url", unique: true t.index ["base_url"], name: "index_fasp_providers_on_base_url", unique: true
end end
@@ -1486,53 +1487,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_07_17_003848) do
add_foreign_key "web_settings", "users", name: "fk_11910667b2", on_delete: :cascade add_foreign_key "web_settings", "users", name: "fk_11910667b2", on_delete: :cascade
add_foreign_key "webauthn_credentials", "users", on_delete: :cascade add_foreign_key "webauthn_credentials", "users", on_delete: :cascade
create_view "instances", materialized: true, sql_definition: <<-SQL
WITH domain_counts(domain, accounts_count) AS (
SELECT accounts.domain,
count(*) AS accounts_count
FROM accounts
WHERE (accounts.domain IS NOT NULL)
GROUP BY accounts.domain
)
SELECT domain_counts.domain,
domain_counts.accounts_count
FROM domain_counts
UNION
SELECT domain_blocks.domain,
COALESCE(domain_counts.accounts_count, (0)::bigint) AS accounts_count
FROM (domain_blocks
LEFT JOIN domain_counts ON (((domain_counts.domain)::text = (domain_blocks.domain)::text)))
UNION
SELECT domain_allows.domain,
COALESCE(domain_counts.accounts_count, (0)::bigint) AS accounts_count
FROM (domain_allows
LEFT JOIN domain_counts ON (((domain_counts.domain)::text = (domain_allows.domain)::text)));
SQL
add_index "instances", "reverse(('.'::text || (domain)::text)), domain", name: "index_instances_on_reverse_domain"
add_index "instances", ["domain"], name: "index_instances_on_domain", unique: true
create_view "user_ips", sql_definition: <<-SQL
SELECT user_id,
ip,
max(used_at) AS used_at
FROM ( SELECT users.id AS user_id,
users.sign_up_ip AS ip,
users.created_at AS used_at
FROM users
WHERE (users.sign_up_ip IS NOT NULL)
UNION ALL
SELECT session_activations.user_id,
session_activations.ip,
session_activations.updated_at
FROM session_activations
UNION ALL
SELECT login_activities.user_id,
login_activities.ip,
login_activities.created_at
FROM login_activities
WHERE (login_activities.success = true)) t0
GROUP BY user_id, ip;
SQL
create_view "account_summaries", materialized: true, sql_definition: <<-SQL create_view "account_summaries", materialized: true, sql_definition: <<-SQL
SELECT accounts.id AS account_id, SELECT accounts.id AS account_id,
mode() WITHIN GROUP (ORDER BY t0.language) AS language, mode() WITHIN GROUP (ORDER BY t0.language) AS language,
@@ -1583,4 +1537,51 @@ ActiveRecord::Schema[8.0].define(version: 2025_07_17_003848) do
SQL SQL
add_index "global_follow_recommendations", ["account_id"], name: "index_global_follow_recommendations_on_account_id", unique: true add_index "global_follow_recommendations", ["account_id"], name: "index_global_follow_recommendations_on_account_id", unique: true
create_view "instances", materialized: true, sql_definition: <<-SQL
WITH domain_counts(domain, accounts_count) AS (
SELECT accounts.domain,
count(*) AS accounts_count
FROM accounts
WHERE (accounts.domain IS NOT NULL)
GROUP BY accounts.domain
)
SELECT domain_counts.domain,
domain_counts.accounts_count
FROM domain_counts
UNION
SELECT domain_blocks.domain,
COALESCE(domain_counts.accounts_count, (0)::bigint) AS accounts_count
FROM (domain_blocks
LEFT JOIN domain_counts ON (((domain_counts.domain)::text = (domain_blocks.domain)::text)))
UNION
SELECT domain_allows.domain,
COALESCE(domain_counts.accounts_count, (0)::bigint) AS accounts_count
FROM (domain_allows
LEFT JOIN domain_counts ON (((domain_counts.domain)::text = (domain_allows.domain)::text)));
SQL
add_index "instances", "reverse(('.'::text || (domain)::text)), domain", name: "index_instances_on_reverse_domain"
add_index "instances", ["domain"], name: "index_instances_on_domain", unique: true
create_view "user_ips", sql_definition: <<-SQL
SELECT user_id,
ip,
max(used_at) AS used_at
FROM ( SELECT users.id AS user_id,
users.sign_up_ip AS ip,
users.created_at AS used_at
FROM users
WHERE (users.sign_up_ip IS NOT NULL)
UNION ALL
SELECT session_activations.user_id,
session_activations.ip,
session_activations.updated_at
FROM session_activations
UNION ALL
SELECT login_activities.user_id,
login_activities.ip,
login_activities.created_at
FROM login_activities
WHERE (login_activities.success = true)) t0
GROUP BY user_id, ip;
SQL
end end

View File

@@ -214,4 +214,102 @@ RSpec.describe Fasp::Provider do
expect(subject.delivery_failure_tracker).to be_a(DeliveryFailureTracker) expect(subject.delivery_failure_tracker).to be_a(DeliveryFailureTracker)
end end
end end
describe '#available?' do
subject { Fabricate(:fasp_provider, delivery_last_failed_at:) }
let(:delivery_last_failed_at) { nil }
before do
allow(subject.delivery_failure_tracker).to receive(:available?).and_return(available)
end
context 'when the delivery failure tracker reports it is available' do
let(:available) { true }
it 'returns true' do
expect(subject.available?).to be true
end
end
context 'when the delivery failure tracker reports it is unavailable' do
let(:available) { false }
context 'when the last failure was more than one hour ago' do
let(:delivery_last_failed_at) { 61.minutes.ago }
it 'returns true' do
expect(subject.available?).to be true
end
end
context 'when the last failure is very recent' do
let(:delivery_last_failed_at) { 5.minutes.ago }
it 'returns false' do
expect(subject.available?).to be false
end
end
end
end
describe '#update_availability!' do
subject { Fabricate(:fasp_provider, delivery_last_failed_at:) }
before do
allow(subject.delivery_failure_tracker).to receive(:available?).and_return(available)
end
context 'when `delivery_last_failed_at` is `nil`' do
let(:delivery_last_failed_at) { nil }
context 'when the delivery failure tracker reports it is available' do
let(:available) { true }
it 'does not update the provider' do
subject.update_availability!
expect(subject.saved_changes?).to be false
end
end
context 'when the delivery failure tracker reports it is unavailable' do
let(:available) { false }
it 'sets `delivery_last_failed_at` to the current time' do
freeze_time
subject.update_availability!
expect(subject.delivery_last_failed_at).to eq Time.zone.now
end
end
end
context 'when `delivery_last_failed_at` is present' do
context 'when the delivery failure tracker reports it is available' do
let(:available) { true }
let(:delivery_last_failed_at) { 5.minutes.ago }
it 'sets `delivery_last_failed_at` to `nil`' do
subject.update_availability!
expect(subject.delivery_last_failed_at).to be_nil
end
end
context 'when the delivery failure tracker reports it is unavailable' do
let(:available) { false }
let(:delivery_last_failed_at) { 5.minutes.ago }
it 'updates `delivery_last_failed_at` to the current time' do
freeze_time
subject.update_availability!
expect(subject.delivery_last_failed_at).to eq Time.zone.now
end
end
end
end
end end

View File

@@ -0,0 +1,57 @@
# frozen_string_literal: true
RSpec.shared_examples 'worker handling fasp delivery failures' do
context 'when provider is not available' do
before do
provider.update(delivery_last_failed_at: 1.minute.ago)
domain = Addressable::URI.parse(provider.base_url).normalized_host
UnavailableDomain.create!(domain:)
end
it 'does not attempt connecting and does not fail the job' do
expect { subject }.to_not raise_error
expect(stubbed_request).to_not have_been_made
end
end
context 'when connection to provider fails' do
before do
base_stubbed_request
.to_raise(HTTP::ConnectionError)
end
context 'when provider becomes unavailable' do
before do
travel_to 5.minutes.ago
4.times do
provider.delivery_failure_tracker.track_failure!
travel_to 1.minute.since
end
end
it 'updates the provider and does not fail the job, so it will not be retried' do
expect { subject }.to_not raise_error
expect(provider.reload.delivery_last_failed_at).to eq Time.current
end
end
context 'when provider is still marked as available' do
it 'fails the job so it can be retried' do
expect { subject }.to raise_error(HTTP::ConnectionError)
end
end
end
context 'when connection to a previously unavailable provider succeeds' do
before do
provider.update(delivery_last_failed_at: 2.hours.ago)
domain = Addressable::URI.parse(provider.base_url).normalized_host
UnavailableDomain.create!(domain:)
end
it 'marks the provider as being available again' do
expect { subject }.to_not raise_error
expect(provider).to be_available
end
end
end

View File

@@ -5,12 +5,14 @@ require 'rails_helper'
RSpec.describe Fasp::AccountSearchWorker, feature: :fasp do RSpec.describe Fasp::AccountSearchWorker, feature: :fasp do
include ProviderRequestHelper include ProviderRequestHelper
subject { described_class.new.perform('cats') }
let(:provider) { Fabricate(:account_search_fasp) } let(:provider) { Fabricate(:account_search_fasp) }
let(:account) { Fabricate(:account) } let(:account) { Fabricate(:account) }
let(:fetch_service) { instance_double(ActivityPub::FetchRemoteActorService, call: true) } let(:fetch_service) { instance_double(ActivityPub::FetchRemoteActorService, call: true) }
let(:path) { '/account_search/v0/search?term=cats&limit=10' }
let!(:stubbed_request) do let!(:stubbed_request) do
path = '/account_search/v0/search?term=cats&limit=10'
stub_provider_request(provider, stub_provider_request(provider,
method: :get, method: :get,
path:, path:,
@@ -25,7 +27,7 @@ RSpec.describe Fasp::AccountSearchWorker, feature: :fasp do
end end
it 'requests search results and fetches received account uris' do it 'requests search results and fetches received account uris' do
described_class.new.perform('cats') subject
expect(stubbed_request).to have_been_made expect(stubbed_request).to have_been_made
expect(fetch_service).to have_received(:call).with('https://fedi.example.com/accounts/2') expect(fetch_service).to have_received(:call).with('https://fedi.example.com/accounts/2')
@@ -35,7 +37,7 @@ RSpec.describe Fasp::AccountSearchWorker, feature: :fasp do
it 'marks a running async refresh as finished' do it 'marks a running async refresh as finished' do
async_refresh = AsyncRefresh.create("fasp:account_search:#{Digest::MD5.base64digest('cats')}", count_results: true) async_refresh = AsyncRefresh.create("fasp:account_search:#{Digest::MD5.base64digest('cats')}", count_results: true)
described_class.new.perform('cats') subject
expect(async_refresh.reload).to be_finished expect(async_refresh.reload).to be_finished
end end
@@ -43,8 +45,16 @@ RSpec.describe Fasp::AccountSearchWorker, feature: :fasp do
it 'tracks the number of fetched accounts in the async refresh' do it 'tracks the number of fetched accounts in the async refresh' do
async_refresh = AsyncRefresh.create("fasp:account_search:#{Digest::MD5.base64digest('cats')}", count_results: true) async_refresh = AsyncRefresh.create("fasp:account_search:#{Digest::MD5.base64digest('cats')}", count_results: true)
described_class.new.perform('cats') subject
expect(async_refresh.reload.result_count).to eq 2 expect(async_refresh.reload.result_count).to eq 2
end end
describe 'provider delivery failure handling' do
let(:base_stubbed_request) do
stub_request(:get, provider.url(path))
end
it_behaves_like('worker handling fasp delivery failures')
end
end end

View File

@@ -5,15 +5,19 @@ require 'rails_helper'
RSpec.describe Fasp::AnnounceAccountLifecycleEventWorker do RSpec.describe Fasp::AnnounceAccountLifecycleEventWorker do
include ProviderRequestHelper include ProviderRequestHelper
subject { described_class.new.perform(account_uri, 'new') }
let(:account_uri) { 'https://masto.example.com/accounts/1' } let(:account_uri) { 'https://masto.example.com/accounts/1' }
let(:subscription) do let(:subscription) do
Fabricate(:fasp_subscription, category: 'account') Fabricate(:fasp_subscription, category: 'account')
end end
let(:provider) { subscription.fasp_provider } let(:provider) { subscription.fasp_provider }
let(:path) { '/data_sharing/v0/announcements' }
let!(:stubbed_request) do let!(:stubbed_request) do
stub_provider_request(provider, stub_provider_request(provider,
method: :post, method: :post,
path: '/data_sharing/v0/announcements', path:,
response_body: { response_body: {
source: { source: {
subscription: { subscription: {
@@ -27,8 +31,16 @@ RSpec.describe Fasp::AnnounceAccountLifecycleEventWorker do
end end
it 'sends the account uri to subscribed providers' do it 'sends the account uri to subscribed providers' do
described_class.new.perform(account_uri, 'new') subject
expect(stubbed_request).to have_been_made expect(stubbed_request).to have_been_made
end end
describe 'provider delivery failure handling' do
let(:base_stubbed_request) do
stub_request(:post, provider.url(path))
end
it_behaves_like('worker handling fasp delivery failures')
end
end end

View File

@@ -5,15 +5,19 @@ require 'rails_helper'
RSpec.describe Fasp::AnnounceContentLifecycleEventWorker do RSpec.describe Fasp::AnnounceContentLifecycleEventWorker do
include ProviderRequestHelper include ProviderRequestHelper
subject { described_class.new.perform(status_uri, 'new') }
let(:status_uri) { 'https://masto.example.com/status/1' } let(:status_uri) { 'https://masto.example.com/status/1' }
let(:subscription) do let(:subscription) do
Fabricate(:fasp_subscription) Fabricate(:fasp_subscription)
end end
let(:provider) { subscription.fasp_provider } let(:provider) { subscription.fasp_provider }
let(:path) { '/data_sharing/v0/announcements' }
let!(:stubbed_request) do let!(:stubbed_request) do
stub_provider_request(provider, stub_provider_request(provider,
method: :post, method: :post,
path: '/data_sharing/v0/announcements', path:,
response_body: { response_body: {
source: { source: {
subscription: { subscription: {
@@ -27,8 +31,16 @@ RSpec.describe Fasp::AnnounceContentLifecycleEventWorker do
end end
it 'sends the status uri to subscribed providers' do it 'sends the status uri to subscribed providers' do
described_class.new.perform(status_uri, 'new') subject
expect(stubbed_request).to have_been_made expect(stubbed_request).to have_been_made
end end
describe 'provider delivery failure handling' do
let(:base_stubbed_request) do
stub_request(:post, provider.url(path))
end
it_behaves_like('worker handling fasp delivery failures')
end
end end

View File

@@ -5,6 +5,8 @@ require 'rails_helper'
RSpec.describe Fasp::AnnounceTrendWorker do RSpec.describe Fasp::AnnounceTrendWorker do
include ProviderRequestHelper include ProviderRequestHelper
subject { described_class.new.perform(status.id, 'favourite') }
let(:status) { Fabricate(:status) } let(:status) { Fabricate(:status) }
let(:subscription) do let(:subscription) do
Fabricate(:fasp_subscription, Fabricate(:fasp_subscription,
@@ -14,10 +16,12 @@ RSpec.describe Fasp::AnnounceTrendWorker do
threshold_likes: 2) threshold_likes: 2)
end end
let(:provider) { subscription.fasp_provider } let(:provider) { subscription.fasp_provider }
let(:path) { '/data_sharing/v0/announcements' }
let!(:stubbed_request) do let!(:stubbed_request) do
stub_provider_request(provider, stub_provider_request(provider,
method: :post, method: :post,
path: '/data_sharing/v0/announcements', path:,
response_body: { response_body: {
source: { source: {
subscription: { subscription: {
@@ -36,15 +40,23 @@ RSpec.describe Fasp::AnnounceTrendWorker do
end end
it 'sends the account uri to subscribed providers' do it 'sends the account uri to subscribed providers' do
described_class.new.perform(status.id, 'favourite') subject
expect(stubbed_request).to have_been_made expect(stubbed_request).to have_been_made
end end
describe 'provider delivery failure handling' do
let(:base_stubbed_request) do
stub_request(:post, provider.url(path))
end
it_behaves_like('worker handling fasp delivery failures')
end
end end
context 'when the configured threshold is not met' do context 'when the configured threshold is not met' do
it 'does not notify any provider' do it 'does not notify any provider' do
described_class.new.perform(status.id, 'favourite') subject
expect(stubbed_request).to_not have_been_made expect(stubbed_request).to_not have_been_made
end end

View File

@@ -5,13 +5,17 @@ require 'rails_helper'
RSpec.describe Fasp::BackfillWorker do RSpec.describe Fasp::BackfillWorker do
include ProviderRequestHelper include ProviderRequestHelper
subject { described_class.new.perform(backfill_request.id) }
let(:backfill_request) { Fabricate(:fasp_backfill_request) } let(:backfill_request) { Fabricate(:fasp_backfill_request) }
let(:provider) { backfill_request.fasp_provider } let(:provider) { backfill_request.fasp_provider }
let(:status) { Fabricate(:status) } let(:status) { Fabricate(:status) }
let(:path) { '/data_sharing/v0/announcements' }
let!(:stubbed_request) do let!(:stubbed_request) do
stub_provider_request(provider, stub_provider_request(provider,
method: :post, method: :post,
path: '/data_sharing/v0/announcements', path:,
response_body: { response_body: {
source: { source: {
backfillRequest: { backfillRequest: {
@@ -25,8 +29,16 @@ RSpec.describe Fasp::BackfillWorker do
end end
it 'sends status uri to provider that requested backfill' do it 'sends status uri to provider that requested backfill' do
described_class.new.perform(backfill_request.id) subject
expect(stubbed_request).to have_been_made expect(stubbed_request).to have_been_made
end end
describe 'provider delivery failure handling' do
let(:base_stubbed_request) do
stub_request(:post, provider.url(path))
end
it_behaves_like('worker handling fasp delivery failures')
end
end end

View File

@@ -5,13 +5,15 @@ require 'rails_helper'
RSpec.describe Fasp::FollowRecommendationWorker, feature: :fasp do RSpec.describe Fasp::FollowRecommendationWorker, feature: :fasp do
include ProviderRequestHelper include ProviderRequestHelper
subject { described_class.new.perform(account.id) }
let(:provider) { Fabricate(:follow_recommendation_fasp) } let(:provider) { Fabricate(:follow_recommendation_fasp) }
let(:account) { Fabricate(:account) } let(:account) { Fabricate(:account) }
let(:account_uri) { ActivityPub::TagManager.instance.uri_for(account) }
let(:fetch_service) { instance_double(ActivityPub::FetchRemoteActorService) } let(:fetch_service) { instance_double(ActivityPub::FetchRemoteActorService) }
let(:path) { "/follow_recommendation/v0/accounts?accountUri=#{URI.encode_uri_component(account_uri)}" }
let!(:stubbed_request) do let!(:stubbed_request) do
account_uri = ActivityPub::TagManager.instance.uri_for(account)
path = "/follow_recommendation/v0/accounts?accountUri=#{URI.encode_uri_component(account_uri)}"
stub_provider_request(provider, stub_provider_request(provider,
method: :get, method: :get,
path:, path:,
@@ -28,7 +30,7 @@ RSpec.describe Fasp::FollowRecommendationWorker, feature: :fasp do
end end
it "sends the requesting account's uri to provider and fetches received account uris" do it "sends the requesting account's uri to provider and fetches received account uris" do
described_class.new.perform(account.id) subject
expect(stubbed_request).to have_been_made expect(stubbed_request).to have_been_made
expect(fetch_service).to have_received(:call).with('https://fedi.example.com/accounts/1') expect(fetch_service).to have_received(:call).with('https://fedi.example.com/accounts/1')
@@ -38,7 +40,7 @@ RSpec.describe Fasp::FollowRecommendationWorker, feature: :fasp do
it 'marks a running async refresh as finished' do it 'marks a running async refresh as finished' do
async_refresh = AsyncRefresh.create("fasp:follow_recommendation:#{account.id}", count_results: true) async_refresh = AsyncRefresh.create("fasp:follow_recommendation:#{account.id}", count_results: true)
described_class.new.perform(account.id) subject
expect(async_refresh.reload).to be_finished expect(async_refresh.reload).to be_finished
end end
@@ -46,14 +48,22 @@ RSpec.describe Fasp::FollowRecommendationWorker, feature: :fasp do
it 'tracks the number of fetched accounts in the async refresh' do it 'tracks the number of fetched accounts in the async refresh' do
async_refresh = AsyncRefresh.create("fasp:follow_recommendation:#{account.id}", count_results: true) async_refresh = AsyncRefresh.create("fasp:follow_recommendation:#{account.id}", count_results: true)
described_class.new.perform(account.id) subject
expect(async_refresh.reload.result_count).to eq 2 expect(async_refresh.reload.result_count).to eq 2
end end
it 'persists the results' do it 'persists the results' do
expect do expect do
described_class.new.perform(account.id) subject
end.to change(Fasp::FollowRecommendation, :count).by(2) end.to change(Fasp::FollowRecommendation, :count).by(2)
end end
describe 'provider delivery failure handling' do
let(:base_stubbed_request) do
stub_request(:get, provider.url(path))
end
it_behaves_like('worker handling fasp delivery failures')
end
end end

View File

@@ -6024,9 +6024,9 @@ __metadata:
linkType: hard linkType: hard
"core-js@npm:^3.30.2, core-js@npm:^3.41.0": "core-js@npm:^3.30.2, core-js@npm:^3.41.0":
version: 3.44.0 version: 3.45.0
resolution: "core-js@npm:3.44.0" resolution: "core-js@npm:3.45.0"
checksum: 10c0/759bf3dc5f75068e9425dddf895fd5531c38794a11ea1c2b65e5ef7c527fe3652d59e8c287e574a211af9bab3c057c5c3fa6f6a773f4e142af895106efad38a4 checksum: 10c0/118350f9f1d81f42a1276590d6c217dca04c789fdb8074c82e53056b1a784948769a62b16b98493fd73e8a988545432f302bca798571e56ad881b9c039a5a83c
languageName: node languageName: node
linkType: hard linkType: hard