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

main
Thibaut Girka 6 years ago
commit b00f60f1d3

@ -178,7 +178,7 @@ jobs:
- *attach_workspace
- run: bundle exec i18n-tasks check-normalized
- run: bundle exec i18n-tasks unused
- run: bundle exec i18n-tasks missing-plural-keys
- run: bundle exec i18n-tasks missing -t plural
- run: bundle exec i18n-tasks check-consistent-interpolations
workflows:

@ -96,7 +96,7 @@ gem 'rdf-normalize', '~> 0.3'
group :development, :test do
gem 'fabrication', '~> 2.20'
gem 'fuubar', '~> 2.3'
gem 'i18n-tasks', '~> 0.9', require: false, git: 'https://github.com/Gargron/i18n-tasks.git', ref: '7a57fbe7000f4f8120e250a757ab345c28c6885c'
gem 'i18n-tasks', '~> 0.9', require: false, git: 'https://github.com/Gargron/i18n-tasks.git', ref: 'ab6e10878ccdb6243f934f30372276d260c14251'
gem 'pry-byebug', '~> 3.6'
gem 'pry-rails', '~> 0.3'
gem 'rspec-rails', '~> 3.8'

@ -1,7 +1,7 @@
GIT
remote: https://github.com/Gargron/i18n-tasks.git
revision: 7a57fbe7000f4f8120e250a757ab345c28c6885c
ref: 7a57fbe7000f4f8120e250a757ab345c28c6885c
revision: ab6e10878ccdb6243f934f30372276d260c14251
ref: ab6e10878ccdb6243f934f30372276d260c14251
specs:
i18n-tasks (0.9.27)
activesupport (>= 4.0.2)

@ -201,6 +201,7 @@ class ApplicationController < ActionController::Base
def respond_with_error(code)
respond_to do |format|
format.any { head code }
format.html do
set_locale
use_pack 'error'

@ -9,6 +9,7 @@ import DisplayName from './display_name';
import StatusContent from './status_content';
import StatusActionBar from './status_action_bar';
import AttachmentList from './attachment_list';
import Card from '../features/status/components/card';
import { injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { MediaGallery, Video } from '../features/ui/util/async-components';
@ -256,6 +257,14 @@ class Status extends ImmutablePureComponent {
</Bundle>
);
}
} else if (status.get('spoiler_text').length === 0 && status.get('card')) {
media = (
<Card
onOpenMedia={this.props.onOpenMedia}
card={status.get('card')}
compact
/>
);
}
if (otherAccounts) {

@ -2,7 +2,7 @@
// https://github.com/missive/emoji-mart/blob/5f2ffcc/src/utils/emoji-index.js
import data from './emoji_mart_data_light';
import { getData, getSanitizedData, intersect } from './emoji_utils';
import { getData, getSanitizedData, uniq, intersect } from './emoji_utils';
let originalPool = {};
let index = {};
@ -103,7 +103,7 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo
}
}
allResults = values.map((value) => {
const searchValue = (value) => {
let aPool = pool,
aIndex = index,
length = 0;
@ -150,15 +150,23 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo
}
return aIndex.results;
}).filter(a => a);
};
if (allResults.length > 1) {
results = intersect.apply(null, allResults);
} else if (allResults.length) {
results = allResults[0];
if (values.length > 1) {
results = searchValue(value);
} else {
results = [];
}
allResults = values.map(searchValue).filter(a => a);
if (allResults.length > 1) {
allResults = intersect.apply(null, allResults);
} else if (allResults.length) {
allResults = allResults[0];
}
results = uniq(results.concat(allResults));
}
if (results) {

@ -59,10 +59,12 @@ export default class Card extends React.PureComponent {
card: ImmutablePropTypes.map,
maxDescription: PropTypes.number,
onOpenMedia: PropTypes.func.isRequired,
compact: PropTypes.boolean,
};
static defaultProps = {
maxDescription: 50,
compact: false,
};
state = {
@ -131,7 +133,7 @@ export default class Card extends React.PureComponent {
}
render () {
const { card, maxDescription } = this.props;
const { card, maxDescription, compact } = this.props;
const { width, embedded } = this.state;
if (card === null) {
@ -139,17 +141,17 @@ export default class Card extends React.PureComponent {
}
const provider = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name');
const horizontal = card.get('width') > card.get('height') && (card.get('width') + 100 >= width) || card.get('type') !== 'link';
const className = classnames('status-card', { horizontal });
const horizontal = (!compact && card.get('width') > card.get('height') && (card.get('width') + 100 >= width)) || card.get('type') !== 'link' || embedded;
const interactive = card.get('type') !== 'link';
const className = classnames('status-card', { horizontal, compact, interactive });
const title = interactive ? <a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener' target='_blank'><strong>{card.get('title')}</strong></a> : <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>;
const ratio = card.get('width') / card.get('height');
const ratio = compact ? 16 / 9 : card.get('width') / card.get('height');
const height = card.get('width') > card.get('height') ? (width / ratio) : (width * ratio);
const description = (
<div className='status-card__content'>
{title}
{!horizontal && <p className='status-card__description'>{trim(card.get('description') || '', maxDescription)}</p>}
{!(horizontal || compact) && <p className='status-card__description'>{trim(card.get('description') || '', maxDescription)}</p>}
<span className='status-card__host'>{provider}</span>
</div>
);
@ -174,7 +176,7 @@ export default class Card extends React.PureComponent {
<div className='status-card__actions'>
<div>
<button onClick={this.handleEmbedClick}><i className={`fa fa-${iconVariant}`} /></button>
<a href={card.get('url')} target='_blank' rel='noopener'><i className='fa fa-external-link' /></a>
{horizontal && <a href={card.get('url')} target='_blank' rel='noopener'><i className='fa fa-external-link' /></a>}
</div>
</div>
</div>
@ -184,7 +186,7 @@ export default class Card extends React.PureComponent {
return (
<div className={className} ref={this.setRef}>
{embed}
{description}
{!compact && description}
</div>
);
} else if (card.get('image')) {

@ -2,7 +2,7 @@ import { connect } from 'react-redux';
import Card from '../components/card';
const mapStateToProps = (state, { statusId }) => ({
card: state.getIn(['cards', statusId], null),
card: state.getIn(['statuses', statusId, 'card'], null),
});
export default connect(mapStateToProps)(Card);

@ -14,7 +14,6 @@ import relationships from './relationships';
import settings from './settings';
import push_notifications from './push_notifications';
import status_lists from './status_lists';
import cards from './cards';
import mutes from './mutes';
import reports from './reports';
import contexts from './contexts';
@ -46,7 +45,6 @@ const reducers = {
relationships,
settings,
push_notifications,
cards,
mutes,
reports,
contexts,

@ -10,6 +10,7 @@ import {
STATUS_REVEAL,
STATUS_HIDE,
} from '../actions/statuses';
import { STATUS_CARD_FETCH_SUCCESS } from '../actions/cards';
import { TIMELINE_DELETE } from '../actions/timelines';
import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer';
import { Map as ImmutableMap, fromJS } from 'immutable';
@ -65,6 +66,8 @@ export default function statuses(state = initialState, action) {
});
case TIMELINE_DELETE:
return deleteStatus(state, action.id, action.references);
case STATUS_CARD_FETCH_SUCCESS:
return state.setIn([action.id, 'card'], fromJS(action.card));
default:
return state;
}

@ -1669,6 +1669,7 @@ a.account__display-name {
padding: 4px 0;
border-radius: 4px;
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
z-index: 9999;
ul {
list-style: none;
@ -2560,6 +2561,9 @@ a.status-card {
display: block;
margin-top: 5px;
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.status-card__image {
@ -2584,6 +2588,31 @@ a.status-card {
}
}
.status-card.compact {
border-color: lighten($ui-base-color, 4%);
&.interactive {
border: 0;
}
.status-card__content {
padding: 8px;
padding-top: 10px;
}
.status-card__title {
white-space: nowrap;
}
.status-card__image {
flex: 0 0 60px;
}
}
a.status-card.compact:hover {
background-color: lighten($ui-base-color, 4%);
}
.status-card__image-image {
border-radius: 4px 0 0 4px;
display: block;

@ -148,6 +148,7 @@ class MediaAttachment < ApplicationRecord
"#{x},#{y}"
end
after_commit :reset_parent_cache, on: :update
before_create :prepare_description, unless: :local?
before_create :set_shortcode
before_post_process :set_type_and_extension
@ -252,4 +253,9 @@ class MediaAttachment < ApplicationRecord
bitrate: movie.bitrate,
}
end
def reset_parent_cache
return if status_id.nil?
Rails.cache.delete("statuses/#{status_id}")
end
end

@ -94,6 +94,7 @@ class Status < ApplicationRecord
:conversation,
:status_stat,
:tags,
:preview_cards,
:stream_entry,
active_mentions: :account,
reblog: [
@ -101,6 +102,7 @@ class Status < ApplicationRecord
:application,
:stream_entry,
:tags,
:preview_cards,
:media_attachments,
:conversation,
:status_stat,
@ -168,6 +170,10 @@ class Status < ApplicationRecord
reblog
end
def preview_card
preview_cards.first
end
def title
if destroyed?
"#{account.acct} deleted status"
@ -241,10 +247,6 @@ class Status < ApplicationRecord
before_validation :set_local
class << self
def cache_ids
left_outer_joins(:status_stat).select('statuses.id, greatest(statuses.updated_at, status_stats.updated_at) AS updated_at')
end
def selectable_visibilities
visibilities.keys - %w(direct limited)
end

@ -14,4 +14,12 @@
class StatusStat < ApplicationRecord
belongs_to :status, inverse_of: :status_stat
after_commit :reset_parent_cache
private
def reset_parent_cache
Rails.cache.delete("statuses/#{status_id}")
end
end

@ -21,6 +21,8 @@ class REST::StatusSerializer < ActiveModel::Serializer
has_many :tags
has_many :emojis, serializer: REST::CustomEmojiSerializer
has_one :preview_card, key: :card, serializer: REST::PreviewCardSerializer
def id
object.id.to_s
end

@ -63,6 +63,7 @@ class FetchLinkCardService < BaseService
def attach_card
@status.preview_cards << @card
Rails.cache.delete(@status)
end
def parse_urls

@ -21,8 +21,7 @@ ast:
hosted_on: Mastodon ta agospiáu en %{domain}
learn_more: Deprendi más
source_code: Códigu fonte
status_count_after:
other: estaos
status_count_after: estaos
terms: Términos del serviciu
user_count_after: usuarios
what_is_mastodon: "¿Qué ye Mastodon?"

@ -30,22 +30,16 @@ cs:
other_instances: Seznam instancí
privacy_policy: Zásady soukromí
source_code: Zdrojový kód
status_count_after:
one: příspěvek
other: příspěvků
status_count_after: příspěvků
status_count_before: Kteří napsali
terms: Podmínky používání
user_count_after:
one: uživatele
other: uživatelů
user_count_after: uživatelů
user_count_before: Domov
what_is_mastodon: Co je Mastodon?
accounts:
choices_html: 'Volby uživatele %{name}:'
follow: Sledovat
followers:
one: Sledovatel
other: Sledovatelé
followers: Sledovatelé
following: Sledovaní
joined: Připojil/a se v %{date}
link_verified_on: Vlastnictví tohoto odkazu bylo zkontrolováno %{date}
@ -57,9 +51,7 @@ cs:
people_who_follow: Lidé, kteří sledují uživatele %{name}
pin_errors:
following: Musíte již sledovat osobu, kterou chcete podpořit
posts:
one: Toot
other: Tooty
posts: Tooty
posts_tab_heading: Tooty
posts_with_replies: Tooty a odpovědi
reserved_username: Toto uživatelské jméno je rezervováno
@ -268,9 +260,7 @@ cs:
suspend: Suspendovat
severity: Přísnost
show:
affected_accounts:
one: Jeden účet v databázi byl ovlivněn
other: "%{count} účtů v databázi byl ovlivněn"
affected_accounts: "%{count} účtů v databázi byl ovlivněn"
retroactive:
silence: Odtišit všechny existující účty z této domény
suspend: Zrušit suspenzaci všech existujících účtů z této domény
@ -562,9 +552,7 @@ cs:
followers_count: Počet sledovatelů
lock_link: Zamkněte svůj účet
purge: Odstranit ze sledovatelů
success:
one: V průběhu utišování sledovatelů z jedné domény...
other: V průběhu utišování sledovatelů z %{count} domén...
success: V průběhu utišování sledovatelů z %{count} domén...
true_privacy_html: Berte prosím na vědomí, že <strong>skutečného soukromí se dá dosáhnout pouze za pomoci end-to-end šifrování</strong>.
unlocked_warning_html: Kdokoliv vás může sledovat a okamžitě vidět vaše soukromé příspěvky. %{lock_link}, abyste mohl/a zkontrolovat a odmítnout sledovatele.
unlocked_warning_title: Váš účet není zamknutý
@ -575,9 +563,7 @@ cs:
generic:
changes_saved_msg: Změny byly úspěšně uloženy!
save_changes: Uložit změny
validation_errors:
one: Něco ještě není úplně v pořádku! Prosím zkontrolujte chybu níže
other: Něco ještě není úplně v pořádku! Prosím zkontrolujte %{count} chyb níže
validation_errors: Něco ještě není úplně v pořádku! Prosím zkontrolujte %{count} chyb níže
imports:
preface: Můžete importovat data, která jste exportoval/a z jiné instance, jako například seznam lidí, které sledujete či blokujete.
success: Vaše data byla úspěšně nahrána a nyní budou zpracována v daný čas
@ -600,9 +586,7 @@ cs:
expires_in_prompt: Nikdy
generate: Vygenerovat
invited_by: 'Byl/a jste pozván/a uživatelem:'
max_uses:
one: 1 použití
other: "%{count} použití"
max_uses: "%{count} použití"
max_uses_prompt: Bez limitu
prompt: Vygenerujte a sdílejte s ostatními odkazy a umožněte jim přístup na tuto instanci
table:
@ -628,12 +612,8 @@ cs:
action: Zobrazit všechna oznámení
body: Zde najdete stručný souhrn zpráv, které jste zmeškal/a od vaší poslední návštěvy %{since}
mention: "%{name} vás zmínil/a v:"
new_followers_summary:
one: Navíc jste získal/a jednoho nového sledovatele, zatímco jste byl/a pryč! Hurá!
other: Navíc jste získal/a %{count} nových sledovatelů, zatímco jste byl/a pryč! Hurá!
subject:
one: "Jedno nové oznámení od vaší poslední návštěvy \U0001F418"
other: "%{count} nových oznámení od vaší poslední návštěvy \U0001F418"
new_followers_summary: Navíc jste získal/a %{count} nových sledovatelů, zatímco jste byl/a pryč! Hurá!
subject: "%{count} nových oznámení od vaší poslední návštěvy \U0001F418"
title: Ve vaší absenci...
favourite:
body: 'Váš příspěvek si oblíbil/a %{name}:'
@ -750,17 +730,11 @@ cs:
statuses:
attached:
description: 'Přiloženo: %{attached}'
image:
one: "%{count} obrázek"
other: "%{count} obrázků"
video:
one: "%{count} video"
other: "%{count} videí"
image: "%{count} obrázků"
video: "%{count} videí"
boosted_from_html: Boostnuto z %{acct_link}
content_warning: 'Varování o obsahu: %{warning}'
disallowed_hashtags:
one: 'obsahuje nepovolený hashtag: %{tags}'
other: 'obsahuje nepovolené hashtagy: %{tags}'
disallowed_hashtags: 'obsahuje nepovolené hashtagy: %{tags}'
language_detection: Zjistit jazyk automaticky
open_in_web: Otevřít na webu
over_character_limit: limit %{max} znaků byl překročen

@ -30,22 +30,16 @@ cy:
other_instances: Rhestr achosion
privacy_policy: Polisi preifatrwydd
source_code: Cod ffynhonnell
status_count_after:
one: statws
other: statws
status_count_after: statws
status_count_before: Pwy ysgrifennodd
terms: Telerau gwasanaeth
user_count_after:
one: defnyddiwr
other: defnyddwyr
user_count_after: defnyddwyr
user_count_before: Cartref i
what_is_mastodon: Beth yw Mastodon?
accounts:
choices_html: 'Dewisiadau %{name}:'
follow: Dilynwch
followers:
one: Dilynwr
other: Dilynwyr
followers: Dilynwyr
following: Yn dilyn
joined: Ymunodd %{date}
media: Cyfryngau
@ -56,9 +50,7 @@ cy:
people_who_follow: Pobl sy'n dilyn %{name}
pin_errors:
following: Rhaid i ti fod yn dilyn y person yr ydych am ei gymeradwyo yn barod
posts:
one: Tŵt
other: Tŵtiau
posts: Tŵtiau
posts_tab_heading: Tŵtiau
posts_with_replies: Tŵtiau ac atebion
reserved_username: Mae'r enw defnyddior yn neilltuedig
@ -262,9 +254,7 @@ cy:
suspend: Atal
severity: Difrifoldeb
show:
affected_accounts:
one: Mae un cyfri yn y bas data wedi ei effeithio
other: "%{count} o gyfrifoedd yn y bas data wedi eu hefeithio"
affected_accounts: "%{count} o gyfrifoedd yn y bas data wedi eu hefeithio"
retroactive:
silence: Dad-dawelu pob cyfri presennol o'r parth hwn
suspend: Dad-atal pob cyfrif o'r parth hwn sy'n bodoli
@ -508,9 +498,7 @@ cy:
generic:
changes_saved_msg: Llwyddwyd i gadw y newidiadau!
save_changes: Cadw newidiadau
validation_errors:
one: Mae rhywbeth o'i le o hyd! Edrychwch ar y gwall isod os gwelwch yn dda
other: Mae rhywbeth o'i le o hyd! Edrychwch ar y %{count} gwall isod os gwelwch yn dda
validation_errors: Mae rhywbeth o'i le o hyd! Edrychwch ar y %{count} gwall isod os gwelwch yn dda
imports:
preface: Mae modd mewnforio data yr ydych wedi allforio o achos arall, megis rhestr o bobl yr ydych yn ei ddilyn neu yn blocio.
success: Uwchlwyddwyd eich data yn llwyddiannus ac fe fydd yn cael ei brosesu mewn da bryd

@ -77,6 +77,4 @@ cs:
expired: vypršel, prosím vyžádejte si nový
not_found: nenalezen
not_locked: nebyl uzamčen
not_saved:
one: '1 chyba zabránila uložení tohoto %{resource}:'
other: "%{count} chyb zabránila uložení tohoto %{resource}:"
not_saved: "%{count} chyb zabránila uložení tohoto %{resource}:"

@ -77,6 +77,4 @@ cy:
expired: wedi dod i ben, gwnewch gais am un newydd os gwelwch yn dda
not_found: heb ei ganfod
not_locked: heb ei gloi
not_saved:
one: 'Gwaharddwyd yr %{resource} rhag cael ei arbed oherwydd 1 gwall:'
other: 'Gwaharddwyd yr %{resource} rhag cael ei arbed oherwydd %{count} gwall:'
not_saved: 'Gwaharddwyd yr %{resource} rhag cael ei arbed oherwydd %{count} gwall:'

@ -58,6 +58,4 @@ hr:
expired: je istekao, zatraži novu
not_found: nije nađen
not_locked: nije zaključan
not_saved:
one: '1 greška je zabranila da ovaj %{resource} bude sačuvan:'
other: "%{count} greške su zabranile da ovaj %{resource} bude sačuvan:"
not_saved: "%{count} greške su zabranile da ovaj %{resource} bude sačuvan:"

@ -77,6 +77,4 @@ pl:
expired: wygasło, poproś o nowe
not_found: nie znaleziono
not_locked: było zablokowane
not_saved:
one: '1 błąd uniemożliwił zapisanie zasobu %{resource}:'
other: 'Błędy (%{count}) uniemożliwiły zapisanie zasobu %{resource}:'
not_saved: 'Błędy (%{count}) uniemożliwiły zapisanie zasobu %{resource}:'

@ -77,6 +77,4 @@ zh-TW:
expired: 已經過期,請重新申請
not_found: 找不到
not_locked: 並未被鎖定
not_saved:
one: 1 個錯誤使 %{resource} 無法被儲存︰
other: "%{count} 個錯誤使 %{resource} 無法被儲存︰"
not_saved: "%{count} 個錯誤使 %{resource} 無法被儲存︰"

@ -61,9 +61,7 @@ hr:
generic:
changes_saved_msg: Izmjene su uspješno sačuvane!
save_changes: Sačuvaj izmjene
validation_errors:
one: Nešto ne štima! Vidi grešku ispod
other: Nešto još uvijek ne štima! Vidi %{count} greške ispod
validation_errors: Nešto još uvijek ne štima! Vidi %{count} greške ispod
imports:
preface: Možeš uvesti određene podatke kao što su svi ljudi koje slijediš ili blokiraš u svoj račun na ovoj instanci, sa fajlova kreiranih izvozom sa druge instance.
success: Tvoji podaci su uspješno uploadani i bit će obrađeni u dogledno vrijeme
@ -76,12 +74,8 @@ hr:
digest:
body: 'Ovo je kratak sažetak propuštenog od tvog prošlog posjeta %{since}:'
mention: "%{name} te je spomenuo:"
new_followers_summary:
one: Imaš novog sljedbenika! Yay!
other: Imaš %{count} novih sljedbenika! Prekrašno!
subject:
one: "1 nova notifikacija od tvog prošlog posjeta \U0001F418"
other: "%{count} novih notifikacija od tvog prošlog posjeta \U0001F418"
new_followers_summary: Imaš %{count} novih sljedbenika! Prekrašno!
subject: "%{count} novih notifikacija od tvog prošlog posjeta \U0001F418"
favourite:
body: 'Tvoj status je %{name} označio kao omiljen:'
subject: "%{name} je označio kao omiljen tvoj status"

@ -279,10 +279,7 @@ pl:
suspend: Zawieś
severity: Priorytet
show:
affected_accounts:
many: Dotyczy %{count} kont w bazie danych
one: Dotyczy jednego konta w bazie danych
other: Dotyczy %{count} kont w bazie danych
affected_accounts: Dotyczy %{count} kont w bazie danych
retroactive:
silence: Odwołaj wyciszenie wszystkich kont w tej domenie
suspend: Odwołaj zawieszenie wszystkich kont w tej domenie
@ -577,9 +574,7 @@ pl:
followers_count: Liczba śledzących
lock_link: Zablokuj swoje konto
purge: Przestań śledzić
success:
one: W trakcie usuwania śledzących z jednej domeny…
other: W trakcie usuwania śledzących z %{count} domen…
success: W trakcie usuwania śledzących z %{count} domen…
true_privacy_html: Pamiętaj, że <strong>rzeczywista prywatność może zostać uzyskana wyłącznie dzięki szyfrowaniu end-to-end</strong>.
unlocked_warning_html: Każdy może Cię śledzić, dzięki czemu może zobaczyć Twoje niepubliczne wpisy. %{lock_link} aby móc kontrolować, kto Cię śledzi.
unlocked_warning_title: Twoje konto nie jest zablokowane
@ -788,9 +783,7 @@ pl:
other: "%{count} filmów"
boosted_from_html: Podbito przez %{acct_link}
content_warning: 'Ostrzeżenie o zawartości: %{warning}'
disallowed_hashtags:
one: 'zawiera niedozwolony hashtag: %{tags}'
other: 'zawiera niedozwolone hashtagi: %{tags}'
disallowed_hashtags: 'zawiera niedozwolone hashtagi: %{tags}'
language_detection: Automatycznie wykrywaj język
open_in_web: Otwórz w przeglądarce
over_character_limit: limit %{max} znaków przekroczony

@ -30,22 +30,16 @@ sk:
other_instances: Zoznam ďalších inštancií
privacy_policy: Ustanovenia o súkromí
source_code: Zdrojový kód
status_count_after:
one: status
other: statusy
status_count_after: statusy
status_count_before: Ktorí napísali
terms: Podmienky užívania
user_count_after:
one: užívateľ
other: užívateľov
user_count_after: užívateľov
user_count_before: Domov pre
what_is_mastodon: Čo je Mastodon?
accounts:
choices_html: "%{name}vé voľby:"
follow: Sledovať
followers:
one: Následovateľ
other: Sledovatelia
followers: Sledovatelia
following: Sledovaní
joined: Pridal/a sa %{date}
media: Médiá
@ -56,9 +50,7 @@ sk:
people_who_follow: Ľudia sledujúci %{name}
pin_errors:
following: Musíš už následovať toho človeka, ktorého si praješ zviditeľniť
posts:
one: Príspevok
other: Príspevky
posts: Príspevky
posts_tab_heading: Príspevky
posts_with_replies: Príspevky s odpoveďami
reserved_username: Prihlasovacie meno je rezervované
@ -746,9 +738,7 @@ sk:
other: "%{count} videí"
boosted_from_html: Povýšené od %{acct_link}
content_warning: 'Varovanie o obsahu: %{warning}'
disallowed_hashtags:
one: 'obsahuje nepovolený haštag: %{tags}'
other: 'obsahuje nepovolené haštagy: %{tags}'
disallowed_hashtags: 'obsahuje nepovolené haštagy: %{tags}'
language_detection: Zisti jazyk automaticky
open_in_web: Otvor v okne prehliadača
over_character_limit: limit počtu %{max} znakov bol presiahnutý

@ -30,22 +30,16 @@ sr:
other_instances: Листа инстанци
privacy_policy: Полиса приватности
source_code: Изворни код
status_count_after:
one: статус
other: статуси
status_count_after: статуси
status_count_before: Који су написали
terms: Услови коришћења
user_count_after:
one: корисник
other: корисници
user_count_after: корисници
user_count_before: Дом за
what_is_mastodon: Шта је Мастодон?
accounts:
choices_html: "%{name}'s избори:"
follow: Запрати
followers:
one: Пратилац
other: Пратиоци
followers: Пратиоци
following: Пратим
joined: Придружио/ла се %{date}
media: Медији
@ -56,9 +50,7 @@ sr:
people_who_follow: Људи који прате %{name}
pin_errors:
following: Морате пратити ову особу ако хоћете да потврдите
posts:
one: Труба
other: Трубе
posts: Трубе
posts_tab_heading: Трубе
posts_with_replies: Трубе и одговори
reserved_username: Корисничко име је резервисано
@ -754,17 +746,11 @@ sr:
statuses:
attached:
description: 'У прилогу: %{attached}'
image:
one: "%{count} слику"
other: "%{count} слике"
video:
one: "%{count} видео"
other: "%{count} видеа"
image: "%{count} слике"
video: "%{count} видеа"
boosted_from_html: Подржано од %{acct_link}
content_warning: 'Упозорење на садржај: %{warning}'
disallowed_hashtags:
one: 'садржи забрањену тарабу: %{tags}'
other: 'садржи забрањене тарабе: %{tags}'
disallowed_hashtags: 'садржи забрањене тарабе: %{tags}'
language_detection: Аутоматскo откривање језика
open_in_web: Отвори у вебу
over_character_limit: ограничење од %{max} карактера прекорачено

@ -518,18 +518,14 @@ uk:
followers_count: Кількість підписників
lock_link: Закрийте акаунт
purge: Видалити з підписників
success:
one: У процесі м'якого блокування підписників з одного домену...
other: У процесі м'якого блокування підписників з %{count} доменів...
success: У процесі м'якого блокування підписників з %{count} доменів...
true_privacy_html: Будь ласка, помітьте, що <strong>справжняя конфіденційність може бути досягнена тільки за допомогою end-to-end шифрування</strong>.
unlocked_warning_html: Хто завгодно може підписатися на Вас та отримати доступ до перегляду Ваших приватних статусів. %{lock_link}, щоб отримати можливість роздивлятися та вручну підтверджувати запити щодо підписки.
unlocked_warning_title: Ваш аккаунт не закритий для підписки
generic:
changes_saved_msg: Зміни успішно збережені!
save_changes: Зберегти зміни
validation_errors:
one: Щось тут не так! Будь ласка, ознайомтеся з помилкою нижче
other: Щось тут не так! Будь ласка, ознайомтеся з %{count} помилками нижче
validation_errors: Щось тут не так! Будь ласка, ознайомтеся з %{count} помилками нижче
imports:
preface: Вы можете завантажити деякі дані, наприклад, списки людей, на яких Ви підписані чи яких блокуєте, в Ваш акаунт на цій інстанції з файлів, експортованих з іншої інстанції.
success: Ваші дані були успішно загружені та будуть оброблені в найближчий момент
@ -552,9 +548,7 @@ uk:
expires_in_prompt: Ніколи
generate: Згенерувати
invited_by: 'Вас запросив(-ла):'
max_uses:
one: 1 використання
other: "%{count} використань"
max_uses: "%{count} використань"
max_uses_prompt: Без обмеження
prompt: Генеруйте та діліться посиланням з іншими для надання доступу до сайту
table:
@ -703,17 +697,11 @@ uk:
statuses:
attached:
description: 'Прикріплено: %{attached}'
image:
one: "%{count} картинка"
other: "%{count} картинки"
video:
one: "%{count} відео"
other: "%{count} відео"
image: "%{count} картинки"
video: "%{count} відео"
boosted_from_html: Просунуто від %{acct_link}
content_warning: 'Попередження про контент: %{warning}'
disallowed_hashtags:
one: 'містив заборонений хештеґ: %{tags}'
other: 'містив заборонені хештеґи: %{tags}'
disallowed_hashtags: 'містив заборонені хештеґи: %{tags}'
language_detection: Автоматично визначати мову
open_in_web: Відкрити у вебі
over_character_limit: перевищено ліміт символів (%{max})

@ -30,13 +30,10 @@ zh-CN:
other_instances: 其他实例
privacy_policy: 隐私政策
source_code: 源代码
status_count_after:
one: 条嘟文
status_count_after: 条嘟文
status_count_before: 他们共嘟出了
terms: 使用条款
user_count_after:
one: 位用户
other: 位用户
user_count_after: 位用户
user_count_before: 这里共注册有
what_is_mastodon: Mastodon 是什么?
accounts:

@ -29,18 +29,15 @@ zh-TW:
learn_more: 了解詳細
other_instances: 其他站點
source_code: 原始碼
status_count_after:
one: 狀態
status_count_after: 狀態
status_count_before: 他們共嘟出了
terms: 使用條款
user_count_after:
one: 使用者
user_count_after: 使用者
user_count_before: 這裡共註冊有
what_is_mastodon: 什麼是 Mastodon?
accounts:
follow: 關注
followers:
other: 關注者
followers: 關注者
following: 正在關注
media: 媒體
moved_html: "%{name} 已經搬遷到 %{new_profile_link}:"
@ -48,9 +45,7 @@ zh-TW:
nothing_here: 暫時沒有內容可供顯示!
people_followed_by: "%{name} 關注的人"
people_who_follow: 關注 %{name} 的人
posts:
one: 嘟掉
other: 嘟文
posts: 嘟文
posts_tab_heading: 嘟文
posts_with_replies: 嘟文與回覆
reserved_username: 此用戶名已被保留
@ -234,9 +229,7 @@ zh-TW:
suspend: 自動封鎖
severity: 嚴重度
show:
affected_accounts:
one: 資料庫中有一個使用者受到影響
other: 資料庫中有%{count}個使用者受影響
affected_accounts: 資料庫中有%{count}個使用者受影響
retroactive:
silence: 對此網域的所有使用者取消靜音
suspend: 對此網域的所有使用者取消封鎖
@ -480,18 +473,14 @@ zh-TW:
followers_count: 關注者數量
lock_link: 將你的帳戶設定為私人
purge: 移除關注者
success:
one: 正準備軟性封鎖 1 個網域的關注者……
other: 正準備軟性封鎖 %{count} 個網域的關注者……
success: 正準備軟性封鎖 %{count} 個網域的關注者……
true_privacy_html: 請謹記,唯有<strong>點對點加密方可以真正確保你的隱私</strong>。
unlocked_warning_html: 任何人都可以在關注你後立即查看非公開的嘟文。只要%{lock_link},你就可以審核並拒絕關注請求。
unlocked_warning_title: 你的帳戶是公開的
generic:
changes_saved_msg: 已成功儲存修改!
save_changes: 儲存修改
validation_errors:
one: 送出的資料有問題
other: 送出的資料有 %{count} 個問題
validation_errors: 送出的資料有 %{count} 個問題
imports:
preface: 您可以在此匯入您在其他站點所匯出的資料檔,包括關注的使用者、封鎖的使用者名單。
success: 資料檔上傳成功,正在匯入,請稍候
@ -514,9 +503,7 @@ zh-TW:
expires_in_prompt: 永不過期
generate: 建立邀請連結
invited_by: 你的邀請人是:
max_uses:
one: 1
other: "%{count} 次"
max_uses: "%{count} 次"
max_uses_prompt: 無限制
prompt: 建立分享連結,邀請他人在本站點註冊
table:
@ -542,12 +529,8 @@ zh-TW:
action: 閱覽所有通知
body: 以下是自%{since}你最後一次登入以來錯過的訊息摘要
mention: "%{name} 在此提及了你:"
new_followers_summary:
one: 而且,你不在的時候,有一個人關注你! 耶!
other: 而且,你不在的時候,有 %{count} 個人關注你了! 好棒!
subject:
one: "自從上次登入以來,你收到 1 則新的通知 \U0001F418"
other: "自從上次登入以來,你收到 %{count} 則新的通知 \U0001F418"
new_followers_summary: 而且,你不在的時候,有 %{count} 個人關注你了! 好棒!
subject: "自從上次登入以來,你收到 %{count} 則新的通知 \U0001F418"
title: 你不在的時候...
favourite:
body: '你的嘟文被 %{name} 加入了最愛:'
@ -653,17 +636,11 @@ zh-TW:
statuses:
attached:
description: 附件: %{attached}
image:
one: "%{count} 幅圖片"
other: "%{count} 幅圖片"
video:
one: "%{count} 段影片"
other: "%{count} 段影片"
image: "%{count} 幅圖片"
video: "%{count} 段影片"
boosted_from_html: 轉嘟自 %{acct_link}
content_warning: 內容警告: %{warning}
disallowed_hashtags:
one: 包含不允許的標籤: %{tags}
other: 包含不允許的標籤: %{tags}
disallowed_hashtags: 包含不允許的標籤: %{tags}
language_detection: 自動偵測語言
open_in_web: 以網頁開啟
over_character_limit: 超過了 %{max} 字的限制

@ -14,12 +14,29 @@ class MigrateAccountConversations < ActiveRecord::Migration[5.2]
sleep 1
end
local_direct_statuses.find_each do |status|
total = estimate_rows(local_direct_statuses) + estimate_rows(notifications_about_direct_statuses)
migrated = 0
started_time = Time.zone.now
last_time = Time.zone.now
local_direct_statuses.includes(:account, mentions: :account).find_each do |status|
AccountConversation.add_status(status.account, status)
migrated += 1
if Time.zone.now - last_time > 1
say_progress(migrated, total, started_time)
last_time = Time.zone.now
end
end
notifications_about_direct_statuses.find_each do |notification|
notifications_about_direct_statuses.includes(:account, mention: { status: [:account, mentions: :account] }).find_each do |notification|
AccountConversation.add_status(notification.account, notification.target_status)
migrated += 1
if Time.zone.now - last_time > 1
say_progress(migrated, total, started_time)
last_time = Time.zone.now
end
end
end
@ -28,16 +45,31 @@ class MigrateAccountConversations < ActiveRecord::Migration[5.2]
private
def estimate_rows(query)
result = exec_query("EXPLAIN #{query.to_sql}").first
result['QUERY PLAN'].scan(/ rows=([\d]+)/).first&.first&.to_i || 0
end
def say_progress(migrated, total, started_time)
status = "Migrated #{migrated} rows"
percentage = 100.0 * migrated / total
status += " (~#{sprintf('%.2f', percentage)}%, "
remaining_time = (100.0 - percentage) * (Time.zone.now - started_time) / percentage
status += "#{(remaining_time / 60).to_i}:"
status += sprintf('%02d', remaining_time.to_i % 60)
status += ' remaining)'
say status, true
end
def local_direct_statuses
Status.unscoped
.local
.where(visibility: :direct)
.includes(:account, mentions: :account)
Status.unscoped.local.where(visibility: :direct)
end
def notifications_about_direct_statuses
Notification.joins(mention: :status)
.where(activity_type: 'Mention', statuses: { visibility: :direct })
.includes(:account, mention: { status: [:account, mentions: :account] })
Notification.joins(mention: :status).where(activity_type: 'Mention', statuses: { visibility: :direct })
end
end

@ -6,6 +6,7 @@ require_relative 'mastodon/emoji_cli'
require_relative 'mastodon/accounts_cli'
require_relative 'mastodon/feeds_cli'
require_relative 'mastodon/settings_cli'
require_relative 'mastodon/domains_cli'
module Mastodon
class CLI < Thor
@ -27,5 +28,8 @@ module Mastodon
desc 'settings SUBCOMMAND ...ARGS', 'Manage dynamic settings'
subcommand 'settings', Mastodon::SettingsCLI
desc 'domains SUBCOMMAND ...ARGS', 'Manage account domains'
subcommand 'domains', Mastodon::DomainsCLI
end
end

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'rubygems/package'
require 'set'
require_relative '../../config/boot'
require_relative '../../config/environment'
require_relative 'cli_helper'
@ -10,6 +10,7 @@ module Mastodon
def self.exit_on_failure?
true
end
option :all, type: :boolean
desc 'rotate [USERNAME]', 'Generate and broadcast new keys'
long_desc <<-LONG_DESC
@ -210,33 +211,25 @@ module Mastodon
Accounts that have had confirmed activity within the last week
are excluded from the checks.
If 10 or more accounts from the same domain cannot be queried
due to a connection error (such as missing DNS records) then
the domain is considered dead, and all other accounts from it
are deleted without further querying.
Domains that are unreachable are not checked.
With the --dry-run option, no deletes will actually be carried
out.
LONG_DESC
def cull
domain_thresholds = Hash.new { |hash, key| hash[key] = 0 }
skip_threshold = 7.days.ago
culled = 0
dead_servers = []
skip_domains = Set.new
dry_run = options[:dry_run] ? ' (DRY RUN)' : ''
Account.remote.where(protocol: :activitypub).partitioned.find_each do |account|
next if account.updated_at >= skip_threshold || (account.last_webfingered_at.present? && account.last_webfingered_at >= skip_threshold)
unless dead_servers.include?(account.domain)
unless skip_domains.include?(account.domain)
begin
code = Request.new(:head, account.uri).perform(&:code)
rescue HTTP::ConnectionError
domain_thresholds[account.domain] += 1
if domain_thresholds[account.domain] >= 10
dead_servers << account.domain
end
skip_domains << account.domain
rescue StandardError
next
end
@ -255,24 +248,12 @@ module Mastodon
end
end
# Remove dead servers
unless dead_servers.empty? || options[:dry_run]
dead_servers.each do |domain|
Account.where(domain: domain).find_each do |account|
SuspendAccountService.new.call(account)
account.destroy
culled += 1
say('.', :green, false)
end
end
end
say
say("Removed #{culled} accounts (#{dead_servers.size} dead servers)#{dry_run}", :green)
say("Removed #{culled} accounts. #{skip_domains.size} servers skipped#{dry_run}", skip_domains.empty? ? :green : :yellow)
unless dead_servers.empty?
say('R.I.P.:', :yellow)
dead_servers.each { |domain| say(' ' + domain) }
unless skip_domains.empty?
say('The following servers were not available during the check:', :yellow)
skip_domains.each { |domain| say(' ' + domain) }
end
end

@ -0,0 +1,40 @@
# frozen_string_literal: true
require_relative '../../config/boot'
require_relative '../../config/environment'
require_relative 'cli_helper'
module Mastodon
class DomainsCLI < Thor
def self.exit_on_failure?
true
end
option :dry_run, type: :boolean
desc 'purge DOMAIN', 'Remove accounts from a DOMAIN without a trace'
long_desc <<-LONG_DESC
Remove all accounts from a given DOMAIN without leaving behind any
records. Unlike a suspension, if the DOMAIN still exists in the wild,
it means the accounts could return if they are resolved again.
LONG_DESC
def purge(domain)
removed = 0
dry_run = options[:dry_run] ? ' (DRY RUN)' : ''
Account.where(domain: domain).find_each do |account|
unless options[:dry_run]
SuspendAccountService.new.call(account)
account.destroy
end
removed += 1
say('.', :green, false)
end
DomainBlock.where(domain: domain).destroy_all
say
say("Removed #{removed} accounts#{dry_run}", :green)
end
end
end

@ -10,6 +10,7 @@ module Mastodon
def self.exit_on_failure?
true
end
option :prefix
option :suffix
option :overwrite, type: :boolean

@ -9,6 +9,7 @@ module Mastodon
def self.exit_on_failure?
true
end
option :all, type: :boolean, default: false
option :background, type: :boolean, default: false
option :dry_run, type: :boolean, default: false
@ -58,7 +59,7 @@ module Mastodon
account = Account.find_local(username)
if account.nil?
say("Account #{username} is not found", :red)
say('No such account', :red)
exit(1)
end

@ -9,6 +9,7 @@ module Mastodon
def self.exit_on_failure?
true
end
option :days, type: :numeric, default: 7
option :background, type: :boolean, default: false
option :verbose, type: :boolean, default: false

@ -9,6 +9,7 @@ module Mastodon
def self.exit_on_failure?
true
end
desc 'open', 'Open registrations'
def open
Setting.open_registrations = true

Loading…
Cancel
Save