Merge pull request #332 from glitch-soc/merge-upstream

Merge in home feed regeneration changes from upstream
This commit is contained in:
David Yip 2018-01-18 22:00:11 -06:00 committed by GitHub
commit 26ecee79bf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
51 changed files with 570 additions and 89 deletions

View file

@ -9,7 +9,11 @@ class Api::V1::Timelines::HomeController < Api::BaseController
def show
@statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
render json: @statuses,
each_serializer: REST::StatusSerializer,
relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
status: regeneration_in_progress? ? 206 : 200
end
private
@ -57,4 +61,8 @@ class Api::V1::Timelines::HomeController < Api::BaseController
def pagination_since_id
@statuses.first.id
end
def regeneration_in_progress?
Redis.current.exists("account:#{current_account.id}:regeneration")
end
end

View file

@ -19,13 +19,14 @@ export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
export const TIMELINE_CONTEXT_UPDATE = 'CONTEXT_UPDATE';
export function refreshTimelineSuccess(timeline, statuses, skipLoading, next) {
export function refreshTimelineSuccess(timeline, statuses, skipLoading, next, partial) {
return {
type: TIMELINE_REFRESH_SUCCESS,
timeline,
statuses,
skipLoading,
next,
partial,
};
};
@ -88,7 +89,7 @@ export function refreshTimeline(timelineId, path, params = {}) {
return function (dispatch, getState) {
const timeline = getState().getIn(['timelines', timelineId], ImmutableMap());
if (timeline.get('isLoading') || timeline.get('online')) {
if (timeline.get('isLoading') || (timeline.get('online') && !timeline.get('isPartial'))) {
return;
}
@ -104,8 +105,12 @@ export function refreshTimeline(timelineId, path, params = {}) {
dispatch(refreshTimelineRequest(timelineId, skipLoading));
api(getState).get(path, { params }).then(response => {
if (response.status === 206) {
dispatch(refreshTimelineSuccess(timelineId, [], skipLoading, null, true));
} else {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null));
dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null, false));
}
}).catch(error => {
dispatch(refreshTimelineFail(timelineId, error, skipLoading));
});

View file

@ -2,9 +2,14 @@ import React from 'react';
import { FormattedMessage } from 'react-intl';
const MissingIndicator = () => (
<div className='missing-indicator'>
<div className='regeneration-indicator missing-indicator'>
<div>
<FormattedMessage id='missing_indicator.label' defaultMessage='Not found' />
<div className='regeneration-indicator__figure' />
<div className='regeneration-indicator__label'>
<FormattedMessage id='missing_indicator.label' tagName='strong' defaultMessage='Not found' />
<FormattedMessage id='missing_indicator.sublabel' defaultMessage='This resource could not be found' />
</div>
</div>
</div>
);

View file

@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import StatusContainer from 'flavours/glitch/containers/status_container';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ScrollableList from './scrollable_list';
import { FormattedMessage } from 'react-intl';
export default class StatusList extends ImmutablePureComponent {
@ -16,6 +17,7 @@ export default class StatusList extends ImmutablePureComponent {
trackScroll: PropTypes.bool,
shouldUpdateScroll: PropTypes.func,
isLoading: PropTypes.bool,
isPartial: PropTypes.bool,
hasMore: PropTypes.bool,
prepend: PropTypes.node,
emptyMessage: PropTypes.node,
@ -49,7 +51,22 @@ export default class StatusList extends ImmutablePureComponent {
render () {
const { statusIds, ...other } = this.props;
const { isLoading } = other;
const { isLoading, isPartial } = other;
if (isPartial) {
return (
<div className='regeneration-indicator'>
<div>
<div className='regeneration-indicator__figure' />
<div className='regeneration-indicator__label'>
<FormattedMessage id='regeneration_indicator.label' tagName='strong' defaultMessage='Loading&hellip;' />
<FormattedMessage id='regeneration_indicator.sublabel' defaultMessage='Your home feed is being prepared!' />
</div>
</div>
</div>
);
}
const scrollableContent = (isLoading || statusIds.size > 0) ? (
statusIds.map((statusId) => (

View file

@ -1,6 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
import { expandHomeTimeline } from 'flavours/glitch/actions/timelines';
import { expandHomeTimeline, refreshHomeTimeline } from 'flavours/glitch/actions/timelines';
import PropTypes from 'prop-types';
import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
import Column from 'flavours/glitch/components/column';
@ -16,6 +16,7 @@ const messages = defineMessages({
const mapStateToProps = state => ({
hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0,
isPartial: state.getIn(['timelines', 'home', 'isPartial'], false),
});
@connect(mapStateToProps)
@ -26,6 +27,7 @@ export default class HomeTimeline extends React.PureComponent {
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool,
isPartial: PropTypes.bool,
columnId: PropTypes.string,
multiColumn: PropTypes.bool,
};
@ -57,6 +59,39 @@ export default class HomeTimeline extends React.PureComponent {
this.props.dispatch(expandHomeTimeline());
}
componentDidMount () {
this._checkIfReloadNeeded(false, this.props.isPartial);
}
componentDidUpdate (prevProps) {
this._checkIfReloadNeeded(prevProps.isPartial, this.props.isPartial);
}
componentWillUnmount () {
this._stopPolling();
}
_checkIfReloadNeeded (wasPartial, isPartial) {
const { dispatch } = this.props;
if (wasPartial === isPartial) {
return;
} else if (!wasPartial && isPartial) {
this.polling = setInterval(() => {
dispatch(refreshHomeTimeline());
}, 3000);
} else if (wasPartial && !isPartial) {
this._stopPolling();
}
}
_stopPolling () {
if (this.polling) {
clearInterval(this.polling);
this.polling = null;
}
}
render () {
const { intl, hasUnread, columnId, multiColumn } = this.props;
const pinned = !!columnId;

View file

@ -120,13 +120,17 @@ export default class ListTimeline extends React.PureComponent {
if (typeof list === 'undefined') {
return (
<Column>
<div className='scrollable'>
<LoadingIndicator />
</div>
</Column>
);
} else if (list === false) {
return (
<Column>
<div className='scrollable'>
<MissingIndicator />
</div>
</Column>
);
}

View file

@ -51,6 +51,7 @@ const makeMapStateToProps = () => {
const mapStateToProps = (state, { timelineId }) => ({
statusIds: getStatusIds(state, { type: timelineId }),
isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true),
isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false),
hasMore: !!state.getIn(['timelines', timelineId, 'next']),
});

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.3 KiB

View file

@ -30,7 +30,7 @@ const initialTimeline = ImmutableMap({
items: ImmutableList(),
});
const normalizeTimeline = (state, timeline, statuses, next) => {
const normalizeTimeline = (state, timeline, statuses, next, isPartial) => {
const oldIds = state.getIn([timeline, 'items'], ImmutableList());
const ids = ImmutableList(statuses.map(status => status.get('id'))).filter(newId => !oldIds.includes(newId));
const wasLoaded = state.getIn([timeline, 'loaded']);
@ -41,6 +41,7 @@ const normalizeTimeline = (state, timeline, statuses, next) => {
mMap.set('isLoading', false);
if (!hadNext) mMap.set('next', next);
mMap.set('items', wasLoaded ? ids.concat(oldIds) : ids);
mMap.set('isPartial', isPartial);
}));
};
@ -125,7 +126,7 @@ export default function timelines(state = initialState, action) {
case TIMELINE_EXPAND_FAIL:
return state.update(action.timeline, initialTimeline, map => map.set('isLoading', false));
case TIMELINE_REFRESH_SUCCESS:
return normalizeTimeline(state, action.timeline, fromJS(action.statuses), action.next);
return normalizeTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial);
case TIMELINE_EXPAND_SUCCESS:
return appendNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next);
case TIMELINE_UPDATE:

View file

@ -838,21 +838,10 @@
}
.missing-indicator {
text-align: center;
font-size: 16px;
font-weight: 500;
color: lighten($ui-base-color, 16%);
background: $ui-base-color;
cursor: default;
display: flex;
flex: 1 1 auto;
align-items: center;
justify-content: center;
padding-top: 20px + 48px;
& > div {
background: url('~images/mastodon-not-found.png') no-repeat center -50px;
padding-top: 210px;
width: 100%;
.regeneration-indicator__figure {
background-image: url('~flavours/glitch/images/elephant_ui_disappointed.svg');
}
}
@ -1162,6 +1151,7 @@ noscript {
@import 'metadata';
@import 'composer';
@import 'columns';
@import 'regeneration_indicator';
@import 'search';
@import 'emoji';
@import 'doodle';

View file

@ -0,0 +1,53 @@
.regeneration-indicator {
text-align: center;
font-size: 16px;
font-weight: 500;
color: lighten($ui-base-color, 16%);
background: $ui-base-color;
cursor: default;
display: flex;
flex: 1 1 auto;
align-items: center;
justify-content: center;
padding: 20px;
& > div {
width: 100%;
background: transparent;
padding-top: 0;
}
&__figure {
background: url('~flavours/glitch/images/elephant_ui_working.svg') no-repeat center 0;
width: 100%;
height: 160px;
background-size: contain;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
&.missing-indicator {
padding-top: 20px + 48px;
.regeneration-indicator__figure {
background-image: url('~flavours/glitch/images/elephant_ui_disappointed.svg');
}
}
&__label {
margin-top: 200px;
strong {
display: block;
margin-bottom: 10px;
color: lighten($ui-base-color, 34%);
}
span {
font-size: 15px;
font-weight: 400;
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View file

@ -19,13 +19,14 @@ export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
export const TIMELINE_CONTEXT_UPDATE = 'CONTEXT_UPDATE';
export function refreshTimelineSuccess(timeline, statuses, skipLoading, next) {
export function refreshTimelineSuccess(timeline, statuses, skipLoading, next, partial) {
return {
type: TIMELINE_REFRESH_SUCCESS,
timeline,
statuses,
skipLoading,
next,
partial,
};
};
@ -88,7 +89,7 @@ export function refreshTimeline(timelineId, path, params = {}) {
return function (dispatch, getState) {
const timeline = getState().getIn(['timelines', timelineId], ImmutableMap());
if (timeline.get('isLoading') || timeline.get('online')) {
if (timeline.get('isLoading') || (timeline.get('online') && !timeline.get('isPartial'))) {
return;
}
@ -104,8 +105,12 @@ export function refreshTimeline(timelineId, path, params = {}) {
dispatch(refreshTimelineRequest(timelineId, skipLoading));
api(getState).get(path, { params }).then(response => {
if (response.status === 206) {
dispatch(refreshTimelineSuccess(timelineId, [], skipLoading, null, true));
} else {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null));
dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null, false));
}
}).catch(error => {
dispatch(refreshTimelineFail(timelineId, error, skipLoading));
});

View file

@ -2,9 +2,14 @@ import React from 'react';
import { FormattedMessage } from 'react-intl';
const MissingIndicator = () => (
<div className='missing-indicator'>
<div className='regeneration-indicator missing-indicator'>
<div>
<FormattedMessage id='missing_indicator.label' defaultMessage='Not found' />
<div className='regeneration-indicator__figure' />
<div className='regeneration-indicator__label'>
<FormattedMessage id='missing_indicator.label' tagName='strong' defaultMessage='Not found' />
<FormattedMessage id='missing_indicator.sublabel' defaultMessage='This resource could not be found' />
</div>
</div>
</div>
);

View file

@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import StatusContainer from '../containers/status_container';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ScrollableList from './scrollable_list';
import { FormattedMessage } from 'react-intl';
export default class StatusList extends ImmutablePureComponent {
@ -16,6 +17,7 @@ export default class StatusList extends ImmutablePureComponent {
trackScroll: PropTypes.bool,
shouldUpdateScroll: PropTypes.func,
isLoading: PropTypes.bool,
isPartial: PropTypes.bool,
hasMore: PropTypes.bool,
prepend: PropTypes.node,
emptyMessage: PropTypes.node,
@ -49,7 +51,22 @@ export default class StatusList extends ImmutablePureComponent {
render () {
const { statusIds, ...other } = this.props;
const { isLoading } = other;
const { isLoading, isPartial } = other;
if (isPartial) {
return (
<div className='regeneration-indicator'>
<div>
<div className='regeneration-indicator__figure' />
<div className='regeneration-indicator__label'>
<FormattedMessage id='regeneration_indicator.label' tagName='strong' defaultMessage='Loading&hellip;' />
<FormattedMessage id='regeneration_indicator.sublabel' defaultMessage='Your home feed is being prepared!' />
</div>
</div>
</div>
);
}
const scrollableContent = (isLoading || statusIds.size > 0) ? (
statusIds.map((statusId) => (

View file

@ -1,6 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
import { expandHomeTimeline } from '../../actions/timelines';
import { expandHomeTimeline, refreshHomeTimeline } from '../../actions/timelines';
import PropTypes from 'prop-types';
import StatusListContainer from '../ui/containers/status_list_container';
import Column from '../../components/column';
@ -16,6 +16,7 @@ const messages = defineMessages({
const mapStateToProps = state => ({
hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0,
isPartial: state.getIn(['timelines', 'home', 'isPartial'], false),
});
@connect(mapStateToProps)
@ -26,6 +27,7 @@ export default class HomeTimeline extends React.PureComponent {
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool,
isPartial: PropTypes.bool,
columnId: PropTypes.string,
multiColumn: PropTypes.bool,
};
@ -57,6 +59,39 @@ export default class HomeTimeline extends React.PureComponent {
this.props.dispatch(expandHomeTimeline());
}
componentDidMount () {
this._checkIfReloadNeeded(false, this.props.isPartial);
}
componentDidUpdate (prevProps) {
this._checkIfReloadNeeded(prevProps.isPartial, this.props.isPartial);
}
componentWillUnmount () {
this._stopPolling();
}
_checkIfReloadNeeded (wasPartial, isPartial) {
const { dispatch } = this.props;
if (wasPartial === isPartial) {
return;
} else if (!wasPartial && isPartial) {
this.polling = setInterval(() => {
dispatch(refreshHomeTimeline());
}, 3000);
} else if (wasPartial && !isPartial) {
this._stopPolling();
}
}
_stopPolling () {
if (this.polling) {
clearInterval(this.polling);
this.polling = null;
}
}
render () {
const { intl, hasUnread, columnId, multiColumn } = this.props;
const pinned = !!columnId;

View file

@ -120,13 +120,17 @@ export default class ListTimeline extends React.PureComponent {
if (typeof list === 'undefined') {
return (
<Column>
<div className='scrollable'>
<LoadingIndicator />
</div>
</Column>
);
} else if (list === false) {
return (
<Column>
<div className='scrollable'>
<MissingIndicator />
</div>
</Column>
);
}

View file

@ -47,6 +47,7 @@ const makeMapStateToProps = () => {
const mapStateToProps = (state, { timelineId }) => ({
statusIds: getStatusIds(state, { type: timelineId }),
isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true),
isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false),
hasMore: !!state.getIn(['timelines', timelineId, 'next']),
});

View file

@ -50,7 +50,7 @@
"column_header.unpin": "Desafixar",
"column_subheading.navigation": "Navegação",
"column_subheading.settings": "Configurações",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.hashtag_warning": "Esse toot não será listado em nenhuma hashtag por ser não listado. Somente toots públicos podem ser pesquisados por hashtag.",
"compose_form.lock_disclaimer": "A sua conta não está {locked}. Qualquer pessoa pode te seguir e visualizar postagens direcionadas a apenas seguidores.",
"compose_form.lock_disclaimer.lock": "trancada",
"compose_form.placeholder": "No que você está pensando?",
@ -223,7 +223,7 @@
"status.media_hidden": "Mídia escondida",
"status.mention": "Mencionar @{name}",
"status.more": "Mais",
"status.mute": "Mute @{name}",
"status.mute": "Silenciar @{name}",
"status.mute_conversation": "Silenciar conversa",
"status.open": "Expandir",
"status.pin": "Fixar no perfil",

View file

@ -195,8 +195,8 @@
"privacy.private.short": "Iba sledujúci",
"privacy.public.long": "Pošli všetkým",
"privacy.public.short": "Verejne",
"privacy.unlisted.long": "Neposielať verejne",
"privacy.unlisted.short": "Nie je v zozname",
"privacy.unlisted.long": "Neposielať do verejných časových osí",
"privacy.unlisted.short": "Verejne mimo osí",
"relative_time.days": "{number}d",
"relative_time.hours": "{number}h",
"relative_time.just_now": "now",

View file

@ -30,7 +30,7 @@ const initialTimeline = ImmutableMap({
items: ImmutableList(),
});
const normalizeTimeline = (state, timeline, statuses, next) => {
const normalizeTimeline = (state, timeline, statuses, next, isPartial) => {
const oldIds = state.getIn([timeline, 'items'], ImmutableList());
const ids = ImmutableList(statuses.map(status => status.get('id'))).filter(newId => !oldIds.includes(newId));
const wasLoaded = state.getIn([timeline, 'loaded']);
@ -41,6 +41,7 @@ const normalizeTimeline = (state, timeline, statuses, next) => {
mMap.set('isLoading', false);
if (!hadNext) mMap.set('next', next);
mMap.set('items', wasLoaded ? ids.concat(oldIds) : ids);
mMap.set('isPartial', isPartial);
}));
};
@ -124,7 +125,7 @@ export default function timelines(state = initialState, action) {
case TIMELINE_EXPAND_FAIL:
return state.update(action.timeline, initialTimeline, map => map.set('isLoading', false));
case TIMELINE_REFRESH_SUCCESS:
return normalizeTimeline(state, action.timeline, fromJS(action.statuses), action.next);
return normalizeTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial);
case TIMELINE_EXPAND_SUCCESS:
return appendNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next);
case TIMELINE_UPDATE:

View file

@ -2303,7 +2303,7 @@
}
}
.missing-indicator {
.regeneration-indicator {
text-align: center;
font-size: 16px;
font-weight: 500;
@ -2314,11 +2314,46 @@
flex: 1 1 auto;
align-items: center;
justify-content: center;
padding: 20px;
& > div {
background: url('~images/mastodon-not-found.png') no-repeat center -50px;
padding-top: 210px;
width: 100%;
background: transparent;
padding-top: 0;
}
&__figure {
background: url('~images/elephant_ui_working.svg') no-repeat center 0;
width: 100%;
height: 160px;
background-size: contain;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
&.missing-indicator {
padding-top: 20px + 48px;
.regeneration-indicator__figure {
background-image: url('~images/elephant_ui_disappointed.svg');
}
}
&__label {
margin-top: 200px;
strong {
display: block;
margin-bottom: 10px;
color: lighten($ui-base-color, 34%);
}
span {
font-size: 15px;
font-weight: 400;
}
}
}
@ -2749,7 +2784,6 @@
@keyframes heartbeat {
from {
transform: scale(1);
transform-origin: center center;
animation-timing-function: ease-out;
}
@ -2775,6 +2809,7 @@
}
.pulse-loading {
transform-origin: center center;
animation: heartbeat 1.5s ease-in-out infinite both;
}

View file

@ -54,7 +54,7 @@ class NotifyService < BaseService
end
def response_to_recipient?
@notification.target_status.in_reply_to_account_id == @recipient.id
@notification.target_status.in_reply_to_account_id == @recipient.id && @notification.target_status.thread&.direct_visibility?
end
def optional_non_following_and_direct?

View file

@ -3,5 +3,6 @@
class PrecomputeFeedService < BaseService
def call(account)
FeedManager.instance.populate_feed(account)
Redis.current.del("account:#{account.id}:regeneration")
end
end

View file

@ -3,7 +3,7 @@
class RegenerationWorker
include Sidekiq::Worker
sidekiq_options queue: 'pull', backtrace: true, unique: :until_executed
sidekiq_options unique: :until_executed
def perform(account_id, _ = :home)
account = Account.find(account_id)

View file

@ -265,12 +265,18 @@ ca:
unresolved: No resolt
view: Visualització
settings:
activity_api_enabled:
desc_html: Compte d'estatus publicats localment, usuaris actius i registres nous en cubs setmanals
title: Publica estadístiques agregades sobre l'activitat de l'usuari
bootstrap_timeline_accounts:
desc_html: Separa diversos noms d'usuari amb comes. Només funcionaran els comptes locals i desbloquejats. El valor predeterminat quan està buit és tots els administradors locals..
title: El seguiment per defecte per als nous usuaris
contact_information:
email: Introdueix una adreça de correu electrònic pùblica
username: Introdueix un nom d'usuari
peers_api_enabled:
desc_html: Els noms de domini que ha trobat aquesta instància al fediverse
title: Publica la llista d'instàncies descobertes
registrations:
closed_message:
desc_html: Apareix en la primera pàgina quan es tanquen els registres<br>Pots utilitzar etiquetes HTML
@ -345,7 +351,7 @@ ca:
warning: Aneu amb compte amb aquestes dades. No ho compartiu mai amb ningú!
your_token: El token d'accés
auth:
agreement_html: En inscriure't, acceptes <a href="%{rules_path}">els nostres termes del servei</a> i <a href="%{terms_path}">la nostra política de privadesa</a>.
agreement_html: En inscriure't, acceptes seguir <a href="%{rules_path}">els nostres termes del servei</a> i <a href="%{terms_path}">la nostra política de privadesa</a>.
change_password: Canvia la contrasenya
delete_account: Esborra el compte
delete_account_html: Si vols esborrar el teu compte pots <a href="%{path}">fer-ho aquí</a>. Se't demanarà confirmació.
@ -546,12 +552,14 @@ ca:
blackberry: Blackberry
chrome: Chrome
edge: Microsoft Edge
electron: Electron
firefox: Firefox
generic: Navegador desconegut
ie: Internet Explorer
micro_messenger: MicroMessenger
nokia: Nokia S40 Ovi Browser
opera: Opera
otter: Altre
phantom_js: PhantomJS
qq: QQ Browser
safari: Safari
@ -596,7 +604,7 @@ ca:
open_in_web: Obre en la web
over_character_limit: Límit de caràcters de %{max} superat
pin_errors:
limit: S'han fixat massa toots
limit: Ja has fixat el màxim nombre de toots
ownership: El toot d'algú altre no es pot fixar
private: No es pot fixar el toot no públic
reblog: No es pot fixar un impuls

View file

@ -17,11 +17,32 @@ ca:
unconfirmed: Has de confirmar l'adreça de correu electrònic abans de continuar.
mailer:
confirmation_instructions:
action: Verificar l'adreça de correu
explanation: Has creat un compte a %{host} amb aquesta adreça de correu electrònic. Estàs a un sol clic de l'activació. Si no fos així, ignora aquest correu electrònic.
extra_html: Si us plau consulta també <a href="%{terms_path}"> les regles de la instància</a> i <a href="%{policy_path}"> les nostres condicions de servei</a>.
subject: 'Mastodon: Instruccions de confirmació'
title: Verifica l'adreça de correu
email_changed:
explanation: 'L''adreça de correu del teu compte s''està canviant a:'
extra: Si no has canviat el teu correu electrònic, és probable que algú hagi accedit al teu compte. Si us plau, canvia la contrasenya immediatament o posa't en contacte amb l'administrador de l'instància si no pots accedir al teu compte.
subject: 'Mastodon: s''ha canviat l''adreça electrònica'
title: Nova adreça de correu electrònic
password_change:
explanation: S'ha canviat la contrasenya del teu compte.
extra: Si no has canviat el teu correu electrònic, és probable que algú hagi accedit al teu compte. Si us plau, canvia la contrasenya immediatament o posa't en contacte amb l'administrador de l'instància si no pots accedir al teu compte.
subject: 'Mastodon: Contrasenya canviada'
title: Contrasenya canviada
reconfirmation_instructions:
explanation: Confirma la nova adreça per canviar el teu correu electrònic.
extra: Si no has iniciat aquest canvi, ignora aquest correu electrònic. L'adreça electrònica del compte de Mastodon no canviarà fins que accedeixis a l'enllaç de dalt.
subject: 'Mastodon: Confirma el correu electrònic per a %{instance}'
title: Verifica l'adreça de correu electrònic
reset_password_instructions:
action: Canviar contrasenya
explanation: Has sol·licitat una contrasenya nova per al teu compte.
extra: Si no ho has sol·licitat, ignora aquest correu electrònic. La teva contrasenya no canviarà fins que accedeixis a l'enllaç de dalt i creis un de nou.
subject: 'Mastodon: Instruccions per a reiniciar contrassenya'
title: Contrasenya restablerta
unlock_instructions:
subject: 'Mastodon: Instruccions per a desblocar'
omniauth_callbacks:

View file

@ -18,8 +18,12 @@ gl:
mailer:
confirmation_instructions:
subject: 'Mastodon: Instruccións de confirmación para %{instance}'
email_changed:
subject: 'Mastodon: email cambiado'
password_change:
subject: 'Mastodon: contrasinal cambiado'
reconfirmation_instructions:
subject: 'Mastodon: Confirme email para %{instance}'
reset_password_instructions:
subject: 'Mastodon: Instruccións para restablecer o contrasinal'
unlock_instructions:

View file

@ -17,15 +17,32 @@ ja:
unconfirmed: 続行するにはメールアドレスを確認する必要があります。
mailer:
confirmation_instructions:
action: メールアドレスの確認
explanation: このメールアドレスで%{host}にアカウントを作成しました。有効にするまであと一歩です。もし心当たりがない場合、申し訳ありませんがこのメールを無視してください。
extra_html: また <a href="%{terms_path}">インスタンスのルール</a> と <a href="%{policy_path}">利用規約</a> もお読みください。
subject: 'Mastodon: メールアドレスの確認'
title: メールアドレスの確認
email_changed:
explanation: 'アカウントのメールアドレスは以下のように変更されます:'
extra: メールアドレスの変更を行っていない場合、他の誰かがあなたのアカウントにアクセスした可能性があります。すぐにパスワードを変更するか、アカウントがロックされている場合はインスタンス管理者に連絡してください。
subject: 'Mastodon: メールアドレスの変更'
title: 新しいメールアドレス
password_change:
explanation: パスワードが変更されました。
extra: パスワードの変更を行っていない場合、他の誰かがあなたのアカウントにアクセスした可能性があります。すぐにパスワードを変更するか、アカウントがロックされている場合はインスタンス管理者に連絡してください。
subject: 'Mastodon: パスワードが変更されました'
title: パスワードの変更
reconfirmation_instructions:
explanation: メールアドレスを変更するため新しいアドレスを確認してください。
extra: この変更に心当たりがない場合、このメールを無視してください。上記リンク先にアクセスするまでアカウントのメールアドレスは変更されません。
subject: 'Mastodon: %{instance}のメールを確認する'
title: メールアドレスの確認
reset_password_instructions:
action: パスワードの変更
explanation: あなたのアカウントに対しパスワードの再発行が要求されました。
extra: この要求に心当たりがない場合、このメールを無視してください。上記リンク先にアクセスし新しいものを作成するまでパスワードは変更されません。
subject: 'Mastodon: パスワード再発行'
title: パスワード再発行
unlock_instructions:
subject: 'Mastodon: アカウントのロックの解除'
omniauth_callbacks:

View file

@ -19,8 +19,12 @@ nl:
mailer:
confirmation_instructions:
subject: 'Mastodon: E-mail bevestigen voor %{instance}'
email_changed:
subject: 'Mastodon: E-mailadres is veranderd'
password_change:
subject: 'Mastodon: Wachtwoord veranderd'
reconfirmation_instructions:
subject: 'Mastodon: Bevestig het e-mailadres voor %{instance}'
reset_password_instructions:
subject: 'Mastodon: Wachtwoord opnieuw instellen'
unlock_instructions:

View file

@ -17,15 +17,32 @@ pl:
unconfirmed: Zweryfikuj adres e-mail, aby kontynuować.
mailer:
confirmation_instructions:
action: Zweryfikuj adres e-mail
explanation: Utworzyłeś konto na %{host} podając ten adres e-mail. Jedno kliknięcie dzieli Cię od aktywacji tego konta. Jeżeli to nie Ty, zignoruj ten e-mail.
extra_html: Przeczytaj też <a href="%{terms_path}">regulamin instancji</a> i <a href="%{policy_path}">nasze zasady użytkowania</a>.
subject: 'Mastodon: Instrukcje weryfikacji adresu e-mail'
title: Zweryfikuj adres e-mail
email_changed:
explanation: 'Adres e-mail dla Twojego konta zostanie zmieniony na:'
extra: Jeżeli nie próbowałeś zmienić adresu e-mail, prawdopodobnie ktoś uzyskał dostęp do Twojego konta. Zmień natychmiastowo hasło lub skontaktuj się z administratorem isntancji, jeżeli nie masz dostępu do konta.
subject: 'Mastodon: Zmieniono adres e-mail'
title: Nowy adres e-mail
password_change:
explanation: Hasło do Twojego konta zostało zmienione.
extra: Jeżeli nie zmieniałeś hasła, prawdopodobnie ktoś uzyskał dostęp do Twojego konta. Zmień hasło natychmiastowo lub skontaktuj się z administratorem instancji, jeżeli nie masz dostępu do konta.
subject: 'Mastodon: Zmieniono hasło'
title: Zmieniono hasło
reconfirmation_instructions:
explanation: Potwierdź nowy adres aby zmienić e-mail.
extra: Jeżeli nie próbowałeś zmienić e-maila, zignoruj tą wiadomość. Adres e-mail przypisany do konta Mastodona nie ulegnie zmianie, jeżeli nie użyjesz powyższego odnośniku.
subject: 'Mastodon: Potwierdź adres e-mail na &{instance}'
title: Zweryfikuj adres e-mail
reset_password_instructions:
action: Zmień hasło
explanation: Próbowałeś uzyskać nowe hasło do swojego konta.
extra: Jeżeli to nie Ty, zignoruj tą wiadomość. Twoje hasło nie ulegnie zmianie, jeżeli nie wykorzystasz powyższego odnośnika i nie utworzysz nowego hasła.
subject: 'Mastodon: Instrukcje ustawienia nowego hasła'
title: Przywracanie hasła
unlock_instructions:
subject: 'Mastodon: Instrukcje odblokowania konta'
omniauth_callbacks:
@ -38,7 +55,7 @@ pl:
updated: Twoje hasło zostało zmienione. Jesteś zalogowany/a.
updated_not_active: Twoje hasło zostało zmienione.
registrations:
destroyed: Twoje konto zostało anulowane. Mamy jednak nadzieję, że do nas wrócisz. Do zobaczenia!
destroyed: Twoje konto zostało zawieszone. Mamy jednak nadzieję, że do nas wrócisz. Do zobaczenia!
signed_up: Twoje konto zostało utworzone. Witamy!
signed_up_but_inactive: Twoje konto zostało utworzone. Nie mogliśmy Cię jednak zalogować, ponieważ konto nie zostało jeszcze aktywowane.
signed_up_but_locked: Twoje konto zostało utworzone. Nie mogliśmy Cię jednak zalogować, ponieważ konto jest zablokowane.

View file

@ -18,8 +18,12 @@ pt-BR:
mailer:
confirmation_instructions:
subject: 'Mastodon: Instruções de confirmação'
email_changed:
subject: 'Mastodon: Email alterado'
password_change:
subject: 'Mastodon: Senha modificada'
reconfirmation_instructions:
subject: 'Mastodon: Confirmar emai para %{instance}'
reset_password_instructions:
subject: 'Mastodon: Instruções para mudança de senha'
unlock_instructions:

View file

@ -83,7 +83,7 @@ pl:
invalid_grant: Grant uwierzytelnienia jest niepoprawny, przeterminowany, unieważniony, nie pasuje do URI przekierowwania użytego w żądaniu uwierzytelnienia, lub został wystawiony przez innego klienta.
invalid_redirect_uri: URI przekierowania jest nieprawidłowy.
invalid_request: 'Żądanie jest nieprawidłowe: brakujący parametr, niewspierana wartość parametru, lub inny błąd.'
invalid_resource_owner: Dostarczone dane uwierzytelniające właściciela zasobu są niepoprawne, lub właściciel zasobu nie może zostać znaleziony.
invalid_resource_owner: Dostarczone dane uwierzytelniające właściciela zasobu są niepoprawne, lub właściciel zasobu nie może zostać znaleziony
invalid_scope: Zakres żądania jest niepoprawny, nieznany, lub błędnie zbudowany.
invalid_token:
expired: Token dostępowy wygasł

View file

@ -265,12 +265,18 @@ gl:
unresolved: Non resolto
view: Vista
settings:
activity_api_enabled:
desc_html: Conta de estados publicados localmente, usuarias activas, e novos rexistros por semana
title: Publicar estatísticas agregadas sobre a actividade da usuaria
bootstrap_timeline_accounts:
desc_html: Separar múltiples nomes de usuaria con vírgulas. Só funcionarán as contas locais non bloqueadas. Si baldeiro, por omisión son todos os local admin.
title: Seguimentos por omisión para novas usuarias
contact_information:
email: e-mail de traballo
username: Nome de usuaria de contacto
peers_api_enabled:
desc_html: Nome de dominio que esta instancia atopou no fediverso
title: Publicar lista de instancias descubertas
registrations:
closed_message:
desc_html: Mostrado na páxina de portada cando o rexistro está pechado. Pode utilizar etiquetas HTML
@ -509,6 +515,7 @@ gl:
quadrillion: Q
thousand: K
trillion: T
unit: " "
pagination:
next: Seguinte
prev: Previo

View file

@ -338,10 +338,13 @@ ja:
body: "%{reporter} が %{target} を通報しました"
subject: "%{instance} の新しい通報 (#%{id})"
application_mailer:
notification_preferences: メール設定の変更
salutation: "%{name} さん"
settings: 'メール設定の変更: %{link}'
signature: Mastodon %{instance} インスタンスからの通知
view: 'リンク:'
view_profile: プロフィールを表示
view_status: トゥートを表示
applications:
created: アプリが作成されました
destroyed: アプリが削除されました
@ -491,29 +494,38 @@ ja:
title: モデレーション
notification_mailer:
digest:
action: 全ての通知を表示
body: "%{instance} での最後のログインからの出来事:"
mention: "%{name} さんがあなたに返信しました:"
new_followers_summary:
one: 新たなフォロワーを獲得しました!
other: "%{count} 人の新たなフォロワーを獲得しました!"
one: また、離れている間に新たなフォロワーを獲得しました!
other: また、離れている間に%{count} 人の新たなフォロワーを獲得しました!
subject:
one: "新しい1件の通知 \U0001F418"
other: "新しい%{count}件の通知 \U0001F418"
title: 不在の間に…
favourite:
body: "%{name} さんにお気に入り登録された、あなたのトゥートがあります:"
subject: "%{name} さんにお気に入りに登録されました"
title: 新たなお気に入り登録
follow:
body: "%{name} さんにフォローされています!"
subject: "%{name} さんにフォローされています"
title: 新たなフォロワー
follow_request:
action: フォローリクエストの管理
body: "%{name} さんがあなたにフォローをリクエストしました"
subject: "%{name} さんからのフォローリクエスト"
title: 新たなフォローリクエスト
mention:
action: 返信
body: "%{name} さんから返信がありました:"
subject: "%{name} さんに返信されました"
title: 新たな返信
reblog:
body: "%{name} さんにブーストされた、あなたのトゥートがあります:"
subject: "%{name} さんにブーストされました"
title: 新たなブースト
number:
human:
decimal_units:

View file

@ -265,12 +265,18 @@ nl:
unresolved: Onopgelost
view: Weergeven
settings:
activity_api_enabled:
desc_html: Wekelijks overzicht van de hoeveelheid lokale toots, actieve gebruikers en nieuwe registraties
title: Statistieken over gebruikersactiviteit publiceren
bootstrap_timeline_accounts:
desc_html: Meerdere gebruikersnamen met komma's scheiden. Alleen lokale en niet opgeschorte accounts werken. Laat leeg voor alle lokale beheerders.
title: Standaard te volgen accounts voor nieuwe gebruikers
contact_information:
email: Vul een openbaar gebruikt e-mailadres in
username: Vul een gebruikersnaam in
peers_api_enabled:
desc_html: Domeinnamen die deze server in de fediverse is tegengekomen
title: Lijst van bekende servers publiceren
registrations:
closed_message:
desc_html: Wordt op de voorpagina weergegeven wanneer registratie van nieuwe accounts is uitgeschakeld<br>En ook hier kan je HTML gebruiken
@ -476,11 +482,11 @@ nl:
title: Moderatie
notification_mailer:
digest:
body: 'Hier is een korte samenvatting van wat je hebt gemist op %{instance} sinds jouw laatste bezoek op %{since}:'
body: Hier is een korte samenvatting van de berichten die je sinds jouw laatste bezoek op %{since} hebt gemist
mention: "%{name} vermeldde jou in:"
new_followers_summary:
one: Jij hebt een nieuwe volger! Hoera!
other: Jij hebt %{count} nieuwe volgers! Prachtig!
one: Je hebt trouwens sinds je weg was er ook een nieuwe volger bijgekregen! Hoera!
other: Je hebt trouwens sinds je weg was er ook %{count} nieuwe volgers bijgekregen! Fantastisch!
subject:
one: "1 nieuwe melding sinds jouw laatste bezoek \U0001F418"
other: "%{count} nieuwe meldingen sinds jouw laatste bezoek \U0001F418"

View file

@ -46,7 +46,7 @@ pl:
posts: Wpisy
posts_with_replies: Wpisy z odpowiedziami
remote_follow: Śledź zdalnie
reserved_username: Ta nazwa użytkownika jest zarezerwowana.
reserved_username: Ta nazwa użytkownika jest zarezerwowana
roles:
admin: Administrator
moderator: Moderator
@ -183,7 +183,7 @@ pl:
title: Niestandardowe emoji
unlisted: Niewidoczne
update_failed_msg: Nie udało się zaktualizować emoji
updated_msg: Pomyślnie zaktualizowano emoji
updated_msg: Pomyślnie zaktualizowano emoji!
upload: Dodaj
domain_blocks:
add_new: Dodaj nową
@ -194,7 +194,7 @@ pl:
create: Utwórz blokadę
hint: Blokada domen nie zabroni tworzenia wpisów kont w bazie danych, ale pozwoli na automatyczną moderację kont do nich należących.
severity:
desc_html: "<strong>Wyciszenie</strong> uczyni wpisy użytkownika widoczne tylko dla osób, które go śledzą. <strong>Zawieszenie</strong> spowoduje usunięcie całej zawartości dodanej przez użytkownika."
desc_html: "<strong>Wyciszenie</strong> uczyni wpisy użytkownika widoczne tylko dla osób, które go śledzą. <strong>Zawieszenie</strong> spowoduje usunięcie całej zawartości dodanej przez użytkownika. Użyj <strong>Żadne</strong>, jeżeli chcesz jedynie odrzucać zawartość multimedialną."
noop: Nic nie rób
silence: Wycisz
suspend: Zawieś
@ -305,7 +305,7 @@ pl:
title: Niestandardowe zasady użytkowania
site_title: Nazwa instancji
thumbnail:
desc_html: 'Używana w podglądzie przez OpenGraph i API. Zalecany rozmiar: 1200x630 pikseli.'
desc_html: 'Używana w podglądzie przez OpenGraph i API. Zalecany rozmiar: 1200x630 pikseli'
title: Miniatura instancji
timeline_preview:
desc_html: Wyświetlaj publiczną oś czasu na stronie widocznej dla niezalogowanych
@ -339,10 +339,12 @@ pl:
body: Użytkownik %{reporter} zgłosił %{target}
subject: Nowe zgłoszenie na %{instance} (#%{id})
application_mailer:
notification_preferences: Zmień ustawienia e-maili
salutation: "%{name},"
settings: 'Zmień ustawienia powiadamiania: %{link}'
signature: Powiadomienie Mastodona z instancji %{instance}
view: 'Zobacz:'
view_status: Wyświetl wpis
applications:
created: Pomyślnie utworzono aplikację
destroyed: Pomyślnie usunięto aplikację
@ -494,33 +496,42 @@ pl:
title: Moderacja
notification_mailer:
digest:
body: 'Oto krótkie podsumowanie co Cię ominęło na %{instance} od Twojej ostatniej wizyty (%{since}):'
action: Wyświetl wszystkie powiadomienia
body: Oto krótkie podsumowanie wiadomości, które ominęły Cię od Twojej ostatniej wizyty (%{since})
mention: "%{name} wspomniał o Tobie w:"
new_followers_summary:
few: "(%{count}) nowe osoby śledzą Cię!"
many: "(%{count}) nowych osób Cię śledzi! Wspaniale!"
one: Śledzi Cię nowa osoba! Gratulacje!
other: "(%{count}) nowych osób Cię śledzi! Wspaniale!"
one: Dodatkowo, w czasie nieobecności zaczęła śledzić Cię jedna osoba Gratulacje!
other: Dodatkowo, zaczęło Cię śledzić %{count} nowych osób! Wspaniale!
subject:
few: "%{count} nowe powiadomienia od Twojej ostatniej wizyty \U0001F418"
many: "%{count} nowych powiadomień od Twojej ostatniej wizyty \U0001F418"
one: "1 nowe powiadomienie od Twojej ostatniej wizyty \U0001F418"
other: "%{count} nowych powiadomień od Twojej ostatniej wizyty \U0001F418"
title: W trakcie Twojej nieobecności…
favourite:
body: 'Twój wpis został polubiony przez %{name}:'
subject: "%{name} lubi Twój wpis"
title: Nowe polubienie
follow:
body: "%{name} Cię śledzi!"
subject: "%{name} Cię śledzi"
title: Nowy śledzący
follow_request:
action: Zarządzaj prośbami o możliwość śledzenia
body: "%{name} poprosił o możliwość śledzenia Cię"
subject: 'Prośba o możliwość śledzenia: %{name}'
title: Nowa prośba o możliwość śledzenia
mention:
action: Odpowiedz
body: "%{name} wspomniał o Tobie w:"
subject: "%{name} wspomniał o Tobie"
title: Nowe wspomnienie o Tobie
reblog:
body: 'Twój wpis został podbity przez %{name}:'
subject: Twój wpis został podbity przez %{name}
title: Nowe podbicie
number:
human:
decimal_units:
@ -568,12 +579,14 @@ pl:
blackberry: Blackberry
chrome: Chrome
edge: Microsoft Edge
electron: Electron
firefox: Firefox
generic: nieznana przeglądarka
ie: Internet Explorer
micro_messenger: MicroMessenger
nokia: Nokia S40 Ovi Browser
opera: Opera
otter: Przeglądarka Otter
phantom_js: PhantomJS
qq: QQ Browser
safari: Safari

View file

@ -265,12 +265,18 @@ pt-BR:
unresolved: Não resolvido
view: Visualizar
settings:
activity_api_enabled:
desc_html: Contagem de status postados localmente, usuários ativos e novos cadastros filtrados semanalmente
title: Publicar estatísticas agregadas sobre atividade de usuários
bootstrap_timeline_accounts:
desc_html: Separe nomes de usuário através de vírgulas. Funciona apenas com contas locais e destrancadas. O padrão quando vazio são todos os administradores locais.
title: Usuários a serem seguidos por padrão por novas contas
contact_information:
email: E-mail
username: Contate usuário
peers_api_enabled:
desc_html: Nomes de domínio que essa instância encontrou no fediverso
title: Publicar lista de instâncias descobertas
registrations:
closed_message:
desc_html: Exibido na página inicial quando cadastros estão fechados. Você pode usar tags HTML
@ -285,7 +291,7 @@ pt-BR:
desc_html: Permitir que qualquer um crie uma conta
title: Cadastro aberto
show_staff_badge:
desc_html: Mostrar uma insígnia de equipe na página de usuário
desc_html: Mostrar uma insígnia de Equipe na página de usuário
title: Mostrar insígnia de equipe
site_description:
desc_html: Parágrafo introdutório na página inicial e em meta tags. Você pode usar tags HTML, em especial <code>&lt;a&gt;</code> e <code>&lt;em&gt;</code>.
@ -345,7 +351,7 @@ pt-BR:
warning: Tenha cuidado com estes dados. Nunca compartilhe com alguém!
your_token: Seu token de acesso
auth:
agreement_html: Cadastrando-se você concorda em seguir <a href="%{rules_path}">as regras da instância</a> e <a href="%{terms_path}">os nossos termos de serviço</a>.
agreement_html: Ao se cadastrar você concorda em seguir <a href="%{rules_path}">as regras da instância</a> e <a href="%{terms_path}">os nossos termos de serviço</a>.
change_password: Segurança
delete_account: Excluir conta
delete_account_html: Se você deseja excluir a sua conta, você pode <a href="%{path}">prosseguir para cá</a>. Uma confirmação será requisitada.
@ -596,7 +602,7 @@ pt-BR:
open_in_web: Abrir na web
over_character_limit: limite de caracteres de %{max} excedido
pin_errors:
limit: Você já fixou o máximo de toots possíveis
limit: Você já fixou a quantidade máxima de toots
ownership: Toots de outras pessoas não podem ser fixados
private: Toot não-público não pode ser fixado
reblog: Um compartilhamento não pode ser fixado

View file

@ -49,6 +49,7 @@ ru:
reserved_username: Имя пользователя зарезервировано
roles:
admin: Администратор
moderator: Мод
unfollow: Отписаться
admin:
account_moderation_notes:
@ -71,6 +72,8 @@ ru:
domain: Домен
edit: Изменить
email: E-mail
enable: Включить
enabled: Включен
feed_url: URL фида
followers: Подписчики
followers_url: URL подписчиков
@ -336,10 +339,12 @@ ru:
body: "%{reporter} подал(а) жалобу на %{target}"
subject: Новая жалоба, узел %{instance} (#%{id})
application_mailer:
notification_preferences: Изменить настройки e-mail
salutation: "%{name},"
settings: 'Изменить настройки e-mail: %{link}'
signature: Уведомления Mastodon от %{instance}
view: 'Просмотр:'
view_status: Просмотреть статус
applications:
created: Приложение успешно создано
destroyed: Приложение успешно удалено
@ -349,7 +354,7 @@ ru:
warning: Будьте очень внимательны с этими данными. Не делитесь ими ни с кем!
your_token: Ваш токен доступа
auth:
agreement_html: Создавая аккаунт, вы соглашаетесь с <a href="%{rules_path}">нашими правилами поведения</a> и <a href="%{terms_path}">политикой конфиденциальности</a>.
agreement_html: Создавая аккаунт, вы соглашаетесь с <a href="%{rules_path}">правилами узла</a> и <a href="%{terms_path}">нашими условиями обслуживания</a>.
change_password: Изменить пароль
delete_account: Удалить аккаунт
delete_account_html: Если Вы хотите удалить свой аккаунт, вы можете <a href="%{path}">перейти сюда</a>. У Вас будет запрошено подтверждение.
@ -554,6 +559,7 @@ ru:
blackberry: Blackberry
chrome: Chrome
edge: Microsoft Edge
electron: Electron
firefox: Firefox
generic: Неизвестный браузер
ie: Internet Explorer

View file

@ -4,7 +4,7 @@ ca:
hints:
defaults:
avatar: PNG, GIF o JPG. Màxim 2MB. Serà escalat a 120x120px
digest: S'envia després d'un llarg període d'inactivitat amb un resum de les mencions que has rebut en la teva absència
digest: Només s'envia després d'un llarg període d'inactivitat amb un resum de les mencions que has rebut en la teva absència
display_name:
one: <span class="name-counter">1</span> càracter
other: <span class="name-counter">%{count}</span> càracters

View file

@ -4,7 +4,7 @@ fr:
hints:
defaults:
avatar: Au format PNG, GIF ou JPG. 2Mo maximum. Sera réduit à 120x120px
digest: Envoyé après une longue période dinactivité et contient un résumé des notifications que vous avez reçues pendant votre absence
digest: Uniquement envoyé après une longue période dinactivité et uniquement si vous avez reçu des messages personnels pendant votre absence
display_name:
one: <span class="name-counter">1</span> caractère restant
other: <span class="name-counter">%{count}</span> caractères restants

View file

@ -4,7 +4,7 @@ ja:
hints:
defaults:
avatar: 2MBまでのPNGやGIF、JPGが利用可能です。120x120pxまで縮小されます
digest: 長期間ログインしなかった際、その期間に受け取った返信の要約を受け取ることができます
digest: 長期間使用していない場合と不在時に返信を受けた場合のみ送信されます
display_name: あと<span class="name-counter">%{count}</span>文字入力できます。
header: 2MBまでのPNGやGIF、JPGが利用可能です。 700x335pxまで縮小されます
locked: フォロワーを手動で承認する必要があります

View file

@ -4,27 +4,27 @@ pl:
hints:
defaults:
avatar: PNG, GIF lub JPG. Maksymalnie 2MB. Zostanie zmniejszony do 120x120px
digest: Wysyłane po długiej nieaktywności, zawiera podsumowanie wspomnień o Twoich profilu
digest: Wysyłane tylko po długiej nieaktywności, jeżeli w tym czasie otrzymaleś jakąś wiadomość bezpośrednią
display_name:
few: Pozostały <span class="name-counter">%{count}</span> znaki.
many: Pozostało <span class="name-counter">%{count}</span> znaków
one: Pozostał <span class="name-counter">1</span> znak.
one: Pozostał <span class="name-counter">1</span> znak
other: Pozostało <span class="name-counter">%{count}</span> znaków
header: PNG, GIF lub JPG. Maksymalnie 2MB. Zostanie zmniejszony do 700x335px
locked: Musisz akceptować prośby o śledzenie
note:
few: Pozostały <span class="name-counter">%{count}</span> znaki.
many: Pozostało <span class="name-counter">%{count}</span> znaków
one: Pozostał <span class="name-counter">1</span> znak.
one: Pozostał <span class="name-counter">1</span> znak
other: Pozostało <span class="name-counter">%{count}</span> znaków
setting_noindex: Wpływa na widoczność strony profilu i Twoich wpisów
setting_skin: Zmienia wygląd używanej odmiany Mastodona
imports:
data: Plik CSV wyeksportowany z innej instancji Mastodona
sessions:
otp: Wprowadź kod weryfikacji dwuetapowej z telefonu lub wykorzystaj jeden z kodów zapasowych
otp: Wprowadź kod weryfikacji dwuetapowej z telefonu lub wykorzystaj jeden z kodów zapasowych.
user:
filtered_languages: Wpisy w wybranych językach nie będą wyświetlać się na publicznych osiach czasu.
filtered_languages: Wpisy w wybranych językach nie będą wyświetlać się na publicznych osiach czasu
labels:
defaults:
avatar: Awatar

View file

@ -30,10 +30,12 @@ sk:
data: Dáta
display_name: Meno
email: Emailová adresa
expires_in: Expirovať po
filtered_languages: Filtrované jazyky
header: Obrázok v hlavičke
locale: Jazyk
locked: Zamknúť účet
max_uses: Maximálny počet použití
new_password: Nové heslo
note: O vás
otp_attempt: Dvoj-faktorový (2FA) kód
@ -44,6 +46,7 @@ sk:
setting_default_sensitive: Označiť každý obrázok/video/súbor ako chúlostivý
setting_delete_modal: Zobrazovať potvrdzovacie okno pred zmazaním toot-u
setting_noindex: Nezaradzovať vaše príspevky do indexácie pre vyhľadávanie
setting_reduce_motion: Redukovať pohyb v animáciách
setting_system_font_ui: Použiť štandardný systémový font
setting_theme: Vzhľad
setting_unfollow_modal: Zobrazovať potvrdzovacie okno pred skončením sledovania iného používateľa
@ -53,6 +56,7 @@ sk:
interactions:
must_be_follower: Blokovať notifikácie pod používateľov, ktorí vás nesledujú
must_be_following: Blokovať notifikácie od ľudí ktorý vás nesledujú
must_be_following_dm: Blokovať priame správy od ľudí ktorých nesleduješ
notification_emails:
digest: Posielať súhrnné emaily
favourite: Poslať email ak niekto označí váš príspevok ako obľúbený

View file

@ -1,6 +1,7 @@
---
sk:
about:
about_hashtag_html: Toto sú verejné tooty otagované <strong>#%{tagom}</strong>. Ak máš účet niekde vo fediverse, môžeš ich používať.
about_mastodon_html: Mastodon je sociálna sieť založená na otvorených webových protokoloch. Jej zrojový kód je otvorený a je decentralizovaná podobne ako email.
about_this: Info
closed_registrations: Registrácie sú momentálne uzatvorené. Avšak, existujú ďalšie Mastodon inštancie kde si môžete založiť účet a získať prístup do tejto siete od nich.
@ -38,6 +39,7 @@ sk:
followers: Sledujúci
following: Sleduje
media: Médiá
moved_html: "%{name} účet bol presunutý na %{new_profile_link}:"
nothing_here: Nič tu nie je!
people_followed_by: Ľudia, ktorých %{name} sleduje
people_who_follow: Ľudia sledujúci %{name}
@ -47,17 +49,31 @@ sk:
reserved_username: Prihlasovacie meno je rezervované
roles:
admin: Admin
moderator: Mod
unfollow: Prestať sledovať
admin:
account_moderation_notes:
account: Moderátor
create: Vytvoriť
created_at: Dátum
created_msg: Poznámka moderátora bola úspešne vytvorená!
delete: Zmazať
destroyed_msg: Poznámka moderátora bola úspešne zmazaná!
accounts:
are_you_sure: Ste si istý?
by_domain: Doména
confirm: Potvrdiť
confirmed: Potvrdený
demote: Degradovať
disable: Zablokovať
disable_two_factor_authentication: Zakázať 2FA
disabled: Blokovaný
display_name: Zobraziť meno
domain: Doména
edit: Upraviť
email: Email
enable: Povoliť
enabled: Povolený
feed_url: URL časovej osi
followers: Sledujúci
followers_url: URL sledujúcich
@ -69,12 +85,15 @@ sk:
local: Lokálne
remote: Federované
title: Lokácia
login_status: Status prihlásenia
media_attachments: Prílohy
memorialize: Zmeniť na "Navždy budeme spomínať"
moderation:
all: Všetko
silenced: Umlčané
suspended: Suspendované
title: Moderácia
moderation_notes: Moderátorské poznámky
most_recent_activity: Posledná aktivita
most_recent_ip: Posledná IP
not_subscribed: Nezaregistrované
@ -85,6 +104,7 @@ sk:
outbox_url: URL poslaných
perform_full_suspension: Suspendovať
profile_url: URL profilu
promote: Povýšiť
protocol: Protokol
public: Verejná os
push_subscription_expires: PuSH odoberanie expiruje
@ -92,6 +112,12 @@ sk:
reset: Reset
reset_password: Obnoviť heslo
resubscribe: Znovu odoberať
role: Oprávnenia
roles:
admin: Administrátor
moderator: Moderátor
staff: Člen
user: Používateľ
salmon_url: Salmon URL
search: Hľadať
shared_inbox_url: URL zdieľanej schránky
@ -108,17 +134,56 @@ sk:
unsubscribe: Prestať odoberať
username: Používateľske meno
web: Web
action_logs:
actions:
confirm_user: "%{name} potvrdil e-mailovú adresu používateľa %{target}"
create_custom_emoji: "%{name} nahral nový emoji %{target}"
create_domain_block: "%{name} zablokoval doménu %{target}"
create_email_domain_block: "%{name} pridal e-mailovú doménu %{target} na zoznam zakázaných"
demote_user: "%{name} degradoval používateľa %{target}"
destroy_domain_block: "%{name} povolil doménu %{target}"
destroy_email_domain_block: "%{name} pridal e-mailovú doménu %{target} na zoznam povolených"
destroy_status: "%{name} zmazal status %{target}"
disable_2fa_user: "%{name} zakázal 2FA pre používateľa %{target}"
disable_custom_emoji: "%{name} zakázal emoji %{target}"
disable_user: "%{name} zakázal prihlásenie pre používateľa %{target}"
enable_custom_emoji: "%{name} povolil emoji %{target}"
enable_user: "%{name} povolil prihlásenie pre používateľa %{target}"
memorialize_account: '%{name} zmenil účet %{target} na stránku "Navždy budeme spomínať"'
promote_user: "%{name} povýšil používateľa %{target}"
reset_password_user: "%{name} resetoval heslo pre používateľa %{target}"
resolve_report: "%{name} zamietol nahlásenie %{target}"
silence_account: "%{name} stíšil účet %{target}"
suspend_account: "%{name} suspendoval účet používateľa %{target}"
unsilence_account: "%{name} zrušil stíšenie účtu používateľa %{target}"
unsuspend_account: "%{name} zrušil suspendáciu účtu používateľa %{target}"
update_custom_emoji: "%{name} aktualizoval emoji %{target}"
update_status: "%{name} aktualizoval status %{target}"
title: Audit log
custom_emojis:
by_domain: Doména
copied_msg: Lokálna kópia emoji úspešne vytvorená
copy: Kopírovať
copy_failed_msg: Nebolo možné vytvoriť lokálnu kópiu tohto emoji
created_msg: Emoji úspešne vytvorené!
delete: Zmazať
destroyed_msg: Emojo úspešne zničený!
disable: Zakázať
disabled_msg: Emoji bolo úspešne zakázané
emoji: Emoji
enable: Povoliť
enabled_msg: Emoji bolo úspešne povolené
image_hint: PNG do 50KB
listed: V zozname
new:
title: Pridať vlastný emoji
title: Pridať nový vlastný emoji
overwrite: Prepísať
shortcode: Skratka
shortcode_hint: Aspoň 2 znaky, povolené sú alfanumerické alebo podčiarkovník
title: Vlastné emoji
unlisted: Nie je na zozname
update_failed_msg: Nebolo možné aktualizovať toto emoji
updated_msg: Emoji bolo úspešne aktualizované!
upload: Nahrať
domain_blocks:
add_new: Pridať nový
@ -129,16 +194,43 @@ sk:
create: Blokovať doménu
hint: Blokovanie domény stále dovolí vytvárať nové účty v databáze, ale tieto budú automaticky moderované.
severity:
desc_html: "<strong>Stíšenie</strong> urobí všetky príspevky účtu neviditeľné pre všetkých ktorý nesledujú tento účet. <strong>Suspendácia</strong> zmaže všetky príspevky, médiá a profilové informácie. Použi <strong>Nič</strong> ak chceš iba neprijímať súbory médií."
noop: Nič
silence: Stíšiť
suspend: Suspendovať
title: Nové blokovanie domény
reject_media: Odmietať súbory s obrázkami alebo videami
reject_media_hint: Zmaže lokálne uložené súbory médií a odmietne ich sťahovanie v budúcnosti. Irelevantné pre suspendáciu
severities:
noop: Nič
silence: Stíšiť
suspend: Suspendovať
severity: Závažnosť
show:
affected_accounts:
one: Jeden účet v databáze ovplyvnený
other: "%{count} účtov v databáze ovplyvnených"
retroactive:
silence: Zrušiť stíšenie všetkých existujúcich účtov z tejto domény
suspend: Zrušiť suspendáciu všetkých existujúcich účtov z tejto domény
title: Zrušiť blokovanie domény pre %{domain}
undo: Vrátiť späť
title: Blokovanie domén
undo: Späť
email_domain_blocks:
add_new: Pridať nový
created_msg: Emailová doména bola úspešne pridaná do zoznamu zakázaných
delete: Zmazať
destroyed_msg: Emailová doména bola úspešne vymazaná zo zoznamu zakázaných
domain: Doména
new:
create: Pridať doménu
auth:
login: Prihlásenie
settings:
authorized_apps: Autorizované aplikácie
back: Naspäť na stránku
users:
invalid_email: Emailová adresa je neplatná
invalid_otp_token: Neplatný 2FA kód
signed_in_as: 'Prihlásený ako:'

View file

@ -43,10 +43,25 @@ describe ApplicationController, type: :controller do
expect_updated_sign_in_at(user)
end
it 'regenerates feed when sign in is older than two weeks' do
allow(RegenerationWorker).to receive(:perform_async)
user.update(current_sign_in_at: 3.weeks.ago)
describe 'feed regeneration' do
before do
alice = Fabricate(:account)
bob = Fabricate(:account)
user.account.follow!(alice)
user.account.follow!(bob)
Fabricate(:status, account: alice, text: 'hello world')
Fabricate(:status, account: bob, text: 'yes hello')
Fabricate(:status, account: user.account, text: 'test')
user.update(last_sign_in_at: 'Tue, 04 Jul 2017 14:45:56 UTC +00:00', current_sign_in_at: 'Wed, 05 Jul 2017 22:10:52 UTC +00:00')
sign_in user, scope: :user
end
it 'sets a regeneration marker while regenerating' do
allow(RegenerationWorker).to receive(:perform_async)
get :show
expect_updated_sign_in_at(user)
@ -54,6 +69,15 @@ describe ApplicationController, type: :controller do
expect(RegenerationWorker).to have_received(:perform_async)
end
it 'regenerates feed when sign in is older than two weeks' do
get :show
expect_updated_sign_in_at(user)
expect(Redis.current.zcard(FeedManager.instance.key(:home, user.account_id))).to eq 3
expect(Redis.current.get("account:#{user.account_id}:regeneration")).to be_nil
end
end
def expect_updated_sign_in_at(user)
expect(user.reload.current_sign_in_at).to be_within(1.0).of(Time.now.utc)
end

View file

@ -82,10 +82,19 @@ RSpec.describe NotifyService do
is_expected.to_not change(Notification, :count)
end
context 'if the message chain initiated by recipient' do
context 'if the message chain initiated by recipient, but is not direct message' do
let(:reply_to) { Fabricate(:status, account: recipient) }
let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: reply_to)) }
it 'does not notify' do
is_expected.to_not change(Notification, :count)
end
end
context 'if the message chain initiated by recipient and is direct message' do
let(:reply_to) { Fabricate(:status, account: recipient, visibility: :direct) }
let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: reply_to)) }
it 'does notify' do
is_expected.to change(Notification, :count)
end