From 6d99a0b652d866d41a56140aa695864b20830411 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Wed, 23 May 2018 13:32:10 +0900 Subject: [PATCH 001/111] Fix tests for invites controller (regression from 4d81809f36fcbfe787e23d490f2cb0ad943ab32c) (#7597) --- spec/controllers/invites_controller_spec.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb index c5c6cb6..9f5ab67 100644 --- a/spec/controllers/invites_controller_spec.rb +++ b/spec/controllers/invites_controller_spec.rb @@ -7,15 +7,21 @@ describe InvitesController do sign_in user end + around do |example| + min_invite_role = Setting.min_invite_role + example.run + Setting.min_invite_role = min_invite_role + end + describe 'GET #index' do subject { get :index } + let(:user) { Fabricate(:user, moderator: false, admin: false) } let!(:invite) { Fabricate(:invite, user: user) } context 'when user is a staff' do - let(:user) { Fabricate(:user, moderator: true, admin: false) } - it 'renders index page' do + Setting.min_invite_role = 'user' expect(subject).to render_template :index expect(assigns(:invites)).to include invite expect(assigns(:invites).count).to eq 1 @@ -23,9 +29,8 @@ describe InvitesController do end context 'when user is not a staff' do - let(:user) { Fabricate(:user, moderator: false, admin: false) } - it 'returns 403' do + Setting.min_invite_role = 'modelator' expect(subject).to have_http_status 403 end end From d8864b9e9d4fd56606a3b809bedeb1216b6620ae Mon Sep 17 00:00:00 2001 From: ThibG Date: Wed, 23 May 2018 15:20:15 +0200 Subject: [PATCH 002/111] Fix caret position after selected suggestion and media upload (#7595) * Fix media upload reseting caret position to last inserted emoji * Fix caret position after inserting suggestions (fixes #6089) --- .../mastodon/features/compose/components/compose_form.js | 10 ++++------ .../features/compose/containers/compose_form_container.js | 1 + app/javascript/mastodon/reducers/compose.js | 7 ++++++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index 9cd39be..6cc594c 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -40,6 +40,7 @@ export default class ComposeForm extends ImmutablePureComponent { privacy: PropTypes.string, spoiler_text: PropTypes.string, focusDate: PropTypes.instanceOf(Date), + caretPosition: PropTypes.number, preselectDate: PropTypes.instanceOf(Date), is_submitting: PropTypes.bool, is_uploading: PropTypes.bool, @@ -96,7 +97,6 @@ export default class ComposeForm extends ImmutablePureComponent { } onSuggestionSelected = (tokenStart, token, value) => { - this._restoreCaret = null; this.props.onSuggestionSelected(tokenStart, token, value); } @@ -116,9 +116,9 @@ export default class ComposeForm extends ImmutablePureComponent { if (this.props.preselectDate !== prevProps.preselectDate) { selectionEnd = this.props.text.length; selectionStart = this.props.text.search(/\s/) + 1; - } else if (typeof this._restoreCaret === 'number') { - selectionStart = this._restoreCaret; - selectionEnd = this._restoreCaret; + } else if (typeof this.props.caretPosition === 'number') { + selectionStart = this.props.caretPosition; + selectionEnd = this.props.caretPosition; } else { selectionEnd = this.props.text.length; selectionStart = selectionEnd; @@ -138,10 +138,8 @@ export default class ComposeForm extends ImmutablePureComponent { handleEmojiPick = (data) => { const { text } = this.props; const position = this.autosuggestTextarea.textarea.selectionStart; - const emojiChar = data.native; const needsSpace = data.custom && position > 0 && !allowedAroundShortCode.includes(text[position - 1]); - this._restoreCaret = position + emojiChar.length + 1 + (needsSpace ? 1 : 0); this.props.onPickEmoji(position, data, needsSpace); } diff --git a/app/javascript/mastodon/features/compose/containers/compose_form_container.js b/app/javascript/mastodon/features/compose/containers/compose_form_container.js index c3aa580..3822dd7 100644 --- a/app/javascript/mastodon/features/compose/containers/compose_form_container.js +++ b/app/javascript/mastodon/features/compose/containers/compose_form_container.js @@ -19,6 +19,7 @@ const mapStateToProps = state => ({ spoiler_text: state.getIn(['compose', 'spoiler_text']), privacy: state.getIn(['compose', 'privacy']), focusDate: state.getIn(['compose', 'focusDate']), + caretPosition: state.getIn(['compose', 'caretPosition']), preselectDate: state.getIn(['compose', 'preselectDate']), is_submitting: state.getIn(['compose', 'is_submitting']), is_uploading: state.getIn(['compose', 'is_uploading']), diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 46d9d6c..62461d1 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -44,6 +44,7 @@ const initialState = ImmutableMap({ privacy: null, text: '', focusDate: null, + caretPosition: null, preselectDate: null, in_reply_to: null, is_composing: false, @@ -91,7 +92,6 @@ function appendMedia(state, media) { map.update('media_attachments', list => list.push(media)); map.set('is_uploading', false); map.set('resetFileKey', Math.floor((Math.random() * 0x10000))); - map.set('focusDate', new Date()); map.set('idempotencyKey', uuid()); if (prevSize === 0 && (state.get('default_sensitive') || state.get('spoiler'))) { @@ -119,6 +119,7 @@ const insertSuggestion = (state, position, token, completion) => { map.set('suggestion_token', null); map.update('suggestions', ImmutableList(), list => list.clear()); map.set('focusDate', new Date()); + map.set('caretPosition', position + completion.length + 1); map.set('idempotencyKey', uuid()); }); }; @@ -142,6 +143,7 @@ const insertEmoji = (state, position, emojiData, needsSpace) => { return state.merge({ text: `${oldText.slice(0, position)}${emoji} ${oldText.slice(position)}`, focusDate: new Date(), + caretPosition: position + emoji.length + 1, idempotencyKey: uuid(), }); }; @@ -216,6 +218,7 @@ export default function compose(state = initialState, action) { map.set('text', statusToTextMentions(state, action.status)); map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy'))); map.set('focusDate', new Date()); + map.set('caretPosition', null); map.set('preselectDate', new Date()); map.set('idempotencyKey', uuid()); @@ -259,6 +262,7 @@ export default function compose(state = initialState, action) { return state.withMutations(map => { map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' ')); map.set('focusDate', new Date()); + map.set('caretPosition', null); map.set('idempotencyKey', uuid()); }); case COMPOSE_DIRECT: @@ -266,6 +270,7 @@ export default function compose(state = initialState, action) { map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' ')); map.set('privacy', 'direct'); map.set('focusDate', new Date()); + map.set('caretPosition', null); map.set('idempotencyKey', uuid()); }); case COMPOSE_SUGGESTIONS_CLEAR: From 2587fcdd27613e52d620e3c93bb089932be48db0 Mon Sep 17 00:00:00 2001 From: ThibG Date: Fri, 25 May 2018 03:03:22 +0200 Subject: [PATCH 003/111] Use .star-icon instead of hardcoding color in detailed statuses (fixes #7610) (#7613) --- app/javascript/mastodon/features/status/components/action_bar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js index bb9b755..9162e13 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.js @@ -155,7 +155,7 @@ export default class ActionBar extends React.PureComponent {
-
+
{shareButton}
From cdbdf7f98bb45439908cb23d0dfae228d59b3c1f Mon Sep 17 00:00:00 2001 From: ThibG Date: Fri, 25 May 2018 14:26:45 +0200 Subject: [PATCH 004/111] Ignore multiple occurrences of a hashtag within a status (fixes #7585) (#7606) --- app/lib/activitypub/activity/create.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index ab1d63c..010ed1b 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -80,7 +80,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity hashtag = tag['name'].gsub(/\A#/, '').mb_chars.downcase hashtag = Tag.where(name: hashtag).first_or_initialize(name: hashtag) - status.tags << hashtag + status.tags << hashtag unless status.tags.include?(hashtag) rescue ActiveRecord::RecordInvalid nil end From 3f8f5642a12928710ba321fbdb2025113066344a Mon Sep 17 00:00:00 2001 From: Jeroen Date: Fri, 25 May 2018 14:27:14 +0200 Subject: [PATCH 005/111] Added the law requirements for the EU/EEA (#7605) * Added the law requirements for the EU/EEA See article 8 of the GDPR * fix * i18n-tasks normalize --- config/locales/en.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 262883e..0b0110c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -760,9 +760,13 @@ en:
-

Children's Online Privacy Protection Act Compliance

+

Site usage by children

-

Our site, products and services are all directed to people who are at least 13 years old. If this server is in the USA, and you are under the age of 13, per the requirements of COPPA (Children's Online Privacy Protection Act) do not use this site.

+

If this server is in the EU or the EEA: Our site, products and services are all directed to people who are at least 16 years old. If you are under the age of 16, per the requirements of the GDPR (General Data Protection Regulation) do not use this site.

+ +

If this server is in the USA: Our site, products and services are all directed to people who are at least 13 years old. If you are under the age of 13, per the requirements of COPPA (Children's Online Privacy Protection Act) do not use this site.

+ +

Law requirements can be different if this server is in another jurisdiction.


From 6042403621243b0492992a89342f61c8859a021f Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Fri, 25 May 2018 23:48:17 +0900 Subject: [PATCH 006/111] Fix color of /about/more link anchors (#7618) --- app/javascript/styles/mastodon/about.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index c9c0e30..7772899 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -396,7 +396,7 @@ $small-breakpoint: 960px; display: flex; justify-content: center; align-items: center; - color: $ui-primary-color; + color: $darker-text-color; text-decoration: none; padding: 12px 16px; line-height: 32px; From 7ea91dcbb3054f3dcad59f0ce4f72e140bbdaaa1 Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Sat, 26 May 2018 01:36:26 +0900 Subject: [PATCH 007/111] More polished light theme (#7620) * Revert "Remove pointer to light theme until it is more polished (#7594)" This reverts commit f8cf85db3b3a4541a0add4b985a9bf0fbe8f2aa0. * True up the hierarchical structure of mastodon-light theme * Fix mastodon-light theme --- app/javascript/styles/mastodon-light.scss | 228 +-------------------- app/javascript/styles/mastodon-light/diff.scss | 157 ++++++++++++++ .../styles/mastodon-light/variables.scss | 38 ++++ config/themes.yml | 1 + 4 files changed, 198 insertions(+), 226 deletions(-) create mode 100644 app/javascript/styles/mastodon-light/diff.scss create mode 100644 app/javascript/styles/mastodon-light/variables.scss diff --git a/app/javascript/styles/mastodon-light.scss b/app/javascript/styles/mastodon-light.scss index ce9452f..756a12d 100644 --- a/app/javascript/styles/mastodon-light.scss +++ b/app/javascript/styles/mastodon-light.scss @@ -1,227 +1,3 @@ -// Set variables -$ui-base-color: #d9e1e8; -$ui-base-lighter-color: darken($ui-base-color, 57%); -$ui-highlight-color: #2b90d9; -$ui-primary-color: darken($ui-highlight-color, 28%); -$ui-secondary-color: #282c37; - -$primary-text-color: black; -$base-overlay-background: $ui-base-color; - -$login-button-color: white; -$account-background-color: white; - -// Import defaults +@import 'mastodon-light/variables'; @import 'application'; - -// Change the color of the log in button -.button { - &.button-alternative-2 { - color: $login-button-color; - } -} - -// Change columns' default background colors -.column { - > .scrollable { - background: lighten($ui-base-color, 13%); - } -} - -.drawer__inner { - background: $ui-base-color; -} - -.drawer__inner__mastodon { - background: $ui-base-color url('data:image/svg+xml;utf8,') no-repeat bottom / 100% auto; -} - -// Change the default appearance of the content warning button -.status__content, -.reply-indicator__content { - - .status__content__spoiler-link { - - background: darken($ui-base-color, 30%); - - &:hover { - background: darken($ui-base-color, 35%); - text-decoration: none; - } - - } - -} - -// Change the default appearance of the action buttons -.icon-button { - - &:hover, - &:active, - &:focus { - color: darken($ui-base-color, 40%); - transition: color 200ms ease-out; - } - - &.disabled { - color: darken($ui-base-color, 30%); - } - -} - -.status { - &.status-direct { - .icon-button.disabled { - color: darken($ui-base-color, 30%); - } - } -} - -button.icon-button i.fa-retweet { - &:hover { - background-image: url("data:image/svg+xml;utf8,"); - } -} - -button.icon-button.disabled i.fa-retweet { - background-image: url("data:image/svg+xml;utf8,"); -} - -// Change the colors used in the dropdown menu -.dropdown-menu { - background: $ui-base-color; -} - -.dropdown-menu__arrow { - - &.left { - border-left-color: $ui-base-color; - } - - &.top { - border-top-color: $ui-base-color; - } - - &.bottom { - border-bottom-color: $ui-base-color; - } - - &.right { - border-right-color: $ui-base-color; - } - -} - -.dropdown-menu__item { - a { - background: $ui-base-color; - color: $ui-secondary-color; - } -} - -// Change the default color of several parts of the compose form -.compose-form { - - .compose-form__warning { - color: lighten($ui-secondary-color, 65%); - } - - strong { - color: lighten($ui-secondary-color, 65%); - } - - .autosuggest-textarea__textarea, - .spoiler-input__input { - - color: darken($ui-base-color, 80%); - - &::placeholder { - color: darken($ui-base-color, 70%); - } - - } - - .compose-form__buttons-wrapper { - background: darken($ui-base-color, 10%); - } - - .privacy-dropdown__option { - color: $ui-primary-color; - } - - .privacy-dropdown__option__content { - - strong { - color: $ui-primary-color; - } - - } - -} - -// Change the default color used for the text in an empty column or on the error column -.empty-column-indicator, -.error-column { - color: darken($ui-base-color, 60%); -} - -// Change the default colors used on some parts of the profile pages -.activity-stream-tabs { - - background: $account-background-color; - - a { - &.active { - color: $ui-primary-color; - } - } - -} - -.activity-stream { - - .entry { - background: $account-background-color; - } - - .status.light { - - .status__content { - color: $primary-text-color; - } - - .display-name { - strong { - color: $primary-text-color; - } - } - - } - -} - -.accounts-grid { - .account-grid-card { - - .controls { - .icon-button { - color: $ui-secondary-color; - } - } - - .name { - a { - color: $primary-text-color; - } - } - - .username { - color: $ui-secondary-color; - } - - .account__header__content { - color: $primary-text-color; - } - - } -} +@import 'mastodon-light/diff'; diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss new file mode 100644 index 0000000..42c790b --- /dev/null +++ b/app/javascript/styles/mastodon-light/diff.scss @@ -0,0 +1,157 @@ +// Notes! +// Sass color functions, "darken" and "lighten" are automatically replaced. + +// Change the colors of button texts +.button { + color: $white; + + &.button-alternative-2 { + color: $white; + } +} + +// Change default background colors of columns +.column { + > .scrollable { + background: $white; + } +} + +.drawer__inner { + background: $ui-base-color; +} + +.drawer__inner__mastodon { + background: $ui-base-color url('data:image/svg+xml;utf8,') no-repeat bottom / 100% auto; +} + +// Change the colors used in the dropdown menu +.dropdown-menu { + background: $ui-base-color; +} + +.dropdown-menu__arrow { + &.left { + border-left-color: $ui-base-color; + } + + &.top { + border-top-color: $ui-base-color; + } + + &.bottom { + border-bottom-color: $ui-base-color; + } + + &.right { + border-right-color: $ui-base-color; + } +} + +.dropdown-menu__item { + a { + background: $ui-base-color; + color: $ui-secondary-color; + } +} + +// Change the text colors on inverted background +.privacy-dropdown__option.active .privacy-dropdown__option__content, +.privacy-dropdown__option.active .privacy-dropdown__option__content strong, +.privacy-dropdown__option:hover .privacy-dropdown__option__content, +.privacy-dropdown__option:hover .privacy-dropdown__option__content strong, +.dropdown-menu__item a:active, +.dropdown-menu__item a:focus, +.dropdown-menu__item a:hover, +.actions-modal ul li:not(:empty) a.active, +.actions-modal ul li:not(:empty) a.active button, +.actions-modal ul li:not(:empty) a:active, +.actions-modal ul li:not(:empty) a:active button, +.actions-modal ul li:not(:empty) a:focus, +.actions-modal ul li:not(:empty) a:focus button, +.actions-modal ul li:not(:empty) a:hover, +.actions-modal ul li:not(:empty) a:hover button, +.admin-wrapper .sidebar ul ul a.selected, +.simple_form .block-button, +.simple_form .button, +.simple_form button { + color: $white; +} + +// Change the background colors of modals +.actions-modal, +.boost-modal, +.confirmation-modal, +.mute-modal, +.report-modal { + background: $ui-secondary-color; +} + +.boost-modal__action-bar, +.confirmation-modal__action-bar, +.mute-modal__action-bar { + background: darken($ui-secondary-color, 6%); +} + +.react-toggle-track { + background: $ui-base-color; +} + +// Change the default color used for the text in an empty column or on the error column +.empty-column-indicator, +.error-column { + color: $primary-text-color; +} + +// Change the default colors used on some parts of the profile pages +.activity-stream-tabs { + background: $account-background-color; + + a { + &.active { + color: $ui-primary-color; + } + } +} + +.activity-stream { + .entry { + background: $account-background-color; + } + + .status.light { + .status__content { + color: $primary-text-color; + } + + .display-name { + strong { + color: $primary-text-color; + } + } + } +} + +.accounts-grid { + .account-grid-card { + .controls { + .icon-button { + color: $ui-secondary-color; + } + } + + .name { + a { + color: $primary-text-color; + } + } + + .username { + color: $ui-secondary-color; + } + + .account__header__content { + color: $primary-text-color; + } + } +} diff --git a/app/javascript/styles/mastodon-light/variables.scss b/app/javascript/styles/mastodon-light/variables.scss new file mode 100644 index 0000000..4be454e --- /dev/null +++ b/app/javascript/styles/mastodon-light/variables.scss @@ -0,0 +1,38 @@ +// Dependent colors +$black: #000000; +$white: #ffffff; + +$classic-base-color: #282c37; +$classic-primary-color: #9baec8; +$classic-secondary-color: #d9e1e8; +$classic-highlight-color: #2b90d9; + +// Differences +$base-overlay-background: $white; + +$ui-base-color: $classic-secondary-color !default; +$ui-base-lighter-color: #b0c0cf; +$ui-primary-color: #9bcbed; +$ui-secondary-color: $classic-base-color !default; +$ui-highlight-color: #2b5fd9; + +$primary-text-color: $black !default; +$darker-text-color: $classic-base-color !default; +$dark-text-color: #444b5d; +$action-button-color: #606984; + +$inverted-text-color: $black !default; +$lighter-text-color: $classic-base-color !default; +$light-text-color: #444b5d; + +//Newly added colors +$account-background-color: $white; + +//Invert darkened and lightened colors +@function darken($color, $amount) { + @return hsl(hue($color), saturation($color), lightness($color) + $amount); +} + +@function lighten($color, $amount) { + @return hsl(hue($color), saturation($color), lightness($color) - $amount); +} diff --git a/config/themes.yml b/config/themes.yml index f0bb1e6..9c21c94 100644 --- a/config/themes.yml +++ b/config/themes.yml @@ -1,2 +1,3 @@ default: styles/application.scss contrast: styles/contrast.scss +mastodon-light: styles/mastodon-light.scss From 8182b615183b83fb6651fb23912abda3cc4ccf03 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sat, 26 May 2018 01:36:46 +0900 Subject: [PATCH 008/111] Enable media timeline (#7598) --- .../mastodon/features/community_timeline/index.js | 24 ++++++++++------------ .../mastodon/features/public_timeline/index.js | 24 ++++++++++------------ 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js index 58b8a8b..f9ee835 100644 --- a/app/javascript/mastodon/features/community_timeline/index.js +++ b/app/javascript/mastodon/features/community_timeline/index.js @@ -8,7 +8,7 @@ import ColumnHeader from '../../components/column_header'; import { expandCommunityTimeline } from '../../actions/timelines'; import { addColumn, removeColumn, moveColumn, changeColumnParams } from '../../actions/columns'; import ColumnSettingsContainer from './containers/column_settings_container'; -// import SectionHeadline from './components/section_headline'; +import SectionHeadline from './components/section_headline'; import { connectCommunityStream } from '../../actions/streaming'; const messages = defineMessages({ @@ -100,17 +100,15 @@ export default class CommunityTimeline extends React.PureComponent { const { intl, hasUnread, columnId, multiColumn, onlyMedia } = this.props; const pinned = !!columnId; - // pending - // - // const headline = ( - // - // ); + const headline = ( + + ); return ( @@ -128,7 +126,7 @@ export default class CommunityTimeline extends React.PureComponent { - // ); + const headline = ( + + ); return ( @@ -128,7 +126,7 @@ export default class PublicTimeline extends React.PureComponent { Date: Sat, 26 May 2018 01:46:28 +0900 Subject: [PATCH 009/111] Introduce flat layout to contexts reducer (#7150) This allows to filter out replies in threads even if contexts of those replies are not fetched. --- app/javascript/mastodon/actions/timelines.js | 20 ----- app/javascript/mastodon/features/status/index.js | 47 ++++++++++-- app/javascript/mastodon/reducers/contexts.js | 94 +++++++++++++++--------- 3 files changed, 101 insertions(+), 60 deletions(-) diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index 8f54dfd..11a199d 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -13,21 +13,9 @@ export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP'; export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT'; -export const TIMELINE_CONTEXT_UPDATE = 'CONTEXT_UPDATE'; - export function updateTimeline(timeline, status) { return (dispatch, getState) => { const references = status.reblog ? getState().get('statuses').filter((item, itemId) => (itemId === status.reblog.id || item.get('reblog') === status.reblog.id)).map((_, itemId) => itemId) : []; - const parents = []; - - if (status.in_reply_to_id) { - let parent = getState().getIn(['statuses', status.in_reply_to_id]); - - while (parent && parent.get('in_reply_to_id')) { - parents.push(parent.get('id')); - parent = getState().getIn(['statuses', parent.get('in_reply_to_id')]); - } - } dispatch(importFetchedStatus(status)); @@ -37,14 +25,6 @@ export function updateTimeline(timeline, status) { status, references, }); - - if (parents.length > 0) { - dispatch({ - type: TIMELINE_CONTEXT_UPDATE, - status, - references: parents, - }); - } }; }; diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index d5af2a4..2e53dfa 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -1,3 +1,4 @@ +import Immutable from 'immutable'; import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; @@ -54,11 +55,47 @@ const messages = defineMessages({ const makeMapStateToProps = () => { const getStatus = makeGetStatus(); - const mapStateToProps = (state, props) => ({ - status: getStatus(state, props.params.statusId), - ancestorsIds: state.getIn(['contexts', 'ancestors', props.params.statusId]), - descendantsIds: state.getIn(['contexts', 'descendants', props.params.statusId]), - }); + const mapStateToProps = (state, props) => { + const status = getStatus(state, props.params.statusId); + let ancestorsIds = Immutable.List(); + let descendantsIds = Immutable.List(); + + if (status) { + ancestorsIds = ancestorsIds.withMutations(mutable => { + function addAncestor(id) { + if (id) { + const inReplyTo = state.getIn(['contexts', 'inReplyTos', id]); + + mutable.unshift(id); + addAncestor(inReplyTo); + } + } + + addAncestor(status.get('in_reply_to_id')); + }); + + descendantsIds = descendantsIds.withMutations(mutable => { + function addDescendantOf(id) { + const replies = state.getIn(['contexts', 'replies', id]); + + if (replies) { + replies.forEach(reply => { + mutable.push(reply); + addDescendantOf(reply); + }); + } + } + + addDescendantOf(status.get('id')); + }); + } + + return { + status, + ancestorsIds, + descendantsIds, + }; + }; return mapStateToProps; }; diff --git a/app/javascript/mastodon/reducers/contexts.js b/app/javascript/mastodon/reducers/contexts.js index ebd01e5..53e70b5 100644 --- a/app/javascript/mastodon/reducers/contexts.js +++ b/app/javascript/mastodon/reducers/contexts.js @@ -3,38 +3,62 @@ import { ACCOUNT_MUTE_SUCCESS, } from '../actions/accounts'; import { CONTEXT_FETCH_SUCCESS } from '../actions/statuses'; -import { TIMELINE_DELETE, TIMELINE_CONTEXT_UPDATE } from '../actions/timelines'; +import { TIMELINE_DELETE, TIMELINE_UPDATE } from '../actions/timelines'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; const initialState = ImmutableMap({ - ancestors: ImmutableMap(), - descendants: ImmutableMap(), + inReplyTos: ImmutableMap(), + replies: ImmutableMap(), }); -const normalizeContext = (state, id, ancestors, descendants) => { - const ancestorsIds = ImmutableList(ancestors.map(ancestor => ancestor.id)); - const descendantsIds = ImmutableList(descendants.map(descendant => descendant.id)); +const normalizeContext = (immutableState, id, ancestors, descendants) => immutableState.withMutations(state => { + state.update('inReplyTos', immutableAncestors => immutableAncestors.withMutations(inReplyTos => { + state.update('replies', immutableDescendants => immutableDescendants.withMutations(replies => { + function addReply({ id, in_reply_to_id }) { + if (in_reply_to_id) { + const siblings = replies.get(in_reply_to_id, ImmutableList()); - return state.withMutations(map => { - map.setIn(['ancestors', id], ancestorsIds); - map.setIn(['descendants', id], descendantsIds); - }); -}; + if (!siblings.includes(id)) { + const index = siblings.findLastIndex(sibling => sibling.id < id); + replies.set(in_reply_to_id, siblings.insert(index + 1, id)); + } + + inReplyTos.set(id, in_reply_to_id); + } + } + + if (ancestors[0]) { + addReply({ id, in_reply_to_id: ancestors[0].id }); + } + + if (descendants[0]) { + addReply({ id: descendants[0].id, in_reply_to_id: id }); + } + + [ancestors, descendants].forEach(statuses => statuses.forEach(addReply)); + })); + })); +}); const deleteFromContexts = (immutableState, ids) => immutableState.withMutations(state => { - state.update('ancestors', immutableAncestors => immutableAncestors.withMutations(ancestors => { - state.update('descendants', immutableDescendants => immutableDescendants.withMutations(descendants => { + state.update('inReplyTos', immutableAncestors => immutableAncestors.withMutations(inReplyTos => { + state.update('replies', immutableDescendants => immutableDescendants.withMutations(replies => { ids.forEach(id => { - descendants.get(id, ImmutableList()).forEach(descendantId => { - ancestors.update(descendantId, ImmutableList(), list => list.filterNot(itemId => itemId === id)); - }); + const inReplyToIdOfId = inReplyTos.get(id); + const repliesOfId = replies.get(id); + const siblings = replies.get(inReplyToIdOfId); - ancestors.get(id, ImmutableList()).forEach(ancestorId => { - descendants.update(ancestorId, ImmutableList(), list => list.filterNot(itemId => itemId === id)); - }); + if (siblings) { + replies.set(inReplyToIdOfId, siblings.filterNot(sibling => sibling === id)); + } + + + if (repliesOfId) { + repliesOfId.forEach(reply => inReplyTos.delete(reply)); + } - descendants.delete(id); - ancestors.delete(id); + inReplyTos.delete(id); + replies.delete(id); }); })); })); @@ -48,23 +72,23 @@ const filterContexts = (state, relationship, statuses) => { return deleteFromContexts(state, ownedStatusIds); }; -const updateContext = (state, status, references) => { - return state.update('descendants', map => { - references.forEach(parentId => { - map = map.update(parentId, ImmutableList(), list => { - if (list.includes(status.id)) { - return list; - } +const updateContext = (state, status) => { + if (status.in_reply_to_id) { + return state.withMutations(mutable => { + const replies = mutable.getIn(['replies', status.in_reply_to_id], ImmutableList()); - return list.push(status.id); - }); + mutable.setIn(['inReplyTos', status.id], status.in_reply_to_id); + + if (!replies.includes(status.id)) { + mutable.setIn(['replies', status.id], replies.push(status.id)); + } }); + } - return map; - }); + return state; }; -export default function contexts(state = initialState, action) { +export default function replies(state = initialState, action) { switch(action.type) { case ACCOUNT_BLOCK_SUCCESS: case ACCOUNT_MUTE_SUCCESS: @@ -73,8 +97,8 @@ export default function contexts(state = initialState, action) { return normalizeContext(state, action.id, action.ancestors, action.descendants); case TIMELINE_DELETE: return deleteFromContexts(state, [action.id]); - case TIMELINE_CONTEXT_UPDATE: - return updateContext(state, action.status, action.references); + case TIMELINE_UPDATE: + return updateContext(state, action.status); default: return state; } From c7ac0396975ba8f8c46edbd504d0ce1c582d1a4b Mon Sep 17 00:00:00 2001 From: Isatis <515462+Reverite@users.noreply.github.com> Date: Fri, 25 May 2018 09:50:31 -0700 Subject: [PATCH 010/111] Remove Puma pidfile before boot if container receives SIGTERM (#7052) --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8058326..496fb25 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -35,7 +35,7 @@ services: image: tootsuite/mastodon restart: always env_file: .env.production - command: bundle exec rails s -p 3000 -b '0.0.0.0' + command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000 -b '0.0.0.0'" networks: - external_network - internal_network From d87649db074b6b9700246d9488f82071d41fcb60 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 26 May 2018 01:08:31 +0200 Subject: [PATCH 011/111] Disable AMS logging (#7623) Especially in production it's just noise and doesn't mix well with the log format --- config/initializers/active_model_serializers.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/initializers/active_model_serializers.rb b/config/initializers/active_model_serializers.rb index b023026..0e69e1d 100644 --- a/config/initializers/active_model_serializers.rb +++ b/config/initializers/active_model_serializers.rb @@ -1,3 +1,5 @@ ActiveModelSerializers.config.tap do |config| config.default_includes = '**' end + +ActiveSupport::Notifications.unsubscribe(ActiveModelSerializers::Logging::RENDER_EVENT) From ebf2fef029bb44dc3872c4fa578cbbda6ec506b2 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 26 May 2018 01:09:30 +0200 Subject: [PATCH 012/111] Catch ActionController::UnknownFormat and return HTTP 406 (#7621) An error like that should not appear in production error log. --- app/controllers/application_controller.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 5b22f17..29ba6ca 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -20,6 +20,7 @@ class ApplicationController < ActionController::Base rescue_from ActionController::RoutingError, with: :not_found rescue_from ActiveRecord::RecordNotFound, with: :not_found rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity + rescue_from ActionController::UnknownFormat, with: :not_acceptable rescue_from Mastodon::NotPermittedError, with: :forbidden before_action :store_current_location, except: :raise_not_found, unless: :devise_controller? @@ -73,6 +74,10 @@ class ApplicationController < ActionController::Base respond_with_error(422) end + def not_acceptable + respond_with_error(406) + end + def single_user_mode? @single_user_mode ||= Rails.configuration.x.single_user_mode && Account.exists? end From 62cb3b199fb195baad6e91f24cf058b39face024 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sat, 26 May 2018 11:45:58 +0900 Subject: [PATCH 013/111] Weblate translations (2018-05-26) (#7624) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Esperanto) Currently translated at 100.0% (76 of 76 strings) Translation: Mastodon/Doorkeeper Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/eo/ * Translated using Weblate (Occitan) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/oc/ * Translated using Weblate (Galician) Currently translated at 100.0% (621 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/gl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (621 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/nl/ * Translated using Weblate (Occitan) Currently translated at 98.0% (609 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/oc/ * Translated using Weblate (Catalan) Currently translated at 100.0% (621 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ca/ * Translated using Weblate (Occitan) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/oc/ * Translated using Weblate (Italian) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/it/ * Translated using Weblate (Esperanto) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/ * Translated using Weblate (Esperanto) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/ * Translated using Weblate (Esperanto) Currently translated at 100.0% (66 of 66 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/eo/ * Translated using Weblate (Italian) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/it/ * Translated using Weblate (Italian) Currently translated at 98.4% (65 of 66 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/it/ * Translated using Weblate (Italian) Currently translated at 84.0% (522 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/it/ * Translated using Weblate (Esperanto) Currently translated at 99.8% (620 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/eo/ * Translated using Weblate (Japanese) Currently translated at 99.6% (619 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/ * Translated using Weblate (Japanese) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ja/ * Translated using Weblate (Catalan) Currently translated at 100.0% (621 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ca/ * Translated using Weblate (Occitan) Currently translated at 98.0% (609 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/oc/ * Translated using Weblate (French) Currently translated at 99.6% (619 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fr/ * Translated using Weblate (Italian) Currently translated at 84.0% (522 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/it/ * Translated using Weblate (Catalan) Currently translated at 100.0% (621 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ca/ * Translated using Weblate (Persian) Currently translated at 99.6% (298 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fa/ * Translated using Weblate (Persian) Currently translated at 99.5% (618 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fa/ * Translated using Weblate (Persian) Currently translated at 100.0% (66 of 66 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/fa/ * Translated using Weblate (Persian) Currently translated at 99.6% (619 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fa/ * Translated using Weblate (Persian) Currently translated at 100.0% (66 of 66 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/fa/ * Translated using Weblate (Persian) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fa/ * Translated using Weblate (Persian) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fa/ * Translated using Weblate (Occitan) Currently translated at 98.0% (609 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/oc/ * Translated using Weblate (Dutch) Currently translated at 99.8% (620 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (621 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/nl/ * Translated using Weblate (Persian) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fa/ * Translated using Weblate (Slovak) Currently translated at 95.0% (590 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Slovak) Currently translated at 98.4% (65 of 66 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/sk/ * Translated using Weblate (Slovak) Currently translated at 100.0% (66 of 66 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/sk/ * Translated using Weblate (Galician) Currently translated at 100.0% (66 of 66 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/gl/ * Translated using Weblate (Galician) Currently translated at 100.0% (621 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/gl/ * Translated using Weblate (French) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fr/ * Translated using Weblate (French) Currently translated at 99.6% (619 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fr/ * Translated using Weblate (Japanese) Currently translated at 99.3% (617 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/ * Translated using Weblate (Japanese) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ja/ * Translated using Weblate (Japanese) Currently translated at 93.9% (62 of 66 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/ja/ * Translated using Weblate (Greek) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/el/ * Translated using Weblate (Greek) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/el/ * Translated using Weblate (Greek) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/el/ Ορθογραφικό λάθος * Translated using Weblate (Greek) Currently translated at 44.4% (276 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/el/ * Translated using Weblate (Greek) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/el/ * Translated using Weblate (Greek) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/el/ * Translated using Weblate (Chinese (Hong Kong)) Currently translated at 100.0% (66 of 66 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/zh_Hant_HK/ * Translated using Weblate (Chinese (Hong Kong)) Currently translated at 100.0% (66 of 66 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/zh_Hant_HK/ * Translated using Weblate (Chinese (Hong Kong)) Currently translated at 99.1% (616 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/zh_Hant_HK/ * Translated using Weblate (Swedish) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sv/ * Translated using Weblate (Swedish) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sv/ * Translated using Weblate (Swedish) Currently translated at 99.3% (617 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sv/ * Translated using Weblate (Arabic) Currently translated at 90.8% (564 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/ * Translated using Weblate (Slovak) Currently translated at 100.0% (66 of 66 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/sk/ * Translated using Weblate (Slovak) Currently translated at 95.0% (590 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (German) Currently translated at 99.0% (615 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/de/ * Translated using Weblate (Dutch) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/nl/ * Translated using Weblate (Swedish) Currently translated at 100.0% (66 of 66 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/sv/ * Translated using Weblate (Swedish) Currently translated at 100.0% (66 of 66 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/sv/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 99.6% (619 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/pt_BR/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/pt_BR/ * Translated using Weblate (Arabic) Currently translated at 93.9% (62 of 66 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/ar/ * Translated using Weblate (Arabic) Currently translated at 98.6% (75 of 76 strings) Translation: Mastodon/Doorkeeper Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/ar/ * Translated using Weblate (Arabic) Currently translated at 90.8% (564 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/ * Translated using Weblate (Greek) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/el/ * Translated using Weblate (Greek) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/el/ * Translated using Weblate (Greek) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/el/ * Translated using Weblate (Greek) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/el/ * Translated using Weblate (Greek) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/el/ * Translated using Weblate (Greek) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/el/ * Translated using Weblate (Greek) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/el/ * Translated using Weblate (Greek) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/el/ * Translated using Weblate (Greek) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/el/ * Translated using Weblate (Greek) Currently translated at 100.0% (299 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/el/ * Translated using Weblate (Greek) Currently translated at 44.6% (277 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/el/ * Translated using Weblate (Slovak) Currently translated at 95.1% (591 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Dutch) Currently translated at 100.0% (621 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/nl/ * Translated using Weblate (Slovak) Currently translated at 100.0% (66 of 66 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/sk/ * Translated using Weblate (Slovak) Currently translated at 95.1% (591 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Japanese) Currently translated at 99.5% (618 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/ * Translated using Weblate (Basque) Currently translated at 89.6% (268 of 299 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eu/ * Translated using Weblate (Basque) Currently translated at 40.9% (27 of 66 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/eu/ * Translated using Weblate (Basque) Currently translated at 1.2% (8 of 621 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/eu/ * i18n-tasks normalize && yarn manage:translations --- .../mastodon/locales/defaultMessages.json | 21 +- app/javascript/mastodon/locales/el.json | 114 +++--- app/javascript/mastodon/locales/eo.json | 10 +- app/javascript/mastodon/locales/eu.json | 430 ++++++++++----------- app/javascript/mastodon/locales/fa.json | 88 ++--- app/javascript/mastodon/locales/fr.json | 8 +- app/javascript/mastodon/locales/it.json | 24 +- app/javascript/mastodon/locales/ja.json | 10 +- app/javascript/mastodon/locales/nl.json | 4 +- app/javascript/mastodon/locales/oc.json | 6 +- app/javascript/mastodon/locales/pt-BR.json | 6 +- app/javascript/mastodon/locales/sv.json | 6 +- config/locales/ar.yml | 14 +- config/locales/ca.yml | 7 +- config/locales/de.yml | 1 + config/locales/doorkeeper.ar.yml | 1 + config/locales/doorkeeper.eo.yml | 1 + config/locales/el.yml | 101 ++++- config/locales/eo.yml | 3 + config/locales/eu.yml | 12 +- config/locales/fa.yml | 22 +- config/locales/fr.yml | 8 +- config/locales/gl.yml | 5 +- config/locales/it.yml | 21 +- config/locales/ja.yml | 24 +- config/locales/nl.yml | 13 +- config/locales/oc.yml | 15 +- config/locales/pt-BR.yml | 7 +- config/locales/simple_form.ar.yml | 2 + config/locales/simple_form.eo.yml | 4 +- config/locales/simple_form.eu.yml | 11 +- config/locales/simple_form.fa.yml | 8 +- config/locales/simple_form.gl.yml | 2 +- config/locales/simple_form.it.yml | 2 + config/locales/simple_form.ja.yml | 2 +- config/locales/simple_form.sk.yml | 17 +- config/locales/simple_form.sv.yml | 5 +- config/locales/simple_form.zh-HK.yml | 4 +- config/locales/sk.yml | 12 +- config/locales/sv.yml | 4 +- config/locales/zh-HK.yml | 8 +- 41 files changed, 604 insertions(+), 459 deletions(-) diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 1431346..56941e5 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -593,16 +593,21 @@ { "descriptors": [ { - "defaultMessage": "Local timeline", - "id": "column.community" - }, - { "defaultMessage": "Toots", "id": "timeline.posts" }, { "defaultMessage": "Media", "id": "timeline.media" + } + ], + "path": "app/javascript/mastodon/features/community_timeline/components/section_headline.json" + }, + { + "descriptors": [ + { + "defaultMessage": "Local timeline", + "id": "column.community" }, { "defaultMessage": "The local timeline is empty. Write something publicly to get the ball rolling!", @@ -1402,14 +1407,6 @@ "id": "column.public" }, { - "defaultMessage": "Toots", - "id": "timeline.posts" - }, - { - "defaultMessage": "Media", - "id": "timeline.media" - }, - { "defaultMessage": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", "id": "empty_column.public" } diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 1e7707b..bb48fe9 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -11,16 +11,16 @@ "account.followers": "Ακόλουθοι", "account.follows": "Ακολουθεί", "account.follows_you": "Σε ακολουθεί", - "account.hide_reblogs": "Απόκρυψη προωθήσεων από τον/την @{name}", + "account.hide_reblogs": "Απόκρυψη προωθήσεων από @{name}", "account.media": "Πολυμέσα", - "account.mention": "Ανέφερε τον/την @{name}", - "account.moved_to": "{name} μετακόμισε στο:", + "account.mention": "Ανάφερε @{name}", + "account.moved_to": "{name} μεταφέρθηκε στο:", "account.mute": "Σώπασε τον/την @{name}", "account.mute_notifications": "Σώπασε τις ειδοποιήσεις από τον/την @{name}", "account.muted": "Αποσιωπημένος/η", "account.posts": "Τουτ", "account.posts_with_replies": "Τουτ και απαντήσεις", - "account.report": "Ανέφερε τον/την @{name}", + "account.report": "Κατάγγειλε τον/την @{name}", "account.requested": "Εκκρεμεί έγκριση. Κάνε κλικ για να ακυρώσεις το αίτημα ακολούθησης", "account.share": "Μοιράσου το προφίλ του/της @{name}", "account.show_reblogs": "Δείξε τις προωθήσεις του/της @{name}", @@ -44,7 +44,7 @@ "column.direct": "Απευθείας μηνύματα", "column.domain_blocks": "Κρυμμένοι τομείς", "column.favourites": "Αγαπημένα", - "column.follow_requests": "Αιτήματα παρακολούθησης", + "column.follow_requests": "Αιτήματα ακολούθησης", "column.home": "Αρχική", "column.lists": "Λίστες", "column.mutes": "Αποσιωπημένοι χρήστες", @@ -60,9 +60,9 @@ "column_header.unpin": "Ξεκαρφίτσωμα", "column_subheading.navigation": "Πλοήγηση", "column_subheading.settings": "Ρυθμίσεις", - "compose_form.direct_message_warning": "Αυτό το τουτ θα εμφανίζεται μόνο σε όλους τους αναφερόμενους χρήστες.", - "compose_form.direct_message_warning_learn_more": "Learn more", - "compose_form.hashtag_warning": "Αυτό το τουτ δεν θα εμφανίζεται κάτω από καμία ταμπέλα καθώς είναι αφανές. Μόνο τα δημόσια τουτ μπορούν να αναζητηθούν ανά ταμπέλα.", + "compose_form.direct_message_warning": "Αυτό το τουτ θα σταλεί μόνο στους αναφερόμενους χρήστες.", + "compose_form.direct_message_warning_learn_more": "Μάθετε περισσότερα", + "compose_form.hashtag_warning": "Αυτό το τουτ δεν θα εμφανίζεται κάτω από κανένα hashtag καθώς είναι αφανές. Μόνο τα δημόσια τουτ μπορούν να αναζητηθούν ανά hashtag.", "compose_form.lock_disclaimer": "Ο λογαριασμός σου δεν είναι {locked}. Οποιοσδήποτε μπορεί να σε ακολουθήσει για να δει τις δημοσιεύσεις σας προς τους ακολούθους σας.", "compose_form.lock_disclaimer.lock": "κλειδωμένος", "compose_form.placeholder": "Τι σκέφτεσαι;", @@ -107,16 +107,16 @@ "empty_column.hashtag": "Δεν υπάρχει ακόμα κάτι για αυτή την ταμπέλα.", "empty_column.home": "Η τοπική σου ροή είναι κενή! Πήγαινε στο {public} ή κάνε αναζήτηση για να ξεκινήσεις και να γνωρίσεις άλλους χρήστες.", "empty_column.home.public_timeline": "η δημόσια ροή", - "empty_column.list": "Δεν υπάρχει τίποτα σε αυτή τη λίστα ακόμα. Όταν τα μέλη της δημοσιεύσουν νέες καταστάσεις, θα εμφανιστούν εδώ", + "empty_column.list": "Δεν υπάρχει τίποτα σε αυτή τη λίστα ακόμα. Όταν τα μέλη της δημοσιεύσουν νέες καταστάσεις, θα εμφανιστούν εδώ.", "empty_column.notifications": "Δεν έχεις ειδοποιήσεις ακόμα. Αλληλεπίδρασε με άλλους χρήστες για να ξεκινήσεις την κουβέντα.", - "empty_column.public": "Δεν υπάρχει τίποτα εδώ! Γράψε κάτι δημόσιο, ή ακολούθησε χειροκίνητα χρήστες από άλλα instances για να το γεμίσεις", + "empty_column.public": "Δεν υπάρχει τίποτα εδώ! Γράψε κάτι δημόσιο, ή ακολούθησε χειροκίνητα χρήστες από άλλα instances για να τη γεμίσεις", "follow_request.authorize": "Ενέκρινε", "follow_request.reject": "Απέρριψε", "getting_started.appsshort": "Εφαρμογές", "getting_started.faq": "Συχνές Ερωτήσεις", "getting_started.heading": "Ξεκινώντας", "getting_started.open_source_notice": "Το Mastodon είναι ελεύθερο λογισμικό. Μπορείς να συνεισφέρεις ή να αναφέρεις ζητήματα στο GitHub στο {github}.", - "getting_started.userguide": "Οδηγός Χρηστών", + "getting_started.userguide": "Οδηγός Χρήσης", "home.column_settings.advanced": "Προχωρημένα", "home.column_settings.basic": "Βασικά", "home.column_settings.filter_regex": "Φιλτράρετε μέσω regular expressions", @@ -139,58 +139,58 @@ "keyboard_shortcuts.search": "για εστίαση αναζήτησης", "keyboard_shortcuts.toggle_hidden": "για εμφάνιση/απόκρυψη κειμένου πίσω από την προειδοποίηση", "keyboard_shortcuts.toot": "για δημιουργία ολοκαίνουριου τουτ", - "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", - "keyboard_shortcuts.up": "to move up in the list", + "keyboard_shortcuts.unfocus": "για την απο-εστίαση του πεδίου σύνθεσης/αναζήτησης", + "keyboard_shortcuts.up": "για να ανέβεις στη λίστα", "lightbox.close": "Close", - "lightbox.next": "Next", - "lightbox.previous": "Previous", - "lists.account.add": "Add to list", - "lists.account.remove": "Remove from list", + "lightbox.next": "Επόμενο", + "lightbox.previous": "Προηγούμενο", + "lists.account.add": "Πρόσθεσε στη λίστα", + "lists.account.remove": "Αφαίρεσε από τη λίστα", "lists.delete": "Delete list", - "lists.edit": "Edit list", - "lists.new.create": "Add list", - "lists.new.title_placeholder": "New list title", - "lists.search": "Search among people you follow", - "lists.subheading": "Your lists", - "loading_indicator.label": "Loading...", - "media_gallery.toggle_visible": "Toggle visibility", - "missing_indicator.label": "Not found", - "missing_indicator.sublabel": "This resource could not be found", - "mute_modal.hide_notifications": "Hide notifications from this user?", - "navigation_bar.blocks": "Blocked users", + "lists.edit": "Τροποποίησε τη λίστα", + "lists.new.create": "Πρόσθεσε λίστα", + "lists.new.title_placeholder": "Τίτλος νέας λίστα", + "lists.search": "Αναζήτησε ανάμεσα σε όσους/όσες ακολουθείς", + "lists.subheading": "Οι λίστες σου", + "loading_indicator.label": "Φορτώνει...", + "media_gallery.toggle_visible": "Αντιστροφή ορατότητας", + "missing_indicator.label": "Δε βρέθηκε", + "missing_indicator.sublabel": "Αυτό το υλικό δε βρέθηκε", + "mute_modal.hide_notifications": "Απόκρυψη ειδοποιήσεων αυτού του χρήστη;", + "navigation_bar.blocks": "Αποκλεισμένοι χρήστες", "navigation_bar.community_timeline": "Local timeline", - "navigation_bar.direct": "Direct messages", + "navigation_bar.direct": "Απευθείας μηνύματα", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Edit profile", "navigation_bar.favourites": "Favourites", "navigation_bar.follow_requests": "Follow requests", "navigation_bar.info": "Extended information", - "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts", + "navigation_bar.keyboard_shortcuts": "Συντομεύσεις πληκτρολογίου", "navigation_bar.lists": "Lists", - "navigation_bar.logout": "Logout", + "navigation_bar.logout": "Αποσύνδεση", "navigation_bar.mutes": "Muted users", "navigation_bar.pins": "Pinned toots", - "navigation_bar.preferences": "Preferences", - "navigation_bar.public_timeline": "Federated timeline", - "notification.favourite": "{name} favourited your status", - "notification.follow": "{name} followed you", - "notification.mention": "{name} mentioned you", - "notification.reblog": "{name} boosted your status", + "navigation_bar.preferences": "Προτιμήσεις", + "navigation_bar.public_timeline": "Ομοσπονδιακή ροή", + "notification.favourite": "Ο/Η {name} σημείωσε ως αγαπημένη την κατάστασή σου", + "notification.follow": "Ο/Η {name} σε ακολούθησε", + "notification.mention": "Ο/Η {name} σε ανέφερε", + "notification.reblog": "Ο/Η {name} προώθησε την κατάστασή σου", "notifications.clear": "Καθαρισμός ειδοποιήσεων", - "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?", + "notifications.clear_confirmation": "Σίγουρα θέλεις να καθαρίσεις όλες τις ειδοποιήσεις σου;", "notifications.column_settings.alert": "Desktop notifications", - "notifications.column_settings.favourite": "Favourites:", - "notifications.column_settings.follow": "New followers:", - "notifications.column_settings.mention": "Mentions:", + "notifications.column_settings.favourite": "Αγαπημένα:", + "notifications.column_settings.follow": "Νέοι ακόλουθοι:", + "notifications.column_settings.mention": "Αναφορές:", "notifications.column_settings.push": "Push notifications", - "notifications.column_settings.push_meta": "This device", - "notifications.column_settings.reblog": "Boosts:", - "notifications.column_settings.show": "Show in column", - "notifications.column_settings.sound": "Play sound", + "notifications.column_settings.push_meta": "Αυτή η συσκευή", + "notifications.column_settings.reblog": "Προωθήσεις:", + "notifications.column_settings.show": "Εμφάνισε σε στήλη", + "notifications.column_settings.sound": "Ηχητική ειδοποίηση", "notifications.group": "{count} notifications", - "onboarding.done": "Done", - "onboarding.next": "Next", - "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.", + "onboarding.done": "Έγινε", + "onboarding.next": "Επόμενο", + "onboarding.page_five.public_timelines": "Η τοπική ροή δείχνει τις δημόσιες δημοσιεύσεις από όσους εδρεύουν στον κόμβο {domain}. Η ομοσπονδιακή ροή δείχνει τις δημόσιες δημοσιεύσεις εκείνων που οι χρήστες του {domain} ακολουθούν. Αυτές οι είναι Δημόσιες Ροές, ένας ωραίος τρόπος να ανακαλύψεις καινούριους ανθρώπους.", "onboarding.page_four.home": "The home timeline shows posts from people you follow.", "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.", "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.", @@ -201,7 +201,7 @@ "onboarding.page_six.almost_done": "Almost done...", "onboarding.page_six.appetoot": "Bon Appetoot!", "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.", - "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.", + "onboarding.page_six.github": "Το Mastodon είναι ελεύθερο λογισμικό. Μπορείς να αναφέρεις σφάλματα, να αιτηθείς νέες λειτουργίες ή να συνεισφέρεις κώδικα στο {github}.", "onboarding.page_six.guidelines": "community guidelines", "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!", "onboarding.page_six.various_app": "mobile apps", @@ -227,13 +227,13 @@ "relative_time.seconds": "{number}s", "reply_indicator.cancel": "Cancel", "report.forward": "Forward to {target}", - "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", - "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", + "report.forward_hint": "Ο λογαριασμός είναι από διαφορετικό διακομιστή. Να σταλεί ανώνυμο αντίγραφο της καταγγελίας κι εκεί;", + "report.hint": "Η καταγγελία θα σταλεί στους διαχειριστές του κόμβου σου. Μπορείς να περιγράψεις γιατί καταγγέλεις το λογαριασμό παρακάτω:", "report.placeholder": "Additional comments", "report.submit": "Submit", - "report.target": "Report {target}", + "report.target": "Καταγγελία {target}", "search.placeholder": "Search", - "search_popout.search_format": "Advanced search format", + "search_popout.search_format": "Προχωρημένη αναζήτηση", "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.", "search_popout.tips.hashtag": "hashtag", "search_popout.tips.status": "status", @@ -247,7 +247,7 @@ "status.block": "Block @{name}", "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "This post cannot be boosted", - "status.delete": "Delete", + "status.delete": "Διαγραφή", "status.direct": "Direct message @{name}", "status.embed": "Embed", "status.favourite": "Favourite", @@ -265,7 +265,7 @@ "status.reblogged_by": "{name} boosted", "status.reply": "Reply", "status.replyAll": "Reply to thread", - "status.report": "Report @{name}", + "status.report": "Καταγγελία @{name}", "status.sensitive_toggle": "Click to view", "status.sensitive_warning": "Sensitive content", "status.share": "Share", @@ -275,7 +275,7 @@ "status.show_more_all": "Show more for all", "status.unmute_conversation": "Unmute conversation", "status.unpin": "Unpin from profile", - "tabs_bar.federated_timeline": "Federated", + "tabs_bar.federated_timeline": "Ομοσπονδιακή", "tabs_bar.home": "Home", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notifications", @@ -287,7 +287,7 @@ "upload_button.label": "Add media", "upload_form.description": "Describe for the visually impaired", "upload_form.focus": "Crop", - "upload_form.undo": "Undo", + "upload_form.undo": "Διαγραφή", "upload_progress.label": "Uploading...", "video.close": "Close video", "video.exit_fullscreen": "Exit full screen", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 04d9dc8..bb27099 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -60,8 +60,8 @@ "column_header.unpin": "Depingli", "column_subheading.navigation": "Navigado", "column_subheading.settings": "Agordado", - "compose_form.direct_message_warning": "Tiu mesaĝo videblos nur por ĉiuj menciitaj uzantoj.", - "compose_form.direct_message_warning_learn_more": "Learn more", + "compose_form.direct_message_warning": "Tiu mesaĝo estos sendita nur al menciitaj uzantoj.", + "compose_form.direct_message_warning_learn_more": "Lerni pli", "compose_form.hashtag_warning": "Ĉi tiu mesaĝo ne estos listigita per ajna kradvorto. Nur publikaj mesaĝoj estas serĉeblaj per kradvortoj.", "compose_form.lock_disclaimer": "Via konta ne estas {locked}. Iu ajn povas sekvi vin por vidi viajn mesaĝojn, kiuj estas nur por sekvantoj.", "compose_form.lock_disclaimer.lock": "ŝlosita", @@ -187,7 +187,7 @@ "notifications.column_settings.reblog": "Diskonigoj:", "notifications.column_settings.show": "Montri en kolumno", "notifications.column_settings.sound": "Eligi sonon", - "notifications.group": "{count} notifications", + "notifications.group": "{count} sciigoj", "onboarding.done": "Farita", "onboarding.next": "Sekva", "onboarding.page_five.public_timelines": "La loka tempolinio montras publikajn mesaĝojn de ĉiuj en {domain}. La fratara tempolinio montras publikajn mesaĝojn de ĉiuj, kiuj estas sekvataj de homoj en {domain}. Tio estas la publikaj tempolinioj, kio estas bona maniero por malkovri novajn homojn.", @@ -280,8 +280,8 @@ "tabs_bar.local_timeline": "Loka tempolinio", "tabs_bar.notifications": "Sciigoj", "tabs_bar.search": "Serĉi", - "timeline.media": "Media", - "timeline.posts": "Toots", + "timeline.media": "Aŭdovidaĵoj", + "timeline.posts": "Mesaĝoj", "ui.beforeunload": "Via malneto perdiĝos se vi eliras de Mastodon.", "upload_area.title": "Altreni kaj lasi por alŝuti", "upload_button.label": "Aldoni aŭdovidaĵon", diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json index a6b1f8e..636c51f 100644 --- a/app/javascript/mastodon/locales/eu.json +++ b/app/javascript/mastodon/locales/eu.json @@ -1,9 +1,9 @@ { "account.badges.bot": "Bot", "account.block": "Blokeatu @{name}", - "account.block_domain": "{domain}(e)ko guztia ezkutatu", + "account.block_domain": "Ezkutatu {domain} domeinuko guztia", "account.blocked": "Blokeatuta", - "account.direct": "@{name}(e)ri mezu zuzena bidali", + "account.direct": "Mezu zuzena @{name} erabiltzaileari", "account.disclaimer_full": "Baliteke beheko informazioak erabiltzailearen profilaren zati bat baino ez erakustea.", "account.domain_blocked": "Ezkutatutako domeinua", "account.edit_profile": "Profila aldatu", @@ -11,291 +11,291 @@ "account.followers": "Jarraitzaileak", "account.follows": "Jarraitzen", "account.follows_you": "Jarraitzen dizu", - "account.hide_reblogs": "@{name}(e)k sustatutakoak ezkutatu", + "account.hide_reblogs": "Ezkutatu @{name} erabiltzailearen bultzadak", "account.media": "Media", "account.mention": "@{name} aipatu", "account.moved_to": "{name} hona lekualdatu da:", "account.mute": "@{name} isilarazi", - "account.mute_notifications": "@{name}(e)ren jakinarazpenak isilarazi", + "account.mute_notifications": "Mututu @{name} erabiltzailearen jakinarazpenak", "account.muted": "Isilarazita", - "account.posts": "Toots", - "account.posts_with_replies": "Toots and replies", + "account.posts": "Toot-ak", + "account.posts_with_replies": "Toot eta erantzunak", "account.report": "@{name} salatu", "account.requested": "Onarpenaren zain. Klikatu jarraitzeko eskaera ezeztatzeko", "account.share": "@{name}(e)ren profila elkarbanatu", - "account.show_reblogs": "@{name}(e)k sustatutakoak erakutsi", - "account.unblock": "@{name} desblokeatu", + "account.show_reblogs": "Erakutsi @{name} erabiltzailearen bultzadak", + "account.unblock": "Desblokeatu @{name}", "account.unblock_domain": "Berriz erakutsi {domain}", "account.unfollow": "Jarraitzeari utzi", - "account.unmute": "Unmute @{name}", - "account.unmute_notifications": "Unmute notifications from @{name}", - "account.view_full_profile": "View full profile", - "alert.unexpected.message": "An unexpected error occurred.", - "alert.unexpected.title": "Oops!", - "boost_modal.combo": "You can press {combo} to skip this next time", - "bundle_column_error.body": "Something went wrong while loading this component.", - "bundle_column_error.retry": "Try again", + "account.unmute": "Desmututu @{name}", + "account.unmute_notifications": "Desmututu @{name} erabiltzailearen jakinarazpenak", + "account.view_full_profile": "Ikusi profil osoa", + "alert.unexpected.message": "Ustekabeko errore bat gertatu da.", + "alert.unexpected.title": "Ene!", + "boost_modal.combo": "{combo} sakatu dezakezu hurrengoan hau saltatzeko", + "bundle_column_error.body": "Zerbait okerra gertatu da osagai hau kargatzean.", + "bundle_column_error.retry": "Saiatu berriro", "bundle_column_error.title": "Network error", - "bundle_modal_error.close": "Close", - "bundle_modal_error.message": "Something went wrong while loading this component.", - "bundle_modal_error.retry": "Try again", - "column.blocks": "Blocked users", - "column.community": "Local timeline", - "column.direct": "Direct messages", - "column.domain_blocks": "Hidden domains", - "column.favourites": "Favourites", - "column.follow_requests": "Follow requests", + "bundle_modal_error.close": "Itxi", + "bundle_modal_error.message": "Zerbait okerra gertatu da osagai hau kargatzean.", + "bundle_modal_error.retry": "Saiatu berriro", + "column.blocks": "Blokeatutako erabiltzaileak", + "column.community": "Denbora-lerro lokala", + "column.direct": "Mezu zuzenak", + "column.domain_blocks": "Domeinu ezkutuak", + "column.favourites": "Gogokoak", + "column.follow_requests": "Jarraitzeko eskariak", "column.home": "Home", - "column.lists": "Lists", - "column.mutes": "Muted users", - "column.notifications": "Notifications", + "column.lists": "Zerrendak", + "column.mutes": "Mutututako erabiltzaileak", + "column.notifications": "Jakinarazpenak", "column.pins": "Pinned toot", - "column.public": "Federated timeline", - "column_back_button.label": "Back", - "column_header.hide_settings": "Hide settings", - "column_header.moveLeft_settings": "Move column to the left", - "column_header.moveRight_settings": "Move column to the right", - "column_header.pin": "Pin", - "column_header.show_settings": "Show settings", - "column_header.unpin": "Unpin", + "column.public": "Federatutako denbora-lerroa", + "column_back_button.label": "Atzera", + "column_header.hide_settings": "Ezkutatu ezarpenak", + "column_header.moveLeft_settings": "Eraman zutabea ezkerrera", + "column_header.moveRight_settings": "Eraman zutabea eskuinera", + "column_header.pin": "Finkatu", + "column_header.show_settings": "Erakutsi ezarpenak", + "column_header.unpin": "Desfinkatu", "column_subheading.navigation": "Navigation", - "column_subheading.settings": "Settings", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", - "compose_form.direct_message_warning_learn_more": "Learn more", - "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.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.", - "compose_form.lock_disclaimer.lock": "locked", - "compose_form.placeholder": "What is on your mind?", + "column_subheading.settings": "Ezarpenak", + "compose_form.direct_message_warning": "Toot hau aipatutako erabiltzaileei besterik ez zaie bidaliko.", + "compose_form.direct_message_warning_learn_more": "Ikasi gehiago", + "compose_form.hashtag_warning": "Toot hau ez da traoletan agertuko zerrendatu gabekoa baita. Traoletan toot publikoak besterik ez dira agertzen.", + "compose_form.lock_disclaimer": "Zure kontua ez dago {locked}. Edonork jarraitu zaitzake zure jarraitzaileentzako soilik diren mezuak ikusteko.", + "compose_form.lock_disclaimer.lock": "blokeatuta", + "compose_form.placeholder": "Zer duzu buruan?", "compose_form.publish": "Toot", "compose_form.publish_loud": "{publish}!", - "compose_form.sensitive.marked": "Media is marked as sensitive", - "compose_form.sensitive.unmarked": "Media is not marked as sensitive", - "compose_form.spoiler.marked": "Text is hidden behind warning", - "compose_form.spoiler.unmarked": "Text is not hidden", - "compose_form.spoiler_placeholder": "Write your warning here", - "confirmation_modal.cancel": "Cancel", + "compose_form.sensitive.marked": "Multimedia mingarri gisa markatu da", + "compose_form.sensitive.unmarked": "Multimedia ez da mingarri gisa markatu", + "compose_form.spoiler.marked": "Testua abisu batek ezkutatzen du", + "compose_form.spoiler.unmarked": "Testua ez dago ezkutatuta", + "compose_form.spoiler_placeholder": "Idatzi zure abisua hemen", + "confirmation_modal.cancel": "Utzi", "confirmations.block.confirm": "Block", - "confirmations.block.message": "Are you sure you want to block {name}?", + "confirmations.block.message": "Ziur {name} blokeatu nahi duzula?", "confirmations.delete.confirm": "Delete", "confirmations.delete.message": "Are you sure you want to delete this status?", "confirmations.delete_list.confirm": "Delete", - "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", - "confirmations.domain_block.confirm": "Hide entire domain", + "confirmations.delete_list.message": "Ziur behin betiko ezabatu nahi duzula zerrenda hau?", + "confirmations.domain_block.confirm": "Ezkutatu domeinu osoa", "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.", - "confirmations.mute.confirm": "Mute", - "confirmations.mute.message": "Are you sure you want to mute {name}?", - "confirmations.unfollow.confirm": "Unfollow", - "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?", + "confirmations.mute.confirm": "Mututu", + "confirmations.mute.message": "Ziur {name} mututu nahi duzula?", + "confirmations.unfollow.confirm": "Utzi jarraitzeari", + "confirmations.unfollow.message": "Ziur {name} jarraitzeari utzi nahi diozula?", "embed.instructions": "Embed this status on your website by copying the code below.", - "embed.preview": "Here is what it will look like:", - "emoji_button.activity": "Activity", - "emoji_button.custom": "Custom", - "emoji_button.flags": "Flags", - "emoji_button.food": "Food & Drink", + "embed.preview": "Hau da izango duen itxura:", + "emoji_button.activity": "Jarduera", + "emoji_button.custom": "Pertsonalizatua", + "emoji_button.flags": "Banderak", + "emoji_button.food": "Janari eta edaria", "emoji_button.label": "Insert emoji", - "emoji_button.nature": "Nature", + "emoji_button.nature": "Natura", "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻", - "emoji_button.objects": "Objects", - "emoji_button.people": "People", - "emoji_button.recent": "Frequently used", - "emoji_button.search": "Search...", - "emoji_button.search_results": "Search results", - "emoji_button.symbols": "Symbols", - "emoji_button.travel": "Travel & Places", + "emoji_button.objects": "Objektuak", + "emoji_button.people": "Jendea", + "emoji_button.recent": "Maiz erabiliak", + "emoji_button.search": "Bilatu...", + "emoji_button.search_results": "Bilaketaren emaitzak", + "emoji_button.symbols": "Sinboloak", + "emoji_button.travel": "Bidaiak eta tokiak", "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!", "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", - "empty_column.hashtag": "There is nothing in this hashtag yet.", + "empty_column.hashtag": "Ez dago ezer traola honetan oraindik.", "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.", - "empty_column.home.public_timeline": "the public timeline", + "empty_column.home.public_timeline": "denbora-lerro publikoa", "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.", "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", - "follow_request.authorize": "Authorize", - "follow_request.reject": "Reject", - "getting_started.appsshort": "Apps", + "follow_request.authorize": "Baimendu", + "follow_request.reject": "Ukatu", + "getting_started.appsshort": "Aplikazioak", "getting_started.faq": "FAQ", - "getting_started.heading": "Getting started", + "getting_started.heading": "Lehen urratsak", "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.", - "getting_started.userguide": "User Guide", - "home.column_settings.advanced": "Advanced", - "home.column_settings.basic": "Basic", - "home.column_settings.filter_regex": "Filter out by regular expressions", - "home.column_settings.show_reblogs": "Show boosts", - "home.column_settings.show_replies": "Show replies", - "home.settings": "Column settings", - "keyboard_shortcuts.back": "to navigate back", - "keyboard_shortcuts.boost": "to boost", + "getting_started.userguide": "Erabiltzaile gida", + "home.column_settings.advanced": "Aurreratua", + "home.column_settings.basic": "Oinarrizkoa", + "home.column_settings.filter_regex": "Iragazi adierazpen erregularren bidez", + "home.column_settings.show_reblogs": "Erakutsi bultzadak", + "home.column_settings.show_replies": "Erakutsi erantzunak", + "home.settings": "Zutabearen ezarpenak", + "keyboard_shortcuts.back": "atzera nabigatzeko", + "keyboard_shortcuts.boost": "bultzada ematea", "keyboard_shortcuts.column": "to focus a status in one of the columns", "keyboard_shortcuts.compose": "to focus the compose textarea", "keyboard_shortcuts.description": "Description", - "keyboard_shortcuts.down": "to move down in the list", + "keyboard_shortcuts.down": "zerrendan behera mugitzea", "keyboard_shortcuts.enter": "to open status", - "keyboard_shortcuts.favourite": "to favourite", + "keyboard_shortcuts.favourite": "gogoko egitea", "keyboard_shortcuts.heading": "Keyboard Shortcuts", - "keyboard_shortcuts.hotkey": "Hotkey", - "keyboard_shortcuts.legend": "to display this legend", - "keyboard_shortcuts.mention": "to mention author", - "keyboard_shortcuts.reply": "to reply", - "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.hotkey": "Laster-tekla", + "keyboard_shortcuts.legend": "legenda hau bistaratzea", + "keyboard_shortcuts.mention": "egilea aipatzea", + "keyboard_shortcuts.reply": "erantzutea", + "keyboard_shortcuts.search": "bilaketan fokua jartzea", "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", - "keyboard_shortcuts.toot": "to start a brand new toot", + "keyboard_shortcuts.toot": "toot berria hastea", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", - "keyboard_shortcuts.up": "to move up in the list", - "lightbox.close": "Close", - "lightbox.next": "Next", - "lightbox.previous": "Previous", - "lists.account.add": "Add to list", - "lists.account.remove": "Remove from list", + "keyboard_shortcuts.up": "zerrendan gora mugitzea", + "lightbox.close": "Itxi", + "lightbox.next": "Hurrengoa", + "lightbox.previous": "Aurrekoa", + "lists.account.add": "Gehitu zerrendara", + "lists.account.remove": "Kendu zerrendatik", "lists.delete": "Delete list", - "lists.edit": "Edit list", - "lists.new.create": "Add list", - "lists.new.title_placeholder": "New list title", - "lists.search": "Search among people you follow", - "lists.subheading": "Your lists", - "loading_indicator.label": "Loading...", - "media_gallery.toggle_visible": "Toggle visibility", - "missing_indicator.label": "Not found", - "missing_indicator.sublabel": "This resource could not be found", - "mute_modal.hide_notifications": "Hide notifications from this user?", - "navigation_bar.blocks": "Blocked users", - "navigation_bar.community_timeline": "Local timeline", - "navigation_bar.direct": "Direct messages", - "navigation_bar.domain_blocks": "Hidden domains", - "navigation_bar.edit_profile": "Edit profile", - "navigation_bar.favourites": "Favourites", - "navigation_bar.follow_requests": "Follow requests", + "lists.edit": "Editatu zerrenda", + "lists.new.create": "Gehitu zerrenda", + "lists.new.title_placeholder": "Zerrenda berriaren izena", + "lists.search": "Bilatu jarraitzen dituzun pertsonen artean", + "lists.subheading": "Zure zerrendak", + "loading_indicator.label": "Kargatzen...", + "media_gallery.toggle_visible": "Txandakatu ikusgaitasuna", + "missing_indicator.label": "Ez aurkitua", + "missing_indicator.sublabel": "Baliabide hau ezin izan da aurkitu", + "mute_modal.hide_notifications": "Ezkutatu erabiltzaile honen jakinarazpenak?", + "navigation_bar.blocks": "Blokeatutako erabiltzaileak", + "navigation_bar.community_timeline": "Denbora-lerro lokala", + "navigation_bar.direct": "Mezu zuzenak", + "navigation_bar.domain_blocks": "Domeinu ezkutuak", + "navigation_bar.edit_profile": "Editatu profila", + "navigation_bar.favourites": "Gogokoak", + "navigation_bar.follow_requests": "Jarraitzeko eskariak", "navigation_bar.info": "Extended information", - "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts", - "navigation_bar.lists": "Lists", - "navigation_bar.logout": "Logout", - "navigation_bar.mutes": "Muted users", - "navigation_bar.pins": "Pinned toots", - "navigation_bar.preferences": "Preferences", - "navigation_bar.public_timeline": "Federated timeline", + "navigation_bar.keyboard_shortcuts": "Teklatu laster-bideak", + "navigation_bar.lists": "Zerrendak", + "navigation_bar.logout": "Amaitu saioa", + "navigation_bar.mutes": "Mutututako erabiltzaileak", + "navigation_bar.pins": "Finkatutako toot-ak", + "navigation_bar.preferences": "Hobespenak", + "navigation_bar.public_timeline": "Federatutako denbora-lerroa", "notification.favourite": "{name} favourited your status", - "notification.follow": "{name} followed you", - "notification.mention": "{name} mentioned you", + "notification.follow": "{name} erabiltzaileak jarraitzen zaitu", + "notification.mention": "{name} erabiltzaileak aipatu zaitu", "notification.reblog": "{name} boosted your status", - "notifications.clear": "Clear notifications", - "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?", - "notifications.column_settings.alert": "Desktop notifications", - "notifications.column_settings.favourite": "Favourites:", - "notifications.column_settings.follow": "New followers:", - "notifications.column_settings.mention": "Mentions:", - "notifications.column_settings.push": "Push notifications", - "notifications.column_settings.push_meta": "This device", - "notifications.column_settings.reblog": "Boosts:", - "notifications.column_settings.show": "Show in column", - "notifications.column_settings.sound": "Play sound", - "notifications.group": "{count} notifications", - "onboarding.done": "Done", - "onboarding.next": "Next", + "notifications.clear": "Garbitu jakinarazpenak", + "notifications.clear_confirmation": "Ziur zure jakinarazpen guztiak behin betirako garbitu nahi dituzula?", + "notifications.column_settings.alert": "Mahaigaineko jakinarazpenak", + "notifications.column_settings.favourite": "Gogokoak:", + "notifications.column_settings.follow": "Jarraitzaile berriak:", + "notifications.column_settings.mention": "Aipamenak:", + "notifications.column_settings.push": "Push jakinarazpenak", + "notifications.column_settings.push_meta": "Gailu hau", + "notifications.column_settings.reblog": "Bultzadak:", + "notifications.column_settings.show": "Erakutsi zutabean", + "notifications.column_settings.sound": "Jo soinua", + "notifications.group": "{count} jakinarazpen", + "onboarding.done": "Egina", + "onboarding.next": "Hurrengoa", "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.", - "onboarding.page_four.home": "The home timeline shows posts from people you follow.", - "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.", + "onboarding.page_four.home": "Hasierako denbora-lerroak jarraitzen duzun jendearen mezuak erakusten ditu.", + "onboarding.page_four.notifications": "Jakinarazpenen zutabeak besteek zurekin hasitako hartu-emanak erakusten ditu.", "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.", - "onboarding.page_one.full_handle": "Your full handle", - "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.", - "onboarding.page_one.welcome": "Welcome to Mastodon!", - "onboarding.page_six.admin": "Your instance's admin is {admin}.", - "onboarding.page_six.almost_done": "Almost done...", + "onboarding.page_one.full_handle": "Zure erabiltzaile-izen osoa", + "onboarding.page_one.handle_hint": "Hau da zure lagunei zu aurkitzeko emango zeniena.", + "onboarding.page_one.welcome": "Ongi etorri Mastodon-era!", + "onboarding.page_six.admin": "Zure instantziaren administratzailea {admin} da.", + "onboarding.page_six.almost_done": "Ia eginda...", "onboarding.page_six.appetoot": "Bon Appetoot!", - "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.", + "onboarding.page_six.apps_available": "{apps} eskuragarri daude iOS, Android eta beste plataformetarako.", "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.", - "onboarding.page_six.guidelines": "community guidelines", - "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!", - "onboarding.page_six.various_app": "mobile apps", + "onboarding.page_six.guidelines": "komunitatearen gida-lerroak", + "onboarding.page_six.read_guidelines": "Irakurri {domain} {guidelines} mesedez!", + "onboarding.page_six.various_app": "mugikorrerako aplikazioak", "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.", "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.", "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.", - "onboarding.skip": "Skip", + "onboarding.skip": "Saltatu", "privacy.change": "Adjust status privacy", - "privacy.direct.long": "Post to mentioned users only", - "privacy.direct.short": "Direct", - "privacy.private.long": "Post to followers only", - "privacy.private.short": "Followers-only", - "privacy.public.long": "Post to public timelines", - "privacy.public.short": "Public", + "privacy.direct.long": "Bidali aipatutako erabiltzaileei besterik ez", + "privacy.direct.short": "Zuzena", + "privacy.private.long": "Bidali jarraitzaileei besterik ez", + "privacy.private.short": "Jarraitzaileak soilik", + "privacy.public.long": "Bistaratu denbora-lerro publikoetan", + "privacy.public.short": "Publikoa", "privacy.unlisted.long": "Do not show in public timelines", - "privacy.unlisted.short": "Unlisted", - "regeneration_indicator.label": "Loading…", - "regeneration_indicator.sublabel": "Your home feed is being prepared!", + "privacy.unlisted.short": "Zerrendatu gabea", + "regeneration_indicator.label": "Kargatzen…", + "regeneration_indicator.sublabel": "Zure hasiera-jarioa prestatzen ari da!", "relative_time.days": "{number}d", "relative_time.hours": "{number}h", - "relative_time.just_now": "now", + "relative_time.just_now": "orain", "relative_time.minutes": "{number}m", "relative_time.seconds": "{number}s", - "reply_indicator.cancel": "Cancel", - "report.forward": "Forward to {target}", + "reply_indicator.cancel": "Utzi", + "report.forward": "Birbidali hona: {target}", "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", - "report.placeholder": "Additional comments", + "report.placeholder": "Iruzkin gehigarriak", "report.submit": "Submit", "report.target": "Report {target}", - "search.placeholder": "Search", - "search_popout.search_format": "Advanced search format", + "search.placeholder": "Bilatu", + "search_popout.search_format": "Bilaketa aurreratuaren formatua", "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.", - "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.hashtag": "traola", "search_popout.tips.status": "status", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", - "search_popout.tips.user": "user", - "search_results.accounts": "People", - "search_results.hashtags": "Hashtags", - "search_results.statuses": "Toots", + "search_popout.tips.user": "erabiltzailea", + "search_results.accounts": "Jendea", + "search_results.hashtags": "Traolak", + "search_results.statuses": "Toot-ak", "search_results.total": "{count, number} {count, plural, one {result} other {results}}", - "standalone.public_title": "A look inside...", + "standalone.public_title": "Begiradatxo bat...", "status.block": "Block @{name}", - "status.cancel_reblog_private": "Unboost", - "status.cannot_reblog": "This post cannot be boosted", + "status.cancel_reblog_private": "Kendu bultzada", + "status.cannot_reblog": "Mezu honi ezin zaio bultzada eman", "status.delete": "Delete", - "status.direct": "Direct message @{name}", - "status.embed": "Embed", - "status.favourite": "Favourite", - "status.load_more": "Load more", - "status.media_hidden": "Media hidden", - "status.mention": "Mention @{name}", - "status.more": "More", - "status.mute": "Mute @{name}", - "status.mute_conversation": "Mute conversation", + "status.direct": "Mezu zuzena @{name} erabiltzaileari", + "status.embed": "Txertatu", + "status.favourite": "Gogokoa", + "status.load_more": "Kargatu gehiago", + "status.media_hidden": "Multimedia ezkutatua", + "status.mention": "Aipatu @{name}", + "status.more": "Gehiago", + "status.mute": "Mututu @{name}", + "status.mute_conversation": "Mututu elkarrizketa", "status.open": "Expand this status", - "status.pin": "Pin on profile", - "status.pinned": "Pinned toot", - "status.reblog": "Boost", - "status.reblog_private": "Boost to original audience", - "status.reblogged_by": "{name} boosted", - "status.reply": "Reply", - "status.replyAll": "Reply to thread", + "status.pin": "Finkatu profilean", + "status.pinned": "Finkatutako toot-a", + "status.reblog": "Bultzada", + "status.reblog_private": "Bultzada jatorrizko hartzaileei", + "status.reblogged_by": "{name} erabiltzailearen bultzada", + "status.reply": "Erantzun", + "status.replyAll": "Erantzun harian", "status.report": "Report @{name}", "status.sensitive_toggle": "Click to view", - "status.sensitive_warning": "Sensitive content", - "status.share": "Share", - "status.show_less": "Show less", - "status.show_less_all": "Show less for all", - "status.show_more": "Show more", - "status.show_more_all": "Show more for all", - "status.unmute_conversation": "Unmute conversation", - "status.unpin": "Unpin from profile", - "tabs_bar.federated_timeline": "Federated", + "status.sensitive_warning": "Eduki mingarria", + "status.share": "Partekatu", + "status.show_less": "Erakutsi gutxiago", + "status.show_less_all": "Erakutsi denetarik gutxiago", + "status.show_more": "Erakutsi gehiago", + "status.show_more_all": "Erakutsi denetarik gehiago", + "status.unmute_conversation": "Desmututu elkarrizketa", + "status.unpin": "Desfinkatu profiletik", + "tabs_bar.federated_timeline": "Federatua", "tabs_bar.home": "Home", "tabs_bar.local_timeline": "Local", - "tabs_bar.notifications": "Notifications", - "tabs_bar.search": "Search", + "tabs_bar.notifications": "Jakinarazpenak", + "tabs_bar.search": "Bilatu", "timeline.media": "Media", - "timeline.posts": "Toots", - "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", - "upload_area.title": "Drag & drop to upload", - "upload_button.label": "Add media", - "upload_form.description": "Describe for the visually impaired", - "upload_form.focus": "Crop", - "upload_form.undo": "Undo", - "upload_progress.label": "Uploading...", - "video.close": "Close video", - "video.exit_fullscreen": "Exit full screen", - "video.expand": "Expand video", + "timeline.posts": "Toot-ak", + "ui.beforeunload": "Zure zirriborroa galduko da Mastodon uzten baduzu.", + "upload_area.title": "Arrastatu eta jaregin igotzeko", + "upload_button.label": "Gehitu multimedia", + "upload_form.description": "Deskribatu ikusmen arazoak dituztenentzat", + "upload_form.focus": "Moztu", + "upload_form.undo": "Ezabatu", + "upload_progress.label": "Igotzen...", + "video.close": "Itxi bideoa", + "video.exit_fullscreen": "Irten pantaila osotik", + "video.expand": "Hedatu bideoa", "video.fullscreen": "Full screen", - "video.hide": "Hide video", - "video.mute": "Mute sound", + "video.hide": "Ezkutatu bideoa", + "video.mute": "Mututu soinua", "video.pause": "Pause", - "video.play": "Play", - "video.unmute": "Unmute sound" + "video.play": "Jo", + "video.unmute": "Desmututu soinua" } diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index c45277d..6c0f34a 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -1,9 +1,9 @@ { - "account.badges.bot": "Bot", + "account.badges.bot": "ربات", "account.block": "مسدودسازی @{name}", "account.block_domain": "پنهان‌سازی همه چیز از سرور {domain}", "account.blocked": "مسدودشده", - "account.direct": "Direct message @{name}", + "account.direct": "پیغام خصوصی به @{name}", "account.disclaimer_full": "اطلاعات زیر ممکن است نمایهٔ این کاربر را به تمامی نشان ندهد.", "account.domain_blocked": "دامین پنهان‌شده", "account.edit_profile": "ویرایش نمایه", @@ -12,7 +12,7 @@ "account.follows": "پی می‌گیرد", "account.follows_you": "پیگیر شماست", "account.hide_reblogs": "پنهان کردن بازبوق‌های @{name}", - "account.media": "رسانه", + "account.media": "عکس و ویدیو", "account.mention": "نام‌بردن از @{name}", "account.moved_to": "{name} منتقل شده است به:", "account.mute": "بی‌صدا کردن @{name}", @@ -30,8 +30,8 @@ "account.unmute": "باصدا کردن @{name}", "account.unmute_notifications": "باصداکردن اعلان‌ها از طرف @{name}", "account.view_full_profile": "نمایش نمایهٔ کامل", - "alert.unexpected.message": "An unexpected error occurred.", - "alert.unexpected.title": "Oops!", + "alert.unexpected.message": "خطای پیش‌بینی‌نشده‌ای رخ داد.", + "alert.unexpected.title": "ای وای!", "boost_modal.combo": "دکمهٔ {combo} را بزنید تا دیگر این را نبینید", "bundle_column_error.body": "هنگام بازکردن این بخش خطایی رخ داد.", "bundle_column_error.retry": "تلاش دوباره", @@ -41,8 +41,8 @@ "bundle_modal_error.retry": "تلاش دوباره", "column.blocks": "کاربران مسدودشده", "column.community": "نوشته‌های محلی", - "column.direct": "Direct messages", - "column.domain_blocks": "Hidden domains", + "column.direct": "پیغام‌های خصوصی", + "column.domain_blocks": "دامین‌های پنهان‌شده", "column.favourites": "پسندیده‌ها", "column.follow_requests": "درخواست‌های پیگیری", "column.home": "خانه", @@ -60,18 +60,18 @@ "column_header.unpin": "رهاکردن", "column_subheading.navigation": "گشت و گذار", "column_subheading.settings": "تنظیمات", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", - "compose_form.direct_message_warning_learn_more": "Learn more", - "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.direct_message_warning": "این بوق تنها به کاربرانی که از آن‌ها نام برده شده فرستاده خواهد شد.", + "compose_form.direct_message_warning_learn_more": "بیشتر بدانید", + "compose_form.hashtag_warning": "از آن‌جا که این بوق فهرست‌نشده است، در نتایج جستجوی هشتگ‌ها پیدا نخواهد شد. تنها بوق‌های عمومی را می‌توان با جستجوی هشتگ پیدا کرد.", "compose_form.lock_disclaimer": "حساب شما {locked} نیست. هر کسی می‌تواند پیگیر شما شود و نوشته‌های ویژهٔ پیگیران شما را ببیند.", "compose_form.lock_disclaimer.lock": "قفل", "compose_form.placeholder": "تازه چه خبر؟", "compose_form.publish": "بوق", "compose_form.publish_loud": "{publish}!", - "compose_form.sensitive.marked": "Media is marked as sensitive", - "compose_form.sensitive.unmarked": "Media is not marked as sensitive", - "compose_form.spoiler.marked": "Text is hidden behind warning", - "compose_form.spoiler.unmarked": "Text is not hidden", + "compose_form.sensitive.marked": "این تصویر به عنوان حساس علامت‌گذاری شده", + "compose_form.sensitive.unmarked": "این تصویر به عنوان حساس علامت‌گذاری نشده", + "compose_form.spoiler.marked": "نوشته پشت هشدار محتوا پنهان است", + "compose_form.spoiler.unmarked": "نوشته پنهان نیست", "compose_form.spoiler_placeholder": "هشدار محتوا", "confirmation_modal.cancel": "بی‌خیال", "confirmations.block.confirm": "مسدود کن", @@ -103,7 +103,7 @@ "emoji_button.symbols": "نمادها", "emoji_button.travel": "سفر و مکان", "empty_column.community": "فهرست نوشته‌های محلی خالی است. چیزی بنویسید تا چرخش بچرخد!", - "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.direct": "شما هیچ پیغام مستقیمی ندارید. اگر چنین پیغامی بگیرید یا بفرستید این‌جا نمایش خواهد یافت.", "empty_column.hashtag": "هنوز هیچ چیزی با این هشتگ نیست.", "empty_column.home": "شما هنوز پیگیر کسی نیستید. {public} را ببینید یا چیزی را جستجو کنید تا کاربران دیگر را ببینید.", "empty_column.home.public_timeline": "فهرست نوشته‌های همه‌جا", @@ -119,7 +119,7 @@ "getting_started.userguide": "راهنمای کاربری", "home.column_settings.advanced": "پیشرفته", "home.column_settings.basic": "اصلی", - "home.column_settings.filter_regex": "با عبارت‌های باقاعده فیلتر کنید", + "home.column_settings.filter_regex": "با عبارت‌های باقاعده (regexp) فیلتر کنید", "home.column_settings.show_reblogs": "نمایش بازبوق‌ها", "home.column_settings.show_replies": "نمایش پاسخ‌ها", "home.settings": "تنظیمات ستون", @@ -155,12 +155,12 @@ "loading_indicator.label": "بارگیری...", "media_gallery.toggle_visible": "تغییر پیدایی", "missing_indicator.label": "پیدا نشد", - "missing_indicator.sublabel": "This resource could not be found", + "missing_indicator.sublabel": "این منبع پیدا نشد", "mute_modal.hide_notifications": "اعلان‌های این کاربر پنهان شود؟", "navigation_bar.blocks": "کاربران مسدودشده", "navigation_bar.community_timeline": "نوشته‌های محلی", - "navigation_bar.direct": "Direct messages", - "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.direct": "پیغام‌های خصوصی", + "navigation_bar.domain_blocks": "دامین‌های پنهان‌شده", "navigation_bar.edit_profile": "ویرایش نمایه", "navigation_bar.favourites": "پسندیده‌ها", "navigation_bar.follow_requests": "درخواست‌های پیگیری", @@ -187,15 +187,15 @@ "notifications.column_settings.reblog": "بازبوق‌ها:", "notifications.column_settings.show": "نمایش در ستون", "notifications.column_settings.sound": "پخش صدا", - "notifications.group": "{count} notifications", + "notifications.group": "{count} اعلان", "onboarding.done": "پایان", "onboarding.next": "بعدی", "onboarding.page_five.public_timelines": "نوشته‌های محلی یعنی نوشته‌های همهٔ کاربران {domain}. نوشته‌های همه‌جا یعنی نوشته‌های همهٔ کسانی که کاربران {domain} آن‌ها را پی می‌گیرند. این فهرست‌های عمومی راه خوبی برای یافتن کاربران تازه هستند.", "onboarding.page_four.home": "ستون «خانه» نوشته‌های کسانی را نشان می‌دهد که شما پی می‌گیرید.", "onboarding.page_four.notifications": "ستون «اعلان‌ها» ارتباط‌های شما با دیگران را نشان می‌دهد.", "onboarding.page_one.federation": "ماستدون شبکه‌ای از سرورهای مستقل است که با پیوستن به یکدیگر یک شبکهٔ اجتماعی بزرگ را تشکیل می‌دهند.", - "onboarding.page_one.full_handle": "Your full handle", - "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.", + "onboarding.page_one.full_handle": "شناسهٔ کاربری کامل شما", + "onboarding.page_one.handle_hint": "این چیزی است که باید به دوستان خود بگویید تا بتوانند شما را پیدا کنند.", "onboarding.page_one.welcome": "به ماستدون خوش آمدید!", "onboarding.page_six.admin": "نشانی مسئول سرور شما {admin} است.", "onboarding.page_six.almost_done": "الان تقریباً آماده‌اید...", @@ -218,50 +218,50 @@ "privacy.public.short": "عمومی", "privacy.unlisted.long": "عمومی، ولی فهرست نکن", "privacy.unlisted.short": "فهرست‌نشده", - "regeneration_indicator.label": "Loading…", - "regeneration_indicator.sublabel": "Your home feed is being prepared!", + "regeneration_indicator.label": "در حال باز شدن…", + "regeneration_indicator.sublabel": "این فهرست دارد آماده می‌شود!", "relative_time.days": "{number}d", "relative_time.hours": "{number}h", "relative_time.just_now": "الان", "relative_time.minutes": "{number}m", "relative_time.seconds": "{number}s", "reply_indicator.cancel": "لغو", - "report.forward": "Forward to {target}", - "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", - "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", + "report.forward": "فرستادن به {target}", + "report.forward_hint": "این حساب در سرور دیگری ثبت شده. آیا می‌خواهید رونوشتی از این گزارش به طور ناشناس به آن‌جا هم فرستاده شود؟", + "report.hint": "این گزارش به مدیران سرور شما فرستاده خواهد شد. می‌توانید دلیل گزارش‌دادن این حساب را در این‌جا بنویسید:", "report.placeholder": "توضیح اضافه", "report.submit": "بفرست", "report.target": "گزارش‌دادن", "search.placeholder": "جستجو", "search_popout.search_format": "راهنمای جستجوی پیشرفته", - "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.", + "search_popout.tips.full_text": "جستجوی متنی ساده می‌تواند بوق‌هایی که شما نوشته‌اید، پسندیده‌اید، بازبوقیده‌اید، یا در آن‌ها از شما نام برده شده است را پیدا کند. همچنین نام‌های کاربری، نام نمایش‌یافته، و هشتگ‌ها را هم شامل می‌شود.", "search_popout.tips.hashtag": "هشتگ", "search_popout.tips.status": "نوشته", "search_popout.tips.text": "جستجوی متنی ساده برای نام‌ها، نام‌های کاربری، و هشتگ‌ها", "search_popout.tips.user": "کاربر", - "search_results.accounts": "People", - "search_results.hashtags": "Hashtags", - "search_results.statuses": "Toots", + "search_results.accounts": "افراد", + "search_results.hashtags": "هشتگ‌ها", + "search_results.statuses": "بوق‌ها", "search_results.total": "{count, number} {count, plural, one {نتیجه} other {نتیجه}}", "standalone.public_title": "نگاهی به کاربران این سرور...", - "status.block": "Block @{name}", - "status.cancel_reblog_private": "Unboost", + "status.block": "مسدودسازی @{name}", + "status.cancel_reblog_private": "حذف بازبوق", "status.cannot_reblog": "این نوشته را نمی‌شود بازبوقید", "status.delete": "پاک‌کردن", - "status.direct": "Direct message @{name}", + "status.direct": "پیغام مستقیم به @{name}", "status.embed": "جاگذاری", "status.favourite": "پسندیدن", "status.load_more": "بیشتر نشان بده", "status.media_hidden": "تصویر پنهان شده", "status.mention": "نام‌بردن از @{name}", "status.more": "بیشتر", - "status.mute": "Mute @{name}", + "status.mute": "بی‌صدا کردن @{name}", "status.mute_conversation": "بی‌صداکردن گفتگو", "status.open": "این نوشته را باز کن", "status.pin": "نوشتهٔ ثابت نمایه", - "status.pinned": "Pinned toot", + "status.pinned": "بوق ثابت", "status.reblog": "بازبوقیدن", - "status.reblog_private": "Boost to original audience", + "status.reblog_private": "بازبوق به مخاطبان اولیه", "status.reblogged_by": "‫{name}‬ بازبوقید", "status.reply": "پاسخ", "status.replyAll": "به نوشته پاسخ دهید", @@ -270,24 +270,24 @@ "status.sensitive_warning": "محتوای حساس", "status.share": "هم‌رسانی", "status.show_less": "نهفتن", - "status.show_less_all": "Show less for all", + "status.show_less_all": "نمایش کمتر همه", "status.show_more": "نمایش", - "status.show_more_all": "Show more for all", + "status.show_more_all": "نمایش بیشتر همه", "status.unmute_conversation": "باصداکردن گفتگو", "status.unpin": "برداشتن نوشتهٔ ثابت نمایه", "tabs_bar.federated_timeline": "همگانی", "tabs_bar.home": "خانه", "tabs_bar.local_timeline": "محلی", "tabs_bar.notifications": "اعلان‌ها", - "tabs_bar.search": "Search", - "timeline.media": "Media", - "timeline.posts": "Toots", + "tabs_bar.search": "جستجو", + "timeline.media": "عکس و ویدیو", + "timeline.posts": "بوق‌ها", "ui.beforeunload": "اگر از ماستدون خارج شوید پیش‌نویس شما پاک خواهد شد.", "upload_area.title": "برای بارگذاری به این‌جا بکشید", "upload_button.label": "افزودن تصویر", "upload_form.description": "نوشتهٔ توضیحی برای کم‌بینایان و نابینایان", - "upload_form.focus": "Crop", - "upload_form.undo": "واگردانی", + "upload_form.focus": "بریدن لبه‌ها", + "upload_form.undo": "حذف", "upload_progress.label": "بارگذاری...", "video.close": "بستن ویدیو", "video.exit_fullscreen": "خروج از حالت تمام صفحه", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 4c2bd39..24b771e 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -3,7 +3,7 @@ "account.block": "Bloquer @{name}", "account.block_domain": "Tout masquer venant de {domain}", "account.blocked": "Bloqué", - "account.direct": "Message direct @{name}", + "account.direct": "Message direct à @{name}", "account.disclaimer_full": "Les données ci-dessous peuvent ne pas refléter ce profil dans sa totalité.", "account.domain_blocked": "Domaine caché", "account.edit_profile": "Modifier le profil", @@ -61,7 +61,7 @@ "column_subheading.navigation": "Navigation", "column_subheading.settings": "Paramètres", "compose_form.direct_message_warning": "Ce pouet sera uniquement envoyé qu'aux personnes mentionnées. Cependant, l'administration de votre instance et des instances réceptrices pourront inspecter ce message.", - "compose_form.direct_message_warning_learn_more": "Learn more", + "compose_form.direct_message_warning_learn_more": "En savoir plus", "compose_form.hashtag_warning": "Ce pouet ne sera pas listé dans les recherches par hashtag car sa visibilité est réglée sur \"non-listé\". Seuls les pouets avec une visibilité \"publique\" peuvent être recherchés par hashtag.", "compose_form.lock_disclaimer": "Votre compte n’est pas {locked}. Tout le monde peut vous suivre et voir vos pouets privés.", "compose_form.lock_disclaimer.lock": "verrouillé", @@ -248,7 +248,7 @@ "status.cancel_reblog_private": "Dé-booster", "status.cannot_reblog": "Cette publication ne peut être boostée", "status.delete": "Effacer", - "status.direct": "Message direct @{name}", + "status.direct": "Message direct à @{name}", "status.embed": "Intégrer", "status.favourite": "Ajouter aux favoris", "status.load_more": "Charger plus", @@ -281,7 +281,7 @@ "tabs_bar.notifications": "Notifications", "tabs_bar.search": "Chercher", "timeline.media": "Media", - "timeline.posts": "Toots", + "timeline.posts": "Pouets", "ui.beforeunload": "Votre brouillon sera perdu si vous quittez Mastodon.", "upload_area.title": "Glissez et déposez pour envoyer", "upload_button.label": "Joindre un média", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 485fe0f..cc0551b 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -19,7 +19,7 @@ "account.mute_notifications": "Silenzia notifiche da @{name}", "account.muted": "Silenziato", "account.posts": "Toot", - "account.posts_with_replies": "Toot con risposte", + "account.posts_with_replies": "Toot e risposte", "account.report": "Segnala @{name}", "account.requested": "In attesa di approvazione", "account.share": "Condividi il profilo di @{name}", @@ -33,7 +33,7 @@ "alert.unexpected.message": "Si è verificato un errore inatteso.", "alert.unexpected.title": "Oops!", "boost_modal.combo": "Puoi premere {combo} per saltare questo passaggio la prossima volta", - "bundle_column_error.body": "C'è stato un errore mentre questo componente veniva caricato.", + "bundle_column_error.body": "E' avvenuto un errore durante il caricamento di questo componente.", "bundle_column_error.retry": "Riprova", "bundle_column_error.title": "Network error", "bundle_modal_error.close": "Chiudi", @@ -60,8 +60,8 @@ "column_header.unpin": "Non fissare in cima", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Impostazioni", - "compose_form.direct_message_warning": "Questo toot sarà visibile solo a tutti gli utenti citati.", - "compose_form.direct_message_warning_learn_more": "Learn more", + "compose_form.direct_message_warning": "Questo toot sarà mandato solo a tutti gli utenti menzionati.", + "compose_form.direct_message_warning_learn_more": "Per saperne di piu'", "compose_form.hashtag_warning": "Questo toot non è listato, quindi non sarà trovato nelle ricerche per hashtag. Solo i toot pubblici possono essere cercati per hashtag.", "compose_form.lock_disclaimer": "Il tuo account non è {bloccato}. Chiunque può decidere di seguirti per vedere i tuoi post per soli seguaci.", "compose_form.lock_disclaimer.lock": "bloccato", @@ -84,7 +84,7 @@ "confirmations.domain_block.message": "Sei davvero sicuro che vuoi bloccare l'intero {domain}? Nella maggior parte dei casi, pochi blocchi o silenziamenti mirati sono sufficienti e preferibili.", "confirmations.mute.confirm": "Silenzia", "confirmations.mute.message": "Sei sicuro di voler silenziare {name}?", - "confirmations.unfollow.confirm": "Non seguire più", + "confirmations.unfollow.confirm": "Smetti di seguire", "confirmations.unfollow.message": "Sei sicuro che non vuoi più seguire {name}?", "embed.instructions": "Inserisci questo status nel tuo sito copiando il codice qui sotto.", "embed.preview": "Ecco come apparirà:", @@ -94,7 +94,7 @@ "emoji_button.food": "Cibo e bevande", "emoji_button.label": "Inserisci emoji", "emoji_button.nature": "Natura", - "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻", + "emoji_button.not_found": "Nessun emojos!! (╯°□°)╯︵ ┻━┻", "emoji_button.objects": "Oggetti", "emoji_button.people": "Persone", "emoji_button.recent": "Usati di frequente", @@ -187,7 +187,7 @@ "notifications.column_settings.reblog": "Post condivisi:", "notifications.column_settings.show": "Mostra in colonna", "notifications.column_settings.sound": "Riproduci suono", - "notifications.group": "{count} notifications", + "notifications.group": "{count} notifiche", "onboarding.done": "Fatto", "onboarding.next": "Prossimo", "onboarding.page_five.public_timelines": "La timeline locale mostra i post pubblici di tutti gli utenti di {domain}. La timeline federata mostra i post pubblici di tutti gli utenti seguiti da quelli di {domain}. Queste sono le timeline pubbliche, che vi danno grandi possibilità di scoprire nuovi utenti.", @@ -228,18 +228,18 @@ "reply_indicator.cancel": "Annulla", "report.forward": "Inoltra a {target}", "report.forward_hint": "Questo account appartiene a un altro server. Mandare anche là una copia anonima del rapporto?", - "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", + "report.hint": "La segnalazione sara' invata ai tuoi moderatori di istanza. Di seguito, puoi fornire il motivo per il quale stai segnalando questo account:", "report.placeholder": "Commenti aggiuntivi", "report.submit": "Invia", - "report.target": "Invio la segnalazione", + "report.target": "Invio la segnalazione {target}", "search.placeholder": "Cerca", - "search_popout.search_format": "Advanced search format", + "search_popout.search_format": "Formato di ricerca avanzato", "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.", "search_popout.tips.hashtag": "hashtag", "search_popout.tips.status": "status", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", - "search_popout.tips.user": "user", - "search_results.accounts": "People", + "search_popout.tips.user": "utente", + "search_results.accounts": "Gente", "search_results.hashtags": "Hashtag", "search_results.statuses": "Toot", "search_results.total": "{count} {count, plural, one {risultato} other {risultati}}", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 614e526..e33dcea 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -60,11 +60,11 @@ "column_header.unpin": "ピン留めを外す", "column_subheading.navigation": "ナビゲーション", "column_subheading.settings": "設定", - "compose_form.direct_message_warning": "このトゥートはメンションされた人にのみ送信されます。ただし送受信したインスタンスの管理者が検査するかもしれません。", - "compose_form.direct_message_warning_learn_more": "Learn more", + "compose_form.direct_message_warning": "このトゥートはメンションされた人にのみ送信されます。", + "compose_form.direct_message_warning_learn_more": "もっと詳しく", "compose_form.hashtag_warning": "このトゥートは未収載なのでハッシュタグの一覧に表示されません。公開トゥートだけがハッシュタグで検索できます。", "compose_form.lock_disclaimer": "あなたのアカウントは{locked}になっていません。誰でもあなたをフォローすることができ、フォロワー限定の投稿を見ることができます。", - "compose_form.lock_disclaimer.lock": "非公開", + "compose_form.lock_disclaimer.lock": "承認制", "compose_form.placeholder": "今なにしてる?", "compose_form.publish": "トゥート", "compose_form.publish_loud": "{publish}!", @@ -187,7 +187,7 @@ "notifications.column_settings.reblog": "ブースト:", "notifications.column_settings.show": "カラムに表示", "notifications.column_settings.sound": "通知音を再生", - "notifications.group": "{count} notifications", + "notifications.group": "{count} 件の通知", "onboarding.done": "完了", "onboarding.next": "次へ", "onboarding.page_five.public_timelines": "連合タイムラインでは{domain}の人がフォローしているMastodon全体での公開投稿を表示します。同じくローカルタイムラインでは{domain}のみの公開投稿を表示します。", @@ -281,7 +281,7 @@ "tabs_bar.notifications": "通知", "tabs_bar.search": "検索", "timeline.media": "Media", - "timeline.posts": "Toots", + "timeline.posts": "投稿", "ui.beforeunload": "Mastodonから離れると送信前の投稿は失われます。", "upload_area.title": "ドラッグ&ドロップでアップロード", "upload_button.label": "メディアを追加", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 1ead201..ee4d9de 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -61,7 +61,7 @@ "column_subheading.navigation": "Navigatie", "column_subheading.settings": "Instellingen", "compose_form.direct_message_warning": "Deze toot wordt alleen naar vermelde gebruikers verstuurd. Echter, de beheerders en moderatoren van jouw en de ontvangende Mastodonserver(s) kunnen dit bericht mogelijk wel bekijken.", - "compose_form.direct_message_warning_learn_more": "Learn more", + "compose_form.direct_message_warning_learn_more": "Meer leren", "compose_form.hashtag_warning": "Deze toot valt niet onder een hashtag te bekijken, omdat deze niet op openbare tijdlijnen wordt getoond. Alleen openbare toots kunnen via hashtags gevonden worden.", "compose_form.lock_disclaimer": "Jouw account is niet {locked}. Iedereen kan jou volgen en kan de toots zien die je alleen aan jouw volgers hebt gericht.", "compose_form.lock_disclaimer.lock": "besloten", @@ -187,7 +187,7 @@ "notifications.column_settings.reblog": "Boosts:", "notifications.column_settings.show": "In kolom tonen", "notifications.column_settings.sound": "Geluid afspelen", - "notifications.group": "{count} notifications", + "notifications.group": "{count} meldingen", "onboarding.done": "Klaar", "onboarding.next": "Volgende", "onboarding.page_five.public_timelines": "De lokale tijdlijn toont openbare toots van iedereen op {domain}. De globale tijdlijn toont openbare toots van iedereen die door gebruikers van {domain} worden gevolgd, dus ook mensen van andere Mastodonservers. Dit zijn de openbare tijdlijnen en vormen een uitstekende manier om nieuwe mensen te leren kennen.", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index e7bc148..3ad5b3d 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -1,5 +1,5 @@ { - "account.badges.bot": "Bot", + "account.badges.bot": "Robòt", "account.block": "Blocar @{name}", "account.block_domain": "Tot amagar del domeni {domain}", "account.blocked": "Blocat", @@ -60,7 +60,7 @@ "column_header.unpin": "Despenjar", "column_subheading.navigation": "Navigacion", "column_subheading.settings": "Paramètres", - "compose_form.direct_message_warning": "Aqueste tut serà pas que visibile pel monde mencionat.", + "compose_form.direct_message_warning": "Sols los mencionats poiràn veire aqueste tut.", "compose_form.direct_message_warning_learn_more": "Ne saber mai", "compose_form.hashtag_warning": "Aqueste tut serà pas ligat a cap d’etiqueta estant qu’es pas listat. Òm pas cercar que los tuts publics per etiqueta.", "compose_form.lock_disclaimer": "Vòstre compte es pas {locked}. Tot lo monde pòt vos sègre e veire los estatuts reservats als seguidors.", @@ -281,7 +281,7 @@ "tabs_bar.notifications": "Notificacions", "tabs_bar.search": "Recèrcas", "timeline.media": "Media", - "timeline.posts": "Toots", + "timeline.posts": "Tuts", "ui.beforeunload": "Vòstre brolhon serà perdut se quitatz Mastodon.", "upload_area.title": "Lisatz e depausatz per mandar", "upload_button.label": "Ajustar un mèdia", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index c69a90e..c8441df 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -60,8 +60,8 @@ "column_header.unpin": "Desafixar", "column_subheading.navigation": "Navegação", "column_subheading.settings": "Configurações", - "compose_form.direct_message_warning": "Este toot só será enviado aos usuários mencionados. No entanto, a administração da sua instância e das instâncias dos usuários mencionados podem inspecionar essa mensagem.", - "compose_form.direct_message_warning_learn_more": "Learn more", + "compose_form.direct_message_warning": "Este toot só será enviado aos usuários mencionados. A mensagem não é encriptada e será armazenada nos servidores dos usuários mencionados.", + "compose_form.direct_message_warning_learn_more": "Saber mais", "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", @@ -187,7 +187,7 @@ "notifications.column_settings.reblog": "Compartilhamento:", "notifications.column_settings.show": "Mostrar nas colunas", "notifications.column_settings.sound": "Reproduzir som", - "notifications.group": "{count} notifications", + "notifications.group": "{count} notificações", "onboarding.done": "Pronto", "onboarding.next": "Próximo", "onboarding.page_five.public_timelines": "A timeline local mostra postagens públicas de todos os usuários no {domain}. A timeline federada mostra todas as postagens de todas as pessoas que pessoas no {domain} seguem. Estas são as timelines públicas, uma ótima maneira de conhecer novas pessoas.", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 6c396e5..d2af13b 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -60,7 +60,7 @@ "column_header.unpin": "Ångra fäst", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Inställningar", - "compose_form.direct_message_warning": "Denna toot kommer endast vara synlig för nämnda användare.", + "compose_form.direct_message_warning": "Denna toot kommer endast att skickas nämnda nämnda användare.", "compose_form.direct_message_warning_learn_more": "Learn more", "compose_form.hashtag_warning": "Denna toot kommer inte att listas under någon hashtag eftersom den är onoterad. Endast offentliga toots kan sökas med hashtag.", "compose_form.lock_disclaimer": "Ditt konto är inte {locked}. Vemsomhelst kan följa dig och även se dina inlägg skrivna för endast dina följare.", @@ -287,12 +287,12 @@ "upload_button.label": "Lägg till media", "upload_form.description": "Beskriv för synskadade", "upload_form.focus": "Beskär", - "upload_form.undo": "Ångra", + "upload_form.undo": "Ta bort", "upload_progress.label": "Laddar upp...", "video.close": "Stäng video", "video.exit_fullscreen": "Stäng helskärm", "video.expand": "Expandera video", - "video.fullscreen": "Helskärm", + "video.fullscreen": "Fullskärm", "video.hide": "Dölj video", "video.mute": "Stäng av ljud", "video.pause": "Pause", diff --git a/config/locales/ar.yml b/config/locales/ar.yml index f187bd6..e2d057b 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -40,6 +40,7 @@ ar: following: يتابعون media: الوسائط moved_html: "%{name} إنتقلَ إلى %{new_profile_link} :" + network_hidden: إنّ المعطيات غير متوفرة nothing_here: لا يوجد أي شيء هنا ! people_followed_by: الأشخاص الذين يتبعهم %{name} people_who_follow: الأشخاص الذين يتبعون %{name} @@ -49,6 +50,7 @@ ar: reserved_username: إسم المستخدم محجوز roles: admin: المدير + bot: روبوت moderator: مُشرِف unfollow: إلغاء المتابعة admin: @@ -283,7 +285,7 @@ ar: create_and_resolve: الحل مع ملاحظة create_and_unresolve: إعادة فتح مع ملاحظة delete: حذف - placeholder: وصف الإجراءات التي تم اتخاذها أو أي تحديثات أخرى لهذا التقرير … + placeholder: قم بوصف الإجراءات التي تم اتخاذها أو أي تحديثات أخرى ذات علاقة … reopen: إعادة فتح التقرير report: 'التقرير #%{id}' report_contents: المحتويات @@ -414,7 +416,7 @@ ar: register_elsewhere: التسجيل على خادوم آخَر resend_confirmation: إعادة إرسال تعليمات التأكيد reset_password: إعادة تعيين كلمة المرور - security: الهوية + security: الأمان set_new_password: إدخال كلمة مرور جديدة authorize_follow: already_following: أنت تتابع بالفعل هذا الحساب @@ -637,7 +639,7 @@ ar: notifications: الإخطارات preferences: التفضيلات settings: الإعدادات - two_factor_authentication: إثبات الهويّة المزدوج + two_factor_authentication: المُصادقة بخُطوَتَيْن your_apps: تطبيقاتك statuses: attached: @@ -678,11 +680,11 @@ ar: default: "%b %d, %Y, %H:%M" two_factor_authentication: code_hint: قم بإدخال الرمز المُوَلّد عبر تطبيق المصادقة للتأكيد - description_html: في حال تفعيل المصادقة بخطوتين ، فتسجيل الدخول يتتطلب منك أن يكون بحوزتك هاتفك النقال قصد توليد الرمز الذي سيتم إدخاله. + description_html: في حال تفعيل المصادقة بخطوتين ، فتسجيل الدخول يتطلب منك أن يكون بحوزتك هاتفك النقال قصد توليد الرمز الذي سيتم إدخاله. disable: تعطيل enable: تفعيل enabled: نظام المصادقة بخطوتين مُفعَّل - enabled_success: تم تفعيل إثبات الهوية المزدوج بنجاح + enabled_success: تم تفعيل المصادقة بخطوتين بنجاح generate_recovery_codes: توليد رموز الإسترجاع instructions_html: "قم بمسح رمز الكيو آر عبر Google Authenticator أو أي تطبيق TOTP على جهازك. من الآن فصاعدا سوف يقوم ذاك التطبيق بتوليد رموز يجب عليك إدخالها عند تسجيل الدخول." manual_instructions: 'في حالة تعذّر مسح رمز الكيو آر أو طُلب منك إدخال يدوي، يُمْكِنك إدخال هذا النص السري على التطبيق :' @@ -705,6 +707,6 @@ ar: title: أهلاً بك، %{name} ! users: invalid_email: عنوان البريد الإلكتروني غير صالح - invalid_otp_token: الرمز الثنائي غير صالح + invalid_otp_token: رمز المصادقة بخطوتين غير صالح seamless_external_login: لقد قمت بتسجيل الدخول عبر خدمة خارجية، إنّ إعدادات الكلمة السرية و البريد الإلكتروني غير متوفرة. signed_in_as: 'تم تسجيل دخولك بصفة :' diff --git a/config/locales/ca.yml b/config/locales/ca.yml index b501e61..ea22ef8 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -283,7 +283,7 @@ ca: create_and_resolve: Resoldre amb nota create_and_unresolve: Reobrir amb nota delete: Esborrar - placeholder: Descriu les accions que s'han pres o qualsevol altra actualització d'aquest informe… + placeholder: Descriu les accions que s'han pres o qualsevol altra actualització relacionada… reopen: Reobrir informe report: 'Informe #%{id}' report_contents: Contingut @@ -619,7 +619,7 @@ ca: micro_messenger: MicroMessenger nokia: Nokia S40 Ovi Browser opera: Opera - otter: Altre + otter: Otter phantom_js: PhantomJS qq: QQ Browser safari: Safari @@ -776,7 +776,8 @@ ca: title: "%{instance} Condicions del servei i política de privadesa" themes: contrast: Alt contrast - default: Mastodont + default: Mastodon + mastodon-light: Mastodon (clar) time: formats: default: "%b %d, %Y, %H:%M" diff --git a/config/locales/de.yml b/config/locales/de.yml index 75c8b11..fbd81b6 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -40,6 +40,7 @@ de: following: Folgt media: Medien moved_html: "%{name} ist auf %{new_profile_link} umgezogen:" + network_hidden: Diese Informationen sind nicht verfügbar nothing_here: Hier gibt es nichts! people_followed_by: Profile, denen %{name} folgt people_who_follow: Profile, die %{name} folgen diff --git a/config/locales/doorkeeper.ar.yml b/config/locales/doorkeeper.ar.yml index 5586b8d..3b42029 100644 --- a/config/locales/doorkeeper.ar.yml +++ b/config/locales/doorkeeper.ar.yml @@ -114,5 +114,6 @@ ar: title: طلب تصريح مفتوح OAuth scopes: follow: متابعة و حجب و فك الحجب و إلغاء متابعة حسابات المستخدمين + push: تلقى إشعارات حسابك read: قراءة بيانات حسابك write: النشر نيابةً عنك diff --git a/config/locales/doorkeeper.eo.yml b/config/locales/doorkeeper.eo.yml index 59df528..9713c46 100644 --- a/config/locales/doorkeeper.eo.yml +++ b/config/locales/doorkeeper.eo.yml @@ -115,5 +115,6 @@ eo: title: OAuth-a rajtigo bezonata scopes: follow: sekvi, bloki, malbloki kaj malsekvi kontojn + push: ricevi puŝ-sciigojn por via konto read: legi la datumojn de via konto write: mesaĝi kiel vi diff --git a/config/locales/el.yml b/config/locales/el.yml index 823a6f5..77b794d 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -18,8 +18,8 @@ el: features: humane_approach_body: Μαθαίνοντας από τις αποτυχίες άλλων δικτύων, το Mastodon στοχεύει να κάνει σχεδιαστικά ηθικές επιλογές για να καταπολεμήσει την κακόβουλη χρήση των κοινωνικών δικτύων. humane_approach_title: Μια πιο ανθρώπινη προσέγγιση - not_a_product_body: Το Mastodon δεν είναι εμπορικό δίκτυο. Χωρίς διαφημίσεις, χωρίς εξόρυξη δεδομένων, χωρίς φραγμένες περιοχές. Δεν έχει κεντρική αρχή. - not_a_product_title: Είσαι άτομο, όχι προϊόν + not_a_product_body: Το Mastodon δεν είναι εμπορικό δίκτυο. Δεν έχει διαφημίσεις, δεν έχει εξόρυξη δεδομένων, δεν έχει φραγμένες περιοχές. Δεν υπάρχει κεντρικό σημείο ελέγχου. + not_a_product_title: Είσαι άνθρωπος, όχι προϊόν real_conversation_body: Με 500 χαρακτήρες στη διάθεσή σου και υποστήριξη για λεπτομερή έλεγχο και προειδοποιήσεις πολυμέσων, μπορείς να εκφραστείς με τον τρόπο που θέλεις. real_conversation_title: Φτιαγμένο για αληθινή συζήτηση within_reach_body: Οι πολλαπλές εφαρμογές για το iOS, το Android και τις υπόλοιπες πλατφόρμες, χάρη σε ένα φιλικό προς τους προγραμματιστές οικοσύστημα API, σου επιτρέπουν να κρατάς επαφή με τους φίλους και τις φίλες σου οπουδήποτε. @@ -40,6 +40,7 @@ el: following: Ακολουθεί media: Πολυμέσα moved_html: 'Ο/Η %{name} μετακόμισε στο %{new_profile_link}:' + network_hidden: Αυτή η πληροφορία δεν είναι διαθέσιμη nothing_here: Δεν υπάρχει τίποτα εδώ! people_followed_by: Χρήστες που ακολουθεί ο/η %{name} people_who_follow: Χρήστες που ακολουθούν τον/την %{name} @@ -54,7 +55,7 @@ el: unfollow: Διακοπή παρακολούθησης admin: account_moderation_notes: - create: Δημιουργία + create: Άφησε σημείωση created_msg: Επιτυχής δημιουργία σημειώματος μεσολάβησης! delete: Διαγραφή destroyed_msg: Επιτυχής καταστροφή σημειώματος μεσολάβησης! @@ -129,7 +130,7 @@ el: role: Δικαιώματα roles: admin: Διαχειριστής - moderator: Μεσολαβητής + moderator: Συντονιστής staff: Προσωπικό user: Χρήστης salmon_url: URL Salmon @@ -137,7 +138,7 @@ el: shared_inbox_url: URL κοινόχρηστων εισερχομένων show: created_reports: Αναφορές από αυτόν το λογαριασμό - report: ανάφερε + report: κατάγγειλε targeted_reports: Αναφορές για αυτόν το λογαριασμό silence: Αποσιώπησε statuses: Καταστάσεις @@ -151,7 +152,7 @@ el: web: Διαδίκτυο action_logs: actions: - assigned_to_self_report: Ο/Η %{name} ανάθεσε την αναφορά %{target} στον εαυτό του/της + assigned_to_self_report: Ο/Η %{name} ανάθεσε την καταγγελία %{target} στον εαυτό του/της change_email_user: Ο/Η %{name} άλλαξε τη διεύθυνση email του χρήστη %{target} confirm_user: Ο/Η %{name} επιβεβαίωσε τη διεύθυνση email του χρήστη %{target} create_custom_emoji: Ο/Η %{name} ανέβασε νέο emoji %{target} @@ -169,12 +170,12 @@ el: memorialize_account: Ο/Η %{name} μετέτρεψε το λογαριασμό του/της %{target} σε σελίδα νεκρολογίας promote_user: Ο/Η %{name} προβίβασε το χρήστη %{target} remove_avatar_user: Ο/Η %{name} αφαίρεσε το αβατάρ του/της %{target} - reopen_report: Ο/Η %{name} ξανάνοιξε την αναφορά %{target} + reopen_report: Ο/Η %{name} ξανάνοιξε την καταγγελία %{target} reset_password_user: Ο/Η %{name} επανέφερε το συνθηματικό του χρήστη %{target} - resolve_report: Ο/Η %{name} επέλυσε την αναφορά %{target} + resolve_report: Ο/Η %{name} επέλυσε την καταγγελία %{target} silence_account: Ο/Η %{name} αποσιώπησε το λογαριασμό του/της %{target} suspend_account: Ο/Η %{name} έπαυσε το λογαριασμό του/της %{target} - unassigned_report: Ο/Η %{name} αποδέσμευσε την αναφορά %{target} + unassigned_report: Ο/Η %{name} αποδέσμευσε την καταγγελία %{target} unsilence_account: Ο/Η %{name} ήρε την αποσιώπηση του λογαριασμού του/της %{target} unsuspend_account: Ο/Η %{name} ήρε την παύση του λογαριασμού του χρήστη %{target} update_custom_emoji: Ο/Η %{name} ενημέρωσε το emoji %{target} @@ -216,6 +217,83 @@ el: severity: noop: Κανένα silence: Σίγαση + suspend: Αναστολή + title: Αποκλεισμός νέου τομέα + reject_media: Απόρριψη πολυμέσων + severities: + noop: Κανένα + silence: Αποσιώπηση + suspend: Αναστολή + severity: Αυστηρότητα + show: + affected_accounts: + one: Επηρεάζεται ένας λογαριασμός στη βάση δεδομένων + other: Επηρεάζονται %{count} λογαριασμοί στη βάση δεδομένων + retroactive: + silence: Αναίρεση αποσιώπησης όλων των λογαριασμός του τομέα + suspend: Αναίρεση αναστολής όλων των λογαριασμών του τομέα + title: Αναίρεση αποκλεισμού για τον τομέα %{domain} + undo: Αναίρεση + title: Αποκλεισμένοι τομείς + undo: Αναίρεση + email_domain_blocks: + add_new: Πρόσθεση νέου + created_msg: Επιτυχής πρόσθεση email τομέα σε μαύρη λίστα + delete: Διαγραφή + destroyed_msg: Επιτυχής διαγραφή email τομέα από τη μαύρη λίστα + domain: Τομέας + new: + create: Πρόσθεση τομέα + title: Νέα εγγραφή email στη μαύρη λίστα + title: Μαύρη λίστα email + instances: + account_count: Γνωστοί λογαριασμοί + domain_name: Τομέας + reset: Επαναφορά + search: Αναζήτηση + title: Γνωστοί κόμβοι + invites: + filter: + all: Όλες + available: Διαθέσιμες + expired: Ληγμένες + title: Φίλτρο + title: Προσκλήσεις + report_notes: + created_msg: Επιτυχής δημιουργία σημείωσης καταγγελίας! + destroyed_msg: Επιτυχής διαγραφή σημείωσης καταγγελίας! + reports: + account: + note: σημείωση + report: καταγγελία + action_taken_by: Ενέργεια από τον/την + are_you_sure: Σίγουρα; + assign_to_self: Ανάθεση σε μένα + assigned: Αρμόδιος συντονιστής + comment: + none: Κανένα + created_at: Αναφέρθηκε + id: ID + mark_as_resolved: Σημειωμένο ως επιλυμένο + mark_as_unresolved: Σημειωμένο ως ανεπίλυτο + notes: + create: Πρόσθεσε σημείωση + create_and_resolve: Επίλυσε με σημείωση + create_and_unresolve: Ξανάνοιξε με σημείωση + delete: Διέγραψε + placeholder: Περιέγραψε τις ενέργειες που έγιναν, ή οποιαδήποτε άλλη ενημέρωση... + reopen: Ξανάνοιξε την καταγγελία + report: 'Καταγγελία #%{id}' + report_contents: Περιεχόμενα + reported_account: Αναφερόμενος λογαριασμός + reported_by: Αναφέρθηκε από + resolved: Επιλύθηκε + resolved_msg: Η καταγγελία επιλύθηκε επιτυχώς! + silence_account: Αποσιώπηση λογαριασμού + status: Κατάσταση + suspend_account: Ανέστειλε λογαριασμό + target: Στόχος + title: Αναφορές settings: hero: desc_html: Εμφανίζεται στην μπροστινή σελίδα. Συνίσταται τουλάχιστον 600x100px. Όταν λείπει, χρησιμοποιείται η μικρογραφία του κόμβου @@ -235,7 +313,7 @@ el: title: Προεπισκόπιση ροής admin_mailer: new_report: - subject: Νέα αναφορά για %{instance} (#%{id}) + subject: Νέα καταγγελία για %{instance} (#%{id}) auth: agreement_html: Με την εγγραφή σου, συμφωνείς να ακολουθείς τους κανόνες αυτού του κόμβου και τους όρους χρήσης του. deletes: @@ -250,5 +328,6 @@ el: welcome: final_step: 'Ξεκίνα τις δημοσιεύσεις! Ακόμα και χωρίς ακόλουθους τα δημόσια μηνύματά σου μπορεί να τα δουν άλλοι, για παράδειγμα στην τοπική ροή και στις ετικέτες. Ίσως να θέλεις να κάνεις μια εισαγωγή του εαυτού σου με την ετικέτα #introductions.' full_handle_hint: Αυτό θα εδώ θα πεις στους φίλους σου για να σου μιλήσουν ή να σε ακολουθήσουν από άλλο κόμβο. - tip_federated_timeline: Η συνδυασμένη ροή είναι μια όψη πραγματικού χρόνου στο δίκτυο του Mastodon. Παρόλα αυτά, περιλαμβάνει μόνο όσους ακολουθούν οι γείτονές σου, άρα δεν είναι πλήρης. + tip_federated_timeline: Η ομοσπονδιακή ροή είναι μια όψη πραγματικού χρόνου στο δίκτυο του Mastodon. Παρόλα αυτά, περιλαμβάνει μόνο όσους ακολουθούν οι γείτονές σου, άρα δεν είναι πλήρης. + tip_following: Ακολουθείς το διαχειριστή του διακομιστή σου αυτόματα. Για να βρεις περισσότερους ενδιαφέροντες ανθρώπους, έλεγξε την τοπική και την ομοσπονδιακή ροή. tip_local_timeline: Η τοπική ροή είναι η όψη πραγματικού χρόνου των ανθρώπων στον κόμβο %{instance}. Αυτοί είναι οι άμεσοι γείτονές σου! diff --git a/config/locales/eo.yml b/config/locales/eo.yml index 8920709..f1a9ff7 100644 --- a/config/locales/eo.yml +++ b/config/locales/eo.yml @@ -40,6 +40,7 @@ eo: following: Sekvatoj media: Aŭdovidaĵoj moved_html: "%{name} moviĝis al %{new_profile_link}:" + network_hidden: Tiu informo ne estas disponebla nothing_here: Estas nenio ĉi tie! people_followed_by: Sekvatoj de %{name} people_who_follow: Sekvantoj de %{name} @@ -670,6 +671,7 @@ eo: video: one: "%{count} video" other: "%{count} videoj" + boosted_from_html: Diskonigita de %{acct_link} content_warning: 'Enhava averto: %{warning}' disallowed_hashtags: one: 'enhavas malpermesitan kradvorton: %{tags}' @@ -700,6 +702,7 @@ eo: themes: contrast: Forta kontrasto default: Mastodon + mastodon-light: Mastodon (hela) time: formats: default: "%Y-%m-%d %H:%M" diff --git a/config/locales/eu.yml b/config/locales/eu.yml index 0967ef4..fc8916a 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -1 +1,11 @@ -{} +--- +eu: + about: + about_this: Honi buruz + administered_by: 'Administratzailea(k):' + contact: Kontaktua + contact_missing: Ezarri gabe + contact_unavailable: E/E + description_headline: Zer da %{domain}? + domain_count_after: beste instantziak + domain_count_before: 'Hona konektatuta:' diff --git a/config/locales/fa.yml b/config/locales/fa.yml index b73f240..e320092 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -40,6 +40,7 @@ fa: following: پی می‌گیرد media: عکس و ویدیو moved_html: "%{name} حساب خود را به %{new_profile_link} منتقل کرده است:" + network_hidden: این اطلاعات در دسترس نیست nothing_here: این‌جا چیزی نیست! people_followed_by: کسانی که %{name} پی می‌گیرد people_who_follow: کسانی که %{name} را پی می‌گیرند @@ -49,6 +50,7 @@ fa: reserved_username: این نام کاربری در دسترس نیست roles: admin: مدیر + bot: ربات moderator: ناظم unfollow: پایان پیگیری admin: @@ -281,7 +283,7 @@ fa: create_and_resolve: حل کردن با یادداشت create_and_unresolve: دوباره گشودن با یادداشت delete: حذف - placeholder: کارهایی را که در این باره انجام شده، یا هر به‌روزرسانی دیگری را بنویسید… + placeholder: کارهایی را که در این باره انجام شده، یا هر به‌روزرسانی دیگری را بنویسید... reopen: دوباره به جریان بیندازید report: 'گزارش #%{id}' report_contents: محتوا @@ -359,9 +361,9 @@ fa: failed_to_execute: اجرا نشد media: title: رسانه - no_media: بدون رسانه + no_media: بدون عکس یا ویدیو title: نوشته‌های حساب - with_media: دارای رسانه + with_media: دارای عکس یا ویدیو subscriptions: callback_url: نشانی Callback confirmed: تأییدشده @@ -463,7 +465,7 @@ fa: archive_takeout: date: تاریخ download: بایگانی خود را باربگیرید - hint_html: شما می‌توانید بایگانی بوق‌ها و پرونده‌های بارگذاری‌شدهٔ خود را درخواست کنید. داده‌های برون‌بری‌شده در قالب ActivityPub خواهند بود و همهٔ نرم‌افزارهای سازگار خواهند توانست آن را بخوانند. شما هر ۷ روز می‌توانید یک بار برای چنین بایگانی‌ای درخواست دهید. + hint_html: شما می‌توانید بایگانی نوشته‌ها و پرونده‌های بارگذاری‌شدهٔ خود را درخواست کنید. داده‌های برون‌بری‌شده در قالب ActivityPub خواهند بود و همهٔ نرم‌افزارهای سازگار خواهند توانست آن را بخوانند. شما هر ۷ روز می‌توانید یک بار برای چنین بایگانی‌ای درخواست دهید. in_progress: در حال ساختن بایگانی شما... request: درخواست بایگانی داده‌هایتان size: اندازه @@ -548,7 +550,7 @@ fa: subject: one: "یک اعلان تازه از زمان آخرین بازدید شما \U0001F418" other: "%{count} اعلان تازه از زمان آخرین بازدید شما \U0001F418" - title: در مدتی که نبودید… + title: در مدتی که نبودید... favourite: body: "%{name} این نوشتهٔ شما را پسندید:" subject: "%{name} نوشتهٔ شما را پسندید" @@ -589,9 +591,9 @@ fa: prev: قبلی truncate: "…" preferences: - languages: زبان‌ها - other: سایر - publishing: انتشار + languages: تنظیمات زبان + other: سایر تنظیمات + publishing: تنظیمات انتشار مطالب web: وب remote_follow: acct: نشانی حساب username@domain خود را این‌جا بنویسید @@ -667,6 +669,7 @@ fa: video: one: "%{count} ویدیو" other: "%{count} ویدیو" + boosted_from_html: بازبوقیده از طرف %{acct_link} content_warning: 'هشدا محتوا: %{warning}' disallowed_hashtags: one: 'دارای هشتگ غیرمجاز: %{tags}' @@ -682,7 +685,7 @@ fa: title: '%{name}: "%{quote}"' visibilities: private: خصوصی - private_long: نمایش تنها به پیگیران + private_long: تنها پیگیران شما می‌بینند public: عمومی public_long: همه می‌توانند ببینند unlisted: فهرست‌نشده @@ -697,6 +700,7 @@ fa: themes: contrast: کنتراست بالا default: ماستدون + mastodon-light: ماستدون (روشن) time: formats: default: "%d %b %Y, %H:%M" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index cd323b6..b3914ea 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -72,7 +72,7 @@ fr: title: Modifier le courriel pour %{username} confirm: Confirmer confirmed: Confirmé - confirming: Confirmant + confirming: Confirmation demote: Rétrograder disable: Désactiver disable_two_factor_authentication: Désactiver l’authentification à deux facteurs @@ -283,7 +283,7 @@ fr: create_and_resolve: Résoudre avec une note create_and_unresolve: Ré-ouvrir avec une note delete: Effacer - placeholder: Décrivez quelles actions ont été prises, ou toute autre mise à jour de ce signalement… + placeholder: Décrivez quelles actions ont été prises, ou toute autre mise à jour… reopen: Ré-ouvrir le signalement report: 'Signalement #%{id}' report_contents: Contenu @@ -550,7 +550,7 @@ fr: subject: one: "Une nouvelle notification depuis votre dernière visite \U0001F418" other: "%{count} nouvelles notifications depuis votre dernière visite \U0001F418" - title: Pendant votre absence… + title: Pendant votre absence... favourite: body: "%{name} a ajouté votre post à ses favoris :" subject: "%{name} a ajouté votre post à ses favoris" @@ -619,7 +619,7 @@ fr: micro_messenger: MicroMessenger nokia: Nokia S40 Ovi Browser opera: Opera - otter: Loutre + otter: Otter phantom_js: PhantomJS qq: QQ Browser safari: Safari diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 87c10de..0c4d5cb 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -283,7 +283,7 @@ gl: create_and_resolve: Resolver con nota create_and_unresolve: Voltar a abrir con nota delete: Eliminar - placeholder: Describir qué decisións foron tomadas, ou calquer actualización a este informe… + placeholder: Describe qué medidas foron tomadas, ou calquer outra información relacionada... reopen: Voltar a abrir o informe report: 'Informe #%{id}' report_contents: Contidos @@ -550,7 +550,7 @@ gl: subject: one: "1 nova notificación desde a súa última visita \U0001F418" other: "%{count} novas notificacións desde a súa última visita \U0001F418" - title: Na súa ausencia… + title: Na súa ausencia... favourite: body: 'O seu estado foi marcado favorito por %{name}:' subject: "%{name} marcou favorito o seu estado" @@ -777,6 +777,7 @@ gl: themes: contrast: Alto contraste default: Mastodon + mastodon-light: Mastodon (claro) time: formats: default: "%d %b, %Y, %H:%M" diff --git a/config/locales/it.yml b/config/locales/it.yml index db754b2..5608c05 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -37,9 +37,10 @@ it: accounts: follow: Segui followers: Seguaci - following: Seguiti + following: Segui media: Media moved_html: "%{name} è stato spostato su %{new_profile_link}:" + network_hidden: Questa informazione non e' disponibile nothing_here: Qui non c'è nulla! people_followed_by: Persone seguite da %{name} people_who_follow: Persone che seguono %{name} @@ -50,7 +51,7 @@ it: roles: admin: Amministratore bot: Bot - moderator: Mod + moderator: Moderatore unfollow: Non seguire più admin: account_moderation_notes: @@ -86,7 +87,7 @@ it: feed_url: URL Feed followers: Follower followers_url: URL follower - follows: Follows + follows: Segue inbox_url: URL inbox ip: IP location: @@ -116,6 +117,7 @@ it: promote: Promuovi protocol: Protocollo public: Pubblico + push_subscription_expires: Sottoscrizione PuSH scaduta redownload: Aggiorna avatar remove_avatar: Rimuovi avatar resend_confirmation: @@ -129,11 +131,14 @@ it: roles: admin: Amministratore moderator: Moderatore - staff: Staff + staff: Personale user: Utente + salmon_url: URL Salmone search: Cerca + shared_inbox_url: URL Inbox Condiviso show: created_reports: Rapporti creati da questo account + report: segnala targeted_reports: Rapporti che riguardano questo account silence: Silenzia statuses: Stati @@ -338,7 +343,7 @@ it: created: Applicazione creata con successo destroyed: Applicazione eliminata con successo invalid_url: L'URL fornito non è valido - regenerate_token: Rigenera token di accesso + regenerate_token: Rigenera il token di accesso token_regenerated: Token di accesso rigenerato warning: Fa' molta attenzione con questi dati. Non fornirli mai a nessun altro! auth: @@ -350,7 +355,7 @@ it: didnt_get_confirmation: Non hai ricevuto le istruzioni di conferma? forgot_password: Hai dimenticato la tua password? login: Entra - logout: Logout + logout: Sloggati migrate_account: Sposta ad un account differente migrate_account_html: Se vuoi che questo account sia reindirizzato a uno diverso, puoi configurarlo qui. or: o @@ -451,7 +456,7 @@ it: max_uses: other: "%{count} utilizzi" max_uses_prompt: Nessun limite - prompt: Genera dei link e forniscili ad altri per concedere l'accesso a questa istanza + prompt: Genera e condividi dei link ad altri per garantire l'accesso a questa istanza table: expires_at: Scade uses: Utilizzi @@ -609,7 +614,7 @@ it: recovery_codes_regenerated: I codici di recupero sono stati rigenerati recovery_instructions_html: Se perdi il telefono, puoi usare uno dei codici di recupero qui sotto per riottenere l'accesso al tuo account. Conserva i codici di recupero in un posto sicuro. Ad esempio puoi stamparli e conservarli insieme ad altri documenti importanti. setup: Configura - wrong_code: Il codice inserito non è corretto! Assicurati che l'orario del server e l'orario del telefono siano corretti. + wrong_code: Il codice inserito non è corretto! Assicurati che l'orario del server e l'orario del dispotivo siano corretti. user_mailer: backup_ready: explanation: Hai richiesto un backup completo del tuo account Mastodon. È pronto per essere scaricato! diff --git a/config/locales/ja.yml b/config/locales/ja.yml index f1f43ef..7fdd531 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -283,7 +283,7 @@ ja: create_and_resolve: 書き込み、解決済みにする create_and_unresolve: 書き込み、未解決として開く delete: 削除 - placeholder: このレポートに取られた措置や、その他の更新を記述してください… + placeholder: どのような措置が取られたか、または関連する更新を記述してください… reopen: 再び開く report: レポート#%{id} report_contents: 内容 @@ -393,7 +393,7 @@ ja: warning: このデータは気をつけて取り扱ってください。他の人と共有しないでください! your_token: アクセストークン auth: - agreement_html: 登録すると インスタンスのルール利用規約 に従うことに同意したことになります。 + agreement_html: 登録すると インスタンスのルールプライバシーポリシー に従うことに同意したことになります。 change_password: パスワード confirm_email: メールアドレスの確認 delete_account: アカウントの削除 @@ -478,14 +478,14 @@ ja: domain: ドメイン explanation_html: あなたの投稿のプライバシーを確保したい場合、誰があなたをフォローしているのかを把握している必要があります。 プライベート投稿は、あなたのフォロワーがいる全てのインスタンスに配信されます。 フォロワーのインスタンスの管理者やソフトウェアがあなたのプライバシーを尊重してくれるかどうか怪しい場合は、そのフォロワーを削除した方がよいかもしれません。 followers_count: フォロワー数 - lock_link: 非公開アカウントにする + lock_link: 承認制アカウントにする purge: フォロワーから削除する success: one: 1個のドメインからソフトブロックするフォロワーを処理中... other: "%{count} 個のドメインからソフトブロックするフォロワーを処理中..." true_privacy_html: "プライバシーの保護はエンドツーエンドの暗号化でのみ実現可能であることに留意ください。" - unlocked_warning_html: 誰でもあなたをフォローすることができ、あなたのプライベート投稿をすぐに見ることができます。フォローする人を限定したい場合は%{lock_link}に設定してください。 - unlocked_warning_title: このアカウントは非公開アカウントに設定されていません + unlocked_warning_html: 誰でもあなたをフォローすることができ、フォロワー限定の投稿をすぐに見ることができます。フォローする人を限定したい場合は%{lock_link}に設定してください。 + unlocked_warning_title: このアカウントは承認制アカウントに設定されていません generic: changes_saved_msg: 正常に変更されました! powered_by: powered by %{link} @@ -684,7 +684,7 @@ ja: show_more: もっと見る title: '%{name}: "%{quote}"' visibilities: - private: 非公開 + private: フォロワー限定 private_long: フォロワーにのみ表示されます public: 公開 public_long: 誰でも見ることができ、かつ公開タイムラインに表示されます @@ -744,7 +744,7 @@ ja:

クッキーを使用していますか?

-

はい。クッキーは (あなたが許可した場合に) WebサイトやサービスがWebブラウザーを介してコンピューターに保存する小さなファイルです。使用することで Web サイトがブラウザーを識別し、登録済みのアカウントがある場合関連付けます。

+

はい。クッキーは (あなたが許可した場合に) WebサイトやサービスがWebブラウザーを介してコンピューターに保存する小さなファイルです。使用することでWebサイトがブラウザーを識別し、登録済みのアカウントがある場合関連付けます。

私たちはクッキーを将来の訪問のために設定を保存し呼び出す用途に使用します。

@@ -760,9 +760,13 @@ ja:
-

児童オンラインプライバシー保護法の遵守

+

児童によるサイト利用について

-

当サイト・製品・サービスは13歳以上の人を対象としています。サーバーが米国にあり、あなたが13歳未満の場合、COPPA (Children's Online Privacy Protection Act - 児童オンラインプライバシー保護法) により当サイトを使用できません。

+

サーバーがEUまたはEEA圏内にある場合: 当サイト・製品・サービスは16歳以上の人を対象としています。あなたが16歳未満の場合、GDPR (General Data Protection Regulation - EU一般データ保護規則) により当サイトを使用できません。

+ +

サーバーが米国にある場合: 当サイト・製品・サービスは13歳以上の人を対象としています。あなたが13歳未満の場合、COPPA (Children's Online Privacy Protection Act - 児童オンラインプライバシー保護法) により当サイトを使用できません。

+ +

サーバーが別の管轄区域にある場合、法的要件は異なることがあります。


@@ -803,7 +807,7 @@ ja: title: アーカイブの取り出し welcome: edit_profile_action: プロフィールを設定 - edit_profile_step: アバター画像やヘッダー画像をアップロードしたり、表示名やその他プロフィールを変更しカスタマイズすることができます。新しいフォロワーからのフォローを許可する前に検討したい場合、アカウントを非公開にすることができます。 + edit_profile_step: アバター画像やヘッダー画像をアップロードしたり、表示名やその他プロフィールを変更しカスタマイズすることができます。新しいフォロワーからのフォローを許可する前に検討したい場合、アカウントを承認制にすることができます。 explanation: 始めるにあたってのアドバイスです final_action: 始めましょう final_step: 'さあ始めましょう! たとえフォロワーがいなくても、あなたの公開した投稿はローカルタイムラインやハッシュタグなどで誰かの目に止まるかもしれません。自己紹介をしたい時は #introductions ハッシュタグを使うといいかもしれません。' diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 75e81b4..ca33fb9 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -283,7 +283,7 @@ nl: create_and_resolve: Oplossen met opmerking create_and_unresolve: Heropenen met opmerking delete: Verwijderen - placeholder: Beschrijf welke acties zijn ondernomen of andere opmerkingen over deze gerapporteerde toot… + placeholder: Beschrijf welke acties zijn ondernomen of andere gerelateerde opmerkingen… reopen: Gerapporteerde toot heropenen report: 'Gerapporteerde toot #%{id}' report_contents: Inhoud @@ -550,7 +550,7 @@ nl: subject: one: "1 nieuwe melding sinds jouw laatste bezoek \U0001F418" other: "%{count} nieuwe meldingen sinds jouw laatste bezoek \U0001F418" - title: Tijdens jouw afwezigheid… + title: Tijdens jouw afwezigheid... favourite: body: 'Jouw toot werd door %{name} als favoriet gemarkeerd:' subject: "%{name} markeerde jouw toot als favoriet" @@ -760,9 +760,13 @@ nl:
-

Children's Online Privacy Protection Act Compliance

+

Site usage by children

-

Our site, products and services are all directed to people who are at least 13 years old. If this server is in the USA, and you are under the age of 13, per the requirements of COPPA (Children's Online Privacy Protection Act) do not use this site.

+

If this server is in the EU or the EEA: Our site, products and services are all directed to people who are at least 16 years old. If you are under the age of 16, per the requirements of the GDPR (General Data Protection Regulation) do not use this site.

+ +

If this server is in the USA: Our site, products and services are all directed to people who are at least 13 years old. If you are under the age of 13, per the requirements of COPPA (Children's Online Privacy Protection Act) do not use this site.

+ +

Law requirements can be different if this server is in another jurisdiction.


@@ -777,6 +781,7 @@ nl: themes: contrast: Hoog contrast default: Mastodon + mastodon-light: Mastodon (licht) time: formats: default: "%d %B %Y om %H:%M" diff --git a/config/locales/oc.yml b/config/locales/oc.yml index 1c596a6..d15e7e1 100644 --- a/config/locales/oc.yml +++ b/config/locales/oc.yml @@ -50,11 +50,12 @@ oc: reserved_username: Aqueste nom d’utilizaire es reservat roles: admin: Admin + bot: Robòt moderator: Moderador unfollow: Quitar de sègre admin: account_moderation_notes: - create: Crear + create: Crear una nòta created_msg: Nòta de moderacion ben creada ! delete: Suprimir destroyed_msg: Nòta de moderacion ben suprimida ! @@ -282,10 +283,11 @@ oc: create: Ajustar una nòta create_and_resolve: Resòlvre amb una nòta create_and_unresolve: Tornar dobrir amb una nòta - placeholder: Explicatz las accions que son estadas menadas o çò qu’es estat fach per aqueste rapòrt… + delete: Escafar + placeholder: Explicatz las accions que son estadas menadas o quicòm de ligat al senhalament… reopen: Tornar dobrir lo rapòrt report: 'senhalament #%{id}' - report_contents: Contenguts + report_contents: Contengut reported_account: Compte senhalat reported_by: Senhalat per resolved: Resolgut @@ -374,6 +376,7 @@ oc: admin_mailer: new_report: body: "%{reporter} a senhalat %{target}" + body_remote: Qualqu’un de %{domain} senhalèt %{target} subject: Novèl senhalament per %{instance} (#%{id}) application_mailer: notification_preferences: Cambiar las preferéncias de corrièl @@ -533,7 +536,7 @@ oc: '429': Lo servidor mòla (subrecargada) '500': content: Un quicomet a pas foncionat coma caliá. - title: Aquesta pagina es incorrècta + title: Aquesta pagina es pas corrècta noscript_html: Per utilizar l’aplicacion web de Mastodon, mercés d’activar JavaScript. O podètz utilizar una aplicacion per vòstra plataforma coma alernativa. exports: archive_takeout: @@ -550,7 +553,7 @@ oc: storage: Mèdias gardats followers: domain: Domeni - explanation_html: Se volètz vos assegurar de la confidencialitat de vòstres estatuts, vos cal saber qual sèc vòstre compte. Vòstres estatuts privats son enviats a totas las instàncias qu’an de monde que vos sègon.. Benlèu que volètz repassar vòstra lista e tirar los seguidors s’avètz de dobtes tocant las politica de confidencialitat de lor instàncias. + explanation_html: Se volètz vos assegurar de la confidencialitat de vòstres estatuts, vos cal saber qual sèc vòstre compte. Vòstres estatuts privats son enviats a totas las instàncias qu’an de monde que vos sègon.. Benlèu que volètz repassar vòstra lista e tirar los seguidors s’avètz de dobtes tocant las politicas de confidencialitat dels gestionaris de lor instància o sul logicial qu’utilizan. followers_count: Nombre de seguidors lock_link: Clavar vòstre compte purge: Tirar dels seguidors @@ -773,6 +776,8 @@ oc: title: Condicions d’utilizacion e politica de confidencialitat de %{instance} themes: contrast: Fòrt contrast + default: Mastodon + mastodon-light: Mastodon (clar) time: formats: default: Lo %d %b de %Y a %Ho%M diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 0946ecd..a0d18e8 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -283,7 +283,7 @@ pt-BR: create_and_resolve: Resolver com nota create_and_unresolve: Reabrir com nota delete: Excluir - placeholder: Descreva que ações foram tomadas, ou quaisquer atualizações sobre esta denúncia… + placeholder: Descreva que ações foram tomadas, ou quaisquer outras atualizações relacionadas… reopen: Reabrir denúncia report: 'Denúncia #%{id}' report_contents: Conteúdos @@ -550,7 +550,7 @@ pt-BR: subject: one: "Uma nova notificação desde o seu último acesso \U0001F418" other: "%{count} novas notificações desde o seu último acesso \U0001F418" - title: Enquanto você estava ausente… + title: Enquanto você estava ausente... favourite: body: 'Sua postagem foi favoritada por %{name}:' subject: "%{name} favoritou a sua postagem" @@ -701,7 +701,7 @@ pt-BR:
  • Informação básica de conta: Se você se registrar nesse servidor, podemos pedir que você utilize um nome de usuário, um e-mail e uma senha. Você também pode adicionar informações extras como um nome de exibição e biografia; enviar uma imagem de perfil e imagem de cabeçalho. O nome de usuário, nome de exibição, biografia, imagem de perfil e imagem de cabeçalho são sempre listadas publicamente.
  • -
  • Posts, informação de seguidores e outras informações públicas: A lista de pessoas que você segue é listada publicamente, o mesmo é verdade para quem te segue. Quando você envia uma mensagem, a data e o horário são armazenados, assim como a aplicação que você usou para enviar a mensagem. Mensagens podem conter mídias anexadas, como imagens e vídeos. Posts públicos e não-listados estão disponíveis publicamente. Quando você destaca um post no seu perfil, isso também é uma informação pública. Seus posts são entregues aos seus seguidores e em alguns casos isso significa que eles são enviados para servidores diferentes e cópias são armazenadas nesses servidores. Quando você remove posts, essa informação também é entregue aos seus seguidores. O ato de compartilhar ou favoritar um outro post é sempre público.
  • +
  • Posts, informação de seguidores e outras informações públicas: A lista de pessoas que você segue é listada publicamente, o mesmo é verdade para quem te segue. Quando você envia uma mensagem, a data e o horário são armazenados, assim como a aplicação que você usou para enviar a mensagem. Mensagens podem conter mídias anexadas, como imagens e vídeos. Posts públicos e não-listados estão disponíveis publicamente. Quando você destaca um post no seu perfil, isso também é uma informação pública. Seus posts são entregues aos seus seguidores e em alguns casos isso significa que eles são enviados para servidores diferentes e cópias são armazenadas nesses servidores. Quando você remove posts, essa informação também é entregue aos seus seguidores. O ato de compartilhar ou favoritar um outro post é sempre público.
  • Mensagens diretas e posts somente para seguidores: Todos os posts são armazenados e processados no servidor. Posts somente para seguidores são entregues aos seus seguidores e usuários que são mencionados neles; mensagens diretas são entregues somente aos usuários mencionados nelas. Em alguns casos isso significa que as mensagens são entregues para servidores diferentes e cópias são armazenadas nesses servidores. Nós fazemos esforços substanciais para limitar o acesso dessas mensagens somente para as pessoas autorizadas, mas outros servidores podem não fazer o mesmo. É importante portanto revisar os servidores à qual seus seguidores pertencem. Você pode usar uma opção para aprovar ou rejeitar novos seguidores manualmente nas configurações. Por favor tenha em mente que os operadores do servidor e de qualquer servidores do destinatário podem ver tais mensagens, e que os destinatários podem fazer capturas de tela, copiar ou de outra maneira compartilhar as mensagens. Não compartilhe informação confidencial pelo Mastodon.
  • IPs e outros metadados: Quando você faz se autentica, nos guardamos o endereço de IP que você usou ao se autenticar e o nome do seu navegador da internet. Todas as sessões autenticadas são disponíveis para serem analisadas e revogadas nas configurações. O último endereço de IP usado é guardado por até 12 meses. Nós também podemos reter históricos do servidor que incluem o endereço de IP de todas as requisições ao nosso servidor.
@@ -776,6 +776,7 @@ pt-BR: themes: contrast: Alto contraste default: Mastodon + mastodon-light: Mastodon (claro) time: formats: default: "%b %d, %Y, %H:%M" diff --git a/config/locales/simple_form.ar.yml b/config/locales/simple_form.ar.yml index 9e3e1e4..3d55571 100644 --- a/config/locales/simple_form.ar.yml +++ b/config/locales/simple_form.ar.yml @@ -9,6 +9,7 @@ ar: fields: يُمكنك عرض 4 عناصر على شكل جدول في ملفك الشخصي header: ملف PNG أو GIF أو JPG. حجمه على أقصى تصدير 2MB. سيتم تصغيره إلى 700x335px locked: يتطلب منك الموافقة يدويا على طلبات المتابعة + setting_hide_network: الحسابات التي تُتابعها و التي تُتابِعك على حد سواء لن تُعرَض على صفحتك الشخصية setting_noindex: ذلك يؤثر على حالة ملفك الشخصي و صفحاتك setting_theme: ذلك يؤثر على الشكل الذي سيبدو عليه ماستدون عندما تقوم بالدخول مِن أي جهاز. imports: @@ -48,6 +49,7 @@ ar: setting_default_sensitive: إعتبر الوسائط دائما كمحتوى حساس setting_delete_modal: إظهار مربع حوار للتأكيد قبل حذف أي تبويق setting_display_sensitive_media: دائمًا إظهار الوسائط الحساسة + setting_hide_network: إخفِ شبكتك setting_noindex: عدم السماح لمحركات البحث بفهرسة ملفك الشخصي setting_reduce_motion: تخفيض عدد الصور في الوسائط المتحركة setting_system_font_ui: إستخدم الخطوط الإفتراضية للنظام diff --git a/config/locales/simple_form.eo.yml b/config/locales/simple_form.eo.yml index 9ff9eba..4027c1b 100644 --- a/config/locales/simple_form.eo.yml +++ b/config/locales/simple_form.eo.yml @@ -4,7 +4,7 @@ eo: hints: defaults: avatar: Formato PNG, GIF aŭ JPG. Ĝis 2MB. Estos malgrandigita al 400x400px - bot: Atentigas homojn, ke la konto ne reprezentas homon + bot: Tiu konto ĉefe faras aŭtomatajn agojn, kaj povas esti ne kontrolata digest: Sendita nur post longa tempo de neaktiveco, kaj nur se vi ricevis personan mesaĝon en via foresto display_name: one: 1 signo restas @@ -15,6 +15,7 @@ eo: note: one: 1 signo restas other: %{count} signoj restas + setting_hide_network: Tiuj, kiujn vi sekvas, kaj tiuj, kiuj sekvas vin ne estos videblaj en via profilo setting_noindex: Influas vian publikan profilon kaj mesaĝajn paĝojn setting_theme: Influas kiel Mastodon aspektas post ensaluto de ajna aparato. imports: @@ -54,6 +55,7 @@ eo: setting_default_sensitive: Ĉiam marki aŭdovidaĵojn tiklaj setting_delete_modal: Montri fenestron por konfirmi antaŭ ol forigi mesaĝon setting_display_sensitive_media: Ĉiam montri aŭdovidaĵojn markitajn tiklaj + setting_hide_network: Kaŝi viajn sekvantojn kaj sekvatojn setting_noindex: Ellistiĝi de retserĉila indeksado setting_reduce_motion: Malrapidigi animaciojn setting_system_font_ui: Uzi la dekomencan tiparon de la sistemo diff --git a/config/locales/simple_form.eu.yml b/config/locales/simple_form.eu.yml index d856fea..22b71ba 100644 --- a/config/locales/simple_form.eu.yml +++ b/config/locales/simple_form.eu.yml @@ -28,5 +28,14 @@ eu: filtered_languages: Iragazitako hizkuntzak locale: Hizkuntza new_password: Pasahitz berria - note: Bio + note: Biografia password: Pasahitza + setting_boost_modal: Erakutsi baieztapen elkarrizketa-koadroa bultzada eman aurretik + setting_default_privacy: Mezuaren pribatutasuna + notification_emails: + reblog: Bidali e-mail mezua norbaitek zure mezuari bultzada ematen badio + 'no': Ez + required: + mark: "*" + text: beharrezkoa + 'yes': Bai diff --git a/config/locales/simple_form.fa.yml b/config/locales/simple_form.fa.yml index a885c93..13ff780 100644 --- a/config/locales/simple_form.fa.yml +++ b/config/locales/simple_form.fa.yml @@ -11,7 +11,7 @@ fa: other: %{count} حرف باقی مانده fields: شما می‌توانید تا چهار مورد را در یک جدول در نمایهٔ خود نمایش دهید header: یکی از قالب‌های PNG یا GIF یا JPG. بیشترین اندازه ۲ مگابایت. تصویر به اندازهٔ ۳۳۵×۷۰۰ پیکسل تبدیل خواهد شد - locked: باید پیگیران تازه را خودتان تأیید کنید. حریم خصوصی پیش‌فرض نوشته‌ها را روی پیگیران تنظیم می‌کند + locked: باید پیگیران تازه را خودتان تأیید کنید note: one: 1 حرف باقی مانده other: %{count} حرف باقی مانده @@ -53,10 +53,10 @@ fa: setting_boost_modal: نمایش پیغام تأیید پیش از بازبوقیدن setting_default_privacy: حریم خصوصی نوشته‌ها setting_default_sensitive: همیشه تصاویر را به عنوان حساس علامت بزن - setting_delete_modal: پیش از پاک کردن یک نوشته پیغام تأیید نشان بده + setting_delete_modal: نمایش پیغام تأیید پیش از پاک کردن یک نوشته setting_display_sensitive_media: همیشه تصویرهای علامت‌زده‌شده به عنوان حساس را نمایش بده setting_hide_network: نهفتن شبکهٔ ارتباطی - setting_noindex: درخواست از موتورهای جستجو برای لغو فهرست‌سازی + setting_noindex: درخواست از موتورهای جستجوگر برای ظاهر نشدن در نتایج جستجو setting_reduce_motion: کاستن از حرکت در پویانمایی‌ها setting_system_font_ui: به‌کاربردن قلم پیش‌فرض سیستم setting_theme: تم سایت @@ -71,7 +71,7 @@ fa: must_be_following_dm: مسدودکردن پیغام‌های خصوصی کسانی که شما پی نمی‌گیرید notification_emails: digest: خلاصه‌کردن چند اعلان در یک ایمیل - favourite: وقتی کسی نوشتهٔ شما پسندید ایمیل بفرست + favourite: وقتی کسی نوشتهٔ شما را پسندید ایمیل بفرست follow: وقتی کسی پیگیر شما شد ایمیل بفرست follow_request: وقتی کسی درخواست پیگیری کرد ایمیل بفرست mention: وقتی کسی از شما نام برد ایمیل بفرست diff --git a/config/locales/simple_form.gl.yml b/config/locales/simple_form.gl.yml index 6dc3da1..bae49a6 100644 --- a/config/locales/simple_form.gl.yml +++ b/config/locales/simple_form.gl.yml @@ -35,7 +35,7 @@ gl: confirm_new_password: Confirme o novo contrasinal confirm_password: Confirme o contrasinal current_password: Contrasinal actual - data: Data + data: Datos display_name: Nome mostrado email: enderezo correo electrónico expires_in: Caducidade despois de diff --git a/config/locales/simple_form.it.yml b/config/locales/simple_form.it.yml index dd533ec..59a4cf5 100644 --- a/config/locales/simple_form.it.yml +++ b/config/locales/simple_form.it.yml @@ -15,6 +15,7 @@ it: note: one: 1 carattere rimanente other: %{count} caratteri rimanenti + setting_hide_network: Chi segui e chi segue te no saranno mostrati sul tuo profilo setting_noindex: Coinvolge il tuo profilo pubblico e le pagine di stato setting_theme: Coinvolge il modo in cui Mastodon verrà visualizzato quando sarai collegato da qualsiasi dispositivo. imports: @@ -54,6 +55,7 @@ it: setting_default_sensitive: Segna sempre i media come sensibili setting_delete_modal: Mostra dialogo di conferma prima di eliminare un toot setting_display_sensitive_media: Mostra sempre i media segnati come sensibili + setting_hide_network: Nascondi la tua rete setting_noindex: Non indicizzare dai motori di ricerca setting_reduce_motion: Riduci movimento nelle animazioni setting_system_font_ui: Usa il carattere di default del sistema diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index a3ea25d..d30afb4 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -39,7 +39,7 @@ ja: filtered_languages: 除外する言語 header: ヘッダー locale: 言語 - locked: 非公開アカウントにする + locked: 承認制アカウントにする max_uses: 使用できる回数 new_password: 新しいパスワード note: プロフィール diff --git a/config/locales/simple_form.sk.yml b/config/locales/simple_form.sk.yml index 83d388b..c6887a3 100644 --- a/config/locales/simple_form.sk.yml +++ b/config/locales/simple_form.sk.yml @@ -4,18 +4,18 @@ sk: hints: defaults: avatar: PNG, GIF alebo JPG. Maximálne 2MB. Bude zmenšený na 400x400px - bot: Tento účet vykonáva hlavne zautomatizované akcie, a je pravdepodobne nespravovaný - digest: Odoslané iba v prípade dlhodobej neprítomnosti, a len ak ste obdŕžali nejaké osobné správy kým ste boli preč + bot: Tento účet vykonáva hlavne automatizované akcie, a je pravdepodobne nespravovaný + digest: Odoslané iba v prípade dlhodobej neprítomnosti, a len ak si obdŕžal/a nejaké osobné správy kým si bol/a preč display_name: few: Ostávajú ti %{count} znaky one: Ostáva ti 1 znak other: Ostáva ti %{count} znakov fields: Môžeš mať 4 položky na svojom profile zobrazené vo forme tabuľky header: PNG, GIF alebo JPG. Maximálne 2MB. Bude zmenšený na 700x335px - locked: Musíte manuálne schváliť sledujúcich + locked: Vyžaduje manuálne schvalovať sledujúcich note: few: Ostávajú ti %{count} znaky - one: Ostáva vám 1 znak + one: Ostáva ti 1 znak other: Ostáva ti %{count} znakov setting_hide_network: Koho následuješ, a kto následuje teba nebude zobrazené na tvojom profile setting_noindex: Ovplyvňuje verejný profil a statusy @@ -57,6 +57,7 @@ sk: setting_default_sensitive: Označ všetky mediálne súbory ako chúlostivé setting_delete_modal: Zobrazuj potvrdzovacie okno pred vymazaním toot-u setting_display_sensitive_media: Vždy zobraz médiá označené ako chúlostivé + setting_hide_network: Ukri svoju sieť kontaktov setting_noindex: Nezaraďuj príspevky do indexu pre vyhľadávče setting_reduce_motion: Redukovať pohyb v animáciách setting_system_font_ui: Použiť základné systémové písmo @@ -64,11 +65,11 @@ sk: setting_unfollow_modal: Zobraziť potvrdzovacie okno pred skončením sledovania iného užívateľa severity: Závažnosť type: Typ importu - username: Užívateľská prezývka - username_or_email: Prezívka, alebo Email + username: Prezývka + username_or_email: Prezívka, alebo email interactions: - must_be_follower: Blokovať notifikácie pod používateľov, ktorí ťa nesledujú - must_be_following: Blokovať notifikácie od ľudí ktorí ťa nesledujú + must_be_follower: Blokovať oznámenia od užívateľov, ktorí ťa nesledujú + must_be_following: Blokovať oznámenia od ľudí ktorých nesleduješ must_be_following_dm: Blokovať súkromné správy od ľudí ktorých nesleduješ notification_emails: digest: Posielať súhrnné emaily diff --git a/config/locales/simple_form.sv.yml b/config/locales/simple_form.sv.yml index f43097e..f027d68 100644 --- a/config/locales/simple_form.sv.yml +++ b/config/locales/simple_form.sv.yml @@ -3,7 +3,7 @@ sv: simple_form: hints: defaults: - avatar: Högst 2 MB. Kommer nedskalas till 400x400px + avatar: Högst 2 MB. Kommer att skalas ner till 400x400px bot: Detta konto utför huvudsakligen automatiserade åtgärder och kanske inte övervakas digest: Skickas endast efter en lång period av inaktivitet och endast om du har fått några personliga meddelanden i din frånvaro display_name: @@ -15,6 +15,7 @@ sv: note: one: 1 tecken kvar other: %{count} tecken kvar + setting_hide_network: Vem du följer och vilka som följer dig kommer inte att visas på din profilsida setting_noindex: Påverkar din offentliga profil och statussidor setting_theme: Påverkar hur Mastodon ser ut oavsett från vilken enhet du är inloggad. imports: @@ -30,6 +31,7 @@ sv: value: Innehåll defaults: avatar: Avatar + bot: Detta är ett botkonto confirm_new_password: Bekräfta nytt lösenord confirm_password: Bekräfta lösenord current_password: Nuvarande lösenord @@ -53,6 +55,7 @@ sv: setting_default_sensitive: Markera alltid media som känsligt setting_delete_modal: Visa bekräftelsedialog innan du raderar en toot setting_display_sensitive_media: Visa alltid media märkt som känsligt + setting_hide_network: Göm ditt nätverk setting_noindex: Uteslutning av sökmotorindexering setting_reduce_motion: Minska rörelser i animationer setting_system_font_ui: Använd systemets standardfont diff --git a/config/locales/simple_form.zh-HK.yml b/config/locales/simple_form.zh-HK.yml index ed80b10..06d3f6f 100644 --- a/config/locales/simple_form.zh-HK.yml +++ b/config/locales/simple_form.zh-HK.yml @@ -15,12 +15,13 @@ zh-HK: note: one: 尚餘 1 個字 other: 尚餘 %{count} 個字 + setting_hide_network: 你關注的人和關注你的人將不會在你的個人資料頁上顯示 setting_noindex: 此設定會影響到你的公開個人資料以及文章頁面 setting_theme: 此設置會影響到你從任意設備登入時 Mastodon 的顯示樣式。 imports: data: 自其他服務站匯出的 CSV 檔案 sessions: - otp: 輸入你手機上生成的雙重認證碼,或者任意一個恢復代碼。 + otp: 輸入你手機上生成的雙重認證碼,或者任意一個恢復代碼: user: filtered_languages: 下面被選擇的語言的文章將不會出現在你的公共時間軸上 labels: @@ -54,6 +55,7 @@ zh-HK: setting_default_sensitive: 預設我的內容為敏感內容 setting_delete_modal: 刪推前詢問我 setting_display_sensitive_media: 預設我的媒體為敏感內容 + setting_hide_network: 隱藏你的社交網絡 setting_noindex: 阻止搜尋引擎檢索 setting_reduce_motion: 減低動畫效果 setting_system_font_ui: 使用系統預設字型 diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 2d2a87a..3d24b12 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -10,7 +10,7 @@ sk: contact_missing: Nezadané contact_unavailable: Neuvedené description_headline: Čo je %{domain}? - domain_count_after: ďalším inštanciám + domain_count_after: ďalším instanciám domain_count_before: Pripojený k extended_description_html: |

Pravidlá

@@ -40,6 +40,7 @@ sk: following: Sleduje media: Médiá moved_html: "%{name} účet bol presunutý na %{new_profile_link}:" + network_hidden: Táto informácia nieje k dispozícii nothing_here: Nič tu nie je! people_followed_by: Ľudia, ktorých %{name} sleduje people_who_follow: Ľudia sledujúci %{name} @@ -282,7 +283,7 @@ sk: create_and_resolve: Vyrieš s poznámkou create_and_unresolve: Otvor znovu, s poznámkou delete: Vymaž - placeholder: Opíš aké opatrenia boli urobené, alebo akékoľvek iné aktualizácie k tomuto nahláseniu… + placeholder: Opíš aké opatrenia boli urobené, alebo akékoľvek iné súvisiace aktualizácie… reopen: Znovu otvor report report: Nahlásiť report_contents: Obsah @@ -396,8 +397,8 @@ sk: change_password: Heslo confirm_email: Potvrdiť email delete_account: Vymazať účet - delete_account_html: Pokiaľ si želáte vymazať svoj účet, môžete tak 1 urobiť tu 2. Budete požiadaný/á o potvrdenie tohto kroku. - didnt_get_confirmation: Neobdŕžali ste kroky pre potvrdenie? + delete_account_html: Pokiaľ chceš vymazať svoj účet, môžeš tak urobiť tu. Budeš požiadaný/á o potvrdenie tohto kroku. + didnt_get_confirmation: Neobdŕžal/a si kroky pre potvrdenie? forgot_password: Zabudli ste heslo? invalid_reset_password_token: Token na obnovu hesla vypršal. Prosím vypítajte si nový. login: Prihlás sa @@ -482,7 +483,7 @@ sk: few: Počas utišovania sledovateľov z %{count} domén... one: Počas utišovania sledovateľov z jednej domény... other: Počas utišovania sledovateľov z %{count} domén... - true_privacy_html: Prosím majte na vedomí, 1 že ozajstné súkromie sa dá dosiahnúť iba za pomoci end-to-end enkrypcie 2. + true_privacy_html: Prosím ber na vedomie, že ozajstné súkromie sa dá dosiahnúť iba za pomoci end-to-end enkrypcie. unlocked_warning_html: Hocikto ťa môže následovať aby mohol/a ihneď vidieť tvoje súkromné príspevky. %{lock_link} aby si mohla skontrolovať a odmietať sledovateľov. unlocked_warning_title: Tvoj účet nieje zamknutý generic: @@ -679,6 +680,7 @@ sk: themes: contrast: Vysoký kontrast default: Mastodon + mastodon-light: Mastodon (svetlý) time: formats: default: "%b %d, %R, %H:%M" diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 5a81d04..23ea7dd 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -55,7 +55,7 @@ sv: unfollow: Sluta följa admin: account_moderation_notes: - create: Skapa + create: Lämna kommentar created_msg: Modereringsnotering skapad utan problem! delete: Ta bort destroyed_msg: Modereringsnotering borttagen utan problem! @@ -283,7 +283,7 @@ sv: create_and_resolve: Lös med anteckning create_and_unresolve: Återuppta med anteckning delete: Radera - placeholder: Beskriv vilka åtgärder som vidtagits eller andra uppdateringar till den här anmälan… + placeholder: Beskriv vilka åtgärder som vidtagits eller andra uppdateringar till den här anmälan. reopen: Återuppta anmälan report: 'Anmäl #%{id}' report_contents: Innehåll diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml index 14eedc9..c489d8b 100644 --- a/config/locales/zh-HK.yml +++ b/config/locales/zh-HK.yml @@ -40,6 +40,7 @@ zh-HK: following: 正在關注 media: 媒體 moved_html: "%{name} 已經轉移到 %{new_profile_link}:" + network_hidden: 此信息不可用 nothing_here: 暫時未有內容可以顯示 people_followed_by: "%{name} 關注的人" people_who_follow: 關注 %{name} 的人 @@ -50,7 +51,7 @@ zh-HK: roles: admin: 管理員 bot: 機械人 - moderator: 監察员 + moderator: 監察員 unfollow: 取消關注 admin: account_moderation_notes: @@ -129,7 +130,7 @@ zh-HK: role: 身份 roles: admin: 管理員 - moderator: 監察员 + moderator: 監察員 staff: 管理人員 user: 普通用戶 salmon_url: Salmon 反饋 URL @@ -282,7 +283,7 @@ zh-HK: create_and_resolve: 建立筆記並標示為「已處理」 create_and_unresolve: 建立筆記並標示為「未處理」 delete: 刪除 - placeholder: 記錄已執行的動作,或其他更新 + placeholder: 記錄已執行的動作,或其他相關的更新…… reopen: 重開舉報 report: '舉報 #%{id}' report_contents: 舉報內容 @@ -696,6 +697,7 @@ zh-HK: themes: contrast: 高對比 default: 萬象 + mastodon-light: 萬象(亮色主題) time: formats: default: "%Y年%-m月%d日 %H:%M" From a0b47542312c653748ac0d017e9b0d68650c5673 Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Sat, 26 May 2018 17:53:44 +0900 Subject: [PATCH 014/111] Fix color mistakes in mastodon-light theme (#7626) * Fix colors of mastodon-light theme Fix colors of modals and focused toots in light theme Fix colors of compose-form items and more Fix colors of status__content__spoiler-link:hover and $valid-value-color Change success green color in light theme * Fix some sass codes * Add !default for explicit color valiables in default theme for overwriting colors easier in the other themes --- app/javascript/styles/mastodon-light/diff.scss | 55 ++++++++++++++++++++-- .../styles/mastodon-light/variables.scss | 7 ++- app/javascript/styles/mastodon/components.scss | 4 +- app/javascript/styles/mastodon/variables.scss | 8 ++-- 4 files changed, 63 insertions(+), 11 deletions(-) diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss index 42c790b..2b88b83 100644 --- a/app/javascript/styles/mastodon-light/diff.scss +++ b/app/javascript/styles/mastodon-light/diff.scss @@ -25,6 +25,55 @@ background: $ui-base-color url('data:image/svg+xml;utf8,') no-repeat bottom / 100% auto; } +.compose-form .compose-form__modifiers .compose-form__upload__actions .icon-button { + color: $ui-base-color; + + &:active, + &:focus, + &:hover { + color: darken($ui-base-color, 7%); + } +} + +.compose-form .compose-form__modifiers .compose-form__upload-description input { + color: $ui-base-color; + + &::placeholder { + color: $ui-base-color; + } +} + +.compose-form .compose-form__buttons-wrapper { + background: darken($ui-base-color, 6%); +} + +.focusable:focus { + background: $ui-base-color; +} + +.status.status-direct { + background: lighten($ui-base-color, 4%); +} + +.focusable:focus .status.status-direct { + background: lighten($ui-base-color, 8%); +} + +.detailed-status, +.detailed-status__action-bar { + background: darken($ui-base-color, 6%); +} + +// Change the background color of status__content__spoiler-link +.reply-indicator__content .status__content__spoiler-link, +.status__content .status__content__spoiler-link { + background: $ui-base-lighter-color; + + &:hover { + background: lighten($ui-base-lighter-color, 6%); + } +} + // Change the colors used in the dropdown menu .dropdown-menu { background: $ui-base-color; @@ -84,17 +133,17 @@ .confirmation-modal, .mute-modal, .report-modal { - background: $ui-secondary-color; + background: $ui-base-color; } .boost-modal__action-bar, .confirmation-modal__action-bar, .mute-modal__action-bar { - background: darken($ui-secondary-color, 6%); + background: darken($ui-base-color, 6%); } .react-toggle-track { - background: $ui-base-color; + background: $ui-secondary-color; } // Change the default color used for the text in an empty column or on the error column diff --git a/app/javascript/styles/mastodon-light/variables.scss b/app/javascript/styles/mastodon-light/variables.scss index 4be454e..9f6d470 100644 --- a/app/javascript/styles/mastodon-light/variables.scss +++ b/app/javascript/styles/mastodon-light/variables.scss @@ -8,7 +8,10 @@ $classic-secondary-color: #d9e1e8; $classic-highlight-color: #2b90d9; // Differences -$base-overlay-background: $white; +$success-green: #3c754d; + +$base-overlay-background: $white !default; +$valid-value-color: $success-green !default; $ui-base-color: $classic-secondary-color !default; $ui-base-lighter-color: #b0c0cf; @@ -26,7 +29,7 @@ $lighter-text-color: $classic-base-color !default; $light-text-color: #444b5d; //Newly added colors -$account-background-color: $white; +$account-background-color: $white !default; //Invert darkened and lightened colors @function darken($color, $amount) { diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 412f8d7..55564b2 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -425,7 +425,7 @@ .icon-button { flex: 0 1 auto; - color: $action-button-color; + color: $secondary-text-color; font-size: 14px; font-weight: 500; padding: 10px; @@ -434,7 +434,7 @@ &:hover, &:focus, &:active { - color: lighten($action-button-color, 7%); + color: lighten($secondary-text-color, 7%); } } diff --git a/app/javascript/styles/mastodon/variables.scss b/app/javascript/styles/mastodon/variables.scss index cbefe35..40aeb4a 100644 --- a/app/javascript/styles/mastodon/variables.scss +++ b/app/javascript/styles/mastodon/variables.scss @@ -1,10 +1,10 @@ // Commonly used web colors $black: #000000; // Black $white: #ffffff; // White -$success-green: #79bd9a; // Padua -$error-red: #df405a; // Cerise -$warning-red: #ff5050; // Sunset Orange -$gold-star: #ca8f04; // Dark Goldenrod +$success-green: #79bd9a !default; // Padua +$error-red: #df405a !default; // Cerise +$warning-red: #ff5050 !default; // Sunset Orange +$gold-star: #ca8f04 !default; // Dark Goldenrod // Values from the classic Mastodon UI $classic-base-color: #282c37; // Midnight Express From 07054ee6f7e2db4f2e087f6aa52cd3b18c0b4297 Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Sat, 26 May 2018 17:53:53 +0900 Subject: [PATCH 015/111] Add right margin of notification message (#7628) --- app/javascript/styles/mastodon/components.scss | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 55564b2..2724454 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1373,9 +1373,8 @@ a.account__display-name { } .notification__message { - margin-left: 68px; - padding: 8px 0; - padding-bottom: 0; + margin: 0 10px 0 68px; + padding: 8px 0 0; cursor: default; color: $darker-text-color; font-size: 15px; From 422f92f3f8a60048bb2a5df4b04a1d16fc6fe5b1 Mon Sep 17 00:00:00 2001 From: bsky Date: Sat, 26 May 2018 22:29:32 +0900 Subject: [PATCH 016/111] Fix lock icon position in account card (#7630) --- app/javascript/styles/mastodon/accounts.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss index de0a7ea..3ccce38 100644 --- a/app/javascript/styles/mastodon/accounts.scss +++ b/app/javascript/styles/mastodon/accounts.scss @@ -79,6 +79,10 @@ font-weight: 400; overflow: hidden; text-overflow: ellipsis; + + .fa { + margin-left: 3px; + } } } From 182bdbc5f4f0194cdd7b771246304c2dfc6f19e0 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 27 May 2018 04:55:19 +0200 Subject: [PATCH 017/111] Don't use Object.assign with Notification, only display actions for mentions (#7632) Fix #7627 --- .../service_worker/web_push_notifications.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/app/javascript/mastodon/service_worker/web_push_notifications.js b/app/javascript/mastodon/service_worker/web_push_notifications.js index f922f7d..3318bba 100644 --- a/app/javascript/mastodon/service_worker/web_push_notifications.js +++ b/app/javascript/mastodon/service_worker/web_push_notifications.js @@ -25,7 +25,7 @@ const notify = options => return self.registration.showNotification(group.title, group); } else if (notifications.length === 1 && notifications[0].tag === GROUP_TAG) { // Already grouped, proceed with appending the notification to the group - const group = { ...notifications[0] }; + const group = cloneNotification(notifications[0]); group.title = formatMessage('notifications.group', options.data.preferred_locale, { count: group.data.count + 1 }); group.body = `${options.title}\n${group.body}`; @@ -57,6 +57,18 @@ const fetchFromApi = (path, method, accessToken) => { }).then(res => res.json()); }; +const cloneNotification = notification => { + const clone = {}; + let k; + + // Object.assign() does not work with notifications + for(k in notification) { + clone[k] = notification[k]; + } + + return clone; +}; + const formatMessage = (messageId, locale, values = {}) => (new IntlMessageFormat(locales[locale][messageId], locale)).format(values); @@ -95,7 +107,7 @@ const handlePush = (event) => { options.body = notification.status.spoiler_text; options.image = undefined; options.actions = [actionExpand(preferred_locale)]; - } else if (notification.status) { + } else if (notification.type === 'mention') { options.actions = [actionReblog(preferred_locale), actionFavourite(preferred_locale)]; } @@ -130,7 +142,7 @@ const findBestClient = clients => { }; const expandNotification = notification => { - const newNotification = { ...notification }; + const newNotification = cloneNotification(notification); newNotification.body = newNotification.data.hiddenBody; newNotification.image = newNotification.data.hiddenImage; @@ -140,7 +152,7 @@ const expandNotification = notification => { }; const removeActionFromNotification = (notification, action) => { - const newNotification = { ...notification }; + const newNotification = cloneNotification(notification); newNotification.actions = newNotification.actions.filter(item => item.action !== action); From 63c7b9157274f57c496399a1a5c728b32415034c Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 27 May 2018 04:58:08 +0200 Subject: [PATCH 018/111] Validate that e-mail resolves with MX and it's not blacklisted (#7631) Original patch by @j-a4 --- app/models/user.rb | 1 + app/validators/email_mx_validator.rb | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 app/validators/email_mx_validator.rb diff --git a/app/models/user.rb b/app/models/user.rb index cfbae58..0becfa7 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -65,6 +65,7 @@ class User < ApplicationRecord validates :locale, inclusion: I18n.available_locales.map(&:to_s), if: :locale? validates_with BlacklistedEmailValidator, if: :email_changed? + validates_with EmailMxValidator, if: :email_changed? scope :recent, -> { order(id: :desc) } scope :admins, -> { where(admin: true) } diff --git a/app/validators/email_mx_validator.rb b/app/validators/email_mx_validator.rb new file mode 100644 index 0000000..d4c7cc2 --- /dev/null +++ b/app/validators/email_mx_validator.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'resolv' + +class EmailMxValidator < ActiveModel::Validator + def validate(user) + return if Rails.env.test? + user.errors.add(:email, I18n.t('users.invalid_email')) if invalid_mx?(user.email) + end + + private + + def invalid_mx?(value) + _, domain = value.split('@', 2) + + return true if domain.nil? + + records = Resolv::DNS.new.getresources(domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s } + records.empty? || on_blacklist?(records) + end + + def on_blacklist?(values) + EmailDomainBlock.where(domain: values).any? + end +end From 9bd23dc4e51ba47283a8e3a66cd94b4e624a5235 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 27 May 2018 21:45:30 +0200 Subject: [PATCH 019/111] Track trending tags (#7638) * Track trending tags - Half-life of 1 day - Historical usage in daily buckets (last 7 days stored) - GET /api/v1/trends Fix #271 * Add trends to web UI * Don't render compose form on search route, adjust search results header * Disqualify tag from trends if it's in disallowed hashtags setting * Count distinct accounts using tag, ignore silenced accounts --- app/controllers/api/v1/trends_controller.rb | 17 +++++ app/javascript/mastodon/actions/trends.js | 32 +++++++++ .../features/compose/components/search_results.js | 59 ++++++++++++++- .../compose/containers/search_results_container.js | 8 ++- app/javascript/mastodon/features/compose/index.js | 4 +- app/javascript/mastodon/reducers/index.js | 2 + app/javascript/mastodon/reducers/trends.js | 13 ++++ app/javascript/styles/mastodon/components.scss | 83 +++++++++++++++++++++- app/models/tag.rb | 16 +++++ app/models/trending_tags.rb | 61 ++++++++++++++++ app/serializers/rest/tag_serializer.rb | 11 +++ app/services/process_hashtags_service.rb | 6 +- config/routes.rb | 1 + package.json | 1 + yarn.lock | 6 ++ 15 files changed, 310 insertions(+), 10 deletions(-) create mode 100644 app/controllers/api/v1/trends_controller.rb create mode 100644 app/javascript/mastodon/actions/trends.js create mode 100644 app/javascript/mastodon/reducers/trends.js create mode 100644 app/models/trending_tags.rb create mode 100644 app/serializers/rest/tag_serializer.rb diff --git a/app/controllers/api/v1/trends_controller.rb b/app/controllers/api/v1/trends_controller.rb new file mode 100644 index 0000000..bcea985 --- /dev/null +++ b/app/controllers/api/v1/trends_controller.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class Api::V1::TrendsController < Api::BaseController + before_action :set_tags + + respond_to :json + + def index + render json: @tags, each_serializer: REST::TagSerializer + end + + private + + def set_tags + @tags = TrendingTags.get(limit_param(10)) + end +end diff --git a/app/javascript/mastodon/actions/trends.js b/app/javascript/mastodon/actions/trends.js new file mode 100644 index 0000000..853e4f6 --- /dev/null +++ b/app/javascript/mastodon/actions/trends.js @@ -0,0 +1,32 @@ +import api from '../api'; + +export const TRENDS_FETCH_REQUEST = 'TRENDS_FETCH_REQUEST'; +export const TRENDS_FETCH_SUCCESS = 'TRENDS_FETCH_SUCCESS'; +export const TRENDS_FETCH_FAIL = 'TRENDS_FETCH_FAIL'; + +export const fetchTrends = () => (dispatch, getState) => { + dispatch(fetchTrendsRequest()); + + api(getState) + .get('/api/v1/trends') + .then(({ data }) => dispatch(fetchTrendsSuccess(data))) + .catch(err => dispatch(fetchTrendsFail(err))); +}; + +export const fetchTrendsRequest = () => ({ + type: TRENDS_FETCH_REQUEST, + skipLoading: true, +}); + +export const fetchTrendsSuccess = trends => ({ + type: TRENDS_FETCH_SUCCESS, + trends, + skipLoading: true, +}); + +export const fetchTrendsFail = error => ({ + type: TRENDS_FETCH_FAIL, + error, + skipLoading: true, + skipAlert: true, +}); diff --git a/app/javascript/mastodon/features/compose/components/search_results.js b/app/javascript/mastodon/features/compose/components/search_results.js index 8445556..f2655c1 100644 --- a/app/javascript/mastodon/features/compose/components/search_results.js +++ b/app/javascript/mastodon/features/compose/components/search_results.js @@ -1,23 +1,75 @@ import React from 'react'; +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { FormattedMessage } from 'react-intl'; +import { FormattedMessage, FormattedNumber } from 'react-intl'; import AccountContainer from '../../../containers/account_container'; import StatusContainer from '../../../containers/status_container'; import { Link } from 'react-router-dom'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import { Sparklines, SparklinesCurve } from 'react-sparklines'; + +const shortNumberFormat = number => { + if (number < 1000) { + return ; + } else { + return K; + } +}; export default class SearchResults extends ImmutablePureComponent { static propTypes = { results: ImmutablePropTypes.map.isRequired, + trends: ImmutablePropTypes.list, + fetchTrends: PropTypes.func.isRequired, }; + componentDidMount () { + const { fetchTrends } = this.props; + fetchTrends(); + } + render () { - const { results } = this.props; + const { results, trends } = this.props; let accounts, statuses, hashtags; let count = 0; + if (results.isEmpty()) { + return ( +
+
+
+ + +
+ + {trends && trends.map(hashtag => ( +
+
+ + #{hashtag.get('name')} + + + {shortNumberFormat(hashtag.getIn(['history', 0, 'accounts']))} }} /> +
+ +
+ {shortNumberFormat(hashtag.getIn(['history', 0, 'uses']))} +
+ +
+ day.get('uses')).toArray()}> + + +
+
+ ))} +
+
+ ); + } + if (results.get('accounts') && results.get('accounts').size > 0) { count += results.get('accounts').size; accounts = ( @@ -48,7 +100,7 @@ export default class SearchResults extends ImmutablePureComponent { {results.get('hashtags').map(hashtag => ( - #{hashtag} + {hashtag} ))}
@@ -58,6 +110,7 @@ export default class SearchResults extends ImmutablePureComponent { return (
+
diff --git a/app/javascript/mastodon/features/compose/containers/search_results_container.js b/app/javascript/mastodon/features/compose/containers/search_results_container.js index 16d95d4..7273460 100644 --- a/app/javascript/mastodon/features/compose/containers/search_results_container.js +++ b/app/javascript/mastodon/features/compose/containers/search_results_container.js @@ -1,8 +1,14 @@ import { connect } from 'react-redux'; import SearchResults from '../components/search_results'; +import { fetchTrends } from '../../../actions/trends'; const mapStateToProps = state => ({ results: state.getIn(['search', 'results']), + trends: state.get('trends'), }); -export default connect(mapStateToProps)(SearchResults); +const mapDispatchToProps = dispatch => ({ + fetchTrends: () => dispatch(fetchTrends()), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(SearchResults); diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js index 19aae03..d8e9ad9 100644 --- a/app/javascript/mastodon/features/compose/index.js +++ b/app/javascript/mastodon/features/compose/index.js @@ -101,7 +101,7 @@ export default class Compose extends React.PureComponent { {(multiColumn || isSearchPage) && }
-
+ {!isSearchPage &&
{multiColumn && ( @@ -109,7 +109,7 @@ export default class Compose extends React.PureComponent {
)} -
+
} {({ x }) => ( diff --git a/app/javascript/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js index 3d9a6a1..019c1f4 100644 --- a/app/javascript/mastodon/reducers/index.js +++ b/app/javascript/mastodon/reducers/index.js @@ -26,6 +26,7 @@ import height_cache from './height_cache'; import custom_emojis from './custom_emojis'; import lists from './lists'; import listEditor from './list_editor'; +import trends from './trends'; const reducers = { dropdown_menu, @@ -55,6 +56,7 @@ const reducers = { custom_emojis, lists, listEditor, + trends, }; export default combineReducers(reducers); diff --git a/app/javascript/mastodon/reducers/trends.js b/app/javascript/mastodon/reducers/trends.js new file mode 100644 index 0000000..95cf8f2 --- /dev/null +++ b/app/javascript/mastodon/reducers/trends.js @@ -0,0 +1,13 @@ +import { TRENDS_FETCH_SUCCESS } from '../actions/trends'; +import { fromJS } from 'immutable'; + +const initialState = null; + +export default function trendsReducer(state = initialState, action) { + switch(action.type) { + case TRENDS_FETCH_SUCCESS: + return fromJS(action.trends); + default: + return state; + } +}; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 2724454..c66bc42 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -3334,9 +3334,15 @@ a.status-card { color: $dark-text-color; background: lighten($ui-base-color, 2%); border-bottom: 1px solid darken($ui-base-color, 4%); - padding: 15px 10px; - font-size: 14px; + padding: 15px; font-weight: 500; + font-size: 16px; + cursor: default; + + .fa { + display: inline-block; + margin-right: 5px; + } } .search-results__section { @@ -5209,3 +5215,76 @@ noscript { background: $ui-base-color; } } + +.trends { + &__header { + color: $dark-text-color; + background: lighten($ui-base-color, 2%); + border-bottom: 1px solid darken($ui-base-color, 4%); + font-weight: 500; + padding: 15px; + font-size: 16px; + cursor: default; + + .fa { + display: inline-block; + margin-right: 5px; + } + } + + &__item { + display: flex; + align-items: center; + padding: 15px; + border-bottom: 1px solid lighten($ui-base-color, 8%); + + &:last-child { + border-bottom: 0; + } + + &__name { + flex: 1 1 auto; + color: $dark-text-color; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + strong { + font-weight: 500; + } + + a { + color: $darker-text-color; + text-decoration: none; + font-size: 14px; + font-weight: 500; + display: block; + + &:hover, + &:focus, + &:active { + span { + text-decoration: underline; + } + } + } + } + + &__current { + width: 100px; + font-size: 24px; + line-height: 36px; + font-weight: 500; + text-align: center; + color: $secondary-text-color; + } + + &__sparkline { + width: 50px; + + path { + stroke: lighten($highlight-text-color, 6%) !important; + } + } + } +} diff --git a/app/models/tag.rb b/app/models/tag.rb index 8b1b024..4f31f79 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -21,6 +21,22 @@ class Tag < ApplicationRecord name end + def history + days = [] + + 7.times do |i| + day = i.days.ago.beginning_of_day.to_i + + days << { + day: day.to_s, + uses: Redis.current.get("activity:tags:#{id}:#{day}") || '0', + accounts: Redis.current.pfcount("activity:tags:#{id}:#{day}:accounts").to_s, + } + end + + days + end + class << self def search_for(term, limit = 5) pattern = sanitize_sql_like(term.strip) + '%' diff --git a/app/models/trending_tags.rb b/app/models/trending_tags.rb new file mode 100644 index 0000000..eedd926 --- /dev/null +++ b/app/models/trending_tags.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +class TrendingTags + KEY = 'trending_tags' + HALF_LIFE = 1.day.to_i + MAX_ITEMS = 500 + EXPIRE_HISTORY_AFTER = 7.days.seconds + + class << self + def record_use!(tag, account, at_time = Time.now.utc) + return if disallowed_hashtags.include?(tag.name) || account.silenced? + + increment_vote!(tag.id, at_time) + increment_historical_use!(tag.id, at_time) + increment_unique_use!(tag.id, account.id, at_time) + end + + def get(limit) + tag_ids = redis.zrevrange(KEY, 0, limit).map(&:to_i) + tags = Tag.where(id: tag_ids).to_a.map { |tag| [tag.id, tag] }.to_h + tag_ids.map { |tag_id| tags[tag_id] }.compact + end + + private + + def increment_vote!(tag_id, at_time) + redis.zincrby(KEY, (2**((at_time.to_i - epoch) / HALF_LIFE)).to_f, tag_id.to_s) + redis.zremrangebyrank(KEY, 0, -MAX_ITEMS) if rand < (2.to_f / MAX_ITEMS) + end + + def increment_historical_use!(tag_id, at_time) + key = "activity:tags:#{tag_id}:#{at_time.beginning_of_day.to_i}" + redis.incrby(key, 1) + redis.expire(key, EXPIRE_HISTORY_AFTER) + end + + def increment_unique_use!(tag_id, account_id, at_time) + key = "activity:tags:#{tag_id}:#{at_time.beginning_of_day.to_i}:accounts" + redis.pfadd(key, account_id) + redis.expire(key, EXPIRE_HISTORY_AFTER) + end + + # The epoch needs to be 2.5 years in the future if the half-life is one day + # While dynamic, it will always be the same within one year + def epoch + @epoch ||= Date.new(Date.current.year + 2.5, 10, 1).to_datetime.to_i + end + + def disallowed_hashtags + return @disallowed_hashtags if defined?(@disallowed_hashtags) + + @disallowed_hashtags = Setting.disallowed_hashtags.nil? ? [] : Setting.disallowed_hashtags + @disallowed_hashtags = @disallowed_hashtags.split(' ') if @disallowed_hashtags.is_a? String + @disallowed_hashtags = @disallowed_hashtags.map(&:downcase) + end + + def redis + Redis.current + end + end +end diff --git a/app/serializers/rest/tag_serializer.rb b/app/serializers/rest/tag_serializer.rb new file mode 100644 index 0000000..74aa571 --- /dev/null +++ b/app/serializers/rest/tag_serializer.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class REST::TagSerializer < ActiveModel::Serializer + include RoutingHelper + + attributes :name, :url, :history + + def url + tag_url(object) + end +end diff --git a/app/services/process_hashtags_service.rb b/app/services/process_hashtags_service.rb index 5b45c86..0695922 100644 --- a/app/services/process_hashtags_service.rb +++ b/app/services/process_hashtags_service.rb @@ -4,8 +4,10 @@ class ProcessHashtagsService < BaseService def call(status, tags = []) tags = Extractor.extract_hashtags(status.text) if status.local? - tags.map { |str| str.mb_chars.downcase }.uniq(&:to_s).each do |tag| - status.tags << Tag.where(name: tag).first_or_initialize(name: tag) + tags.map { |str| str.mb_chars.downcase }.uniq(&:to_s).each do |name| + tag = Tag.where(name: name).first_or_create(name: name) + status.tags << tag + TrendingTags.record_use!(tag, status.account, status.created_at) end end end diff --git a/config/routes.rb b/config/routes.rb index 3042b5e..2fcb885 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -254,6 +254,7 @@ Rails.application.routes.draw do resources :mutes, only: [:index] resources :favourites, only: [:index] resources :reports, only: [:index, :create] + resources :trends, only: [:index] namespace :apps do get :verify_credentials, to: 'credentials#show' diff --git a/package.json b/package.json index 61f3840..6ee6f98 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ "react-redux-loading-bar": "^2.9.3", "react-router-dom": "^4.1.1", "react-router-scroll-4": "^1.0.0-beta.1", + "react-sparklines": "^1.7.0", "react-swipeable-views": "^0.12.3", "react-textarea-autosize": "^5.2.1", "react-toggle": "^4.0.1", diff --git a/yarn.lock b/yarn.lock index 50c8855..de48c99 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6124,6 +6124,12 @@ react-router@^4.2.0: prop-types "^15.5.4" warning "^3.0.0" +react-sparklines@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/react-sparklines/-/react-sparklines-1.7.0.tgz#9b1d97e8c8610095eeb2ad658d2e1fcf91f91a60" + dependencies: + prop-types "^15.5.10" + react-swipeable-views-core@^0.12.11: version "0.12.11" resolved "https://registry.yarnpkg.com/react-swipeable-views-core/-/react-swipeable-views-core-0.12.11.tgz#3cf2b4daffbb36f9d69bd19bf5b2d5370b6b2c1b" From dfbadd68374ab5ddcaa75907261bd91da688fc6b Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 28 May 2018 02:42:06 +0200 Subject: [PATCH 020/111] Replace recursion in status mapStateToProps (#7645) --- app/javascript/mastodon/features/status/index.js | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index 2e53dfa..505a88a 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -62,31 +62,28 @@ const makeMapStateToProps = () => { if (status) { ancestorsIds = ancestorsIds.withMutations(mutable => { - function addAncestor(id) { - if (id) { - const inReplyTo = state.getIn(['contexts', 'inReplyTos', id]); + let id = status.get('in_reply_to_id'); - mutable.unshift(id); - addAncestor(inReplyTo); - } + while (id) { + mutable.unshift(id); + id = state.getIn(['contexts', 'inReplyTos', id]); } - - addAncestor(status.get('in_reply_to_id')); }); descendantsIds = descendantsIds.withMutations(mutable => { - function addDescendantOf(id) { + const ids = [status.get('id')]; + + while (ids.length > 0) { + let id = ids.shift(); const replies = state.getIn(['contexts', 'replies', id]); if (replies) { replies.forEach(reply => { mutable.push(reply); - addDescendantOf(reply); + ids.unshift(reply); }); } } - - addDescendantOf(status.get('id')); }); } From ab36e0ef72a52e5cc184d63cda0fe411c8b4086a Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 28 May 2018 05:21:04 +0200 Subject: [PATCH 021/111] Record trending tags from ActivityPub, too (#7647) --- app/lib/activitypub/activity/create.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 010ed1b..869749f 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -78,9 +78,12 @@ class ActivityPub::Activity::Create < ActivityPub::Activity return if tag['name'].blank? hashtag = tag['name'].gsub(/\A#/, '').mb_chars.downcase - hashtag = Tag.where(name: hashtag).first_or_initialize(name: hashtag) + hashtag = Tag.where(name: hashtag).first_or_create(name: hashtag) - status.tags << hashtag unless status.tags.include?(hashtag) + return if status.tags.include?(hashtag) + + status.tags << hashtag + TrendingTags.record_use!(hashtag, status.account, status.created_at) rescue ActiveRecord::RecordInvalid nil end From b87a1229c74aebec02cd08091f25613f6dff4428 Mon Sep 17 00:00:00 2001 From: tateisu Date: Mon, 28 May 2018 18:04:06 +0900 Subject: [PATCH 022/111] optimize direct timeline (#7614) * optimize direct timeline * fix typo in class name * change filter condition for direct timeline * fix codestyle issue * revoke index_accounts_not_silenced because direct timeline does not use it. * revoke index_accounts_not_silenced because direct timeline does not use it. * fix rspec test condition. * fix rspec test condition. * fix rspec test condition. * revoke adding column and partial index * (direct timeline) move merging logic to model * fix pagination parameter * add method arguments that switches return array of status or cache_ids * fix order by * returns ActiveRecord.Relation in default behavor * fix codestyle issue --- .../api/v1/timelines/direct_controller.rb | 15 +++++--- app/models/status.rb | 43 +++++++++++++++++++--- spec/models/status_spec.rb | 23 ++++++------ 3 files changed, 59 insertions(+), 22 deletions(-) diff --git a/app/controllers/api/v1/timelines/direct_controller.rb b/app/controllers/api/v1/timelines/direct_controller.rb index d455227..ef64078 100644 --- a/app/controllers/api/v1/timelines/direct_controller.rb +++ b/app/controllers/api/v1/timelines/direct_controller.rb @@ -23,15 +23,18 @@ class Api::V1::Timelines::DirectController < Api::BaseController end def direct_statuses - direct_timeline_statuses.paginate_by_max_id( - limit_param(DEFAULT_STATUSES_LIMIT), - params[:max_id], - params[:since_id] - ) + direct_timeline_statuses end def direct_timeline_statuses - Status.as_direct_timeline(current_account) + # this query requires built in pagination. + Status.as_direct_timeline( + current_account, + limit_param(DEFAULT_STATUSES_LIMIT), + params[:max_id], + params[:since_id], + true # returns array of cache_ids object + ) end def insert_pagination_headers diff --git a/app/models/status.rb b/app/models/status.rb index 853e75b..54f3f68 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -188,12 +188,45 @@ class Status < ApplicationRecord where(account: [account] + account.following).where(visibility: [:public, :unlisted, :private]) end - def as_direct_timeline(account) - query = joins("LEFT OUTER JOIN mentions ON statuses.id = mentions.status_id AND mentions.account_id = #{account.id}") - .where("mentions.account_id = #{account.id} OR statuses.account_id = #{account.id}") - .where(visibility: [:direct]) + def as_direct_timeline(account, limit = 20, max_id = nil, since_id = nil, cache_ids = false) + # direct timeline is mix of direct message from_me and to_me. + # 2 querys are executed with pagination. + # constant expression using arel_table is required for partial index + + # _from_me part does not require any timeline filters + query_from_me = where(account_id: account.id) + .where(Status.arel_table[:visibility].eq(3)) + .limit(limit) + .order('statuses.id DESC') + + # _to_me part requires mute and block filter. + # FIXME: may we check mutes.hide_notifications? + query_to_me = Status + .joins(:mentions) + .merge(Mention.where(account_id: account.id)) + .where(Status.arel_table[:visibility].eq(3)) + .limit(limit) + .order('mentions.status_id DESC') + .not_excluded_by_account(account) + + if max_id.present? + query_from_me = query_from_me.where('statuses.id < ?', max_id) + query_to_me = query_to_me.where('mentions.status_id < ?', max_id) + end + + if since_id.present? + query_from_me = query_from_me.where('statuses.id > ?', since_id) + query_to_me = query_to_me.where('mentions.status_id > ?', since_id) + end - apply_timeline_filters(query, account, false) + if cache_ids + # returns array of cache_ids object that have id and updated_at + (query_from_me.cache_ids.to_a + query_to_me.cache_ids.to_a).uniq(&:id).sort_by(&:id).reverse.take(limit) + else + # returns ActiveRecord.Relation + items = (query_from_me.select(:id).to_a + query_to_me.select(:id).to_a).uniq(&:id).sort_by(&:id).reverse.take(limit) + Status.where(id: items.map(&:id)) + end end def as_public_timeline(account = nil, local_only = false) diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb index c670101..aee4f49 100644 --- a/spec/models/status_spec.rb +++ b/spec/models/status_spec.rb @@ -154,7 +154,7 @@ RSpec.describe Status, type: :model do describe '#target' do it 'returns nil if the status is self-contained' do - expect(subject.target).to be_nil + expect(subject.target).to be_nil end it 'returns nil if the status is a reply' do @@ -333,24 +333,25 @@ RSpec.describe Status, type: :model do expect(@results).to_not include(@followed_public_status) end - it 'includes direct statuses mentioning recipient from followed' do - Fabricate(:mention, account: account, status: @followed_direct_status) - expect(@results).to include(@followed_direct_status) - end - it 'does not include direct statuses not mentioning recipient from followed' do expect(@results).to_not include(@followed_direct_status) end - it 'includes direct statuses mentioning recipient from non-followed' do - Fabricate(:mention, account: account, status: @not_followed_direct_status) - expect(@results).to include(@not_followed_direct_status) - end - it 'does not include direct statuses not mentioning recipient from non-followed' do expect(@results).to_not include(@not_followed_direct_status) end + it 'includes direct statuses mentioning recipient from followed' do + Fabricate(:mention, account: account, status: @followed_direct_status) + results2 = Status.as_direct_timeline(account) + expect(results2).to include(@followed_direct_status) + end + + it 'includes direct statuses mentioning recipient from non-followed' do + Fabricate(:mention, account: account, status: @not_followed_direct_status) + results2 = Status.as_direct_timeline(account) + expect(results2).to include(@not_followed_direct_status) + end end describe '.as_public_timeline' do From b0b34a5e381c8dd97abd69d817a8b82d3019085b Mon Sep 17 00:00:00 2001 From: Shuhei Kitagawa Date: Mon, 28 May 2018 22:56:58 +0900 Subject: [PATCH 023/111] Add a test for emojis_controller (#7652) --- spec/controllers/emojis_controller_spec.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 spec/controllers/emojis_controller_spec.rb diff --git a/spec/controllers/emojis_controller_spec.rb b/spec/controllers/emojis_controller_spec.rb new file mode 100644 index 0000000..68bae25 --- /dev/null +++ b/spec/controllers/emojis_controller_spec.rb @@ -0,0 +1,17 @@ +require 'rails_helper' + +describe EmojisController do + render_views + + let(:emoji) { Fabricate(:custom_emoji) } + + describe 'GET #show' do + subject(:responce) { get :show, params: { id: emoji.id, format: :json } } + subject(:body) { JSON.parse(response.body, symbolize_names: true) } + + it 'returns the right response' do + expect(responce).to have_http_status 200 + expect(body[:name]).to eq ':coolcat:' + end + end +end From 04a2cf8bcc7ab5878b29ee60af41dd661a6e19eb Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 28 May 2018 19:12:53 +0200 Subject: [PATCH 024/111] Fix incomplete flex style on trends items (#7655) --- app/javascript/styles/mastodon/components.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index c66bc42..a2a18b5 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -5259,6 +5259,9 @@ noscript { font-size: 14px; font-weight: 500; display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; &:hover, &:focus, @@ -5271,6 +5274,7 @@ noscript { } &__current { + flex: 0 0 auto; width: 100px; font-size: 24px; line-height: 36px; @@ -5280,6 +5284,7 @@ noscript { } &__sparkline { + flex: 0 0 auto; width: 50px; path { From c0355878ba00808d1d8710cb27b1fbb36bcb54e8 Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Tue, 29 May 2018 02:13:20 +0900 Subject: [PATCH 025/111] Fix embed, error and onboarding modals in light theme (#7656) --- app/javascript/styles/mastodon-light/diff.scss | 38 ++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss index 2b88b83..fe30431 100644 --- a/app/javascript/styles/mastodon-light/diff.scss +++ b/app/javascript/styles/mastodon-light/diff.scss @@ -132,20 +132,54 @@ .boost-modal, .confirmation-modal, .mute-modal, -.report-modal { +.report-modal, +.embed-modal, +.error-modal, +.onboarding-modal { background: $ui-base-color; } .boost-modal__action-bar, .confirmation-modal__action-bar, -.mute-modal__action-bar { +.mute-modal__action-bar, +.onboarding-modal__paginator, +.error-modal__footer { background: darken($ui-base-color, 6%); + + .onboarding-modal__nav, + .error-modal__nav { + &:hover, + &:focus, + &:active { + background-color: darken($ui-base-color, 12%); + } + } +} + +.display-case__case { + background: $white; +} + +.embed-modal .embed-modal__container .embed-modal__html { + background: $white; + + &:focus { + background: darken($ui-base-color, 6%); + } } .react-toggle-track { background: $ui-secondary-color; } +.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track { + background: darken($ui-secondary-color, 10%); +} + +.react-toggle.react-toggle--checked:hover:not(.react-toggle--disabled) .react-toggle-track { + background: lighten($ui-highlight-color, 10%); +} + // Change the default color used for the text in an empty column or on the error column .empty-column-indicator, .error-column { From 03f4c214b459a241a1aab08095a986475bd6f8f0 Mon Sep 17 00:00:00 2001 From: takayamaki Date: Tue, 29 May 2018 02:14:24 +0900 Subject: [PATCH 026/111] fix: Don't validate MX record in development (#7654) --- app/validators/email_mx_validator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/validators/email_mx_validator.rb b/app/validators/email_mx_validator.rb index d4c7cc2..3cc5853 100644 --- a/app/validators/email_mx_validator.rb +++ b/app/validators/email_mx_validator.rb @@ -4,7 +4,7 @@ require 'resolv' class EmailMxValidator < ActiveModel::Validator def validate(user) - return if Rails.env.test? + return if Rails.env.test? || Rails.env.development? user.errors.add(:email, I18n.t('users.invalid_email')) if invalid_mx?(user.email) end From d95642f6d913a99fc44f0ac0695d53534afb7962 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Tue, 29 May 2018 07:43:47 +0900 Subject: [PATCH 027/111] Cache attachments on external host with service worker (#7493) --- .env.production.sample | 8 ++++++++ .eslintrc.yml | 3 +++ app/javascript/mastodon/service_worker/entry.js | 2 +- config/webpack/production.js | 20 ++++++++++++++++++-- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/.env.production.sample b/.env.production.sample index 24b6b01..3047f75 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -88,6 +88,10 @@ SMTP_FROM_ADDRESS=notifications@example.com # CDN_HOST=https://assets.example.com # S3 (optional) +# The attachment host must allow cross origin request from WEB_DOMAIN or +# LOCAL_DOMAIN if WEB_DOMAIN is not set. For example, the server may have the +# following header field: +# Access-Control-Allow-Origin: https://192.168.1.123:9000/ # S3_ENABLED=true # S3_BUCKET= # AWS_ACCESS_KEY_ID= @@ -97,6 +101,8 @@ SMTP_FROM_ADDRESS=notifications@example.com # S3_HOSTNAME=192.168.1.123:9000 # S3 (Minio Config (optional) Please check Minio instance for details) +# The attachment host must allow cross origin request - see the description +# above. # S3_ENABLED=true # S3_BUCKET= # AWS_ACCESS_KEY_ID= @@ -108,6 +114,8 @@ SMTP_FROM_ADDRESS=notifications@example.com # S3_SIGNATURE_VERSION= # Swift (optional) +# The attachment host must allow cross origin request - see the description +# above. # SWIFT_ENABLED=true # SWIFT_USERNAME= # For Keystone V3, the value for SWIFT_TENANT should be the project name diff --git a/.eslintrc.yml b/.eslintrc.yml index 576e5b7..205c946 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -7,6 +7,9 @@ env: es6: true jest: true +globals: + ATTACHMENT_HOST: false + parser: babel-eslint plugins: diff --git a/app/javascript/mastodon/service_worker/entry.js b/app/javascript/mastodon/service_worker/entry.js index ce42271..c1854c1 100644 --- a/app/javascript/mastodon/service_worker/entry.js +++ b/app/javascript/mastodon/service_worker/entry.js @@ -49,7 +49,7 @@ self.addEventListener('fetch', function(event) { return response; })); - } else if (storageFreeable && process.env.CDN_HOST ? url.host === process.env.CDN_HOST : url.pathname.startsWith('/system/')) { + } else if (storageFreeable && (ATTACHMENT_HOST ? url.host === ATTACHMENT_HOST : url.pathname.startsWith('/system/'))) { event.respondWith(openSystemCache().then(cache => { return cache.match(event.request.url).then(cached => { if (cached === undefined) { diff --git a/config/webpack/production.js b/config/webpack/production.js index a823307..408c569 100644 --- a/config/webpack/production.js +++ b/config/webpack/production.js @@ -6,8 +6,9 @@ const CompressionPlugin = require('compression-webpack-plugin'); const sharedConfig = require('./shared.js'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const OfflinePlugin = require('offline-plugin'); -const { env, publicPath } = require('./configuration.js'); +const { publicPath } = require('./configuration.js'); const path = require('path'); +const { URL } = require('url'); let compressionAlgorithm; try { @@ -19,6 +20,21 @@ try { compressionAlgorithm = 'gzip'; } +let attachmentHost; + +if (process.env.S3_ENABLED === 'true') { + if (process.env.S3_CLOUDFRONT_HOST) { + attachmentHost = process.env.S3_CLOUDFRONT_HOST; + } else { + attachmentHost = process.env.S3_HOSTNAME || `s3-${process.env.S3_REGION || 'us-east-1'}.amazonaws.com`; + } +} else if (process.env.SWIFT_ENABLED === 'true') { + const { host } = new URL(process.env.SWIFT_OBJECT_URL); + attachmentHost = host; +} else { + attachmentHost = null; +} + module.exports = merge(sharedConfig, { output: { filename: '[name]-[chunkhash].js', @@ -90,7 +106,7 @@ module.exports = merge(sharedConfig, { '**/*.woff', ], ServiceWorker: { - entry: `imports-loader?process.env=>${encodeURIComponent(JSON.stringify(env))}!${encodeURI(path.join(__dirname, '../../app/javascript/mastodon/service_worker/entry.js'))}`, + entry: `imports-loader?ATTACHMENT_HOST=>${encodeURIComponent(JSON.stringify(attachmentHost))}!${encodeURI(path.join(__dirname, '../../app/javascript/mastodon/service_worker/entry.js'))}`, cacheName: 'mastodon', output: '../assets/sw.js', publicPath: '/sw.js', From e599d7caf2642c7143616e8402b7d730d32c349d Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 29 May 2018 01:39:02 +0200 Subject: [PATCH 028/111] Rescue Mastodon::DimensionsValidationError in Remoteable (#7662) Fix #7660 --- app/models/concerns/remotable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/concerns/remotable.rb b/app/models/concerns/remotable.rb index 20ddbca..c17f047 100644 --- a/app/models/concerns/remotable.rb +++ b/app/models/concerns/remotable.rb @@ -41,7 +41,7 @@ module Remotable rescue HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, Paperclip::Errors::NotIdentifiedByImageMagickError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e Rails.logger.debug "Error fetching remote #{attachment_name}: #{e}" nil - rescue Paperclip::Error => e + rescue Paperclip::Error, Mastodon::DimensionsValidationError => e Rails.logger.debug "Error processing remote #{attachment_name}: #{e}" nil end From 90b64c006998ec3bae365007781c61e8a79eeeef Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 29 May 2018 02:01:04 +0200 Subject: [PATCH 029/111] Always display tab navigation on local/federated timeline even when empty (#7663) Fix #7659 --- app/javascript/mastodon/components/scrollable_list.js | 11 ++++++++--- app/javascript/mastodon/components/status_list.js | 1 + app/javascript/mastodon/features/community_timeline/index.js | 1 + app/javascript/mastodon/features/public_timeline/index.js | 1 + 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/javascript/mastodon/components/scrollable_list.js b/app/javascript/mastodon/components/scrollable_list.js index fd6858d..4b433f3 100644 --- a/app/javascript/mastodon/components/scrollable_list.js +++ b/app/javascript/mastodon/components/scrollable_list.js @@ -25,6 +25,7 @@ export default class ScrollableList extends PureComponent { isLoading: PropTypes.bool, hasMore: PropTypes.bool, prepend: PropTypes.node, + alwaysPrepend: PropTypes.bool, emptyMessage: PropTypes.node, children: PropTypes.node, }; @@ -140,7 +141,7 @@ export default class ScrollableList extends PureComponent { } render () { - const { children, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage, onLoadMore } = this.props; + const { children, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, alwaysPrepend, emptyMessage, onLoadMore } = this.props; const { fullscreen } = this.state; const childrenCount = React.Children.count(children); @@ -172,8 +173,12 @@ export default class ScrollableList extends PureComponent { ); } else { scrollableArea = ( -
- {emptyMessage} +
+ {alwaysPrepend && prepend} + +
+ {emptyMessage} +
); } diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js index 0c971ce..1c34d06 100644 --- a/app/javascript/mastodon/components/status_list.js +++ b/app/javascript/mastodon/components/status_list.js @@ -24,6 +24,7 @@ export default class StatusList extends ImmutablePureComponent { hasMore: PropTypes.bool, prepend: PropTypes.node, emptyMessage: PropTypes.node, + alwaysPrepend: PropTypes.bool, }; static defaultProps = { diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js index f9ee835..d375edb 100644 --- a/app/javascript/mastodon/features/community_timeline/index.js +++ b/app/javascript/mastodon/features/community_timeline/index.js @@ -127,6 +127,7 @@ export default class CommunityTimeline extends React.PureComponent { Date: Tue, 29 May 2018 02:01:24 +0200 Subject: [PATCH 030/111] Add GET /api/v2/search which returns rich tag objects, adjust web UI (#7661) --- app/controllers/api/v2/search_controller.rb | 8 ++++ app/javascript/mastodon/actions/search.js | 2 +- .../features/compose/components/search_results.js | 56 +++++++++++----------- app/javascript/mastodon/reducers/search.js | 4 +- app/javascript/styles/mastodon/components.scss | 44 ++++++++--------- app/serializers/rest/v2/search_serializer.rb | 7 +++ config/routes.rb | 4 ++ 7 files changed, 69 insertions(+), 56 deletions(-) create mode 100644 app/controllers/api/v2/search_controller.rb create mode 100644 app/serializers/rest/v2/search_serializer.rb diff --git a/app/controllers/api/v2/search_controller.rb b/app/controllers/api/v2/search_controller.rb new file mode 100644 index 0000000..2e91d68 --- /dev/null +++ b/app/controllers/api/v2/search_controller.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class Api::V2::SearchController < Api::V1::SearchController + def index + @search = Search.new(search) + render json: @search, serializer: REST::V2::SearchSerializer + end +end diff --git a/app/javascript/mastodon/actions/search.js b/app/javascript/mastodon/actions/search.js index 882c170..b670d25 100644 --- a/app/javascript/mastodon/actions/search.js +++ b/app/javascript/mastodon/actions/search.js @@ -33,7 +33,7 @@ export function submitSearch() { dispatch(fetchSearchRequest()); - api(getState).get('/api/v1/search', { + api(getState).get('/api/v2/search', { params: { q: value, resolve: true, diff --git a/app/javascript/mastodon/features/compose/components/search_results.js b/app/javascript/mastodon/features/compose/components/search_results.js index f2655c1..445bf27 100644 --- a/app/javascript/mastodon/features/compose/components/search_results.js +++ b/app/javascript/mastodon/features/compose/components/search_results.js @@ -16,6 +16,28 @@ const shortNumberFormat = number => { } }; +const renderHashtag = hashtag => ( +
+
+ + #{hashtag.get('name')} + + + {shortNumberFormat(hashtag.getIn(['history', 0, 'accounts']))} }} /> +
+ +
+ {shortNumberFormat(hashtag.getIn(['history', 0, 'uses']))} +
+ +
+ day.get('uses')).toArray()}> + + +
+
+); + export default class SearchResults extends ImmutablePureComponent { static propTypes = { @@ -44,27 +66,7 @@ export default class SearchResults extends ImmutablePureComponent {
- {trends && trends.map(hashtag => ( -
-
- - #{hashtag.get('name')} - - - {shortNumberFormat(hashtag.getIn(['history', 0, 'accounts']))} }} /> -
- -
- {shortNumberFormat(hashtag.getIn(['history', 0, 'uses']))} -
- -
- day.get('uses')).toArray()}> - - -
-
- ))} + {trends && trends.map(hashtag => renderHashtag(hashtag))}
); @@ -74,7 +76,7 @@ export default class SearchResults extends ImmutablePureComponent { count += results.get('accounts').size; accounts = (
-
+
{results.get('accounts').map(accountId => )}
@@ -85,7 +87,7 @@ export default class SearchResults extends ImmutablePureComponent { count += results.get('statuses').size; statuses = (
-
+
{results.get('statuses').map(statusId => )}
@@ -96,13 +98,9 @@ export default class SearchResults extends ImmutablePureComponent { count += results.get('hashtags').size; hashtags = (
-
+
- {results.get('hashtags').map(hashtag => ( - - {hashtag} - - ))} + {results.get('hashtags').map(hashtag => renderHashtag(hashtag))}
); } diff --git a/app/javascript/mastodon/reducers/search.js b/app/javascript/mastodon/reducers/search.js index 56fd722..4758def 100644 --- a/app/javascript/mastodon/reducers/search.js +++ b/app/javascript/mastodon/reducers/search.js @@ -9,7 +9,7 @@ import { COMPOSE_REPLY, COMPOSE_DIRECT, } from '../actions/compose'; -import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; const initialState = ImmutableMap({ value: '', @@ -39,7 +39,7 @@ export default function search(state = initialState, action) { return state.set('results', ImmutableMap({ accounts: ImmutableList(action.results.accounts.map(item => item.id)), statuses: ImmutableList(action.results.statuses.map(item => item.id)), - hashtags: ImmutableList(action.results.hashtags), + hashtags: fromJS(action.results.hashtags), })).set('submitted', true); default: return state; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index a2a18b5..c93d8e8 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -3284,6 +3284,15 @@ a.status-card { } .search__icon { + &::-moz-focus-inner { + border: 0; + } + + &::-moz-focus-inner, + &:focus { + outline: 0 !important; + } + .fa { position: absolute; top: 10px; @@ -3333,7 +3342,6 @@ a.status-card { .search-results__header { color: $dark-text-color; background: lighten($ui-base-color, 2%); - border-bottom: 1px solid darken($ui-base-color, 4%); padding: 15px; font-weight: 500; font-size: 16px; @@ -3346,33 +3354,21 @@ a.status-card { } .search-results__section { - margin-bottom: 20px; + margin-bottom: 5px; h5 { - position: relative; - - &::before { - content: ""; - display: block; - position: absolute; - left: 0; - right: 0; - top: 50%; - width: 100%; - height: 0; - border-top: 1px solid lighten($ui-base-color, 8%); - } + background: darken($ui-base-color, 4%); + border-bottom: 1px solid lighten($ui-base-color, 8%); + cursor: default; + display: flex; + padding: 15px; + font-weight: 500; + font-size: 16px; + color: $dark-text-color; - span { + .fa { display: inline-block; - background: $ui-base-color; - color: $darker-text-color; - font-size: 14px; - font-weight: 500; - padding: 10px; - position: relative; - z-index: 1; - cursor: default; + margin-right: 5px; } } diff --git a/app/serializers/rest/v2/search_serializer.rb b/app/serializers/rest/v2/search_serializer.rb new file mode 100644 index 0000000..cdb6b3a --- /dev/null +++ b/app/serializers/rest/v2/search_serializer.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class REST::V2::SearchSerializer < ActiveModel::Serializer + has_many :accounts, serializer: REST::AccountSerializer + has_many :statuses, serializer: REST::StatusSerializer + has_many :hashtags, serializer: REST::TagSerializer +end diff --git a/config/routes.rb b/config/routes.rb index 2fcb885..31e90e2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -315,6 +315,10 @@ Rails.application.routes.draw do end end + namespace :v2 do + get '/search', to: 'search#index', as: :search + end + namespace :web do resource :settings, only: [:update] resource :embed, only: [:create] From 13b60e6a146bb4717586573d63fa27f93d03a8fd Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Tue, 29 May 2018 20:33:20 +0900 Subject: [PATCH 031/111] Use URL polyfill (#7664) --- config/webpack/production.js | 2 +- package.json | 3 ++- yarn.lock | 20 +++++++++++++++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/config/webpack/production.js b/config/webpack/production.js index 408c569..1469a94 100644 --- a/config/webpack/production.js +++ b/config/webpack/production.js @@ -8,7 +8,7 @@ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPl const OfflinePlugin = require('offline-plugin'); const { publicPath } = require('./configuration.js'); const path = require('path'); -const { URL } = require('url'); +const { URL } = require('whatwg-url'); let compressionAlgorithm; try { diff --git a/package.json b/package.json index 6ee6f98..893be30 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,8 @@ "webpack-bundle-analyzer": "^2.9.1", "webpack-manifest-plugin": "^1.2.1", "webpack-merge": "^4.1.1", - "websocket.js": "^0.1.12" + "websocket.js": "^0.1.12", + "whatwg-url": "^6.4.1" }, "devDependencies": { "babel-eslint": "^8.2.1", diff --git a/yarn.lock b/yarn.lock index de48c99..e0f1674 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4373,6 +4373,10 @@ lodash.restparam@^3.0.0: version "3.6.1" resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + lodash.tail@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664" @@ -7257,6 +7261,12 @@ tough-cookie@^2.3.2, tough-cookie@~2.3.0, tough-cookie@~2.3.3: dependencies: punycode "^1.4.1" +tr46@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" + dependencies: + punycode "^2.1.0" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -7494,7 +7504,7 @@ webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" -webidl-conversions@^4.0.0: +webidl-conversions@^4.0.0, webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" @@ -7637,6 +7647,14 @@ whatwg-url@^4.3.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" +whatwg-url@^6.4.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.4.1.tgz#fdb94b440fd4ad836202c16e9737d511f012fd67" + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + whet.extend@~0.9.9: version "0.9.9" resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1" From 90908fc24ba57c877de75fe117b8cc234e29d4f0 Mon Sep 17 00:00:00 2001 From: abcang Date: Tue, 29 May 2018 20:34:02 +0900 Subject: [PATCH 032/111] Fix N+1 on AtomSerializer (#7669) --- app/lib/ostatus/atom_serializer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/ostatus/atom_serializer.rb b/app/lib/ostatus/atom_serializer.rb index 698f2ee..5c6ff4f 100644 --- a/app/lib/ostatus/atom_serializer.rb +++ b/app/lib/ostatus/atom_serializer.rb @@ -354,7 +354,7 @@ class OStatus::AtomSerializer append_element(entry, 'summary', status.spoiler_text, 'xml:lang': status.language) if status.spoiler_text? append_element(entry, 'content', Formatter.instance.format(status).to_str, type: 'html', 'xml:lang': status.language) - status.mentions.order(:id).each do |mentioned| + status.mentions.sort_by(&:id).each do |mentioned| append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': OStatus::TagManager::TYPES[:person], href: OStatus::TagManager.instance.uri_for(mentioned.account)) end From 0345cd5a0f434a43ea988a2b50ab6f8597bcf58e Mon Sep 17 00:00:00 2001 From: ThibG Date: Tue, 29 May 2018 16:25:05 +0200 Subject: [PATCH 033/111] Fix error when unmuting a domain without listing muted domains first (#7670) --- app/javascript/mastodon/reducers/domain_lists.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/reducers/domain_lists.js b/app/javascript/mastodon/reducers/domain_lists.js index a9e3519..eff97fb 100644 --- a/app/javascript/mastodon/reducers/domain_lists.js +++ b/app/javascript/mastodon/reducers/domain_lists.js @@ -6,7 +6,9 @@ import { import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable'; const initialState = ImmutableMap({ - blocks: ImmutableMap(), + blocks: ImmutableMap({ + items: ImmutableOrderedSet(), + }), }); export default function domainLists(state = initialState, action) { From 7706ed038f1a16d232635d564a28e6f90e53e0fd Mon Sep 17 00:00:00 2001 From: unarist Date: Wed, 30 May 2018 00:42:29 +0900 Subject: [PATCH 034/111] Fix context building in the reducer (#7671) This fixes below bugs: * addReply() had used native compare operator for string ids => descendants may appears at wrong position * CONTEXT_FETCH_SUCCESS had added the focused status as the reply of the *first* status in ancestors, not last status. => descendants may also appears wrong position as well as correct position * TIMELINE_UPDATE had added the status to replies of *itself* instead of replied status => browser will hangs if you open the status due to a circular reference --- app/javascript/mastodon/reducers/contexts.js | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/app/javascript/mastodon/reducers/contexts.js b/app/javascript/mastodon/reducers/contexts.js index 53e70b5..4c2d6cc 100644 --- a/app/javascript/mastodon/reducers/contexts.js +++ b/app/javascript/mastodon/reducers/contexts.js @@ -5,6 +5,7 @@ import { import { CONTEXT_FETCH_SUCCESS } from '../actions/statuses'; import { TIMELINE_DELETE, TIMELINE_UPDATE } from '../actions/timelines'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import compareId from '../compare_id'; const initialState = ImmutableMap({ inReplyTos: ImmutableMap(), @@ -15,27 +16,27 @@ const normalizeContext = (immutableState, id, ancestors, descendants) => immutab state.update('inReplyTos', immutableAncestors => immutableAncestors.withMutations(inReplyTos => { state.update('replies', immutableDescendants => immutableDescendants.withMutations(replies => { function addReply({ id, in_reply_to_id }) { - if (in_reply_to_id) { - const siblings = replies.get(in_reply_to_id, ImmutableList()); + if (in_reply_to_id && !inReplyTos.has(id)) { - if (!siblings.includes(id)) { - const index = siblings.findLastIndex(sibling => sibling.id < id); - replies.set(in_reply_to_id, siblings.insert(index + 1, id)); - } + replies.update(in_reply_to_id, ImmutableList(), siblings => { + const index = siblings.findLastIndex(sibling => compareId(sibling, id) < 0); + return siblings.insert(index + 1, id); + }); inReplyTos.set(id, in_reply_to_id); } } - if (ancestors[0]) { - addReply({ id, in_reply_to_id: ancestors[0].id }); - } + // We know in_reply_to_id of statuses but `id` itself. + // So we assume that the status of the id replies to last ancestors. - if (descendants[0]) { - addReply({ id: descendants[0].id, in_reply_to_id: id }); + ancestors.forEach(addReply); + + if (ancestors[0]) { + addReply({ id, in_reply_to_id: ancestors[ancestors.length - 1].id }); } - [ancestors, descendants].forEach(statuses => statuses.forEach(addReply)); + descendants.forEach(addReply); })); })); }); @@ -80,7 +81,7 @@ const updateContext = (state, status) => { mutable.setIn(['inReplyTos', status.id], status.in_reply_to_id); if (!replies.includes(status.id)) { - mutable.setIn(['replies', status.id], replies.push(status.id)); + mutable.setIn(['replies', status.in_reply_to_id], replies.push(status.id)); } }); } From 461542784b555237316f3dd5e32ea224cd3ab8ef Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 29 May 2018 22:55:33 +0200 Subject: [PATCH 035/111] Reduce wasted work in RemoveStatusService due to inactive followers (#7672) --- app/models/concerns/account_interactions.rb | 11 +++++++++++ app/services/batched_remove_status_service.rb | 4 ++-- app/services/fan_out_on_write_service.rb | 4 ++-- app/services/remove_status_service.rb | 4 ++-- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb index ae43711..ef59f5d 100644 --- a/app/models/concerns/account_interactions.rb +++ b/app/models/concerns/account_interactions.rb @@ -183,4 +183,15 @@ module AccountInteractions def pinned?(status) status_pins.where(status: status).exists? end + + def followers_for_local_distribution + followers.local + .joins(:user) + .where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago) + end + + def lists_for_local_distribution + lists.joins(account: :user) + .where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago) + end end diff --git a/app/services/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb index ace51a1..dab1c47 100644 --- a/app/services/batched_remove_status_service.rb +++ b/app/services/batched_remove_status_service.rb @@ -53,7 +53,7 @@ class BatchedRemoveStatusService < BaseService end def unpush_from_home_timelines(account, statuses) - recipients = account.followers.local.to_a + recipients = account.followers_for_local_distribution.to_a recipients << account if account.local? @@ -65,7 +65,7 @@ class BatchedRemoveStatusService < BaseService end def unpush_from_list_timelines(account, statuses) - account.lists.select(:id, :account_id).each do |list| + account.lists_for_local_distribution.select(:id, :account_id).each do |list| statuses.each do |status| FeedManager.instance.unpush_from_list(list, status) end diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index 8b36302..5efd3ed 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -38,7 +38,7 @@ class FanOutOnWriteService < BaseService def deliver_to_followers(status) Rails.logger.debug "Delivering status #{status.id} to followers" - status.account.followers.where(domain: nil).joins(:user).where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago).select(:id).reorder(nil).find_in_batches do |followers| + status.account.followers_for_local_distribution.select(:id).reorder(nil).find_in_batches do |followers| FeedInsertWorker.push_bulk(followers) do |follower| [status.id, follower.id, :home] end @@ -48,7 +48,7 @@ class FanOutOnWriteService < BaseService def deliver_to_lists(status) Rails.logger.debug "Delivering status #{status.id} to lists" - status.account.lists.joins(account: :user).where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago).select(:id).reorder(nil).find_in_batches do |lists| + status.account.lists_for_local_distribution.select(:id).reorder(nil).find_in_batches do |lists| FeedInsertWorker.push_bulk(lists) do |list| [status.id, list.id, :list] end diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index 8c3e184..b963107 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -43,13 +43,13 @@ class RemoveStatusService < BaseService end def remove_from_followers - @account.followers.local.find_each do |follower| + @account.followers_for_local_distribution.find_each do |follower| FeedManager.instance.unpush_from_home(follower, @status) end end def remove_from_lists - @account.lists.select(:id, :account_id).find_each do |list| + @account.lists_for_local_distribution.select(:id, :account_id).find_each do |list| FeedManager.instance.unpush_from_list(list, @status) end end From a7d726c3836a87006cedcdc4bd186f8aff89d093 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 30 May 2018 02:50:23 +0200 Subject: [PATCH 036/111] Improve counter caches on Status and Account (#7644) Do not touch statuses_count on accounts table when mass-destroying statuses to reduce load when removing accounts, same for reblogs_count and favourites_count Do not count statuses with direct visibility in statuses_count Fix #828 --- app/models/favourite.rb | 25 ++++++++++++- app/models/status.rb | 51 +++++++++++++++++++++++++-- app/services/batched_remove_status_service.rb | 5 ++- app/services/suspend_account_service.rb | 7 ++-- spec/models/account_spec.rb | 31 ++++++++++++++++ spec/models/status_spec.rb | 14 ++++++++ 6 files changed, 126 insertions(+), 7 deletions(-) diff --git a/app/models/favourite.rb b/app/models/favourite.rb index c998a67..0fce82f 100644 --- a/app/models/favourite.rb +++ b/app/models/favourite.rb @@ -16,7 +16,7 @@ class Favourite < ApplicationRecord update_index('statuses#status', :status) if Chewy.enabled? belongs_to :account, inverse_of: :favourites - belongs_to :status, inverse_of: :favourites, counter_cache: true + belongs_to :status, inverse_of: :favourites has_one :notification, as: :activity, dependent: :destroy @@ -25,4 +25,27 @@ class Favourite < ApplicationRecord before_validation do self.status = status.reblog if status&.reblog? end + + after_create :increment_cache_counters + after_destroy :decrement_cache_counters + + private + + def increment_cache_counters + if association(:status).loaded? + status.update_attribute(:favourites_count, status.favourites_count + 1) + else + Status.where(id: status_id).update_all('favourites_count = COALESCE(favourites_count, 0) + 1') + end + end + + def decrement_cache_counters + return if association(:status).loaded? && (status.marked_for_destruction? || status.marked_for_mass_destruction?) + + if association(:status).loaded? + status.update_attribute(:favourites_count, [status.favourites_count - 1, 0].max) + else + Status.where(id: status_id).update_all('favourites_count = GREATEST(COALESCE(favourites_count, 0) - 1, 0)') + end + end end diff --git a/app/models/status.rb b/app/models/status.rb index 54f3f68..08ec36f 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -41,12 +41,12 @@ class Status < ApplicationRecord belongs_to :application, class_name: 'Doorkeeper::Application', optional: true - belongs_to :account, inverse_of: :statuses, counter_cache: true + belongs_to :account, inverse_of: :statuses belongs_to :in_reply_to_account, foreign_key: 'in_reply_to_account_id', class_name: 'Account', optional: true belongs_to :conversation, optional: true belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :replies, optional: true - belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblogs, counter_cache: :reblogs_count, optional: true + belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblogs, optional: true has_many :favourites, inverse_of: :status, dependent: :destroy has_many :reblogs, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblog, dependent: :destroy @@ -167,6 +167,17 @@ class Status < ApplicationRecord @emojis ||= CustomEmoji.from_text([spoiler_text, text].join(' '), account.domain) end + def mark_for_mass_destruction! + @marked_for_mass_destruction = true + end + + def marked_for_mass_destruction? + @marked_for_mass_destruction + end + + after_create :increment_counter_caches + after_destroy :decrement_counter_caches + after_create_commit :store_uri, if: :local? after_create_commit :update_statistics, if: :local? @@ -388,4 +399,40 @@ class Status < ApplicationRecord return unless public_visibility? || unlisted_visibility? ActivityTracker.increment('activity:statuses:local') end + + def increment_counter_caches + return if direct_visibility? + + if association(:account).loaded? + account.update_attribute(:statuses_count, account.statuses_count + 1) + else + Account.where(id: account_id).update_all('statuses_count = COALESCE(statuses_count, 0) + 1') + end + + return unless reblog? + + if association(:reblog).loaded? + reblog.update_attribute(:reblogs_count, reblog.reblogs_count + 1) + else + Status.where(id: reblog_of_id).update_all('reblogs_count = COALESCE(reblogs_count, 0) + 1') + end + end + + def decrement_counter_caches + return if direct_visibility? || marked_for_mass_destruction? + + if association(:account).loaded? + account.update_attribute(:statuses_count, [account.statuses_count - 1, 0].max) + else + Account.where(id: account_id).update_all('statuses_count = GREATEST(COALESCE(statuses_count, 0) - 1, 0)') + end + + return unless reblog? + + if association(:reblog).loaded? + reblog.update_attribute(:reblogs_count, [reblog.reblogs_count - 1, 0].max) + else + Status.where(id: reblog_of_id).update_all('reblogs_count = GREATEST(COALESCE(reblogs_count, 0) - 1, 0)') + end + end end diff --git a/app/services/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb index dab1c47..ebb4034 100644 --- a/app/services/batched_remove_status_service.rb +++ b/app/services/batched_remove_status_service.rb @@ -21,7 +21,10 @@ class BatchedRemoveStatusService < BaseService @activity_xml = {} # Ensure that rendered XML reflects destroyed state - statuses.each(&:destroy) + statuses.each do |status| + status.mark_for_mass_destruction! + status.destroy + end # Batch by source account statuses.group_by(&:account_id).each_value do |account_statuses| diff --git a/app/services/suspend_account_service.rb b/app/services/suspend_account_service.rb index 56fa2d8..708d15e 100644 --- a/app/services/suspend_account_service.rb +++ b/app/services/suspend_account_service.rb @@ -41,9 +41,10 @@ class SuspendAccountService < BaseService end def purge_profile! - @account.suspended = true - @account.display_name = '' - @account.note = '' + @account.suspended = true + @account.display_name = '' + @account.note = '' + @account.statuses_count = 0 @account.avatar.destroy @account.header.destroy @account.save! diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 3aaaa55..cce659a 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -525,6 +525,37 @@ RSpec.describe Account, type: :model do end end + describe '#statuses_count' do + subject { Fabricate(:account) } + + it 'counts statuses' do + Fabricate(:status, account: subject) + Fabricate(:status, account: subject) + expect(subject.statuses_count).to eq 2 + end + + it 'does not count direct statuses' do + Fabricate(:status, account: subject, visibility: :direct) + expect(subject.statuses_count).to eq 0 + end + + it 'is decremented when status is removed' do + status = Fabricate(:status, account: subject) + expect(subject.statuses_count).to eq 1 + status.destroy + expect(subject.statuses_count).to eq 0 + end + + it 'is decremented when status is removed when account is not preloaded' do + status = Fabricate(:status, account: subject) + expect(subject.reload.statuses_count).to eq 1 + clean_status = Status.find(status.id) + expect(clean_status.association(:account).loaded?).to be false + clean_status.destroy + expect(subject.reload.statuses_count).to eq 0 + end + end + describe '.following_map' do it 'returns an hash' do expect(Account.following_map([], 1)).to be_a Hash diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb index aee4f49..5113b65 100644 --- a/spec/models/status_spec.rb +++ b/spec/models/status_spec.rb @@ -175,6 +175,13 @@ RSpec.describe Status, type: :model do expect(subject.reblogs_count).to eq 2 end + + it 'is decremented when reblog is removed' do + reblog = Fabricate(:status, account: bob, reblog: subject) + expect(subject.reblogs_count).to eq 1 + reblog.destroy + expect(subject.reblogs_count).to eq 0 + end end describe '#favourites_count' do @@ -184,6 +191,13 @@ RSpec.describe Status, type: :model do expect(subject.favourites_count).to eq 2 end + + it 'is decremented when favourite is removed' do + favourite = Fabricate(:favourite, account: bob, status: subject) + expect(subject.favourites_count).to eq 1 + favourite.destroy + expect(subject.favourites_count).to eq 0 + end end describe '#proper' do From a16e06bbf584412c5a0f812da37fcd6e2c479d1a Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 30 May 2018 02:51:26 +0200 Subject: [PATCH 037/111] Deduplicate accounts and make unique username/domain index case-insensitive (#7658) Fix #6937 Fix #6837 Fix #6667 --- .../20180528141303_fix_accounts_unique_index.rb | 84 ++++++++++++++++++++++ db/schema.rb | 5 +- 2 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20180528141303_fix_accounts_unique_index.rb diff --git a/db/migrate/20180528141303_fix_accounts_unique_index.rb b/db/migrate/20180528141303_fix_accounts_unique_index.rb new file mode 100644 index 0000000..2e5eca8 --- /dev/null +++ b/db/migrate/20180528141303_fix_accounts_unique_index.rb @@ -0,0 +1,84 @@ +class FixAccountsUniqueIndex < ActiveRecord::Migration[5.2] + disable_ddl_transaction! + + def up + say '' + say 'WARNING: This migration may take a *long* time for large instances' + say 'It will *not* lock tables for any significant time, but it may run' + say 'for a very long time. We will pause for 10 seconds to allow you to' + say 'interrupt this migration if you are not ready.' + say '' + say 'This migration will irreversibly delete user accounts with duplicate' + say 'usernames. You may use the `rake mastodon:maintenance:find_duplicate_usernames`' + say 'task to manually deal with such accounts before running this migration.' + + 10.downto(1) do |i| + say "Continuing in #{i} second#{i == 1 ? '' : 's'}...", true + sleep 1 + end + + duplicates = Account.connection.select_all('SELECT string_agg(id::text, \',\') AS ids FROM accounts GROUP BY lower(username), lower(domain) HAVING count(*) > 1').to_hash + + duplicates.each do |row| + deduplicate_account!(row['ids'].split(',')) + end + + remove_index :accounts, name: 'index_accounts_on_username_and_domain_lower' if index_name_exists?(:accounts, 'index_accounts_on_username_and_domain_lower') + safety_assured { execute 'CREATE UNIQUE INDEX CONCURRENTLY index_accounts_on_username_and_domain_lower ON accounts (lower(username), lower(domain))' } + remove_index :accounts, name: 'index_accounts_on_username_and_domain' if index_name_exists?(:accounts, 'index_accounts_on_username_and_domain') + end + + def down + raise ActiveRecord::IrreversibleMigration + end + + private + + def deduplicate_account!(account_ids) + accounts = Account.where(id: account_ids).to_a + accounts = account.first.local? ? accounts.sort_by(&:created_at) : accounts.sort_by(&:updated_at).reverse + reference_account = accounts.shift + + accounts.each do |other_account| + if other_account.public_key == reference_account.public_key + # The accounts definitely point to the same resource, so + # it's safe to re-attribute content and relationships + merge_accounts!(reference_account, other_account) + elsif other_account.local? + # Since domain is in the GROUP BY clause, both accounts + # are always either going to be local or not local, so only + # one check is needed. Since we cannot support two users with + # the same username locally, one has to go. 😢 + other_account.user.destroy + end + + other_account.destroy + end + end + + def merge_accounts!(main_account, duplicate_account) + [Status, Favourite, Mention, StatusPin, StreamEntry].each do |klass| + klass.where(account_id: duplicate_account.id).update_all(account_id: main_account.id) + end + + # Since it's the same remote resource, the remote resource likely + # already believes we are following/blocking, so it's safe to + # re-attribute the relationships too. However, during the presence + # of the index bug users could have *also* followed the reference + # account already, therefore mass update will not work and we need + # to check for (and skip past) uniqueness errors + [Follow, FollowRequest, Block, Mute].each do |klass| + klass.where(account_id: duplicate_account.id).find_each do |record| + record.update(account_id: main_account.id) + rescue ActiveRecord::RecordNotUnique + next + end + + klass.where(target_account_id: duplicate_account.id).find_each do |record| + record.update(target_account_id: main_account.id) + rescue ActiveRecord::RecordNotUnique + next + end + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 7435b6c..c9d4e0f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2018_05_14_140000) do +ActiveRecord::Schema.define(version: 2018_05_28_141303) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -77,10 +77,9 @@ ActiveRecord::Schema.define(version: 2018_05_14_140000) do t.jsonb "fields" t.string "actor_type" t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin - t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower" + t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower", unique: true t.index ["uri"], name: "index_accounts_on_uri" t.index ["url"], name: "index_accounts_on_url" - t.index ["username", "domain"], name: "index_accounts_on_username_and_domain", unique: true end create_table "admin_action_logs", force: :cascade do |t| From 9130b3cda9cd460aa137e399a8b50880aba3bb63 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Wed, 30 May 2018 16:39:52 +0900 Subject: [PATCH 038/111] Fix broken migrate (regression from #7658) (#7674) --- db/migrate/20180528141303_fix_accounts_unique_index.rb | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/db/migrate/20180528141303_fix_accounts_unique_index.rb b/db/migrate/20180528141303_fix_accounts_unique_index.rb index 2e5eca8..92e490f 100644 --- a/db/migrate/20180528141303_fix_accounts_unique_index.rb +++ b/db/migrate/20180528141303_fix_accounts_unique_index.rb @@ -36,7 +36,7 @@ class FixAccountsUniqueIndex < ActiveRecord::Migration[5.2] def deduplicate_account!(account_ids) accounts = Account.where(id: account_ids).to_a - accounts = account.first.local? ? accounts.sort_by(&:created_at) : accounts.sort_by(&:updated_at).reverse + accounts = accounts.first.local? ? accounts.sort_by(&:created_at) : accounts.sort_by(&:updated_at).reverse reference_account = accounts.shift accounts.each do |other_account| @@ -69,15 +69,19 @@ class FixAccountsUniqueIndex < ActiveRecord::Migration[5.2] # to check for (and skip past) uniqueness errors [Follow, FollowRequest, Block, Mute].each do |klass| klass.where(account_id: duplicate_account.id).find_each do |record| - record.update(account_id: main_account.id) - rescue ActiveRecord::RecordNotUnique - next + begin + record.update(account_id: main_account.id) + rescue ActiveRecord::RecordNotUnique + next + end end klass.where(target_account_id: duplicate_account.id).find_each do |record| - record.update(target_account_id: main_account.id) - rescue ActiveRecord::RecordNotUnique - next + begin + record.update(target_account_id: main_account.id) + rescue ActiveRecord::RecordNotUnique + next + end end end end From 1a7a74ff76a129031a3fd6d73688ab9409899002 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 30 May 2018 18:41:47 +0200 Subject: [PATCH 039/111] Improve getting started column (#7676) * Adjust footer of getting started column - Improved style - Moved hotkeys, about this instance and logout to footer - Removed FAQ, User Guide, Apps links - Use hamburger icon for the column * Add edit profile action button to profile and more to dropdown * Add "Trending now" to getting started column * Add preferences/security links on mobile layout --- app/javascript/mastodon/components/hashtag.js | 41 ++++++++ .../features/account/components/action_bar.js | 28 +++++- .../mastodon/features/account/components/header.js | 11 +++ .../features/compose/components/search_results.js | 39 +------- app/javascript/mastodon/features/compose/index.js | 2 +- .../mastodon/features/domain_blocks/index.js | 2 +- .../mastodon/features/getting_started/index.js | 109 ++++++++++++--------- app/javascript/mastodon/locales/en.json | 2 +- app/javascript/styles/mastodon/components.scss | 71 +++++++++----- 9 files changed, 197 insertions(+), 108 deletions(-) create mode 100644 app/javascript/mastodon/components/hashtag.js diff --git a/app/javascript/mastodon/components/hashtag.js b/app/javascript/mastodon/components/hashtag.js new file mode 100644 index 0000000..cc37a91 --- /dev/null +++ b/app/javascript/mastodon/components/hashtag.js @@ -0,0 +1,41 @@ +import React from 'react'; +import { Sparklines, SparklinesCurve } from 'react-sparklines'; +import { Link } from 'react-router-dom'; +import { FormattedMessage, FormattedNumber } from 'react-intl'; +import ImmutablePropTypes from 'react-immutable-proptypes'; + +const shortNumberFormat = number => { + if (number < 1000) { + return ; + } else { + return K; + } +}; + +const Hashtag = ({ hashtag }) => ( +
+
+ + #{hashtag.get('name')} + + + {shortNumberFormat(hashtag.getIn(['history', 0, 'accounts']))} }} /> +
+ +
+ {shortNumberFormat(hashtag.getIn(['history', 0, 'uses']))} +
+ +
+ day.get('uses')).toArray()}> + + +
+
+); + +Hashtag.propTypes = { + hashtag: ImmutablePropTypes.map.isRequired, +}; + +export default Hashtag; diff --git a/app/javascript/mastodon/features/account/components/action_bar.js b/app/javascript/mastodon/features/account/components/action_bar.js index 23dbf32..3a1f928 100644 --- a/app/javascript/mastodon/features/account/components/action_bar.js +++ b/app/javascript/mastodon/features/account/components/action_bar.js @@ -23,6 +23,14 @@ const messages = defineMessages({ unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' }, hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide boosts from @{name}' }, showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' }, + pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' }, + preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, + follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, + favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, + lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' }, + blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, + domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' }, + mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, }); @injectIntl @@ -54,17 +62,29 @@ export default class ActionBar extends React.PureComponent { let menu = []; let extraInfo = ''; - menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention }); - menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect }); + if (account.get('id') !== me) { + menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention }); + menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect }); + menu.push(null); + } if ('share' in navigator) { menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare }); + menu.push(null); } - menu.push(null); - if (account.get('id') === me) { menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' }); + menu.push({ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' }); + menu.push({ text: intl.formatMessage(messages.pins), to: '/pinned' }); + menu.push(null); + menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' }); + menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' }); + menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' }); + menu.push(null); + menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' }); + menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' }); + menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' }); } else { if (account.getIn(['relationship', 'following'])) { if (account.getIn(['relationship', 'showing_reblogs'])) { diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index 7358053..dd2cd40 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -14,6 +14,7 @@ const messages = defineMessages({ follow: { id: 'account.follow', defaultMessage: 'Follow' }, requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' }, unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, + edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, }); class Avatar extends ImmutablePureComponent { @@ -74,6 +75,10 @@ export default class Header extends ImmutablePureComponent { intl: PropTypes.object.isRequired, }; + openEditProfile = () => { + window.open('/settings/profile', '_blank'); + } + render () { const { account, intl } = this.props; @@ -118,6 +123,12 @@ export default class Header extends ImmutablePureComponent { ); } + } else { + actionBtn = ( +
+ +
+ ); } if (account.get('moved') && !account.getIn(['relationship', 'following'])) { diff --git a/app/javascript/mastodon/features/compose/components/search_results.js b/app/javascript/mastodon/features/compose/components/search_results.js index 445bf27..cf02236 100644 --- a/app/javascript/mastodon/features/compose/components/search_results.js +++ b/app/javascript/mastodon/features/compose/components/search_results.js @@ -1,42 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { FormattedMessage, FormattedNumber } from 'react-intl'; +import { FormattedMessage } from 'react-intl'; import AccountContainer from '../../../containers/account_container'; import StatusContainer from '../../../containers/status_container'; -import { Link } from 'react-router-dom'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { Sparklines, SparklinesCurve } from 'react-sparklines'; - -const shortNumberFormat = number => { - if (number < 1000) { - return ; - } else { - return K; - } -}; - -const renderHashtag = hashtag => ( -
-
- - #{hashtag.get('name')} - - - {shortNumberFormat(hashtag.getIn(['history', 0, 'accounts']))} }} /> -
- -
- {shortNumberFormat(hashtag.getIn(['history', 0, 'uses']))} -
- -
- day.get('uses')).toArray()}> - - -
-
-); +import Hashtag from '../../../components/hashtag'; export default class SearchResults extends ImmutablePureComponent { @@ -66,7 +35,7 @@ export default class SearchResults extends ImmutablePureComponent { - {trends && trends.map(hashtag => renderHashtag(hashtag))} + {trends && trends.map(hashtag => )} ); @@ -100,7 +69,7 @@ export default class SearchResults extends ImmutablePureComponent {
- {results.get('hashtags').map(hashtag => renderHashtag(hashtag))} + {results.get('hashtags').map(hashtag => )}
); } diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js index d8e9ad9..df1ec49 100644 --- a/app/javascript/mastodon/features/compose/index.js +++ b/app/javascript/mastodon/features/compose/index.js @@ -75,7 +75,7 @@ export default class Compose extends React.PureComponent { const { columns } = this.props; header = (