This commit is contained in:
Essem 2023-03-15 12:55:46 -05:00
commit eeee52b6c8
No known key found for this signature in database
GPG key ID: 7D497397CC3A2A8C
332 changed files with 4737 additions and 1295 deletions

View file

@ -1,16 +1,14 @@
# [Choice] Ruby version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.1, 3.0, 2, 2.7, 2.6, 3-bullseye, 3.1-bullseye, 3.0-bullseye, 2-bullseye, 2.7-bullseye, 2.6-bullseye, 3-buster, 3.1-buster, 3.0-buster, 2-buster, 2.7-buster, 2.6-buster
ARG VARIANT=3.1-bullseye
FROM mcr.microsoft.com/vscode/devcontainers/ruby:${VARIANT}
# For details, see https://github.com/devcontainers/images/tree/main/src/ruby
FROM mcr.microsoft.com/devcontainers/ruby:0-3.2-bullseye
# Install Rails
# RUN gem install rails webdrivers
# Default value to allow debug server to serve content over GitHub Codespace's port forwarding service
# The value is a comma-separated list of allowed domains
ENV RAILS_DEVELOPMENT_HOSTS=".githubpreview.dev"
ENV RAILS_DEVELOPMENT_HOSTS=".githubpreview.dev,.preview.app.github.dev,.app.github.dev"
# [Choice] Node.js version: lts/*, 18, 16, 14
ARG NODE_VERSION="lts/*"
ARG NODE_VERSION="16"
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"
# [Optional] Uncomment this section to install additional OS packages.
@ -22,3 +20,5 @@ RUN gem install foreman
# [Optional] Uncomment this line to install global node packages.
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g yarn" 2>&1
COPY welcome-message.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt

View file

@ -1,30 +1,13 @@
// For more details, see https://aka.ms/devcontainer.json.
{
"name": "Mastodon",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/mastodon",
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"EditorConfig.EditorConfig",
"dbaeumer.vscode-eslint",
"rebornix.Ruby",
"webben.browserslist"
]
}
},
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
"ghcr.io/devcontainers/features/sshd:1": {
"version": "latest"
}
"ghcr.io/devcontainers/features/sshd:1": {}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
@ -33,7 +16,16 @@
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": ".devcontainer/post-create.sh",
"waitFor": "postCreateCommand",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {},
// Add the IDs of extensions you want installed when the container is created.
"extensions": ["EditorConfig.EditorConfig", "webben.browserslist"]
}
}
}

View file

@ -5,15 +5,8 @@ services:
build:
context: .
dockerfile: Dockerfile
args:
# Update 'VARIANT' to pick a version of Ruby: 3, 3.1, 3.0, 2, 2.7, 2.6
# Append -bullseye or -buster to pin to an OS version.
# Use -bullseye variants on local arm64/Apple Silicon.
VARIANT: '3.0-bullseye'
# Optional Node.js version to install
NODE_VERSION: '16'
volumes:
- ..:/mastodon:cached
- ../..:/workspaces:cached
environment:
RAILS_ENV: development
NODE_ENV: development
@ -33,7 +26,6 @@ services:
networks:
- external_network
- internal_network
user: vscode
db:
image: postgres:14-alpine

View file

@ -0,0 +1,8 @@
👋 Welcome to "Mastodon" in GitHub Codespaces!
🛠️ Your environment is fully setup with all the required software.
🔍 To explore VS Code to its fullest, search using the Command Palette (Cmd/Ctrl + Shift + P or F1).
📝 Edit away, run your app as usual, and we'll automatically make it available for you to access.

View file

@ -19,7 +19,6 @@ AllCops:
NewCops: enable
Exclude:
- db/schema.rb
- 'app/views/**/*'
- 'config/**/*'
- 'bin/*'
- 'Rakefile'
@ -97,6 +96,10 @@ Rails/Exit:
- 'lib/mastodon/cli_helper.rb'
- 'lib/cli.rb'
RSpec/FilePath:
CustomTransform:
DeepL: deepl
RSpec/NotToNot:
EnforcedStyle: to_not
@ -123,3 +126,6 @@ Style/TrailingCommaInArrayLiteral:
Style/TrailingCommaInHashLiteral:
EnforcedStyleForMultiline: 'comma'
Style/SymbolArray:
Enabled: false

View file

@ -2056,13 +2056,6 @@ Style/HashAsLastArrayItem:
- 'app/services/notify_service.rb'
- 'db/migrate/20181024224956_migrate_account_conversations.rb'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowSplatArgument.
Style/HashConversion:
Exclude:
- 'app/services/import_service.rb'
# Offense count: 12
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle, EnforcedShorthandSyntax, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols.
@ -2242,134 +2235,3 @@ Style/SlicingWithRange:
- 'lib/active_record/batches.rb'
- 'lib/mastodon/premailer_webpack_strategy.rb'
- 'lib/tasks/repo.rake'
# Offense count: 272
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle, MinSize.
# SupportedStyles: percent, brackets
Style/SymbolArray:
Exclude:
- 'app/controllers/accounts_controller.rb'
- 'app/controllers/activitypub/replies_controller.rb'
- 'app/controllers/admin/accounts_controller.rb'
- 'app/controllers/admin/announcements_controller.rb'
- 'app/controllers/admin/domain_blocks_controller.rb'
- 'app/controllers/admin/email_domain_blocks_controller.rb'
- 'app/controllers/admin/relationships_controller.rb'
- 'app/controllers/admin/relays_controller.rb'
- 'app/controllers/admin/roles_controller.rb'
- 'app/controllers/admin/rules_controller.rb'
- 'app/controllers/admin/statuses_controller.rb'
- 'app/controllers/admin/trends/statuses_controller.rb'
- 'app/controllers/admin/warning_presets_controller.rb'
- 'app/controllers/admin/webhooks_controller.rb'
- 'app/controllers/api/v1/accounts/credentials_controller.rb'
- 'app/controllers/api/v1/accounts_controller.rb'
- 'app/controllers/api/v1/admin/accounts_controller.rb'
- 'app/controllers/api/v1/admin/canonical_email_blocks_controller.rb'
- 'app/controllers/api/v1/admin/domain_allows_controller.rb'
- 'app/controllers/api/v1/admin/domain_blocks_controller.rb'
- 'app/controllers/api/v1/admin/email_domain_blocks_controller.rb'
- 'app/controllers/api/v1/admin/ip_blocks_controller.rb'
- 'app/controllers/api/v1/admin/reports_controller.rb'
- 'app/controllers/api/v1/crypto/deliveries_controller.rb'
- 'app/controllers/api/v1/crypto/keys/claims_controller.rb'
- 'app/controllers/api/v1/crypto/keys/uploads_controller.rb'
- 'app/controllers/api/v1/featured_tags_controller.rb'
- 'app/controllers/api/v1/filters_controller.rb'
- 'app/controllers/api/v1/lists_controller.rb'
- 'app/controllers/api/v1/notifications_controller.rb'
- 'app/controllers/api/v1/push/subscriptions_controller.rb'
- 'app/controllers/api/v1/scheduled_statuses_controller.rb'
- 'app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb'
- 'app/controllers/api/v1/statuses_controller.rb'
- 'app/controllers/api/v2/filters/keywords_controller.rb'
- 'app/controllers/api/v2/filters/statuses_controller.rb'
- 'app/controllers/api/v2/filters_controller.rb'
- 'app/controllers/api/web/push_subscriptions_controller.rb'
- 'app/controllers/application_controller.rb'
- 'app/controllers/auth/registrations_controller.rb'
- 'app/controllers/filters_controller.rb'
- 'app/controllers/settings/applications_controller.rb'
- 'app/controllers/settings/featured_tags_controller.rb'
- 'app/controllers/settings/profiles_controller.rb'
- 'app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb'
- 'app/controllers/statuses_controller.rb'
- 'app/lib/feed_manager.rb'
- 'app/models/account.rb'
- 'app/models/account_filter.rb'
- 'app/models/admin/status_filter.rb'
- 'app/models/announcement.rb'
- 'app/models/concerns/ldap_authenticable.rb'
- 'app/models/concerns/status_threading_concern.rb'
- 'app/models/custom_filter.rb'
- 'app/models/domain_block.rb'
- 'app/models/import.rb'
- 'app/models/list.rb'
- 'app/models/media_attachment.rb'
- 'app/models/preview_card.rb'
- 'app/models/relay.rb'
- 'app/models/report.rb'
- 'app/models/site_upload.rb'
- 'app/models/status.rb'
- 'app/serializers/initial_state_serializer.rb'
- 'app/serializers/rest/notification_serializer.rb'
- 'db/migrate/20160220174730_create_accounts.rb'
- 'db/migrate/20160221003621_create_follows.rb'
- 'db/migrate/20160223171800_create_favourites.rb'
- 'db/migrate/20160224223247_create_mentions.rb'
- 'db/migrate/20160314164231_add_owner_to_application.rb'
- 'db/migrate/20160316103650_add_missing_indices.rb'
- 'db/migrate/20160926213048_remove_owner_from_application.rb'
- 'db/migrate/20161003145426_create_blocks.rb'
- 'db/migrate/20161006213403_rails_settings_migration.rb'
- 'db/migrate/20161105130633_create_statuses_tags_join_table.rb'
- 'db/migrate/20161119211120_create_notifications.rb'
- 'db/migrate/20161128103007_create_subscriptions.rb'
- 'db/migrate/20161222204147_create_follow_requests.rb'
- 'db/migrate/20170112154826_migrate_settings.rb'
- 'db/migrate/20170301222600_create_mutes.rb'
- 'db/migrate/20170406215816_add_notifications_and_favourites_indices.rb'
- 'db/migrate/20170424003227_create_account_domain_blocks.rb'
- 'db/migrate/20170427011934_re_add_owner_to_application.rb'
- 'db/migrate/20170507141759_optimize_index_subscriptions.rb'
- 'db/migrate/20170508230434_create_conversation_mutes.rb'
- 'db/migrate/20170720000000_add_index_favourites_on_account_id_and_id.rb'
- 'db/migrate/20170823162448_create_status_pins.rb'
- 'db/migrate/20170901142658_create_join_table_preview_cards_statuses.rb'
- 'db/migrate/20170905044538_add_index_id_account_id_activity_type_on_notifications.rb'
- 'db/migrate/20170917153509_create_custom_emojis.rb'
- 'db/migrate/20170918125918_ids_to_bigints.rb'
- 'db/migrate/20171116161857_create_list_accounts.rb'
- 'db/migrate/20171122120436_add_index_account_and_reblog_of_id_to_statuses.rb'
- 'db/migrate/20171125185353_add_index_reblog_of_id_and_account_to_statuses.rb'
- 'db/migrate/20171125190735_remove_old_reblog_index_on_statuses.rb'
- 'db/migrate/20171129172043_add_index_on_stream_entries.rb'
- 'db/migrate/20171226094803_more_faster_index_on_notifications.rb'
- 'db/migrate/20180106000232_add_index_on_statuses_for_api_v1_accounts_account_id_statuses.rb'
- 'db/migrate/20180514140000_revert_index_change_on_statuses_for_api_v1_accounts_account_id_statuses.rb'
- 'db/migrate/20180808175627_create_account_pins.rb'
- 'db/migrate/20180831171112_create_bookmarks.rb'
- 'db/migrate/20180929222014_create_account_conversations.rb'
- 'db/migrate/20181007025445_create_pghero_space_stats.rb'
- 'db/migrate/20181203003808_create_accounts_tags_join_table.rb'
- 'db/migrate/20190316190352_create_account_identity_proofs.rb'
- 'db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb'
- 'db/migrate/20190820003045_update_statuses_index.rb'
- 'db/migrate/20190823221802_add_local_index_to_statuses.rb'
- 'db/migrate/20190904222339_create_markers.rb'
- 'db/migrate/20200113125135_create_announcement_mutes.rb'
- 'db/migrate/20200114113335_create_announcement_reactions.rb'
- 'db/migrate/20200119112504_add_public_index_to_statuses.rb'
- 'db/migrate/20200628133322_create_account_notes.rb'
- 'db/migrate/20200917222316_add_index_notifications_on_type.rb'
- 'db/migrate/20210425135952_add_index_on_media_attachments_account_id_status_id.rb'
- 'db/migrate/20220714171049_create_tag_follows.rb'
- 'db/migrate/20221021055441_add_index_featured_tags_on_account_id_and_tag_id.rb'
- 'db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb'
- 'db/post_migrate/20200917222734_remove_index_notifications_on_account_activity.rb'
- 'spec/controllers/api/v1/streaming_controller_spec.rb'
- 'spec/controllers/api/v2/admin/accounts_controller_spec.rb'
- 'spec/controllers/concerns/signature_verification_spec.rb'
- 'spec/fabricators/notification_fabricator.rb'
- 'spec/models/public_feed_spec.rb'

View file

@ -6,7 +6,7 @@ ruby '>= 2.7.0', '< 3.3.0'
gem 'pkg-config', '~> 1.5'
gem 'rexml', '~> 3.2'
gem 'puma', '~> 5.6'
gem 'puma', '~> 6.1'
gem 'rails', '~> 6.1.7'
gem 'sprockets', '~> 3.7.2'
gem 'thor', '~> 1.2'
@ -104,9 +104,7 @@ group :development, :test do
gem 'fabrication', '~> 2.30'
gem 'fuubar', '~> 2.5'
gem 'i18n-tasks', '~> 1.0', require: false
gem 'pry-byebug', '~> 3.10'
gem 'pry-rails', '~> 0.3'
gem 'rspec-rails', '~> 5.1'
gem 'rspec-rails', '~> 6.0'
gem 'rubocop-performance', require: false
gem 'rubocop-rails', require: false
gem 'rubocop-rspec', require: false
@ -119,7 +117,6 @@ end
group :test do
gem 'capybara', '~> 3.38'
gem 'climate_control', '~> 0.2'
gem 'faker', '~> 3.1'
gem 'json-schema', '~> 3.0'
gem 'rack-test', '~> 2.0'

View file

@ -144,7 +144,7 @@ GEM
bootsnap (1.16.0)
msgpack (~> 1.2)
brakeman (5.4.0)
browser (4.2.0)
browser (5.3.1)
brpoplpush-redis_script (0.1.3)
concurrent-ruby (~> 1.0, >= 1.0.5)
redis (>= 1.0, < 6)
@ -155,7 +155,6 @@ GEM
bundler-audit (0.9.1)
bundler (>= 1.2.0, < 3)
thor (~> 1.0)
byebug (11.1.3)
capistrano (3.17.2)
airbrussh (>= 1.0.0)
i18n
@ -193,7 +192,7 @@ GEM
cocoon (1.2.15)
coderay (1.1.3)
color_diff (0.1)
concurrent-ruby (1.2.0)
concurrent-ruby (1.2.2)
connection_pool (2.3.0)
cose (1.3.0)
cbor (~> 0.5.9)
@ -499,16 +498,8 @@ GEM
net-smtp
premailer (~> 1.7, >= 1.7.9)
private_address_check (0.5.0)
pry (0.14.1)
coderay (~> 1.1)
method_source (~> 1.0)
pry-byebug (3.10.1)
byebug (~> 11.0)
pry (>= 0.13, < 0.15)
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (5.0.1)
puma (5.6.5)
puma (6.1.0)
nio4r (~> 2.0)
pundit (2.3.0)
activesupport (>= 3.0.0)
@ -569,7 +560,7 @@ GEM
rdf-normalize (0.5.1)
rdf (~> 3.2)
redcarpet (3.6.0)
redis (4.5.1)
redis (4.8.1)
redis-namespace (1.10.0)
redis (>= 4)
redlock (1.3.2)
@ -587,26 +578,26 @@ GEM
chunky_png (~> 1.0)
rqrcode_core (~> 1.0)
rqrcode_core (1.2.0)
rspec-core (3.11.0)
rspec-support (~> 3.11.0)
rspec-expectations (3.11.0)
rspec-core (3.12.1)
rspec-support (~> 3.12.0)
rspec-expectations (3.12.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.11.0)
rspec-mocks (3.11.1)
rspec-support (~> 3.12.0)
rspec-mocks (3.12.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.11.0)
rspec-rails (5.1.2)
actionpack (>= 5.2)
activesupport (>= 5.2)
railties (>= 5.2)
rspec-core (~> 3.10)
rspec-expectations (~> 3.10)
rspec-mocks (~> 3.10)
rspec-support (~> 3.10)
rspec-support (~> 3.12.0)
rspec-rails (6.0.1)
actionpack (>= 6.1)
activesupport (>= 6.1)
railties (>= 6.1)
rspec-core (~> 3.11)
rspec-expectations (~> 3.11)
rspec-mocks (~> 3.11)
rspec-support (~> 3.11)
rspec-sidekiq (3.1.0)
rspec-core (~> 3.0, >= 3.0.0)
sidekiq (>= 2.4.0)
rspec-support (3.11.1)
rspec-support (3.12.0)
rspec_junit_formatter (0.6.0)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (1.45.1)
@ -794,7 +785,6 @@ DEPENDENCIES
capybara (~> 3.38)
charlock_holmes (~> 0.7.7)
chewy (~> 7.2)
climate_control (~> 0.2)
cocoon (~> 1.2)
color_diff (~> 0.1)
concurrent-ruby
@ -853,10 +843,8 @@ DEPENDENCIES
posix-spawn
premailer-rails
private_address_check (~> 0.5)
pry-byebug (~> 3.10)
pry-rails (~> 0.3)
public_suffix (~> 5.0)
puma (~> 5.6)
puma (~> 6.1)
pundit (~> 2.3)
rack (~> 2.2.6)
rack-attack (~> 6.6)
@ -872,7 +860,7 @@ DEPENDENCIES
redis-namespace (~> 1.10)
rexml (~> 3.2)
rqrcode (~> 2.1)
rspec-rails (~> 5.1)
rspec-rails (~> 6.0)
rspec-sidekiq (~> 3.1)
rspec_junit_formatter (~> 0.6)
rubocop

View file

@ -20,6 +20,8 @@ class RelationshipsController < ApplicationController
@form.save
rescue ActionController::ParameterMissing
# Do nothing
rescue Mastodon::NotPermittedError, ActiveRecord::RecordNotFound
flash[:alert] = I18n.t('relationships.follow_failure') if action_from_button == 'follow'
ensure
redirect_to relationships_path(filter_params)
end
@ -61,8 +63,8 @@ class RelationshipsController < ApplicationController
'unfollow'
elsif params[:remove_from_followers]
'remove_from_followers'
elsif params[:block_domains]
'block_domains'
elsif params[:block_domains] || params[:remove_domains_from_followers]
'remove_domains_from_followers'
end
end

View file

@ -1,11 +0,0 @@
# frozen_string_literal: true
module Admin::AnnouncementsHelper
def time_range(announcement)
if announcement.all_day?
safe_join([l(announcement.starts_at.to_date), ' - ', l(announcement.ends_at.to_date)])
else
safe_join([l(announcement.starts_at), ' - ', l(announcement.ends_at)])
end
end
end

View file

@ -41,9 +41,9 @@ module HomeHelper
def obscured_counter(count)
if count <= 0
0
'0'
elsif count == 1
1
'1'
else
'1+'
end
@ -57,14 +57,6 @@ module HomeHelper
end
end
def optional_link_to(condition, path, options = {}, &block)
if condition
link_to(path, options, &block)
else
content_tag(:div, &block)
end
end
def sign_up_message
if closed_registrations?
t('auth.registration_closed', instance: site_hostname)

View file

@ -6,6 +6,7 @@ export default class GIFV extends React.PureComponent {
static propTypes = {
src: PropTypes.string.isRequired,
alt: PropTypes.string,
lang: PropTypes.string,
width: PropTypes.number,
height: PropTypes.number,
onClick: PropTypes.func,
@ -35,7 +36,7 @@ export default class GIFV extends React.PureComponent {
};
render () {
const { src, width, height, alt } = this.props;
const { src, width, height, alt, lang } = this.props;
const { loading } = this.state;
return (
@ -48,6 +49,7 @@ export default class GIFV extends React.PureComponent {
tabIndex='0'
aria-label={alt}
title={alt}
lang={lang}
onClick={this.handleClick}
/>
)}
@ -58,6 +60,7 @@ export default class GIFV extends React.PureComponent {
tabIndex='0'
aria-label={alt}
title={alt}
lang={lang}
muted
loop
autoPlay

View file

@ -10,6 +10,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
static propTypes = {
status: ImmutablePropTypes.map.isRequired,
lang: PropTypes.string,
height: PropTypes.number,
width: PropTypes.number,
revealed: PropTypes.bool,
@ -49,7 +50,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
};
render () {
const { status, width, height, revealed } = this.props;
const { status, lang, width, height, revealed } = this.props;
const mediaAttachments = status.get('media_attachments');
if (mediaAttachments.size === 0) {
@ -65,6 +66,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
<Component
src={audio.get('url')}
alt={audio.get('description')}
lang={lang || status.get('language')}
width={width}
height={height}
poster={audio.get('preview_url') || status.getIn(['account', 'avatar_static'])}
@ -88,6 +90,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
blurhash={video.get('blurhash')}
src={video.get('url')}
alt={video.get('description')}
lang={lang || status.get('language')}
width={width}
height={height}
inline
@ -104,6 +107,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
{Component => (
<Component
media={mediaAttachments}
lang={lang || status.get('language')}
sensitive={status.get('sensitive')}
defaultWidth={width}
revealed={revealed}

View file

@ -36,6 +36,7 @@ class Item extends React.PureComponent {
static propTypes = {
attachment: ImmutablePropTypes.map.isRequired,
lang: PropTypes.string,
standalone: PropTypes.bool,
index: PropTypes.number.isRequired,
size: PropTypes.number.isRequired,
@ -98,7 +99,7 @@ class Item extends React.PureComponent {
};
render () {
const { attachment, index, size, standalone, letterbox, displayWidth, visible } = this.props;
const { attachment, lang, index, size, standalone, letterbox, displayWidth, visible } = this.props;
let width = 50;
let height = 100;
@ -154,7 +155,7 @@ class Item extends React.PureComponent {
if (attachment.get('type') === 'unknown') {
return (
<div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} style={{ cursor: 'pointer' }} title={attachment.get('description')} target='_blank' rel='noopener noreferrer'>
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} style={{ cursor: 'pointer' }} title={attachment.get('description')} lang={lang} target='_blank' rel='noopener noreferrer'>
<Blurhash
hash={attachment.get('blurhash')}
className='media-gallery__preview'
@ -195,6 +196,7 @@ class Item extends React.PureComponent {
sizes={sizes}
alt={attachment.get('description')}
title={attachment.get('description')}
lang={lang}
style={{ objectPosition: letterbox ? null : `${x}% ${y}%` }}
onLoad={this.handleImageLoad}
/>
@ -209,6 +211,7 @@ class Item extends React.PureComponent {
className={`media-gallery__item-gifv-thumbnail${letterbox ? ' letterbox' : ''}`}
aria-label={attachment.get('description')}
title={attachment.get('description')}
lang={lang}
role='application'
src={attachment.get('url')}
onClick={this.handleClick}
@ -251,6 +254,7 @@ class MediaGallery extends React.PureComponent {
fullwidth: PropTypes.bool,
hidden: PropTypes.bool,
media: ImmutablePropTypes.list.isRequired,
lang: PropTypes.string,
size: PropTypes.object,
onOpenMedia: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
@ -342,7 +346,7 @@ class MediaGallery extends React.PureComponent {
}
render () {
const { media, intl, sensitive, letterbox, fullwidth, defaultWidth, autoplay } = this.props;
const { media, lang, intl, sensitive, letterbox, fullwidth, defaultWidth, autoplay } = this.props;
const { visible } = this.state;
const size = media.take(4).size;
const uncached = media.every(attachment => attachment.get('type') === 'unknown');
@ -364,9 +368,9 @@ class MediaGallery extends React.PureComponent {
}
if (this.isStandaloneEligible()) {
children = <Item standalone autoplay={autoplay} onClick={this.handleClick} attachment={media.get(0)} displayWidth={width} visible={visible} />;
children = <Item standalone autoplay={autoplay} onClick={this.handleClick} attachment={media.get(0)} lang={lang} displayWidth={width} visible={visible} />;
} else {
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} autoplay={autoplay} onClick={this.handleClick} attachment={attachment} index={i} size={size} letterbox={letterbox} displayWidth={width} visible={visible || uncached} />);
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} autoplay={autoplay} onClick={this.handleClick} attachment={attachment} index={i} lang={lang} size={size} letterbox={letterbox} displayWidth={width} visible={visible || uncached} />);
}
if (uncached) {

View file

@ -40,6 +40,7 @@ class Poll extends ImmutablePureComponent {
static propTypes = {
poll: ImmutablePropTypes.map,
lang: PropTypes.string,
intl: PropTypes.object.isRequired,
disabled: PropTypes.bool,
refresh: PropTypes.func,
@ -126,7 +127,7 @@ class Poll extends ImmutablePureComponent {
};
renderOption (option, optionIndex, showResults) {
const { poll, disabled, intl } = this.props;
const { poll, lang, disabled, intl } = this.props;
const pollVotesCount = poll.get('voters_count') || poll.get('votes_count');
const percent = pollVotesCount === 0 ? 0 : (option.get('votes_count') / pollVotesCount) * 100;
const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') >= other.get('votes_count'));
@ -159,6 +160,7 @@ class Poll extends ImmutablePureComponent {
onKeyPress={this.handleOptionKeyPress}
aria-checked={active}
aria-label={option.get('title')}
lang={lang}
data-index={optionIndex}
/>
)}
@ -175,6 +177,7 @@ class Poll extends ImmutablePureComponent {
<span
className='poll__option__text translate'
lang={lang}
dangerouslySetInnerHTML={{ __html: titleEmojified }}
/>

View file

@ -634,6 +634,7 @@ class Status extends ImmutablePureComponent {
<Component
src={attachment.get('url')}
alt={attachment.get('description')}
lang={status.get('language')}
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
@ -663,6 +664,7 @@ class Status extends ImmutablePureComponent {
blurhash={attachment.get('blurhash')}
src={attachment.get('url')}
alt={attachment.get('description')}
lang={status.get('language')}
inline
sensitive={status.get('sensitive')}
letterbox={settings.getIn(['media', 'letterbox'])}
@ -684,6 +686,7 @@ class Status extends ImmutablePureComponent {
{Component => (
<Component
media={attachments}
lang={status.get('language')}
sensitive={status.get('sensitive')}
letterbox={settings.getIn(['media', 'letterbox'])}
fullwidth={settings.getIn(['media', 'fullwidth'])}
@ -718,7 +721,7 @@ class Status extends ImmutablePureComponent {
}
if (status.get('poll')) {
contentMedia.push(<PollContainer pollId={status.get('poll')} />);
contentMedia.push(<PollContainer pollId={status.get('poll')} lang={status.get('language')} />);
contentMediaIcons.push('tasks');
}

View file

@ -5,7 +5,7 @@ import { FormattedMessage, injectIntl } from 'react-intl';
import Permalink from './permalink';
import classnames from 'classnames';
import Icon from 'flavours/glitch/components/icon';
import { autoPlayGif, languages as preloadedLanguages, translationEnabled } from 'flavours/glitch/initial_state';
import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state';
import { decode as decodeIDNA } from 'flavours/glitch/utils/idna';
const textMatchesTarget = (text, origin, host) => {
@ -315,7 +315,7 @@ class StatusContent extends React.PureComponent {
} = this.props;
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
const renderTranslate = translationEnabled && this.context.identity.signedIn && this.props.onTranslate && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('contentHtml').length > 0 && status.get('language') !== null && intl.locale !== status.get('language');
const renderTranslate = this.props.onTranslate && status.get('translatable');
const content = { __html: status.get('translation') ? status.getIn(['translation', 'content']) : status.get('contentHtml') };
const spoilerContent = { __html: status.get('spoilerHtml') };

View file

@ -76,6 +76,7 @@ export default class MediaItem extends ImmutablePureComponent {
<img
src={attachment.get('preview_url') || attachment.getIn(['account', 'avatar_static'])}
alt={attachment.get('description')}
lang={status.get('language')}
onLoad={this.handleImageLoad}
/>
);
@ -95,6 +96,7 @@ export default class MediaItem extends ImmutablePureComponent {
<img
src={attachment.get('preview_url')}
alt={attachment.get('description')}
lang={status.get('language')}
style={{ objectPosition: `${x}% ${y}%` }}
onLoad={this.handleImageLoad}
/>
@ -105,6 +107,7 @@ export default class MediaItem extends ImmutablePureComponent {
className='media-gallery__item-gifv-thumbnail'
aria-label={attachment.get('description')}
title={attachment.get('description')}
lang={status.get('language')}
role='application'
src={attachment.get('url')}
onMouseEnter={this.handleMouseEnter}

View file

@ -28,6 +28,7 @@ class Audio extends React.PureComponent {
static propTypes = {
src: PropTypes.string.isRequired,
alt: PropTypes.string,
lang: PropTypes.string,
poster: PropTypes.string,
duration: PropTypes.number,
width: PropTypes.number,
@ -464,7 +465,7 @@ class Audio extends React.PureComponent {
};
render () {
const { src, intl, alt, editable, autoPlay, sensitive, blurhash } = this.props;
const { src, intl, alt, lang, editable, autoPlay, sensitive, blurhash } = this.props;
const { paused, muted, volume, currentTime, duration, buffer, dragging, revealed } = this.state;
const progress = Math.min((currentTime / duration) * 100, 100);
@ -509,6 +510,7 @@ class Audio extends React.PureComponent {
onKeyDown={this.handleAudioKeyDown}
title={alt}
aria-label={alt}
lang={lang}
/>
<div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}>

View file

@ -173,6 +173,7 @@ class DetailedStatus extends ImmutablePureComponent {
<Audio
src={attachment.get('url')}
alt={attachment.get('description')}
lang={status.get('language')}
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
@ -195,6 +196,7 @@ class DetailedStatus extends ImmutablePureComponent {
blurhash={attachment.get('blurhash')}
src={attachment.get('url')}
alt={attachment.get('description')}
lang={status.get('language')}
inline
sensitive={status.get('sensitive')}
letterbox={settings.getIn(['media', 'letterbox'])}
@ -213,6 +215,7 @@ class DetailedStatus extends ImmutablePureComponent {
standalone
sensitive={status.get('sensitive')}
media={status.get('media_attachments')}
lang={status.get('language')}
letterbox={settings.getIn(['media', 'letterbox'])}
fullwidth={settings.getIn(['media', 'fullwidth'])}
hidden={!expanded}
@ -229,7 +232,7 @@ class DetailedStatus extends ImmutablePureComponent {
}
if (status.get('poll')) {
contentMedia.push(<PollContainer pollId={status.get('poll')} />);
contentMedia.push(<PollContainer pollId={status.get('poll')} lang={status.get('language')} />);
contentMediaIcons.push('tasks');
}

View file

@ -7,15 +7,17 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import Footer from 'flavours/glitch/features/picture_in_picture/components/footer';
const mapStateToProps = (state, { statusId }) => ({
language: state.getIn(['statuses', statusId, 'language']),
accountStaticAvatar: state.getIn(['accounts', state.getIn(['statuses', statusId, 'account']), 'avatar_static']),
});
export default @connect(mapStateToProps)
export default @connect(mapStateToProps, null, null, { forwardRef: true })
class AudioModal extends ImmutablePureComponent {
static propTypes = {
media: ImmutablePropTypes.map.isRequired,
statusId: PropTypes.string.isRequired,
language: PropTypes.string,
accountStaticAvatar: PropTypes.string.isRequired,
options: PropTypes.shape({
autoPlay: PropTypes.bool,
@ -29,7 +31,7 @@ class AudioModal extends ImmutablePureComponent {
};
render () {
const { media, accountStaticAvatar, statusId, onClose } = this.props;
const { media, language, accountStaticAvatar, statusId, onClose } = this.props;
const options = this.props.options || {};
return (
@ -38,6 +40,7 @@ class AudioModal extends ImmutablePureComponent {
<Audio
src={media.get('url')}
alt={media.get('description')}
lang={language}
duration={media.getIn(['meta', 'original', 'duration'], 0)}
height={150}
poster={media.get('preview_url') || accountStaticAvatar}

View file

@ -12,6 +12,7 @@ import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp';
import MediaAttachments from 'flavours/glitch/components/media_attachments';
const mapStateToProps = (state, { statusId }) => ({
language: state.getIn(['statuses', statusId, 'language']),
versions: state.getIn(['history', statusId, 'items']),
});
@ -30,11 +31,12 @@ class CompareHistoryModal extends React.PureComponent {
onClose: PropTypes.func.isRequired,
index: PropTypes.number.isRequired,
statusId: PropTypes.string.isRequired,
language: PropTypes.string.isRequired,
versions: ImmutablePropTypes.list.isRequired,
};
render () {
const { index, versions, onClose } = this.props;
const { index, versions, language, onClose } = this.props;
const currentVersion = versions.get(index);
const emojiMap = currentVersion.get('emojis').reduce((obj, emoji) => {
@ -65,12 +67,12 @@ class CompareHistoryModal extends React.PureComponent {
<div className='status__content'>
{currentVersion.get('spoiler_text').length > 0 && (
<React.Fragment>
<div className='translate' dangerouslySetInnerHTML={spoilerContent} />
<div className='translate' dangerouslySetInnerHTML={spoilerContent} lang={language} />
<hr />
</React.Fragment>
)}
<div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} />
<div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} lang={language} />
{!!currentVersion.get('poll') && (
<div className='poll'>
@ -82,6 +84,7 @@ class CompareHistoryModal extends React.PureComponent {
<span
className='poll__option__text translate'
dangerouslySetInnerHTML={{ __html: emojify(escapeTextContentForBrowser(option.get('title')), emojiMap) }}
lang={language}
/>
</li>
))}
@ -89,7 +92,7 @@ class CompareHistoryModal extends React.PureComponent {
</div>
)}
<MediaAttachments status={currentVersion} />
<MediaAttachments status={currentVersion} lang={language} />
</div>
</div>
</div>

View file

@ -23,8 +23,8 @@ const mapDispatchToProps = (dispatch) => ({
},
});
export default @connect(null, mapDispatchToProps)
@withRouter
export default @withRouter
@connect(null, mapDispatchToProps)
class Header extends React.PureComponent {
static contextTypes = {

View file

@ -8,6 +8,7 @@ export default class ImageLoader extends PureComponent {
static propTypes = {
alt: PropTypes.string,
lang: PropTypes.string,
src: PropTypes.string.isRequired,
previewSrc: PropTypes.string,
width: PropTypes.number,
@ -18,6 +19,7 @@ export default class ImageLoader extends PureComponent {
static defaultProps = {
alt: '',
lang: '',
width: null,
height: null,
};
@ -129,7 +131,7 @@ export default class ImageLoader extends PureComponent {
};
render () {
const { alt, src, width, height, onClick } = this.props;
const { alt, lang, src, width, height, onClick } = this.props;
const { loading } = this.state;
const className = classNames('image-loader', {
@ -154,6 +156,7 @@ export default class ImageLoader extends PureComponent {
) : (
<ZoomableImage
alt={alt}
lang={lang}
src={src}
onClick={onClick}
width={width}

View file

@ -3,6 +3,7 @@ import ReactSwipeableViews from 'react-swipeable-views';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import Video from 'flavours/glitch/features/video';
import { connect } from 'react-redux';
import classNames from 'classnames';
import { defineMessages, injectIntl } from 'react-intl';
import IconButton from 'flavours/glitch/components/icon_button';
@ -20,7 +21,12 @@ const messages = defineMessages({
next: { id: 'lightbox.next', defaultMessage: 'Next' },
});
export default @injectIntl
const mapStateToProps = (state, { statusId }) => ({
language: state.getIn(['statuses', statusId, 'language']),
});
export default @connect(mapStateToProps, null, null, { forwardRef: true })
@injectIntl
class MediaModal extends ImmutablePureComponent {
static contextTypes = {
@ -131,7 +137,7 @@ class MediaModal extends ImmutablePureComponent {
}
render () {
const { media, statusId, intl, onClose } = this.props;
const { media, language, statusId, intl, onClose } = this.props;
const { navigationHidden } = this.state;
const index = this.getIndex();
@ -151,6 +157,7 @@ class MediaModal extends ImmutablePureComponent {
width={width}
height={height}
alt={image.get('description')}
lang={language}
key={image.get('url')}
onClick={this.toggleNavigation}
zoomButtonHidden={this.state.zoomButtonHidden}
@ -173,6 +180,7 @@ class MediaModal extends ImmutablePureComponent {
onCloseVideo={onClose}
detailed
alt={image.get('description')}
lang={language}
key={image.get('url')}
/>
);
@ -184,6 +192,7 @@ class MediaModal extends ImmutablePureComponent {
height={height}
key={image.get('preview_url')}
alt={image.get('description')}
lang={language}
onClick={this.toggleNavigation}
/>
);

View file

@ -78,8 +78,8 @@ class NavigationPanel extends React.Component {
{signedIn && (
<React.Fragment>
<ColumnLink transparent to='/conversations' icon='at' text={intl.formatMessage(messages.direct)} />
<ColumnLink transparent to='/favourites' icon='star' text={intl.formatMessage(messages.favourites)} />
<ColumnLink transparent to='/bookmarks' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} />
<ColumnLink transparent to='/favourites' icon='star' text={intl.formatMessage(messages.favourites)} />
<ColumnLink transparent to='/lists' icon='list-ul' text={intl.formatMessage(messages.lists)} />
<ListPanel />

View file

@ -2,11 +2,17 @@ import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import Video from 'flavours/glitch/features/video';
import { connect } from 'react-redux';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Footer from 'flavours/glitch/features/picture_in_picture/components/footer';
import { getAverageFromBlurhash } from 'flavours/glitch/blurhash';
export default class VideoModal extends ImmutablePureComponent {
const mapStateToProps = (state, { statusId }) => ({
language: state.getIn(['statuses', statusId, 'language']),
});
export default @connect(mapStateToProps, null, null, { forwardRef: true })
class VideoModal extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
@ -15,6 +21,7 @@ export default class VideoModal extends ImmutablePureComponent {
static propTypes = {
media: ImmutablePropTypes.map.isRequired,
statusId: PropTypes.string,
language: PropTypes.string,
options: PropTypes.shape({
startTime: PropTypes.number,
autoPlay: PropTypes.bool,
@ -35,7 +42,7 @@ export default class VideoModal extends ImmutablePureComponent {
}
render () {
const { media, statusId, onClose } = this.props;
const { media, statusId, language, onClose } = this.props;
const options = this.props.options || {};
return (
@ -53,6 +60,7 @@ export default class VideoModal extends ImmutablePureComponent {
autoFocus
detailed
alt={media.get('description')}
lang={language}
/>
</div>

View file

@ -96,6 +96,7 @@ class ZoomableImage extends React.PureComponent {
static propTypes = {
alt: PropTypes.string,
lang: PropTypes.string,
src: PropTypes.string.isRequired,
width: PropTypes.number,
height: PropTypes.number,
@ -106,6 +107,7 @@ class ZoomableImage extends React.PureComponent {
static defaultProps = {
alt: '',
lang: '',
width: null,
height: null,
};
@ -403,7 +405,7 @@ class ZoomableImage extends React.PureComponent {
};
render () {
const { alt, src, width, height, intl } = this.props;
const { alt, lang, src, width, height, intl } = this.props;
const { scale, lockTranslate } = this.state;
const overflow = scale === MIN_SCALE ? 'hidden' : 'scroll';
const zoomButtonShouldHide = this.state.navigationHidden || this.props.zoomButtonHidden || this.state.zoomMatrix.rate <= MIN_SCALE ? 'media-modal__zoom-button--hidden' : '';
@ -431,6 +433,7 @@ class ZoomableImage extends React.PureComponent {
ref={this.setImageRef}
alt={alt}
title={alt}
lang={lang}
src={src}
width={width}
height={height}

View file

@ -101,6 +101,7 @@ class Video extends React.PureComponent {
frameRate: PropTypes.string,
src: PropTypes.string.isRequired,
alt: PropTypes.string,
lang: PropTypes.string,
width: PropTypes.number,
height: PropTypes.number,
sensitive: PropTypes.bool,
@ -538,7 +539,7 @@ class Video extends React.PureComponent {
}
render () {
const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, letterbox, fullwidth, detailed, sensitive, editable, blurhash, autoFocus } = this.props;
const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, lang, letterbox, fullwidth, detailed, sensitive, editable, blurhash, autoFocus } = this.props;
const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
const progress = Math.min((currentTime / duration) * 100, 100);
const playerStyle = {};
@ -603,6 +604,7 @@ class Video extends React.PureComponent {
tabIndex='0'
aria-label={alt}
title={alt}
lang={lang}
width={width}
height={height}
volume={volume}

View file

@ -144,7 +144,6 @@ export const useBlurhash = getMeta('use_blurhash');
export const usePendingItems = getMeta('use_pending_items');
export const version = getMeta('version');
export const visibleReactions = getMeta('visible_reactions');
export const translationEnabled = getMeta('translation_enabled');
export const languages = initialState?.languages;
export const statusPageUrl = getMeta('status_page_url');

View file

@ -8,12 +8,13 @@
"advanced_options.icon_title": "Opciones avanzadas",
"advanced_options.local-only.long": "No publicar a otras instancias",
"advanced_options.local-only.short": "Local",
"advanced_options.local-only.tooltip": "Este toot es local",
"advanced_options.local-only.tooltip": "Esta publicación es local",
"advanced_options.threaded_mode.long": "Al publicar abre automáticamente una respuesta",
"advanced_options.threaded_mode.short": "Modo hilo",
"advanced_options.threaded_mode.tooltip": "Modo hilo habilitado",
"boost_modal.missing_description": "Esta publicación contiene medios sin descripción",
"column.favourited_by": "Marcado como favorito por",
"column.reblogged_by": "Retooteado por",
"column.reblogged_by": "Impulsado por",
"column_header.profile": "Perfil",
"column_subheading.lists": "Listas",
"column_subheading.navigation": "Navegación",
@ -50,26 +51,26 @@
"settings.always_show_spoilers_field": "Siempre mostrar el campo de advertencia de contenido",
"settings.auto_collapse": "Colapsar automáticamente",
"settings.auto_collapse_all": "Todo",
"settings.auto_collapse_lengthy": "Toots largos",
"settings.auto_collapse_media": "Toots con medios",
"settings.auto_collapse_lengthy": "Publicaciones largas",
"settings.auto_collapse_media": "Publicaciones multimedia",
"settings.auto_collapse_notifications": "Notificaciones",
"settings.auto_collapse_reblogs": "Retoots",
"settings.auto_collapse_reblogs": "Impulsos",
"settings.auto_collapse_replies": "Respuestas",
"settings.close": "Cerrar",
"settings.collapsed_statuses": "Toots colapsados",
"settings.collapsed_statuses": "Publicaciones colapsadas",
"settings.compose_box_opts": "Cuadro de redacción",
"settings.confirm_before_clearing_draft": "Mostrar diálogo de confirmación antes de sobreescribir un mensaje estabas escribiendo",
"settings.confirm_boost_missing_media_description": "Mostrar diálogo de confirmación antes de retootear publicaciones con medios sin descripción",
"settings.confirm_missing_media_description": "Mostrar diálogo de confirmación antes de publicar toots con medios sin descripción",
"settings.confirm_before_clearing_draft": "Mostrar diálogo de confirmación antes de sobreescribir el mensaje siendo redactado",
"settings.confirm_boost_missing_media_description": "Mostrar diálogo de confirmación antes de impulsar publicaciones con medios sin descripciones",
"settings.confirm_missing_media_description": "Mostrar diálogo de confirmación antes de enviar publicaciones con medios sin descripciones",
"settings.content_warnings": "Advertencias de contenido",
"settings.content_warnings.regexp": "Regexp (expresión regular)",
"settings.content_warnings_filter": "No descolapsar estas advertencias de contenido:",
"settings.enable_collapsed": "Habilitar toots colapsados",
"settings.enable_content_warnings_auto_unfold": "Descolapsar automáticamente advertencias de contenido",
"settings.enable_collapsed": "Habilitar publicaciones colapsadas",
"settings.enable_content_warnings_auto_unfold": "Desplegar automáticamente advertencias de contenido",
"settings.hicolor_privacy_icons": "Íconos de privacidad más visibles",
"settings.image_backgrounds": "Fondos de imágenes",
"settings.image_backgrounds_media": "Vista previa de medios de toots colapsados",
"settings.image_backgrounds_users": "Darle fondo de imagen a toots colapsados",
"settings.image_backgrounds_media": "Vista previa de medios de publicaciones colapsadas",
"settings.image_backgrounds_users": "Darle fondo de imagen a publicaciones colapsadas",
"settings.inline_preview_cards": "Vista previa para enlaces externos",
"settings.layout": "Diseño",
"settings.layout_opts": "Opciones de diseño",
@ -91,21 +92,21 @@
"settings.rewrite_mentions_acct": "Reescribir con nombre de usuarix y dominio (para cuentas remotas)",
"settings.rewrite_mentions_no": "No reescribir menciones",
"settings.rewrite_mentions_username": "Reescribir con nombre de usuarix",
"settings.show_action_bar": "Mostrar botones de acción en toots colapsados",
"settings.show_content_type_choice": "Mostrar selección de tipo de contenido al crear toots",
"settings.show_action_bar": "Mostrar botones de acción en publicaciones colapsadas",
"settings.show_content_type_choice": "Mostrar selección de tipo de contenido al crear publicaciones",
"settings.show_reply_counter": "Mostrar un conteo estimado de respuestas",
"settings.side_arm": "Botón secundario:",
"settings.side_arm.none": "Ninguno",
"settings.side_arm_reply_mode": "Al responder a un toot, el botón de toot secundario debe:",
"settings.side_arm_reply_mode.copy": "Copiar opción de privacidad del toot al que estás respondiendo",
"settings.side_arm_reply_mode": "Al responder a una publicación, el botón de publicación secundario debe:",
"settings.side_arm_reply_mode.copy": "Copiar opción de privacidad de la publicación a la que estás respondiendo",
"settings.side_arm_reply_mode.keep": "Conservar opción de privacidad",
"settings.side_arm_reply_mode.restrict": "Restringir la opción de privacidad a la misma del toot al que estás respondiendo",
"settings.side_arm_reply_mode.restrict": "Restringir la opción de privacidad a la misma de la publicación a la que estás respondiendo",
"settings.swipe_to_change_columns": "Permitir deslizar para cambiar columnas (Sólo en móvil)",
"settings.tag_misleading_links": "Marcar enlaces engañosos",
"settings.tag_misleading_links.hint": "Añadir una indicación visual indicando el destino de los enlace que no los mencionen explícitamente",
"settings.wide_view": "Vista amplia (solo modo de escritorio)",
"status.collapse": "Colapsar",
"status.in_reply_to": "Este toot es una respuesta",
"status.is_poll": "Este toot es una encuesta",
"status.in_reply_to": "Esta publicación es una respuesta",
"status.is_poll": "Esta publicación es una encuesta",
"status.uncollapse": "Descolapsar"
}

View file

@ -164,8 +164,7 @@
white-space: pre-wrap;
p,
pre,
blockquote {
pre {
margin-bottom: 20px;
white-space: pre-wrap;
@ -174,79 +173,6 @@
}
}
h1,
h2,
h3,
h4,
h5 {
margin-top: 20px;
margin-bottom: 20px;
}
h1,
h2 {
font-weight: 700;
font-size: 18px;
}
h2 {
font-size: 16px;
}
h3,
h4,
h5 {
font-weight: 500;
}
blockquote {
padding-left: 10px;
border-left: 3px solid $inverted-text-color;
color: $inverted-text-color;
white-space: normal;
p:last-child {
margin-bottom: 0;
}
}
b,
strong {
font-weight: 700;
}
em,
i {
font-style: italic;
}
sub {
font-size: smaller;
vertical-align: sub;
}
sup {
font-size: smaller;
vertical-align: super;
}
ul,
ol {
margin-left: 1em;
p {
margin: 0;
}
}
ul {
list-style-type: disc;
}
ol {
list-style-type: decimal;
}
a {
color: $lighter-text-color;
text-decoration: none;

View file

@ -68,8 +68,7 @@
}
p,
pre,
blockquote {
pre {
margin-bottom: 20px;
white-space: pre-wrap;
unicode-bidi: plaintext;
@ -79,89 +78,6 @@
}
}
.status__content__text,
.e-content {
overflow: hidden;
& > ul,
& > ol {
margin-bottom: 20px;
}
h1,
h2,
h3,
h4,
h5 {
margin-top: 20px;
margin-bottom: 20px;
}
h1,
h2 {
font-weight: 700;
font-size: 1.2em;
}
h2 {
font-size: 1.1em;
}
h3,
h4,
h5 {
font-weight: 500;
}
blockquote {
padding-left: 10px;
border-left: 3px solid $darker-text-color;
color: $darker-text-color;
white-space: normal;
p:last-child {
margin-bottom: 0;
}
}
b,
strong {
font-weight: 700;
}
em,
i {
font-style: italic;
}
sub {
font-size: smaller;
vertical-align: sub;
}
sup {
font-size: smaller;
vertical-align: super;
}
ul,
ol {
margin-left: 2em;
p {
margin: 0;
}
}
ul {
list-style-type: disc;
}
ol {
list-style-type: decimal;
}
}
a {
color: $secondary-text-color;
text-decoration: none;

View file

@ -21,3 +21,4 @@
@import 'accessibility';
@import 'rtl';
@import 'dashboard';
@import 'rich_text';

View file

@ -0,0 +1,99 @@
.status__content__text,
.e-content,
.reply-indicator__content {
pre,
blockquote {
margin-bottom: 20px;
white-space: pre-wrap;
unicode-bidi: plaintext;
&:last-child {
margin-bottom: 0;
}
}
blockquote {
padding-left: 10px;
border-left: 3px solid $darker-text-color;
color: $darker-text-color;
white-space: normal;
p:last-child {
margin-bottom: 0;
}
}
& > ul,
& > ol {
margin-bottom: 20px;
}
h1,
h2,
h3,
h4,
h5 {
margin-top: 20px;
margin-bottom: 20px;
}
h1,
h2 {
font-weight: 700;
font-size: 1.2em;
}
h2 {
font-size: 1.1em;
}
h3,
h4,
h5 {
font-weight: 500;
}
b,
strong {
font-weight: 700;
}
em,
i {
font-style: italic;
}
sub {
font-size: smaller;
vertical-align: sub;
}
sup {
font-size: smaller;
vertical-align: super;
}
ul,
ol {
margin-left: 2em;
p {
margin: 0;
}
}
ul {
list-style-type: disc;
}
ol {
list-style-type: decimal;
}
}
.reply-indicator__content {
blockquote {
border-left-color: $inverted-text-color;
color: $inverted-text-color;
}
}

View file

@ -15,10 +15,10 @@ export default class ColumnBackButton extends React.PureComponent {
};
handleClick = () => {
if (window.history && window.history.length === 1) {
this.context.router.history.push('/');
} else {
if (window.history && window.history.state) {
this.context.router.history.goBack();
} else {
this.context.router.history.push('/');
}
};

View file

@ -43,14 +43,6 @@ class ColumnHeader extends React.PureComponent {
animating: false,
};
historyBack = () => {
if (window.history && window.history.length === 1) {
this.context.router.history.push('/');
} else {
this.context.router.history.goBack();
}
};
handleToggleClick = (e) => {
e.stopPropagation();
this.setState({ collapsed: !this.state.collapsed, animating: true });
@ -69,7 +61,11 @@ class ColumnHeader extends React.PureComponent {
};
handleBackClick = () => {
this.historyBack();
if (window.history && window.history.state) {
this.context.router.history.goBack();
} else {
this.context.router.history.push('/');
}
};
handleTransitionEnd = () => {

View file

@ -6,6 +6,7 @@ export default class GIFV extends React.PureComponent {
static propTypes = {
src: PropTypes.string.isRequired,
alt: PropTypes.string,
lang: PropTypes.string,
width: PropTypes.number,
height: PropTypes.number,
onClick: PropTypes.func,
@ -35,7 +36,7 @@ export default class GIFV extends React.PureComponent {
};
render () {
const { src, width, height, alt } = this.props;
const { src, width, height, alt, lang } = this.props;
const { loading } = this.state;
return (
@ -48,6 +49,7 @@ export default class GIFV extends React.PureComponent {
tabIndex='0'
aria-label={alt}
title={alt}
lang={lang}
onClick={this.handleClick}
/>
)}
@ -58,6 +60,7 @@ export default class GIFV extends React.PureComponent {
tabIndex='0'
aria-label={alt}
title={alt}
lang={lang}
muted
loop
autoPlay

View file

@ -10,6 +10,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
static propTypes = {
status: ImmutablePropTypes.map.isRequired,
lang: PropTypes.string,
height: PropTypes.number,
width: PropTypes.number,
};
@ -48,7 +49,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
};
render () {
const { status, width, height } = this.props;
const { status, lang, width, height } = this.props;
const mediaAttachments = status.get('media_attachments');
if (mediaAttachments.size === 0) {
@ -64,6 +65,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
<Component
src={audio.get('url')}
alt={audio.get('description')}
lang={lang || status.get('language')}
width={width}
height={height}
poster={audio.get('preview_url') || status.getIn(['account', 'avatar_static'])}
@ -87,6 +89,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
blurhash={video.get('blurhash')}
src={video.get('url')}
alt={video.get('description')}
lang={lang || status.get('language')}
width={width}
height={height}
inline
@ -102,6 +105,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
{Component => (
<Component
media={mediaAttachments}
lang={lang || status.get('language')}
sensitive={status.get('sensitive')}
defaultWidth={width}
height={height}

View file

@ -17,6 +17,7 @@ class Item extends React.PureComponent {
static propTypes = {
attachment: ImmutablePropTypes.map.isRequired,
lang: PropTypes.string,
standalone: PropTypes.bool,
index: PropTypes.number.isRequired,
size: PropTypes.number.isRequired,
@ -78,7 +79,7 @@ class Item extends React.PureComponent {
};
render () {
const { attachment, index, size, standalone, displayWidth, visible } = this.props;
const { attachment, lang, index, size, standalone, displayWidth, visible } = this.props;
let width = 50;
let height = 100;
@ -134,7 +135,7 @@ class Item extends React.PureComponent {
if (attachment.get('type') === 'unknown') {
return (
<div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} style={{ cursor: 'pointer' }} title={attachment.get('description')} target='_blank' rel='noopener noreferrer'>
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} style={{ cursor: 'pointer' }} title={attachment.get('description')} lang={lang} target='_blank' rel='noopener noreferrer'>
<Blurhash
hash={attachment.get('blurhash')}
className='media-gallery__preview'
@ -174,6 +175,7 @@ class Item extends React.PureComponent {
sizes={sizes}
alt={attachment.get('description')}
title={attachment.get('description')}
lang={lang}
style={{ objectPosition: `${x}% ${y}%` }}
onLoad={this.handleImageLoad}
/>
@ -188,6 +190,7 @@ class Item extends React.PureComponent {
className='media-gallery__item-gifv-thumbnail'
aria-label={attachment.get('description')}
title={attachment.get('description')}
lang={lang}
role='application'
src={attachment.get('url')}
onClick={this.handleClick}
@ -227,6 +230,7 @@ class MediaGallery extends React.PureComponent {
sensitive: PropTypes.bool,
standalone: PropTypes.bool,
media: ImmutablePropTypes.list.isRequired,
lang: PropTypes.string,
size: PropTypes.object,
height: PropTypes.number.isRequired,
onOpenMedia: PropTypes.func.isRequired,
@ -310,9 +314,8 @@ class MediaGallery extends React.PureComponent {
}
render () {
const { media, intl, sensitive, height, defaultWidth, standalone, autoplay } = this.props;
const { media, lang, intl, sensitive, height, defaultWidth, standalone, autoplay } = this.props;
const { visible } = this.state;
const width = this.state.width || defaultWidth;
let children, spoilerButton;
@ -333,9 +336,9 @@ class MediaGallery extends React.PureComponent {
const uncached = media.every(attachment => attachment.get('type') === 'unknown');
if (standalone && this.isFullSizeEligible()) {
children = <Item standalone autoplay={autoplay} onClick={this.handleClick} attachment={media.get(0)} displayWidth={width} visible={visible} />;
children = <Item standalone autoplay={autoplay} onClick={this.handleClick} attachment={media.get(0)} lang={lang} displayWidth={width} visible={visible} />;
} else {
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} autoplay={autoplay} onClick={this.handleClick} attachment={attachment} index={i} size={size} displayWidth={width} visible={visible || uncached} />);
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} autoplay={autoplay} onClick={this.handleClick} attachment={attachment} index={i} lang={lang} size={size} displayWidth={width} visible={visible || uncached} />);
}
if (uncached) {

View file

@ -40,6 +40,7 @@ class Poll extends ImmutablePureComponent {
static propTypes = {
poll: ImmutablePropTypes.map,
lang: PropTypes.string,
intl: PropTypes.object.isRequired,
disabled: PropTypes.bool,
refresh: PropTypes.func,
@ -126,7 +127,7 @@ class Poll extends ImmutablePureComponent {
};
renderOption (option, optionIndex, showResults) {
const { poll, disabled, intl } = this.props;
const { poll, lang, disabled, intl } = this.props;
const pollVotesCount = poll.get('voters_count') || poll.get('votes_count');
const percent = pollVotesCount === 0 ? 0 : (option.get('votes_count') / pollVotesCount) * 100;
const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') >= other.get('votes_count'));
@ -159,6 +160,7 @@ class Poll extends ImmutablePureComponent {
onKeyPress={this.handleOptionKeyPress}
aria-checked={active}
aria-label={option.get('title')}
lang={lang}
data-index={optionIndex}
/>
)}
@ -175,6 +177,7 @@ class Poll extends ImmutablePureComponent {
<span
className='poll__option__text translate'
lang={lang}
dangerouslySetInnerHTML={{ __html: titleEmojified }}
/>

View file

@ -422,6 +422,7 @@ class Status extends ImmutablePureComponent {
<Component
src={attachment.get('url')}
alt={attachment.get('description')}
lang={status.get('language')}
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
@ -451,6 +452,7 @@ class Status extends ImmutablePureComponent {
blurhash={attachment.get('blurhash')}
src={attachment.get('url')}
alt={attachment.get('description')}
lang={status.get('language')}
width={this.props.cachedMediaWidth}
height={110}
inline
@ -470,6 +472,7 @@ class Status extends ImmutablePureComponent {
{Component => (
<Component
media={status.get('media_attachments')}
lang={status.get('language')}
sensitive={status.get('sensitive')}
height={110}
onOpenMedia={this.handleOpenMedia}

View file

@ -6,7 +6,7 @@ import { Link } from 'react-router-dom';
import classnames from 'classnames';
import PollContainer from 'mastodon/containers/poll_container';
import Icon from 'mastodon/components/icon';
import { autoPlayGif, languages as preloadedLanguages, translationEnabled } from 'mastodon/initial_state';
import { autoPlayGif, languages as preloadedLanguages } from 'mastodon/initial_state';
const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top)
@ -220,7 +220,7 @@ class StatusContent extends React.PureComponent {
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
const renderReadMore = this.props.onClick && status.get('collapsed');
const renderTranslate = translationEnabled && this.context.identity.signedIn && this.props.onTranslate && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('contentHtml').length > 0 && status.get('language') !== null && intl.locale !== status.get('language');
const renderTranslate = this.props.onTranslate && status.get('translatable');
const content = { __html: status.get('translation') ? status.getIn(['translation', 'content']) : status.get('contentHtml') };
const spoilerContent = { __html: status.get('spoilerHtml') };
@ -242,7 +242,7 @@ class StatusContent extends React.PureComponent {
);
const poll = !!status.get('poll') && (
<PollContainer pollId={status.get('poll')} />
<PollContainer pollId={status.get('poll')} lang={status.get('language')} />
);
if (status.get('spoiler_text').length > 0) {

View file

@ -76,6 +76,7 @@ export default class MediaItem extends ImmutablePureComponent {
<img
src={attachment.get('preview_url') || attachment.getIn(['account', 'avatar_static'])}
alt={attachment.get('description')}
lang={status.get('language')}
onLoad={this.handleImageLoad}
/>
);
@ -95,6 +96,7 @@ export default class MediaItem extends ImmutablePureComponent {
<img
src={attachment.get('preview_url')}
alt={attachment.get('description')}
lang={status.get('language')}
style={{ objectPosition: `${x}% ${y}%` }}
onLoad={this.handleImageLoad}
/>
@ -105,6 +107,7 @@ export default class MediaItem extends ImmutablePureComponent {
className='media-gallery__item-gifv-thumbnail'
aria-label={attachment.get('description')}
title={attachment.get('description')}
lang={status.get('language')}
role='application'
src={attachment.get('url')}
onMouseEnter={this.handleMouseEnter}

View file

@ -28,6 +28,7 @@ class Audio extends React.PureComponent {
static propTypes = {
src: PropTypes.string.isRequired,
alt: PropTypes.string,
lang: PropTypes.string,
poster: PropTypes.string,
duration: PropTypes.number,
width: PropTypes.number,
@ -458,7 +459,7 @@ class Audio extends React.PureComponent {
};
render () {
const { src, intl, alt, editable, autoPlay, sensitive, blurhash } = this.props;
const { src, intl, alt, lang, editable, autoPlay, sensitive, blurhash } = this.props;
const { paused, muted, volume, currentTime, duration, buffer, dragging, revealed } = this.state;
const progress = Math.min((currentTime / duration) * 100, 100);
@ -503,6 +504,7 @@ class Audio extends React.PureComponent {
onKeyDown={this.handleAudioKeyDown}
title={alt}
aria-label={alt}
lang={lang}
/>
<div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}>

View file

@ -143,6 +143,7 @@ class DetailedStatus extends ImmutablePureComponent {
<Audio
src={attachment.get('url')}
alt={attachment.get('description')}
lang={status.get('language')}
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
@ -165,6 +166,7 @@ class DetailedStatus extends ImmutablePureComponent {
blurhash={attachment.get('blurhash')}
src={attachment.get('url')}
alt={attachment.get('description')}
lang={status.get('language')}
width={300}
height={150}
inline
@ -180,6 +182,7 @@ class DetailedStatus extends ImmutablePureComponent {
standalone
sensitive={status.get('sensitive')}
media={status.get('media_attachments')}
lang={status.get('language')}
height={300}
onOpenMedia={this.props.onOpenMedia}
visible={this.props.showMedia}

View file

@ -7,15 +7,17 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import Footer from 'mastodon/features/picture_in_picture/components/footer';
const mapStateToProps = (state, { statusId }) => ({
language: state.getIn(['statuses', statusId, 'language']),
accountStaticAvatar: state.getIn(['accounts', state.getIn(['statuses', statusId, 'account']), 'avatar_static']),
});
export default @connect(mapStateToProps)
export default @connect(mapStateToProps, null, null, { forwardRef: true })
class AudioModal extends ImmutablePureComponent {
static propTypes = {
media: ImmutablePropTypes.map.isRequired,
statusId: PropTypes.string.isRequired,
language: PropTypes.string,
accountStaticAvatar: PropTypes.string.isRequired,
options: PropTypes.shape({
autoPlay: PropTypes.bool,
@ -25,7 +27,7 @@ class AudioModal extends ImmutablePureComponent {
};
render () {
const { media, accountStaticAvatar, statusId, onClose } = this.props;
const { media, language, accountStaticAvatar, statusId, onClose } = this.props;
const options = this.props.options || {};
return (
@ -34,6 +36,7 @@ class AudioModal extends ImmutablePureComponent {
<Audio
src={media.get('url')}
alt={media.get('description')}
lang={language}
duration={media.getIn(['meta', 'original', 'duration'], 0)}
height={150}
poster={media.get('preview_url') || accountStaticAvatar}

View file

@ -12,6 +12,7 @@ import RelativeTimestamp from 'mastodon/components/relative_timestamp';
import MediaAttachments from 'mastodon/components/media_attachments';
const mapStateToProps = (state, { statusId }) => ({
language: state.getIn(['statuses', statusId, 'language']),
versions: state.getIn(['history', statusId, 'items']),
});
@ -30,11 +31,12 @@ class CompareHistoryModal extends React.PureComponent {
onClose: PropTypes.func.isRequired,
index: PropTypes.number.isRequired,
statusId: PropTypes.string.isRequired,
language: PropTypes.string.isRequired,
versions: ImmutablePropTypes.list.isRequired,
};
render () {
const { index, versions, onClose } = this.props;
const { index, versions, language, onClose } = this.props;
const currentVersion = versions.get(index);
const emojiMap = currentVersion.get('emojis').reduce((obj, emoji) => {
@ -65,12 +67,12 @@ class CompareHistoryModal extends React.PureComponent {
<div className='status__content'>
{currentVersion.get('spoiler_text').length > 0 && (
<React.Fragment>
<div className='translate' dangerouslySetInnerHTML={spoilerContent} />
<div className='translate' dangerouslySetInnerHTML={spoilerContent} lang={language} />
<hr />
</React.Fragment>
)}
<div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} />
<div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} lang={language} />
{!!currentVersion.get('poll') && (
<div className='poll'>
@ -82,6 +84,7 @@ class CompareHistoryModal extends React.PureComponent {
<span
className='poll__option__text translate'
dangerouslySetInnerHTML={{ __html: emojify(escapeTextContentForBrowser(option.get('title')), emojiMap) }}
lang={language}
/>
</li>
))}
@ -89,7 +92,7 @@ class CompareHistoryModal extends React.PureComponent {
</div>
)}
<MediaAttachments status={currentVersion} />
<MediaAttachments status={currentVersion} lang={language} />
</div>
</div>
</div>

View file

@ -22,8 +22,8 @@ const mapDispatchToProps = (dispatch) => ({
},
});
export default @connect(null, mapDispatchToProps)
@withRouter
export default @withRouter
@connect(null, mapDispatchToProps)
class Header extends React.PureComponent {
static contextTypes = {

View file

@ -8,6 +8,7 @@ export default class ImageLoader extends PureComponent {
static propTypes = {
alt: PropTypes.string,
lang: PropTypes.string,
src: PropTypes.string.isRequired,
previewSrc: PropTypes.string,
width: PropTypes.number,
@ -18,6 +19,7 @@ export default class ImageLoader extends PureComponent {
static defaultProps = {
alt: '',
lang: '',
width: null,
height: null,
};
@ -129,7 +131,7 @@ export default class ImageLoader extends PureComponent {
};
render () {
const { alt, src, width, height, onClick } = this.props;
const { alt, lang, src, width, height, onClick } = this.props;
const { loading } = this.state;
const className = classNames('image-loader', {
@ -154,6 +156,7 @@ export default class ImageLoader extends PureComponent {
) : (
<ZoomableImage
alt={alt}
lang={lang}
src={src}
onClick={onClick}
width={width}

View file

@ -3,6 +3,7 @@ import ReactSwipeableViews from 'react-swipeable-views';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import Video from 'mastodon/features/video';
import { connect } from 'react-redux';
import classNames from 'classnames';
import { defineMessages, injectIntl } from 'react-intl';
import IconButton from 'mastodon/components/icon_button';
@ -20,7 +21,12 @@ const messages = defineMessages({
next: { id: 'lightbox.next', defaultMessage: 'Next' },
});
export default @injectIntl
const mapStateToProps = (state, { statusId }) => ({
language: state.getIn(['statuses', statusId, 'language']),
});
export default @connect(mapStateToProps, null, null, { forwardRef: true })
@injectIntl
class MediaModal extends ImmutablePureComponent {
static propTypes = {
@ -129,7 +135,7 @@ class MediaModal extends ImmutablePureComponent {
};
render () {
const { media, statusId, intl, onClose } = this.props;
const { media, language, statusId, intl, onClose } = this.props;
const { navigationHidden } = this.state;
const index = this.getIndex();
@ -149,6 +155,7 @@ class MediaModal extends ImmutablePureComponent {
width={width}
height={height}
alt={image.get('description')}
lang={language}
key={image.get('url')}
onClick={this.toggleNavigation}
zoomButtonHidden={this.state.zoomButtonHidden}
@ -171,6 +178,7 @@ class MediaModal extends ImmutablePureComponent {
onCloseVideo={onClose}
detailed
alt={image.get('description')}
lang={language}
key={image.get('url')}
/>
);
@ -182,6 +190,7 @@ class MediaModal extends ImmutablePureComponent {
height={height}
key={image.get('preview_url')}
alt={image.get('description')}
lang={language}
onClick={this.toggleNavigation}
/>
);

View file

@ -82,8 +82,8 @@ class NavigationPanel extends React.Component {
{signedIn && (
<React.Fragment>
<ColumnLink transparent to='/conversations' icon='at' text={intl.formatMessage(messages.direct)} />
<ColumnLink transparent to='/favourites' icon='star' text={intl.formatMessage(messages.favourites)} />
<ColumnLink transparent to='/bookmarks' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} />
<ColumnLink transparent to='/favourites' icon='star' text={intl.formatMessage(messages.favourites)} />
<ColumnLink transparent to='/lists' icon='list-ul' text={intl.formatMessage(messages.lists)} />
<ListPanel />

View file

@ -2,15 +2,22 @@ import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import Video from 'mastodon/features/video';
import { connect } from 'react-redux';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Footer from 'mastodon/features/picture_in_picture/components/footer';
import { getAverageFromBlurhash } from 'mastodon/blurhash';
export default class VideoModal extends ImmutablePureComponent {
const mapStateToProps = (state, { statusId }) => ({
language: state.getIn(['statuses', statusId, 'language']),
});
export default @connect(mapStateToProps, null, null, { forwardRef: true })
class VideoModal extends ImmutablePureComponent {
static propTypes = {
media: ImmutablePropTypes.map.isRequired,
statusId: PropTypes.string,
language: PropTypes.string,
options: PropTypes.shape({
startTime: PropTypes.number,
autoPlay: PropTypes.bool,
@ -31,7 +38,7 @@ export default class VideoModal extends ImmutablePureComponent {
}
render () {
const { media, statusId, onClose } = this.props;
const { media, statusId, language, onClose } = this.props;
const options = this.props.options || {};
return (
@ -49,6 +56,7 @@ export default class VideoModal extends ImmutablePureComponent {
autoFocus
detailed
alt={media.get('description')}
lang={language}
/>
</div>

View file

@ -96,6 +96,7 @@ class ZoomableImage extends React.PureComponent {
static propTypes = {
alt: PropTypes.string,
lang: PropTypes.string,
src: PropTypes.string.isRequired,
width: PropTypes.number,
height: PropTypes.number,
@ -106,6 +107,7 @@ class ZoomableImage extends React.PureComponent {
static defaultProps = {
alt: '',
lang: '',
width: null,
height: null,
};
@ -403,7 +405,7 @@ class ZoomableImage extends React.PureComponent {
};
render () {
const { alt, src, width, height, intl } = this.props;
const { alt, lang, src, width, height, intl } = this.props;
const { scale, lockTranslate } = this.state;
const overflow = scale === MIN_SCALE ? 'hidden' : 'scroll';
const zoomButtonShouldHide = this.state.navigationHidden || this.props.zoomButtonHidden || this.state.zoomMatrix.rate <= MIN_SCALE ? 'media-modal__zoom-button--hidden' : '';
@ -431,6 +433,7 @@ class ZoomableImage extends React.PureComponent {
ref={this.setImageRef}
alt={alt}
title={alt}
lang={lang}
src={src}
width={width}
height={height}

View file

@ -474,10 +474,10 @@ class UI extends React.PureComponent {
};
handleHotkeyBack = () => {
if (window.history && window.history.length === 1) {
this.context.router.history.push('/');
} else {
if (window.history && window.history.state) {
this.context.router.history.goBack();
} else {
this.context.router.history.push('/');
}
};

View file

@ -102,6 +102,7 @@ class Video extends React.PureComponent {
frameRate: PropTypes.string,
src: PropTypes.string.isRequired,
alt: PropTypes.string,
lang: PropTypes.string,
width: PropTypes.number,
height: PropTypes.number,
sensitive: PropTypes.bool,
@ -524,7 +525,7 @@ class Video extends React.PureComponent {
}
render () {
const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, detailed, sensitive, editable, blurhash, autoFocus } = this.props;
const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, lang, detailed, sensitive, editable, blurhash, autoFocus } = this.props;
const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
const progress = Math.min((currentTime / duration) * 100, 100);
const playerStyle = {};
@ -585,6 +586,7 @@ class Video extends React.PureComponent {
tabIndex='0'
aria-label={alt}
title={alt}
lang={lang}
width={width}
height={height}
volume={volume}

View file

@ -82,7 +82,6 @@
* @property {boolean=} use_pending_items
* @property {string} version
* @property {number} visible_reactions
* @property {boolean} translation_enabled
*/
/**
@ -136,7 +135,6 @@ export const useBlurhash = getMeta('use_blurhash');
export const usePendingItems = getMeta('use_pending_items');
export const version = getMeta('version');
export const visibleReactions = getMeta('visible_reactions');
export const translationEnabled = getMeta('translation_enabled');
export const languages = initialState?.languages;
export const statusPageUrl = getMeta('status_page_url');

View file

@ -138,7 +138,7 @@
"compose_form.poll.remove_option": "إزالة هذا الخيار",
"compose_form.poll.switch_to_multiple": "تغيِير الاستطلاع للسماح باِخيارات مُتعدِّدة",
"compose_form.poll.switch_to_single": "تغيِير الاستطلاع للسماح باِخيار واحد فقط",
"compose_form.publish": "انشر",
"compose_form.publish": "نشر",
"compose_form.publish_form": "انشر",
"compose_form.publish_loud": "{publish}!",
"compose_form.save_changes": "احفظ التعديلات",
@ -176,7 +176,7 @@
"conversation.delete": "احذف المحادثة",
"conversation.mark_as_read": "اعتبرها كمقروءة",
"conversation.open": "اعرض المحادثة",
"conversation.with": "بـ {names}",
"conversation.with": "مع {names}",
"copypaste.copied": "تم نسخه",
"copypaste.copy": "انسخ",
"directory.federated": "مِن الفديفرس المعروف",
@ -215,7 +215,7 @@
"empty_column.bookmarked_statuses": "ليس لديك أية منشورات في الفواصل المرجعية بعد. عندما ستقوم بإضافة البعض منها، ستظهر هنا.",
"empty_column.community": "الخط العام المحلي فارغ. أكتب شيئا ما للعامة كبداية!",
"empty_column.direct": "لم تتلق أية رسالة خاصة مباشِرة بعد. سوف يتم عرض الرسائل المباشرة هنا إن قمت بإرسال واحدة أو تلقيت البعض منها.",
"empty_column.domain_blocks": "ليس هناك نطاقات مخفية بعد.",
"empty_column.domain_blocks": "ليس هناك نطاقات تم حجبها بعد.",
"empty_column.explore_statuses": "ليس هناك ما هو متداوَل الآن. عد في وقت لاحق!",
"empty_column.favourited_statuses": "ليس لديك أية منشورات مفضلة بعد. عندما ستقوم بالإعجاب بواحدة، ستظهر هنا.",
"empty_column.favourites": "لم يقم أي أحد بالإعجاب بهذا المنشور بعد. عندما يقوم أحدهم بذلك سوف يظهر هنا.",
@ -245,7 +245,7 @@
"filter_modal.added.context_mismatch_explanation": "فئة عامل التصفية هذه لا تنطبق على السياق الذي وصلت فيه إلى هذه المشاركة. إذا كنت ترغب في تصفية المنشور في هذا السياق أيضا، فسيتعين عليك تعديل عامل التصفية.",
"filter_modal.added.context_mismatch_title": "عدم تطابق السياق!",
"filter_modal.added.expired_explanation": "انتهت صلاحية فئة عامل التصفية هذه، سوف تحتاج إلى تغيير تاريخ انتهاء الصلاحية لتطبيقها.",
"filter_modal.added.expired_title": "تصفية منتهية الصلاحية!",
"filter_modal.added.expired_title": "عامل تصفية انتهت صلاحيته!",
"filter_modal.added.review_and_configure": "لمراجعة وزيادة تكوين فئة عوامل التصفية هذه، انتقل إلى {settings_link}.",
"filter_modal.added.review_and_configure_title": "إعدادات التصفية",
"filter_modal.added.settings_link": "صفحة الإعدادات",

View file

@ -383,7 +383,7 @@
"navigation_bar.filters": "Pallabres desactivaes",
"navigation_bar.follow_requests": "Solicitúes de siguimientu",
"navigation_bar.followed_tags": "Followed hashtags",
"navigation_bar.follows_and_followers": "Follows and followers",
"navigation_bar.follows_and_followers": "Perfiles que sigues ya te siguen",
"navigation_bar.lists": "Llistes",
"navigation_bar.logout": "Zarrar la sesión",
"navigation_bar.mutes": "Perfiles colos avisos desactivaos",

View file

@ -16,7 +16,7 @@
"account.badges.bot": "Bot",
"account.badges.group": "Gruppe",
"account.block": "@{name} blockieren",
"account.block_domain": "Alles von {domain} verstecken",
"account.block_domain": "{domain} sperren",
"account.blocked": "Blockiert",
"account.browse_more_on_origin_server": "Mehr auf dem Originalprofil durchsuchen",
"account.cancel_follow_request": "Folgeanfrage zurückziehen",

View file

@ -32,9 +32,9 @@
"account.follow": "Seguir",
"account.followers": "Seguidores",
"account.followers.empty": "Todavía nadie sigue a este usuario.",
"account.followers_counter": "{count, plural, one {{counter} Seguidor} other {{counter} Seguidores}}",
"account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}",
"account.following": "Siguiendo",
"account.following_counter": "{count, plural, other {{counter} Siguiendo}}",
"account.following_counter": "{count, plural, other {Siguiendo a {counter}}}",
"account.follows.empty": "Todavía este usuario no sigue a nadie.",
"account.follows_you": "Te sigue",
"account.go_to_profile": "Ir al perfil",
@ -57,7 +57,7 @@
"account.requested_follow": "{name} solicitó seguirte",
"account.share": "Compartir el perfil de @{name}",
"account.show_reblogs": "Mostrar adhesiones de @{name}",
"account.statuses_counter": "{count, plural, one {{counter} Mensaje} other {{counter} Mensajes}}",
"account.statuses_counter": "{count, plural, one {{counter} mensaje} other {{counter} mensajes}}",
"account.unblock": "Desbloquear a @{name}",
"account.unblock_domain": "Desbloquear dominio {domain}",
"account.unblock_short": "Desbloquear",

View file

@ -51,7 +51,7 @@
"account.muted": "Silenciado",
"account.open_original_page": "Abrir página original",
"account.posts": "Publicaciones",
"account.posts_with_replies": "Publicaciones y respuestas",
"account.posts_with_replies": "Pub. y respuestas",
"account.report": "Reportar a @{name}",
"account.requested": "Esperando aprobación. Clica para cancelar la solicitud de seguimiento",
"account.requested_follow": "{name} ha solicitado seguirte",

View file

@ -29,7 +29,7 @@
"account.featured_tags.last_status_at": "Viimeisin viesti {date}",
"account.featured_tags.last_status_never": "Ei viestejä",
"account.featured_tags.title": "Käyttäjän {name} esillä olevat aihetunnisteet",
"account.follow": "Seuratut",
"account.follow": "Seuraa",
"account.followers": "Seuraajat",
"account.followers.empty": "Kukaan ei seuraa tätä käyttäjää vielä.",
"account.followers_counter": "{count, plural, one {{counter} seuraaja} other {{counter} seuraajaa}}",
@ -582,7 +582,7 @@
"status.reblogs.empty": "Kukaan ei ole vielä tehostanut tätä viestiä. Kun joku tekee niin, näkyy kyseinen henkilö tässä.",
"status.redraft": "Poista ja palauta muokattavaksi",
"status.remove_bookmark": "Poista kirjanmerkki",
"status.replied_to": "Vastasit käyttäjälle {name}",
"status.replied_to": "Vastaus käyttäjälle {name}",
"status.reply": "Vastaa",
"status.replyAll": "Vastaa ketjuun",
"status.report": "Raportoi @{name}",

View file

@ -54,7 +54,7 @@
"account.posts_with_replies": "टूट्स एवं जवाब",
"account.report": "रिपोर्ट @{name}",
"account.requested": "मंजूरी का इंतजार। फॉलो रिक्वेस्ट को रद्द करने के लिए क्लिक करें",
"account.requested_follow": "{name} has requested to follow you",
"account.requested_follow": "{name} ने आपको फॉलो करने के लिए अनुरोध किया है",
"account.share": "@{name} की प्रोफाइल शेयर करे",
"account.show_reblogs": "@{name} के बूस्ट दिखाए",
"account.statuses_counter": "{count, plural, one {{counter} भोंपू} other {{counter} भोंपू}}",
@ -128,7 +128,7 @@
"compose.language.search": "भाषाएँ खोजें...",
"compose_form.direct_message_warning_learn_more": "और जानें",
"compose_form.encryption_warning": "मास्टोडॉन पर पोस्ट एन्ड-टू-एन्ड एन्क्रिप्टेड नहीं है। कोई भी व्यक्तिगत जानकारी मास्टोडॉन पर मत भेजें।",
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
"compose_form.hashtag_warning": "ये पोस्ट किसी भी हैशटैग में लिस्ट नहीं किया जाएगा क्योंकि ये पब्लिक नहीं है। सिर्फ पब्लिक पोस्ट ही हैशटैग से खोजे जा सकते हैं।",
"compose_form.lock_disclaimer": "आपका खाता {locked} नहीं है। आपको केवल फॉलोवर्स को दिखाई दिए जाने वाले पोस्ट देखने के लिए कोई भी फॉलो कर सकता है।",
"compose_form.lock_disclaimer.lock": "लॉक्ड",
"compose_form.placeholder": "What is on your mind?",
@ -221,7 +221,7 @@
"empty_column.favourites": "अभी तक किसी ने भी इस टूट को पसंद (स्टार) नहीं किया है. जब भी कोई इसे पसंद करेगा, उनका नाम यहाँ दिखेगा।",
"empty_column.follow_recommendations": "ऐसा लगता है कि आपके लिए कोई सुझाव जेनरेट नहीं किया जा सका. आप उन लोगों को खोजने के लिए सर्च का उपयोग करने का प्रयास कर सकते हैं जिन्हें आप जानते हैं या ट्रेंडिंग हैशटैग का पता लगा सकते हैं।",
"empty_column.follow_requests": "अभी तक किसी ने भी आपका अनुसरण करने की विनती नहीं की है. जब भी कोई आपको विनती भेजेगा, वो यहाँ दिखेगी.",
"empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
"empty_column.followed_tags": "आपने किसी हैशटैग को फॉलो नहीं किया है। जैसे ही आप फॉलो करेंगे, आपके फॉलो किए गए हैशटैग यहां दिखेंगे।",
"empty_column.hashtag": "यह हैशटैग अभी तक खाली है।",
"empty_column.home": "आपकी मुख्य कालक्रम अभी खली है. अन्य उपयोगकर्ताओं से मिलने के लिए और अपनी गतिविधियां शुरू करने के लिए या तो {public} पर जाएं या खोज का उपयोग करें।",
"empty_column.home.suggestions": "कुछ सुझाव देखिए",
@ -240,8 +240,8 @@
"explore.suggested_follows": "आपके लिए",
"explore.title": "एक्स्प्लोर",
"explore.trending_links": "समाचार",
"explore.trending_statuses": "Posts",
"explore.trending_tags": "Hashtags",
"explore.trending_statuses": "पोस्ट्स",
"explore.trending_tags": "हैशटैग्स",
"filter_modal.added.context_mismatch_explanation": "यह फ़िल्टर श्रेणी उस संदर्भ पर लागू नहीं होती जिसमें आपने इस पोस्ट को एक्सेस किया है। यदि आप चाहते हैं कि इस संदर्भ में भी पोस्ट को फ़िल्टर किया जाए, तो आपको फ़िल्टर को एडिट करना होगा।",
"filter_modal.added.context_mismatch_title": "कंटेंट मिसमैच!",
"filter_modal.added.expired_explanation": "यह फ़िल्टर श्रेणी समाप्त हो गई है, इसे लागू करने के लिए आपको समाप्ति तिथि बदलनी होगी।",
@ -264,7 +264,7 @@
"follow_request.authorize": "अधिकार दें",
"follow_request.reject": "अस्वीकार करें",
"follow_requests.unlocked_explanation": "हालाँकि आपका खाता लॉक नहीं है, फिर भी {domain} डोमेन स्टाफ ने सोचा कि आप इन खातों के मैन्युअल अनुरोधों की समीक्षा करना चाहते हैं।",
"followed_tags": "Followed hashtags",
"followed_tags": "फॉलो किए गए हैशटैग्स",
"footer.about": "अबाउट",
"footer.directory": "प्रोफाइल्स डायरेक्टरी",
"footer.get_app": "अप्प प्राप्त करें",
@ -272,7 +272,7 @@
"footer.keyboard_shortcuts": "कीबोर्ड शॉर्टकट",
"footer.privacy_policy": "प्राइवेसी पालिसी",
"footer.source_code": "सोर्स कोड देखें",
"footer.status": "Status",
"footer.status": "स्टेटस",
"generic.saved": "सेव्ड",
"getting_started.heading": "पहले कदम रखें",
"hashtag.column_header.tag_mode.all": "और {additional}",

View file

@ -312,7 +312,7 @@
"keyboard_shortcuts.column": "Setja virkni í dálk",
"keyboard_shortcuts.compose": "Setja virkni á textainnsetningarreit",
"keyboard_shortcuts.description": "Lýsing",
"keyboard_shortcuts.direct": "að opna dálk með beinum skilaboðum",
"keyboard_shortcuts.direct": "Opna dálk með beinum skilaboðum",
"keyboard_shortcuts.down": "Fara neðar í listanum",
"keyboard_shortcuts.enter": "Opna færslu",
"keyboard_shortcuts.favourite": "Eftirlætisfærsla",

View file

@ -474,7 +474,7 @@
"relative_time.full.seconds": "{number, plural, one {# secondo} other {# secondi}} fa",
"relative_time.hours": "{number}h",
"relative_time.just_now": "ora",
"relative_time.minutes": "{number} minuti",
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"relative_time.today": "oggi",
"reply_indicator.cancel": "Annulla",

View file

@ -10,7 +10,7 @@
"about.domain_blocks.suspended.title": "Suspended",
"about.not_available": "This information has not been made available on this server.",
"about.powered_by": "Decentralized social media powered by {mastodon}",
"about.rules": "Server rules",
"about.rules": "Ilugan n uqeddac",
"account.account_note_header": "Tazmilt",
"account.add_or_remove_from_list": "Rnu neɣ kkes seg tebdarin",
"account.badges.bot": "Aṛubut",
@ -83,7 +83,7 @@
"boost_modal.combo": "Tzemreḍ ad tetekkiḍ ɣef {combo} akken ad tessurfeḍ aya tikelt-nniḍen",
"bundle_column_error.copy_stacktrace": "Nɣel tuccḍa n uneqqis",
"bundle_column_error.error.body": "The requested page could not be rendered. It could be due to a bug in our code, or a browser compatibility issue.",
"bundle_column_error.error.title": "Oh, no!",
"bundle_column_error.error.title": "Uh, ala !",
"bundle_column_error.network.body": "There was an error when trying to load this page. This could be due to a temporary problem with your internet connection or this server.",
"bundle_column_error.network.title": "Tuccḍa deg uẓeṭṭa",
"bundle_column_error.retry": "Ɛreḍ tikelt-nniḍen",

View file

@ -145,7 +145,7 @@
"compose_form.sensitive.hide": "미디어를 민감함으로 설정하기",
"compose_form.sensitive.marked": "미디어가 열람주의로 설정되어 있습니다",
"compose_form.sensitive.unmarked": "미디어가 열람주의로 설정 되어 있지 않습니다",
"compose_form.spoiler.marked": "콘텐츠 경고 제거",
"compose_form.spoiler.marked": "열람주의 제거",
"compose_form.spoiler.unmarked": "열람 주의 문구 추가",
"compose_form.spoiler_placeholder": "경고 문구를 여기에 작성하세요",
"confirmation_modal.cancel": "취소",
@ -224,7 +224,7 @@
"empty_column.followed_tags": "아직 아무 해시태그도 팔로우하고 있지 않습니다. 해시태그를 팔로우하면, 여기에 표시됩니다.",
"empty_column.hashtag": "이 해시태그는 아직 사용되지 않았습니다.",
"empty_column.home": "당신의 홈 타임라인은 비어있습니다! 더 많은 사람들을 팔로우 하여 채워보세요. {suggestions}",
"empty_column.home.suggestions": "몇몇 제안 보기",
"empty_column.home.suggestions": "몇몇 제안 보기",
"empty_column.list": "리스트에 아직 아무것도 없습니다. 리스트의 누군가가 게시물을 올리면 여기에 나타납니다.",
"empty_column.lists": "아직 리스트가 없습니다. 리스트를 만들면 여기에 나타납니다.",
"empty_column.mutes": "아직 아무도 뮤트하지 않았습니다.",
@ -259,7 +259,7 @@
"filter_modal.select_filter.title": "이 게시물을 필터",
"filter_modal.title.status": "게시물 필터",
"follow_recommendations.done": "완료",
"follow_recommendations.heading": "게시물을 받아 볼 사람을 팔로우 하세요! 여기 몇몇 추천이 있습니다.",
"follow_recommendations.heading": "게시물을 받아 볼 사람을 팔로우하세요! 여기 몇몇 추천이 있습니다.",
"follow_recommendations.lead": "당신이 팔로우 하는 사람들의 게시물이 시간순으로 정렬되어 당신의 홈 피드에 표시될 것입니다. 실수를 두려워 하지 마세요, 언제든지 쉽게 팔로우 취소를 할 수 있습니다!",
"follow_request.authorize": "허가",
"follow_request.reject": "거부",
@ -400,7 +400,7 @@
"notification.follow": "{name} 님이 나를 팔로우했습니다",
"notification.follow_request": "{name} 님이 팔로우 요청을 보냈습니다",
"notification.mention": "{name}님의 멘션",
"notification.own_poll": "내 투표가 끝났습니다",
"notification.own_poll": "투표를 마쳤습니다.",
"notification.poll": "참여했던 투표가 끝났습니다.",
"notification.reblog": "{name} 님이 부스트했습니다",
"notification.status": "{name} 님이 방금 게시물을 올렸습니다",
@ -589,13 +589,13 @@
"status.sensitive_warning": "민감한 내용",
"status.share": "공유",
"status.show_filter_reason": "그냥 표시하기",
"status.show_less": "적게 보기",
"status.show_less": "기",
"status.show_less_all": "모두 접기",
"status.show_more": "더 보기",
"status.show_more": "펼치기",
"status.show_more_all": "모두 펼치기",
"status.show_original": "원본 보기",
"status.translate": "번역",
"status.translated_from_with": "{lang}에서 {provider}를 사용해 번역됨",
"status.translated_from_with": "{provider}에 의해 {lang}에서 번역됨",
"status.uncached_media_warning": "사용할 수 없음",
"status.unmute_conversation": "이 대화의 뮤트 해제하기",
"status.unpin": "고정 해제",

View file

@ -1,7 +1,7 @@
{
"about.blocks": "Moderatorių prižiūrimi serveriai",
"about.contact": "Kontaktai:",
"about.disclaimer": "Mastodon is free, open-source software, and a trademark of Mastodon gGmbH.",
"about.disclaimer": "Mastodon, tai nemokama, atviro kodo programa, kuriuos prekybinis ženklas priklauso Mastodon GmbH.",
"about.domain_blocks.no_reason_available": "Priežastis nežinoma",
"about.domain_blocks.preamble": "Mastodon generally allows you to view content from and interact with users from any other server in the fediverse. These are the exceptions that have been made on this particular server.",
"about.domain_blocks.silenced.explanation": "You will generally not see profiles and content from this server, unless you explicitly look it up or opt into it by following.",

View file

@ -128,7 +128,7 @@
"compose.language.search": "Cari bahasa...",
"compose_form.direct_message_warning_learn_more": "Ketahui lebih lanjut",
"compose_form.encryption_warning": "Hantaran pada Mastodon tidak disulitkan hujung ke hujung. Jangan berkongsi sebarang maklumat sensitif melalui Mastodon.",
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
"compose_form.hashtag_warning": "Hantaran ini tidak akan disenaraikan di bawah mana-mana tanda pagar kerana ia tidak tersenarai. Hanya hantaran awam sahaja boleh dicari menggunakan tanda pagar.",
"compose_form.lock_disclaimer": "Akaun anda tidak {locked}. Sesiapa pun boleh mengikuti anda untuk melihat hantaran pengikut-sahaja anda.",
"compose_form.lock_disclaimer.lock": "dikunci",
"compose_form.placeholder": "Apakah yang sedang anda fikirkan?",
@ -237,11 +237,11 @@
"errors.unexpected_crash.copy_stacktrace": "Salin surih tindanan ke papan keratan",
"errors.unexpected_crash.report_issue": "Laporkan masalah",
"explore.search_results": "Hasil carian",
"explore.suggested_follows": "For you",
"explore.suggested_follows": "Untuk anda",
"explore.title": "Terokai",
"explore.trending_links": "News",
"explore.trending_statuses": "Posts",
"explore.trending_tags": "Hashtags",
"explore.trending_links": "Baru",
"explore.trending_statuses": "Hantar",
"explore.trending_tags": "-Hashtags",
"filter_modal.added.context_mismatch_explanation": "Kumpulan penapis ini tidak terpakai pada konteks di mana anda mengakses hantaran ini. Jika anda ingin hantaran ini untuk ditapis dalam konteks ini juga, anda perlu menyunting penapis tersebut.",
"filter_modal.added.context_mismatch_title": "Konteks tidak sepadan!",
"filter_modal.added.expired_explanation": "Kumpulan filter ini telah tamat tempoh, anda perlu mengubah tarikh luput untuk melaksanakannya.",
@ -264,7 +264,7 @@
"follow_request.authorize": "Benarkan",
"follow_request.reject": "Tolak",
"follow_requests.unlocked_explanation": "Walaupun akaun anda tidak dikunci, kakitangan {domain} merasakan anda mungkin ingin menyemak permintaan ikutan daripada akaun ini secara manual.",
"followed_tags": "Followed hashtags",
"followed_tags": "Topik terpilih",
"footer.about": "Perihal",
"footer.directory": "Direktori profil",
"footer.get_app": "Dapatkan app",
@ -272,7 +272,7 @@
"footer.keyboard_shortcuts": "Pintasan papan kekunci",
"footer.privacy_policy": "Dasar privasi",
"footer.source_code": "Lihat kod sumber",
"footer.status": "Status",
"footer.status": "Status:",
"generic.saved": "Disimpan",
"getting_started.heading": "Mari bermula",
"hashtag.column_header.tag_mode.all": "dan {additional}",
@ -382,7 +382,7 @@
"navigation_bar.favourites": "Kegemaran",
"navigation_bar.filters": "Perkataan yang dibisukan",
"navigation_bar.follow_requests": "Permintaan ikutan",
"navigation_bar.followed_tags": "Followed hashtags",
"navigation_bar.followed_tags": "Ikuti hashtag",
"navigation_bar.follows_and_followers": "Ikutan dan pengikut",
"navigation_bar.lists": "Senarai",
"navigation_bar.logout": "Log keluar",
@ -544,9 +544,9 @@
"server_banner.server_stats": "Statistik pelayan:",
"sign_in_banner.create_account": "Cipta akaun",
"sign_in_banner.sign_in": "Daftar masuk",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
"sign_in_banner.text": "Daftar masuk untuk mengikut profil atau tanda pagar, menggemari, mengkongsi dan membalas kepada hantaran, atau berinteraksi daripada akaun anda pada pelayan lain.",
"status.admin_account": "Buka antara muka penyederhanaan untuk @{name}",
"status.admin_domain": "Open moderation interface for {domain}",
"status.admin_domain": "antara muka penyederhanaan",
"status.admin_status": "Buka hantaran ini dalam antara muka penyederhanaan",
"status.block": "Sekat @{name}",
"status.bookmark": "Tanda buku",

View file

@ -102,7 +102,7 @@
"column.blocks": "ဘလော့ထားသောအကောင့်များ",
"column.bookmarks": "မှတ်တမ်းများ",
"column.community": "ဒေသတွင်း အချိန်ဇယား",
"column.direct": "တိုက်ရိုက် မက်ဆေ့ခ်ျများ",
"column.direct": "တိုက်ရိုက်မက်ဆေ့ခ်ျများ",
"column.directory": "ပရိုဖိုင်များကို ရှာဖွေမည်\n",
"column.domain_blocks": " ဒိုမိန်းကိုပိတ်မည်",
"column.favourites": "အကြိုက်ဆုံးများ",
@ -121,7 +121,7 @@
"column_header.show_settings": "ဆက်တင်များကို ပြပါ။",
"column_header.unpin": "မတွဲတော့ပါ",
"column_subheading.settings": "ဆက်တင်များ",
"community.column_settings.local_only": "Local only",
"community.column_settings.local_only": "ပြည်တွင်း၌သာ",
"community.column_settings.media_only": "Media only",
"community.column_settings.remote_only": "Remote only",
"compose.language.change": "ဘာသာစကား ပြောင်းမည်",
@ -195,32 +195,32 @@
"embed.preview": "Here is what it will look like:",
"emoji_button.activity": "Activity",
"emoji_button.clear": "ရှင်းလင်းမည်",
"emoji_button.custom": "Custom",
"emoji_button.custom": "စိတ်ကြိုက်",
"emoji_button.flags": "Flags",
"emoji_button.food": "အစားအသောက်",
"emoji_button.label": "Insert emoji",
"emoji_button.label": "အီမိုဂျီထည့်ပါ",
"emoji_button.nature": "သဘာဝ",
"emoji_button.not_found": "No matching emojis found",
"emoji_button.not_found": "ကိုက်ညီသော အီမိုဂျီများ ရှာမတွေ့ပါ",
"emoji_button.objects": "Objects",
"emoji_button.people": "လူများ",
"emoji_button.recent": "မကြာခဏ အသုံးပြုသော",
"emoji_button.search": "ရှာရန်...",
"emoji_button.search_results": "Search results",
"emoji_button.symbols": "Symbols",
"emoji_button.travel": "Travel & Places",
"empty_column.account_suspended": "Account suspended",
"emoji_button.search_results": "ရှာဖွေမှု ရလဒ်များ",
"emoji_button.symbols": "သင်္ကေတများ",
"emoji_button.travel": "ခရီးသွားခြင်း နှင့် နေရာများ",
"empty_column.account_suspended": "အကောင့်ရပ်ဆိုင်းထားသည်",
"empty_column.account_timeline": "No posts found",
"empty_column.account_unavailable": "Profile unavailable",
"empty_column.account_unavailable": "ပရိုဖိုင် မရနိုင်ပါ",
"empty_column.blocks": "ပိတ်ထားသောအကောင့်များမရှိသေးပါ",
"empty_column.bookmarked_statuses": "You don't have any bookmarked posts yet. When you bookmark one, it will show up here.",
"empty_column.bookmarked_statuses": "သင့်တွင် မှတ်သားထားသော ပို့စ်များ မရှိသေးပါ။ တစ်ခုကို အမှတ်အသားပြုလိုက်ပါက ၎င်းကို ဤနေရာတွင် ပြထားပါမည်။",
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
"empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
"empty_column.direct": "သင့်တွင် တိုက်ရိုက်မက်ဆေ့ချ်များ မရှိသေးပါ။ ပေးပို့ထားပါက သို့မဟုတ် လက်ခံထားပါက ၎င်းတို့ကို ဤနေရာတွင် ပြထားပါမည်။",
"empty_column.domain_blocks": "သင်ပိတ်ထားသော ဒိုမိန်းမရှိသေးပါ",
"empty_column.explore_statuses": "Nothing is trending right now. Check back later!",
"empty_column.favourited_statuses": "You don't have any favourite posts yet. When you favourite one, it will show up here.",
"empty_column.favourites": "No one has favourited this post yet. When someone does, they will show up here.",
"empty_column.explore_statuses": "အခုလောလောဆယ်တွင် ရေပန်းစားနေသောပို့စ်များ မရှိသေးပါ။ နောက်မှ ပြန်စစ်ဆေးပါရန်။",
"empty_column.favourited_statuses": "သင့်တွင် အကြိုက်ဆုံးပို့စ်များ မရှိသေးပါ။ တစ်ခုကို သင်နှစ်သက်ထားပါက ၎င်းကို ဤနေရာတွင် ပြထားပါမည်။",
"empty_column.favourites": "ဤပို့စ်ကို အကြိုက်တွေ့သူ မရှိသေးပါ။ တစ်စုံတစ်ယောက်က ကြိုက်နှစ်သက်ပါက ဤနေရာတွင် ပြထားပါမည်။",
"empty_column.follow_recommendations": "Looks like no suggestions could be generated for you. You can try using search to look for people you might know or explore trending hashtags.",
"empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
"empty_column.follow_requests": "သင့်တွင် စောင့်ကြည့်ရန် တောင်းဆိုမှုများ မရှိသေးပါ။ သင်လက်ခံရရှိပါက ၎င်းကို ဤနေရာတွင် ပြထားပါမည်။",
"empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
"empty_column.hashtag": "There is nothing in this hashtag yet.",
"empty_column.home": "Your home timeline is empty! Follow more people to fill it up. {suggestions}",
@ -231,12 +231,12 @@
"empty_column.notifications": "သတိပေးချက်မရှိသေးပါ။ သတိပေးချက်အသစ်ရှိလျှင် ဤနေရာတွင်ကြည့်ရှုနိုင်သည်",
"empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up",
"error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
"error.unexpected_crash.explanation_addons": "This page could not be displayed correctly. This error is likely caused by a browser add-on or automatic translation tools.",
"error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
"error.unexpected_crash.explanation_addons": "ဤစာမျက်နှာကို မှန်ကန်စွာ မပြသနိုင်ခဲ့ပါ။ ဤအမှားသည် Browser add-on ထည့်သွင်းထားမှုများ သို့မဟုတ် အလိုအလျောက် ဘာသာပြန်ကိရိယာများကြောင့် ဖြစ်နိုင်သည်။",
"error.unexpected_crash.next_steps": "စာမျက်နှာကို ပြန်လည်စတင်ကြည့်ပါ။ ၎င်းမှာ အကူအညီမဖြစ်ပါက သင်သည် အခြား Browser သို့မဟုတ် မူရင်းအက်ပ်မှတစ်ဆင့် Mastodon ကို ဆက်လက်အသုံးပြုနိုင်ပါမည်။",
"error.unexpected_crash.next_steps_addons": "Try disabling them and refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
"errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
"errors.unexpected_crash.report_issue": "Report issue",
"explore.search_results": "Search results",
"errors.unexpected_crash.report_issue": "အဆင်မပြေမှုကို တိုင်ကြားရန်",
"explore.search_results": "ရှာဖွေမှုရလဒ်များ",
"explore.suggested_follows": "သင့်အတွက်",
"explore.title": "စူးစမ်းရန်",
"explore.trending_links": "သတင်းများ",
@ -247,50 +247,50 @@
"filter_modal.added.expired_explanation": "This filter category has expired, you will need to change the expiration date for it to apply.",
"filter_modal.added.expired_title": "Expired filter!",
"filter_modal.added.review_and_configure": "To review and further configure this filter category, go to the {settings_link}.",
"filter_modal.added.review_and_configure_title": "Filter settings",
"filter_modal.added.review_and_configure_title": "စစ်ထုတ်မှု သတ်မှတ်ချက်များ",
"filter_modal.added.settings_link": "settings page",
"filter_modal.added.short_explanation": "This post has been added to the following filter category: {title}.",
"filter_modal.added.title": "Filter added!",
"filter_modal.added.short_explanation": "ဤပို့စ်ကို အောက်ပါ စစ်ထုတ်မှုအမျိုးအစားတွင် ပေါင်းထည့်ထားပါသည် - {title}။",
"filter_modal.added.title": "စစ်ထုတ်မှု ထည့်သွင်းပြီးပါပြီ။",
"filter_modal.select_filter.context_mismatch": "does not apply to this context",
"filter_modal.select_filter.expired": "သက်တမ်းကုန်သွားပါပြီ",
"filter_modal.select_filter.prompt_new": "New category: {name}",
"filter_modal.select_filter.search": "Search or create",
"filter_modal.select_filter.subtitle": "Use an existing category or create a new one",
"filter_modal.select_filter.title": "Filter this post",
"filter_modal.title.status": "Filter a post",
"follow_recommendations.done": "Done",
"follow_recommendations.heading": "Follow people you'd like to see posts from! Here are some suggestions.",
"filter_modal.select_filter.prompt_new": "အမျိုးအစားအသစ် - {name}",
"filter_modal.select_filter.search": "ရှာရန် သို့မဟုတ် ဖန်တီးရန်",
"filter_modal.select_filter.subtitle": "ရှိပြီးသားအမျိုးအစားကို သုံးပါ သို့မဟုတ် အသစ်တစ်ခု ဖန်တီးပါ",
"filter_modal.select_filter.title": "ဤပို့စ်ကို စစ်ထုတ်ပါ",
"filter_modal.title.status": "ပို့စ်တစ်ခု စစ်ထုတ်ပါ",
"follow_recommendations.done": "ပြီးပါပြီ",
"follow_recommendations.heading": "သင်မြင်လိုသော သူများထံမှ ပို့စ်များကို စောင့်ကြည့်ပါ။ ဤတွင် အကြံပြုချက်အချို့ရှိပါသည်။",
"follow_recommendations.lead": "Posts from people you follow will show up in chronological order on your home feed. Don't be afraid to make mistakes, you can unfollow people just as easily any time!",
"follow_request.authorize": "Authorize",
"follow_request.reject": "Reject",
"follow_request.reject": "ဖယ်ရှားပါ",
"follow_requests.unlocked_explanation": "Even though your account is not locked, the {domain} staff thought you might want to review follow requests from these accounts manually.",
"followed_tags": "Followed hashtags",
"footer.about": "အကြောင်း",
"footer.directory": "Profiles directory",
"footer.get_app": "Get the app",
"footer.invite": "Invite people",
"footer.keyboard_shortcuts": "Keyboard shortcuts",
"footer.privacy_policy": "Privacy policy",
"footer.directory": "ပရိုဖိုင်များလမ်းညွှန်",
"footer.get_app": "အက်ပ်ကို ရယူပါ",
"footer.invite": "လူများကို ဖိတ်ပါ",
"footer.keyboard_shortcuts": "ကီးဘုတ်အမြန်ခလုတ်များ",
"footer.privacy_policy": "ကိုယ်ရေးအချက်အလက်မူဝါဒ",
"footer.source_code": "မူရင်းကုဒ်အားကြည့်ရှုမည်",
"footer.status": "အခြေအနေ",
"generic.saved": "သိမ်းဆည်းထားပြီး",
"getting_started.heading": "စတင်မည်",
"hashtag.column_header.tag_mode.all": "and {additional}",
"hashtag.column_header.tag_mode.any": "or {additional}",
"hashtag.column_header.tag_mode.none": "without {additional}",
"hashtag.column_settings.select.no_options_message": "No suggestions found",
"hashtag.column_header.tag_mode.all": "နှင့် {additional}",
"hashtag.column_header.tag_mode.any": "သို့မဟုတ် {additional}",
"hashtag.column_header.tag_mode.none": "မပါဘဲ {additional}",
"hashtag.column_settings.select.no_options_message": "အကြံပြုချက်မတွေ့ပါ",
"hashtag.column_settings.select.placeholder": "Enter hashtags…",
"hashtag.column_settings.tag_mode.all": "All of these",
"hashtag.column_settings.tag_mode.any": "Any of these",
"hashtag.column_settings.tag_mode.none": "None of these",
"hashtag.column_settings.tag_toggle": "Include additional tags in this column",
"hashtag.follow": "Follow hashtag",
"hashtag.unfollow": "Unfollow hashtag",
"home.column_settings.basic": "Basic",
"hashtag.follow": "Hashtag ကို စောင့်ကြည့်မယ်",
"hashtag.unfollow": "Hashtag ကို မစောင့်ကြည့်ပါ",
"home.column_settings.basic": "အခြေခံ",
"home.column_settings.show_reblogs": "Show boosts",
"home.column_settings.show_replies": "Show replies",
"home.hide_announcements": "Hide announcements",
"home.show_announcements": "Show announcements",
"home.column_settings.show_replies": "ပြန်စာများကို ပြပါ",
"home.hide_announcements": "ကြေညာချက်များကို ဖျောက်ပါ",
"home.show_announcements": "ကြေညာချက်များကို ပြပါ",
"interaction_modal.description.favourite": "With an account on Mastodon, you can favourite this post to let the author know you appreciate it and save it for later.",
"interaction_modal.description.follow": "With an account on Mastodon, you can follow {name} to receive their posts in your home feed.",
"interaction_modal.description.reblog": "With an account on Mastodon, you can boost this post to share it with your own followers.",
@ -299,10 +299,10 @@
"interaction_modal.on_this_server": "ဤဆာဗာတွင်",
"interaction_modal.other_server_instructions": "Copy and paste this URL into the search field of your favourite Mastodon app or the web interface of your Mastodon server.",
"interaction_modal.preamble": "Since Mastodon is decentralized, you can use your existing account hosted by another Mastodon server or compatible platform if you don't have an account on this one.",
"interaction_modal.title.favourite": "Favourite {name}'s post",
"interaction_modal.title.follow": "Follow {name}",
"interaction_modal.title.favourite": "အကြိုက်ဆုံး {name} ၏ ပို့စ်",
"interaction_modal.title.follow": "{name} ကို စောင့်ကြည့်မယ်",
"interaction_modal.title.reblog": "Boost {name}'s post",
"interaction_modal.title.reply": "Reply to {name}'s post",
"interaction_modal.title.reply": "{name} ၏ ပို့စ်ကို စာပြန်မယ်",
"intervals.full.days": "{number, plural, one {# day} other {# days}}",
"intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
"intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
@ -312,7 +312,7 @@
"keyboard_shortcuts.column": "to focus a status in one of the columns",
"keyboard_shortcuts.compose": "to focus the compose textarea",
"keyboard_shortcuts.description": "ဖော်ပြချက်",
"keyboard_shortcuts.direct": "to open direct messages column",
"keyboard_shortcuts.direct": "တိုက်ရိုက်မက်ဆေ့ချ်ကော်လံကို ဖွင့်ရန်",
"keyboard_shortcuts.down": "to move down in the list",
"keyboard_shortcuts.enter": "to open status",
"keyboard_shortcuts.favourite": "to favourite",
@ -345,8 +345,8 @@
"lightbox.expand": "ပုံကိုဖွင့်ပါ",
"lightbox.next": "ရှေ့သို့",
"lightbox.previous": "Previous",
"limited_account_hint.action": "Show profile anyway",
"limited_account_hint.title": "This profile has been hidden by the moderators of {domain}.",
"limited_account_hint.action": "ဘာပဲဖြစ်ဖြစ် ပရိုဖိုင်ကို ပြပါ",
"limited_account_hint.title": "ဤပရိုဖိုင်ကို {domain} ၏ စိစစ်သူများမှ ဖျောက်ထားသည်။",
"lists.account.add": "စာရင်းထဲသို့ထည့်ပါ",
"lists.account.remove": "စာရင်းမှ ဖယ်ရှားလိုက်ပါ။",
"lists.delete": "စာရင်းကိုဖျက်ပါ",
@ -374,37 +374,37 @@
"navigation_bar.bookmarks": "မှတ်ထားသည်များ",
"navigation_bar.community_timeline": "ဒေသစံတော်ချိန်",
"navigation_bar.compose": "Compose new post",
"navigation_bar.direct": "Direct messages",
"navigation_bar.direct": "တိုက်ရိုက်မက်ဆေ့ချ်များ",
"navigation_bar.discover": "Discover",
"navigation_bar.domain_blocks": "Hidden domains",
"navigation_bar.edit_profile": "ကိုယ်ရေးမှတ်တမ်းပြင်ဆင်မည်",
"navigation_bar.explore": "Explore",
"navigation_bar.favourites": "Favourites",
"navigation_bar.favourites": "အကြိုက်ဆုံးများ",
"navigation_bar.filters": "Muted words",
"navigation_bar.follow_requests": "Follow requests",
"navigation_bar.follow_requests": "တောင်းဆိုချက်များကို စောင့်ကြည့်ပါ",
"navigation_bar.followed_tags": "Followed hashtags",
"navigation_bar.follows_and_followers": "Follows and followers",
"navigation_bar.lists": "Lists",
"navigation_bar.logout": "Logout",
"navigation_bar.follows_and_followers": "စောင့်ကြည့်သူများနှင့် စောင့်ကြည့်စာရင်း",
"navigation_bar.lists": "စာရင်းများ",
"navigation_bar.logout": "ထွက်မယ်",
"navigation_bar.mutes": "Muted users",
"navigation_bar.personal": "Personal",
"navigation_bar.pins": "Pinned posts",
"navigation_bar.personal": "ကိုယ်ရေးကိုယ်တာ",
"navigation_bar.pins": "ပင်တွဲထားသောပို့စ်များ",
"navigation_bar.preferences": "Preferences",
"navigation_bar.public_timeline": "Federated timeline",
"navigation_bar.search": "Search",
"navigation_bar.security": "Security",
"not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
"navigation_bar.search": "ရှာရန်",
"navigation_bar.security": "လုံခြုံရေး",
"not_signed_in_indicator.not_signed_in": "ဤရင်းမြစ်သို့ ဝင်ရောက်ရန်အတွက် သင်သည် အကောင့်ဝင်ရန် လိုအပ်ပါသည်။",
"notification.admin.report": "{name} reported {target}",
"notification.admin.sign_up": "{name} signed up",
"notification.admin.sign_up": "{name} က အကောင့်ဖွင့်ထားသည်",
"notification.favourite": "{name} favourited your status",
"notification.follow": "{name} followed you",
"notification.follow_request": "{name} has requested to follow you",
"notification.mention": "{name} mentioned you",
"notification.follow": "{name} သင့်ကို စောင့်ကြည့်ခဲ့သည်",
"notification.follow_request": "{name} က သင့်ကို စောင့်ကြည့်ရန် တောင်းဆိုထားသည်",
"notification.mention": "{name} က သင့်ကို ဖော်ပြခဲ့သည်",
"notification.own_poll": "စစ်တမ်းကောက်မှု ပြီးဆုံးပါပြီ",
"notification.poll": "သင်ပါဝင်ခဲ့သော စစ်တမ်းပြီးပါပြီ",
"notification.reblog": "{name} boosted your status",
"notification.status": "{name} just posted",
"notification.update": "{name} edited a post",
"notification.status": "{name} က အခုလေးတင် ပို့စ်တင်လိုက်ပါပြီ",
"notification.update": "{name} က ပို့စ်တစ်ခုကို ပြင်ဆင်ခဲ့သည်",
"notifications.clear": "အသိပေးချက်များအား ရှင်းလင်းပါ",
"notifications.clear_confirmation": "သတိပေးချက်အားလုံးကို အပြီးတိုင်ဖယ်ရှားမည်",
"notifications.column_settings.admin.report": "New reports:",
@ -413,29 +413,29 @@
"notifications.column_settings.favourite": "ကြိုက်နှစ်သက်မှုများ",
"notifications.column_settings.filter_bar.advanced": "ခေါင်းစဥ်အားလုံးများကိုဖော်ပြပါ",
"notifications.column_settings.filter_bar.category": "Quick filter bar",
"notifications.column_settings.filter_bar.show_bar": "Show filter bar",
"notifications.column_settings.follow": "New followers:",
"notifications.column_settings.filter_bar.show_bar": "စစ်ထုတ်မှုဘားကို ပြပါ",
"notifications.column_settings.follow": "စောင့်ကြည့်သူအသစ်များ -",
"notifications.column_settings.follow_request": "New follow requests:",
"notifications.column_settings.mention": "Mentions:",
"notifications.column_settings.poll": "စစ်တမ်းရလဒ်",
"notifications.column_settings.push": "Push notifications",
"notifications.column_settings.push": "အသိပေးချက်များအား ရအောင်ပို့ခြင်း",
"notifications.column_settings.reblog": "Boosts:",
"notifications.column_settings.show": "Show in column",
"notifications.column_settings.sound": "Play sound",
"notifications.column_settings.status": "New posts:",
"notifications.column_settings.unread_notifications.category": "Unread notifications",
"notifications.column_settings.show": "ကော်လံတွင်ပြပါ",
"notifications.column_settings.sound": "အသံဖွင့်မည်",
"notifications.column_settings.status": "ပို့စ်အသစ်များ -",
"notifications.column_settings.unread_notifications.category": "မဖတ်ရသေးသောအသိပေးချက်များ -",
"notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "ပြင်ဆင်ထားမှုများ -",
"notifications.filter.all": "အားလုံး",
"notifications.filter.boosts": "အားပေးမည်",
"notifications.filter.favourites": "ကြိုက်နှစ်သက်မှုများ",
"notifications.filter.follows": "ဖောလိုးမည်",
"notifications.filter.mentions": " မန်းရှင်းမည်",
"notifications.filter.polls": "စစ်တမ်းရလဒ်",
"notifications.filter.statuses": "Updates from people you follow",
"notifications.filter.statuses": "သင်စောင့်ကြည့်သူများထံမှ အပ်ဒိတ်များ",
"notifications.grant_permission": "ခွင့်ပြုချက်ပေးမည်",
"notifications.group": "{count} notifications",
"notifications.mark_as_read": "Mark every notification as read",
"notifications.group": "အသိပေးချက်များ {count} ခု",
"notifications.mark_as_read": "အသိပေးချက်တိုင်းကို ဖတ်ပြီးကြောင်း အမှတ်အသားပြုပါ",
"notifications.permission_denied": "Desktop notifications are unavailable due to previously denied browser permissions request",
"notifications.permission_denied_alert": "Desktop notifications can't be enabled, as browser permission has been denied before",
"notifications.permission_required": "Desktop notifications are unavailable because the required permission has not been granted.",
@ -462,9 +462,9 @@
"privacy.unlisted.long": "Visible for all, but opted-out of discovery features",
"privacy.unlisted.short": "Unlisted",
"privacy_policy.last_updated": "Last updated {date}",
"privacy_policy.title": "Privacy Policy",
"refresh": "Refresh",
"regeneration_indicator.label": "Loading…",
"privacy_policy.title": "ကိုယ်ရေးအချက်အလက်မူဝါဒ",
"refresh": "ပြန်လည်စတင်ပါ",
"regeneration_indicator.label": "လုပ်ဆောင်နေသည်…",
"regeneration_indicator.sublabel": "Your home feed is being prepared!",
"relative_time.days": "{number}d",
"relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
@ -473,7 +473,7 @@
"relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
"relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
"relative_time.hours": "{number}h",
"relative_time.just_now": "now",
"relative_time.just_now": "ယခု",
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"relative_time.today": "ယနေ့",
@ -516,13 +516,13 @@
"report.unfollow": "Unfollow @{name}",
"report.unfollow_explanation": "You are following this account. To not see their posts in your home feed anymore, unfollow them.",
"report_notification.attached_statuses": "{count, plural, one {{count} post} other {{count} posts}} attached",
"report_notification.categories.other": "Other",
"report_notification.categories.other": "အခြား",
"report_notification.categories.spam": "Spam",
"report_notification.categories.violation": "Rule violation",
"report_notification.open": "Open report",
"search.placeholder": "ရှာဖွေရန်",
"search.search_or_paste": "URL ရိုက်ထည့်ပါ သို့မဟုတ် ရှာဖွေပါ",
"search_popout.search_format": "Advanced search format",
"search_popout.search_format": "အဆင့်မြင့်ရှာဖွေမှုပုံစံ",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "ဟက်ရှ်တက်ခ်",
"search_popout.tips.status": "ပို့စ်",
@ -534,29 +534,29 @@
"search_results.nothing_found": "ရှာဖွေလိုသောအရာမရှိပါ",
"search_results.statuses": "ပို့စ်တင်မယ်",
"search_results.statuses_fts_disabled": "Searching posts by their content is not enabled on this Mastodon server.",
"search_results.title": "Search for {q}",
"search_results.title": "{q} ကို ရှာပါ",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"server_banner.about_active_users": "People using this server during the last 30 days (Monthly Active Users)",
"server_banner.active_users": "active users",
"server_banner.active_users": "လက်ရှိအသုံးပြုသူများ",
"server_banner.administered_by": "Administered by:",
"server_banner.introduction": "{domain} is part of the decentralized social network powered by {mastodon}.",
"server_banner.learn_more": "Learn more",
"server_banner.learn_more": "ပိုမိုသိရှိရန်",
"server_banner.server_stats": "Server stats:",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.create_account": "အကောင့်ဖန်တီးမည်",
"sign_in_banner.sign_in": "အကောင့်ဝင်မည်",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
"status.admin_account": "Open moderation interface for @{name}",
"status.admin_domain": "Open moderation interface for {domain}",
"status.admin_status": "Open this status in the moderation interface",
"status.block": "@{name} ကိုဘလော့မည်",
"status.bookmark": "Bookmark",
"status.bookmark": "မှတ်ထားသည်များ",
"status.cancel_reblog_private": "Unboost",
"status.cannot_reblog": "This post cannot be boosted",
"status.copy": "Copy link to status",
"status.delete": "Delete",
"status.delete": "ဖျက်ရန်",
"status.detailed_status": "Detailed conversation view",
"status.direct": "Direct message @{name}",
"status.edit": "Edit",
"status.direct": "@{name} ကို တိုက်ရိုက်စာပို့မည်",
"status.edit": "ပြင်ဆင်ရန်",
"status.edited": "Edited {date}",
"status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
"status.embed": "Embed",
@ -574,86 +574,86 @@
"status.mute_conversation": "Mute conversation",
"status.open": "ပို့စ်ကိုချဲ့ထွင်မည်",
"status.pin": "Pin on profile",
"status.pinned": "Pinned post",
"status.read_more": "Read more",
"status.pinned": "ပင်တွဲထားသောပို့စ်",
"status.read_more": "ပိုမိုဖတ်ရှုရန်",
"status.reblog": "Boost",
"status.reblog_private": "Boost with original visibility",
"status.reblogged_by": "{name} boosted",
"status.reblogs.empty": "No one has boosted this post yet. When someone does, they will show up here.",
"status.redraft": "Delete & re-draft",
"status.remove_bookmark": "Remove bookmark",
"status.replied_to": "Replied to {name}",
"status.reply": "Reply",
"status.remove_bookmark": "မှတ်ထားသည်များကို ဖယ်ရှားပါ",
"status.replied_to": "{name} ကို စာပြန်ခဲ့သည်",
"status.reply": "စာပြန်ရန်",
"status.replyAll": "Reply to thread",
"status.report": "Report @{name}",
"status.sensitive_warning": "Sensitive content",
"status.share": "Share",
"status.show_filter_reason": "Show anyway",
"status.show_less": "Show less",
"status.show_less_all": "Show less for all",
"status.show_more": "Show more",
"status.show_more_all": "Show more for all",
"status.show_original": "Show original",
"status.translate": "Translate",
"status.translated_from_with": "Translated from {lang} using {provider}",
"status.uncached_media_warning": "Not available",
"status.sensitive_warning": "သတိထားရသော အကြောင်းအရာ",
"status.share": "မျှဝေ",
"status.show_filter_reason": "မည်သို့ပင်ဖြစ်စေ ပြပါ",
"status.show_less": "အနည်းငယ်သာ ပြပါ",
"status.show_less_all": "အားလုံးအတွက် အနည်းငယ်သာ ပြပါ",
"status.show_more": "ပိုမိုပြရန်",
"status.show_more_all": "အားလုံးအတွက် ပိုပြပါ",
"status.show_original": "မူရင်းပြပါ",
"status.translate": "ဘာသာပြန်ပါ",
"status.translated_from_with": "{provider} ကို အသုံးပြု၍ {lang} မှ ဘာသာပြန်ထားသည်",
"status.uncached_media_warning": "မရနိုင်ပါ",
"status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile",
"subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
"subscribed_languages.save": "Save changes",
"subscribed_languages.target": "Change subscribed languages for {target}",
"subscribed_languages.save": "ပြောင်းလဲမှုများကို သိမ်းဆည်းပါ",
"subscribed_languages.target": "{target} အတွက် စာရင်းသွင်းထားသော ဘာသာစကားများကို ပြောင်းပါ",
"suggestions.dismiss": "Dismiss suggestion",
"suggestions.header": "You might be interested in…",
"tabs_bar.federated_timeline": "Federated",
"tabs_bar.home": "Home",
"tabs_bar.local_timeline": "Local",
"tabs_bar.notifications": "Notifications",
"tabs_bar.federated_timeline": "ဖက်ဒီ",
"tabs_bar.home": "ပင်မစာမျက်နှာ",
"tabs_bar.local_timeline": "ပြည်တွင်း",
"tabs_bar.notifications": "အသိပေးချက်များ",
"time_remaining.days": "{number, plural, one {# day} other {# days}} left",
"time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
"time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
"time_remaining.moments": "Moments remaining",
"time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
"timeline_hint.remote_resource_not_displayed": "{resource} from other servers are not displayed.",
"timeline_hint.resources.followers": "Followers",
"timeline_hint.resources.follows": "Follows",
"timeline_hint.resources.statuses": "Older posts",
"timeline_hint.remote_resource_not_displayed": "အခြားဆာဗာများမှ {resource} ကို ပြသမည်မဟုတ်ပါ။",
"timeline_hint.resources.followers": "စောင့်ကြည့်သူများ",
"timeline_hint.resources.follows": "စောင့်ကြည့်မယ်",
"timeline_hint.resources.statuses": "ပို့စ်အဟောင်းများ",
"trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {{days} days}}",
"trends.trending_now": "Trending now",
"trends.trending_now": "လက်ရှိခေတ်စားနေသော ပို့စ်များ",
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
"units.short.billion": "{count}B",
"units.short.million": "{count}M",
"units.short.thousand": "{count}K",
"upload_area.title": "Drag & drop to upload",
"upload_button.label": "Add images, a video or an audio file",
"upload_error.limit": "File upload limit exceeded.",
"upload_button.label": "ပုံများ၊ ဗီဒီယို သို့မဟုတ် အသံဖိုင်တစ်ခု ထည့်ပါ",
"upload_error.limit": "ဖိုင်အများဆုံးတင်နိုင်မည့်ကန့်သတ်ချက်ကို ကျော်သွားပါပြီ။",
"upload_error.poll": "စစ်တမ်းနှင့်အတူဖိုင်များတင်ခွင့်မပြုပါ",
"upload_form.audio_description": "အကြားအာရုံချို့ယွင်းသော ခက်ခဲသောသူများအတွက် ဖော်ပြထားသည်",
"upload_form.description": "အမြင်အာရုံချို့ယွင်းသော ခက်ခဲသောသူများအတွက် ဖော်ပြထားသည်",
"upload_form.description_missing": "No description added",
"upload_form.edit": "Edit",
"upload_form.description_missing": "ဖော်ပြချက် မထည့်ပါ",
"upload_form.edit": "ပြင်ရန်",
"upload_form.thumbnail": "Change thumbnail",
"upload_form.undo": "Delete",
"upload_form.undo": "ဖျက်ရန်",
"upload_form.video_description": "အမြင်အာရုံနှင့်အကြားအာရုံ ချို့ယွင်းသော ခက်ခဲသောသူများအတွက် ဖော်ပြထားသည်",
"upload_modal.analyzing_picture": "Analyzing picture…",
"upload_modal.apply": "Apply",
"upload_modal.applying": "Applying…",
"upload_modal.choose_image": "Choose image",
"upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
"upload_modal.choose_image": "ပုံရွေးပါ",
"upload_modal.description_placeholder": "သီဟိုဠ်မှ ဉာဏ်ကြီးရှင်သည် အာယုဝဍ္ဎနဆေးညွှန်းစာကို ဇလွန်ဈေးဘေး ဗာဒံပင်ထက် အဓိဋ္ဌာန်လျက် ဂဃနဏဖတ်ခဲ့သည်",
"upload_modal.detect_text": "Detect text from picture",
"upload_modal.edit_media": "Edit media",
"upload_modal.edit_media": "မီဒီယာကို ပြင်ဆင်ရန်",
"upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
"upload_modal.preparing_ocr": "Preparing OCR…",
"upload_modal.preparing_ocr": "OCR ပြင်ဆင်နေသည်…",
"upload_modal.preview_label": "Preview ({ratio})",
"upload_progress.label": "တင်နေသည်...",
"upload_progress.processing": "Processing…",
"video.close": "Close video",
"video.download": "Download file",
"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"
"upload_progress.processing": "လုပ်ဆောင်နေသည်…",
"video.close": "ဗီဒီယိုကို ပိတ်ပါ",
"video.download": "ဖိုင်ကို ဒေါင်းလုဒ်လုပ်ပါ",
"video.exit_fullscreen": "မျက်နှာပြင်အပြည့်မှ ထွက်ပါ",
"video.expand": "ဗီဒီယိုကို ချဲ့ပါ",
"video.fullscreen": "မျက်နှာပြင်အပြည့်",
"video.hide": "ဗီဒီယိုကို ဖျောက်ပါ",
"video.mute": "အသံပိတ်ထားပါ",
"video.pause": "ခဏရပ်ပါ",
"video.play": "ဖွင့်ပါ",
"video.unmute": "အသံပြန်ဖွင့်ပါ"
}

View file

@ -34,7 +34,7 @@
"account.followers.empty": "Ingen fylgjer denne brukaren enno.",
"account.followers_counter": "{count, plural, one {{counter} fylgjar} other {{counter} fylgjarar}}",
"account.following": "Fylgjer",
"account.following_counter": "{count, plural, one {Fylgjar {counter}} other {Fylgjar {counter}}}",
"account.following_counter": "{count, plural, one {Fylgjer {counter}} other {Fylgjer {counter}}}",
"account.follows.empty": "Denne brukaren fylgjer ikkje nokon enno.",
"account.follows_you": "Fylgjer deg",
"account.go_to_profile": "Gå til profil",

View file

@ -221,7 +221,7 @@
"empty_column.favourites": "Ingen har favoritmarkerat detta inlägg än. När någon gör det kommer de synas här.",
"empty_column.follow_recommendations": "Det ser ut som om inga förslag kan genereras till dig. Du kan prova att använda sök för att leta efter personer som du kanske känner eller utforska trendande hash-taggar.",
"empty_column.follow_requests": "Du har inga följarförfrågningar än. När du får en kommer den visas här.",
"empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
"empty_column.followed_tags": "Du följer inga hashtags ännu. När du gör det kommer de att dyka upp här.",
"empty_column.hashtag": "Det finns inget i denna hashtag ännu.",
"empty_column.home": "Din hemma-tidslinje är tom! Följ fler användare för att fylla den. {suggestions}",
"empty_column.home.suggestions": "Se några förslag",
@ -264,7 +264,7 @@
"follow_request.authorize": "Godkänn",
"follow_request.reject": "Avvisa",
"follow_requests.unlocked_explanation": "Även om ditt konto inte är låst tror {domain} personalen att du kanske vill granska dessa följares förfrågningar manuellt.",
"followed_tags": "Followed hashtags",
"followed_tags": "Följda hashtags",
"footer.about": "Om",
"footer.directory": "Profilkatalog",
"footer.get_app": "Skaffa appen",
@ -544,7 +544,7 @@
"server_banner.server_stats": "Serverstatistik:",
"sign_in_banner.create_account": "Skapa konto",
"sign_in_banner.sign_in": "Logga in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
"sign_in_banner.text": "Logga in för att följa profiler eller hashtags, favoritmarkera, dela och svara på inlägg. Du kan också interagera med ditt konto på en annan server.",
"status.admin_account": "Öppet modereringsgränssnitt för @{name}",
"status.admin_domain": "Öppet modereringsgränssnitt för @{domain}",
"status.admin_status": "Öppna detta inlägg i modereringsgränssnittet",

View file

@ -7,7 +7,7 @@
"about.domain_blocks.silenced.explanation": "Гадәттә, сез бу серверның профильләрен һәм эчтәлеген күрмәячәксез, әгәр сез аларны ачыктан-ачык карамасагыз яки бу адымнарны үтәп язылмасагыз.",
"about.domain_blocks.silenced.title": "Чикле",
"about.domain_blocks.suspended.explanation": "Бу серверның бернинди мәгълүматлары да эшкәртелмәячәк, сакланмаячак яки алмаштырылмаячак, бу сервердан кулланучылар белән үзара бәйләнешне яки аралашуны мөмкин итми.",
"about.domain_blocks.suspended.title": "Асылмалы",
"about.domain_blocks.suspended.title": "Блокланган",
"about.not_available": "Бу серверда бу мәгълүмат юк иде.",
"about.powered_by": "Децентрализованные социаль челтәрләр нигезендә {mastodon}",
"about.rules": "Сервер кагыйдәләре",
@ -39,22 +39,22 @@
"account.follows_you": "Сезгә язылган",
"account.go_to_profile": "Профильгә күчү",
"account.hide_reblogs": "Скрывать көчен нче @{name}",
"account.joined_short": "Кушылу",
"account.languages": "Подписка телләрен үзгәртү",
"account.joined_short": "Кушылды",
"account.languages": "Сайланган телләрен үзгәртү",
"account.link_verified_on": "Бу сылтамага милек хокукы тикшерелде {date}",
"account.locked_info": "Бу - ябык аккаунт. Аны язылучылар гына күрә ала.",
"account.media": "Медиа",
"account.mention": "@{name} искәртү",
"account.moved_to": "{name} аларның яңа счеты хәзер күрсәтте:",
"account.mute": "Тавышсыз @{name}",
"account.mute_notifications": "Отключите хәбәрләр нче @{name}",
"account.muted": "Тавышсыз",
"account.mute": "@{name} кулланучыга әһәмият бирмәү",
"account.mute_notifications": "@{name} кулланучыдан хәбәрләргә әһәмият бирмәү",
"account.muted": "Әһәмият бирмәнгән",
"account.open_original_page": "Чыганак битен ачу",
"account.posts": "Toots",
"account.posts_with_replies": "Toots and replies",
"account.report": "Хисап @{name}",
"account.posts": "Пост",
"account.posts_with_replies": "Пост һәм җавап",
"account.report": "@{name} кулланучыга шикаять итү",
"account.requested": "Awaiting approval",
"account.requested_follow": "{name} сиңа иярүеңне сорады",
"account.requested_follow": "{name} Сезгә язылу соравын җиберде",
"account.share": "Уртаклашу @{name} профиль",
"account.show_reblogs": "Күрсәтергә көчәйтү нче @{name}",
"account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}",

View file

@ -104,14 +104,14 @@
"column.community": "本站時間軸",
"column.direct": "私訊",
"column.directory": "瀏覽個人檔案",
"column.domain_blocks": "已封鎖網域",
"column.domain_blocks": "已封鎖網域",
"column.favourites": "最愛",
"column.follow_requests": "跟隨請求",
"column.home": "首頁",
"column.lists": "列表",
"column.mutes": "已靜音的使用者",
"column.notifications": "通知",
"column.pins": "釘選嘟文",
"column.pins": "釘選嘟文",
"column.public": "聯邦時間軸",
"column_back_button.label": "上一頁",
"column_header.hide_settings": "隱藏設定",
@ -168,7 +168,7 @@
"confirmations.mute.explanation": "這將會隱藏來自他們的嘟文與通知,但是他們還是可以查閱您的嘟文與跟隨您。",
"confirmations.mute.message": "您確定要靜音 {name} 嗎?",
"confirmations.redraft.confirm": "刪除並重新編輯",
"confirmations.redraft.message": "您確定要刪掉這則嘟文並重新編輯嗎?將會失去這則嘟文的轉嘟及最愛,且回覆這則的嘟文將會變成獨立的嘟文。",
"confirmations.redraft.message": "您確定要刪除這則嘟文並重新編輯嗎?您將失去這則嘟文的轉嘟及最愛,且對原始嘟文的回覆都會變成獨立的嘟文。",
"confirmations.reply.confirm": "回覆",
"confirmations.reply.message": "現在回覆將蓋掉您目前正在撰寫的訊息。是否仍要回覆?",
"confirmations.unfollow.confirm": "取消跟隨",
@ -190,7 +190,7 @@
"dismissable_banner.explore_links": "這些新聞故事正在被此伺服器以及去中心化網路上的人們熱烈討論著。",
"dismissable_banner.explore_statuses": "這些於此伺服器以及去中心化網路中其他伺服器發出的嘟文正在被此伺服器上的人們熱烈討論著。",
"dismissable_banner.explore_tags": "這些主題標籤正在被此伺服器以及去中心化網路上的人們熱烈討論著。",
"dismissable_banner.public_timeline": "這些是來自此伺服器以及去中心化網路中其他已知伺服器的最新公開嘟文。",
"dismissable_banner.public_timeline": "這些是來自這裡以及去中心化網路中其他已知伺服器之最新公開嘟文。",
"embed.instructions": "要在您的網站嵌入此嘟文,請複製以下程式碼。",
"embed.preview": "它將顯示成這樣:",
"emoji_button.activity": "活動",
@ -246,7 +246,7 @@
"filter_modal.added.context_mismatch_title": "不符合情境!",
"filter_modal.added.expired_explanation": "此過濾器類別已失效,您需要更新過期日期以套用。",
"filter_modal.added.expired_title": "過期的過濾器!",
"filter_modal.added.review_and_configure": "若欲檢視和進一步設定此過濾器類別,請至 {settings_link}。",
"filter_modal.added.review_and_configure": "檢視和進一步設定此過濾器類別,請至 {settings_link}。",
"filter_modal.added.review_and_configure_title": "過濾器設定",
"filter_modal.added.settings_link": "設定頁面",
"filter_modal.added.short_explanation": "此嘟文已被新增至以下過濾器類別:{title}。",
@ -306,11 +306,11 @@
"intervals.full.days": "{number, plural, one {# 天} other {# 天}}",
"intervals.full.hours": "{number, plural, one {# 小時} other {# 小時}}",
"intervals.full.minutes": "{number, plural, one {# 分鐘} other {# 分鐘}}",
"keyboard_shortcuts.back": "返回上一頁",
"keyboard_shortcuts.blocked": "開啟「封鎖使用者」名單",
"keyboard_shortcuts.back": "上一頁",
"keyboard_shortcuts.blocked": "開啟「封鎖使用者」列表",
"keyboard_shortcuts.boost": "轉嘟",
"keyboard_shortcuts.column": "聚焦至其中一欄的嘟文",
"keyboard_shortcuts.compose": "聚焦至撰寫文字區塊",
"keyboard_shortcuts.column": "將游標移至其中一欄",
"keyboard_shortcuts.compose": "將游標移至文字撰寫區塊",
"keyboard_shortcuts.description": "說明",
"keyboard_shortcuts.direct": "開啟私訊欄",
"keyboard_shortcuts.down": "往下移動",
@ -332,13 +332,13 @@
"keyboard_shortcuts.profile": "開啟作者的個人檔案頁面",
"keyboard_shortcuts.reply": "回應嘟文",
"keyboard_shortcuts.requests": "開啟跟隨請求列表",
"keyboard_shortcuts.search": "聚焦至搜尋框",
"keyboard_shortcuts.search": "將游標移至搜尋框",
"keyboard_shortcuts.spoilers": "顯示或隱藏內容警告之嘟文",
"keyboard_shortcuts.start": "開啟「開始使用」欄位",
"keyboard_shortcuts.toggle_hidden": "顯示或隱藏在內容警告之後的嘟文",
"keyboard_shortcuts.toggle_sensitivity": "顯示或隱藏媒體",
"keyboard_shortcuts.toot": "發個新嘟文",
"keyboard_shortcuts.unfocus": "取消輸入文字區塊或搜尋之焦點",
"keyboard_shortcuts.unfocus": "跳離文字撰寫區塊或搜尋框",
"keyboard_shortcuts.up": "往上移動",
"lightbox.close": "關閉",
"lightbox.compress": "折疊圖片檢視框",
@ -376,27 +376,27 @@
"navigation_bar.compose": "撰寫新嘟文",
"navigation_bar.direct": "私訊",
"navigation_bar.discover": "探索",
"navigation_bar.domain_blocks": "已封鎖網域",
"navigation_bar.domain_blocks": "已封鎖網域",
"navigation_bar.edit_profile": "編輯個人檔案",
"navigation_bar.explore": "探索",
"navigation_bar.favourites": "最愛",
"navigation_bar.filters": "已靜音的關鍵字",
"navigation_bar.follow_requests": "跟隨請求",
"navigation_bar.followed_tags": "已跟隨主題標籤",
"navigation_bar.followed_tags": "已跟隨主題標籤",
"navigation_bar.follows_and_followers": "跟隨中與跟隨者",
"navigation_bar.lists": "列表",
"navigation_bar.logout": "登出",
"navigation_bar.mutes": "已靜音的使用者",
"navigation_bar.personal": "個人",
"navigation_bar.pins": "釘選嘟文",
"navigation_bar.pins": "釘選嘟文",
"navigation_bar.preferences": "偏好設定",
"navigation_bar.public_timeline": "聯邦時間軸",
"navigation_bar.search": "搜尋",
"navigation_bar.security": "安全性",
"not_signed_in_indicator.not_signed_in": "您需要登入才能存取此資源。",
"notification.admin.report": "{name} 檢舉 {target}",
"notification.admin.report": "{name} 檢舉 {target}",
"notification.admin.sign_up": "{name} 已經註冊",
"notification.favourite": "{name} 把您的嘟文加入了最愛",
"notification.favourite": "{name} 已將您的嘟文加入最愛",
"notification.follow": "{name} 跟隨了您",
"notification.follow_request": "{name} 要求跟隨您",
"notification.mention": "{name} 提到了您",
@ -404,7 +404,7 @@
"notification.poll": "您曾投過的投票已經結束",
"notification.reblog": "{name} 轉嘟了您的嘟文",
"notification.status": "{name} 剛剛嘟文",
"notification.update": "{name} 編輯嘟文",
"notification.update": "{name} 編輯嘟文",
"notifications.clear": "清除通知",
"notifications.clear_confirmation": "您確定要永久清除您的通知嗎?",
"notifications.column_settings.admin.report": "新檢舉報告:",
@ -544,7 +544,7 @@
"server_banner.server_stats": "伺服器統計:",
"sign_in_banner.create_account": "新增帳號",
"sign_in_banner.sign_in": "登入",
"sign_in_banner.text": "登入以追蹤個人檔案、主題標籤、最愛,分享和回覆嘟文。您也能與您其他伺服器之帳號進行互動。",
"sign_in_banner.text": "登入以跟隨個人檔案和主題標籤,或收藏、分享和回覆嘟文。您也可以使用您的帳號在其他伺服器上進行互動。",
"status.admin_account": "開啟 @{name} 的管理介面",
"status.admin_domain": "開啟 {domain} 的管理介面",
"status.admin_status": "在管理介面開啟此嘟文",

View file

@ -23,3 +23,4 @@
@import 'mastodon/dashboard';
@import 'mastodon/rtl';
@import 'mastodon/accessibility';
@import 'mastodon/rich_text';

View file

@ -0,0 +1,64 @@
.status__content__text,
.e-content,
.reply-indicator__content {
pre,
blockquote {
margin-bottom: 20px;
white-space: pre-wrap;
unicode-bidi: plaintext;
&:last-child {
margin-bottom: 0;
}
}
blockquote {
padding-left: 10px;
border-left: 3px solid $darker-text-color;
color: $darker-text-color;
white-space: normal;
p:last-child {
margin-bottom: 0;
}
}
& > ul,
& > ol {
margin-bottom: 20px;
}
b,
strong {
font-weight: 700;
}
em,
i {
font-style: italic;
}
ul,
ol {
margin-left: 2em;
p {
margin: 0;
}
}
ul {
list-style-type: disc;
}
ol {
list-style-type: decimal;
}
}
.reply-indicator__content {
blockquote {
border-left-color: $inverted-text-color;
color: $inverted-text-color;
}
}

View file

@ -322,27 +322,27 @@ class FeedManager
def clean_feeds!(type, ids)
reblogged_id_sets = {}
redis.pipelined do
redis.pipelined do |pipeline|
ids.each do |feed_id|
redis.del(key(type, feed_id))
reblog_key = key(type, feed_id, 'reblogs')
# We collect a future for this: we don't block while getting
# it, but we can iterate over it later.
reblogged_id_sets[feed_id] = redis.zrange(reblog_key, 0, -1)
redis.del(reblog_key)
reblogged_id_sets[feed_id] = pipeline.zrange(reblog_key, 0, -1)
pipeline.del(key(type, feed_id), reblog_key)
end
end
# Remove all of the reblog tracking keys we just removed the
# references to.
redis.pipelined do
reblogged_id_sets.each do |feed_id, future|
future.value.each do |reblogged_id|
reblog_set_key = key(type, feed_id, "reblogs:#{reblogged_id}")
redis.del(reblog_set_key)
end
keys_to_delete = reblogged_id_sets.flat_map do |feed_id, future|
future.value.map do |reblogged_id|
key(type, feed_id, "reblogs:#{reblogged_id}")
end
end
redis.del(keys_to_delete) unless keys_to_delete.empty?
nil
end
private

View file

@ -21,6 +21,10 @@ class TranslationService
ENV['DEEPL_API_KEY'].present? || ENV['LIBRE_TRANSLATE_ENDPOINT'].present?
end
def supported?(_source_language, _target_language)
false
end
def translate(_text, _source_language, _target_language)
raise NotImplementedError
end

View file

@ -11,33 +11,53 @@ class TranslationService::DeepL < TranslationService
end
def translate(text, source_language, target_language)
request(text, source_language, target_language).perform do |res|
form = { text: text, source_lang: source_language&.upcase, target_lang: target_language, tag_handling: 'html' }
request(:post, '/v2/translate', form: form) do |res|
transform_response(res.body_with_limit)
end
end
def supported?(source_language, target_language)
source_language.in?(languages('source')) && target_language.in?(languages('target'))
end
private
def languages(type)
Rails.cache.fetch("translation_service/deepl/languages/#{type}", expires_in: 7.days, race_condition_ttl: 1.minute) do
request(:get, "/v2/languages?type=#{type}") do |res|
# In DeepL, EN and PT are deprecated in favor of EN-GB/EN-US and PT-BR/PT-PT, so
# they are supported but not returned by the API.
extra = type == 'source' ? [nil] : %w(en pt)
languages = Oj.load(res.body_with_limit).map { |language| language['language'].downcase }
languages + extra
end
end
end
def request(verb, path, **options)
req = Request.new(verb, "#{base_url}#{path}", **options)
req.add_headers(Authorization: "DeepL-Auth-Key #{@api_key}")
req.perform do |res|
case res.code
when 429
raise TooManyRequestsError
when 456
raise QuotaExceededError
when 200...300
transform_response(res.body_with_limit)
yield res
else
raise UnexpectedResponseError
end
end
end
private
def request(text, source_language, target_language)
req = Request.new(:post, endpoint_url, form: { text: text, source_lang: source_language&.upcase, target_lang: target_language, tag_handling: 'html' })
req.add_headers(Authorization: "DeepL-Auth-Key #{@api_key}")
req
end
def endpoint_url
def base_url
if @plan == 'free'
'https://api-free.deepl.com/v2/translate'
'https://api-free.deepl.com'
else
'https://api.deepl.com/v2/translate'
'https://api.deepl.com'
end
end

View file

@ -9,29 +9,45 @@ class TranslationService::LibreTranslate < TranslationService
end
def translate(text, source_language, target_language)
request(text, source_language, target_language).perform do |res|
body = Oj.dump(q: text, source: source_language.presence || 'auto', target: target_language, format: 'html', api_key: @api_key)
request(:post, '/translate', body: body) do |res|
transform_response(res.body_with_limit, source_language)
end
end
def supported?(source_language, target_language)
languages.key?(source_language) && languages[source_language].include?(target_language)
end
private
def languages
Rails.cache.fetch('translation_service/libre_translate/languages', expires_in: 7.days, race_condition_ttl: 1.minute) do
request(:get, '/languages') do |res|
languages = Oj.load(res.body_with_limit).to_h { |language| [language['code'], language['targets']] }
languages[nil] = languages.values.flatten.uniq
languages
end
end
end
def request(verb, path, **options)
req = Request.new(verb, "#{@base_url}#{path}", allow_local: true, **options)
req.add_headers('Content-Type': 'application/json')
req.perform do |res|
case res.code
when 429
raise TooManyRequestsError
when 403
raise QuotaExceededError
when 200...300
transform_response(res.body_with_limit, source_language)
yield res
else
raise UnexpectedResponseError
end
end
end
private
def request(text, source_language, target_language)
body = Oj.dump(q: text, source: source_language.presence || 'auto', target: target_language, format: 'html', api_key: @api_key)
req = Request.new(:post, "#{@base_url}/translate", body: body, allow_local: true)
req.add_headers('Content-Type': 'application/json')
req
end
def transform_response(str, source_language)
json = Oj.load(str, mode: :strict)

View file

@ -7,9 +7,17 @@ class ApplicationMailer < ActionMailer::Base
helper :instance
helper :formatting
after_action :set_autoreply_headers!
protected
def locale_for_account(account, &block)
I18n.with_locale(account.user_locale || I18n.default_locale, &block)
end
def set_autoreply_headers!
headers['Precedence'] = 'list'
headers['X-Auto-Response-Suppress'] = 'All'
headers['Auto-Submitted'] = 'auto-generated'
end
end

View file

@ -17,13 +17,13 @@ class AccountFilter
attr_reader :params
def initialize(params)
@params = params
@params = params.to_h.symbolize_keys
end
def results
scope = Account.includes(:account_stat, user: [:ips, :invite_request]).without_instance_actor.reorder(nil)
params.each do |key, value|
relevant_params.each do |key, value|
next if key.to_s == 'page'
scope.merge!(scope_for(key, value)) if value.present?
@ -34,6 +34,16 @@ class AccountFilter
private
def relevant_params
params.tap do |args|
args.delete(:origin) if origin_is_remote_and_domain_present?
end
end
def origin_is_remote_and_domain_present?
params[:origin] == 'remote' && params[:by_domain].present?
end
def scope_for(key, value)
case key.to_s
when 'origin'
@ -94,7 +104,15 @@ class AccountFilter
def order_scope(value)
case value.to_s
when 'active'
accounts_with_users.left_joins(:account_stat).order(Arel.sql('coalesce(users.current_sign_in_at, account_stats.last_status_at, to_timestamp(0)) desc, accounts.id desc'))
accounts_with_users
.left_joins(:account_stat)
.order(
Arel.sql(
<<~SQL.squish
COALESCE(users.current_sign_in_at, account_stats.last_status_at, to_timestamp(0)) DESC, accounts.id DESC
SQL
)
)
when 'recent'
Account.recent
else

View file

@ -61,7 +61,7 @@ module Omniauthable
user.account.avatar_remote_url = nil
end
user.skip_confirmation! if email_is_verified
user.confirm! if email_is_verified
user.save!
user
end

View file

@ -20,9 +20,9 @@ class FollowRecommendationSuppression < ApplicationRecord
private
def remove_follow_recommendations
redis.pipelined do
redis.pipelined do |pipeline|
I18n.available_locales.each do |locale|
redis.zrem("follow_recommendations:#{locale}", account_id)
pipeline.zrem("follow_recommendations:#{locale}", account_id)
end
end
end

View file

@ -17,8 +17,8 @@ class Form::AccountBatch
unfollow!
when 'remove_from_followers'
remove_from_followers!
when 'block_domains'
block_domains!
when 'remove_domains_from_followers'
remove_domains_from_followers!
when 'approve'
approve!
when 'reject'
@ -35,9 +35,15 @@ class Form::AccountBatch
private
def follow!
error = nil
accounts.each do |target_account|
FollowService.new.call(current_account, target_account)
rescue Mastodon::NotPermittedError, ActiveRecord::RecordNotFound => e
error ||= e
end
raise error if error.present?
end
def unfollow!
@ -50,10 +56,8 @@ class Form::AccountBatch
RemoveFromFollowersService.new.call(current_account, account_ids)
end
def block_domains!
AfterAccountDomainBlockWorker.push_bulk(account_domains) do |domain|
[current_account.id, domain]
end
def remove_domains_from_followers!
RemoveDomainsFromFollowersService.new.call(current_account, account_domains)
end
def account_domains

View file

@ -23,6 +23,7 @@ class PollVote < ApplicationRecord
after_create_commit :increment_counter_cache
delegate :local?, to: :account
delegate :multiple?, :expired?, to: :poll, prefix: true
def object_type
:vote

View file

@ -238,6 +238,16 @@ class Status < ApplicationRecord
public_visibility? || unlisted_visibility?
end
def translatable?
translate_target_locale = I18n.locale.to_s.split(/[_-]/).first
distributable? &&
content.present? &&
language != translate_target_locale &&
TranslationService.configured? &&
TranslationService.configured.supported?(language, translate_target_locale)
end
alias sign? distributable?
def with_media?

View file

@ -74,10 +74,6 @@ class InstancePresenter < ActiveModelSerializers::Model
Rails.cache.fetch('distinct_domain_count') { Instance.count }
end
def sample_accounts
Rails.cache.fetch('sample_accounts', expires_in: 12.hours) { Account.local.discoverable.popular.limit(3) }
end
def version
Mastodon::Version.to_s
end

View file

@ -48,7 +48,6 @@ class InitialStateSerializer < ActiveModel::Serializer
timeline_preview: Setting.timeline_preview,
activity_api_enabled: Setting.activity_api_enabled,
single_user_mode: Rails.configuration.x.single_user_mode,
translation_enabled: TranslationService.configured?,
trends_as_landing_page: Setting.trends_as_landing_page,
status_page_url: Setting.status_page_url,
}

View file

@ -4,7 +4,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
include FormattingHelper
attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id,
:sensitive, :spoiler_text, :visibility, :language,
:sensitive, :spoiler_text, :visibility, :language, :translatable,
:uri, :url, :replies_count, :reblogs_count,
:favourites_count, :edited_at
@ -53,6 +53,10 @@ class REST::StatusSerializer < ActiveModel::Serializer
object.account.user_shows_application? || (current_user? && current_user.account_id == object.account_id)
end
def translatable
current_user? && object.translatable?
end
def visibility
# This visibility is masked behind "private"
# to avoid API changes because there are no

View file

@ -48,9 +48,9 @@ class BatchedRemoveStatusService < BaseService
# Cannot be batched
@status_id_cutoff = Mastodon::Snowflake.id_at(2.weeks.ago)
redis.pipelined do
redis.pipelined do |pipeline|
statuses.each do |status|
unpush_from_public_timelines(status)
unpush_from_public_timelines(status, pipeline)
end
end
end
@ -73,22 +73,22 @@ class BatchedRemoveStatusService < BaseService
end
end
def unpush_from_public_timelines(status)
def unpush_from_public_timelines(status, pipeline)
return unless status.public_visibility? && status.id > @status_id_cutoff
payload = Oj.dump(event: :delete, payload: status.id.to_s)
redis.publish('timeline:public', payload)
redis.publish(status.local? ? 'timeline:public:local' : 'timeline:public:remote', payload)
pipeline.publish('timeline:public', payload)
pipeline.publish(status.local? ? 'timeline:public:local' : 'timeline:public:remote', payload)
if status.media_attachments.any?
redis.publish('timeline:public:media', payload)
redis.publish(status.local? ? 'timeline:public:local:media' : 'timeline:public:remote:media', payload)
pipeline.publish('timeline:public:media', payload)
pipeline.publish(status.local? ? 'timeline:public:local:media' : 'timeline:public:remote:media', payload)
end
status.tags.map { |tag| tag.name.mb_chars.downcase }.each do |hashtag|
redis.publish("timeline:hashtag:#{hashtag}", payload)
redis.publish("timeline:hashtag:#{hashtag}:local", payload) if status.local?
pipeline.publish("timeline:hashtag:#{hashtag}", payload)
pipeline.publish("timeline:hashtag:#{hashtag}:local", payload) if status.local?
end
end

View file

@ -0,0 +1,40 @@
# frozen_string_literal: true
class FollowMigrationService < FollowService
# Follow an account with the same settings as another account, and unfollow the old account once the request is sent
# @param [Account] source_account From which to follow
# @param [Account] target_account Account to follow
# @param [Account] old_target_account Account to unfollow once the follow request has been sent to the new one
# @option [Boolean] bypass_locked Whether to immediately follow the new account even if it is locked
def call(source_account, target_account, old_target_account, bypass_locked: false)
@old_target_account = old_target_account
follow = source_account.active_relationships.find_by(target_account: old_target_account)
reblogs = follow&.show_reblogs?
notify = follow&.notify?
languages = follow&.languages
super(source_account, target_account, reblogs: reblogs, notify: notify, languages: languages, bypass_locked: bypass_locked, bypass_limit: true)
end
private
def request_follow!
follow_request = @source_account.request_follow!(@target_account, **follow_options.merge(rate_limit: @options[:with_rate_limit], bypass_limit: @options[:bypass_limit]))
if @target_account.local?
LocalNotificationWorker.perform_async(@target_account.id, follow_request.id, follow_request.class.name, 'follow_request')
UnfollowService.new.call(@source_account, @old_target_account, skip_unmerge: true)
elsif @target_account.activitypub?
ActivityPub::MigratedFollowDeliveryWorker.perform_async(build_json(follow_request), @source_account.id, @target_account.inbox_url, @old_target_account.id)
end
follow_request
end
def direct_follow!
follow = super
UnfollowService.new.call(@source_account, @old_target_account, skip_unmerge: true)
follow
end
end

View file

@ -67,7 +67,7 @@ class ImportService < BaseService
def import_relationships!(action, undo_action, overwrite_scope, limit, extra_fields = {})
local_domain_suffix = "@#{Rails.configuration.x.local_domain}"
items = @data.take(limit).map { |row| [row['Account address']&.strip&.delete_suffix(local_domain_suffix), Hash[extra_fields.map { |key, field_settings| [key, row[field_settings[:header]]&.strip || field_settings[:default]] }]] }.reject { |(id, _)| id.blank? }
items = @data.take(limit).map { |row| [row['Account address']&.strip&.delete_suffix(local_domain_suffix), extra_fields.to_h { |key, field_settings| [key, row[field_settings[:header]]&.strip || field_settings[:default]] }] }.reject { |(id, _)| id.blank? }
if @import.overwrite?
presence_hash = items.each_with_object({}) { |(id, extra), mapping| mapping[id] = [true, extra] }

View file

@ -0,0 +1,23 @@
# frozen_string_literal: true
class RemoveDomainsFromFollowersService < BaseService
include Payloadable
def call(source_account, target_domains)
source_account.passive_relationships.where(account_id: Account.where(domain: target_domains)).find_each do |follow|
follow.destroy
create_notification(follow) if source_account.local? && !follow.account.local? && follow.account.activitypub?
end
end
private
def create_notification(follow)
ActivityPub::DeliveryWorker.perform_async(build_json(follow), follow.target_account_id, follow.account.inbox_url)
end
def build_json(follow)
Oj.dump(serialize_payload(follow, ActivityPub::RejectFollowSerializer))
end
end

View file

@ -6,7 +6,7 @@ class TranslateStatusService < BaseService
include FormattingHelper
def call(status, target_language)
raise Mastodon::NotPermittedError unless status.public_visibility? || status.unlisted_visibility?
raise Mastodon::NotPermittedError unless status.translatable?
@status = status
@content = status_content_format(@status)

View file

@ -6,7 +6,7 @@ class Ed25519KeyValidator < ActiveModel::EachValidator
key = Base64.decode64(value)
record.errors[attribute] << I18n.t('crypto.errors.invalid_key') unless verified?(key)
record.errors.add(attribute, I18n.t('crypto.errors.invalid_key')) unless verified?(key)
end
private

View file

@ -8,7 +8,7 @@ class Ed25519SignatureValidator < ActiveModel::EachValidator
signature = Base64.decode64(value)
message = option_to_value(record, :message)
record.errors[attribute] << I18n.t('crypto.errors.invalid_signature') unless verified?(verify_key, signature, message)
record.errors.add(attribute, I18n.t('crypto.errors.invalid_signature')) unless verified?(verify_key, signature, message)
end
private

View file

@ -2,13 +2,13 @@
class VoteValidator < ActiveModel::Validator
def validate(vote)
vote.errors.add(:base, I18n.t('polls.errors.expired')) if vote.poll.expired?
vote.errors.add(:base, I18n.t('polls.errors.expired')) if vote.poll_expired?
vote.errors.add(:base, I18n.t('polls.errors.invalid_choice')) if invalid_choice?(vote)
if vote.poll.multiple? && vote.poll.votes.where(account: vote.account, choice: vote.choice).exists?
if vote.poll_multiple? && already_voted_for_same_choice_on_multiple_poll?(vote)
vote.errors.add(:base, I18n.t('polls.errors.already_voted'))
elsif !vote.poll.multiple? && vote.poll.votes.where(account: vote.account).exists?
elsif !vote.poll_multiple? && already_voted_on_non_multiple_poll?(vote)
vote.errors.add(:base, I18n.t('polls.errors.already_voted'))
end
end
@ -18,4 +18,24 @@ class VoteValidator < ActiveModel::Validator
def invalid_choice?(vote)
vote.choice.negative? || vote.choice >= vote.poll.options.size
end
def already_voted_for_same_choice_on_multiple_poll?(vote)
if vote.persisted?
account_votes_on_same_poll(vote).where(choice: vote.choice).where.not(poll_votes: { id: vote }).exists?
else
account_votes_on_same_poll(vote).where(choice: vote.choice).exists?
end
end
def already_voted_on_non_multiple_poll?(vote)
if vote.persisted?
account_votes_on_same_poll(vote).where.not(poll_votes: { id: vote }).exists?
else
account_votes_on_same_poll(vote).exists?
end
end
def account_votes_on_same_poll(vote)
vote.poll.votes.where(account: vote.account)
end
end

View file

@ -5,7 +5,7 @@ RSS::Builder.build do |doc|
doc.image(full_asset_url(@account.avatar.url(:original)), display_name(@account), params[:tag].present? ? short_account_tag_url(@account, params[:tag]) : short_account_url(@account))
doc.last_build_date(@statuses.first.created_at) if @statuses.any?
doc.icon(full_asset_url(@account.avatar.url(:original)))
doc.generator("Mastodon v#{Mastodon::Version.to_s}")
doc.generator("Mastodon v#{Mastodon::Version}")
@statuses.each do |status|
doc.item do |item|
@ -18,12 +18,12 @@ RSS::Builder.build do |doc|
item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, media.file.size)
end
status.ordered_media_attachments.each do |media|
item.media_content(full_asset_url(media.file.url(:original, false)), media.file.content_type, media.file.size) do |media_content|
media_content.medium(media.gifv? ? 'image' : media.type.to_s)
status.ordered_media_attachments.each do |media_attachment|
item.media_content(full_asset_url(media_attachment.file.url(:original, false)), media_attachment.file.content_type, media_attachment.file.size) do |media_content|
media_content.medium(media_attachment.gifv? ? 'image' : media_attachment.type.to_s)
media_content.rating(status.sensitive? ? 'adult' : 'nonadult')
media_content.description(media.description) if media.description.present?
media_content.thumbnail(media.thumbnail.url(:original, false)) if media.thumbnail?
media_content.description(media_attachment.description) if media_attachment.description.present?
media_content.thumbnail(media_attachment.thumbnail.url(:original, false)) if media_attachment.thumbnail?
end
end

Some files were not shown because too many files have changed in this diff Show more