Merge branch 'master' into glitch-soc/merge-upstream
This commit is contained in:
		
						commit
						c954f89bdd
					
				
					 44 changed files with 267 additions and 256 deletions
				
			
		|  | @ -178,7 +178,7 @@ jobs: | ||||||
|       - *attach_workspace |       - *attach_workspace | ||||||
|       - run: bundle exec i18n-tasks check-normalized |       - run: bundle exec i18n-tasks check-normalized | ||||||
|       - run: bundle exec i18n-tasks unused |       - run: bundle exec i18n-tasks unused | ||||||
|       - run: bundle exec i18n-tasks missing-plural-keys |       - run: bundle exec i18n-tasks missing -t plural | ||||||
|       - run: bundle exec i18n-tasks check-consistent-interpolations |       - run: bundle exec i18n-tasks check-consistent-interpolations | ||||||
| 
 | 
 | ||||||
| workflows: | workflows: | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								Gemfile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Gemfile
									
									
									
									
									
								
							|  | @ -96,7 +96,7 @@ gem 'rdf-normalize', '~> 0.3' | ||||||
| group :development, :test do | group :development, :test do | ||||||
|   gem 'fabrication', '~> 2.20' |   gem 'fabrication', '~> 2.20' | ||||||
|   gem 'fuubar', '~> 2.3' |   gem 'fuubar', '~> 2.3' | ||||||
|   gem 'i18n-tasks', '~> 0.9', require: false, git: 'https://github.com/Gargron/i18n-tasks.git', ref: '7a57fbe7000f4f8120e250a757ab345c28c6885c' |   gem 'i18n-tasks', '~> 0.9', require: false, git: 'https://github.com/Gargron/i18n-tasks.git', ref: 'ab6e10878ccdb6243f934f30372276d260c14251' | ||||||
|   gem 'pry-byebug', '~> 3.6' |   gem 'pry-byebug', '~> 3.6' | ||||||
|   gem 'pry-rails', '~> 0.3' |   gem 'pry-rails', '~> 0.3' | ||||||
|   gem 'rspec-rails', '~> 3.8' |   gem 'rspec-rails', '~> 3.8' | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| GIT | GIT | ||||||
|   remote: https://github.com/Gargron/i18n-tasks.git |   remote: https://github.com/Gargron/i18n-tasks.git | ||||||
|   revision: 7a57fbe7000f4f8120e250a757ab345c28c6885c |   revision: ab6e10878ccdb6243f934f30372276d260c14251 | ||||||
|   ref: 7a57fbe7000f4f8120e250a757ab345c28c6885c |   ref: ab6e10878ccdb6243f934f30372276d260c14251 | ||||||
|   specs: |   specs: | ||||||
|     i18n-tasks (0.9.27) |     i18n-tasks (0.9.27) | ||||||
|       activesupport (>= 4.0.2) |       activesupport (>= 4.0.2) | ||||||
|  |  | ||||||
|  | @ -201,6 +201,7 @@ class ApplicationController < ActionController::Base | ||||||
|   def respond_with_error(code) |   def respond_with_error(code) | ||||||
|     respond_to do |format| |     respond_to do |format| | ||||||
|       format.any  { head code } |       format.any  { head code } | ||||||
|  | 
 | ||||||
|       format.html do |       format.html do | ||||||
|         set_locale |         set_locale | ||||||
|         use_pack 'error' |         use_pack 'error' | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ import DisplayName from './display_name'; | ||||||
| import StatusContent from './status_content'; | import StatusContent from './status_content'; | ||||||
| import StatusActionBar from './status_action_bar'; | import StatusActionBar from './status_action_bar'; | ||||||
| import AttachmentList from './attachment_list'; | import AttachmentList from './attachment_list'; | ||||||
|  | import Card from '../features/status/components/card'; | ||||||
| import { injectIntl, FormattedMessage } from 'react-intl'; | import { injectIntl, FormattedMessage } from 'react-intl'; | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
| import { MediaGallery, Video } from '../features/ui/util/async-components'; | import { MediaGallery, Video } from '../features/ui/util/async-components'; | ||||||
|  | @ -256,6 +257,14 @@ class Status extends ImmutablePureComponent { | ||||||
|           </Bundle> |           </Bundle> | ||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
|  |     } else if (status.get('spoiler_text').length === 0 && status.get('card')) { | ||||||
|  |       media = ( | ||||||
|  |         <Card | ||||||
|  |           onOpenMedia={this.props.onOpenMedia} | ||||||
|  |           card={status.get('card')} | ||||||
|  |           compact | ||||||
|  |         /> | ||||||
|  |       ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (otherAccounts) { |     if (otherAccounts) { | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
| // https://github.com/missive/emoji-mart/blob/5f2ffcc/src/utils/emoji-index.js
 | // https://github.com/missive/emoji-mart/blob/5f2ffcc/src/utils/emoji-index.js
 | ||||||
| 
 | 
 | ||||||
| import data from './emoji_mart_data_light'; | import data from './emoji_mart_data_light'; | ||||||
| import { getData, getSanitizedData, intersect } from './emoji_utils'; | import { getData, getSanitizedData, uniq, intersect } from './emoji_utils'; | ||||||
| 
 | 
 | ||||||
| let originalPool = {}; | let originalPool = {}; | ||||||
| let index = {}; | let index = {}; | ||||||
|  | @ -103,7 +103,7 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     allResults = values.map((value) => { |     const searchValue = (value) => { | ||||||
|       let aPool = pool, |       let aPool = pool, | ||||||
|         aIndex = index, |         aIndex = index, | ||||||
|         length = 0; |         length = 0; | ||||||
|  | @ -150,15 +150,23 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       return aIndex.results; |       return aIndex.results; | ||||||
|     }).filter(a => a); |     }; | ||||||
| 
 | 
 | ||||||
|     if (allResults.length > 1) { |     if (values.length > 1) { | ||||||
|       results = intersect.apply(null, allResults); |       results = searchValue(value); | ||||||
|     } else if (allResults.length) { |  | ||||||
|       results = allResults[0]; |  | ||||||
|     } else { |     } else { | ||||||
|       results = []; |       results = []; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     allResults = values.map(searchValue).filter(a => a); | ||||||
|  | 
 | ||||||
|  |     if (allResults.length > 1) { | ||||||
|  |       allResults = intersect.apply(null, allResults); | ||||||
|  |     } else if (allResults.length) { | ||||||
|  |       allResults = allResults[0]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     results = uniq(results.concat(allResults)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   if (results) { |   if (results) { | ||||||
|  |  | ||||||
|  | @ -59,10 +59,12 @@ export default class Card extends React.PureComponent { | ||||||
|     card: ImmutablePropTypes.map, |     card: ImmutablePropTypes.map, | ||||||
|     maxDescription: PropTypes.number, |     maxDescription: PropTypes.number, | ||||||
|     onOpenMedia: PropTypes.func.isRequired, |     onOpenMedia: PropTypes.func.isRequired, | ||||||
|  |     compact: PropTypes.boolean, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   static defaultProps = { |   static defaultProps = { | ||||||
|     maxDescription: 50, |     maxDescription: 50, | ||||||
|  |     compact: false, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   state = { |   state = { | ||||||
|  | @ -131,25 +133,25 @@ export default class Card extends React.PureComponent { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|     const { card, maxDescription } = this.props; |     const { card, maxDescription, compact } = this.props; | ||||||
|     const { width, embedded }      = this.state; |     const { width, embedded } = this.state; | ||||||
| 
 | 
 | ||||||
|     if (card === null) { |     if (card === null) { | ||||||
|       return null; |       return null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const provider    = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name'); |     const provider    = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name'); | ||||||
|     const horizontal  = card.get('width') > card.get('height') && (card.get('width') + 100 >= width) || card.get('type') !== 'link'; |     const horizontal  = (!compact && card.get('width') > card.get('height') && (card.get('width') + 100 >= width)) || card.get('type') !== 'link' || embedded; | ||||||
|     const className   = classnames('status-card', { horizontal }); |  | ||||||
|     const interactive = card.get('type') !== 'link'; |     const interactive = card.get('type') !== 'link'; | ||||||
|  |     const className   = classnames('status-card', { horizontal, compact, interactive }); | ||||||
|     const title       = interactive ? <a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener' target='_blank'><strong>{card.get('title')}</strong></a> : <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>; |     const title       = interactive ? <a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener' target='_blank'><strong>{card.get('title')}</strong></a> : <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>; | ||||||
|     const ratio       = card.get('width') / card.get('height'); |     const ratio       = compact ? 16 / 9 : card.get('width') / card.get('height'); | ||||||
|     const height      = card.get('width') > card.get('height') ? (width / ratio) : (width * ratio); |     const height      = card.get('width') > card.get('height') ? (width / ratio) : (width * ratio); | ||||||
| 
 | 
 | ||||||
|     const description = ( |     const description = ( | ||||||
|       <div className='status-card__content'> |       <div className='status-card__content'> | ||||||
|         {title} |         {title} | ||||||
|         {!horizontal && <p className='status-card__description'>{trim(card.get('description') || '', maxDescription)}</p>} |         {!(horizontal || compact) && <p className='status-card__description'>{trim(card.get('description') || '', maxDescription)}</p>} | ||||||
|         <span className='status-card__host'>{provider}</span> |         <span className='status-card__host'>{provider}</span> | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|  | @ -174,7 +176,7 @@ export default class Card extends React.PureComponent { | ||||||
|             <div className='status-card__actions'> |             <div className='status-card__actions'> | ||||||
|               <div> |               <div> | ||||||
|                 <button onClick={this.handleEmbedClick}><i className={`fa fa-${iconVariant}`} /></button> |                 <button onClick={this.handleEmbedClick}><i className={`fa fa-${iconVariant}`} /></button> | ||||||
|                 <a href={card.get('url')} target='_blank' rel='noopener'><i className='fa fa-external-link' /></a> |                 {horizontal && <a href={card.get('url')} target='_blank' rel='noopener'><i className='fa fa-external-link' /></a>} | ||||||
|               </div> |               </div> | ||||||
|             </div> |             </div> | ||||||
|           </div> |           </div> | ||||||
|  | @ -184,7 +186,7 @@ export default class Card extends React.PureComponent { | ||||||
|       return ( |       return ( | ||||||
|         <div className={className} ref={this.setRef}> |         <div className={className} ref={this.setRef}> | ||||||
|           {embed} |           {embed} | ||||||
|           {description} |           {!compact && description} | ||||||
|         </div> |         </div> | ||||||
|       ); |       ); | ||||||
|     } else if (card.get('image')) { |     } else if (card.get('image')) { | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import { connect } from 'react-redux'; | ||||||
| import Card from '../components/card'; | import Card from '../components/card'; | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = (state, { statusId }) => ({ | const mapStateToProps = (state, { statusId }) => ({ | ||||||
|   card: state.getIn(['cards', statusId], null), |   card: state.getIn(['statuses', statusId, 'card'], null), | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export default connect(mapStateToProps)(Card); | export default connect(mapStateToProps)(Card); | ||||||
|  |  | ||||||
|  | @ -14,7 +14,6 @@ import relationships from './relationships'; | ||||||
| import settings from './settings'; | import settings from './settings'; | ||||||
| import push_notifications from './push_notifications'; | import push_notifications from './push_notifications'; | ||||||
| import status_lists from './status_lists'; | import status_lists from './status_lists'; | ||||||
| import cards from './cards'; |  | ||||||
| import mutes from './mutes'; | import mutes from './mutes'; | ||||||
| import reports from './reports'; | import reports from './reports'; | ||||||
| import contexts from './contexts'; | import contexts from './contexts'; | ||||||
|  | @ -46,7 +45,6 @@ const reducers = { | ||||||
|   relationships, |   relationships, | ||||||
|   settings, |   settings, | ||||||
|   push_notifications, |   push_notifications, | ||||||
|   cards, |  | ||||||
|   mutes, |   mutes, | ||||||
|   reports, |   reports, | ||||||
|   contexts, |   contexts, | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ import { | ||||||
|   STATUS_REVEAL, |   STATUS_REVEAL, | ||||||
|   STATUS_HIDE, |   STATUS_HIDE, | ||||||
| } from '../actions/statuses'; | } from '../actions/statuses'; | ||||||
|  | import { STATUS_CARD_FETCH_SUCCESS } from '../actions/cards'; | ||||||
| import { TIMELINE_DELETE } from '../actions/timelines'; | import { TIMELINE_DELETE } from '../actions/timelines'; | ||||||
| import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer'; | import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer'; | ||||||
| import { Map as ImmutableMap, fromJS } from 'immutable'; | import { Map as ImmutableMap, fromJS } from 'immutable'; | ||||||
|  | @ -65,6 +66,8 @@ export default function statuses(state = initialState, action) { | ||||||
|     }); |     }); | ||||||
|   case TIMELINE_DELETE: |   case TIMELINE_DELETE: | ||||||
|     return deleteStatus(state, action.id, action.references); |     return deleteStatus(state, action.id, action.references); | ||||||
|  |   case STATUS_CARD_FETCH_SUCCESS: | ||||||
|  |     return state.setIn([action.id, 'card'], fromJS(action.card)); | ||||||
|   default: |   default: | ||||||
|     return state; |     return state; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -1669,6 +1669,7 @@ a.account__display-name { | ||||||
|   padding: 4px 0; |   padding: 4px 0; | ||||||
|   border-radius: 4px; |   border-radius: 4px; | ||||||
|   box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4); |   box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4); | ||||||
|  |   z-index: 9999; | ||||||
| 
 | 
 | ||||||
|   ul { |   ul { | ||||||
|     list-style: none; |     list-style: none; | ||||||
|  | @ -2560,6 +2561,9 @@ a.status-card { | ||||||
|   display: block; |   display: block; | ||||||
|   margin-top: 5px; |   margin-top: 5px; | ||||||
|   font-size: 13px; |   font-size: 13px; | ||||||
|  |   overflow: hidden; | ||||||
|  |   text-overflow: ellipsis; | ||||||
|  |   white-space: nowrap; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .status-card__image { | .status-card__image { | ||||||
|  | @ -2584,6 +2588,31 @@ a.status-card { | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .status-card.compact { | ||||||
|  |   border-color: lighten($ui-base-color, 4%); | ||||||
|  | 
 | ||||||
|  |   &.interactive { | ||||||
|  |     border: 0; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .status-card__content { | ||||||
|  |     padding: 8px; | ||||||
|  |     padding-top: 10px; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .status-card__title { | ||||||
|  |     white-space: nowrap; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .status-card__image { | ||||||
|  |     flex: 0 0 60px; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | a.status-card.compact:hover { | ||||||
|  |   background-color: lighten($ui-base-color, 4%); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .status-card__image-image { | .status-card__image-image { | ||||||
|   border-radius: 4px 0 0 4px; |   border-radius: 4px 0 0 4px; | ||||||
|   display: block; |   display: block; | ||||||
|  |  | ||||||
|  | @ -148,6 +148,7 @@ class MediaAttachment < ApplicationRecord | ||||||
|     "#{x},#{y}" |     "#{x},#{y}" | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   after_commit :reset_parent_cache, on: :update | ||||||
|   before_create :prepare_description, unless: :local? |   before_create :prepare_description, unless: :local? | ||||||
|   before_create :set_shortcode |   before_create :set_shortcode | ||||||
|   before_post_process :set_type_and_extension |   before_post_process :set_type_and_extension | ||||||
|  | @ -252,4 +253,9 @@ class MediaAttachment < ApplicationRecord | ||||||
|       bitrate: movie.bitrate, |       bitrate: movie.bitrate, | ||||||
|     } |     } | ||||||
|   end |   end | ||||||
|  | 
 | ||||||
|  |   def reset_parent_cache | ||||||
|  |     return if status_id.nil? | ||||||
|  |     Rails.cache.delete("statuses/#{status_id}") | ||||||
|  |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -94,6 +94,7 @@ class Status < ApplicationRecord | ||||||
|                    :conversation, |                    :conversation, | ||||||
|                    :status_stat, |                    :status_stat, | ||||||
|                    :tags, |                    :tags, | ||||||
|  |                    :preview_cards, | ||||||
|                    :stream_entry, |                    :stream_entry, | ||||||
|                    active_mentions: :account, |                    active_mentions: :account, | ||||||
|                    reblog: [ |                    reblog: [ | ||||||
|  | @ -101,6 +102,7 @@ class Status < ApplicationRecord | ||||||
|                      :application, |                      :application, | ||||||
|                      :stream_entry, |                      :stream_entry, | ||||||
|                      :tags, |                      :tags, | ||||||
|  |                      :preview_cards, | ||||||
|                      :media_attachments, |                      :media_attachments, | ||||||
|                      :conversation, |                      :conversation, | ||||||
|                      :status_stat, |                      :status_stat, | ||||||
|  | @ -168,6 +170,10 @@ class Status < ApplicationRecord | ||||||
|     reblog |     reblog | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   def preview_card | ||||||
|  |     preview_cards.first | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   def title |   def title | ||||||
|     if destroyed? |     if destroyed? | ||||||
|       "#{account.acct} deleted status" |       "#{account.acct} deleted status" | ||||||
|  | @ -241,10 +247,6 @@ class Status < ApplicationRecord | ||||||
|   before_validation :set_local |   before_validation :set_local | ||||||
| 
 | 
 | ||||||
|   class << self |   class << self | ||||||
|     def cache_ids |  | ||||||
|       left_outer_joins(:status_stat).select('statuses.id, greatest(statuses.updated_at, status_stats.updated_at) AS updated_at') |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     def selectable_visibilities |     def selectable_visibilities | ||||||
|       visibilities.keys - %w(direct limited) |       visibilities.keys - %w(direct limited) | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -14,4 +14,12 @@ | ||||||
| 
 | 
 | ||||||
| class StatusStat < ApplicationRecord | class StatusStat < ApplicationRecord | ||||||
|   belongs_to :status, inverse_of: :status_stat |   belongs_to :status, inverse_of: :status_stat | ||||||
|  | 
 | ||||||
|  |   after_commit :reset_parent_cache | ||||||
|  | 
 | ||||||
|  |   private | ||||||
|  | 
 | ||||||
|  |   def reset_parent_cache | ||||||
|  |     Rails.cache.delete("statuses/#{status_id}") | ||||||
|  |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -21,6 +21,8 @@ class REST::StatusSerializer < ActiveModel::Serializer | ||||||
|   has_many :tags |   has_many :tags | ||||||
|   has_many :emojis, serializer: REST::CustomEmojiSerializer |   has_many :emojis, serializer: REST::CustomEmojiSerializer | ||||||
| 
 | 
 | ||||||
|  |   has_one :preview_card, key: :card, serializer: REST::PreviewCardSerializer | ||||||
|  | 
 | ||||||
|   def id |   def id | ||||||
|     object.id.to_s |     object.id.to_s | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -63,6 +63,7 @@ class FetchLinkCardService < BaseService | ||||||
| 
 | 
 | ||||||
|   def attach_card |   def attach_card | ||||||
|     @status.preview_cards << @card |     @status.preview_cards << @card | ||||||
|  |     Rails.cache.delete(@status) | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def parse_urls |   def parse_urls | ||||||
|  |  | ||||||
|  | @ -1 +1,2 @@ | ||||||
|  | --- | ||||||
| ast: {} | ast: {} | ||||||
|  |  | ||||||
|  | @ -21,8 +21,7 @@ ast: | ||||||
|     hosted_on: Mastodon ta agospiáu en %{domain} |     hosted_on: Mastodon ta agospiáu en %{domain} | ||||||
|     learn_more: Deprendi más |     learn_more: Deprendi más | ||||||
|     source_code: Códigu fonte |     source_code: Códigu fonte | ||||||
|     status_count_after: |     status_count_after: estaos | ||||||
|       other: estaos |  | ||||||
|     terms: Términos del serviciu |     terms: Términos del serviciu | ||||||
|     user_count_after: usuarios |     user_count_after: usuarios | ||||||
|     what_is_mastodon: "¿Qué ye Mastodon?" |     what_is_mastodon: "¿Qué ye Mastodon?" | ||||||
|  |  | ||||||
|  | @ -30,22 +30,16 @@ cs: | ||||||
|     other_instances: Seznam instancí |     other_instances: Seznam instancí | ||||||
|     privacy_policy: Zásady soukromí |     privacy_policy: Zásady soukromí | ||||||
|     source_code: Zdrojový kód |     source_code: Zdrojový kód | ||||||
|     status_count_after: |     status_count_after: příspěvků | ||||||
|       one: příspěvek |  | ||||||
|       other: příspěvků |  | ||||||
|     status_count_before: Kteří napsali |     status_count_before: Kteří napsali | ||||||
|     terms: Podmínky používání |     terms: Podmínky používání | ||||||
|     user_count_after: |     user_count_after: uživatelů | ||||||
|       one: uživatele |  | ||||||
|       other: uživatelů |  | ||||||
|     user_count_before: Domov |     user_count_before: Domov | ||||||
|     what_is_mastodon: Co je Mastodon? |     what_is_mastodon: Co je Mastodon? | ||||||
|   accounts: |   accounts: | ||||||
|     choices_html: 'Volby uživatele %{name}:' |     choices_html: 'Volby uživatele %{name}:' | ||||||
|     follow: Sledovat |     follow: Sledovat | ||||||
|     followers: |     followers: Sledovatelé | ||||||
|       one: Sledovatel |  | ||||||
|       other: Sledovatelé |  | ||||||
|     following: Sledovaní |     following: Sledovaní | ||||||
|     joined: Připojil/a se v %{date} |     joined: Připojil/a se v %{date} | ||||||
|     link_verified_on: Vlastnictví tohoto odkazu bylo zkontrolováno %{date} |     link_verified_on: Vlastnictví tohoto odkazu bylo zkontrolováno %{date} | ||||||
|  | @ -57,9 +51,7 @@ cs: | ||||||
|     people_who_follow: Lidé, kteří sledují uživatele %{name} |     people_who_follow: Lidé, kteří sledují uživatele %{name} | ||||||
|     pin_errors: |     pin_errors: | ||||||
|       following: Musíte již sledovat osobu, kterou chcete podpořit |       following: Musíte již sledovat osobu, kterou chcete podpořit | ||||||
|     posts: |     posts: Tooty | ||||||
|       one: Toot |  | ||||||
|       other: Tooty |  | ||||||
|     posts_tab_heading: Tooty |     posts_tab_heading: Tooty | ||||||
|     posts_with_replies: Tooty a odpovědi |     posts_with_replies: Tooty a odpovědi | ||||||
|     reserved_username: Toto uživatelské jméno je rezervováno |     reserved_username: Toto uživatelské jméno je rezervováno | ||||||
|  | @ -268,9 +260,7 @@ cs: | ||||||
|         suspend: Suspendovat |         suspend: Suspendovat | ||||||
|       severity: Přísnost |       severity: Přísnost | ||||||
|       show: |       show: | ||||||
|         affected_accounts: |         affected_accounts: "%{count} účtů v databázi byl ovlivněn" | ||||||
|           one: Jeden účet v databázi byl ovlivněn |  | ||||||
|           other: "%{count} účtů v databázi byl ovlivněn" |  | ||||||
|         retroactive: |         retroactive: | ||||||
|           silence: Odtišit všechny existující účty z této domény |           silence: Odtišit všechny existující účty z této domény | ||||||
|           suspend: Zrušit suspenzaci všech existujících účtů z této domény |           suspend: Zrušit suspenzaci všech existujících účtů z této domény | ||||||
|  | @ -562,9 +552,7 @@ cs: | ||||||
|     followers_count: Počet sledovatelů |     followers_count: Počet sledovatelů | ||||||
|     lock_link: Zamkněte svůj účet |     lock_link: Zamkněte svůj účet | ||||||
|     purge: Odstranit ze sledovatelů |     purge: Odstranit ze sledovatelů | ||||||
|     success: |     success: V průběhu utišování sledovatelů z %{count} domén... | ||||||
|       one: V průběhu utišování sledovatelů z jedné domény... |  | ||||||
|       other: V průběhu utišování sledovatelů z %{count} domén... |  | ||||||
|     true_privacy_html: Berte prosím na vědomí, že <strong>skutečného soukromí se dá dosáhnout pouze za pomoci end-to-end šifrování</strong>. |     true_privacy_html: Berte prosím na vědomí, že <strong>skutečného soukromí se dá dosáhnout pouze za pomoci end-to-end šifrování</strong>. | ||||||
|     unlocked_warning_html: Kdokoliv vás může sledovat a okamžitě vidět vaše soukromé příspěvky. %{lock_link}, abyste mohl/a zkontrolovat a odmítnout sledovatele. |     unlocked_warning_html: Kdokoliv vás může sledovat a okamžitě vidět vaše soukromé příspěvky. %{lock_link}, abyste mohl/a zkontrolovat a odmítnout sledovatele. | ||||||
|     unlocked_warning_title: Váš účet není zamknutý |     unlocked_warning_title: Váš účet není zamknutý | ||||||
|  | @ -575,9 +563,7 @@ cs: | ||||||
|   generic: |   generic: | ||||||
|     changes_saved_msg: Změny byly úspěšně uloženy! |     changes_saved_msg: Změny byly úspěšně uloženy! | ||||||
|     save_changes: Uložit změny |     save_changes: Uložit změny | ||||||
|     validation_errors: |     validation_errors: Něco ještě není úplně v pořádku! Prosím zkontrolujte %{count} chyb níže | ||||||
|       one: Něco ještě není úplně v pořádku! Prosím zkontrolujte chybu níže |  | ||||||
|       other: Něco ještě není úplně v pořádku! Prosím zkontrolujte %{count} chyb níže |  | ||||||
|   imports: |   imports: | ||||||
|     preface: Můžete importovat data, která jste exportoval/a z jiné instance, jako například seznam lidí, které sledujete či blokujete. |     preface: Můžete importovat data, která jste exportoval/a z jiné instance, jako například seznam lidí, které sledujete či blokujete. | ||||||
|     success: Vaše data byla úspěšně nahrána a nyní budou zpracována v daný čas |     success: Vaše data byla úspěšně nahrána a nyní budou zpracována v daný čas | ||||||
|  | @ -600,9 +586,7 @@ cs: | ||||||
|     expires_in_prompt: Nikdy |     expires_in_prompt: Nikdy | ||||||
|     generate: Vygenerovat |     generate: Vygenerovat | ||||||
|     invited_by: 'Byl/a jste pozván/a uživatelem:' |     invited_by: 'Byl/a jste pozván/a uživatelem:' | ||||||
|     max_uses: |     max_uses: "%{count} použití" | ||||||
|       one: 1 použití |  | ||||||
|       other: "%{count} použití" |  | ||||||
|     max_uses_prompt: Bez limitu |     max_uses_prompt: Bez limitu | ||||||
|     prompt: Vygenerujte a sdílejte s ostatními odkazy a umožněte jim přístup na tuto instanci |     prompt: Vygenerujte a sdílejte s ostatními odkazy a umožněte jim přístup na tuto instanci | ||||||
|     table: |     table: | ||||||
|  | @ -628,12 +612,8 @@ cs: | ||||||
|       action: Zobrazit všechna oznámení |       action: Zobrazit všechna oznámení | ||||||
|       body: Zde najdete stručný souhrn zpráv, které jste zmeškal/a od vaší poslední návštěvy %{since} |       body: Zde najdete stručný souhrn zpráv, které jste zmeškal/a od vaší poslední návštěvy %{since} | ||||||
|       mention: "%{name} vás zmínil/a v:" |       mention: "%{name} vás zmínil/a v:" | ||||||
|       new_followers_summary: |       new_followers_summary: Navíc jste získal/a %{count} nových sledovatelů, zatímco jste byl/a pryč! Hurá! | ||||||
|         one: Navíc jste získal/a jednoho nového sledovatele, zatímco jste byl/a pryč! Hurá! |       subject: "%{count} nových oznámení od vaší poslední návštěvy \U0001F418" | ||||||
|         other: Navíc jste získal/a %{count} nových sledovatelů, zatímco jste byl/a pryč! Hurá! |  | ||||||
|       subject: |  | ||||||
|         one: "Jedno nové oznámení od vaší poslední návštěvy \U0001F418" |  | ||||||
|         other: "%{count} nových oznámení od vaší poslední návštěvy \U0001F418" |  | ||||||
|       title: Ve vaší absenci... |       title: Ve vaší absenci... | ||||||
|     favourite: |     favourite: | ||||||
|       body: 'Váš příspěvek si oblíbil/a %{name}:' |       body: 'Váš příspěvek si oblíbil/a %{name}:' | ||||||
|  | @ -750,17 +730,11 @@ cs: | ||||||
|   statuses: |   statuses: | ||||||
|     attached: |     attached: | ||||||
|       description: 'Přiloženo: %{attached}' |       description: 'Přiloženo: %{attached}' | ||||||
|       image: |       image: "%{count} obrázků" | ||||||
|         one: "%{count} obrázek" |       video: "%{count} videí" | ||||||
|         other: "%{count} obrázků" |  | ||||||
|       video: |  | ||||||
|         one: "%{count} video" |  | ||||||
|         other: "%{count} videí" |  | ||||||
|     boosted_from_html: Boostnuto z %{acct_link} |     boosted_from_html: Boostnuto z %{acct_link} | ||||||
|     content_warning: 'Varování o obsahu: %{warning}' |     content_warning: 'Varování o obsahu: %{warning}' | ||||||
|     disallowed_hashtags: |     disallowed_hashtags: 'obsahuje nepovolené hashtagy: %{tags}' | ||||||
|       one: 'obsahuje nepovolený hashtag: %{tags}' |  | ||||||
|       other: 'obsahuje nepovolené hashtagy: %{tags}' |  | ||||||
|     language_detection: Zjistit jazyk automaticky |     language_detection: Zjistit jazyk automaticky | ||||||
|     open_in_web: Otevřít na webu |     open_in_web: Otevřít na webu | ||||||
|     over_character_limit: limit %{max} znaků byl překročen |     over_character_limit: limit %{max} znaků byl překročen | ||||||
|  |  | ||||||
|  | @ -30,22 +30,16 @@ cy: | ||||||
|     other_instances: Rhestr achosion |     other_instances: Rhestr achosion | ||||||
|     privacy_policy: Polisi preifatrwydd |     privacy_policy: Polisi preifatrwydd | ||||||
|     source_code: Cod ffynhonnell |     source_code: Cod ffynhonnell | ||||||
|     status_count_after: |     status_count_after: statws | ||||||
|       one: statws |  | ||||||
|       other: statws |  | ||||||
|     status_count_before: Pwy ysgrifennodd |     status_count_before: Pwy ysgrifennodd | ||||||
|     terms: Telerau gwasanaeth |     terms: Telerau gwasanaeth | ||||||
|     user_count_after: |     user_count_after: defnyddwyr | ||||||
|       one: defnyddiwr |  | ||||||
|       other: defnyddwyr |  | ||||||
|     user_count_before: Cartref i |     user_count_before: Cartref i | ||||||
|     what_is_mastodon: Beth yw Mastodon? |     what_is_mastodon: Beth yw Mastodon? | ||||||
|   accounts: |   accounts: | ||||||
|     choices_html: 'Dewisiadau %{name}:' |     choices_html: 'Dewisiadau %{name}:' | ||||||
|     follow: Dilynwch |     follow: Dilynwch | ||||||
|     followers: |     followers: Dilynwyr | ||||||
|       one: Dilynwr |  | ||||||
|       other: Dilynwyr |  | ||||||
|     following: Yn dilyn |     following: Yn dilyn | ||||||
|     joined: Ymunodd %{date} |     joined: Ymunodd %{date} | ||||||
|     media: Cyfryngau |     media: Cyfryngau | ||||||
|  | @ -56,9 +50,7 @@ cy: | ||||||
|     people_who_follow: Pobl sy'n dilyn %{name} |     people_who_follow: Pobl sy'n dilyn %{name} | ||||||
|     pin_errors: |     pin_errors: | ||||||
|       following: Rhaid i ti fod yn dilyn y person yr ydych am ei gymeradwyo yn barod |       following: Rhaid i ti fod yn dilyn y person yr ydych am ei gymeradwyo yn barod | ||||||
|     posts: |     posts: Tŵtiau | ||||||
|       one: Tŵt |  | ||||||
|       other: Tŵtiau |  | ||||||
|     posts_tab_heading: Tŵtiau |     posts_tab_heading: Tŵtiau | ||||||
|     posts_with_replies: Tŵtiau ac atebion |     posts_with_replies: Tŵtiau ac atebion | ||||||
|     reserved_username: Mae'r enw defnyddior yn neilltuedig |     reserved_username: Mae'r enw defnyddior yn neilltuedig | ||||||
|  | @ -262,9 +254,7 @@ cy: | ||||||
|         suspend: Atal |         suspend: Atal | ||||||
|       severity: Difrifoldeb |       severity: Difrifoldeb | ||||||
|       show: |       show: | ||||||
|         affected_accounts: |         affected_accounts: "%{count} o gyfrifoedd yn y bas data wedi eu hefeithio" | ||||||
|           one: Mae un cyfri yn y bas data wedi ei effeithio |  | ||||||
|           other: "%{count} o gyfrifoedd yn y bas data wedi eu hefeithio" |  | ||||||
|         retroactive: |         retroactive: | ||||||
|           silence: Dad-dawelu pob cyfri presennol o'r parth hwn |           silence: Dad-dawelu pob cyfri presennol o'r parth hwn | ||||||
|           suspend: Dad-atal pob cyfrif o'r parth hwn sy'n bodoli |           suspend: Dad-atal pob cyfrif o'r parth hwn sy'n bodoli | ||||||
|  | @ -508,9 +498,7 @@ cy: | ||||||
|   generic: |   generic: | ||||||
|     changes_saved_msg: Llwyddwyd i gadw y newidiadau! |     changes_saved_msg: Llwyddwyd i gadw y newidiadau! | ||||||
|     save_changes: Cadw newidiadau |     save_changes: Cadw newidiadau | ||||||
|     validation_errors: |     validation_errors: Mae rhywbeth o'i le o hyd! Edrychwch ar y %{count} gwall isod os gwelwch yn dda | ||||||
|       one: Mae rhywbeth o'i le o hyd! Edrychwch ar y gwall isod os gwelwch yn dda |  | ||||||
|       other: Mae rhywbeth o'i le o hyd! Edrychwch ar y %{count} gwall isod os gwelwch yn dda |  | ||||||
|   imports: |   imports: | ||||||
|     preface: Mae modd mewnforio data yr ydych wedi allforio o achos arall, megis rhestr o bobl yr ydych yn ei ddilyn neu yn blocio. |     preface: Mae modd mewnforio data yr ydych wedi allforio o achos arall, megis rhestr o bobl yr ydych yn ei ddilyn neu yn blocio. | ||||||
|     success: Uwchlwyddwyd eich data yn llwyddiannus ac fe fydd yn cael ei brosesu mewn da bryd |     success: Uwchlwyddwyd eich data yn llwyddiannus ac fe fydd yn cael ei brosesu mewn da bryd | ||||||
|  |  | ||||||
|  | @ -1 +1,2 @@ | ||||||
|  | --- | ||||||
| ast: {} | ast: {} | ||||||
|  |  | ||||||
|  | @ -77,6 +77,4 @@ cs: | ||||||
|       expired: vypršel, prosím vyžádejte si nový |       expired: vypršel, prosím vyžádejte si nový | ||||||
|       not_found: nenalezen |       not_found: nenalezen | ||||||
|       not_locked: nebyl uzamčen |       not_locked: nebyl uzamčen | ||||||
|       not_saved: |       not_saved: "%{count} chyb zabránila uložení tohoto %{resource}:" | ||||||
|         one: '1 chyba zabránila uložení tohoto %{resource}:' |  | ||||||
|         other: "%{count} chyb zabránila uložení tohoto %{resource}:" |  | ||||||
|  |  | ||||||
|  | @ -77,6 +77,4 @@ cy: | ||||||
|       expired: wedi dod i ben, gwnewch gais am un newydd os gwelwch yn dda |       expired: wedi dod i ben, gwnewch gais am un newydd os gwelwch yn dda | ||||||
|       not_found: heb ei ganfod |       not_found: heb ei ganfod | ||||||
|       not_locked: heb ei gloi |       not_locked: heb ei gloi | ||||||
|       not_saved: |       not_saved: 'Gwaharddwyd yr %{resource} rhag cael ei arbed oherwydd %{count} gwall:' | ||||||
|         one: 'Gwaharddwyd yr %{resource} rhag cael ei arbed oherwydd 1 gwall:' |  | ||||||
|         other: 'Gwaharddwyd yr %{resource} rhag cael ei arbed oherwydd %{count} gwall:' |  | ||||||
|  |  | ||||||
|  | @ -58,6 +58,4 @@ hr: | ||||||
|       expired: je istekao, zatraži novu |       expired: je istekao, zatraži novu | ||||||
|       not_found: nije nađen |       not_found: nije nađen | ||||||
|       not_locked: nije zaključan |       not_locked: nije zaključan | ||||||
|       not_saved: |       not_saved: "%{count} greške su zabranile da ovaj %{resource} bude sačuvan:" | ||||||
|         one: '1 greška je zabranila da ovaj %{resource} bude sačuvan:' |  | ||||||
|         other: "%{count} greške su zabranile da ovaj %{resource} bude sačuvan:" |  | ||||||
|  |  | ||||||
|  | @ -77,6 +77,4 @@ pl: | ||||||
|       expired: wygasło, poproś o nowe |       expired: wygasło, poproś o nowe | ||||||
|       not_found: nie znaleziono |       not_found: nie znaleziono | ||||||
|       not_locked: było zablokowane |       not_locked: było zablokowane | ||||||
|       not_saved: |       not_saved: 'Błędy (%{count}) uniemożliwiły zapisanie zasobu %{resource}:' | ||||||
|         one: '1 błąd uniemożliwił zapisanie zasobu %{resource}:' |  | ||||||
|         other: 'Błędy (%{count}) uniemożliwiły zapisanie zasobu %{resource}:' |  | ||||||
|  |  | ||||||
|  | @ -77,6 +77,4 @@ zh-TW: | ||||||
|       expired: 已經過期,請重新申請 |       expired: 已經過期,請重新申請 | ||||||
|       not_found: 找不到 |       not_found: 找不到 | ||||||
|       not_locked: 並未被鎖定 |       not_locked: 並未被鎖定 | ||||||
|       not_saved: |       not_saved: "%{count} 個錯誤使 %{resource} 無法被儲存︰" | ||||||
|         one: 1 個錯誤使 %{resource} 無法被儲存︰ |  | ||||||
|         other: "%{count} 個錯誤使 %{resource} 無法被儲存︰" |  | ||||||
|  |  | ||||||
|  | @ -1 +1,2 @@ | ||||||
|  | --- | ||||||
| ast: {} | ast: {} | ||||||
|  |  | ||||||
|  | @ -1 +1,2 @@ | ||||||
|  | --- | ||||||
| {} | {} | ||||||
|  |  | ||||||
|  | @ -61,9 +61,7 @@ hr: | ||||||
|   generic: |   generic: | ||||||
|     changes_saved_msg: Izmjene su uspješno sačuvane! |     changes_saved_msg: Izmjene su uspješno sačuvane! | ||||||
|     save_changes: Sačuvaj izmjene |     save_changes: Sačuvaj izmjene | ||||||
|     validation_errors: |     validation_errors: Nešto još uvijek ne štima! Vidi %{count} greške ispod | ||||||
|       one: Nešto ne štima! Vidi grešku ispod |  | ||||||
|       other: Nešto još uvijek ne štima! Vidi %{count} greške ispod |  | ||||||
|   imports: |   imports: | ||||||
|     preface: Možeš uvesti određene podatke kao što su svi ljudi koje slijediš ili blokiraš u svoj račun na ovoj instanci, sa fajlova kreiranih izvozom sa druge instance. |     preface: Možeš uvesti određene podatke kao što su svi ljudi koje slijediš ili blokiraš u svoj račun na ovoj instanci, sa fajlova kreiranih izvozom sa druge instance. | ||||||
|     success: Tvoji podaci su uspješno uploadani i bit će obrađeni u dogledno vrijeme |     success: Tvoji podaci su uspješno uploadani i bit će obrađeni u dogledno vrijeme | ||||||
|  | @ -76,12 +74,8 @@ hr: | ||||||
|     digest: |     digest: | ||||||
|       body: 'Ovo je kratak sažetak propuštenog od tvog prošlog posjeta %{since}:' |       body: 'Ovo je kratak sažetak propuštenog od tvog prošlog posjeta %{since}:' | ||||||
|       mention: "%{name} te je spomenuo:" |       mention: "%{name} te je spomenuo:" | ||||||
|       new_followers_summary: |       new_followers_summary: Imaš %{count} novih sljedbenika! Prekrašno! | ||||||
|         one: Imaš novog sljedbenika! Yay! |       subject: "%{count} novih notifikacija od tvog prošlog posjeta \U0001F418" | ||||||
|         other: Imaš %{count} novih sljedbenika! Prekrašno! |  | ||||||
|       subject: |  | ||||||
|         one: "1 nova notifikacija od tvog prošlog posjeta \U0001F418" |  | ||||||
|         other: "%{count} novih notifikacija od tvog prošlog posjeta \U0001F418" |  | ||||||
|     favourite: |     favourite: | ||||||
|       body: 'Tvoj status je %{name} označio kao omiljen:' |       body: 'Tvoj status je %{name} označio kao omiljen:' | ||||||
|       subject: "%{name} je označio kao omiljen tvoj status" |       subject: "%{name} je označio kao omiljen tvoj status" | ||||||
|  |  | ||||||
|  | @ -279,10 +279,7 @@ pl: | ||||||
|         suspend: Zawieś |         suspend: Zawieś | ||||||
|       severity: Priorytet |       severity: Priorytet | ||||||
|       show: |       show: | ||||||
|         affected_accounts: |         affected_accounts: Dotyczy %{count} kont w bazie danych | ||||||
|           many: Dotyczy %{count} kont w bazie danych |  | ||||||
|           one: Dotyczy jednego konta w bazie danych |  | ||||||
|           other: Dotyczy %{count} kont w bazie danych |  | ||||||
|         retroactive: |         retroactive: | ||||||
|           silence: Odwołaj wyciszenie wszystkich kont w tej domenie |           silence: Odwołaj wyciszenie wszystkich kont w tej domenie | ||||||
|           suspend: Odwołaj zawieszenie wszystkich kont w tej domenie |           suspend: Odwołaj zawieszenie wszystkich kont w tej domenie | ||||||
|  | @ -577,9 +574,7 @@ pl: | ||||||
|     followers_count: Liczba śledzących |     followers_count: Liczba śledzących | ||||||
|     lock_link: Zablokuj swoje konto |     lock_link: Zablokuj swoje konto | ||||||
|     purge: Przestań śledzić |     purge: Przestań śledzić | ||||||
|     success: |     success: W trakcie usuwania śledzących z %{count} domen… | ||||||
|       one: W trakcie usuwania śledzących z jednej domeny… |  | ||||||
|       other: W trakcie usuwania śledzących z %{count} domen… |  | ||||||
|     true_privacy_html: Pamiętaj, że <strong>rzeczywista prywatność może zostać uzyskana wyłącznie dzięki szyfrowaniu end-to-end</strong>. |     true_privacy_html: Pamiętaj, że <strong>rzeczywista prywatność może zostać uzyskana wyłącznie dzięki szyfrowaniu end-to-end</strong>. | ||||||
|     unlocked_warning_html: Każdy może Cię śledzić, dzięki czemu może zobaczyć Twoje niepubliczne wpisy. %{lock_link} aby móc kontrolować, kto Cię śledzi. |     unlocked_warning_html: Każdy może Cię śledzić, dzięki czemu może zobaczyć Twoje niepubliczne wpisy. %{lock_link} aby móc kontrolować, kto Cię śledzi. | ||||||
|     unlocked_warning_title: Twoje konto nie jest zablokowane |     unlocked_warning_title: Twoje konto nie jest zablokowane | ||||||
|  | @ -788,9 +783,7 @@ pl: | ||||||
|         other: "%{count} filmów" |         other: "%{count} filmów" | ||||||
|     boosted_from_html: Podbito przez %{acct_link} |     boosted_from_html: Podbito przez %{acct_link} | ||||||
|     content_warning: 'Ostrzeżenie o zawartości: %{warning}' |     content_warning: 'Ostrzeżenie o zawartości: %{warning}' | ||||||
|     disallowed_hashtags: |     disallowed_hashtags: 'zawiera niedozwolone hashtagi: %{tags}' | ||||||
|       one: 'zawiera niedozwolony hashtag: %{tags}' |  | ||||||
|       other: 'zawiera niedozwolone hashtagi: %{tags}' |  | ||||||
|     language_detection: Automatycznie wykrywaj język |     language_detection: Automatycznie wykrywaj język | ||||||
|     open_in_web: Otwórz w przeglądarce |     open_in_web: Otwórz w przeglądarce | ||||||
|     over_character_limit: limit %{max} znaków przekroczony |     over_character_limit: limit %{max} znaków przekroczony | ||||||
|  |  | ||||||
|  | @ -1 +1,2 @@ | ||||||
|  | --- | ||||||
| {} | {} | ||||||
|  |  | ||||||
|  | @ -30,22 +30,16 @@ sk: | ||||||
|     other_instances: Zoznam ďalších inštancií |     other_instances: Zoznam ďalších inštancií | ||||||
|     privacy_policy: Ustanovenia o súkromí |     privacy_policy: Ustanovenia o súkromí | ||||||
|     source_code: Zdrojový kód |     source_code: Zdrojový kód | ||||||
|     status_count_after: |     status_count_after: statusy | ||||||
|       one: status |  | ||||||
|       other: statusy |  | ||||||
|     status_count_before: Ktorí napísali |     status_count_before: Ktorí napísali | ||||||
|     terms: Podmienky užívania |     terms: Podmienky užívania | ||||||
|     user_count_after: |     user_count_after: užívateľov | ||||||
|       one: užívateľ |  | ||||||
|       other: užívateľov |  | ||||||
|     user_count_before: Domov pre |     user_count_before: Domov pre | ||||||
|     what_is_mastodon: Čo je Mastodon? |     what_is_mastodon: Čo je Mastodon? | ||||||
|   accounts: |   accounts: | ||||||
|     choices_html: "%{name}vé voľby:" |     choices_html: "%{name}vé voľby:" | ||||||
|     follow: Sledovať |     follow: Sledovať | ||||||
|     followers: |     followers: Sledovatelia | ||||||
|       one: Následovateľ |  | ||||||
|       other: Sledovatelia |  | ||||||
|     following: Sledovaní |     following: Sledovaní | ||||||
|     joined: Pridal/a sa %{date} |     joined: Pridal/a sa %{date} | ||||||
|     media: Médiá |     media: Médiá | ||||||
|  | @ -56,9 +50,7 @@ sk: | ||||||
|     people_who_follow: Ľudia sledujúci %{name} |     people_who_follow: Ľudia sledujúci %{name} | ||||||
|     pin_errors: |     pin_errors: | ||||||
|       following: Musíš už následovať toho človeka, ktorého si praješ zviditeľniť |       following: Musíš už následovať toho človeka, ktorého si praješ zviditeľniť | ||||||
|     posts: |     posts: Príspevky | ||||||
|       one: Príspevok |  | ||||||
|       other: Príspevky |  | ||||||
|     posts_tab_heading: Príspevky |     posts_tab_heading: Príspevky | ||||||
|     posts_with_replies: Príspevky s odpoveďami |     posts_with_replies: Príspevky s odpoveďami | ||||||
|     reserved_username: Prihlasovacie meno je rezervované |     reserved_username: Prihlasovacie meno je rezervované | ||||||
|  | @ -746,9 +738,7 @@ sk: | ||||||
|         other: "%{count} videí" |         other: "%{count} videí" | ||||||
|     boosted_from_html: Povýšené od %{acct_link} |     boosted_from_html: Povýšené od %{acct_link} | ||||||
|     content_warning: 'Varovanie o obsahu: %{warning}' |     content_warning: 'Varovanie o obsahu: %{warning}' | ||||||
|     disallowed_hashtags: |     disallowed_hashtags: 'obsahuje nepovolené haštagy: %{tags}' | ||||||
|       one: 'obsahuje nepovolený haštag: %{tags}' |  | ||||||
|       other: 'obsahuje nepovolené haštagy: %{tags}' |  | ||||||
|     language_detection: Zisti jazyk automaticky |     language_detection: Zisti jazyk automaticky | ||||||
|     open_in_web: Otvor v okne prehliadača |     open_in_web: Otvor v okne prehliadača | ||||||
|     over_character_limit: limit počtu %{max} znakov bol presiahnutý |     over_character_limit: limit počtu %{max} znakov bol presiahnutý | ||||||
|  |  | ||||||
|  | @ -30,22 +30,16 @@ sr: | ||||||
|     other_instances: Листа инстанци |     other_instances: Листа инстанци | ||||||
|     privacy_policy: Полиса приватности |     privacy_policy: Полиса приватности | ||||||
|     source_code: Изворни код |     source_code: Изворни код | ||||||
|     status_count_after: |     status_count_after: статуси | ||||||
|       one: статус |  | ||||||
|       other: статуси |  | ||||||
|     status_count_before: Који су написали |     status_count_before: Који су написали | ||||||
|     terms: Услови коришћења |     terms: Услови коришћења | ||||||
|     user_count_after: |     user_count_after: корисници | ||||||
|       one: корисник |  | ||||||
|       other: корисници |  | ||||||
|     user_count_before: Дом за |     user_count_before: Дом за | ||||||
|     what_is_mastodon: Шта је Мастодон? |     what_is_mastodon: Шта је Мастодон? | ||||||
|   accounts: |   accounts: | ||||||
|     choices_html: "%{name}'s избори:" |     choices_html: "%{name}'s избори:" | ||||||
|     follow: Запрати |     follow: Запрати | ||||||
|     followers: |     followers: Пратиоци | ||||||
|       one: Пратилац |  | ||||||
|       other: Пратиоци |  | ||||||
|     following: Пратим |     following: Пратим | ||||||
|     joined: Придружио/ла се %{date} |     joined: Придружио/ла се %{date} | ||||||
|     media: Медији |     media: Медији | ||||||
|  | @ -56,9 +50,7 @@ sr: | ||||||
|     people_who_follow: Људи који прате %{name} |     people_who_follow: Људи који прате %{name} | ||||||
|     pin_errors: |     pin_errors: | ||||||
|       following: Морате пратити ову особу ако хоћете да потврдите |       following: Морате пратити ову особу ако хоћете да потврдите | ||||||
|     posts: |     posts: Трубе | ||||||
|       one: Труба |  | ||||||
|       other: Трубе |  | ||||||
|     posts_tab_heading: Трубе |     posts_tab_heading: Трубе | ||||||
|     posts_with_replies: Трубе и одговори |     posts_with_replies: Трубе и одговори | ||||||
|     reserved_username: Корисничко име је резервисано |     reserved_username: Корисничко име је резервисано | ||||||
|  | @ -754,17 +746,11 @@ sr: | ||||||
|   statuses: |   statuses: | ||||||
|     attached: |     attached: | ||||||
|       description: 'У прилогу: %{attached}' |       description: 'У прилогу: %{attached}' | ||||||
|       image: |       image: "%{count} слике" | ||||||
|         one: "%{count} слику" |       video: "%{count} видеа" | ||||||
|         other: "%{count} слике" |  | ||||||
|       video: |  | ||||||
|         one: "%{count} видео" |  | ||||||
|         other: "%{count} видеа" |  | ||||||
|     boosted_from_html: Подржано од %{acct_link} |     boosted_from_html: Подржано од %{acct_link} | ||||||
|     content_warning: 'Упозорење на садржај: %{warning}' |     content_warning: 'Упозорење на садржај: %{warning}' | ||||||
|     disallowed_hashtags: |     disallowed_hashtags: 'садржи забрањене тарабе: %{tags}' | ||||||
|       one: 'садржи забрањену тарабу: %{tags}' |  | ||||||
|       other: 'садржи забрањене тарабе: %{tags}' |  | ||||||
|     language_detection: Аутоматскo откривање језика |     language_detection: Аутоматскo откривање језика | ||||||
|     open_in_web: Отвори у вебу |     open_in_web: Отвори у вебу | ||||||
|     over_character_limit: ограничење од %{max} карактера прекорачено |     over_character_limit: ограничење од %{max} карактера прекорачено | ||||||
|  |  | ||||||
|  | @ -518,18 +518,14 @@ uk: | ||||||
|     followers_count: Кількість підписників |     followers_count: Кількість підписників | ||||||
|     lock_link: Закрийте акаунт |     lock_link: Закрийте акаунт | ||||||
|     purge: Видалити з підписників |     purge: Видалити з підписників | ||||||
|     success: |     success: У процесі м'якого блокування підписників з %{count} доменів... | ||||||
|       one: У процесі м'якого блокування підписників з одного домену... |  | ||||||
|       other: У процесі м'якого блокування підписників з %{count} доменів... |  | ||||||
|     true_privacy_html: Будь ласка, помітьте, що <strong>справжняя конфіденційність може бути досягнена тільки за допомогою end-to-end шифрування</strong>. |     true_privacy_html: Будь ласка, помітьте, що <strong>справжняя конфіденційність може бути досягнена тільки за допомогою end-to-end шифрування</strong>. | ||||||
|     unlocked_warning_html: Хто завгодно може підписатися на Вас та отримати доступ до перегляду Ваших приватних статусів. %{lock_link}, щоб отримати можливість роздивлятися та вручну підтверджувати запити щодо підписки. |     unlocked_warning_html: Хто завгодно може підписатися на Вас та отримати доступ до перегляду Ваших приватних статусів. %{lock_link}, щоб отримати можливість роздивлятися та вручну підтверджувати запити щодо підписки. | ||||||
|     unlocked_warning_title: Ваш аккаунт не закритий для підписки |     unlocked_warning_title: Ваш аккаунт не закритий для підписки | ||||||
|   generic: |   generic: | ||||||
|     changes_saved_msg: Зміни успішно збережені! |     changes_saved_msg: Зміни успішно збережені! | ||||||
|     save_changes: Зберегти зміни |     save_changes: Зберегти зміни | ||||||
|     validation_errors: |     validation_errors: Щось тут не так! Будь ласка, ознайомтеся з %{count} помилками нижче | ||||||
|       one: Щось тут не так! Будь ласка, ознайомтеся з помилкою нижче |  | ||||||
|       other: Щось тут не так! Будь ласка, ознайомтеся з %{count} помилками нижче |  | ||||||
|   imports: |   imports: | ||||||
|     preface: Вы можете завантажити деякі дані, наприклад, списки людей, на яких Ви підписані чи яких блокуєте, в Ваш акаунт на цій інстанції з файлів, експортованих з іншої інстанції. |     preface: Вы можете завантажити деякі дані, наприклад, списки людей, на яких Ви підписані чи яких блокуєте, в Ваш акаунт на цій інстанції з файлів, експортованих з іншої інстанції. | ||||||
|     success: Ваші дані були успішно загружені та будуть оброблені в найближчий момент |     success: Ваші дані були успішно загружені та будуть оброблені в найближчий момент | ||||||
|  | @ -552,9 +548,7 @@ uk: | ||||||
|     expires_in_prompt: Ніколи |     expires_in_prompt: Ніколи | ||||||
|     generate: Згенерувати |     generate: Згенерувати | ||||||
|     invited_by: 'Вас запросив(-ла):' |     invited_by: 'Вас запросив(-ла):' | ||||||
|     max_uses: |     max_uses: "%{count} використань" | ||||||
|       one: 1 використання |  | ||||||
|       other: "%{count} використань" |  | ||||||
|     max_uses_prompt: Без обмеження |     max_uses_prompt: Без обмеження | ||||||
|     prompt: Генеруйте та діліться посиланням з іншими для надання доступу до сайту |     prompt: Генеруйте та діліться посиланням з іншими для надання доступу до сайту | ||||||
|     table: |     table: | ||||||
|  | @ -703,17 +697,11 @@ uk: | ||||||
|   statuses: |   statuses: | ||||||
|     attached: |     attached: | ||||||
|       description: 'Прикріплено: %{attached}' |       description: 'Прикріплено: %{attached}' | ||||||
|       image: |       image: "%{count} картинки" | ||||||
|         one: "%{count} картинка" |       video: "%{count} відео" | ||||||
|         other: "%{count} картинки" |  | ||||||
|       video: |  | ||||||
|         one: "%{count} відео" |  | ||||||
|         other: "%{count} відео" |  | ||||||
|     boosted_from_html: Просунуто від %{acct_link} |     boosted_from_html: Просунуто від %{acct_link} | ||||||
|     content_warning: 'Попередження про контент: %{warning}' |     content_warning: 'Попередження про контент: %{warning}' | ||||||
|     disallowed_hashtags: |     disallowed_hashtags: 'містив заборонені хештеґи: %{tags}' | ||||||
|       one: 'містив заборонений хештеґ: %{tags}' |  | ||||||
|       other: 'містив заборонені хештеґи: %{tags}' |  | ||||||
|     language_detection: Автоматично визначати мову |     language_detection: Автоматично визначати мову | ||||||
|     open_in_web: Відкрити у вебі |     open_in_web: Відкрити у вебі | ||||||
|     over_character_limit: перевищено ліміт символів (%{max}) |     over_character_limit: перевищено ліміт символів (%{max}) | ||||||
|  |  | ||||||
|  | @ -30,13 +30,10 @@ zh-CN: | ||||||
|     other_instances: 其他实例 |     other_instances: 其他实例 | ||||||
|     privacy_policy: 隐私政策 |     privacy_policy: 隐私政策 | ||||||
|     source_code: 源代码 |     source_code: 源代码 | ||||||
|     status_count_after: |     status_count_after: 条嘟文 | ||||||
|       one: 条嘟文 |  | ||||||
|     status_count_before: 他们共嘟出了 |     status_count_before: 他们共嘟出了 | ||||||
|     terms: 使用条款 |     terms: 使用条款 | ||||||
|     user_count_after: |     user_count_after: 位用户 | ||||||
|       one: 位用户 |  | ||||||
|       other: 位用户 |  | ||||||
|     user_count_before: 这里共注册有 |     user_count_before: 这里共注册有 | ||||||
|     what_is_mastodon: Mastodon 是什么? |     what_is_mastodon: Mastodon 是什么? | ||||||
|   accounts: |   accounts: | ||||||
|  |  | ||||||
|  | @ -29,18 +29,15 @@ zh-TW: | ||||||
|     learn_more: 了解詳細 |     learn_more: 了解詳細 | ||||||
|     other_instances: 其他站點 |     other_instances: 其他站點 | ||||||
|     source_code: 原始碼 |     source_code: 原始碼 | ||||||
|     status_count_after: |     status_count_after: 狀態 | ||||||
|       one: 狀態 |  | ||||||
|     status_count_before: 他們共嘟出了 |     status_count_before: 他們共嘟出了 | ||||||
|     terms: 使用條款 |     terms: 使用條款 | ||||||
|     user_count_after: |     user_count_after: 使用者 | ||||||
|       one: 使用者 |  | ||||||
|     user_count_before: 這裡共註冊有 |     user_count_before: 這裡共註冊有 | ||||||
|     what_is_mastodon: 什麼是 Mastodon? |     what_is_mastodon: 什麼是 Mastodon? | ||||||
|   accounts: |   accounts: | ||||||
|     follow: 關注 |     follow: 關注 | ||||||
|     followers: |     followers: 關注者 | ||||||
|       other: 關注者 |  | ||||||
|     following: 正在關注 |     following: 正在關注 | ||||||
|     media: 媒體 |     media: 媒體 | ||||||
|     moved_html: "%{name} 已經搬遷到 %{new_profile_link}:" |     moved_html: "%{name} 已經搬遷到 %{new_profile_link}:" | ||||||
|  | @ -48,9 +45,7 @@ zh-TW: | ||||||
|     nothing_here: 暫時沒有內容可供顯示! |     nothing_here: 暫時沒有內容可供顯示! | ||||||
|     people_followed_by: "%{name} 關注的人" |     people_followed_by: "%{name} 關注的人" | ||||||
|     people_who_follow: 關注 %{name} 的人 |     people_who_follow: 關注 %{name} 的人 | ||||||
|     posts: |     posts: 嘟文 | ||||||
|       one: 嘟掉 |  | ||||||
|       other: 嘟文 |  | ||||||
|     posts_tab_heading: 嘟文 |     posts_tab_heading: 嘟文 | ||||||
|     posts_with_replies: 嘟文與回覆 |     posts_with_replies: 嘟文與回覆 | ||||||
|     reserved_username: 此用戶名已被保留 |     reserved_username: 此用戶名已被保留 | ||||||
|  | @ -234,9 +229,7 @@ zh-TW: | ||||||
|         suspend: 自動封鎖 |         suspend: 自動封鎖 | ||||||
|       severity: 嚴重度 |       severity: 嚴重度 | ||||||
|       show: |       show: | ||||||
|         affected_accounts: |         affected_accounts: 資料庫中有%{count}個使用者受影響 | ||||||
|           one: 資料庫中有一個使用者受到影響 |  | ||||||
|           other: 資料庫中有%{count}個使用者受影響 |  | ||||||
|         retroactive: |         retroactive: | ||||||
|           silence: 對此網域的所有使用者取消靜音 |           silence: 對此網域的所有使用者取消靜音 | ||||||
|           suspend: 對此網域的所有使用者取消封鎖 |           suspend: 對此網域的所有使用者取消封鎖 | ||||||
|  | @ -480,18 +473,14 @@ zh-TW: | ||||||
|     followers_count: 關注者數量 |     followers_count: 關注者數量 | ||||||
|     lock_link: 將你的帳戶設定為私人 |     lock_link: 將你的帳戶設定為私人 | ||||||
|     purge: 移除關注者 |     purge: 移除關注者 | ||||||
|     success: |     success: 正準備軟性封鎖 %{count} 個網域的關注者…… | ||||||
|       one: 正準備軟性封鎖 1 個網域的關注者…… |  | ||||||
|       other: 正準備軟性封鎖 %{count} 個網域的關注者…… |  | ||||||
|     true_privacy_html: 請謹記,唯有<strong>點對點加密方可以真正確保你的隱私</strong>。 |     true_privacy_html: 請謹記,唯有<strong>點對點加密方可以真正確保你的隱私</strong>。 | ||||||
|     unlocked_warning_html: 任何人都可以在關注你後立即查看非公開的嘟文。只要%{lock_link},你就可以審核並拒絕關注請求。 |     unlocked_warning_html: 任何人都可以在關注你後立即查看非公開的嘟文。只要%{lock_link},你就可以審核並拒絕關注請求。 | ||||||
|     unlocked_warning_title: 你的帳戶是公開的 |     unlocked_warning_title: 你的帳戶是公開的 | ||||||
|   generic: |   generic: | ||||||
|     changes_saved_msg: 已成功儲存修改! |     changes_saved_msg: 已成功儲存修改! | ||||||
|     save_changes: 儲存修改 |     save_changes: 儲存修改 | ||||||
|     validation_errors: |     validation_errors: 送出的資料有 %{count} 個問題 | ||||||
|       one: 送出的資料有問題 |  | ||||||
|       other: 送出的資料有 %{count} 個問題 |  | ||||||
|   imports: |   imports: | ||||||
|     preface: 您可以在此匯入您在其他站點所匯出的資料檔,包括關注的使用者、封鎖的使用者名單。 |     preface: 您可以在此匯入您在其他站點所匯出的資料檔,包括關注的使用者、封鎖的使用者名單。 | ||||||
|     success: 資料檔上傳成功,正在匯入,請稍候 |     success: 資料檔上傳成功,正在匯入,請稍候 | ||||||
|  | @ -514,9 +503,7 @@ zh-TW: | ||||||
|     expires_in_prompt: 永不過期 |     expires_in_prompt: 永不過期 | ||||||
|     generate: 建立邀請連結 |     generate: 建立邀請連結 | ||||||
|     invited_by: 你的邀請人是: |     invited_by: 你的邀請人是: | ||||||
|     max_uses: |     max_uses: "%{count} 次" | ||||||
|       one: 1 次 |  | ||||||
|       other: "%{count} 次" |  | ||||||
|     max_uses_prompt: 無限制 |     max_uses_prompt: 無限制 | ||||||
|     prompt: 建立分享連結,邀請他人在本站點註冊 |     prompt: 建立分享連結,邀請他人在本站點註冊 | ||||||
|     table: |     table: | ||||||
|  | @ -542,12 +529,8 @@ zh-TW: | ||||||
|       action: 閱覽所有通知 |       action: 閱覽所有通知 | ||||||
|       body: 以下是自%{since}你最後一次登入以來錯過的訊息摘要 |       body: 以下是自%{since}你最後一次登入以來錯過的訊息摘要 | ||||||
|       mention: "%{name} 在此提及了你:" |       mention: "%{name} 在此提及了你:" | ||||||
|       new_followers_summary: |       new_followers_summary: 而且,你不在的時候,有 %{count} 個人關注你了! 好棒! | ||||||
|         one: 而且,你不在的時候,有一個人關注你! 耶! |       subject: "自從上次登入以來,你收到 %{count} 則新的通知 \U0001F418" | ||||||
|         other: 而且,你不在的時候,有 %{count} 個人關注你了! 好棒! |  | ||||||
|       subject: |  | ||||||
|         one: "自從上次登入以來,你收到 1 則新的通知 \U0001F418" |  | ||||||
|         other: "自從上次登入以來,你收到 %{count} 則新的通知 \U0001F418" |  | ||||||
|       title: 你不在的時候... |       title: 你不在的時候... | ||||||
|     favourite: |     favourite: | ||||||
|       body: '你的嘟文被 %{name} 加入了最愛:' |       body: '你的嘟文被 %{name} 加入了最愛:' | ||||||
|  | @ -653,17 +636,11 @@ zh-TW: | ||||||
|   statuses: |   statuses: | ||||||
|     attached: |     attached: | ||||||
|       description: 附件: %{attached} |       description: 附件: %{attached} | ||||||
|       image: |       image: "%{count} 幅圖片" | ||||||
|         one: "%{count} 幅圖片" |       video: "%{count} 段影片" | ||||||
|         other: "%{count} 幅圖片" |  | ||||||
|       video: |  | ||||||
|         one: "%{count} 段影片" |  | ||||||
|         other: "%{count} 段影片" |  | ||||||
|     boosted_from_html: 轉嘟自 %{acct_link} |     boosted_from_html: 轉嘟自 %{acct_link} | ||||||
|     content_warning: 內容警告: %{warning} |     content_warning: 內容警告: %{warning} | ||||||
|     disallowed_hashtags: |     disallowed_hashtags: 包含不允許的標籤: %{tags} | ||||||
|       one: 包含不允許的標籤: %{tags} |  | ||||||
|       other: 包含不允許的標籤: %{tags} |  | ||||||
|     language_detection: 自動偵測語言 |     language_detection: 自動偵測語言 | ||||||
|     open_in_web: 以網頁開啟 |     open_in_web: 以網頁開啟 | ||||||
|     over_character_limit: 超過了 %{max} 字的限制 |     over_character_limit: 超過了 %{max} 字的限制 | ||||||
|  |  | ||||||
|  | @ -14,12 +14,29 @@ class MigrateAccountConversations < ActiveRecord::Migration[5.2] | ||||||
|       sleep 1 |       sleep 1 | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     local_direct_statuses.find_each do |status| |     total        = estimate_rows(local_direct_statuses) + estimate_rows(notifications_about_direct_statuses) | ||||||
|  |     migrated     = 0 | ||||||
|  |     started_time = Time.zone.now | ||||||
|  |     last_time    = Time.zone.now | ||||||
|  | 
 | ||||||
|  |     local_direct_statuses.includes(:account, mentions: :account).find_each do |status| | ||||||
|       AccountConversation.add_status(status.account, status) |       AccountConversation.add_status(status.account, status) | ||||||
|  |       migrated += 1 | ||||||
|  | 
 | ||||||
|  |       if Time.zone.now - last_time > 1 | ||||||
|  |         say_progress(migrated, total, started_time) | ||||||
|  |         last_time = Time.zone.now | ||||||
|  |       end | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     notifications_about_direct_statuses.find_each do |notification| |     notifications_about_direct_statuses.includes(:account, mention: { status: [:account, mentions: :account] }).find_each do |notification| | ||||||
|       AccountConversation.add_status(notification.account, notification.target_status) |       AccountConversation.add_status(notification.account, notification.target_status) | ||||||
|  |       migrated += 1 | ||||||
|  | 
 | ||||||
|  |       if Time.zone.now - last_time > 1 | ||||||
|  |         say_progress(migrated, total, started_time) | ||||||
|  |         last_time = Time.zone.now | ||||||
|  |       end | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  | @ -28,16 +45,31 @@ class MigrateAccountConversations < ActiveRecord::Migration[5.2] | ||||||
| 
 | 
 | ||||||
|   private |   private | ||||||
| 
 | 
 | ||||||
|  |   def estimate_rows(query) | ||||||
|  |     result = exec_query("EXPLAIN #{query.to_sql}").first | ||||||
|  |     result['QUERY PLAN'].scan(/ rows=([\d]+)/).first&.first&.to_i || 0 | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def say_progress(migrated, total, started_time) | ||||||
|  |     status = "Migrated #{migrated} rows" | ||||||
|  | 
 | ||||||
|  |     percentage = 100.0 * migrated / total | ||||||
|  |     status += " (~#{sprintf('%.2f', percentage)}%, " | ||||||
|  | 
 | ||||||
|  |     remaining_time = (100.0 - percentage) * (Time.zone.now - started_time) / percentage | ||||||
|  | 
 | ||||||
|  |     status += "#{(remaining_time / 60).to_i}:" | ||||||
|  |     status += sprintf('%02d', remaining_time.to_i % 60) | ||||||
|  |     status += ' remaining)' | ||||||
|  | 
 | ||||||
|  |     say status, true | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   def local_direct_statuses |   def local_direct_statuses | ||||||
|     Status.unscoped |     Status.unscoped.local.where(visibility: :direct) | ||||||
|           .local |  | ||||||
|           .where(visibility: :direct) |  | ||||||
|           .includes(:account, mentions: :account) |  | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def notifications_about_direct_statuses |   def notifications_about_direct_statuses | ||||||
|     Notification.joins(mention: :status) |     Notification.joins(mention: :status).where(activity_type: 'Mention', statuses: { visibility: :direct }) | ||||||
|                 .where(activity_type: 'Mention', statuses: { visibility: :direct }) |  | ||||||
|                 .includes(:account, mention: { status: [:account, mentions: :account] }) |  | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ require_relative 'mastodon/emoji_cli' | ||||||
| require_relative 'mastodon/accounts_cli' | require_relative 'mastodon/accounts_cli' | ||||||
| require_relative 'mastodon/feeds_cli' | require_relative 'mastodon/feeds_cli' | ||||||
| require_relative 'mastodon/settings_cli' | require_relative 'mastodon/settings_cli' | ||||||
|  | require_relative 'mastodon/domains_cli' | ||||||
| 
 | 
 | ||||||
| module Mastodon | module Mastodon | ||||||
|   class CLI < Thor |   class CLI < Thor | ||||||
|  | @ -27,5 +28,8 @@ module Mastodon | ||||||
| 
 | 
 | ||||||
|     desc 'settings SUBCOMMAND ...ARGS', 'Manage dynamic settings' |     desc 'settings SUBCOMMAND ...ARGS', 'Manage dynamic settings' | ||||||
|     subcommand 'settings', Mastodon::SettingsCLI |     subcommand 'settings', Mastodon::SettingsCLI | ||||||
|  | 
 | ||||||
|  |     desc 'domains SUBCOMMAND ...ARGS', 'Manage account domains' | ||||||
|  |     subcommand 'domains', Mastodon::DomainsCLI | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| # frozen_string_literal: true | # frozen_string_literal: true | ||||||
| 
 | 
 | ||||||
| require 'rubygems/package' | require 'set' | ||||||
| require_relative '../../config/boot' | require_relative '../../config/boot' | ||||||
| require_relative '../../config/environment' | require_relative '../../config/environment' | ||||||
| require_relative 'cli_helper' | require_relative 'cli_helper' | ||||||
|  | @ -10,6 +10,7 @@ module Mastodon | ||||||
|     def self.exit_on_failure? |     def self.exit_on_failure? | ||||||
|       true |       true | ||||||
|     end |     end | ||||||
|  | 
 | ||||||
|     option :all, type: :boolean |     option :all, type: :boolean | ||||||
|     desc 'rotate [USERNAME]', 'Generate and broadcast new keys' |     desc 'rotate [USERNAME]', 'Generate and broadcast new keys' | ||||||
|     long_desc <<-LONG_DESC |     long_desc <<-LONG_DESC | ||||||
|  | @ -210,33 +211,25 @@ module Mastodon | ||||||
|       Accounts that have had confirmed activity within the last week |       Accounts that have had confirmed activity within the last week | ||||||
|       are excluded from the checks. |       are excluded from the checks. | ||||||
| 
 | 
 | ||||||
|       If 10 or more accounts from the same domain cannot be queried |       Domains that are unreachable are not checked. | ||||||
|       due to a connection error (such as missing DNS records) then |  | ||||||
|       the domain is considered dead, and all other accounts from it |  | ||||||
|       are deleted without further querying. |  | ||||||
| 
 | 
 | ||||||
|       With the --dry-run option, no deletes will actually be carried |       With the --dry-run option, no deletes will actually be carried | ||||||
|       out. |       out. | ||||||
|     LONG_DESC |     LONG_DESC | ||||||
|     def cull |     def cull | ||||||
|       domain_thresholds = Hash.new { |hash, key| hash[key] = 0 } |       skip_threshold = 7.days.ago | ||||||
|       skip_threshold    = 7.days.ago |       culled         = 0 | ||||||
|       culled            = 0 |       skip_domains   = Set.new | ||||||
|       dead_servers      = [] |       dry_run        = options[:dry_run] ? ' (DRY RUN)' : '' | ||||||
|       dry_run           = options[:dry_run] ? ' (DRY RUN)' : '' |  | ||||||
| 
 | 
 | ||||||
|       Account.remote.where(protocol: :activitypub).partitioned.find_each do |account| |       Account.remote.where(protocol: :activitypub).partitioned.find_each do |account| | ||||||
|         next if account.updated_at >= skip_threshold || (account.last_webfingered_at.present? && account.last_webfingered_at >= skip_threshold) |         next if account.updated_at >= skip_threshold || (account.last_webfingered_at.present? && account.last_webfingered_at >= skip_threshold) | ||||||
| 
 | 
 | ||||||
|         unless dead_servers.include?(account.domain) |         unless skip_domains.include?(account.domain) | ||||||
|           begin |           begin | ||||||
|             code = Request.new(:head, account.uri).perform(&:code) |             code = Request.new(:head, account.uri).perform(&:code) | ||||||
|           rescue HTTP::ConnectionError |           rescue HTTP::ConnectionError | ||||||
|             domain_thresholds[account.domain] += 1 |             skip_domains << account.domain | ||||||
| 
 |  | ||||||
|             if domain_thresholds[account.domain] >= 10 |  | ||||||
|               dead_servers << account.domain |  | ||||||
|             end |  | ||||||
|           rescue StandardError |           rescue StandardError | ||||||
|             next |             next | ||||||
|           end |           end | ||||||
|  | @ -255,24 +248,12 @@ module Mastodon | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       # Remove dead servers |  | ||||||
|       unless dead_servers.empty? || options[:dry_run] |  | ||||||
|         dead_servers.each do |domain| |  | ||||||
|           Account.where(domain: domain).find_each do |account| |  | ||||||
|             SuspendAccountService.new.call(account) |  | ||||||
|             account.destroy |  | ||||||
|             culled += 1 |  | ||||||
|             say('.', :green, false) |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       say |       say | ||||||
|       say("Removed #{culled} accounts (#{dead_servers.size} dead servers)#{dry_run}", :green) |       say("Removed #{culled} accounts. #{skip_domains.size} servers skipped#{dry_run}", skip_domains.empty? ? :green : :yellow) | ||||||
| 
 | 
 | ||||||
|       unless dead_servers.empty? |       unless skip_domains.empty? | ||||||
|         say('R.I.P.:', :yellow) |         say('The following servers were not available during the check:', :yellow) | ||||||
|         dead_servers.each { |domain| say('    ' + domain) } |         skip_domains.each { |domain| say('    ' + domain) } | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										40
									
								
								lib/mastodon/domains_cli.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								lib/mastodon/domains_cli.rb
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | require_relative '../../config/boot' | ||||||
|  | require_relative '../../config/environment' | ||||||
|  | require_relative 'cli_helper' | ||||||
|  | 
 | ||||||
|  | module Mastodon | ||||||
|  |   class DomainsCLI < Thor | ||||||
|  |     def self.exit_on_failure? | ||||||
|  |       true | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     option :dry_run, type: :boolean | ||||||
|  |     desc 'purge DOMAIN', 'Remove accounts from a DOMAIN without a trace' | ||||||
|  |     long_desc <<-LONG_DESC | ||||||
|  |       Remove all accounts from a given DOMAIN without leaving behind any | ||||||
|  |       records. Unlike a suspension, if the DOMAIN still exists in the wild, | ||||||
|  |       it means the accounts could return if they are resolved again. | ||||||
|  |     LONG_DESC | ||||||
|  |     def purge(domain) | ||||||
|  |       removed = 0 | ||||||
|  |       dry_run = options[:dry_run] ? ' (DRY RUN)' : '' | ||||||
|  | 
 | ||||||
|  |       Account.where(domain: domain).find_each do |account| | ||||||
|  |         unless options[:dry_run] | ||||||
|  |           SuspendAccountService.new.call(account) | ||||||
|  |           account.destroy | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         removed += 1 | ||||||
|  |         say('.', :green, false) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       DomainBlock.where(domain: domain).destroy_all | ||||||
|  | 
 | ||||||
|  |       say | ||||||
|  |       say("Removed #{removed} accounts#{dry_run}", :green) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -10,6 +10,7 @@ module Mastodon | ||||||
|     def self.exit_on_failure? |     def self.exit_on_failure? | ||||||
|       true |       true | ||||||
|     end |     end | ||||||
|  | 
 | ||||||
|     option :prefix |     option :prefix | ||||||
|     option :suffix |     option :suffix | ||||||
|     option :overwrite, type: :boolean |     option :overwrite, type: :boolean | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ module Mastodon | ||||||
|     def self.exit_on_failure? |     def self.exit_on_failure? | ||||||
|       true |       true | ||||||
|     end |     end | ||||||
|  | 
 | ||||||
|     option :all, type: :boolean, default: false |     option :all, type: :boolean, default: false | ||||||
|     option :background, type: :boolean, default: false |     option :background, type: :boolean, default: false | ||||||
|     option :dry_run, type: :boolean, default: false |     option :dry_run, type: :boolean, default: false | ||||||
|  | @ -58,7 +59,7 @@ module Mastodon | ||||||
|         account = Account.find_local(username) |         account = Account.find_local(username) | ||||||
| 
 | 
 | ||||||
|         if account.nil? |         if account.nil? | ||||||
|           say("Account #{username} is not found", :red) |           say('No such account', :red) | ||||||
|           exit(1) |           exit(1) | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ module Mastodon | ||||||
|     def self.exit_on_failure? |     def self.exit_on_failure? | ||||||
|       true |       true | ||||||
|     end |     end | ||||||
|  | 
 | ||||||
|     option :days, type: :numeric, default: 7 |     option :days, type: :numeric, default: 7 | ||||||
|     option :background, type: :boolean, default: false |     option :background, type: :boolean, default: false | ||||||
|     option :verbose, type: :boolean, default: false |     option :verbose, type: :boolean, default: false | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ module Mastodon | ||||||
|     def self.exit_on_failure? |     def self.exit_on_failure? | ||||||
|       true |       true | ||||||
|     end |     end | ||||||
|  | 
 | ||||||
|     desc 'open', 'Open registrations' |     desc 'open', 'Open registrations' | ||||||
|     def open |     def open | ||||||
|       Setting.open_registrations = true |       Setting.open_registrations = true | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue