diff options
Diffstat (limited to 'app')
108 files changed, 1696 insertions, 829 deletions
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 309cb65da..31144fe05 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -49,7 +49,7 @@ class AccountsController < ApplicationController end def default_statuses - @account.statuses.where(visibility: [:public, :unlisted]) + @account.statuses.not_local_only.where(visibility: [:public, :unlisted]) end def only_media_scope diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb index e9a512e70..7428c3f22 100644 --- a/app/controllers/admin/accounts_controller.rb +++ b/app/controllers/admin/accounts_controller.rb @@ -89,7 +89,8 @@ module Admin :username, :display_name, :email, - :ip + :ip, + :staff ) end end diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb index 3fa2a0b72..ccab03de4 100644 --- a/app/controllers/admin/custom_emojis_controller.rb +++ b/app/controllers/admin/custom_emojis_controller.rb @@ -92,7 +92,9 @@ module Admin def filter_params params.permit( :local, - :remote + :remote, + :by_domain, + :shortcode ) end end diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index 7cfe8fe71..5983c0fbe 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -72,19 +72,4 @@ class Api::BaseController < ApplicationController def render_empty render json: {}, status: 200 end - - def set_maps(statuses) # rubocop:disable Style/AccessorMethodName - if current_account.nil? - @reblogs_map = {} - @favourites_map = {} - @mutes_map = {} - return - end - - status_ids = statuses.compact.flat_map { |s| [s.id, s.reblog_of_id] }.uniq - conversation_ids = statuses.compact.map(&:conversation_id).compact.uniq - @reblogs_map = Status.reblogs_map(status_ids, current_account) - @favourites_map = Status.favourites_map(status_ids, current_account) - @mutes_map = Status.mutes_map(conversation_ids, current_account) - end end diff --git a/app/controllers/api/v1/accounts/lists_controller.rb b/app/controllers/api/v1/accounts/lists_controller.rb new file mode 100644 index 000000000..a7ba89ce2 --- /dev/null +++ b/app/controllers/api/v1/accounts/lists_controller.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class Api::V1::Accounts::ListsController < Api::BaseController + before_action -> { doorkeeper_authorize! :read } + before_action :require_user! + before_action :set_account + + respond_to :json + + def index + @lists = @account.lists.where(account: current_account) + render json: @lists, each_serializer: REST::ListSerializer + end + + private + + def set_account + @account = Account.find(params[:account_id]) + end +end diff --git a/app/controllers/api/v1/lists_controller.rb b/app/controllers/api/v1/lists_controller.rb index 9437373bd..180a91d81 100644 --- a/app/controllers/api/v1/lists_controller.rb +++ b/app/controllers/api/v1/lists_controller.rb @@ -1,18 +1,14 @@ # frozen_string_literal: true class Api::V1::ListsController < Api::BaseController - LISTS_LIMIT = 50 - before_action -> { doorkeeper_authorize! :read }, only: [:index, :show] before_action -> { doorkeeper_authorize! :write }, except: [:index, :show] before_action :require_user! before_action :set_list, except: [:index, :create] - after_action :insert_pagination_headers, only: :index - def index - @lists = List.where(account: current_account).paginate_by_max_id(limit_param(LISTS_LIMIT), params[:max_id], params[:since_id]) + @lists = List.where(account: current_account).all render json: @lists, each_serializer: REST::ListSerializer end @@ -44,36 +40,4 @@ class Api::V1::ListsController < Api::BaseController def list_params params.permit(:title) end - - def insert_pagination_headers - set_pagination_headers(next_path, prev_path) - end - - def next_path - if records_continue? - api_v1_lists_url pagination_params(max_id: pagination_max_id) - end - end - - def prev_path - unless @lists.empty? - api_v1_lists_url pagination_params(since_id: pagination_since_id) - end - end - - def pagination_max_id - @lists.last.id - end - - def pagination_since_id - @lists.first.id - end - - def records_continue? - @lists.size == limit_param(LISTS_LIMIT) - end - - def pagination_params(core_params) - params.permit(:limit).merge(core_params) - end end diff --git a/app/controllers/api/web/push_subscriptions_controller.rb b/app/controllers/api/web/push_subscriptions_controller.rb index d66237feb..52e250d02 100644 --- a/app/controllers/api/web/push_subscriptions_controller.rb +++ b/app/controllers/api/web/push_subscriptions_controller.rb @@ -28,6 +28,8 @@ class Api::Web::PushSubscriptionsController < Api::BaseController }, } + data.deep_merge!(params[:data]) if params[:data] + web_subscription = ::Web::PushSubscription.create!( endpoint: params[:subscription][:endpoint], key_p256dh: params[:subscription][:keys][:p256dh], diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb index 72d544102..f45d77b88 100644 --- a/app/controllers/auth/sessions_controller.rb +++ b/app/controllers/auth/sessions_controller.rb @@ -8,8 +8,8 @@ class Auth::SessionsController < Devise::SessionsController skip_before_action :require_no_authentication, only: [:create] skip_before_action :check_suspension, only: [:destroy] prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create] + prepend_before_action :set_pack before_action :set_instance_presenter, only: [:new] - before_action :set_pack def create super do |resource| diff --git a/app/controllers/concerns/rate_limit_headers.rb b/app/controllers/concerns/rate_limit_headers.rb index 36cb91075..b79c558d8 100644 --- a/app/controllers/concerns/rate_limit_headers.rb +++ b/app/controllers/concerns/rate_limit_headers.rb @@ -44,7 +44,8 @@ module RateLimitHeaders end def api_throttle_data - request.env['rack.attack.throttle_data']['api'] + most_limited_type, = request.env['rack.attack.throttle_data'].min_by { |_, v| v[:limit] } + request.env['rack.attack.throttle_data'][most_limited_type] end def request_time diff --git a/app/controllers/settings/migrations_controller.rb b/app/controllers/settings/migrations_controller.rb index b18403a7f..bc6436b87 100644 --- a/app/controllers/settings/migrations_controller.rb +++ b/app/controllers/settings/migrations_controller.rb @@ -28,6 +28,7 @@ class Settings::MigrationsController < ApplicationController end def migration_account_changed? - current_account.moved_to_account_id != @migration.account&.id + current_account.moved_to_account_id != @migration.account&.id && + current_account.id != @migration.account&.id end end diff --git a/app/controllers/settings/two_factor_authentication/confirmations_controller.rb b/app/controllers/settings/two_factor_authentication/confirmations_controller.rb index 4cf62db13..f1fa03f0a 100644 --- a/app/controllers/settings/two_factor_authentication/confirmations_controller.rb +++ b/app/controllers/settings/two_factor_authentication/confirmations_controller.rb @@ -2,11 +2,7 @@ module Settings module TwoFactorAuthentication - class ConfirmationsController < ApplicationController - layout 'admin' - - before_action :authenticate_user! - + class ConfirmationsController < BaseController def new prepare_two_factor_form end diff --git a/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb b/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb index e591e9502..94d1567f3 100644 --- a/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb +++ b/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb @@ -2,11 +2,7 @@ module Settings module TwoFactorAuthentication - class RecoveryCodesController < ApplicationController - layout 'admin' - - before_action :authenticate_user! - + class RecoveryCodesController < BaseController def create @recovery_codes = current_user.generate_otp_backup_codes! current_user.save! diff --git a/app/helpers/admin/filter_helper.rb b/app/helpers/admin/filter_helper.rb index 9443934b3..359c43d0e 100644 --- a/app/helpers/admin/filter_helper.rb +++ b/app/helpers/admin/filter_helper.rb @@ -1,11 +1,12 @@ # frozen_string_literal: true module Admin::FilterHelper - ACCOUNT_FILTERS = %i(local remote by_domain silenced suspended recent username display_name email ip).freeze - REPORT_FILTERS = %i(resolved account_id target_account_id).freeze - INVITE_FILTER = %i(available expired).freeze + ACCOUNT_FILTERS = %i(local remote by_domain silenced suspended recent username display_name email ip staff).freeze + REPORT_FILTERS = %i(resolved account_id target_account_id).freeze + INVITE_FILTER = %i(available expired).freeze + CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze - FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER + FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER + CUSTOM_EMOJI_FILTERS def filter_link_to(text, link_to_params, link_class_params = link_to_params) new_url = filtered_url_for(link_to_params) diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index abce85812..634c47eaa 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -10,6 +10,7 @@ module SettingsHelper eo: 'Esperanto', es: 'Español', fa: 'فارسی', + gl: 'Galego', fi: 'Suomi', fr: 'Français', he: 'עברית', @@ -27,6 +28,7 @@ module SettingsHelper pt: 'Português', 'pt-BR': 'Português do Brasil', ru: 'Русский', + sk: 'Slovensky', sv: 'Svenska', th: 'ภาษาไทย', tr: 'Türkçe', diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js index f5ecaae71..1a258c7e4 100644 --- a/app/javascript/flavours/glitch/features/account/components/header.js +++ b/app/javascript/flavours/glitch/features/account/components/header.js @@ -63,7 +63,15 @@ export default class Header extends ImmutablePureComponent { <div className='account__header__wrapper'> <div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}> <div> - <Avatar account={account} size={90} /> + <a + href={account.get('url')} + className='account__header__avatar' + role='presentation' + target='_blank' + rel='noopener' + > + <Avatar account={account} size={90} /> + </a> <span className='account__header__display-name' dangerouslySetInnerHTML={{ __html: displayName }} /> <span className='account__header__username'>@{account.get('acct')} {account.get('locked') ? <i className='fa fa-lock' /> : null}</span> diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js index 02b97ad00..63c9500b1 100644 --- a/app/javascript/flavours/glitch/features/compose/index.js +++ b/app/javascript/flavours/glitch/features/compose/index.js @@ -112,7 +112,7 @@ export default class Compose extends React.PureComponent { <Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}> {({ x }) => - <div className='drawer__inner darker' style={{ transform: `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}> + <div className='drawer__inner darker scrollable optionally-scrollable' style={{ transform: `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}> <SearchResultsContainer /> </div> } diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js index 0cb5238b0..538aa3d28 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js @@ -99,7 +99,7 @@ export default class DetailedStatus extends ImmutablePureComponent { } return ( - <div className='detailed-status'> + <div className='detailed-status' data-status-by={status.getIn(['account', 'acct'])}> <a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name'> <div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={48} /></div> <DisplayName account={status.get('account')} /> diff --git a/app/javascript/flavours/glitch/locales/en.js b/app/javascript/flavours/glitch/locales/en.js index 0681d27d8..a2a2b4a9b 100644 --- a/app/javascript/flavours/glitch/locales/en.js +++ b/app/javascript/flavours/glitch/locales/en.js @@ -47,6 +47,14 @@ const messages = { 'notification_purge.btn_none': 'Select\nnone', 'notification_purge.btn_invert': 'Invert\nselection', 'notification_purge.btn_apply': 'Clear\nselected', + + 'compose.attach.upload': 'Upload a file', + 'compose.attach.doodle': 'Draw something', + 'compose.attach': 'Attach...', + + 'advanced-options.local-only.short': 'Local-only', + 'advanced-options.local-only.long': 'Do not post to other instances', + 'advanced_options.icon_title': 'Advanced options', }; export default Object.assign({}, inherited, messages); diff --git a/app/javascript/flavours/glitch/locales/ja.js b/app/javascript/flavours/glitch/locales/ja.js index 39dc3b81d..96e6e0b2c 100644 --- a/app/javascript/flavours/glitch/locales/ja.js +++ b/app/javascript/flavours/glitch/locales/ja.js @@ -23,11 +23,11 @@ const messages = { 'settings.enable_collapsed': 'トゥート折りたたみを有効にする', 'settings.general': '一般', 'settings.image_backgrounds': '画像背景', - 'settings.image_backgrounds_media': '折りたまれたメディア付きテゥートをプレビュー', + 'settings.image_backgrounds_media': '折りたまれたメディア付きトゥートをプレビュー', 'settings.image_backgrounds_users': '折りたまれたトゥートの背景を変更する', 'settings.media': 'メディア', 'settings.media_letterbox': 'メディアをレターボックス式で表示', - 'settings.media_fullwidth': '全幅メディアプリビュー', + 'settings.media_fullwidth': '全幅メディアプレビュー', 'settings.preferences': 'ユーザー設定', 'settings.wide_view': 'ワイドビュー(Desktopレイアウトのみ)', 'settings.navbar_under': 'ナビを画面下部に移動させる(Mobileレイアウトのみ)', @@ -37,6 +37,10 @@ const messages = { 'status.collapse': '折りたたむ', 'status.uncollapse': '折りたたみを解除', + 'favourite_modal.combo': '次からは {combo} を押せば、これをスキップできます。', + + 'home.column_settings.show_direct': 'DMを表示', + 'notification.markForDeletion': '選択', 'notifications.clear': '通知を全てクリアする', 'notifications.marked_clear_confirmation': '削除した全ての通知を完全に削除してもよろしいですか?', @@ -46,6 +50,14 @@ const messages = { 'notification_purge.btn_none': '選択\n解除', 'notification_purge.btn_invert': '選択を\n反転', 'notification_purge.btn_apply': '選択したものを\n削除', + + 'compose.attach.upload': 'ファイルをアップロード', + 'compose.attach.doodle': '落書きをする', + 'compose.attach': 'アタッチ...', + + 'advanced-options.local-only.short': 'ローカル限定', + 'advanced-options.local-only.long': '他のインスタンスには投稿されません', + 'advanced_options.icon_title': '高度な設定', }; export default Object.assign({}, inherited, messages); diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss index 7c5032217..4e923bb98 100644 --- a/app/javascript/flavours/glitch/styles/admin.scss +++ b/app/javascript/flavours/glitch/styles/admin.scss @@ -363,3 +363,120 @@ } } } + +.spacer { + flex: 1 1 auto; +} + +.log-entry { + margin-bottom: 8px; + line-height: 20px; + + &__header { + display: flex; + justify-content: flex-start; + align-items: center; + padding: 10px; + background: $ui-base-color; + color: $ui-primary-color; + border-radius: 4px 4px 0 0; + font-size: 14px; + position: relative; + } + + &__avatar { + margin-right: 10px; + + .avatar { + display: block; + margin: 0; + border-radius: 50%; + width: 40px; + height: 40px; + } + } + + &__title { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + &__timestamp { + color: lighten($ui-base-color, 34%); + } + + &__extras { + background: lighten($ui-base-color, 6%); + border-radius: 0 0 4px 4px; + padding: 10px; + color: $ui-primary-color; + font-family: 'mastodon-font-monospace', monospace; + font-size: 12px; + white-space: nowrap; + min-height: 20px; + } + + &__icon { + font-size: 28px; + margin-right: 10px; + color: lighten($ui-base-color, 34%); + } + + &__icon__overlay { + position: absolute; + top: 10px; + right: 10px; + width: 10px; + height: 10px; + border-radius: 50%; + + &.positive { + background: $success-green; + } + + &.negative { + background: $error-red; + } + + &.neutral { + background: $ui-highlight-color; + } + } + + a, + .username, + .target { + color: $ui-secondary-color; + text-decoration: none; + font-weight: 500; + } + + .diff-old { + color: $error-red; + } + + .diff-neutral { + color: $ui-secondary-color; + } + + .diff-new { + color: $success-green; + } +} + +.name-tag { + display: flex; + align-items: center; + + .avatar { + display: block; + margin: 0; + margin-right: 5px; + border-radius: 50%; + } + + .username { + font-weight: 500; + } +} diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss index 35f308b2d..7dc95b474 100644 --- a/app/javascript/flavours/glitch/styles/components/index.scss +++ b/app/javascript/flavours/glitch/styles/components/index.scss @@ -1254,6 +1254,15 @@ } } +.account__header__avatar { + background-size: 90px 90px; + display: block; + height: 90px; + margin: 0 auto 10px; + overflow: hidden; + width: 90px; +} + .account-authorize { padding: 14px 10px; diff --git a/app/javascript/mastodon/actions/favourites.js b/app/javascript/mastodon/actions/favourites.js index 09ce51fce..93094c526 100644 --- a/app/javascript/mastodon/actions/favourites.js +++ b/app/javascript/mastodon/actions/favourites.js @@ -10,6 +10,10 @@ export const FAVOURITED_STATUSES_EXPAND_FAIL = 'FAVOURITED_STATUSES_EXPAND_FA export function fetchFavouritedStatuses() { return (dispatch, getState) => { + if (getState().getIn(['status_lists', 'favourites', 'isLoading'])) { + return; + } + dispatch(fetchFavouritedStatusesRequest()); api(getState).get('/api/v1/favourites').then(response => { @@ -46,7 +50,7 @@ export function expandFavouritedStatuses() { return (dispatch, getState) => { const url = getState().getIn(['status_lists', 'favourites', 'next'], null); - if (url === null) { + if (url === null || getState().getIn(['status_lists', 'favourites', 'isLoading'])) { return; } diff --git a/app/javascript/mastodon/actions/push_notifications.js b/app/javascript/mastodon/actions/push_notifications.js index 55661d2b0..de06385f9 100644 --- a/app/javascript/mastodon/actions/push_notifications.js +++ b/app/javascript/mastodon/actions/push_notifications.js @@ -1,4 +1,5 @@ import axios from 'axios'; +import { pushNotificationsSetting } from '../settings'; export const SET_BROWSER_SUPPORT = 'PUSH_NOTIFICATIONS_SET_BROWSER_SUPPORT'; export const SET_SUBSCRIPTION = 'PUSH_NOTIFICATIONS_SET_SUBSCRIPTION'; @@ -42,11 +43,15 @@ export function saveSettings() { const state = getState().get('push_notifications'); const subscription = state.get('subscription'); const alerts = state.get('alerts'); + const data = { alerts }; axios.put(`/api/web/push_subscriptions/${subscription.get('id')}`, { - data: { - alerts, - }, + data, + }).then(() => { + const me = getState().getIn(['meta', 'me']); + if (me) { + pushNotificationsSetting.set(me, data); + } }); }; } diff --git a/app/javascript/mastodon/components/avatar.js b/app/javascript/mastodon/components/avatar.js index f7c484ee3..570505833 100644 --- a/app/javascript/mastodon/components/avatar.js +++ b/app/javascript/mastodon/components/avatar.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import { autoPlayGif } from '../initial_state'; export default class Avatar extends React.PureComponent { @@ -8,12 +9,12 @@ export default class Avatar extends React.PureComponent { account: ImmutablePropTypes.map.isRequired, size: PropTypes.number.isRequired, style: PropTypes.object, - animate: PropTypes.bool, inline: PropTypes.bool, + animate: PropTypes.bool, }; static defaultProps = { - animate: false, + animate: autoPlayGif, size: 20, inline: false, }; diff --git a/app/javascript/mastodon/components/avatar_overlay.js b/app/javascript/mastodon/components/avatar_overlay.js index f5d67b34e..3ec1d7730 100644 --- a/app/javascript/mastodon/components/avatar_overlay.js +++ b/app/javascript/mastodon/components/avatar_overlay.js @@ -1,22 +1,29 @@ import React from 'react'; +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import { autoPlayGif } from '../initial_state'; export default class AvatarOverlay extends React.PureComponent { static propTypes = { account: ImmutablePropTypes.map.isRequired, friend: ImmutablePropTypes.map.isRequired, + animate: PropTypes.bool, + }; + + static defaultProps = { + animate: autoPlayGif, }; render() { - const { account, friend } = this.props; + const { account, friend, animate } = this.props; const baseStyle = { - backgroundImage: `url(${account.get('avatar_static')})`, + backgroundImage: `url(${account.get(animate ? 'avatar' : 'avatar_static')})`, }; const overlayStyle = { - backgroundImage: `url(${friend.get('avatar_static')})`, + backgroundImage: `url(${friend.get(animate ? 'avatar' : 'avatar_static')})`, }; return ( diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index 7890755f3..a876c5197 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -156,6 +156,8 @@ export default class ComposeForm extends ImmutablePureComponent { return ( <div className='compose-form'> + <WarningContainer /> + <Collapsable isVisible={this.props.spoiler} fullHeight={50}> <div className='spoiler-input'> <label> @@ -165,8 +167,6 @@ export default class ComposeForm extends ImmutablePureComponent { </div> </Collapsable> - <WarningContainer /> - <ReplyIndicatorContainer /> <div className='compose-form__autosuggest-wrapper'> @@ -199,11 +199,11 @@ export default class ComposeForm extends ImmutablePureComponent { <SensitiveButtonContainer /> <SpoilerButtonContainer /> </div> + <div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div> + </div> - <div className='compose-form__publish'> - <div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div> - <div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0)} block /></div> - </div> + <div className='compose-form__publish'> + <div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0)} block /></div> </div> </div> ); diff --git a/app/javascript/mastodon/features/compose/components/reply_indicator.js b/app/javascript/mastodon/features/compose/components/reply_indicator.js index 7672440b4..d8cda96f3 100644 --- a/app/javascript/mastodon/features/compose/components/reply_indicator.js +++ b/app/javascript/mastodon/features/compose/components/reply_indicator.js @@ -6,6 +6,7 @@ import IconButton from '../../../components/icon_button'; import DisplayName from '../../../components/display_name'; import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import { isRtl } from '../../../rtl'; const messages = defineMessages({ cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' }, @@ -42,7 +43,10 @@ export default class ReplyIndicator extends ImmutablePureComponent { return null; } - const content = { __html: status.get('contentHtml') }; + const content = { __html: status.get('contentHtml') }; + const style = { + direction: isRtl(status.get('search_index')) ? 'rtl' : 'ltr', + }; return ( <div className='reply-indicator'> @@ -55,7 +59,7 @@ export default class ReplyIndicator extends ImmutablePureComponent { </a> </div> - <div className='reply-indicator__content' dangerouslySetInnerHTML={content} /> + <div className='reply-indicator__content' style={style} dangerouslySetInnerHTML={content} /> </div> ); } diff --git a/app/javascript/mastodon/features/compose/components/upload.js b/app/javascript/mastodon/features/compose/components/upload.js index 6ab76492a..3a3d17710 100644 --- a/app/javascript/mastodon/features/compose/components/upload.js +++ b/app/javascript/mastodon/features/compose/components/upload.js @@ -62,7 +62,7 @@ export default class Upload extends ImmutablePureComponent { render () { const { intl, media } = this.props; const active = this.state.hovered || this.state.focused; - const description = this.state.dirtyDescription || media.get('description') || ''; + const description = this.state.dirtyDescription || (this.state.dirtyDescription !== '' && media.get('description')) || ''; return ( <div className='compose-form__upload' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}> diff --git a/app/javascript/mastodon/features/favourited_statuses/index.js b/app/javascript/mastodon/features/favourited_statuses/index.js index 1e1f5873c..67b107bc8 100644 --- a/app/javascript/mastodon/features/favourited_statuses/index.js +++ b/app/javascript/mastodon/features/favourited_statuses/index.js @@ -9,6 +9,7 @@ import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import StatusList from '../../components/status_list'; import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import { debounce } from 'lodash'; const messages = defineMessages({ heading: { id: 'column.favourites', defaultMessage: 'Favourites' }, @@ -16,6 +17,7 @@ const messages = defineMessages({ const mapStateToProps = state => ({ statusIds: state.getIn(['status_lists', 'favourites', 'items']), + isLoading: state.getIn(['status_lists', 'favourites', 'isLoading'], true), hasMore: !!state.getIn(['status_lists', 'favourites', 'next']), }); @@ -30,6 +32,7 @@ export default class Favourites extends ImmutablePureComponent { columnId: PropTypes.string, multiColumn: PropTypes.bool, hasMore: PropTypes.bool, + isLoading: PropTypes.bool, }; componentWillMount () { @@ -59,12 +62,12 @@ export default class Favourites extends ImmutablePureComponent { this.column = c; } - handleScrollToBottom = () => { + handleScrollToBottom = debounce(() => { this.props.dispatch(expandFavouritedStatuses()); - } + }, 300, { leading: true }) render () { - const { intl, statusIds, columnId, multiColumn, hasMore } = this.props; + const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; const pinned = !!columnId; return ( @@ -85,6 +88,7 @@ export default class Favourites extends ImmutablePureComponent { statusIds={statusIds} scrollKey={`favourited_statuses-${columnId}`} hasMore={hasMore} + isLoading={isLoading} onScrollToBottom={this.handleScrollToBottom} /> </Column> diff --git a/app/javascript/mastodon/features/keyboard_shortcuts/index.js b/app/javascript/mastodon/features/keyboard_shortcuts/index.js index 22991fcba..8531e03c0 100644 --- a/app/javascript/mastodon/features/keyboard_shortcuts/index.js +++ b/app/javascript/mastodon/features/keyboard_shortcuts/index.js @@ -33,59 +33,59 @@ export default class KeyboardShortcuts extends ImmutablePureComponent { </thead> <tbody> <tr> - <td><code>r</code></td> + <td><kbd>r</kbd></td> <td><FormattedMessage id='keyboard_shortcuts.reply' defaultMessage='to reply' /></td> </tr> <tr> - <td><code>m</code></td> + <td><kbd>m</kbd></td> <td><FormattedMessage id='keyboard_shortcuts.mention' defaultMessage='to mention author' /></td> </tr> <tr> - <td><code>f</code></td> + <td><kbd>f</kbd></td> <td><FormattedMessage id='keyboard_shortcuts.favourite' defaultMessage='to favourite' /></td> </tr> <tr> - <td><code>b</code></td> + <td><kbd>b</kbd></td> <td><FormattedMessage id='keyboard_shortcuts.boost' defaultMessage='to boost' /></td> </tr> <tr> - <td><code>enter</code></td> + <td><kbd>enter</kbd></td> <td><FormattedMessage id='keyboard_shortcuts.enter' defaultMessage='to open status' /></td> </tr> <tr> - <td><code>up</code></td> + <td><kbd>up</kbd></td> <td><FormattedMessage id='keyboard_shortcuts.up' defaultMessage='to move up in the list' /></td> </tr> <tr> - <td><code>down</code></td> + <td><kbd>down</kbd></td> <td><FormattedMessage id='keyboard_shortcuts.down' defaultMessage='to move down in the list' /></td> </tr> <tr> - <td><code>1</code>-<code>9</code></td> + <td><kbd>1</kbd>-<kbd>9</kbd></td> <td><FormattedMessage id='keyboard_shortcuts.column' defaultMessage='to focus a status in one of the columns' /></td> </tr> <tr> - <td><code>n</code></td> + <td><kbd>n</kbd></td> <td><FormattedMessage id='keyboard_shortcuts.compose' defaultMessage='to focus the compose textarea' /></td> </tr> <tr> - <td><code>alt</code>+<code>n</code></td> + <td><kbd>alt</kbd>+<kbd>n</kbd></td> <td><FormattedMessage id='keyboard_shortcuts.toot' defaultMessage='to start a brand new toot' /></td> </tr> <tr> - <td><code>backspace</code></td> + <td><kbd>backspace</kbd></td> <td><FormattedMessage id='keyboard_shortcuts.back' defaultMessage='to navigate back' /></td> </tr> <tr> - <td><code>s</code></td> + <td><kbd>s</kbd></td> <td><FormattedMessage id='keyboard_shortcuts.search' defaultMessage='to focus search' /></td> </tr> <tr> - <td><code>esc</code></td> + <td><kbd>esc</kbd></td> <td><FormattedMessage id='keyboard_shortcuts.unfocus' defaultMessage='to un-focus compose textarea/search' /></td> </tr> <tr> - <td><code>?</code></td> + <td><kbd>?</kbd></td> <td><FormattedMessage id='keyboard_shortcuts.legend' defaultMessage='to display this legend' /></td> </tr> </tbody> diff --git a/app/javascript/mastodon/features/list_timeline/index.js b/app/javascript/mastodon/features/list_timeline/index.js index 1dcd4de14..ae136e48f 100644 --- a/app/javascript/mastodon/features/list_timeline/index.js +++ b/app/javascript/mastodon/features/list_timeline/index.js @@ -161,7 +161,7 @@ export default class ListTimeline extends React.PureComponent { scrollKey={`list_timeline-${columnId}`} timelineId={`list:${id}`} loadMore={this.handleLoadMore} - emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet.' />} + emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet. When members of this list post new statuses, they will appear here.' />} /> </Column> ); diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.js b/app/javascript/mastodon/features/notifications/components/column_settings.js index 88a29d4d3..57cded4f1 100644 --- a/app/javascript/mastodon/features/notifications/components/column_settings.js +++ b/app/javascript/mastodon/features/notifications/components/column_settings.js @@ -40,10 +40,10 @@ export default class ColumnSettings extends React.PureComponent { <span id='notifications-follow' className='column-settings__section'><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span> <div className='column-settings__row'> - <SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'follow']} onChange={onChange} label={alertStr} /> - {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingKey={['alerts', 'follow']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />} - <SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'follow']} onChange={onChange} label={showStr} /> - <SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'follow']} onChange={onChange} label={soundStr} /> + <SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'follow']} onChange={onChange} label={alertStr} /> + {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'follow']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />} + <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'follow']} onChange={onChange} label={showStr} /> + <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} /> </div> </div> @@ -51,10 +51,10 @@ export default class ColumnSettings extends React.PureComponent { <span id='notifications-favourite' className='column-settings__section'><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span> <div className='column-settings__row'> - <SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'favourite']} onChange={onChange} label={alertStr} /> - {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingKey={['alerts', 'favourite']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />} - <SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'favourite']} onChange={onChange} label={showStr} /> - <SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'favourite']} onChange={onChange} label={soundStr} /> + <SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'favourite']} onChange={onChange} label={alertStr} /> + {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'favourite']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />} + <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'favourite']} onChange={onChange} label={showStr} /> + <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'favourite']} onChange={onChange} label={soundStr} /> </div> </div> @@ -62,10 +62,10 @@ export default class ColumnSettings extends React.PureComponent { <span id='notifications-mention' className='column-settings__section'><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span> <div className='column-settings__row'> - <SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'mention']} onChange={onChange} label={alertStr} /> - {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingKey={['alerts', 'mention']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />} - <SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'mention']} onChange={onChange} label={showStr} /> - <SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'mention']} onChange={onChange} label={soundStr} /> + <SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'mention']} onChange={onChange} label={alertStr} /> + {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'mention']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />} + <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'mention']} onChange={onChange} label={showStr} /> + <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'mention']} onChange={onChange} label={soundStr} /> </div> </div> @@ -73,10 +73,10 @@ export default class ColumnSettings extends React.PureComponent { <span id='notifications-reblog' className='column-settings__section'><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span> <div className='column-settings__row'> - <SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'reblog']} onChange={onChange} label={alertStr} /> - {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingKey={['alerts', 'reblog']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />} - <SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={showStr} /> - <SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'reblog']} onChange={onChange} label={soundStr} /> + <SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'reblog']} onChange={onChange} label={alertStr} /> + {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'reblog']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />} + <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'reblog']} onChange={onChange} label={showStr} /> + <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'reblog']} onChange={onChange} label={soundStr} /> </div> </div> </div> diff --git a/app/javascript/mastodon/features/notifications/components/setting_toggle.js b/app/javascript/mastodon/features/notifications/components/setting_toggle.js index 281359d2a..ac2211e48 100644 --- a/app/javascript/mastodon/features/notifications/components/setting_toggle.js +++ b/app/javascript/mastodon/features/notifications/components/setting_toggle.js @@ -8,23 +8,23 @@ export default class SettingToggle extends React.PureComponent { static propTypes = { prefix: PropTypes.string, settings: ImmutablePropTypes.map.isRequired, - settingKey: PropTypes.array.isRequired, + settingPath: PropTypes.array.isRequired, label: PropTypes.node.isRequired, meta: PropTypes.node, onChange: PropTypes.func.isRequired, } onChange = ({ target }) => { - this.props.onChange(this.props.settingKey, target.checked); + this.props.onChange(this.props.settingPath, target.checked); } render () { - const { prefix, settings, settingKey, label, meta } = this.props; - const id = ['setting-toggle', prefix, ...settingKey].filter(Boolean).join('-'); + const { prefix, settings, settingPath, label, meta } = this.props; + const id = ['setting-toggle', prefix, ...settingPath].filter(Boolean).join('-'); return ( <div className='setting-toggle'> - <Toggle id={id} checked={settings.getIn(settingKey)} onChange={this.onChange} onKeyDown={this.onKeyDown} /> + <Toggle id={id} checked={settings.getIn(settingPath)} onChange={this.onChange} onKeyDown={this.onKeyDown} /> <label htmlFor={id} className='setting-toggle__label'>{label}</label> {meta && <span className='setting-meta__label'>{meta}</span>} </div> diff --git a/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js b/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js index f15fbb2f4..f14be2aaf 100644 --- a/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js +++ b/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js @@ -8,6 +8,7 @@ import { } from '../../../actions/timelines'; import Column from '../../../components/column'; import ColumnHeader from '../../../components/column_header'; +import { connectHashtagStream } from '../../../actions/streaming'; @connect() export default class HashtagTimeline extends React.PureComponent { @@ -29,16 +30,13 @@ export default class HashtagTimeline extends React.PureComponent { const { dispatch, hashtag } = this.props; dispatch(refreshHashtagTimeline(hashtag)); - - this.polling = setInterval(() => { - dispatch(refreshHashtagTimeline(hashtag)); - }, 10000); + this.disconnect = dispatch(connectHashtagStream(hashtag)); } componentWillUnmount () { - if (typeof this.polling !== 'undefined') { - clearInterval(this.polling); - this.polling = null; + if (this.disconnect) { + this.disconnect(); + this.disconnect = null; } } diff --git a/app/javascript/mastodon/features/standalone/public_timeline/index.js b/app/javascript/mastodon/features/standalone/public_timeline/index.js index de4b5320a..5805d1a10 100644 --- a/app/javascript/mastodon/features/standalone/public_timeline/index.js +++ b/app/javascript/mastodon/features/standalone/public_timeline/index.js @@ -9,6 +9,7 @@ import { import Column from '../../../components/column'; import ColumnHeader from '../../../components/column_header'; import { defineMessages, injectIntl } from 'react-intl'; +import { connectPublicStream } from '../../../actions/streaming'; const messages = defineMessages({ title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' }, @@ -35,16 +36,13 @@ export default class PublicTimeline extends React.PureComponent { const { dispatch } = this.props; dispatch(refreshPublicTimeline()); - - this.polling = setInterval(() => { - dispatch(refreshPublicTimeline()); - }, 3000); + this.disconnect = dispatch(connectPublicStream()); } componentWillUnmount () { - if (typeof this.polling !== 'undefined') { - clearInterval(this.polling); - this.polling = null; + if (this.disconnect) { + this.disconnect(); + this.disconnect = null; } } diff --git a/app/javascript/mastodon/features/status/components/card.js b/app/javascript/mastodon/features/status/components/card.js index d3e322c36..2f6a7831e 100644 --- a/app/javascript/mastodon/features/status/components/card.js +++ b/app/javascript/mastodon/features/status/components/card.js @@ -43,7 +43,7 @@ export default class Card extends React.PureComponent { Immutable.fromJS([ { type: 'image', - url: card.get('url'), + url: card.get('embed_url'), description: card.get('title'), meta: { original: { @@ -59,6 +59,8 @@ export default class Card extends React.PureComponent { renderLink () { const { card, maxDescription } = this.props; + const { width } = this.state; + const horizontal = card.get('width') > card.get('height') && (card.get('width') + 100 >= width); let image = ''; let provider = card.get('provider_name'); @@ -75,17 +77,15 @@ export default class Card extends React.PureComponent { provider = decodeIDNA(getHostname(card.get('url'))); } - const className = classnames('status-card', { - 'horizontal': card.get('width') > card.get('height'), - }); + const className = classnames('status-card', { horizontal }); return ( - <a href={card.get('url')} className={className} target='_blank' rel='noopener'> + <a href={card.get('url')} className={className} target='_blank' rel='noopener' ref={this.setRef}> {image} <div className='status-card__content'> <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong> - <p className='status-card__description'>{(card.get('description') || '').substring(0, maxDescription)}</p> + {!horizontal && <p className='status-card__description'>{(card.get('description') || '').substring(0, maxDescription)}</p>} <span className='status-card__host'>{provider}</span> </div> </a> diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js index 93ed9e605..f00b74dfd 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.js +++ b/app/javascript/mastodon/features/ui/components/columns_area.js @@ -53,7 +53,10 @@ export default class ColumnsArea extends ImmutablePureComponent { if (!this.props.singleColumn) { this.node.addEventListener('wheel', this.handleWheel, detectPassiveEvents.hasSupport ? { passive: true } : false); } - this.lastIndex = getIndex(this.context.router.history.location.pathname); + + this.lastIndex = getIndex(this.context.router.history.location.pathname); + this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl'); + this.setState({ shouldAnimate: true }); } @@ -79,7 +82,8 @@ export default class ColumnsArea extends ImmutablePureComponent { handleChildrenContentChange() { if (!this.props.singleColumn) { - this._interruptScrollAnimation = scrollRight(this.node, this.node.scrollWidth - window.innerWidth); + const modifier = this.isRtlLayout ? -1 : 1; + this._interruptScrollAnimation = scrollRight(this.node, (this.node.scrollWidth - window.innerWidth) * modifier); } } diff --git a/app/javascript/mastodon/features/ui/components/video_modal.js b/app/javascript/mastodon/features/ui/components/video_modal.js index 1437deeb0..6a883759f 100644 --- a/app/javascript/mastodon/features/ui/components/video_modal.js +++ b/app/javascript/mastodon/features/ui/components/video_modal.js @@ -23,6 +23,7 @@ export default class VideoModal extends ImmutablePureComponent { src={media.get('url')} startTime={time} onCloseVideo={onClose} + detailed description={media.get('description')} /> </div> diff --git a/app/javascript/mastodon/features/video/index.js b/app/javascript/mastodon/features/video/index.js index 003bf23a8..0ee8bb6c8 100644 --- a/app/javascript/mastodon/features/video/index.js +++ b/app/javascript/mastodon/features/video/index.js @@ -17,6 +17,18 @@ const messages = defineMessages({ exit_fullscreen: { id: 'video.exit_fullscreen', defaultMessage: 'Exit full screen' }, }); +const formatTime = secondsNum => { + let hours = Math.floor(secondsNum / 3600); + let minutes = Math.floor((secondsNum - (hours * 3600)) / 60); + let seconds = secondsNum - (hours * 3600) - (minutes * 60); + + if (hours < 10) hours = '0' + hours; + if (minutes < 10) minutes = '0' + minutes; + if (seconds < 10) seconds = '0' + seconds; + + return (hours === '00' ? '' : `${hours}:`) + `${minutes}:${seconds}`; +}; + const findElementPosition = el => { let box; @@ -83,11 +95,13 @@ export default class Video extends React.PureComponent { startTime: PropTypes.number, onOpenVideo: PropTypes.func, onCloseVideo: PropTypes.func, + detailed: PropTypes.bool, intl: PropTypes.object.isRequired, }; state = { - progress: 0, + currentTime: 0, + duration: 0, paused: true, dragging: false, fullscreen: false, @@ -117,7 +131,10 @@ export default class Video extends React.PureComponent { } handleTimeUpdate = () => { - this.setState({ progress: 100 * (this.video.currentTime / this.video.duration) }); + this.setState({ + currentTime: Math.floor(this.video.currentTime), + duration: Math.floor(this.video.duration), + }); } handleMouseDown = e => { @@ -143,8 +160,10 @@ export default class Video extends React.PureComponent { handleMouseMove = throttle(e => { const { x } = getPointerPosition(this.seek, e); - this.video.currentTime = this.video.duration * x; - this.setState({ progress: x * 100 }); + const currentTime = Math.floor(this.video.duration * x); + + this.video.currentTime = currentTime; + this.setState({ currentTime }); }, 60); togglePlay = () => { @@ -226,11 +245,12 @@ export default class Video extends React.PureComponent { } render () { - const { preview, src, width, height, startTime, onOpenVideo, onCloseVideo, intl, alt } = this.props; - const { progress, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state; + const { preview, src, width, height, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed } = this.props; + const { currentTime, duration, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state; + const progress = (currentTime / duration) * 100; return ( - <div className={classNames('video-player', { inactive: !revealed, inline: width && height && !fullscreen, fullscreen })} style={{ width, height }} ref={this.setPlayerRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}> + <div className={classNames('video-player', { inactive: !revealed, detailed, inline: width && height && !fullscreen, fullscreen })} style={{ width, height }} ref={this.setPlayerRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}> <video ref={this.setVideoRef} src={src} @@ -267,16 +287,27 @@ export default class Video extends React.PureComponent { /> </div> - <div className='video-player__buttons left'> - <button aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button> - <button aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button> - {!onCloseVideo && <button aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><i className='fa fa-fw fa-eye' /></button>} - </div> - - <div className='video-player__buttons right'> - {(!fullscreen && onOpenVideo) && <button aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><i className='fa fa-fw fa-expand' /></button>} - {onCloseVideo && <button aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><i className='fa fa-fw fa-times' /></button>} - <button aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><i className={classNames('fa fa-fw', { 'fa-arrows-alt': !fullscreen, 'fa-compress': fullscreen })} /></button> + <div className='video-player__buttons-bar'> + <div className='video-player__buttons left'> + <button aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button> + <button aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button> + + {!onCloseVideo && <button aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><i className='fa fa-fw fa-eye' /></button>} + + {(detailed || fullscreen) && + <span> + <span className='video-player__time-current'>{formatTime(currentTime)}</span> + <span className='video-player__time-sep'>/</span> + <span className='video-player__time-total'>{formatTime(duration)}</span> + </span> + } + </div> + + <div className='video-player__buttons right'> + {(!fullscreen && onOpenVideo) && <button aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><i className='fa fa-fw fa-expand' /></button>} + {onCloseVideo && <button aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><i className='fa fa-fw fa-compress' /></button>} + <button aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><i className={classNames('fa fa-fw', { 'fa-arrows-alt': !fullscreen, 'fa-compress': fullscreen })} /></button> + </div> </div> </div> </div> diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index a984b38d2..d699a69df 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -36,9 +36,9 @@ "column.favourites": "المفضلة", "column.follow_requests": "طلبات المتابعة", "column.home": "الرئيسية", - "column.lists": "Lists", + "column.lists": "القوائم", "column.mutes": "الحسابات المكتومة", - "column.notifications": "الإشعارات", + "column.notifications": "الإخطارات", "column.pins": "التبويقات المثبتة", "column.public": "الخيط العام الموحد", "column_back_button.label": "العودة", @@ -64,7 +64,7 @@ "confirmations.delete.confirm": "حذف", "confirmations.delete.message": "هل أنت متأكد أنك تريد حذف هذا المنشور ؟", "confirmations.delete_list.confirm": "Delete", - "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", + "confirmations.delete_list.message": "هل تود حقا حذف هذه القائمة ؟", "confirmations.domain_block.confirm": "إخفاء إسم النطاق كاملا", "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": "أكتم", @@ -109,32 +109,32 @@ "home.settings": "إعدادات العمود", "keyboard_shortcuts.back": "للعودة", "keyboard_shortcuts.boost": "للترقية", - "keyboard_shortcuts.column": "to focus a status in one of the columns", - "keyboard_shortcuts.compose": "to focus the compose textarea", + "keyboard_shortcuts.column": "للتركيز على منشور على أحد الأعمدة", + "keyboard_shortcuts.compose": "للتركيز على نافذة تحرير النصوص", "keyboard_shortcuts.description": "Description", "keyboard_shortcuts.down": "للإنتقال إلى أسفل القائمة", "keyboard_shortcuts.enter": "to open status", - "keyboard_shortcuts.favourite": "to favourite", + "keyboard_shortcuts.favourite": "للإضافة إلى المفضلة", "keyboard_shortcuts.heading": "Keyboard Shortcuts", - "keyboard_shortcuts.hotkey": "Hotkey", - "keyboard_shortcuts.legend": "to display this legend", + "keyboard_shortcuts.hotkey": "مفتاح الإختصار", + "keyboard_shortcuts.legend": "لعرض هذا المفتاح", "keyboard_shortcuts.mention": "لذِكر الناشر", "keyboard_shortcuts.reply": "للردّ", - "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.search": "للتركيز على البحث", "keyboard_shortcuts.toot": "لتحرير تبويق جديد", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "للإنتقال إلى أعلى القائمة", "lightbox.close": "إغلاق", "lightbox.next": "التالي", "lightbox.previous": "العودة", - "lists.account.add": "Add to list", - "lists.account.remove": "Remove from list", + "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 follows", - "lists.subheading": "Your lists", + "lists.edit": "تعديل القائمة", + "lists.new.create": "إنشاء قائمة", + "lists.new.title_placeholder": "عنوان القائمة الجديدة", + "lists.search": "إبحث في قائمة الحسابات التي تُتابِعها", + "lists.subheading": "قوائمك", "loading_indicator.label": "تحميل ...", "media_gallery.toggle_visible": "عرض / إخفاء", "missing_indicator.label": "تعذر العثور عليه", @@ -146,7 +146,7 @@ "navigation_bar.follow_requests": "طلبات المتابعة", "navigation_bar.info": "معلومات إضافية", "navigation_bar.keyboard_shortcuts": "إختصارات لوحة المفاتيح", - "navigation_bar.lists": "Lists", + "navigation_bar.lists": "القوائم", "navigation_bar.logout": "خروج", "navigation_bar.mutes": "الحسابات المكتومة", "navigation_bar.pins": "التبويقات المثبتة", @@ -209,7 +209,7 @@ "search_popout.search_format": "نمط البحث المتقدم", "search_popout.tips.hashtag": "وسم", "search_popout.tips.status": "حالة", - "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.text": "جملة قصيرة تُمكّنُك من عرض أسماء و حسابات و كلمات رمزية", "search_popout.tips.user": "مستخدِم", "search_results.total": "{count, number} {count, plural, one {result} و {results}}", "standalone.public_title": "نظرة على ...", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index d20120b11..1c04b3bfa 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Зареждане...", "media_gallery.toggle_visible": "Toggle visibility", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index bfa931fc0..62d85a5e1 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -36,7 +36,7 @@ "column.favourites": "Favorits", "column.follow_requests": "Peticions per seguir-te", "column.home": "Inici", - "column.lists": "Lists", + "column.lists": "Llistes", "column.mutes": "Usuaris silenciats", "column.notifications": "Notificacions", "column.pins": "Toot fixat", @@ -64,7 +64,7 @@ "confirmations.delete.confirm": "Esborrar", "confirmations.delete.message": "Estàs segur que vols esborrar aquest estat?", "confirmations.delete_list.confirm": "Delete", - "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", + "confirmations.delete_list.message": "Estàs segur que vols esborrar permanenment aquesta llista?", "confirmations.domain_block.confirm": "Amagar tot el domini", "confirmations.domain_block.message": "Estàs realment, realment segur que vols bloquejar totalment {domain}? En la majoria dels casos bloquejar o silenciar és suficient i preferible.", "confirmations.mute.confirm": "Silenciar", @@ -127,14 +127,14 @@ "lightbox.close": "Tancar", "lightbox.next": "Següent", "lightbox.previous": "Anterior", - "lists.account.add": "Add to list", - "lists.account.remove": "Remove from list", + "lists.account.add": "Afegir a la llista", + "lists.account.remove": "Treure de la llista", "lists.delete": "Delete list", - "lists.edit": "Edit list", - "lists.new.create": "Add list", - "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", - "lists.subheading": "Your lists", + "lists.edit": "Editar llista", + "lists.new.create": "Afegir llista", + "lists.new.title_placeholder": "Nou títol de llista", + "lists.search": "Cercar entre les persones que segueixes", + "lists.subheading": "Les teves llistes", "loading_indicator.label": "Carregant...", "media_gallery.toggle_visible": "Alternar visibilitat", "missing_indicator.label": "No trobat", @@ -146,7 +146,7 @@ "navigation_bar.follow_requests": "Sol·licituds de seguiment", "navigation_bar.info": "Informació addicional", "navigation_bar.keyboard_shortcuts": "Dreceres de teclat", - "navigation_bar.lists": "Lists", + "navigation_bar.lists": "Llistes", "navigation_bar.logout": "Tancar sessió", "navigation_bar.mutes": "Usuaris silenciats", "navigation_bar.pins": "Toots fixats", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index b6d9e27a7..6354f18b6 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Wird geladen …", "media_gallery.toggle_visible": "Sichtbarkeit umschalten", diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index f5154634c..3fc4a8c96 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -93,7 +93,7 @@ "empty_column.hashtag": "There is nothing in this hashtag yet.", "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.list": "There is nothing in this list yet.", + "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", @@ -135,7 +135,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Loading...", "media_gallery.toggle_visible": "Toggle visibility", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 619c7320a..9e66c379f 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Ŝarganta…", "media_gallery.toggle_visible": "Baskuli videblecon", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 411615744..6122a79ab 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Cargando…", "media_gallery.toggle_visible": "Cambiar visibilidad", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index aa5c21feb..75057a7dd 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "بارگیری...", "media_gallery.toggle_visible": "تغییر پیدایی", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index db9319e2e..4ddc1cca7 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Ladataan...", "media_gallery.toggle_visible": "Toggle visibility", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 9b9469bc2..ecfff87c8 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -63,8 +63,8 @@ "confirmations.block.message": "Confirmez-vous le blocage de {name} ?", "confirmations.delete.confirm": "Supprimer", "confirmations.delete.message": "Confirmez-vous la suppression de ce pouet ?", - "confirmations.delete_list.confirm": "Delete", - "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", + "confirmations.delete_list.confirm": "Supprimer", + "confirmations.delete_list.message": "Êtes-vous sûr de vouloir supprimer définitivement cette liste ?", "confirmations.domain_block.confirm": "Masquer le domaine entier", "confirmations.domain_block.message": "Êtes-vous vraiment, vraiment sûr⋅e de vouloir bloquer {domain} en entier ? Dans la plupart des cas, quelques blocages ou masquages ciblés sont suffisants et préférables.", "confirmations.mute.confirm": "Masquer", @@ -91,7 +91,7 @@ "empty_column.hashtag": "Il n’y a encore aucun contenu associé à ce hashtag", "empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à d’autres utilisateur⋅ice⋅s.", "empty_column.home.public_timeline": "le fil public", - "empty_column.list": "Il n'y a rien dans cette liste pour l'instant.", + "empty_column.list": "Il n'y a rien dans cette liste pour l'instant. Dès que des personnes de cette liste publierons de nouveaux statuts, ils apparaîtront ici.", "empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres utilisateur⋅ice⋅s pour débuter la conversation.", "empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des utilisateur⋅ice⋅s d’autres instances pour remplir le fil public.", "follow_request.authorize": "Accepter", @@ -113,28 +113,28 @@ "keyboard_shortcuts.compose": "pour centrer la zone de redaction", "keyboard_shortcuts.description": "Description", "keyboard_shortcuts.down": "descendre dans la liste", - "keyboard_shortcuts.enter": "to open status", - "keyboard_shortcuts.favourite": "to favourite", - "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.enter": "pour ouvrir le statut", + "keyboard_shortcuts.favourite": "vers les favoris", + "keyboard_shortcuts.heading": "Raccourcis clavier", + "keyboard_shortcuts.hotkey": "Raccourci", + "keyboard_shortcuts.legend": "pour afficher cette légende", + "keyboard_shortcuts.mention": "pour mentionner l'auteur", + "keyboard_shortcuts.reply": "pour répondre", "keyboard_shortcuts.search": "to focus search", - "keyboard_shortcuts.toot": "to start a brand new toot", + "keyboard_shortcuts.toot": "pour démarrer un tout nouveau pouet", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "to move up in the list", "lightbox.close": "Fermer", "lightbox.next": "Suivant", "lightbox.previous": "Précédent", - "lists.account.add": "Add to list", - "lists.account.remove": "Remove from list", - "lists.delete": "Delete list", - "lists.edit": "Edit list", - "lists.new.create": "Add list", - "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", - "lists.subheading": "Your lists", + "lists.account.add": "Ajouter à la liste", + "lists.account.remove": "Supprimer de la liste", + "lists.delete": "Effacer la liste", + "lists.edit": "Éditer la liste", + "lists.new.create": "Ajouter une liste", + "lists.new.title_placeholder": "Titre de la nouvelle liste", + "lists.search": "Rechercher parmi les gens que vous suivez", + "lists.subheading": "Vos listes", "loading_indicator.label": "Chargement…", "media_gallery.toggle_visible": "Modifier la visibilité", "missing_indicator.label": "Non trouvé", @@ -145,8 +145,8 @@ "navigation_bar.favourites": "Favoris", "navigation_bar.follow_requests": "Demandes de suivi", "navigation_bar.info": "Plus d’informations", - "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts", - "navigation_bar.lists": "Lists", + "navigation_bar.keyboard_shortcuts": "Raccourcis clavier", + "navigation_bar.lists": "Listes", "navigation_bar.logout": "Déconnexion", "navigation_bar.mutes": "Comptes masqués", "navigation_bar.pins": "Pouets épinglés", @@ -241,7 +241,7 @@ "tabs_bar.home": "Accueil", "tabs_bar.local_timeline": "Fil public local", "tabs_bar.notifications": "Notifications", - "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", + "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", "upload_form.description": "Décrire pour les malvoyants", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json new file mode 100644 index 000000000..6398daa11 --- /dev/null +++ b/app/javascript/mastodon/locales/gl.json @@ -0,0 +1,259 @@ +{ + "account.block": "Bloquear @{name}", + "account.block_domain": "Ocultar calquer contido de {domain}", + "account.disclaimer_full": "A información inferior podería mostrar un perfil incompleto da usuaria.", + "account.edit_profile": "Editar perfil", + "account.follow": "Seguir", + "account.followers": "Seguidoras", + "account.follows": "Seguindo", + "account.follows_you": "Séguena", + "account.hide_reblogs": "Ocultar repeticións de @{name}", + "account.media": "Medios", + "account.mention": "Mencionar @{name}", + "account.moved_to": "{name} marchou a:", + "account.mute": "Acalar @{name}", + "account.mute_notifications": "Acalar as notificacións de @{name}", + "account.posts": "Publicacións", + "account.report": "Informar sobre @{name}", + "account.requested": "Agardando aceptación. Pulse para cancelar a solicitude de seguimento", + "account.share": "Compartir o perfil de @{name}", + "account.show_reblogs": "Mostrar repeticións de @{name}", + "account.unblock": "Desbloquear @{name}", + "account.unblock_domain": "Non ocultar {domain}", + "account.unfollow": "Non seguir", + "account.unmute": "Non acalar @{name}", + "account.unmute_notifications": "Desbloquear as notificacións de @{name}", + "account.view_full_profile": "Ver o perfil completo", + "boost_modal.combo": "Pulse {combo} para saltar esto a próxima vez", + "bundle_column_error.body": "Houbo un fallo mentras se cargaba este compoñente.", + "bundle_column_error.retry": "Inténteo de novo", + "bundle_column_error.title": "Fallo na rede", + "bundle_modal_error.close": "Pechar", + "bundle_modal_error.message": "Algo fallou mentras se cargaba este compoñente.", + "bundle_modal_error.retry": "Inténteo de novo", + "column.blocks": "Usuarias bloqueadas", + "column.community": "Liña temporal local", + "column.favourites": "Favoritas", + "column.follow_requests": "Peticións de seguimento", + "column.home": "Inicio", + "column.lists": "Listas", + "column.mutes": "Usuarias acaladas", + "column.notifications": "Notificacións", + "column.pins": "Mensaxes fixadas", + "column.public": "Liña temporal federada", + "column_back_button.label": "Atrás", + "column_header.hide_settings": "Agochar axustes", + "column_header.moveLeft_settings": "Mover a columna hacia a esquerda", + "column_header.moveRight_settings": "Mover a columna hacia a dereita", + "column_header.pin": "Fixar", + "column_header.show_settings": "Mostras axustes", + "column_header.unpin": "Soltar", + "column_subheading.navigation": "Navegación", + "column_subheading.settings": "Axustes", + "compose_form.lock_disclaimer": "A súa conta non está {locked}. Calquera pode seguila para ver as súas mensaxes só-para-seguidoras.", + "compose_form.lock_disclaimer.lock": "bloqueado", + "compose_form.placeholder": "A qué andas?", + "compose_form.publish": "Toot", + "compose_form.publish_loud": "{publish}!", + "compose_form.sensitive": "Marcar medios como sensibles", + "compose_form.spoiler": "Agochar texto detrás de un aviso", + "compose_form.spoiler_placeholder": "Escriba o aviso aquí", + "confirmation_modal.cancel": "Cancelar", + "confirmations.block.confirm": "Bloquear", + "confirmations.block.message": "Está segura de querer bloquear a {name}?", + "confirmations.delete.confirm": "Borrar", + "confirmations.delete.message": "Está segura de que quere eliminar este estado?", + "confirmations.delete_list.confirm": "Delete", + "confirmations.delete_list.message": "Estás seguro de que queres eliminar permanentemente esta lista?", + "confirmations.domain_block.confirm": "Agochar un dominio completo", + "confirmations.domain_block.message": "Realmente está segura de que quere bloquear por completo o dominio {domain}? Normalmente é suficiente, e preferible, bloquear de xeito selectivo varios elementos.", + "confirmations.mute.confirm": "Acalar", + "confirmations.mute.message": "Está segura de que quere acalar a {name}?", + "confirmations.unfollow.confirm": "Deixar de seguir", + "confirmations.unfollow.message": "Quere deixar de seguir a {name}?", + "embed.instructions": "Copie o código inferior para incrustar no seu sitio web este estado.", + "embed.preview": "Así será mostrado:", + "emoji_button.activity": "Actividade", + "emoji_button.custom": "Personalizado", + "emoji_button.flags": "Marcas", + "emoji_button.food": "Comida e Bebida", + "emoji_button.label": "Insertar emoji", + "emoji_button.nature": "Natureza", + "emoji_button.not_found": "Sen emojos!! (╯°□°)╯︵ ┻━┻", + "emoji_button.objects": "Obxetos", + "emoji_button.people": "Xente", + "emoji_button.recent": "Utilizadas con frecuencia", + "emoji_button.search": "Buscar...", + "emoji_button.search_results": "Resultados da busca", + "emoji_button.symbols": "Símbolos", + "emoji_button.travel": "Viaxes e Lugares", + "empty_column.community": "A liña temporal local está baldeira. Escriba algo de xeito público para que rule!", + "empty_column.hashtag": "Aínda non hai nada con esta etiqueta.", + "empty_column.home": "A súa liña temporal de inicio está baldeira! Visite {public} ou utilice a busca para atopar outras usuarias.", + "empty_column.home.public_timeline": "a liña temporal pública", + "empty_column.list": "Aínda non hai nada en esta lista.", + "empty_column.notifications": "Aínda non ten notificacións. Interactúe con outras para iniciar unha conversa.", + "empty_column.public": "Nada por aquí! Escriba algo de xeito público, ou siga manualmente usuarias de outras instancias para ir enchéndoa", + "follow_request.authorize": "Autorizar", + "follow_request.reject": "Rexeitar", + "getting_started.appsshort": "Aplicacións", + "getting_started.faq": "PMF", + "getting_started.heading": "Comezando", + "getting_started.open_source_notice": "Mastodon é software de código aberto. Pode contribuír ou informar de fallos en GitHub en {github}.", + "getting_started.userguide": "Guía de usuaria", + "home.column_settings.advanced": "Avanzado", + "home.column_settings.basic": "Básico", + "home.column_settings.filter_regex": "Filtrar expresións regulares", + "home.column_settings.show_reblogs": "Mostrar repeticións", + "home.column_settings.show_replies": "Mostrar respostas", + "home.settings": "Axustes da columna", + "keyboard_shortcuts.back": "voltar atrás", + "keyboard_shortcuts.boost": "repetir", + "keyboard_shortcuts.column": "destacar un estado en unha das columnas", + "keyboard_shortcuts.compose": "Foco no área de escritura", + "keyboard_shortcuts.description": "Descrición", + "keyboard_shortcuts.down": "ir hacia abaixo na lista", + "keyboard_shortcuts.enter": "abrir estado", + "keyboard_shortcuts.favourite": "marcar como favorito", + "keyboard_shortcuts.heading": "Atallos do teclado", + "keyboard_shortcuts.hotkey": "Tecla de acceso directo", + "keyboard_shortcuts.legend": "para mostrar esta lenda", + "keyboard_shortcuts.mention": "para mencionar o autor", + "keyboard_shortcuts.reply": "para responder", + "keyboard_shortcuts.search": "para centrar a busca", + "keyboard_shortcuts.toot": "escribir un toot novo", + "keyboard_shortcuts.unfocus": "quitar o foco do área de escritura/busca", + "keyboard_shortcuts.up": "ir hacia arriba na lista", + "lightbox.close": "Fechar", + "lightbox.next": "Seguinte", + "lightbox.previous": "Anterior", + "lists.account.add": "Engadir á lista", + "lists.account.remove": "Eliminar da lista", + "lists.delete": "Delete list", + "lists.edit": "Editar lista", + "lists.new.create": "Engadir lista", + "lists.new.title_placeholder": "Novo título da lista", + "lists.search": "Procurar entre a xente que segues", + "lists.subheading": "As túas listas", + "loading_indicator.label": "Cargando...", + "media_gallery.toggle_visible": "Dar visibilidade", + "missing_indicator.label": "Non atopado", + "mute_modal.hide_notifications": "Esconder notificacións deste usuario?", + "navigation_bar.blocks": "Usuarios bloqueados", + "navigation_bar.community_timeline": "Liña temporal local", + "navigation_bar.edit_profile": "Editar perfil", + "navigation_bar.favourites": "Favoritas", + "navigation_bar.follow_requests": "Peticións de seguimento", + "navigation_bar.info": "Sobre esta instancia", + "navigation_bar.keyboard_shortcuts": "Atallos do teclado", + "navigation_bar.lists": "Listas", + "navigation_bar.logout": "Sair", + "navigation_bar.mutes": "Usuarias acaladas", + "navigation_bar.pins": "Mensaxes fixadas", + "navigation_bar.preferences": "Preferencias", + "navigation_bar.public_timeline": "Liña temporal federada", + "notification.favourite": "{name} marcou como favorito o seu estado", + "notification.follow": "{name} está a seguila", + "notification.mention": "{name} mencionoute", + "notification.reblog": "{name} promocionou o seu estado", + "notifications.clear": "Limpar notificacións", + "notifications.clear_confirmation": "Estás seguro de que queres limpar permanentemente todas as túas notificacións?", + "notifications.column_settings.alert": "Notificacións de escritorio", + "notifications.column_settings.favourite": "Favoritas:", + "notifications.column_settings.follow": "Novos seguidores:", + "notifications.column_settings.mention": "Mencións:", + "notifications.column_settings.push": "Enviar notificacións", + "notifications.column_settings.push_meta": "Este aparello", + "notifications.column_settings.reblog": "Promocións:", + "notifications.column_settings.show": "Mostrar en columna", + "notifications.column_settings.sound": "Reproducir son", + "onboarding.done": "Feito", + "onboarding.next": "Seguinte", + "onboarding.page_five.public_timelines": "A liña de tempo local mostra as publicacións públicas de todos en {domain}. A liña de tempo federada mostra as publicacións públicas de todos os que as persoas en {domain} seguen. Estas son as Liñas de tempo públicas, unha boa forma de descubrir novas persoas.", + "onboarding.page_four.home": "A liña de tempo local mostra as publicacións das persoas que segues.", + "onboarding.page_four.notifications": "A columna de notificacións mostra cando alguén interactúa contigo.", + "onboarding.page_one.federation": "Mastodon é unha rede de servidores independentes que se unen para facer unha rede social máis grande. Chamamos instancias a estes servidores.", + "onboarding.page_one.handle": "Estás en {domain}, polo que o teu nome de usuario completo é {handle}", + "onboarding.page_one.welcome": "Benvido a Mastodon!", + "onboarding.page_six.admin": "O administrador da túa instancia é {admin}.", + "onboarding.page_six.almost_done": "Case feito...", + "onboarding.page_six.appetoot": "Que tootes ben!", + "onboarding.page_six.apps_available": "Hai {apps} dispoñíbeis para iOS, Android e outras plataformas.", + "onboarding.page_six.github": "Mastodon é un software gratuito e de código aberto. Pode informar de erros, solicitar novas funcionalidades ou contribuír ao código en {github}.", + "onboarding.page_six.guidelines": "directrices da comunidade", + "onboarding.page_six.read_guidelines": "Por favor, le as {guidelines} do {domain}!", + "onboarding.page_six.various_app": "aplicacións móbiles", + "onboarding.page_three.profile": "Edita o teu perfil para cambiar o teu avatar, bio e nome. Alí, tamén atoparás outras preferencias.", + "onboarding.page_three.search": "Utilice a barra de busca para atopar xente e descubrir etiquetas, como {illustration} e {introductions}. Para atopar unha usuaria que non está en esta instancia utilice o seu enderezo completo.", + "onboarding.page_two.compose": "Escriba mensaxes desde a columna de composición. Pode subir imaxes, mudar as opcións de intimidade e engadir avisos sobre o contido coas iconas inferiores.", + "onboarding.skip": "Saltar", + "privacy.change": "Axustar a intimidade do estado", + "privacy.direct.long": "Enviar exclusivamente as usuarias mencionadas", + "privacy.direct.short": "Directa", + "privacy.private.long": "Enviar só as seguidoras", + "privacy.private.short": "Só-seguidoras", + "privacy.public.long": "Publicar na liña temporal pública", + "privacy.public.short": "Pública", + "privacy.unlisted.long": "Non publicar en liñas temporais públicas", + "privacy.unlisted.short": "Non listada", + "relative_time.days": "{number}d", + "relative_time.hours": "{number}h", + "relative_time.just_now": "agora", + "relative_time.minutes": "{number}m", + "relative_time.seconds": "{number}s", + "reply_indicator.cancel": "Cancelar", + "report.placeholder": "Comentarios adicionais", + "report.submit": "Enviar", + "report.target": "Informar {target}", + "search.placeholder": "Buscar", + "search_popout.search_format": "Formato de busca avanzada", + "search_popout.tips.hashtag": "etiqueta", + "search_popout.tips.status": "estado", + "search_popout.tips.text": "Texto simple devolve coincidencias con nomes públicos, nomes de usuaria e etiquetas", + "search_popout.tips.user": "usuaria", + "search_results.total": "{count, number} {count,plural,one {result} outros {results}}", + "standalone.public_title": "Ollada dentro...", + "status.cannot_reblog": "Esta mensaxe non pode ser promocionada", + "status.delete": "Eliminar", + "status.embed": "Incrustar", + "status.favourite": "Favorita", + "status.load_more": "Cargar máis", + "status.media_hidden": "Medios ocultos", + "status.mention": "Mencionar @{name}", + "status.more": "Máis", + "status.mute_conversation": "Acalar conversa", + "status.open": "Expandir este estado", + "status.pin": "Fixar no perfil", + "status.reblog": "Promocionar", + "status.reblogged_by": "{name} promocionado", + "status.reply": "Resposta", + "status.replyAll": "Resposta a conversa", + "status.report": "Informar @{name}", + "status.sensitive_toggle": "Pulse para ver", + "status.sensitive_warning": "Contido sensible", + "status.share": "Compartir", + "status.show_less": "Mostrar menos", + "status.show_more": "Mostrar máis", + "status.unmute_conversation": "Non acalar a conversa", + "status.unpin": "Despegar do perfil", + "tabs_bar.compose": "Compoñer", + "tabs_bar.federated_timeline": "Federado", + "tabs_bar.home": "Inicio", + "tabs_bar.local_timeline": "Local", + "tabs_bar.notifications": "Notificacións", + "ui.beforeunload": "O borrador perderase se sae de Mastodon.", + "upload_area.title": "Arrastre e solte para subir", + "upload_button.label": "Engadir medios", + "upload_form.description": "Describa para deficientes visuais", + "upload_form.undo": "Desfacer", + "upload_progress.label": "Subindo...", + "video.close": "Pechar video", + "video.exit_fullscreen": "Saír da pantalla completa", + "video.expand": "Expandir vídeo", + "video.fullscreen": "Pantalla completa", + "video.hide": "Agochar vídeo", + "video.mute": "Acalar son", + "video.pause": "Pausar", + "video.play": "Reproducir", + "video.unmute": "Permitir son" +} diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index ec1e30dd5..5444c8e34 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "טוען...", "media_gallery.toggle_visible": "נראה\\בלתי נראה", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index c21482670..f70c66223 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Učitavam...", "media_gallery.toggle_visible": "Preklopi vidljivost", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 71dd810b6..7cb816fe9 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Betöltés...", "media_gallery.toggle_visible": "Toggle visibility", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index 744423e78..429b77182 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Tunggu sebentar...", "media_gallery.toggle_visible": "Tampil/Sembunyikan", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index b1523e626..3e5c8edb9 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Kargante...", "media_gallery.toggle_visible": "Chanjar videbleso", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 9a2d320fd..e2ad1632a 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Carico...", "media_gallery.toggle_visible": "Imposta visibilità", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 125618bf2..68abd906f 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -24,7 +24,7 @@ "account.unmute": "ミュート解除", "account.unmute_notifications": "@{name}さんからの通知を受け取らない", "account.view_full_profile": "全ての情報を見る", - "boost_modal.combo": "次からは{combo}を押せば、これをスキップできます。", + "boost_modal.combo": "次からは{combo}を押せば、これをスキップできます", "bundle_column_error.body": "コンポーネントの読み込み中に問題が発生しました。", "bundle_column_error.retry": "再試行", "bundle_column_error.title": "ネットワークエラー", @@ -95,7 +95,7 @@ "empty_column.home.public_timeline": "連合タイムライン", "empty_column.list": "このリストにはまだなにもありません。", "empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。", - "empty_column.public": "ここにはまだ何もありません!公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう!", + "empty_column.public": "ここにはまだ何もありません! 公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう", "follow_request.authorize": "許可", "follow_request.reject": "拒否", "getting_started.appsshort": "アプリ", @@ -162,12 +162,12 @@ "notifications.clear": "通知を消去", "notifications.clear_confirmation": "本当に通知を消去しますか?", "notifications.column_settings.alert": "デスクトップ通知", - "notifications.column_settings.favourite": "お気に入り", - "notifications.column_settings.follow": "新しいフォロワー", - "notifications.column_settings.mention": "返信", + "notifications.column_settings.favourite": "お気に入り:", + "notifications.column_settings.follow": "新しいフォロワー:", + "notifications.column_settings.mention": "返信:", "notifications.column_settings.push": "プッシュ通知", "notifications.column_settings.push_meta": "このデバイス", - "notifications.column_settings.reblog": "ブースト", + "notifications.column_settings.reblog": "ブースト:", "notifications.column_settings.show": "カラムに表示", "notifications.column_settings.sound": "通知音を再生", "onboarding.done": "完了", @@ -176,7 +176,7 @@ "onboarding.page_four.home": "「ホーム」タイムラインではあなたがフォローしている人の投稿を表示します。", "onboarding.page_four.notifications": "「通知」ではあなたへの他の人からの関わりを表示します。", "onboarding.page_one.federation": "Mastodonは誰でも参加できるSNSです。", - "onboarding.page_one.handle": "あなたは今数あるMastodonインスタンスの1つである{domain}にいます。あなたのフルハンドルは{handle}です。", + "onboarding.page_one.handle": "今あなたは数あるMastodonインスタンスの1つである{domain}にいます。あなたのフルハンドルは{handle}です", "onboarding.page_one.welcome": "Mastodonへようこそ!", "onboarding.page_six.admin": "あなたのインスタンスの管理者は{admin}です。", "onboarding.page_six.almost_done": "以上です。", @@ -184,7 +184,7 @@ "onboarding.page_six.apps_available": "iOS、Androidあるいは他のプラットフォームで使える{apps}があります。", "onboarding.page_six.github": "MastodonはOSSです。バグ報告や機能要望あるいは貢献を{github}から行なえます。", "onboarding.page_six.guidelines": "コミュニティガイドライン", - "onboarding.page_six.read_guidelines": "{guidelines}を読むことを忘れないようにしてください。", + "onboarding.page_six.read_guidelines": "{guidelines}を読むことを忘れないようにしてください!", "onboarding.page_six.various_app": "様々なモバイルアプリ", "onboarding.page_three.profile": "「プロフィールを編集」から、あなたの自己紹介や表示名を変更できます。またそこでは他の設定ができます。", "onboarding.page_three.search": "検索バーで、{illustration}や{introductions}のように特定のハッシュタグの投稿を見たり、ユーザーを探したりできます。", @@ -215,7 +215,7 @@ "search_popout.tips.text": "表示名やユーザー名、ハッシュタグに一致する単純なテキスト", "search_popout.tips.user": "ユーザー", "search_results.total": "{count, number}件の結果", - "standalone.public_title": "今こんな話をしています", + "standalone.public_title": "今こんな話をしています...", "status.cannot_reblog": "この投稿はブーストできません", "status.delete": "削除", "status.embed": "埋め込み", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 3f47baa76..0798fa7cf 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -1,52 +1,52 @@ { "account.block": "차단", "account.block_domain": "{domain} 전체를 숨김", - "account.disclaimer_full": "Information below may reflect the user's profile incompletely.", + "account.disclaimer_full": "여기 있는 정보는 유저의 프로파일을 정확히 반영하지 못 할 수도 있습니다.", "account.edit_profile": "프로필 편집", "account.follow": "팔로우", "account.followers": "팔로워", "account.follows": "팔로우", "account.follows_you": "날 팔로우합니다", - "account.hide_reblogs": "Hide boosts from @{name}", + "account.hide_reblogs": "@{name}의 부스트를 숨기기", "account.media": "미디어", "account.mention": "답장", - "account.moved_to": "{name} has moved to:", + "account.moved_to": "{name}는 계정을 이동했습니다:", "account.mute": "뮤트", - "account.mute_notifications": "Mute notifications from @{name}", + "account.mute_notifications": "@{name}의 알림을 뮤트", "account.posts": "포스트", "account.report": "신고", "account.requested": "승인 대기 중", - "account.share": "Share @{name}'s profile", - "account.show_reblogs": "Show boosts from @{name}", + "account.share": "@{name}의 프로파일 공유", + "account.show_reblogs": "@{name}의 부스트 보기", "account.unblock": "차단 해제", "account.unblock_domain": "{domain} 숨김 해제", "account.unfollow": "팔로우 해제", "account.unmute": "뮤트 해제", - "account.unmute_notifications": "Unmute notifications from @{name}", + "account.unmute_notifications": "@{name}의 알림 뮤트 해제", "account.view_full_profile": "전체 프로필 보기", "boost_modal.combo": "다음부터 {combo}를 누르면 이 과정을 건너뛸 수 있습니다.", "bundle_column_error.body": "Something went wrong while loading this component.", - "bundle_column_error.retry": "Try again", - "bundle_column_error.title": "Network error", - "bundle_modal_error.close": "Close", + "bundle_column_error.retry": "다시 시도", + "bundle_column_error.title": "네트워크 에러", + "bundle_modal_error.close": "닫기", "bundle_modal_error.message": "Something went wrong while loading this component.", - "bundle_modal_error.retry": "Try again", + "bundle_modal_error.retry": "다시 시도", "column.blocks": "차단 중인 사용자", "column.community": "로컬 타임라인", "column.favourites": "즐겨찾기", "column.follow_requests": "팔로우 요청", "column.home": "홈", - "column.lists": "Lists", + "column.lists": "리스트", "column.mutes": "뮤트 중인 사용자", "column.notifications": "알림", "column.pins": "고정된 툿", "column.public": "연합 타임라인", "column_back_button.label": "돌아가기", - "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.hide_settings": "설정 숨기기", + "column_header.moveLeft_settings": "왼쪽으로 이동", + "column_header.moveRight_settings": "오른쪽으로 이동", "column_header.pin": "고정하기", - "column_header.show_settings": "Show settings", + "column_header.show_settings": "설정 보이기", "column_header.unpin": "고정 해제", "column_subheading.navigation": "내비게이션", "column_subheading.settings": "설정", @@ -63,35 +63,35 @@ "confirmations.block.message": "정말로 {name}를 차단하시겠습니까?", "confirmations.delete.confirm": "삭제", "confirmations.delete.message": "정말로 삭제하시겠습니까?", - "confirmations.delete_list.confirm": "Delete", - "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", + "confirmations.delete_list.confirm": "삭제", + "confirmations.delete_list.message": "정말로 이 리스트를 삭제하시겠습니까?", "confirmations.domain_block.confirm": "도메인 전체를 숨김", "confirmations.domain_block.message": "정말로 {domain} 전체를 숨기시겠습니까? 대부분의 경우 개별 차단이나 뮤트로 충분합니다.", "confirmations.mute.confirm": "뮤트", "confirmations.mute.message": "정말로 {name}를 뮤트하시겠습니까?", - "confirmations.unfollow.confirm": "Unfollow", - "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?", + "confirmations.unfollow.confirm": "언팔로우", + "confirmations.unfollow.message": "정말로 {name}를 언팔로우하시겠습니까?", "embed.instructions": "아래의 코드를 복사하여 대화를 원하는 곳으로 공유하세요.", "embed.preview": "다음과 같이 표시됩니다:", "emoji_button.activity": "활동", - "emoji_button.custom": "Custom", + "emoji_button.custom": "커스텀", "emoji_button.flags": "국기", "emoji_button.food": "음식", "emoji_button.label": "emoji를 추가", "emoji_button.nature": "자연", - "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻", + "emoji_button.not_found": "없어!! (╯°□°)╯︵ ┻━┻", "emoji_button.objects": "물건", "emoji_button.people": "사람들", - "emoji_button.recent": "Frequently used", + "emoji_button.recent": "자주 사용 됨", "emoji_button.search": "검색...", - "emoji_button.search_results": "Search results", + "emoji_button.search_results": "검색 결과", "emoji_button.symbols": "기호", "emoji_button.travel": "여행과 장소", "empty_column.community": "로컬 타임라인에 아무 것도 없습니다. 아무거나 적어 보세요!", "empty_column.hashtag": "이 해시태그는 아직 사용되지 않았습니다.", "empty_column.home": "아직 아무도 팔로우 하고 있지 않습니다. {public}를 보러 가거나, 검색하여 다른 사용자를 찾아 보세요.", "empty_column.home.public_timeline": "연합 타임라인", - "empty_column.list": "There is nothing in this list yet.", + "empty_column.list": "리스트에 아직 아무 것도 없습니다.", "empty_column.notifications": "아직 알림이 없습니다. 다른 사람과 대화를 시작해 보세요!", "empty_column.public": "여기엔 아직 아무 것도 없습니다! 공개적으로 무언가 포스팅하거나, 다른 인스턴스 유저를 팔로우 해서 가득 채워보세요!", "follow_request.authorize": "허가", @@ -107,46 +107,46 @@ "home.column_settings.show_reblogs": "부스트 표시", "home.column_settings.show_replies": "답글 표시", "home.settings": "컬럼 설정", - "keyboard_shortcuts.back": "to navigate back", - "keyboard_shortcuts.boost": "to boost", - "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.enter": "to open status", - "keyboard_shortcuts.favourite": "to favourite", - "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.toot": "to start a brand new toot", - "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", - "keyboard_shortcuts.up": "to move up in the list", + "keyboard_shortcuts.back": "뒤로가기", + "keyboard_shortcuts.boost": "부스트", + "keyboard_shortcuts.column": "해당 열에 포커스", + "keyboard_shortcuts.compose": "작성창으로 포커스", + "keyboard_shortcuts.description": "설명", + "keyboard_shortcuts.down": "리스트에서 아래로 이동", + "keyboard_shortcuts.enter": "열기", + "keyboard_shortcuts.favourite": "관심글 지정", + "keyboard_shortcuts.heading": "키보드 단축키", + "keyboard_shortcuts.hotkey": "핫키", + "keyboard_shortcuts.legend": "이 도움말 표시", + "keyboard_shortcuts.mention": "멘션", + "keyboard_shortcuts.reply": "답장", + "keyboard_shortcuts.search": "검색창에 포커스", + "keyboard_shortcuts.toot": "새 툿 작성", + "keyboard_shortcuts.unfocus": "작성창에서 포커스 해제", + "keyboard_shortcuts.up": "리스트에서 위로 이동", "lightbox.close": "닫기", - "lightbox.next": "Next", - "lightbox.previous": "Previous", - "lists.account.add": "Add to list", - "lists.account.remove": "Remove from list", - "lists.delete": "Delete list", - "lists.edit": "Edit list", - "lists.new.create": "Add list", - "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lightbox.next": "다음", + "lightbox.previous": "이전", + "lists.account.add": "리스트에 추가", + "lists.account.remove": "리스트에서 제거", + "lists.delete": "리스트 삭제", + "lists.edit": "리스트 편집", + "lists.new.create": "리스트 추가", + "lists.new.title_placeholder": "새 리스트의 이름", + "lists.search": "팔로우 중인 사람들 중에서 찾기", "lists.subheading": "Your lists", "loading_indicator.label": "불러오는 중...", "media_gallery.toggle_visible": "표시 전환", "missing_indicator.label": "찾을 수 없습니다", - "mute_modal.hide_notifications": "Hide notifications from this user?", + "mute_modal.hide_notifications": "이 사용자로부터의 알림을 뮤트하시겠습니까?", "navigation_bar.blocks": "차단한 사용자", "navigation_bar.community_timeline": "로컬 타임라인", "navigation_bar.edit_profile": "프로필 편집", "navigation_bar.favourites": "즐겨찾기", "navigation_bar.follow_requests": "팔로우 요청", "navigation_bar.info": "이 인스턴스에 대해서", - "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts", - "navigation_bar.lists": "Lists", + "navigation_bar.keyboard_shortcuts": "키보드 단축키", + "navigation_bar.lists": "리스트", "navigation_bar.logout": "로그아웃", "navigation_bar.mutes": "뮤트 중인 사용자", "navigation_bar.pins": "고정된 툿", @@ -162,8 +162,8 @@ "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.push": "푸시 알림", + "notifications.column_settings.push_meta": "이 장치", "notifications.column_settings.reblog": "부스트", "notifications.column_settings.show": "컬럼에 표시", "notifications.column_settings.sound": "효과음 재생", @@ -220,7 +220,7 @@ "status.load_more": "더 보기", "status.media_hidden": "미디어 숨겨짐", "status.mention": "답장", - "status.more": "More", + "status.more": "자세히", "status.mute_conversation": "이 대화를 뮤트", "status.open": "상세 정보 표시", "status.pin": "고정", @@ -231,7 +231,7 @@ "status.report": "신고", "status.sensitive_toggle": "클릭해서 표시하기", "status.sensitive_warning": "민감한 미디어", - "status.share": "Share", + "status.share": "공유", "status.show_less": "숨기기", "status.show_more": "더 보기", "status.unmute_conversation": "이 대화의 뮤트 해제하기", @@ -241,19 +241,19 @@ "tabs_bar.home": "홈", "tabs_bar.local_timeline": "로컬", "tabs_bar.notifications": "알림", - "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", + "ui.beforeunload": "지금 나가면 저장되지 않은 항목을 잃게 됩니다.", "upload_area.title": "드래그 & 드롭으로 업로드", "upload_button.label": "미디어 추가", "upload_form.description": "Describe for the visually impaired", "upload_form.undo": "재시도", "upload_progress.label": "업로드 중...", - "video.close": "Close video", - "video.exit_fullscreen": "Exit full screen", - "video.expand": "Expand video", - "video.fullscreen": "Full screen", - "video.hide": "Hide video", - "video.mute": "Mute sound", - "video.pause": "Pause", - "video.play": "Play", - "video.unmute": "Unmute sound" + "video.close": "동영상 닫기", + "video.exit_fullscreen": "전체화면 나가기", + "video.expand": "동영상 확장", + "video.fullscreen": "전체화면", + "video.hide": "동영상 숨기기", + "video.mute": "음소거", + "video.pause": "일시정지", + "video.play": "재생", + "video.unmute": "음소거 해제" } diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 26e86308d..e154d1ab2 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -36,7 +36,7 @@ "column.favourites": "Favorieten", "column.follow_requests": "Volgverzoeken", "column.home": "Start", - "column.lists": "Lists", + "column.lists": "Lijsten", "column.mutes": "Genegeerde gebruikers", "column.notifications": "Meldingen", "column.pins": "Vastgezette toots", @@ -64,7 +64,7 @@ "confirmations.delete.confirm": "Verwijderen", "confirmations.delete.message": "Weet je het zeker dat je deze toot wilt verwijderen?", "confirmations.delete_list.confirm": "Delete", - "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", + "confirmations.delete_list.message": "Weet je zeker dat je deze lijst definitief wilt verwijderen?", "confirmations.domain_block.confirm": "Negeer alles van deze server", "confirmations.domain_block.message": "Weet je het echt, echt zeker dat je alles van {domain} wil negeren? In de meeste gevallen is het blokkeren of negeren van een paar specifieke personen voldoende en gewenst.", "confirmations.mute.confirm": "Negeren", @@ -91,7 +91,7 @@ "empty_column.hashtag": "Er is nog niks te vinden onder deze hashtag.", "empty_column.home": "Jij volgt nog niemand. Bezoek {public} of gebruik het zoekvenster om andere mensen te ontmoeten.", "empty_column.home.public_timeline": "de globale tijdlijn", - "empty_column.list": "Er is nog niks in deze lijst.", + "empty_column.list": "Er is nog niks in deze lijst. Wanneer lijstleden nieuwe toots publiceren, zijn deze hier te zien.", "empty_column.notifications": "Je hebt nog geen meldingen. Heb interactie met andere mensen om het gesprek aan te gaan.", "empty_column.public": "Er is hier helemaal niks! Toot iets in het openbaar of volg mensen van andere servers om het te vullen", "follow_request.authorize": "Goedkeuren", @@ -107,34 +107,34 @@ "home.column_settings.show_reblogs": "Boosts tonen", "home.column_settings.show_replies": "Reacties tonen", "home.settings": "Kolom-instellingen", - "keyboard_shortcuts.back": "om terug te navigeren", + "keyboard_shortcuts.back": "om terug te gaan", "keyboard_shortcuts.boost": "om te boosten", - "keyboard_shortcuts.column": "om te focussen op een status in één van de kolommen", - "keyboard_shortcuts.compose": "om te focussen op het toot tekstvak", - "keyboard_shortcuts.description": "Description", + "keyboard_shortcuts.column": "om op een toot te focussen in één van de kolommen", + "keyboard_shortcuts.compose": "om het tekstvak voor toots te focussen", + "keyboard_shortcuts.description": "Omschrijving", "keyboard_shortcuts.down": "om naar beneden door de lijst te bewegen", "keyboard_shortcuts.enter": "to open status", - "keyboard_shortcuts.favourite": "om het te markeren als favoriet", - "keyboard_shortcuts.heading": "Keyboard Shortcuts", + "keyboard_shortcuts.favourite": "om als favoriet te markeren", + "keyboard_shortcuts.heading": "Sneltoetsen", "keyboard_shortcuts.hotkey": "Sneltoets", "keyboard_shortcuts.legend": "om deze legenda weer te geven", "keyboard_shortcuts.mention": "om de auteur te vermelden", - "keyboard_shortcuts.reply": "om te antwoorden", - "keyboard_shortcuts.search": "om te focussen op zoeken", + "keyboard_shortcuts.reply": "om te reageren", + "keyboard_shortcuts.search": "om het zoekvak te focussen", "keyboard_shortcuts.toot": "om een nieuwe toot te starten", - "keyboard_shortcuts.unfocus": "om te ontfocussen van het toot tekstvak/zoeken", + "keyboard_shortcuts.unfocus": "om het tekst- en zoekvak te ontfocussen", "keyboard_shortcuts.up": "om omhoog te bewegen in de lijst", "lightbox.close": "Sluiten", "lightbox.next": "Volgende", "lightbox.previous": "Vorige", - "lists.account.add": "Add to list", - "lists.account.remove": "Remove from list", - "lists.delete": "Delete list", - "lists.edit": "Edit list", - "lists.new.create": "Add list", - "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", - "lists.subheading": "Your lists", + "lists.account.add": "Aan lijst toevoegen", + "lists.account.remove": "Uit lijst verwijderen", + "lists.delete": "Lijst verwijderen", + "lists.edit": "Lijst bewerken", + "lists.new.create": "Lijst toevoegen", + "lists.new.title_placeholder": "Naam nieuwe lijst", + "lists.search": "Zoek naar mensen die je volgt", + "lists.subheading": "Jouw lijsten", "loading_indicator.label": "Laden…", "media_gallery.toggle_visible": "Media wel/niet tonen", "missing_indicator.label": "Niet gevonden", @@ -146,7 +146,7 @@ "navigation_bar.follow_requests": "Volgverzoeken", "navigation_bar.info": "Uitgebreide informatie", "navigation_bar.keyboard_shortcuts": "Toetsenbord sneltoetsen", - "navigation_bar.lists": "Lists", + "navigation_bar.lists": "Lijsten", "navigation_bar.logout": "Afmelden", "navigation_bar.mutes": "Genegeerde gebruikers", "navigation_bar.pins": "Vastgezette toots", @@ -204,7 +204,7 @@ "reply_indicator.cancel": "Annuleren", "report.placeholder": "Extra opmerkingen", "report.submit": "Verzenden", - "report.target": "Rapporteren van", + "report.target": "Rapporteer {target}", "search.placeholder": "Zoeken", "search_popout.search_format": "Geavanceerd zoeken", "search_popout.tips.hashtag": "hashtag", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index 233b6c946..bf2b6259a 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Laster...", "media_gallery.toggle_visible": "Veksle synlighet", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index ec7202ff6..0d1f7c971 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -10,22 +10,22 @@ "account.hide_reblogs": "Rescondre los partages de @{name}", "account.media": "Mèdias", "account.mention": "Mencionar @{name}", - "account.moved_to": "{name} a mudat los catons a : ", + "account.moved_to": "{name} a mudat los catons a :", "account.mute": "Rescondre @{name}", - "account.mute_notifications": "Mute notifications from @{name}", + "account.mute_notifications": "Rescondre las notificacions de @{name}", "account.posts": "Estatuts", "account.report": "Senhalar @{name}", - "account.requested": "Invitacion mandada. Clicatz per anullar.", + "account.requested": "Invitacion mandada. Clicatz per anullar", "account.share": "Partejar lo perfil a @{name}", "account.show_reblogs": "Mostrar los partages de @{name}", "account.unblock": "Desblocar @{name}", "account.unblock_domain": "Desblocar {domain}", "account.unfollow": "Quitar de sègre", "account.unmute": "Quitar de rescondre @{name}", - "account.unmute_notifications": "Unmute notifications from @{name}", + "account.unmute_notifications": "Mostrar las notificacions de @{name}", "account.view_full_profile": "Veire lo perfil complet", "boost_modal.combo": "Podètz botar {combo} per passar aquò lo còp que ven", - "bundle_column_error.body": "Quicòm a fach meuca pendent lo cargament d’aqueste compausant.", + "bundle_column_error.body": "Quicòm a fach mèuca pendent lo cargament d’aqueste compausant.", "bundle_column_error.retry": "Tornar ensajar", "bundle_column_error.title": "Error de ret", "bundle_modal_error.close": "Tampar", @@ -36,7 +36,7 @@ "column.favourites": "Favorits", "column.follow_requests": "Demandas d’abonament", "column.home": "Acuèlh", - "column.lists": "Lists", + "column.lists": "Listas", "column.mutes": "Personas rescondudas", "column.notifications": "Notificacions", "column.pins": "Tuts penjats", @@ -63,8 +63,8 @@ "confirmations.block.message": "Sètz segur de voler blocar {name} ?", "confirmations.delete.confirm": "Escafar", "confirmations.delete.message": "Sètz segur de voler escafar l’estatut ?", - "confirmations.delete_list.confirm": "Delete", - "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", + "confirmations.delete_list.confirm": "Suprimir", + "confirmations.delete_list.message": "Sètz segur de voler suprimir aquesta lista per totjorn ?", "confirmations.domain_block.confirm": "Amagar tot lo domeni", "confirmations.domain_block.message": "Sètz segur segur de voler blocar completament {domain} ? De còps cal pas que blocar o rescondre unas personas solament.", "confirmations.mute.confirm": "Rescondre", @@ -72,7 +72,7 @@ "confirmations.unfollow.confirm": "Quitar de sègre", "confirmations.unfollow.message": "Volètz vertadièrament quitar de sègre {name} ?", "embed.instructions": "Embarcar aqueste estatut per lo far veire sus un site Internet en copiar lo còdi çai-jos.", - "embed.preview": "Semblarà aquò : ", + "embed.preview": "Semblarà aquò :", "emoji_button.activity": "Activitats", "emoji_button.custom": "Personalizats", "emoji_button.flags": "Drapèus", @@ -84,16 +84,16 @@ "emoji_button.people": "Gents", "emoji_button.recent": "Sovent utilizats", "emoji_button.search": "Cercar…", - "emoji_button.search_results": "Resultat de recèrca", + "emoji_button.search_results": "Resultats de recèrca", "emoji_button.symbols": "Simbòls", "emoji_button.travel": "Viatges & lòcs", "empty_column.community": "Lo flux public local es void. Escrivètz quicòm per lo garnir !", - "empty_column.hashtag": "I a pas encara de contengut ligat a aqueste hashtag", + "empty_column.hashtag": "I a pas encara de contengut ligat a aquesta etiqueta.", "empty_column.home": "Vòstre flux d’acuèlh es void. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.", "empty_column.home.public_timeline": "lo flux public", - "empty_column.list": "I a pas res dins la lista pel moment.", + "empty_column.list": "I a pas res dins la lista pel moment. Quand de membres d’aquesta lista publiquen de novèls estatuts los veiretz aquí.", "empty_column.notifications": "Avètz pas encara de notificacions. Respondètz a qualqu’un per començar una conversacion.", - "empty_column.public": "I a pas res aquí ! Escrivètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo flux public.", + "empty_column.public": "I a pas res aquí ! Escrivètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo flux public", "follow_request.authorize": "Autorizar", "follow_request.reject": "Regetar", "getting_started.appsshort": "Apps", @@ -116,7 +116,7 @@ "keyboard_shortcuts.enter": "per dobrir los estatuts", "keyboard_shortcuts.favourite": "per apondre als favorits", "keyboard_shortcuts.heading": "Acorchis clavièr", - "keyboard_shortcuts.hotkey": "Clau", + "keyboard_shortcuts.hotkey": "Acorchis", "keyboard_shortcuts.legend": "per mostrar aquesta legenda", "keyboard_shortcuts.mention": "per mencionar l’autor", "keyboard_shortcuts.reply": "per respondre", @@ -127,35 +127,35 @@ "lightbox.close": "Tampar", "lightbox.next": "Seguent", "lightbox.previous": "Precedent", - "lists.account.add": "Add to list", - "lists.account.remove": "Remove from list", - "lists.delete": "Delete list", - "lists.edit": "Edit list", - "lists.new.create": "Add list", - "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", - "lists.subheading": "Your lists", + "lists.account.add": "Ajustar a la lista", + "lists.account.remove": "Levar de la lista", + "lists.delete": "Suprimir la lista", + "lists.edit": "Modificar la lista", + "lists.new.create": "Ajustar una lista", + "lists.new.title_placeholder": "Títol de la nòva lista", + "lists.search": "Cercar demest lo monde que seguètz", + "lists.subheading": "Vòstras listas", "loading_indicator.label": "Cargament…", "media_gallery.toggle_visible": "Modificar la visibilitat", "missing_indicator.label": "Pas trobat", - "mute_modal.hide_notifications": "Hide notifications from this user?", + "mute_modal.hide_notifications": "Rescondre las notificacions d’aquesta persona ?", "navigation_bar.blocks": "Personas blocadas", "navigation_bar.community_timeline": "Flux public local", "navigation_bar.edit_profile": "Modificar lo perfil", "navigation_bar.favourites": "Favorits", - "navigation_bar.follow_requests": "Demandas d'abonament", + "navigation_bar.follow_requests": "Demandas d’abonament", "navigation_bar.info": "Mai informacions", "navigation_bar.keyboard_shortcuts": "Acorchis clavièr", - "navigation_bar.lists": "Lists", + "navigation_bar.lists": "Listas", "navigation_bar.logout": "Desconnexion", "navigation_bar.mutes": "Personas rescondudas", "navigation_bar.pins": "Tuts penjats", "navigation_bar.preferences": "Preferéncias", "navigation_bar.public_timeline": "Flux public global", - "notification.favourite": "{name} a ajustat a sos favorits :", + "notification.favourite": "{name} a ajustat a sos favorits", "notification.follow": "{name} vos sèc", - "notification.mention": "{name} vos a mencionat :", - "notification.reblog": "{name} a partejat vòstre estatut :", + "notification.mention": "{name} vos a mencionat", + "notification.reblog": "{name} a partejat vòstre estatut", "notifications.clear": "Escafar", "notifications.clear_confirmation": "Volètz vertadièrament escafar totas vòstras las notificacions ?", "notifications.column_settings.alert": "Notificacions localas", @@ -171,7 +171,7 @@ "onboarding.next": "Seguent", "onboarding.page_five.public_timelines": "Lo flux local mòstra los estatuts publics del monde de vòstra instància, aquí {domain}. Lo flux federat mòstra los estatuts publics de la gent que los de {domain} sègon. Son los fluxes publics, un bon biais de trobar de mond.", "onboarding.page_four.home": "Lo flux d’acuèlh mòstra los estatuts del mond que seguètz.", - "onboarding.page_four.notifications": "La colomna de notificacions vos fa veire quand qualqu’un interagís amb vos", + "onboarding.page_four.notifications": "La colomna de notificacions vos fa veire quand qualqu’un interagís amb vos.", "onboarding.page_one.federation": "Mastodon es un malhum de servidors independents que comunican per construire un malhum mai larg. Òm los apèla instàncias.", "onboarding.page_one.handle": "Sètz sus {domain}, doncas vòstre identificant complet es {handle}", "onboarding.page_one.welcome": "Benvengut a Mastodon !", @@ -209,7 +209,7 @@ "search_popout.search_format": "Format recèrca avançada", "search_popout.tips.hashtag": "etiqueta", "search_popout.tips.status": "estatut", - "search_popout.tips.text": "Tèxt brut tòrna escais, noms d’utilizaire e etiquetas correspondents", + "search_popout.tips.text": "Lo tèxt brut tòrna escais, noms d’utilizaire e etiquetas correspondents", "search_popout.tips.user": "utilizaire", "search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}", "standalone.public_title": "Una ulhada dedins…", @@ -225,7 +225,7 @@ "status.open": "Desplegar aqueste estatut", "status.pin": "Penjar al perfil", "status.reblog": "Partejar", - "status.reblogged_by": "{name} a partejat :", + "status.reblogged_by": "{name} a partejat", "status.reply": "Respondre", "status.replyAll": "Respondre a la conversacion", "status.report": "Senhalar @{name}", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 1df27d536..70632846c 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -36,7 +36,7 @@ "column.favourites": "Favoritos", "column.follow_requests": "Seguidores pendentes", "column.home": "Página inicial", - "column.lists": "Lists", + "column.lists": "Listas", "column.mutes": "Usuários silenciados", "column.notifications": "Notificações", "column.pins": "Postagens fixadas", @@ -64,7 +64,7 @@ "confirmations.delete.confirm": "Excluir", "confirmations.delete.message": "Você tem certeza de que quer excluir esta postagem?", "confirmations.delete_list.confirm": "Delete", - "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", + "confirmations.delete_list.message": "Você tem certeza que quer deletar permanentemente a lista?", "confirmations.domain_block.confirm": "Esconder o domínio inteiro", "confirmations.domain_block.message": "Você quer mesmo bloquear {domain} inteiro? Na maioria dos casos, silenciar ou bloquear alguns usuários é o suficiente e o recomendado.", "confirmations.mute.confirm": "Silenciar", @@ -109,44 +109,44 @@ "home.settings": "Configurações de colunas", "keyboard_shortcuts.back": "para navegar de volta", "keyboard_shortcuts.boost": "para compartilhar", - "keyboard_shortcuts.column": "to focus a status in one of the columns", - "keyboard_shortcuts.compose": "to focus the compose textarea", + "keyboard_shortcuts.column": "Focar um status em uma das colunas", + "keyboard_shortcuts.compose": "para focar a área de redação", "keyboard_shortcuts.description": "Description", - "keyboard_shortcuts.down": "to move down in the list", + "keyboard_shortcuts.down": "para mover para baixo na lista", "keyboard_shortcuts.enter": "to open status", - "keyboard_shortcuts.favourite": "to favourite", + "keyboard_shortcuts.favourite": "para adicionar aos favoritos", "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.toot": "to start a brand new toot", - "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", - "keyboard_shortcuts.up": "to move up in the list", + "keyboard_shortcuts.hotkey": "Atalho", + "keyboard_shortcuts.legend": "para mostrar essa legenda", + "keyboard_shortcuts.mention": "para mencionar o autor", + "keyboard_shortcuts.reply": "para responder", + "keyboard_shortcuts.search": "para focar a pesquisa", + "keyboard_shortcuts.toot": "para compor um novo toot", + "keyboard_shortcuts.unfocus": "para remover o foco da área de composição/pesquisa", + "keyboard_shortcuts.up": "para mover para cima na lista", "lightbox.close": "Fechar", "lightbox.next": "Próximo", "lightbox.previous": "Anterior", - "lists.account.add": "Add to list", - "lists.account.remove": "Remove from list", + "lists.account.add": "Adicionar a listas", + "lists.account.remove": "Remover da lista", "lists.delete": "Delete list", - "lists.edit": "Edit list", - "lists.new.create": "Add list", - "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", - "lists.subheading": "Your lists", + "lists.edit": "Editar lista", + "lists.new.create": "Adicionar lista", + "lists.new.title_placeholder": "Novo título da lista", + "lists.search": "Procurar entre as pessoas que você segue", + "lists.subheading": "Suas listas", "loading_indicator.label": "Carregando...", "media_gallery.toggle_visible": "Esconder/Mostrar", "missing_indicator.label": "Não encontrado", - "mute_modal.hide_notifications": "Hide notifications from this user?", + "mute_modal.hide_notifications": "Esconder notificações deste usuário?", "navigation_bar.blocks": "Usuários bloqueados", "navigation_bar.community_timeline": "Local", "navigation_bar.edit_profile": "Editar perfil", "navigation_bar.favourites": "Favoritos", "navigation_bar.follow_requests": "Seguidores pendentes", "navigation_bar.info": "Mais informações", - "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts", - "navigation_bar.lists": "Lists", + "navigation_bar.keyboard_shortcuts": "Atalhos de teclado", + "navigation_bar.lists": "Listas", "navigation_bar.logout": "Sair", "navigation_bar.mutes": "Usuários silenciados", "navigation_bar.pins": "Postagens fixadas", @@ -177,7 +177,7 @@ "onboarding.page_one.welcome": "Seja bem-vindo(a) ao Mastodon!", "onboarding.page_six.admin": "O administrador de sua instância é {admin}.", "onboarding.page_six.almost_done": "Quase acabando...", - "onboarding.page_six.appetoot": "Bon Appetoot!", + "onboarding.page_six.appetoot": "Bom Apetoot!", "onboarding.page_six.apps_available": "Há {apps} disponíveis para iOS, Android e outras plataformas.", "onboarding.page_six.github": "Mastodon é um software gratuito e de código aberto. Você pode reportar bugs, prequisitar novas funções ou contribuir para o código no {github}.", "onboarding.page_six.guidelines": "diretrizes da comunidade", @@ -220,7 +220,7 @@ "status.load_more": "Carregar mais", "status.media_hidden": "Mídia escondida", "status.mention": "Mencionar @{name}", - "status.more": "More", + "status.more": "Mais", "status.mute_conversation": "Silenciar conversa", "status.open": "Expandir", "status.pin": "Fixar no perfil", @@ -241,7 +241,7 @@ "tabs_bar.home": "Página inicial", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificações", - "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", + "ui.beforeunload": "Seu rascunho será perdido se você sair do Mastodon.", "upload_area.title": "Arraste e solte para enviar", "upload_button.label": "Adicionar mídia", "upload_form.description": "Descreva a imagem para deficientes visuais", diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json index 3d3e19571..15d5deb93 100644 --- a/app/javascript/mastodon/locales/pt.json +++ b/app/javascript/mastodon/locales/pt.json @@ -1,7 +1,7 @@ { "account.block": "Bloquear @{name}", "account.block_domain": "Esconder tudo do domínio {domain}", - "account.disclaimer_full": "Information below may reflect the user's profile incompletely.", + "account.disclaimer_full": "As informações abaixo podem refletir o perfil do usuário de forma incompleta.", "account.edit_profile": "Editar perfil", "account.follow": "Seguir", "account.followers": "Seguidores", @@ -19,7 +19,7 @@ "account.share": "Partilhar o perfil @{name}", "account.show_reblogs": "Mostrar partilhas de @{name}", "account.unblock": "Não bloquear @{name}", - "account.unblock_domain": "Unhide {domain}", + "account.unblock_domain": "Mostrar {domain}", "account.unfollow": "Deixar de seguir", "account.unmute": "Não silenciar @{name}", "account.unmute_notifications": "Deixar de silenciar @{name}", @@ -36,7 +36,7 @@ "column.favourites": "Favoritos", "column.follow_requests": "Seguidores Pendentes", "column.home": "Home", - "column.lists": "Lists", + "column.lists": "Listas", "column.mutes": "Utilizadores silenciados", "column.notifications": "Notificações", "column.pins": "Pinned toot", @@ -64,7 +64,7 @@ "confirmations.delete.confirm": "Eliminar", "confirmations.delete.message": "De certeza que queres eliminar esta publicação?", "confirmations.delete_list.confirm": "Delete", - "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", + "confirmations.delete_list.message": "Tens a certeza de que desejas apagar permanentemente esta lista?", "confirmations.domain_block.confirm": "Esconder tudo deste domínio", "confirmations.domain_block.message": "De certeza que queres bloquear por completo o domínio {domain}? Na maioria dos casos, silenciar ou bloquear alguns utilizadores é o suficiente e o recomendado.", "confirmations.mute.confirm": "Silenciar", @@ -88,12 +88,12 @@ "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viagens & Lugares", "empty_column.community": "Ainda não existe conteúdo local para mostrar!", - "empty_column.hashtag": "Não foram encontradas publicações com essa hashtag", + "empty_column.hashtag": "Não foram encontradas publicações com essa hashtag.", "empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.", "empty_column.home.public_timeline": "global", "empty_column.list": "Ainda não existem publicações nesta lista.", "empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.", - "empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos.", + "empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos", "follow_request.authorize": "Autorizar", "follow_request.reject": "Rejeitar", "getting_started.appsshort": "Aplicações", @@ -116,7 +116,7 @@ "keyboard_shortcuts.enter": "para expandir uma publicação", "keyboard_shortcuts.favourite": "para adicionar aos favoritos", "keyboard_shortcuts.heading": "Atalhos do teclado", - "keyboard_shortcuts.hotkey": "Hotkey", + "keyboard_shortcuts.hotkey": "Atalho", "keyboard_shortcuts.legend": "para mostrar esta legenda", "keyboard_shortcuts.mention": "para mencionar o autor", "keyboard_shortcuts.reply": "para responder", @@ -127,14 +127,14 @@ "lightbox.close": "Fechar", "lightbox.next": "Próximo", "lightbox.previous": "Anterior", - "lists.account.add": "Add to list", - "lists.account.remove": "Remove from list", + "lists.account.add": "Adicionar à lista", + "lists.account.remove": "Remover da lista", "lists.delete": "Delete list", - "lists.edit": "Edit list", - "lists.new.create": "Add list", - "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", - "lists.subheading": "Your lists", + "lists.edit": "Editar lista", + "lists.new.create": "Adicionar lista", + "lists.new.title_placeholder": "Novo título da lista", + "lists.search": "Pesquisa entre as pessoas que segues", + "lists.subheading": "As tuas listas", "loading_indicator.label": "A carregar...", "media_gallery.toggle_visible": "Esconder/Mostrar", "missing_indicator.label": "Não encontrado", @@ -146,7 +146,7 @@ "navigation_bar.follow_requests": "Seguidores pendentes", "navigation_bar.info": "Mais informações", "navigation_bar.keyboard_shortcuts": "Atalhos de teclado", - "navigation_bar.lists": "Lists", + "navigation_bar.lists": "Listas", "navigation_bar.logout": "Sair", "navigation_bar.mutes": "Utilizadores silenciados", "navigation_bar.pins": "Posts fixos", @@ -209,13 +209,13 @@ "search_popout.search_format": "Formato avançado de pesquisa", "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.text": "O texto simples retorna a correspondência de nomes, utilizadores e hashtags", "search_popout.tips.user": "utilizador", "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}", "standalone.public_title": "Espreitar lá dentro...", "status.cannot_reblog": "Este post não pode ser partilhado", "status.delete": "Eliminar", - "status.embed": "Embed", + "status.embed": "Incorporar", "status.favourite": "Adicionar aos favoritos", "status.load_more": "Carregar mais", "status.media_hidden": "Media escondida", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index 0aef2d9df..e9925b675 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Загрузка...", "media_gallery.toggle_visible": "Показать/скрыть", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json new file mode 100644 index 000000000..aacbc076a --- /dev/null +++ b/app/javascript/mastodon/locales/sk.json @@ -0,0 +1,214 @@ +{ + "account.block": "Blokovať @{name}", + "account.block_domain": "Blokovať všetko z {domain}", + "account.disclaimer_full": "Inofrmácie nižšie nemusia reflektovať použivateľský účet kompletne.", + "account.edit_profile": "Upraviť profil", + "account.follow": "Sledovať", + "account.followers": "Sledujúci", + "account.follows": "Sledovaní", + "account.follows_you": "Sleduje teba", + "account.media": "Média", + "account.mention": "Napísať @{name}", + "account.mute": "Ignorovať @{name}", + "account.posts": "Správ", + "account.report": "Nahlásiť @{name}", + "account.requested": "Čaká na schválenie. Klikni na zrušenie žiadosti", + "account.share": "Zdieľať @{name} profil", + "account.unblock": "Odblokovať @{name}", + "account.unblock_domain": "Prestať blokovať {domain}", + "account.unfollow": "Prestať nasledovať", + "account.unmute": "Prestať ignorovať @{name}", + "account.view_full_profile": "Pozri celý profil", + "boost_modal.combo": "Nabudúce môžeš kliknúť {combo} a preskočiť", + "bundle_column_error.body": "Nastala chyba pri načítaní tohto komponentu.", + "bundle_column_error.retry": "Skús znova", + "bundle_column_error.title": "Chyba siete", + "bundle_modal_error.close": "Zatvoriť", + "bundle_modal_error.message": "Nastala chyba pri načítaní tohto komponentu.", + "bundle_modal_error.retry": "Skúsiť znova", + "column.blocks": "Blokovaní používatelia", + "column.community": "Lokálna časová os", + "column.favourites": "Obľúbené", + "column.follow_requests": "Žiadosti", + "column.home": "Moja časová os", + "column.mutes": "Ignorovaní používatelia", + "column.notifications": "Notifikácie", + "column.pins": "Pripnuté toots", + "column.public": "Federovaná časová os", + "column_back_button.label": "Späť", + "column_header.hide_settings": "Skryť nastavenia", + "column_header.moveLeft_settings": "Presunúť stĺpec doľava", + "column_header.moveRight_settings": "Presunúť stĺpec doprava", + "column_header.pin": "Pripnúť", + "column_header.show_settings": "Ukázať nastavenia", + "column_header.unpin": "Odopnúť", + "column_subheading.navigation": "Navigácia", + "column_subheading.settings": "Nastavenia", + "compose_form.lock_disclaimer": "Tvoj účet nie je zamknutý. Ktokoľvek ťa môže nasledovať a vidieť tvoje správy pre sledujúcich.", + "compose_form.lock_disclaimer.lock": "zamknutý", + "compose_form.placeholder": "Čo máš na mysli?", + "compose_form.publish": "Toot", + "compose_form.publish_loud": "{publish}!", + "compose_form.sensitive": "Označ súbor ako chúlostivý", + "compose_form.spoiler": "Skryť text za varovanie", + "compose_form.spoiler_placeholder": "Napíš sem tvoje varovanie", + "confirmation_modal.cancel": "Zrušiť", + "confirmations.block.confirm": "Blokovať", + "confirmations.block.message": "Naozaj chceš blokovať {name}?", + "confirmations.delete.confirm": "Zmazať", + "confirmations.delete.message": "Naozaj chceš zmazať túto správu?", + "confirmations.domain_block.confirm": "Skryť celú doménu", + "confirmations.domain_block.message": "Si si naozaj istý, že chceš blokovať celú {domain}? Vo väčšine prípadov stačí blokovať alebo ignorovať daných používateľov.", + "confirmations.mute.confirm": "Ignoruj", + "confirmations.mute.message": "Naozaj chceš ignorovať {name}?", + "confirmations.unfollow.confirm": "Nesledovať", + "confirmations.unfollow.message": "Naozaj chceš prestať sledovať {name}?", + "embed.instructions": "Skopíruj kód nižšie a ridaj tento status na tvoju web stránku.", + "embed.preview": "Tu je ukážka ako to bude vyzerať:", + "emoji_button.activity": "Aktivity", + "emoji_button.custom": "Vlastné", + "emoji_button.flags": "Vlajky", + "emoji_button.food": "Jedlá a nápoje", + "emoji_button.label": "Vlož emoji", + "emoji_button.nature": "Zvieratká", + "emoji_button.not_found": "Nenájdené", + "emoji_button.objects": "Predmety", + "emoji_button.people": "Ľudia", + "emoji_button.recent": "Často používané", + "emoji_button.search": "Hľadaj...", + "emoji_button.search_results": "Nájdené", + "emoji_button.symbols": "Symboly", + "emoji_button.travel": "Cestovanie a miesta", + "empty_column.community": "Lokálna časová os je prázdna. Napíš niečo aby sa to začalo hýbať!", + "empty_column.hashtag": "Ešte nič nie je v tomto hashtag-u.", + "empty_column.home": "Ešte nesleduješ nikoho. Pre začiatok pozri {public} alebo použi vyhľadávanie aby si našiel ostatných používateľov.", + "empty_column.home.inactivity": "Tvoja časová os je prázdna. Ak si bol dlho neaktívny, za krátku chvíľku bude obnovená.", + "empty_column.home.public_timeline": "verejnú časovú os", + "empty_column.notifications": "Nemáš žiadne notifikácie. Napíš niekomu, nasleduj niekoho alebo komunikuj s ostatnými.", + "empty_column.public": "Ešte tu nič nie je. Napíš niečo verejne alebo začni sledovať používateľov z iných Mastodon serverov aby tu niečo bolo", + "follow_request.authorize": "Potvrdiť", + "follow_request.reject": "Odmietnúť", + "getting_started.appsshort": "Aplikácie", + "getting_started.faq": "FAQ", + "getting_started.heading": "Začíname", + "getting_started.open_source_notice": "Mastodon má otvorený kód. Reportovať chyby alebo prispievať vlastným kódom môžeš na GitHube v {github}.", + "getting_started.userguide": "Používateľská príručka", + "home.column_settings.advanced": "Rozšírené", + "home.column_settings.basic": "Základné", + "home.column_settings.filter_regex": "Filtrovať použitím regulárnych výrazov", + "home.column_settings.show_reblogs": "Zobraziť boosts", + "home.column_settings.show_replies": "Zobraziť odpovede", + "home.settings": "Nastavenia stĺpcov", + "lightbox.close": "Zavrieť", + "lightbox.next": "Ďalší", + "lightbox.previous": "Predchádzajúci", + "loading_indicator.label": "Nahrávam...", + "media_gallery.toggle_visible": "Zapnúť/Vypnúť viditeľnosť", + "missing_indicator.label": "Nenájdené", + "navigation_bar.blocks": "Blokovaní používatelia", + "navigation_bar.community_timeline": "Lokálna časová os", + "navigation_bar.edit_profile": "Upraviť profil", + "navigation_bar.favourites": "Obľúbené", + "navigation_bar.follow_requests": "Žiadosti", + "navigation_bar.info": "O tomto Mastodon serveri", + "navigation_bar.logout": "Odhlásiť", + "navigation_bar.mutes": "Ignorovaní používatelia", + "navigation_bar.pins": "Pripnuté toots", + "navigation_bar.preferences": "Možnosti", + "navigation_bar.public_timeline": "Federovaná časová os", + "notification.favourite": "{name} sa páči tvoj status", + "notification.follow": "{name} ťa začal(a) sledovať", + "notification.mention": "{name} ťa zmienil", + "notification.reblog": "{name} re-tootol tvoj status", + "notifications.clear": "Vymazať notifikácie", + "notifications.clear_confirmation": "Naozaj chceš vymazať všetky tvoje notifikácie?", + "notifications.column_settings.alert": "Bublinové notifikácie", + "notifications.column_settings.favourite": "Obľúbené:", + "notifications.column_settings.follow": "Nový nasledujúci:", + "notifications.column_settings.mention": "Zmienenia:", + "notifications.column_settings.push": "Push notifikácie", + "notifications.column_settings.push_meta": "Toto zariadenie", + "notifications.column_settings.reblog": "Re-toots:", + "notifications.column_settings.show": "Zobraziť v stĺpci", + "notifications.column_settings.sound": "Prehrať zvuk", + "onboarding.done": "Koniec", + "onboarding.next": "Ďalej", + "onboarding.page_five.public_timelines": "Lokálna časová os zobrazuje verejné správy od všetkých na {domain}. Federovaná časová os zobrazuje verejné správy od všetkých ľudí ktoré {domain} nasleduje. Tieto sú takzvané Verejné Časové Osi, výborná možnosť ako nájsť a spoznať nových ľudí.", + "onboarding.page_four.home": "Domovská časová os zobrazí správy od ľudí ktorých sleduješ.", + "onboarding.page_four.notifications": "Stĺpec s notifikáciami zobrazí keď budeš s niekým komunikovať.", + "onboarding.page_one.federation": "Mastodon je sieť nezávislých serverov spojením ktorých vzniká jedna veľká federovaná sociálna sieť.", + "onboarding.page_one.handle": "Ty si na {domain}, takže tvoje celý nickname je {handle}", + "onboarding.page_one.welcome": "Vitajte v Mastodon!", + "onboarding.page_six.admin": "Správca tohto servera je {admin}.", + "onboarding.page_six.almost_done": "Takmer hotovo...", + "onboarding.page_six.appetoot": "Bon Appetoot!", + "onboarding.page_six.apps_available": "Aplikácie {apps} sú dostupné na pre iOS, Android and ďalšie platformy.", + "onboarding.page_six.github": "Mastodon je free open-source software. Chyby, nové funkcie alebo prispievať svojím kódom mǒžeš na {github}.", + "onboarding.page_six.guidelines": "pravidlá komunity", + "onboarding.page_six.read_guidelines": "Prosím prečítajte si {domain} pravidlá {guidelines}!", + "onboarding.page_six.various_app": "mobilné applikácie", + "onboarding.page_three.profile": "Uprav svoj profile a zmeň svoj avatar, bio a meno ktoré bude zobrazené. V nastaveniach nájdeš ďalšie možnosti.", + "onboarding.page_three.search": "Použi vyhľadávacie políčko na nájdenie ľudí a hashtagov, ako napríklad {slovensko}, {slovakia} alebo {pivo}. Na nájdenie človeka ktorý je registrovaný na inom Mastodon serveri použi jeho celý nickname.", + "onboarding.page_two.compose": "Správy píš zo stĺpca na komponovanie. Môžeš nahrávať obrázky, meniť nastavenia súkromia správ a pridávať varovania ikonkami nižšie.", + "onboarding.skip": "Preskočiť", + "privacy.change": "Zmeň viditeľnosť statusu", + "privacy.direct.long": "Pošli priamo iba spomenutým používateľom", + "privacy.direct.short": "Súkromne", + "privacy.private.long": "Pošli iba sledujúcim", + "privacy.private.short": "Iba sledujúci", + "privacy.public.long": "Pošli všetkým", + "privacy.public.short": "Verejne", + "privacy.unlisted.long": "Neposielať verejne", + "privacy.unlisted.short": "Nie je v zozname", + "reply_indicator.cancel": "Zrušiť", + "report.placeholder": "Ďalšie komentáre", + "report.submit": "Poslať", + "report.target": "Reportovať {target}", + "search.placeholder": "Hľadaj", + "search_results.total": "{count, number} nájdených", + "standalone.public_title": "Čo tam nájdeš...", + "status.cannot_reblog": "Tento príspevok nemôže byť re-tootnutý", + "status.delete": "Zmazať", + "status.embed": "Embed", + "status.favourite": "Páči sa mi", + "status.load_more": "Zobraziť viac", + "status.media_hidden": "Skryté médiá", + "status.mention": "Napísať @{name}", + "status.mute_conversation": "Ignorovať konverzáciu", + "status.open": "Otvoriť", + "status.pin": "Pripnúť na profil", + "status.reblog": "Re-toot", + "status.reblogged_by": "{name} re-tootol", + "status.reply": "Odpovedať", + "status.replyAll": "Odpovedať všetkým", + "status.report": "Nahlásiť @{name}", + "status.sensitive_toggle": "Klikni pre zobrazenie", + "status.sensitive_warning": "Chúlostivý obsah", + "status.share": "Zdieľať", + "status.show_less": "Zobraziť menej", + "status.show_more": "Zobraziť viac", + "status.unmute_conversation": "Prestať ignorovať konverzáciu", + "status.unpin": "Odopnúť z profilu", + "tabs_bar.compose": "Napísať", + "tabs_bar.federated_timeline": "Federovaná", + "tabs_bar.home": "Domov", + "tabs_bar.local_timeline": "Local", + "tabs_bar.notifications": "Notifikácie", + "upload_area.title": "Ťahaj a pusti pre nahratie", + "upload_button.label": "Pridať", + "upload_form.undo": "Späť", + "upload_progress.label": "Nahrávam...", + "video.close": "Zavrieť video", + "video.exit_fullscreen": "Vpnúť zobrazenie na celú obrazovku", + "video.expand": "Zväčšiť video", + "video.fullscreen": "Zapnúť zobrazenie na celú obrazovku", + "video.hide": "Skryť video", + "video.mute": "Vypnúť zvuk", + "video.pause": "Pauza", + "video.play": "Prehrať", + "video.unmute": "Zapnúť zvuk", + "video_player.expand": "Zväčšiť video", + "video_player.toggle_sound": "Zapnúť/Vypnúť zvuk", + "video_player.toggle_visible": "Zapnúť/Vypnúť video", + "video_player.video_error": "Video nebolo možné prehrať" +} diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 53090452f..9d9646509 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Laddar...", "media_gallery.toggle_visible": "Växla synlighet", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index 2f064a193..cc18a6096 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Loading...", "media_gallery.toggle_visible": "Toggle visibility", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index be8103d1c..c51f3e417 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Yükleniyor...", "media_gallery.toggle_visible": "Görünürlüğü değiştir", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 273661462..86c0ce76d 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "Завантаження...", "media_gallery.toggle_visible": "Показати/приховати", diff --git a/app/javascript/mastodon/locales/whitelist_gl.json b/app/javascript/mastodon/locales/whitelist_gl.json new file mode 100644 index 000000000..0d4f101c7 --- /dev/null +++ b/app/javascript/mastodon/locales/whitelist_gl.json @@ -0,0 +1,2 @@ +[ +] diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index cb5607cc5..9be6a9f73 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -25,11 +25,11 @@ "account.unmute_notifications": "不再隐藏来自 @{name} 的通知", "account.view_full_profile": "查看完整资料", "boost_modal.combo": "下次按住 {combo} 即可跳过此提示", - "bundle_column_error.body": "载入组件出错。", + "bundle_column_error.body": "载入这个组件时发生了错误。", "bundle_column_error.retry": "重试", "bundle_column_error.title": "网络错误", "bundle_modal_error.close": "关闭", - "bundle_modal_error.message": "载入组件出错。", + "bundle_modal_error.message": "载入这个组件时发生了错误。", "bundle_modal_error.retry": "重试", "column.blocks": "屏蔽用户", "column.community": "本站时间轴", @@ -50,8 +50,8 @@ "column_header.unpin": "取消固定", "column_subheading.navigation": "导航", "column_subheading.settings": "设置", - "compose_form.lock_disclaimer": "你的帐户没有{locked}。任何人都可以通过关注你来查看仅关注者可见的嘟文。", - "compose_form.lock_disclaimer.lock": "被保护", + "compose_form.lock_disclaimer": "你的帐户没有{locked}。任何人都可以在关注你后立即查看仅关注者可见的嘟文。", + "compose_form.lock_disclaimer.lock": "开启保护", "compose_form.placeholder": "在想啥?", "compose_form.publish": "嘟嘟", "compose_form.publish_loud": "{publish}!", @@ -60,17 +60,17 @@ "compose_form.spoiler_placeholder": "折叠部分的警告消息", "confirmation_modal.cancel": "取消", "confirmations.block.confirm": "屏蔽", - "confirmations.block.message": "想好了,真的要屏蔽 {name}?", + "confirmations.block.message": "你确定要屏蔽 {name} 吗?", "confirmations.delete.confirm": "删除", - "confirmations.delete.message": "想好了,真的要删除这条嘟文?", + "confirmations.delete.message": "你确定要删除这条嘟文吗?", "confirmations.delete_list.confirm": "删除", "confirmations.delete_list.message": "你确定要永久删除这个列表吗?", "confirmations.domain_block.confirm": "隐藏整个网站的内容", - "confirmations.domain_block.message": "你真的真的确定要隐藏所有来自 {domain} 的内容吗?多数情况下,屏蔽或隐藏几个特定的用户就应该能满足你的需要了。", + "confirmations.domain_block.message": "你真的确定要隐藏所有来自 {domain} 的内容吗?多数情况下,屏蔽或隐藏几个特定的用户应该就能满足你的需要了。", "confirmations.mute.confirm": "隐藏", - "confirmations.mute.message": "想好了,真的要隐藏 {name}?", + "confirmations.mute.message": "你确定要隐藏 {name} 吗?", "confirmations.unfollow.confirm": "取消关注", - "confirmations.unfollow.message": "确定要取消关注 {name} 吗?", + "confirmations.unfollow.message": "你确定要取消关注 {name} 吗?", "embed.instructions": "要在你的网站上嵌入这条嘟文,请复制以下代码。", "embed.preview": "它会像这样显示出来:", "emoji_button.activity": "活动", @@ -91,9 +91,9 @@ "empty_column.hashtag": "这个话题标签下暂时没有内容。", "empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。", "empty_column.home.public_timeline": "公共时间轴", - "empty_column.list": "这个列表中暂时没有内容。", - "empty_column.notifications": "你还没有收到过通知信息,快向其他用户搭讪吧。", - "empty_column.public": "这里神马都没有!写一些公开的嘟文,或者关注其他实例的用户,这里就会有嘟文出现了哦!", + "empty_column.list": "这个列表中暂时没有内容。列表中用户所发送的的新嘟文将会在这里显示。", + "empty_column.notifications": "你还没有收到过任何通知,快向其他用户搭讪吧。", + "empty_column.public": "这里神马都没有!写一些公开的嘟文,或者关注其他实例的用户后,这里就会有嘟文出现了哦!", "follow_request.authorize": "同意", "follow_request.reject": "拒绝", "getting_started.appsshort": "应用", @@ -138,7 +138,7 @@ "loading_indicator.label": "加载中……", "media_gallery.toggle_visible": "切换显示/隐藏", "missing_indicator.label": "找不到内容", - "mute_modal.hide_notifications": "隐藏来自这个用户的通知", + "mute_modal.hide_notifications": "同时隐藏来自这个用户的通知", "navigation_bar.blocks": "被屏蔽的用户", "navigation_bar.community_timeline": "本站时间轴", "navigation_bar.edit_profile": "修改个人资料", @@ -157,25 +157,25 @@ "notification.mention": "{name} 提及你", "notification.reblog": "{name} 转嘟了你的嘟文", "notifications.clear": "清空通知列表", - "notifications.clear_confirmation": "你确定要清空通知列表吗?", + "notifications.clear_confirmation": "你确定要永久清空通知列表吗?", "notifications.column_settings.alert": "桌面通知", - "notifications.column_settings.favourite": "你的嘟文被收藏:", - "notifications.column_settings.follow": "关注你:", - "notifications.column_settings.mention": "提及你:", + "notifications.column_settings.favourite": "当你的嘟文被收藏时:", + "notifications.column_settings.follow": "当有人关注你时:", + "notifications.column_settings.mention": "当有人在嘟文中提及你时:", "notifications.column_settings.push": "推送通知", "notifications.column_settings.push_meta": "此设备", - "notifications.column_settings.reblog": "你的嘟文被转嘟:", + "notifications.column_settings.reblog": "当有人转嘟了你的嘟文时:", "notifications.column_settings.show": "在通知栏显示", "notifications.column_settings.sound": "播放音效", "onboarding.done": "出发!", "onboarding.next": "下一步", "onboarding.page_five.public_timelines": "本站时间轴显示的是由本站({domain})用户发布的所有公开嘟文。跨站公共时间轴显示的的是由本站用户关注对象所发布的所有公开嘟文。这些就是寻人好去处的公共时间轴啦。", - "onboarding.page_four.home": "你的主页上的时间轴上显示的是你关注对象的嘟文。", - "onboarding.page_four.notifications": "如果有人与你互动,便会出现在通知栏中哦~", - "onboarding.page_one.federation": "Mastodon 是由一系列独立的服务器共同打造的强大的社交网络,我们将这些各自独立但又相互连接的服务器叫做实例。", - "onboarding.page_one.handle": "你在 {domain},{handle} 就是你的完整帐户名称。", + "onboarding.page_four.home": "你的主页时间轴上显示的是你的关注对象所发布的嘟文。", + "onboarding.page_four.notifications": "如果有人与你互动了,他们就会出现在通知栏中哦~", + "onboarding.page_one.federation": "Mastodon 是由一系列独立的服务器共同打造的强大的社交网络,我们将这些各自独立而又相互连接的服务器叫做实例。", + "onboarding.page_one.handle": "你是在 {domain} 上注册的,所以你的完整用户地址是 {handle}。", "onboarding.page_one.welcome": "欢迎来到 Mastodon!", - "onboarding.page_six.admin": "{admin} 是你所在服务器实例的管理员.", + "onboarding.page_six.admin": "{admin} 是你所在服务器实例的管理员。", "onboarding.page_six.almost_done": "差不多了……", "onboarding.page_six.appetoot": "嗷呜~", "onboarding.page_six.apps_available": "我们还有适用于 iOS、Android 和其它平台的{apps}哦~", @@ -184,8 +184,8 @@ "onboarding.page_six.read_guidelines": "别忘了看看 {domain} 的{guidelines}!", "onboarding.page_six.various_app": "移动设备应用", "onboarding.page_three.profile": "你可以修改你的个人资料,比如头像、简介和昵称等偏好设置。", - "onboarding.page_three.search": "你可以通过搜索功能寻找用户和话题标签,比如{illustration}或者{introductions}。如果你想搜索其他实例上的用户,就需要输入完整帐户名称(用户名@域名)哦。", - "onboarding.page_two.compose": "在撰写栏中开始嘟嘟吧!下方的按钮分别用来上传图片,修改嘟文可见范围,以及添加警告信息。", + "onboarding.page_three.search": "你可以通过搜索功能寻找用户和话题标签,比如{illustration}或者{introductions}。如果你想搜索其他实例上的用户,就需要输入完整用户地址(@用户名@域名)哦。", + "onboarding.page_two.compose": "在撰写栏中开始嘟嘟吧!下方的按钮分别可以用来上传图片、修改嘟文可见范围,以及添加警告信息。", "onboarding.skip": "跳过", "privacy.change": "设置嘟文可见范围", "privacy.direct.long": "只有被提及的用户能看到", @@ -196,11 +196,11 @@ "privacy.public.short": "公开", "privacy.unlisted.long": "所有人可见,但不会出现在公共时间轴上", "privacy.unlisted.short": "不公开", - "relative_time.days": "{number} 天", - "relative_time.hours": "{number} 时", + "relative_time.days": "{number}天", + "relative_time.hours": "{number}时", "relative_time.just_now": "刚刚", - "relative_time.minutes": "{number} 分", - "relative_time.seconds": "{number} 秒", + "relative_time.minutes": "{number}分", + "relative_time.seconds": "{number}秒", "reply_indicator.cancel": "取消", "report.placeholder": "附言", "report.submit": "提交", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index dbb9584c6..15a68c915 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "載入中...", "media_gallery.toggle_visible": "打開或關上", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 0b05a83cd..1bdc883a8 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -133,7 +133,7 @@ "lists.edit": "Edit list", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", - "lists.search": "Search among follows", + "lists.search": "Search among people you follow", "lists.subheading": "Your lists", "loading_indicator.label": "讀取中...", "media_gallery.toggle_visible": "切換可見性", diff --git a/app/javascript/mastodon/middleware/sounds.js b/app/javascript/mastodon/middleware/sounds.js index 3d1e3eaba..9f1bc02b9 100644 --- a/app/javascript/mastodon/middleware/sounds.js +++ b/app/javascript/mastodon/middleware/sounds.js @@ -15,7 +15,7 @@ const play = audio => { if (typeof audio.fastSeek === 'function') { audio.fastSeek(0); } else { - audio.seek(0); + audio.currentTime = 0; } } diff --git a/app/javascript/mastodon/reducers/status_lists.js b/app/javascript/mastodon/reducers/status_lists.js index c4aeb338f..6c5f33557 100644 --- a/app/javascript/mastodon/reducers/status_lists.js +++ b/app/javascript/mastodon/reducers/status_lists.js @@ -1,6 +1,10 @@ import { + FAVOURITED_STATUSES_FETCH_REQUEST, FAVOURITED_STATUSES_FETCH_SUCCESS, + FAVOURITED_STATUSES_FETCH_FAIL, + FAVOURITED_STATUSES_EXPAND_REQUEST, FAVOURITED_STATUSES_EXPAND_SUCCESS, + FAVOURITED_STATUSES_EXPAND_FAIL, } from '../actions/favourites'; import { PINNED_STATUSES_FETCH_SUCCESS, @@ -30,6 +34,7 @@ const normalizeList = (state, listType, statuses, next) => { return state.update(listType, listMap => listMap.withMutations(map => { map.set('next', next); map.set('loaded', true); + map.set('isLoading', false); map.set('items', ImmutableList(statuses.map(item => item.id))); })); }; @@ -37,6 +42,7 @@ const normalizeList = (state, listType, statuses, next) => { const appendToList = (state, listType, statuses, next) => { return state.update(listType, listMap => listMap.withMutations(map => { map.set('next', next); + map.set('isLoading', false); map.set('items', map.get('items').concat(statuses.map(item => item.id))); })); }; @@ -55,6 +61,12 @@ const removeOneFromList = (state, listType, status) => { export default function statusLists(state = initialState, action) { switch(action.type) { + case FAVOURITED_STATUSES_FETCH_REQUEST: + case FAVOURITED_STATUSES_EXPAND_REQUEST: + return state.setIn(['favourites', 'isLoading'], true); + case FAVOURITED_STATUSES_FETCH_FAIL: + case FAVOURITED_STATUSES_EXPAND_FAIL: + return state.setIn(['favourites', 'isLoading'], false); case FAVOURITED_STATUSES_FETCH_SUCCESS: return normalizeList(state, 'favourites', action.statuses, action.next); case FAVOURITED_STATUSES_EXPAND_SUCCESS: diff --git a/app/javascript/mastodon/settings.js b/app/javascript/mastodon/settings.js new file mode 100644 index 000000000..dbd969cb1 --- /dev/null +++ b/app/javascript/mastodon/settings.js @@ -0,0 +1,46 @@ +export default class Settings { + + constructor(keyBase = null) { + this.keyBase = keyBase; + } + + generateKey(id) { + return this.keyBase ? [this.keyBase, `id${id}`].join('.') : id; + } + + set(id, data) { + const key = this.generateKey(id); + try { + const encodedData = JSON.stringify(data); + localStorage.setItem(key, encodedData); + return data; + } catch (e) { + return null; + } + } + + get(id) { + const key = this.generateKey(id); + try { + const rawData = localStorage.getItem(key); + return JSON.parse(rawData); + } catch (e) { + return null; + } + } + + remove(id) { + const data = this.get(id); + if (data) { + const key = this.generateKey(id); + try { + localStorage.removeItem(key); + } catch (e) { + } + } + return data; + } + +} + +export const pushNotificationsSetting = new Settings('mastodon_push_notification_data'); diff --git a/app/javascript/mastodon/stream.js b/app/javascript/mastodon/stream.js index 36c68ffc5..9a6f4f26d 100644 --- a/app/javascript/mastodon/stream.js +++ b/app/javascript/mastodon/stream.js @@ -62,7 +62,13 @@ export function connectStream(path, pollingRefresh = null, callbacks = () => ({ export default function getStream(streamingAPIBaseURL, accessToken, stream, { connected, received, disconnected, reconnected }) { - const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?access_token=${accessToken}&stream=${stream}`); + const params = [ `stream=${stream}` ]; + + if (accessToken !== null) { + params.push(`access_token=${accessToken}`); + } + + const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?${params.join('&')}`); ws.onopen = connected; ws.onmessage = e => received(JSON.parse(e.data)); diff --git a/app/javascript/mastodon/web_push_subscription.js b/app/javascript/mastodon/web_push_subscription.js index 3dbed09ea..17aca4060 100644 --- a/app/javascript/mastodon/web_push_subscription.js +++ b/app/javascript/mastodon/web_push_subscription.js @@ -1,6 +1,7 @@ import axios from 'axios'; import { store } from './containers/mastodon'; import { setBrowserSupport, setSubscription, clearSubscription } from './actions/push_notifications'; +import { pushNotificationsSetting } from './settings'; // Taken from https://www.npmjs.com/package/web-push const urlBase64ToUint8Array = (base64String) => { @@ -35,16 +36,33 @@ const subscribe = (registration) => const unsubscribe = ({ registration, subscription }) => subscription ? subscription.unsubscribe().then(() => registration) : registration; -const sendSubscriptionToBackend = (subscription) => - axios.post('/api/web/push_subscriptions', { - subscription, - }).then(response => response.data); +const sendSubscriptionToBackend = (subscription) => { + const params = { subscription }; + + const me = store.getState().getIn(['meta', 'me']); + if (me) { + const data = pushNotificationsSetting.get(me); + if (data) { + params.data = data; + } + } + + return axios.post('/api/web/push_subscriptions', params).then(response => response.data); +}; // Last one checks for payload support: https://web-push-book.gauntface.com/chapter-06/01-non-standards-browsers/#no-payload const supportsPushNotifications = ('serviceWorker' in navigator && 'PushManager' in window && 'getKey' in PushSubscription.prototype); export function register () { store.dispatch(setBrowserSupport(supportsPushNotifications)); + const me = store.getState().getIn(['meta', 'me']); + + if (me && !pushNotificationsSetting.get(me)) { + const alerts = store.getState().getIn(['push_notifications', 'alerts']); + if (alerts) { + pushNotificationsSetting.set(me, { alerts: alerts }); + } + } if (supportsPushNotifications) { if (!getApplicationServerKey()) { @@ -79,6 +97,9 @@ export function register () { // it means that the backend subscription is valid (and was set during hydration) if (!(subscription instanceof PushSubscription)) { store.dispatch(setSubscription(subscription)); + if (me) { + pushNotificationsSetting.set(me, { alerts: subscription.alerts }); + } } }) .catch(error => { @@ -90,6 +111,9 @@ export function register () { // Clear alerts and hide UI settings store.dispatch(clearSubscription()); + if (me) { + pushNotificationsSetting.remove(me); + } try { getRegistration() diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 17322264e..b5655975a 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -265,198 +265,286 @@ .compose-form { padding: 10px; -} -.compose-form__warning { - color: darken($ui-secondary-color, 65%); - margin-bottom: 15px; - background: $ui-primary-color; - box-shadow: 0 2px 6px rgba($base-shadow-color, 0.3); - padding: 8px 10px; - border-radius: 4px; - font-size: 13px; - font-weight: 400; - - strong { + .compose-form__warning { color: darken($ui-secondary-color, 65%); - font-weight: 500; + margin-bottom: 15px; + background: $ui-primary-color; + box-shadow: 0 2px 6px rgba($base-shadow-color, 0.3); + padding: 8px 10px; + border-radius: 4px; + font-size: 13px; + font-weight: 400; - @each $lang in $cjk-langs { - &:lang(#{$lang}) { - font-weight: 700; + strong { + color: darken($ui-secondary-color, 65%); + font-weight: 500; + + @each $lang in $cjk-langs { + &:lang(#{$lang}) { + font-weight: 700; + } + } + } + + a { + color: darken($ui-primary-color, 33%); + font-weight: 500; + text-decoration: underline; + + &:hover, + &:active, + &:focus { + text-decoration: none; } } } - a { - color: darken($ui-primary-color, 33%); - font-weight: 500; - text-decoration: underline; + .compose-form__autosuggest-wrapper { + position: relative; + + .emoji-picker-dropdown { + position: absolute; + right: 5px; + top: 5px; + } + } + + .autosuggest-textarea, + .spoiler-input { + position: relative; + } + + .autosuggest-textarea__textarea, + .spoiler-input__input { + display: block; + box-sizing: border-box; + width: 100%; + margin: 0; + color: $ui-base-color; + background: $simple-background-color; + padding: 10px; + font-family: inherit; + font-size: 14px; + resize: vertical; + border: 0; + outline: 0; - &:hover, - &:active, &:focus { - text-decoration: none; + outline: 0; + } + + @media screen and (max-width: 600px) { + font-size: 16px; } } -} -.compose-form__modifiers { - color: $ui-base-color; - font-family: inherit; - font-size: 14px; - background: $simple-background-color; - border-radius: 0 0 4px; -} + .spoiler-input__input { + border-radius: 4px; + } -.compose-form__buttons-wrapper { - display: flex; - justify-content: space-between; -} + .autosuggest-textarea__textarea { + min-height: 100px; + border-radius: 4px 4px 0 0; + padding-bottom: 0; + padding-right: 10px + 22px; + resize: none; -.compose-form__buttons { - padding: 10px; - background: darken($simple-background-color, 8%); - box-shadow: inset 0 5px 5px rgba($base-shadow-color, 0.05); - border-radius: 0 0 4px 4px; - display: flex; + @media screen and (max-width: 600px) { + height: 100px !important; // prevent auto-resize textarea + resize: vertical; + } + } - .icon-button { - box-sizing: content-box; - padding: 0 3px; + .autosuggest-textarea__suggestions { + box-sizing: border-box; + display: none; + position: absolute; + top: 100%; + width: 100%; + z-index: 99; + box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4); + background: $ui-secondary-color; + border-radius: 0 0 4px 4px; + color: $ui-base-color; + font-size: 14px; + padding: 6px; + + &.autosuggest-textarea__suggestions--visible { + display: block; + } } -} -.compose-form__upload-button-icon { - line-height: 27px; -} + .autosuggest-textarea__suggestions__item { + padding: 10px; + cursor: pointer; + border-radius: 4px; -.compose-form__sensitive-button { - display: none; + &:hover, + &:focus, + &:active, + &.selected { + background: darken($ui-secondary-color, 10%); + } + } - &.compose-form__sensitive-button--visible { + .autosuggest-account, + .autosuggest-emoji { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + line-height: 18px; + font-size: 14px; + } + + .autosuggest-account-icon, + .autosuggest-emoji img { display: block; + margin-right: 8px; + width: 16px; + height: 16px; } - .compose-form__sensitive-button__icon { - line-height: 27px; + .autosuggest-account .display-name__account { + color: lighten($ui-base-color, 36%); } -} -.compose-form__upload-wrapper { - overflow: hidden; -} + .compose-form__modifiers { + color: $ui-base-color; + font-family: inherit; + font-size: 14px; + background: $simple-background-color; -.compose-form__uploads-wrapper { - display: flex; - flex-direction: row; - padding: 5px; - flex-wrap: wrap; -} + .compose-form__upload-wrapper { + overflow: hidden; + } -.compose-form__upload { - flex: 1 1 0; - min-width: 40%; - margin: 5px; + .compose-form__uploads-wrapper { + display: flex; + flex-direction: row; + padding: 5px; + flex-wrap: wrap; + } - &-description { - position: absolute; - z-index: 2; - bottom: 0; - left: 0; - right: 0; - box-sizing: border-box; - background: linear-gradient(0deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent); - padding: 10px; - opacity: 0; - transition: opacity .1s ease; + .compose-form__upload { + flex: 1 1 0; + min-width: 40%; + margin: 5px; - input { - background: transparent; - color: $ui-secondary-color; - border: 0; - padding: 0; - margin: 0; - width: 100%; - font-family: inherit; - font-size: 14px; - font-weight: 500; + &-description { + position: absolute; + z-index: 2; + bottom: 0; + left: 0; + right: 0; + box-sizing: border-box; + background: linear-gradient(0deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent); + padding: 10px; + opacity: 0; + transition: opacity .1s ease; + + input { + background: transparent; + color: $ui-secondary-color; + border: 0; + padding: 0; + margin: 0; + width: 100%; + font-family: inherit; + font-size: 14px; + font-weight: 500; + + &:focus { + color: $white; + } - &:focus { - color: $white; + &::placeholder { + opacity: 0.54; + color: $ui-secondary-color; + } + } + + &.active { + opacity: 1; + } } - &::placeholder { - opacity: 0.54; - color: $ui-secondary-color; + .icon-button { + mix-blend-mode: difference; } } - &.active { - opacity: 1; + .compose-form__upload-thumbnail { + border-radius: 4px; + background-position: center; + background-size: cover; + background-repeat: no-repeat; + height: 100px; + width: 100%; } } - .icon-button { - mix-blend-mode: difference; - } -} + .compose-form__buttons-wrapper { + padding: 10px; + background: darken($simple-background-color, 8%); + border-radius: 0 0 4px 4px; + display: flex; + justify-content: space-between; -.compose-form__upload-thumbnail { - border-radius: 4px; - background-position: center; - background-size: cover; - background-repeat: no-repeat; - height: 100px; - width: 100%; -} + .compose-form__buttons { + display: flex; -.compose-form__label { - display: block; - line-height: 24px; - vertical-align: middle; + .compose-form__upload-button-icon { + line-height: 27px; + } - &.with-border { - border-top: 1px solid $ui-base-color; - padding-top: 10px; - } + .compose-form__sensitive-button { + display: none; - .compose-form__label__text { - display: inline-block; - vertical-align: middle; - margin-bottom: 14px; - margin-left: 8px; - color: $ui-primary-color; - } -} + &.compose-form__sensitive-button--visible { + display: block; + } -.compose-form__textarea, -.follow-form__input { - background: $simple-background-color; + .compose-form__sensitive-button__icon { + line-height: 27px; + } + } + } - &:disabled { - background: $ui-secondary-color; - } -} + .icon-button { + box-sizing: content-box; + padding: 0 3px; + } -.compose-form__autosuggest-wrapper { - position: relative; + .character-counter__wrapper { + align-self: center; + margin-right: 4px; - .emoji-picker-dropdown { - position: absolute; - right: 5px; - top: 5px; + .character-counter { + cursor: default; + font-family: 'mastodon-font-sans-serif', sans-serif; + font-size: 14px; + font-weight: 600; + color: lighten($ui-base-color, 12%); + + &.character-counter--over { + color: $warning-red; + } + } + } } -} -.compose-form__publish { - display: flex; - min-width: 0; -} + .compose-form__publish { + display: flex; + justify-content: flex-end; + min-width: 0; -.compose-form__publish-button-wrapper { - overflow: hidden; - padding-top: 10px; + .compose-form__publish-button-wrapper { + overflow: hidden; + padding-top: 10px; + } + } } .emojione { @@ -518,6 +606,7 @@ font-weight: 400; overflow: hidden; white-space: pre-wrap; + padding-top: 2px; &:focus { outline: 0; @@ -616,6 +705,10 @@ .status.status-direct { background: lighten($ui-base-color, 12%); + + &.muted { + background: transparent; + } } .detailed-status, @@ -770,7 +863,7 @@ .status__action-bar { align-items: center; display: flex; - margin-top: 10px; + margin-top: 8px; } .status__action-bar-button { @@ -803,7 +896,7 @@ .emojione { width: 24px; height: 24px; - margin: -3px 0 0; + margin: -1px 0 0; } } @@ -1973,121 +2066,6 @@ cursor: default; } -.autosuggest-textarea, -.spoiler-input { - position: relative; -} - -.autosuggest-textarea__textarea, -.spoiler-input__input { - display: block; - box-sizing: border-box; - width: 100%; - margin: 0; - color: $ui-base-color; - background: $simple-background-color; - padding: 10px; - font-family: inherit; - font-size: 14px; - resize: vertical; - border: 0; - outline: 0; - - &:focus { - outline: 0; - } - - @media screen and (max-width: 600px) { - font-size: 16px; - } -} - -.spoiler-input__input { - border-radius: 4px; -} - -.autosuggest-textarea__textarea { - min-height: 100px; - border-radius: 4px 4px 0 0; - padding-bottom: 0; - padding-right: 10px + 22px; - resize: none; - - @media screen and (max-width: 600px) { - height: 100px !important; // prevent auto-resize textarea - resize: vertical; - } -} - -.autosuggest-textarea__suggestions { - box-sizing: border-box; - display: none; - position: absolute; - top: 100%; - width: 100%; - z-index: 99; - box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4); - background: $ui-secondary-color; - border-radius: 0 0 4px 4px; - color: $ui-base-color; - font-size: 14px; - padding: 6px; - - &.autosuggest-textarea__suggestions--visible { - display: block; - } -} - -.autosuggest-textarea__suggestions__item { - padding: 10px; - cursor: pointer; - border-radius: 4px; - - &:hover, - &:focus, - &:active, - &.selected { - background: darken($ui-secondary-color, 10%); - } -} - -.autosuggest-account, -.autosuggest-emoji { - display: flex; - flex-direction: row; - align-items: center; - justify-content: flex-start; - line-height: 18px; - font-size: 14px; -} - -.autosuggest-account-icon, -.autosuggest-emoji img { - display: block; - margin-right: 8px; - width: 16px; - height: 16px; -} - -.autosuggest-account .display-name__account { - color: lighten($ui-base-color, 36%); -} - -.character-counter__wrapper { - line-height: 36px; - margin: 0 16px 0 8px; - padding-top: 10px; -} - -.character-counter { - cursor: default; - font-size: 16px; -} - -.character-counter--over { - color: $warning-red; -} - .getting-started__wrapper { position: relative; overflow-y: auto; @@ -2126,7 +2104,7 @@ padding: 0 10px 8px; } - code { + kbd { display: inline-block; padding: 3px 5px; background-color: lighten($ui-base-color, 8%); @@ -2273,14 +2251,19 @@ button.icon-button.active i.fa-retweet { .status-card__image-image { border-radius: 4px 4px 0 0; } + + .status-card__title { + white-space: inherit; + } } .status-card__image-image { border-radius: 4px 0 0 4px; display: block; - height: auto; margin: 0; width: 100%; + height: 100%; + object-fit: cover; } .load-more { @@ -3998,6 +3981,7 @@ button.icon-button.active i.fa-retweet { position: relative; background: $base-shadow-color; max-width: 100%; + border-radius: 4px; video { height: 100%; @@ -4032,8 +4016,8 @@ button.icon-button.active i.fa-retweet { left: 0; right: 0; box-sizing: border-box; - background: linear-gradient(0deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 60%, transparent); - padding: 0 10px; + background: linear-gradient(0deg, rgba($base-shadow-color, 0.85) 0, rgba($base-shadow-color, 0.45) 60%, transparent); + padding: 0 15px; opacity: 0; transition: opacity .1s ease; @@ -4086,40 +4070,67 @@ button.icon-button.active i.fa-retweet { } } - &__buttons { + &__buttons-bar { + display: flex; + justify-content: space-between; padding-bottom: 10px; + } + + &__buttons { font-size: 16px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; &.left { - float: left; - button { - padding-right: 10px; + padding-left: 0; } } &.right { - float: right; - button { - padding-left: 10px; + padding-right: 0; } } button { background: transparent; - padding: 0; + padding: 2px 10px; + font-size: 16px; border: 0; - color: $white; + color: rgba($white, 0.75); &:active, &:hover, &:focus { - color: $ui-highlight-color; + color: $white; } } } + &__time-sep, + &__time-total, + &__time-current { + font-size: 14px; + font-weight: 500; + } + + &__time-current { + color: $white; + margin-left: 10px; + } + + &__time-sep { + display: inline-block; + margin: 0 6px; + } + + &__time-sep, + &__time-total { + color: $white; + } + &__seek { cursor: pointer; height: 24px; @@ -4129,6 +4140,7 @@ button.icon-button.active i.fa-retweet { content: ""; width: 100%; background: rgba($white, 0.35); + border-radius: 4px; display: block; position: absolute; height: 4px; @@ -4140,8 +4152,9 @@ button.icon-button.active i.fa-retweet { display: block; position: absolute; height: 4px; + border-radius: 4px; top: 10px; - background: $ui-highlight-color; + background: lighten($ui-highlight-color, 8%); } &__buffer { @@ -4158,7 +4171,8 @@ button.icon-button.active i.fa-retweet { top: 6px; margin-left: -6px; transition: opacity .1s ease; - background: $ui-highlight-color; + background: lighten($ui-highlight-color, 8%); + box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2); pointer-events: none; &.active { @@ -4172,6 +4186,16 @@ button.icon-button.active i.fa-retweet { } } } + + &.detailed, + &.fullscreen { + .video-player__buttons { + button { + padding-top: 10px; + padding-bottom: 10px; + } + } + } } .media-spoiler-video { diff --git a/app/javascript/styles/mastodon/rtl.scss b/app/javascript/styles/mastodon/rtl.scss index 67bfa8a38..77420c84b 100644 --- a/app/javascript/styles/mastodon/rtl.scss +++ b/app/javascript/styles/mastodon/rtl.scss @@ -7,9 +7,9 @@ body.rtl { margin-left: 5px; } - .character-counter__wrapper { - margin-right: 8px; - margin-left: 16px; + .compose-form .compose-form__buttons-wrapper .character-counter__wrapper { + margin-right: 0; + margin-left: 4px; } .navigation-bar__profile { @@ -30,6 +30,22 @@ body.rtl { .column-header__buttons { left: 0; right: auto; + margin-left: -15px; + margin-right: 0; + } + + .column-inline-form .icon-button { + margin-left: 0; + margin-right: 5px; + } + + .column-header__links .text-btn { + margin-left: 10px; + margin-right: 0; + } + + .account__avatar-wrapper { + float: right; } .column-header__back-button { @@ -41,10 +57,6 @@ body.rtl { float: left; } - .compose-form__modifiers { - border-radius: 0 0 0 4px; - } - .setting-toggle { margin-left: 0; margin-right: 8px; diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 31e0abe39..3a985c19b 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -20,11 +20,13 @@ class ActivityPub::Activity::Create < ActivityPub::Activity private def process_status + media_attachments = process_attachments + ApplicationRecord.transaction do @status = Status.create!(status_params) process_tags(@status) - process_attachments(@status) + attach_media(@status, media_attachments) end resolve_thread(@status) @@ -105,22 +107,36 @@ class ActivityPub::Activity::Create < ActivityPub::Activity emoji.save end - def process_attachments(status) + def process_attachments return if @object['attachment'].nil? + media_attachments = [] + as_array(@object['attachment']).each do |attachment| next if unsupported_media_type?(attachment['mediaType']) || attachment['url'].blank? href = Addressable::URI.parse(attachment['url']).normalize.to_s - media_attachment = MediaAttachment.create(status: status, account: status.account, remote_url: href, description: attachment['name'].presence) + media_attachment = MediaAttachment.create(account: @account, remote_url: href, description: attachment['name'].presence) + media_attachments << media_attachment next if skip_download? media_attachment.file_remote_url = href media_attachment.save end + + media_attachments rescue Addressable::URI::InvalidURIError => e Rails.logger.debug e + + media_attachments + end + + def attach_media(status, media_attachments) + return if media_attachments.blank? + + media = MediaAttachment.where(status_id: nil, id: media_attachments.take(4).map(&:id)) + media.update(status_id: status.id) end def resolve_thread(status) diff --git a/app/lib/ostatus/activity/creation.rb b/app/lib/ostatus/activity/creation.rb index 3418e2420..f210e134a 100644 --- a/app/lib/ostatus/activity/creation.rb +++ b/app/lib/ostatus/activity/creation.rb @@ -26,6 +26,8 @@ class OStatus::Activity::Creation < OStatus::Activity::Base cached_reblog = reblog status = nil + media_attachments = save_media + ApplicationRecord.transaction do status = Status.create!( uri: id, @@ -44,7 +46,7 @@ class OStatus::Activity::Creation < OStatus::Activity::Base save_mentions(status) save_hashtags(status) - save_media(status) + attach_media(status, media_attachments) save_emojis(status) end @@ -126,18 +128,20 @@ class OStatus::Activity::Creation < OStatus::Activity::Base ProcessHashtagsService.new.call(parent, tags) end - def save_media(parent) - do_not_download = DomainBlock.find_by(domain: parent.account.domain)&.reject_media? + def save_media + do_not_download = DomainBlock.find_by(domain: @account.domain)&.reject_media? + media_attachments = [] @xml.xpath('./xmlns:link[@rel="enclosure"]', xmlns: OStatus::TagManager::XMLNS).each do |link| next unless link['href'] - media = MediaAttachment.where(status: parent, remote_url: link['href']).first_or_initialize(account: parent.account, status: parent, remote_url: link['href']) + media = MediaAttachment.where(status: nil, remote_url: link['href']).first_or_initialize(account: @account, status: nil, remote_url: link['href']) parsed_url = Addressable::URI.parse(link['href']).normalize next if !%w(http https).include?(parsed_url.scheme) || parsed_url.host.empty? media.save + media_attachments << media next if do_not_download @@ -148,6 +152,15 @@ class OStatus::Activity::Creation < OStatus::Activity::Base next end end + + media_attachments + end + + def attach_media(parent, media_attachments) + return if media_attachments.blank? + + media = MediaAttachment.where(status_id: nil, id: media_attachments.take(4).map(&:id)) + media.update(status_id: parent.id) end def save_emojis(parent) diff --git a/app/lib/provider_discovery.rb b/app/lib/provider_discovery.rb index bcc4ed500..04ba38101 100644 --- a/app/lib/provider_discovery.rb +++ b/app/lib/provider_discovery.rb @@ -2,13 +2,26 @@ class ProviderDiscovery < OEmbed::ProviderDiscovery class << self + def get(url, **options) + provider = discover_provider(url, options) + + options.delete(:html) + + provider.get(url, options) + end + def discover_provider(url, **options) - res = Request.new(:get, url).perform format = options[:format] - raise OEmbed::NotFound, url if res.code != 200 || res.mime_type != 'text/html' + if options[:html] + html = Nokogiri::HTML(options[:html]) + else + res = Request.new(:get, url).perform + + raise OEmbed::NotFound, url if res.code != 200 || res.mime_type != 'text/html' - html = Nokogiri::HTML(res.to_s) + html = Nokogiri::HTML(res.to_s) + end if format.nil? || format == :json provider_endpoint ||= html.at_xpath('//link[@type="application/json+oembed"]')&.attribute('href')&.value diff --git a/app/lib/status_filter.rb b/app/lib/status_filter.rb index 27e1f9d30..a6a050ce1 100644 --- a/app/lib/status_filter.rb +++ b/app/lib/status_filter.rb @@ -35,7 +35,7 @@ class StatusFilter end def silenced_account? - status_account_silenced? && !account_following_status_account? + !account&.silenced? && status_account_silenced? && !account_following_status_account? end def status_account_silenced? diff --git a/app/models/account.rb b/app/models/account.rb index 48b17bbb8..c75ea028e 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -287,6 +287,7 @@ class Account < ApplicationRecord FROM accounts WHERE #{query} @@ #{textsearch} AND accounts.suspended = false + AND accounts.moved_to_account_id IS NULL ORDER BY rank DESC LIMIT ? SQL @@ -312,6 +313,7 @@ class Account < ApplicationRecord WHERE accounts.id IN (SELECT * FROM first_degree) AND #{query} @@ #{textsearch} AND accounts.suspended = false + AND accounts.moved_to_account_id IS NULL GROUP BY accounts.id ORDER BY rank DESC LIMIT ? @@ -327,6 +329,7 @@ class Account < ApplicationRecord LEFT OUTER JOIN follows AS f ON (accounts.id = f.account_id AND f.target_account_id = ?) OR (accounts.id = f.target_account_id AND f.account_id = ?) WHERE #{query} @@ #{textsearch} AND accounts.suspended = false + AND accounts.moved_to_account_id IS NULL GROUP BY accounts.id ORDER BY rank DESC LIMIT ? diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb index 189872368..dc7a03039 100644 --- a/app/models/account_filter.rb +++ b/app/models/account_filter.rb @@ -45,6 +45,8 @@ class AccountFilter else Account.default_scoped end + when 'staff' + accounts_with_users.merge User.staff else raise "Unknown filter: #{key}" end diff --git a/app/models/custom_emoji_filter.rb b/app/models/custom_emoji_filter.rb index 2d1394a59..2c09ed65c 100644 --- a/app/models/custom_emoji_filter.rb +++ b/app/models/custom_emoji_filter.rb @@ -27,6 +27,8 @@ class CustomEmojiFilter CustomEmoji.remote when 'by_domain' CustomEmoji.where(domain: value) + when 'shortcode' + CustomEmoji.where(shortcode: value) else raise "Unknown filter: #{key}" end diff --git a/app/models/list.rb b/app/models/list.rb index 910864b26..be85c3b87 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -4,7 +4,7 @@ # Table name: lists # # id :integer not null, primary key -# account_id :integer +# account_id :integer not null # title :string default(""), not null # created_at :datetime not null # updated_at :datetime not null @@ -13,6 +13,8 @@ class List < ApplicationRecord include Paginable + PER_ACCOUNT_LIMIT = 50 + belongs_to :account has_many :list_accounts, inverse_of: :list, dependent: :destroy @@ -20,6 +22,10 @@ class List < ApplicationRecord validates :title, presence: true + validates_each :account_id, on: :create do |record, _attr, value| + record.errors.add(:base, I18n.t('lists.errors.limit')) if List.where(account_id: value).count >= PER_ACCOUNT_LIMIT + end + before_destroy :clean_feed_manager private diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb index 5baddba8a..716b82243 100644 --- a/app/models/preview_card.rb +++ b/app/models/preview_card.rb @@ -33,7 +33,7 @@ class PreviewCard < ApplicationRecord has_and_belongs_to_many :statuses - has_attached_file :image, styles: { original: '280x280>' }, convert_options: { all: '-quality 80 -strip' } + has_attached_file :image, styles: { original: '400x400>' }, convert_options: { all: '-quality 80 -strip' } include Attachmentable include Remotable diff --git a/app/models/status.rb b/app/models/status.rb index 70cfdc1c7..db3072571 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -23,6 +23,7 @@ # account_id :integer not null # application_id :integer # in_reply_to_account_id :integer +# local_only :boolean # class Status < ApplicationRecord @@ -74,6 +75,8 @@ class Status < ApplicationRecord scope :not_excluded_by_account, ->(account) { where.not(account_id: account.excluded_from_timeline_account_ids) } scope :not_domain_blocked_by_account, ->(account) { account.excluded_from_timeline_domains.blank? ? left_outer_joins(:account) : left_outer_joins(:account).where('accounts.domain IS NULL OR accounts.domain NOT IN (?)', account.excluded_from_timeline_domains) } + scope :not_local_only, -> { where(local_only: [false, nil]) } + cache_associated :account, :application, :media_attachments, :tags, :stream_entry, mentions: :account, reblog: [:account, :application, :stream_entry, :tags, :media_attachments, mentions: :account], thread: :account delegate :domain, to: :account, prefix: true @@ -138,6 +141,8 @@ class Status < ApplicationRecord around_create Mastodon::Snowflake::Callbacks + before_create :set_locality + before_validation :prepare_contents, if: :local? before_validation :set_reblog before_validation :set_visibility @@ -218,7 +223,7 @@ class Status < ApplicationRecord visibility = [:public, :unlisted] if account.nil? - where(visibility: visibility) + where(visibility: visibility).not_local_only elsif target_account.blocking?(account) # get rid of blocked peeps none elsif account.id == target_account.id # author can see own stuff @@ -257,7 +262,7 @@ class Status < ApplicationRecord end def filter_timeline_default(query) - query.excluding_silenced_accounts + query.not_local_only.excluding_silenced_accounts end def account_silencing_filter(account) @@ -269,9 +274,13 @@ class Status < ApplicationRecord end end - def local_only? + def marked_local_only? # match both with and without U+FE0F (the emoji variation selector) - /👁\ufe0f?\z/.match?(content) + /#{local_only_emoji}\ufe0f?\z/.match?(content) + end + + def local_only_emoji + '👁' end private @@ -299,6 +308,12 @@ class Status < ApplicationRecord self.sensitive = sensitive || spoiler_text.present? end + def set_locality + if account.domain.nil? && !attribute_changed?(:local_only) + self.local_only = marked_local_only? + end + end + def set_conversation self.reply = !(in_reply_to_id.nil? && thread.nil?) unless reply diff --git a/app/models/tag.rb b/app/models/tag.rb index 0fa08e157..dc2c8d129 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -23,7 +23,7 @@ class Tag < ApplicationRecord class << self def search_for(term, limit = 5) - pattern = sanitize_sql_like(term) + '%' + pattern = sanitize_sql_like(term.strip) + '%' Tag.where('lower(name) like lower(?)', pattern).order(:name).limit(limit) end end diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb index bab944c5a..19b746520 100644 --- a/app/serializers/rest/account_serializer.rb +++ b/app/serializers/rest/account_serializer.rb @@ -7,9 +7,7 @@ class REST::AccountSerializer < ActiveModel::Serializer :note, :url, :avatar, :avatar_static, :header, :header_static, :followers_count, :following_count, :statuses_count - has_one :moved_to_account, key: :moved, serializer: REST::AccountSerializer, if: :moved? - - delegate :moved?, to: :object + has_one :moved_to_account, key: :moved, serializer: REST::AccountSerializer, if: :moved_and_not_nested? def id object.id.to_s @@ -38,4 +36,8 @@ class REST::AccountSerializer < ActiveModel::Serializer def header_static full_asset_url(object.header_static_url) end + + def moved_and_not_nested? + object.moved? && object.moved_to_account.moved_to_account_id.nil? + end end diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb index a289ceac4..3be110665 100644 --- a/app/services/account_search_service.rb +++ b/app/services/account_search_service.rb @@ -4,7 +4,7 @@ class AccountSearchService < BaseService attr_reader :query, :limit, :options, :account def call(query, limit, account = nil, options = {}) - @query = query + @query = query.strip @limit = limit @options = options @account = account diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb index cec96d927..d0472a1d7 100644 --- a/app/services/fetch_link_card_service.rb +++ b/app/services/fetch_link_card_service.rb @@ -38,7 +38,13 @@ class FetchLinkCardService < BaseService @card ||= PreviewCard.new(url: @url) res = Request.new(:head, @url).perform - return if res.code != 200 || res.mime_type != 'text/html' + return if res.code != 405 && (res.code != 200 || res.mime_type != 'text/html') + + @response = Request.new(:get, @url).perform + + return if @response.code != 200 || @response.mime_type != 'text/html' + + @html = @response.to_s attempt_oembed || attempt_opengraph end @@ -70,30 +76,32 @@ class FetchLinkCardService < BaseService end def attempt_oembed - response = OEmbed::Providers.get(@url) + embed = OEmbed::Providers.get(@url, html: @html) - return false unless response.respond_to?(:type) + return false unless embed.respond_to?(:type) - @card.type = response.type - @card.title = response.respond_to?(:title) ? response.title : '' - @card.author_name = response.respond_to?(:author_name) ? response.author_name : '' - @card.author_url = response.respond_to?(:author_url) ? response.author_url : '' - @card.provider_name = response.respond_to?(:provider_name) ? response.provider_name : '' - @card.provider_url = response.respond_to?(:provider_url) ? response.provider_url : '' + @card.type = embed.type + @card.title = embed.respond_to?(:title) ? embed.title : '' + @card.author_name = embed.respond_to?(:author_name) ? embed.author_name : '' + @card.author_url = embed.respond_to?(:author_url) ? embed.author_url : '' + @card.provider_name = embed.respond_to?(:provider_name) ? embed.provider_name : '' + @card.provider_url = embed.respond_to?(:provider_url) ? embed.provider_url : '' @card.width = 0 @card.height = 0 case @card.type when 'link' - @card.image = URI.parse(response.thumbnail_url) if response.respond_to?(:thumbnail_url) + @card.image = URI.parse(embed.thumbnail_url) if embed.respond_to?(:thumbnail_url) when 'photo' - @card.embed_url = response.url - @card.width = response.width.presence || 0 - @card.height = response.height.presence || 0 + return false unless embed.respond_to?(:url) + @card.embed_url = embed.url + @card.image = URI.parse(embed.url) + @card.width = embed.width.presence || 0 + @card.height = embed.height.presence || 0 when 'video' - @card.width = response.width.presence || 0 - @card.height = response.height.presence || 0 - @card.html = Formatter.instance.sanitize(response.html, Sanitize::Config::MASTODON_OEMBED) + @card.width = embed.width.presence || 0 + @card.height = embed.height.presence || 0 + @card.html = Formatter.instance.sanitize(embed.html, Sanitize::Config::MASTODON_OEMBED) when 'rich' # Most providers rely on <script> tags, which is a no-no return false @@ -105,17 +113,11 @@ class FetchLinkCardService < BaseService end def attempt_opengraph - response = Request.new(:get, @url).perform - - return if response.code != 200 || response.mime_type != 'text/html' - - html = response.to_s - detector = CharlockHolmes::EncodingDetector.new detector.strip_tags = true - guess = detector.detect(html, response.charset) - page = Nokogiri::HTML(html, nil, guess&.fetch(:encoding, nil)) + guess = detector.detect(@html, @response.charset) + page = Nokogiri::HTML(@html, nil, guess&.fetch(:encoding, nil)) if meta_property(page, 'twitter:player') @card.type = :video @@ -132,16 +134,16 @@ class FetchLinkCardService < BaseService @card.image_remote_url = meta_property(page, 'og:image') if meta_property(page, 'og:image') end - @card.title = meta_property(page, 'og:title').presence || page.at_xpath('//title')&.content || '' - @card.description = meta_property(page, 'og:description').presence || meta_property(page, 'description') || '' + @card.title = meta_property(page, 'og:title').presence || page.at_xpath('//title')&.content || '' + @card.description = meta_property(page, 'og:description').presence || meta_property(page, 'description') || '' return if @card.title.blank? && @card.html.blank? @card.save_with_optional_image! end - def meta_property(html, property) - html.at_xpath("//meta[@property=\"#{property}\"]")&.attribute('content')&.value || html.at_xpath("//meta[@name=\"#{property}\"]")&.attribute('content')&.value + def meta_property(page, property) + page.at_xpath("//meta[@property=\"#{property}\"]")&.attribute('content')&.value || page.at_xpath("//meta[@name=\"#{property}\"]")&.attribute('content')&.value end def lock_options diff --git a/app/services/fetch_remote_status_service.rb b/app/services/fetch_remote_status_service.rb index 9c009335b..9c3008035 100644 --- a/app/services/fetch_remote_status_service.rb +++ b/app/services/fetch_remote_status_service.rb @@ -40,6 +40,6 @@ class FetchRemoteStatusService < BaseService end def confirmed_domain?(domain, account) - account.domain.nil? || domain.casecmp(account.domain).zero? || domain.casecmp(Addressable::URI.parse(account.remote_url || account.uri).normalized_host).zero? + account.domain.nil? || domain.casecmp(account.domain).zero? || domain.casecmp(Addressable::URI.parse(account.remote_url.presence || account.uri).normalized_host).zero? end end diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb index 20579ca63..ac0207a0a 100644 --- a/app/services/follow_service.rb +++ b/app/services/follow_service.rb @@ -22,7 +22,7 @@ class FollowService < BaseService elsif source_account.requested?(target_account) # This isn't managed by a method in AccountInteractions, so we modify it # ourselves if necessary. - req = follow_requests.find_by(target_account: other_account) + req = source_account.follow_requests.find_by(target_account: target_account) req.update!(show_reblogs: reblogs) return end diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 59531a76c..6b6a37676 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -40,8 +40,7 @@ class PostStatusService < BaseService LinkCrawlWorker.perform_async(status.id) unless status.spoiler_text? DistributionWorker.perform_async(status.id) - # match both with and without U+FE0F (the emoji variation selector) - unless /👁\ufe0f?\z/.match?(status.content) + unless status.local_only? Pubsubhubbub::DistributionWorker.perform_async(status.stream_entry.id) ActivityPub::DistributionWorker.perform_async(status.id) ActivityPub::ReplyDistributionWorker.perform_async(status.id) if status.reply? && status.thread.account.local? diff --git a/app/services/reblog_service.rb b/app/services/reblog_service.rb index 52e3ba0e0..8d8b15a41 100644 --- a/app/services/reblog_service.rb +++ b/app/services/reblog_service.rb @@ -21,7 +21,7 @@ class ReblogService < BaseService DistributionWorker.perform_async(reblog.id) - unless /👁$/.match?(reblogged_status.content) + unless reblogged_status.local_only? Pubsubhubbub::DistributionWorker.perform_async(reblog.stream_entry.id) ActivityPub::DistributionWorker.perform_async(reblog.id) end diff --git a/app/services/resolve_remote_account_service.rb b/app/services/resolve_remote_account_service.rb index 3293fe40f..d7d0be210 100644 --- a/app/services/resolve_remote_account_service.rb +++ b/app/services/resolve_remote_account_service.rb @@ -44,7 +44,7 @@ class ResolveRemoteAccountService < BaseService if lock.acquired? @account = Account.find_remote(@username, @domain) - if activitypub_ready? + if activitypub_ready? || @account&.activitypub? handle_activitypub else handle_ostatus diff --git a/app/validators/status_pin_validator.rb b/app/validators/status_pin_validator.rb index 9760e1138..64da04120 100644 --- a/app/validators/status_pin_validator.rb +++ b/app/validators/status_pin_validator.rb @@ -2,9 +2,9 @@ class StatusPinValidator < ActiveModel::Validator def validate(pin) - pin.errors.add(:status, I18n.t('statuses.pin_errors.reblog')) if pin.status.reblog? - pin.errors.add(:status, I18n.t('statuses.pin_errors.ownership')) if pin.account_id != pin.status.account_id - pin.errors.add(:status, I18n.t('statuses.pin_errors.private')) unless %w(public unlisted).include?(pin.status.visibility) - pin.errors.add(:status, I18n.t('statuses.pin_errors.limit')) if pin.account.status_pins.count > 4 + pin.errors.add(:base, I18n.t('statuses.pin_errors.reblog')) if pin.status.reblog? + pin.errors.add(:base, I18n.t('statuses.pin_errors.ownership')) if pin.account_id != pin.status.account_id + pin.errors.add(:base, I18n.t('statuses.pin_errors.private')) unless %w(public unlisted).include?(pin.status.visibility) + pin.errors.add(:base, I18n.t('statuses.pin_errors.limit')) if pin.account.status_pins.count > 4 end end diff --git a/app/views/admin/accounts/_account.html.haml b/app/views/admin/accounts/_account.html.haml index 5265d77f6..598f6cddd 100644 --- a/app/views/admin/accounts/_account.html.haml +++ b/app/views/admin/accounts/_account.html.haml @@ -4,22 +4,11 @@ %td.domain - unless account.local? = link_to account.domain, admin_accounts_path(by_domain: account.domain) - %td.protocol - - unless account.local? - = account.protocol.humanize - %td.confirmed - - if account.local? - - if account.user_confirmed? - %i.fa.fa-check - - else - %i.fa.fa-times - %td.subscribed + %td - if account.local? - = t('admin.accounts.location.local') - - elsif account.subscribed? - %i.fa.fa-check + = t("admin.accounts.roles.#{account.user&.role}") - else - %i.fa.fa-times + = account.protocol.humanize %td = table_link_to 'circle', t('admin.accounts.web'), web_path("accounts/#{account.id}") = table_link_to 'globe', t('admin.accounts.public'), TagManager.instance.url_for(account) diff --git a/app/views/admin/accounts/index.html.haml b/app/views/admin/accounts/index.html.haml index 27a0682d8..6aa39a80a 100644 --- a/app/views/admin/accounts/index.html.haml +++ b/app/views/admin/accounts/index.html.haml @@ -31,6 +31,11 @@ - else = filter_link_to t('admin.accounts.moderation.suspended'), suspended: '1' .filter-subset + %strong= t('admin.accounts.role') + %ul + %li= filter_link_to t('admin.accounts.moderation.all'), staff: nil + %li= filter_link_to t('admin.accounts.roles.staff'), staff: '1' + .filter-subset %strong= t('admin.accounts.order.title') %ul %li= filter_link_to t('admin.accounts.order.alphabetic'), recent: nil @@ -56,9 +61,7 @@ %tr %th= t('admin.accounts.username') %th= t('admin.accounts.domain') - %th= t('admin.accounts.protocol') - %th= t('admin.accounts.confirmed') - %th= fa_icon 'paper-plane-o' + %th %th %tbody = render @accounts diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index ddb1cf15d..5f5d0995c 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -104,7 +104,7 @@ - else = link_to t('admin.accounts.perform_full_suspension'), admin_account_suspension_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:suspend, @account) -- unless @account.local? +- if !@account.local? && @account.hub_url.present? %hr %h3 OStatus @@ -132,6 +132,7 @@ - if @account.subscribed? = link_to t('admin.accounts.unsubscribe'), unsubscribe_admin_account_path(@account.id), method: :post, class: 'button negative' if can?(:unsubscribe, @account) +- if !@account.local? && @account.inbox_url.present? %hr %h3 ActivityPub diff --git a/app/views/admin/custom_emojis/_custom_emoji.html.haml b/app/views/admin/custom_emojis/_custom_emoji.html.haml index bab34bc8d..f7fd2538c 100644 --- a/app/views/admin/custom_emojis/_custom_emoji.html.haml +++ b/app/views/admin/custom_emojis/_custom_emoji.html.haml @@ -7,7 +7,7 @@ - if custom_emoji.local? = t('admin.accounts.location.local') - else - = custom_emoji.domain + = link_to custom_emoji.domain, admin_custom_emojis_path(by_domain: custom_emoji.domain) %td - if custom_emoji.local? - if custom_emoji.visible_in_picker diff --git a/app/views/admin/custom_emojis/index.html.haml b/app/views/admin/custom_emojis/index.html.haml index 20ffb8529..89ea3a6fe 100644 --- a/app/views/admin/custom_emojis/index.html.haml +++ b/app/views/admin/custom_emojis/index.html.haml @@ -17,6 +17,20 @@ - else = filter_link_to t('admin.accounts.location.remote'), remote: '1', local: nil += form_tag admin_custom_emojis_url, method: 'GET', class: 'simple_form' do + .fields-group + - Admin::FilterHelper::CUSTOM_EMOJI_FILTERS.each do |key| + - if params[key].present? + = hidden_field_tag key, params[key] + + - %i(shortcode by_domain).each do |key| + .input.string.optional + = text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.custom_emojis.#{key}") + + .actions + %button= t('admin.accounts.search') + = link_to t('admin.accounts.reset'), admin_accounts_path, class: 'button negative' + .table-wrapper %table.table %thead diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml index b488bd9ba..d88ec8280 100644 --- a/app/views/stream_entries/_detailed_status.html.haml +++ b/app/views/stream_entries/_detailed_status.html.haml @@ -17,16 +17,16 @@ %p{ style: 'margin-bottom: 0' }< %span.p-summary> #{Formatter.instance.format_spoiler(status)} %a.status__content__spoiler-link{ href: '#' }= t('statuses.show_more') - .e-content{ lang: status.language, style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }< - = Formatter.instance.format(status, custom_emojify: true) - - if !status.media_attachments.empty? - - if status.media_attachments.first.video? - - video = status.media_attachments.first - %div{ data: { component: 'Video', props: Oj.dump(src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive?, width: 670, height: 380) }}< - - else - %div{ data: { component: 'MediaGallery', props: Oj.dump(height: 380, sensitive: status.sensitive?, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, 'reduceMotion': current_account&.user&.setting_reduce_motion, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }) }}< - - elsif status.preview_cards.first - %div{ data: { component: 'Card', props: Oj.dump('maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_cards.first, serializer: REST::PreviewCardSerializer).as_json) }}< + .e-content{ lang: status.language, style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }= Formatter.instance.format(status, custom_emojify: true) + + - if !status.media_attachments.empty? + - if status.media_attachments.first.video? + - video = status.media_attachments.first + %div{ data: { component: 'Video', props: Oj.dump(src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive?, width: 670, height: 380, detailed: true) }}< + - else + %div{ data: { component: 'MediaGallery', props: Oj.dump(height: 380, sensitive: status.sensitive?, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, 'reduceMotion': current_account&.user&.setting_reduce_motion, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }) }}< + - elsif status.preview_cards.first + %div{ data: { component: 'Card', props: Oj.dump('maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_cards.first, serializer: REST::PreviewCardSerializer).as_json) }} .detailed-status__meta %data.dt-published{ value: status.created_at.to_time.iso8601 } diff --git a/app/views/stream_entries/show.html.haml b/app/views/stream_entries/show.html.haml index 895a61247..b52334a28 100644 --- a/app/views/stream_entries/show.html.haml +++ b/app/views/stream_entries/show.html.haml @@ -1,3 +1,6 @@ +- content_for :page_title do + = t('statuses.title', name: display_name(@account), quote: truncate(@stream_entry.activity.text, length: 50, omission: '…')) + - content_for :header_tags do - if @account.user&.setting_noindex %meta{ name: 'robots', content: 'noindex' }/ diff --git a/app/views/tags/show.html.haml b/app/views/tags/show.html.haml index e05fe1c39..03f19e20a 100644 --- a/app/views/tags/show.html.haml +++ b/app/views/tags/show.html.haml @@ -19,8 +19,11 @@ %p= t 'about.about_hashtag_html', hashtag: @tag.name .cta - = link_to t('auth.login'), new_user_session_path, class: 'button button-secondary' - = link_to t('about.learn_more'), root_url, class: 'button button-alternative' + - if user_signed_in? + = link_to t('settings.back'), root_path, class: 'button button-secondary' + - else + = link_to t('auth.login'), new_user_session_path, class: 'button button-secondary' + = link_to t('about.learn_more'), about_path, class: 'button button-alternative' .features-list .features-list__row |