diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index d14af5d7d9..97331f74ea 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -70,7 +70,7 @@ services: hard: -1 libretranslate: - image: libretranslate/libretranslate:v1.5.6 + image: libretranslate/libretranslate:v1.5.7 restart: unless-stopped volumes: - lt-data:/home/libretranslate/.local diff --git a/.env.development b/.env.development new file mode 100644 index 0000000000..0330da8377 --- /dev/null +++ b/.env.development @@ -0,0 +1,4 @@ +# Required by ActiveRecord encryption feature +ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=fkSxKD2bF396kdQbrP1EJ7WbU7ZgNokR +ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=r0hvVmzBVsjxC7AMlwhOzmtc36ZCOS1E +ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=PhdFyyfy5xJ7WVd2lWBpcPScRQHzRTNr diff --git a/.env.test b/.env.test index 2f8c1afd6e..9e6abea5c9 100644 --- a/.env.test +++ b/.env.test @@ -3,3 +3,8 @@ NODE_ENV=production # Federation LOCAL_DOMAIN=cb6e6126.ngrok.io LOCAL_HTTPS=true + +# Required by ActiveRecord encryption feature +ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=fkSxKD2bF396kdQbrP1EJ7WbU7ZgNokR +ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=r0hvVmzBVsjxC7AMlwhOzmtc36ZCOS1E +ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=PhdFyyfy5xJ7WVd2lWBpcPScRQHzRTNr diff --git a/.eslintrc.js b/.eslintrc.js index 87a1af483b..8fe3a98b4a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -380,6 +380,7 @@ module.exports = defineConfig({ "message": "Use typed hooks `useAppDispatch` and `useAppSelector` instead." } ], + "@typescript-eslint/restrict-template-expressions": ['warn', { allowNumber: true }], 'jsdoc/require-jsdoc': 'off', // Those rules set stricter rules for TS files diff --git a/.github/stylelint-matcher.json b/.github/stylelint-matcher.json deleted file mode 100644 index cdfd4086bd..0000000000 --- a/.github/stylelint-matcher.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "problemMatcher": [ - { - "owner": "stylelint", - "pattern": [ - { - "regexp": "^([^\\s].*)$", - "file": 1 - }, - { - "regexp": "^\\s+((\\d+):(\\d+))?\\s+(✖|×)\\s+(.*)\\s{2,}(.*)$", - "line": 2, - "column": 3, - "message": 5, - "code": 6, - "loop": true - } - ] - } - ] -} diff --git a/.github/workflows/crowdin-download.yml b/.github/workflows/crowdin-download.yml index d5023c53e7..08de2749fb 100644 --- a/.github/workflows/crowdin-download.yml +++ b/.github/workflows/crowdin-download.yml @@ -53,7 +53,7 @@ jobs: # Create or update the pull request - name: Create Pull Request - uses: peter-evans/create-pull-request@v6.0.2 + uses: peter-evans/create-pull-request@v6.0.5 with: commit-message: 'New Crowdin translations' title: 'New Crowdin Translations (automated)' diff --git a/.github/workflows/lint-css.yml b/.github/workflows/lint-css.yml index e5f4874877..d3b8035cd8 100644 --- a/.github/workflows/lint-css.yml +++ b/.github/workflows/lint-css.yml @@ -38,9 +38,5 @@ jobs: - name: Set up Javascript environment uses: ./.github/actions/setup-javascript - - uses: xt0rted/stylelint-problem-matcher@v1 - - - run: echo "::add-matcher::.github/stylelint-matcher.json" - - name: Stylelint - run: yarn lint:css + run: yarn lint:css -f github diff --git a/.github/workflows/test-js.yml b/.github/workflows/test-js.yml index 79622b6c1f..481afdba30 100644 --- a/.github/workflows/test-js.yml +++ b/.github/workflows/test-js.yml @@ -38,5 +38,5 @@ jobs: - name: Set up Javascript environment uses: ./.github/actions/setup-javascript - - name: Jest testing + - name: JavaScript testing run: yarn jest --reporters github-actions summary diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index 624c3b7a2f..b28f5261c2 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -28,6 +28,9 @@ jobs: env: RAILS_ENV: ${{ matrix.mode }} BUNDLE_WITH: ${{ matrix.mode }} + ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY: precompile_placeholder + ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT: precompile_placeholder + ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY: precompile_placeholder OTP_SECRET: precompile_placeholder SECRET_KEY_BASE: precompile_placeholder @@ -111,10 +114,9 @@ jobs: fail-fast: false matrix: ruby-version: - - '3.0' - '3.1' + - '3.2' - '.ruby-version' - - '3.3' steps: - uses: actions/checkout@v4 @@ -187,10 +189,9 @@ jobs: fail-fast: false matrix: ruby-version: - - '3.0' - '3.1' + - '3.2' - '.ruby-version' - - '3.3' steps: - uses: actions/checkout@v4 @@ -287,10 +288,9 @@ jobs: fail-fast: false matrix: ruby-version: - - '3.0' - '3.1' + - '3.2' - '.ruby-version' - - '3.3' search-image: - docker.elastic.co/elasticsearch/elasticsearch:7.17.13 include: diff --git a/.gitignore b/.gitignore index c5af8eb67f..a70f30f952 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,6 @@ /public/packs-test .env .env.production -.env.development /node_modules/ /build/ @@ -69,3 +68,6 @@ yarn-debug.log # Ignore Docker option files docker-compose.override.yml + +# Ignore dotenv .local files +.env*.local diff --git a/.haml-lint.yml b/.haml-lint.yml index 2b553ca56c..74d243a3ad 100644 --- a/.haml-lint.yml +++ b/.haml-lint.yml @@ -1,6 +1,5 @@ exclude: - 'vendor/**/*' - - lib/templates/haml/scaffold/_form.html.haml require: - ./lib/linter/haml_middle_dot.rb @@ -11,6 +10,6 @@ linters: MiddleDot: enabled: true LineLength: - max: 320 + max: 300 ViewLength: max: 200 # Override default value of 100 inherited from rubocop diff --git a/.nvmrc b/.nvmrc index a3597ecbd1..7795cadb57 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20.11 +20.12 diff --git a/.rubocop.yml b/.rubocop.yml index d968346f6b..542e90b5e3 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -9,12 +9,13 @@ inherit_mode: require: - rubocop-rails - rubocop-rspec + - rubocop-rspec_rails - rubocop-performance - rubocop-capybara - ./lib/linter/rubocop_middle_dot AllCops: - TargetRubyVersion: 3.0 # Set to minimum supported version of CI + TargetRubyVersion: 3.1 # Set to minimum supported version of CI DisplayCopNames: true DisplayStyleGuide: true ExtraDetails: true @@ -39,13 +40,7 @@ Layout/FirstHashElementIndentation: # Reason: Currently disabled in .rubocop_todo.yml # https://docs.rubocop.org/rubocop/cops_layout.html#layoutlinelength Layout/LineLength: - Max: 320 # Default of 120 causes a duplicate entry in generated todo file - -# Reason: -# https://docs.rubocop.org/rubocop/cops_lint.html#lintuselessaccessmodifier -Lint/UselessAccessModifier: - ContextCreatingMethods: - - class_methods + Max: 300 # Default of 120 causes a duplicate entry in generated todo file ## Disable most Metrics/*Length cops # Reason: those are often triggered and force significant refactors when this happend @@ -86,6 +81,11 @@ Metrics/CyclomaticComplexity: Metrics/ParameterLists: CountKeywordArgs: false +# Reason: Prefer seeing a variable name +# https://docs.rubocop.org/rubocop/cops_naming.html#namingblockforwarding +Naming/BlockForwarding: + EnforcedStyle: explicit + # Reason: Prevailing style is argument file paths # https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsfilepath Rails/FilePath: @@ -148,11 +148,6 @@ RSpec/NamedSubject: RSpec/NotToNot: EnforcedStyle: to_not -# Reason: Prevailing style uses numeric status codes, matches Rails/HttpStatus -# https://docs.rubocop.org/rubocop-rspec/cops_rspec_rails.html#rspecrailshttpstatus -RSpec/Rails/HttpStatus: - EnforcedStyle: numeric - # Reason: Match overrides from Rspec/FilePath rule above # https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecspecfilepathformat RSpec/SpecFilePathFormat: @@ -163,6 +158,11 @@ RSpec/SpecFilePathFormat: OEmbedController: oembed_controller OStatus: ostatus +# Reason: Prevailing style uses numeric status codes, matches Rails/HttpStatus +# https://docs.rubocop.org/rubocop-rspec/cops_rspec_rails.html#rspecrailshttpstatus +RSpecRails/HttpStatus: + EnforcedStyle: numeric + # Reason: # https://docs.rubocop.org/rubocop/cops_style.html#styleclassandmodulechildren Style/ClassAndModuleChildren: @@ -182,10 +182,16 @@ Style/FormatStringToken: AllowedMethods: - redirect_with_vary +# Reason: Prevailing style choice +# https://docs.rubocop.org/rubocop/cops_style.html#stylehashaslastarrayitem +Style/HashAsLastArrayItem: + Enabled: false + # Reason: Enforce modern Ruby style # https://docs.rubocop.org/rubocop/cops_style.html#stylehashsyntax Style/HashSyntax: EnforcedStyle: ruby19_no_mixed_keys + EnforcedShorthandSyntax: either # Reason: # https://docs.rubocop.org/rubocop/cops_style.html#stylenumericliterals diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index e7d1d08011..0b6f63ff7e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,18 +1,11 @@ # This configuration was generated by # `rubocop --auto-gen-config --auto-gen-only-exclude --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.60.2. +# using RuboCop version 1.62.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include. -# Include: **/*.gemfile, **/Gemfile, **/gems.rb -Bundler/OrderedGems: - Exclude: - - 'Gemfile' - Lint/NonLocalExitFromIterator: Exclude: - 'app/helpers/jsonld_helper.rb' @@ -36,7 +29,7 @@ Metrics/PerceivedComplexity: # Configuration parameters: CountAsOne. RSpec/ExampleLength: - Max: 20 # Override default of 5 + Max: 18 RSpec/MultipleExpectations: Max: 7 @@ -49,27 +42,10 @@ RSpec/MultipleMemoizedHelpers: RSpec/NestedGroups: Max: 6 -# Configuration parameters: Include. -# Include: app/models/**/*.rb -Rails/HasAndBelongsToMany: - Exclude: - - 'app/models/concerns/account/associations.rb' - - 'app/models/status.rb' - - 'app/models/tag.rb' - Rails/OutputSafety: Exclude: - 'config/initializers/simple_form.rb' -# Configuration parameters: Include. -# Include: app/models/**/*.rb -Rails/UniqueValidationWithoutIndex: - Exclude: - - 'app/models/account_alias.rb' - - 'app/models/custom_filter_status.rb' - - 'app/models/identity.rb' - - 'app/models/webauthn_credential.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: AllowedMethods, AllowedPatterns. # AllowedMethods: ==, equal?, eql? @@ -88,7 +64,6 @@ Style/FetchEnvVar: Exclude: - 'app/lib/redis_configuration.rb' - 'app/lib/translation_service.rb' - - 'config/environments/development.rb' - 'config/environments/production.rb' - 'config/initializers/2_limited_federation_mode.rb' - 'config/initializers/3_omniauth.rb' @@ -98,7 +73,6 @@ Style/FetchEnvVar: - 'config/initializers/paperclip.rb' - 'config/initializers/vapid.rb' - 'lib/mastodon/redis_config.rb' - - 'lib/premailer_webpack_strategy.rb' - 'lib/tasks/repo.rake' - 'spec/features/profile_spec.rb' @@ -144,22 +118,8 @@ Style/GuardClause: - 'lib/mastodon/cli/accounts.rb' - 'lib/mastodon/cli/maintenance.rb' - 'lib/mastodon/cli/media.rb' - - 'lib/paperclip/attachment_extensions.rb' - 'lib/tasks/repo.rake' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: braces, no_braces -Style/HashAsLastArrayItem: - Exclude: - - 'app/controllers/admin/statuses_controller.rb' - - 'app/controllers/api/v1/statuses_controller.rb' - - 'app/models/concerns/account/counters.rb' - - 'app/models/concerns/status/threading_concern.rb' - - 'app/models/status.rb' - - 'app/services/batched_remove_status_service.rb' - - 'app/services/notify_service.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Style/HashTransformValues: Exclude: @@ -207,13 +167,6 @@ Style/OptionalBooleanParameter: - 'app/workers/unfollow_follow_worker.rb' - 'lib/mastodon/redis_config.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: PreferredDelimiters. -Style/PercentLiteralDelimiters: - Exclude: - - 'config/deploy.rb' - - 'config/initializers/doorkeeper.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: short, verbose @@ -244,52 +197,12 @@ Style/SafeNavigation: Exclude: - 'app/models/concerns/account/finder_concern.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: only_raise, only_fail, semantic -Style/SignalException: - Exclude: - - 'lib/devise/strategies/two_factor_ldap_authenticatable.rb' - - 'lib/devise/strategies/two_factor_pam_authenticatable.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/SingleArgumentDig: - Exclude: - - 'lib/webpacker/manifest_extensions.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: Mode. Style/StringConcatenation: Exclude: - 'config/initializers/paperclip.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. -# SupportedStyles: single_quotes, double_quotes -Style/StringLiterals: - Exclude: - - 'config/environments/production.rb' - - 'config/initializers/backtrace_silencers.rb' - - 'config/initializers/http_client_proxy.rb' - - 'config/initializers/rack_attack.rb' - - 'config/initializers/webauthn.rb' - - 'config/routes.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyleForMultiline. -# SupportedStylesForMultiline: comma, consistent_comma, no_comma -Style/TrailingCommaInArguments: - Exclude: - - 'config/initializers/paperclip.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyleForMultiline. -# SupportedStylesForMultiline: comma, consistent_comma, no_comma -Style/TrailingCommaInHashLiteral: - Exclude: - - 'config/environments/production.rb' - - 'config/environments/test.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: WordRegex. # SupportedStyles: percent, brackets diff --git a/.ruby-version b/.ruby-version index 15a2799817..bea438e9ad 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.3.0 +3.3.1 diff --git a/Dockerfile b/Dockerfile index 8778133e0d..4278242bc9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -# syntax=docker/dockerfile:1.4 +# syntax=docker/dockerfile:1.7 # Please see https://docs.docker.com/engine/reference/builder for information about # the extended buildx capabilities used in this file. @@ -7,15 +7,15 @@ ARG TARGETPLATFORM=${TARGETPLATFORM} ARG BUILDPLATFORM=${BUILDPLATFORM} -# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.2.3"] -ARG RUBY_VERSION="3.2.3" +# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.3.1"] +ARG RUBY_VERSION="3.3.1" # # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"] ARG NODE_MAJOR_VERSION="20" # Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="bookworm"] ARG DEBIAN_VERSION="bookworm" # Node image to use for base image based on combined variables (ex: 20-bookworm-slim) FROM docker.io/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim as node -# Ruby image to use for base image based on combined variables (ex: 3.2.3-slim-bookworm) +# Ruby image to use for base image based on combined variables (ex: 3.3.1-slim-bookworm) FROM docker.io/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} as ruby # Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA @@ -29,7 +29,7 @@ ARG MASTODON_VERSION_METADATA="" # See: https://docs.joinmastodon.org/admin/config/#rails_serve_static_files ARG RAILS_SERVE_STATIC_FILES="true" # Allow to use YJIT compiler -# See: https://github.com/ruby/ruby/blob/v3_2_3/doc/yjit/yjit.md +# See: https://github.com/ruby/ruby/blob/v3_2_4/doc/yjit/yjit.md ARG RUBY_YJIT_ENABLE="1" # Timezone used by the Docker container and runtime, change with [--build-arg TZ=Europe/Berlin] ARG TZ="Etc/UTC" @@ -205,7 +205,12 @@ ARG TARGETPLATFORM RUN \ # Use Ruby on Rails to create Mastodon assets - OTP_SECRET=precompile_placeholder SECRET_KEY_BASE=precompile_placeholder bundle exec rails assets:precompile; \ + ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=precompile_placeholder \ + ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=precompile_placeholder \ + ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=precompile_placeholder \ + OTP_SECRET=precompile_placeholder \ + SECRET_KEY_BASE=precompile_placeholder \ + bundle exec rails assets:precompile; \ # Cleanup temporary files rm -fr /opt/mastodon/tmp; @@ -257,4 +262,4 @@ USER mastodon # Expose default Puma ports EXPOSE 3000 # Set container tini as default entry point -ENTRYPOINT ["/usr/bin/tini", "--"] \ No newline at end of file +ENTRYPOINT ["/usr/bin/tini", "--"] diff --git a/Gemfile b/Gemfile index 5dfaeaba9a..eb507e9d18 100644 --- a/Gemfile +++ b/Gemfile @@ -1,28 +1,28 @@ # frozen_string_literal: true source 'https://rubygems.org' -ruby '>= 3.0.0' +ruby '>= 3.1.0' -gem 'puma', '~> 6.3' -gem 'rails', '~> 7.1.1' gem 'propshaft' -gem 'thor', '~> 1.2' +gem 'puma', '~> 6.3' gem 'rack', '~> 2.2.7' +gem 'rails', '~> 7.1.1' +gem 'thor', '~> 1.2' # For why irb is in the Gemfile, see: https://ruby.social/@st0012/111444685161478182 gem 'irb', '~> 1.8' +gem 'dotenv' gem 'haml-rails', '~>2.0' gem 'pg', '~> 1.5' gem 'pghero' -gem 'dotenv-rails', '~> 2.8' gem 'aws-sdk-s3', '~> 1.123', require: false +gem 'blurhash', '~> 0.1' gem 'fog-core', '<= 2.4.0' gem 'fog-openstack', '~> 1.0', require: false gem 'kt-paperclip', '~> 7.2' gem 'md-paperclip-azure', '~> 2.2', require: false -gem 'blurhash', '~> 0.1' gem 'active_model_serializers', '~> 0.10' gem 'addressable', '~> 2.8' @@ -31,7 +31,7 @@ gem 'browser' gem 'charlock_holmes', '~> 0.7.7' gem 'chewy', '~> 7.3' gem 'devise', '~> 4.9' -gem 'devise-two-factor', '~> 4.1' +gem 'devise-two-factor' group :pam_authentication, optional: true do gem 'devise_pam_authenticatable2', '~> 9.2' @@ -39,11 +39,11 @@ end gem 'net-ldap', '~> 0.18' -gem 'omniauth-cas', '~> 3.0.0.beta.1' -gem 'omniauth-saml', '~> 2.0' -gem 'omniauth_openid_connect', '~> 0.6.1' gem 'omniauth', '~> 2.0' +gem 'omniauth-cas', '~> 3.0.0.beta.1' +gem 'omniauth_openid_connect', '~> 0.6.1' gem 'omniauth-rails_csrf_protection', '~> 1.0' +gem 'omniauth-saml', '~> 2.0' gem 'color_diff', '~> 0.1' gem 'csv', '~> 3.2' @@ -53,9 +53,8 @@ gem 'ed25519', '~> 1.3' gem 'fast_blank', '~> 1.0' gem 'fastimage' gem 'hiredis', '~> 0.6' -gem 'redis-namespace', '~> 1.10' gem 'htmlentities', '~> 4.3' -gem 'http', '~> 5.1' +gem 'http', '~> 5.2.0' gem 'http_accept_language', '~> 2.1' gem 'httplog', '~> 1.6.2' gem 'i18n', '1.14.1' # TODO: Remove version when resolved: https://github.com/glebm/i18n-tasks/issues/552 / https://github.com/ruby-i18n/i18n/pull/688 @@ -63,40 +62,40 @@ gem 'idn-ruby', require: 'idn' gem 'inline_svg' gem 'kaminari', '~> 1.2' gem 'link_header', '~> 0.0' +gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' gem 'mime-types', '~> 3.5.0', require: 'mime/types/columnar' gem 'nokogiri', '~> 1.15' gem 'nsa' gem 'oj', '~> 3.14' gem 'ox', '~> 2.14' gem 'parslet' -gem 'posix-spawn' +gem 'premailer-rails' gem 'public_suffix', '~> 5.0' gem 'pundit', '~> 2.3' -gem 'premailer-rails' gem 'rack-attack', '~> 6.6' gem 'rack-cors', '~> 2.0', require: 'rack/cors' gem 'rails-i18n', '~> 7.0' gem 'redcarpet', '~> 3.6' gem 'redis', '~> 4.5', require: ['redis', 'redis/connection/hiredis'] -gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' +gem 'redis-namespace', '~> 1.10' gem 'rqrcode', '~> 2.2' gem 'ruby-progressbar', '~> 1.13' gem 'sanitize', '~> 6.0' gem 'scenic', '~> 1.7' gem 'sidekiq', '~> 6.5' +gem 'sidekiq-bulk', '~> 0.2.0' gem 'sidekiq-scheduler', '~> 5.0' gem 'sidekiq-unique-jobs', '~> 7.1' -gem 'sidekiq-bulk', '~> 0.2.0' -gem 'simple-navigation', '~> 4.4' gem 'simple_form', '~> 5.2' -gem 'stoplight', '~> 3.0.1' +gem 'simple-navigation', '~> 4.4' +gem 'stoplight', '~> 4.1' gem 'strong_migrations', '1.8.0' gem 'tty-prompt', '~> 0.23', require: false gem 'twitter-text', '~> 3.1.0' gem 'tzinfo-data', '~> 1.2023' +gem 'webauthn', '~> 3.0' gem 'webpacker', '~> 5.4' gem 'webpush', github: 'ClearlyClaire/webpush', ref: 'f14a4d52e201128b1b00245d11b6de80d6cfdcd9' -gem 'webauthn', '~> 3.0' gem 'json-ld' gem 'json-ld-preloaded', '~> 3.2' @@ -198,13 +197,14 @@ group :production do gem 'lograge', '~> 0.12' end +gem 'cocoon', '~> 1.2' gem 'concurrent-ruby', require: false gem 'connection_pool', require: false gem 'xorcist', '~> 1.1' -gem 'cocoon', '~> 1.2' - gem 'net-http', '~> 0.4.0' gem 'rubyzip', '~> 2.3' gem 'hcaptcha', '~> 7.1' + +gem 'mail', '~> 2.8' diff --git a/Gemfile.lock b/Gemfile.lock index b341ca84b3..435144700f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -97,22 +97,20 @@ GEM activerecord (>= 3.2, < 8.0) rake (>= 10.4, < 14.0) ast (2.4.2) - attr_encrypted (4.0.0) - encryptor (~> 3.0.0) - attr_required (1.0.1) + attr_required (1.0.2) awrence (1.2.1) aws-eventstream (1.3.0) - aws-partitions (1.873.0) - aws-sdk-core (3.190.1) + aws-partitions (1.922.0) + aws-sdk-core (3.194.1) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.75.0) - aws-sdk-core (~> 3, >= 3.188.0) + aws-sdk-kms (1.80.0) + aws-sdk-core (~> 3, >= 3.193.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.142.0) - aws-sdk-core (~> 3, >= 3.189.0) + aws-sdk-s3 (1.149.1) + aws-sdk-core (~> 3, >= 3.194.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.8) aws-sigv4 (1.8.0) @@ -132,7 +130,7 @@ GEM erubi (>= 1.0.0) rack (>= 0.9.0) rouge (>= 1.0.0) - better_html (2.0.2) + better_html (2.1.1) actionview (>= 6.0) activesupport (>= 6.0) ast (~> 2.0) @@ -140,9 +138,9 @@ GEM parser (>= 2.4) smart_properties bigdecimal (3.1.7) - bindata (2.4.15) - binding_of_caller (1.0.0) - debug_inspector (>= 0.0.1) + bindata (2.5.0) + binding_of_caller (1.0.1) + debug_inspector (>= 1.2.0) blurhash (0.1.7) bootsnap (1.18.3) msgpack (~> 1.2) @@ -167,11 +165,11 @@ GEM xpath (~> 3.2) case_transform (0.2) activesupport - cbor (0.5.9.6) + cbor (0.5.9.8) charlock_holmes (0.7.7) - chewy (7.5.1) + chewy (7.6.0) activesupport (>= 5.2) - elasticsearch (>= 7.12.0, < 7.14.0) + elasticsearch (>= 7.14.0, < 8) elasticsearch-dsl chunky_png (1.4.0) climate_control (1.2.0) @@ -182,31 +180,30 @@ GEM cose (1.3.0) cbor (~> 0.5.9) openssl-signature_algorithm (~> 1.0) - crack (0.4.6) + crack (1.0.0) bigdecimal rexml crass (1.0.6) - css_parser (1.14.0) + css_parser (1.17.1) addressable - csv (3.2.8) + csv (3.3.0) database_cleaner-active_record (2.1.0) activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) date (3.3.4) - debug (1.9.1) + debug (1.9.2) irb (~> 1.10) reline (>= 0.3.8) - debug_inspector (1.1.0) - devise (4.9.3) + debug_inspector (1.2.0) + devise (4.9.4) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) responders warden (~> 1.2.3) - devise-two-factor (4.1.1) + devise-two-factor (5.0.0) activesupport (~> 7.0) - attr_encrypted (>= 1.3, < 5, != 2) devise (~> 4.0) railties (~> 7.0) rotp (~> 6.0) @@ -217,36 +214,31 @@ GEM discard (1.3.0) activerecord (>= 4.2, < 8) docile (1.4.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) + domain_name (0.6.20240107) doorkeeper (5.6.9) railties (>= 5) - dotenv (2.8.1) - dotenv-rails (2.8.1) - dotenv (= 2.8.1) - railties (>= 3.2) + dotenv (3.1.2) drb (2.2.1) ed25519 (1.3.0) - elasticsearch (7.13.3) - elasticsearch-api (= 7.13.3) - elasticsearch-transport (= 7.13.3) - elasticsearch-api (7.13.3) + elasticsearch (7.17.10) + elasticsearch-api (= 7.17.10) + elasticsearch-transport (= 7.17.10) + elasticsearch-api (7.17.10) multi_json elasticsearch-dsl (0.1.10) - elasticsearch-transport (7.13.3) - faraday (~> 1) + elasticsearch-transport (7.17.10) + faraday (>= 1, < 3) multi_json email_spec (2.2.2) htmlentities (~> 4.3.3) launchy (~> 2.1) mail (~> 2.7) - encryptor (3.0.0) erubi (1.12.0) - et-orbi (1.2.7) + et-orbi (1.2.11) tzinfo - excon (0.109.0) + excon (0.110.0) fabrication (2.31.0) - faker (3.2.3) + faker (3.3.1) i18n (>= 1.8.11, < 2) faraday (1.10.3) faraday-em_http (~> 1.0) @@ -274,10 +266,10 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fast_blank (1.0.1) - fastimage (2.3.0) - ffi (1.15.5) - ffi-compiler (1.0.1) - ffi (>= 1.0.0) + fastimage (2.3.1) + ffi (1.16.3) + ffi-compiler (1.3.2) + ffi (>= 1.15.5) rake fog-core (2.4.0) builder @@ -291,7 +283,7 @@ GEM fog-core (~> 2.1) fog-json (>= 1.0) formatador (1.1.0) - fugit (1.8.1) + fugit (1.10.1) et-orbi (~> 1, >= 1.2.7) raabro (~> 1.4) fuubar (2.5.1) @@ -308,7 +300,7 @@ GEM activesupport (>= 5.1) haml (>= 4.0.6) railties (>= 5.1) - haml_lint (0.57.0) + haml_lint (0.58.0) haml (>= 5.0) parallel (~> 1.10) rainbow @@ -318,15 +310,16 @@ GEM hashie (5.0.0) hcaptcha (7.1.0) json - highline (2.1.0) + highline (3.0.1) hiredis (0.6.3) hkdf (0.3.0) htmlentities (4.3.4) - http (5.1.1) + http (5.2.0) addressable (~> 2.8) + base64 (~> 0.1) http-cookie (~> 1.0) http-form_data (~> 2.2) - llhttp-ffi (~> 0.4.0) + llhttp-ffi (~> 0.5.0) http-cookie (1.0.5) domain_name (~> 0.5) http-form_data (2.3.0) @@ -353,11 +346,11 @@ GEM activesupport (>= 3.0) nokogiri (>= 1.6) io-console (0.7.2) - irb (1.12.0) - rdoc + irb (1.13.1) + rdoc (>= 4.0.0) reline (>= 0.4.2) jmespath (1.6.2) - json (2.7.1) + json (2.7.2) json-canonicalization (1.0.0) json-jwt (1.15.3.1) activesupport (>= 4.2) @@ -374,7 +367,7 @@ GEM json-ld-preloaded (3.3.0) json-ld (~> 3.3) rdf (~> 3.3) - json-schema (4.2.0) + json-schema (4.3.0) addressable (>= 2.8) jsonapi-renderer (0.2.2) jwt (2.7.1) @@ -399,15 +392,15 @@ GEM language_server-protocol (3.17.0.3) launchy (2.5.2) addressable (~> 2.8) - letter_opener (1.8.1) - launchy (>= 2.2, < 3) + letter_opener (1.10.0) + launchy (>= 2.2, < 4) letter_opener_web (2.0.0) actionmailer (>= 5.2) letter_opener (~> 1.7) railties (>= 5.2) rexml link_header (0.0.8) - llhttp-ffi (0.4.0) + llhttp-ffi (0.5.0) ffi-compiler (~> 1.0) rake (~> 13.0) lograge (0.14.0) @@ -423,7 +416,7 @@ GEM net-imap net-pop net-smtp - marcel (1.0.2) + marcel (1.0.4) mario-redis-lock (1.2.1) redis (>= 3.0.5) matrix (0.4.2) @@ -434,13 +427,13 @@ GEM memory_profiler (1.0.1) mime-types (3.5.2) mime-types-data (~> 3.2015) - mime-types-data (3.2023.1205) + mime-types-data (3.2024.0305) mini_mime (1.1.5) - mini_portile2 (2.8.5) + mini_portile2 (2.8.6) minitest (5.22.3) msgpack (1.7.2) multi_json (1.15.0) - multipart-post (2.3.0) + multipart-post (2.4.0) mutex_m (0.2.0) net-http (0.4.1) uri @@ -454,10 +447,10 @@ GEM net-protocol net-protocol (0.2.2) timeout - net-smtp (0.4.0.1) + net-smtp (0.5.0) net-protocol - nio4r (2.5.9) - nokogiri (1.16.3) + nio4r (2.7.1) + nokogiri (1.16.4) mini_portile2 (~> 2.8.2) racc (~> 1.4) nsa (0.3.0) @@ -501,7 +494,7 @@ GEM orm_adapter (0.5.0) ox (2.14.18) parallel (1.24.0) - parser (3.3.0.5) + parser (3.3.1.0) ast (~> 2.4.1) racc parslet (2.0.0) @@ -510,8 +503,7 @@ GEM pg (1.5.6) pghero (3.4.1) activerecord (>= 6) - posix-spawn (0.3.15) - premailer (1.21.0) + premailer (1.23.0) addressable css_parser (>= 1.12.0) htmlentities (>= 4.0.0) @@ -527,7 +519,7 @@ GEM railties (>= 7.0.0) psych (5.1.2) stringio - public_suffix (5.0.4) + public_suffix (5.0.5) puma (6.4.2) nio4r (~> 2.0) pundit (2.3.1) @@ -548,7 +540,7 @@ GEM rack-protection (3.2.0) base64 (>= 0.1.0) rack (~> 2.2, >= 2.2.4) - rack-proxy (0.7.6) + rack-proxy (0.7.7) rack rack-session (1.0.2) rack (< 3) @@ -594,7 +586,7 @@ GEM thor (~> 1.0, >= 1.2.2) zeitwerk (~> 2.6) rainbow (3.1.1) - rake (13.1.0) + rake (13.2.1) rdf (3.3.1) bcp47_spec (~> 0.2) link_header (~> 0.0, >= 0.0.8) @@ -609,16 +601,16 @@ GEM redlock (1.3.2) redis (>= 3.0.0, < 6.0) regexp_parser (2.9.0) - reline (0.4.3) + reline (0.5.5) io-console (~> 0.5) - request_store (1.5.1) + request_store (1.6.0) rack (>= 1.4) responders (3.1.1) actionpack (>= 5.2) railties (>= 5.2) rexml (3.2.6) rotp (6.3.0) - rouge (4.1.2) + rouge (4.2.1) rpam2 (4.0.2) rqrcode (2.2.0) chunky_png (~> 1.0) @@ -642,13 +634,13 @@ GEM rspec-expectations (~> 3.13) rspec-mocks (~> 3.13) rspec-support (~> 3.13) - rspec-sidekiq (4.1.0) + rspec-sidekiq (4.2.0) rspec-core (~> 3.0) rspec-expectations (~> 3.0) rspec-mocks (~> 3.0) sidekiq (>= 5, < 8) rspec-support (3.13.1) - rubocop (1.62.1) + rubocop (1.63.4) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -659,27 +651,30 @@ GEM rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.31.2) - parser (>= 3.3.0.4) + rubocop-ast (1.31.3) + parser (>= 3.3.1.0) rubocop-capybara (2.20.0) rubocop (~> 1.41) rubocop-factory_bot (2.25.1) rubocop (~> 1.41) - rubocop-performance (1.20.2) + rubocop-performance (1.21.0) rubocop (>= 1.48.1, < 2.0) - rubocop-ast (>= 1.30.0, < 2.0) - rubocop-rails (2.24.0) + rubocop-ast (>= 1.31.1, < 2.0) + rubocop-rails (2.24.1) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.33.0, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rspec (2.27.1) + rubocop-rspec (2.29.2) rubocop (~> 1.40) rubocop-capybara (~> 2.17) rubocop-factory_bot (~> 2.22) + rubocop-rspec_rails (~> 2.28) + rubocop-rspec_rails (2.28.3) + rubocop (~> 1.40) ruby-prof (1.7.0) ruby-progressbar (1.13.0) - ruby-saml (1.15.0) + ruby-saml (1.16.0) nokogiri (>= 1.13.10) rexml ruby2_keywords (0.0.5) @@ -691,10 +686,10 @@ GEM sanitize (6.1.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) - scenic (1.7.0) + scenic (1.8.0) activerecord (>= 4.0.0) railties (>= 4.0.0) - selenium-webdriver (4.18.1) + selenium-webdriver (4.20.1) base64 (~> 0.2) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) @@ -731,7 +726,7 @@ GEM smart_properties (1.17.0) stackprof (0.2.26) statsd-ruby (1.5.0) - stoplight (3.0.2) + stoplight (4.1.0) redlock (~> 1.0) stringio (3.1.0) strong_migrations (1.8.0) @@ -746,7 +741,7 @@ GEM unicode-display_width (>= 1.1.1, < 3) terrapin (1.0.1) climate_control - test-prof (1.3.2) + test-prof (1.3.3) thor (1.3.1) tilt (2.3.0) timeout (0.4.1) @@ -763,7 +758,7 @@ GEM tty-cursor (~> 0.7) tty-screen (~> 0.8) wisper (~> 2.0) - tty-screen (0.8.1) + tty-screen (0.8.2) twitter-text (3.1.0) idn-ruby unf (~> 0.1.0) @@ -773,9 +768,9 @@ GEM tzinfo (>= 1.0.0) unf (0.1.4) unf_ext - unf_ext (0.0.8.2) + unf_ext (0.0.9.1) unicode-display_width (2.5.0) - uri (0.12.2) + uri (0.13.0) validate_email (0.1.6) activemodel (>= 3.0) mail (>= 2.2.5) @@ -796,7 +791,7 @@ GEM webfinger (1.2.0) activesupport httpclient (>= 2.4) - webmock (3.22.0) + webmock (3.23.0) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) @@ -843,11 +838,11 @@ DEPENDENCIES database_cleaner-active_record debug (~> 1.8) devise (~> 4.9) - devise-two-factor (~> 4.1) + devise-two-factor devise_pam_authenticatable2 (~> 9.2) discard (~> 1.2) doorkeeper (~> 5.6) - dotenv-rails (~> 2.8) + dotenv ed25519 (~> 1.3) email_spec fabrication (~> 2.30) @@ -862,7 +857,7 @@ DEPENDENCIES hcaptcha (~> 7.1) hiredis (~> 0.6) htmlentities (~> 4.3) - http (~> 5.1) + http (~> 5.2.0) http_accept_language (~> 2.1) httplog (~> 1.6.2) i18n (= 1.14.1) @@ -879,6 +874,7 @@ DEPENDENCIES letter_opener_web (~> 2.0) link_header (~> 0.0) lograge (~> 0.12) + mail (~> 2.8) mario-redis-lock (~> 1.2) md-paperclip-azure (~> 2.2) memory_profiler @@ -897,7 +893,6 @@ DEPENDENCIES parslet pg (~> 1.5) pghero - posix-spawn premailer-rails private_address_check (~> 0.5) propshaft @@ -939,7 +934,7 @@ DEPENDENCIES simplecov (~> 0.22) simplecov-lcov (~> 0.8) stackprof - stoplight (~> 3.0.1) + stoplight (~> 4.1) strong_migrations (= 1.8.0) test-prof thor (~> 1.2) @@ -953,7 +948,7 @@ DEPENDENCIES xorcist (~> 1.1) RUBY VERSION - ruby 3.2.2p53 + ruby 3.3.1p55 BUNDLED WITH - 2.5.4 + 2.5.9 diff --git a/Vagrantfile b/Vagrantfile index 12bd1ba67a..8a95e91f36 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -173,6 +173,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| # Otherwise, you can access the site at http://localhost:3000 and http://localhost:4000 , http://localhost:8080 config.vm.network :forwarded_port, guest: 3000, host: 3000 + config.vm.network :forwarded_port, guest: 3035, host: 3035 config.vm.network :forwarded_port, guest: 4000, host: 4000 config.vm.network :forwarded_port, guest: 8080, host: 8080 config.vm.network :forwarded_port, guest: 9200, host: 9200 diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 567edb33b1..af00038eae 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -46,7 +46,7 @@ class AccountsController < ApplicationController end def default_statuses - @account.statuses.not_local_only.where(visibility: [:public, :unlisted]) + @account.statuses.not_local_only.distributable_visibility end def only_media_scope diff --git a/app/controllers/activitypub/replies_controller.rb b/app/controllers/activitypub/replies_controller.rb index 3f43e89a5e..11aac48c9c 100644 --- a/app/controllers/activitypub/replies_controller.rb +++ b/app/controllers/activitypub/replies_controller.rb @@ -31,7 +31,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController def set_replies @replies = only_other_accounts? ? Status.where.not(account_id: @account.id).joins(:account).merge(Account.without_suspended) : @account.statuses - @replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted]) + @replies = @replies.distributable_visibility.where(in_reply_to_id: @status.id) @replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id]) end diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb index a71bb61298..4b5afbe157 100644 --- a/app/controllers/admin/base_controller.rb +++ b/app/controllers/admin/base_controller.rb @@ -7,7 +7,6 @@ module Admin layout 'admin' - before_action :set_pack before_action :set_body_classes before_action :set_cache_headers @@ -19,10 +18,6 @@ module Admin @body_classes = 'admin' end - def set_pack - use_pack 'admin' - end - def set_cache_headers response.cache_control.replace(private: true, no_store: true) end diff --git a/app/controllers/admin/domain_allows_controller.rb b/app/controllers/admin/domain_allows_controller.rb index 31be1978bb..b0f139e3a8 100644 --- a/app/controllers/admin/domain_allows_controller.rb +++ b/app/controllers/admin/domain_allows_controller.rb @@ -25,6 +25,8 @@ class Admin::DomainAllowsController < Admin::BaseController def destroy authorize @domain_allow, :destroy? UnallowDomainService.new.call(@domain_allow) + log_action :destroy, @domain_allow + redirect_to admin_instances_path, notice: I18n.t('admin.domain_allows.destroyed_msg') end diff --git a/app/controllers/admin/site_uploads_controller.rb b/app/controllers/admin/site_uploads_controller.rb index a5d2cf41cf..96e61cf6bb 100644 --- a/app/controllers/admin/site_uploads_controller.rb +++ b/app/controllers/admin/site_uploads_controller.rb @@ -9,7 +9,7 @@ module Admin @site_upload.destroy! - redirect_to admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg') + redirect_back fallback_location: admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg') end private diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index f87d596ce3..c1a5e43f88 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -9,6 +9,7 @@ class Api::BaseController < ApplicationController include Api::CachingConcern include Api::ContentSecurityPolicy include Api::ErrorHandling + include Api::Pagination skip_before_action :require_functional!, unless: :limited_federation_mode? @@ -29,21 +30,6 @@ class Api::BaseController < ApplicationController protected - def pagination_max_id - pagination_collection.last.id - end - - def pagination_since_id - pagination_collection.first.id - end - - def set_pagination_headers(next_path = nil, prev_path = nil) - links = [] - links << [next_path, [%w(rel next)]] if next_path - links << [prev_path, [%w(rel prev)]] if prev_path - response.headers['Link'] = LinkHeader.new(links) unless links.empty? - end - def limit_param(default_limit) return default_limit unless params[:limit] @@ -72,10 +58,6 @@ class Api::BaseController < ApplicationController render json: { error: 'Your login is currently disabled' }, status: 403 if current_user&.account&.unavailable? end - def require_valid_pagination_options! - render json: { error: 'Pagination values for `offset` and `limit` must be positive' }, status: 400 if pagination_options_invalid? - end - def require_user! if !current_user render json: { error: 'This method requires an authenticated user' }, status: 422 @@ -104,14 +86,6 @@ class Api::BaseController < ApplicationController private - def insert_pagination_headers - set_pagination_headers(next_path, prev_path) - end - - def pagination_options_invalid? - params.slice(:limit, :offset).values.map(&:to_i).any?(&:negative?) - end - def respond_with_error(code) render json: { error: Rack::Utils::HTTP_STATUS_CODES[code] }, status: code end diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb index 8f31336b9f..e8f712457e 100644 --- a/app/controllers/api/v1/accounts/credentials_controller.rb +++ b/app/controllers/api/v1/accounts/credentials_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Api::V1::Accounts::CredentialsController < Api::BaseController - before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, except: [:update] + before_action -> { doorkeeper_authorize! :read, :'read:accounts', :'read:me' }, except: [:update] before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:update] before_action :require_user! diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index 23fc85b475..be7b302d3b 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -9,16 +9,22 @@ class Api::V1::AccountsController < Api::BaseController before_action -> { doorkeeper_authorize! :follow, :write, :'write:blocks' }, only: [:block, :unblock] before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:create] - before_action :require_user!, except: [:show, :create] - before_action :set_account, except: [:create] - before_action :check_account_approval, except: [:create] - before_action :check_account_confirmation, except: [:create] + before_action :require_user!, except: [:index, :show, :create] + before_action :set_account, except: [:index, :create] + before_action :set_accounts, only: [:index] + before_action :check_account_approval, except: [:index, :create] + before_action :check_account_confirmation, except: [:index, :create] before_action :check_enabled_registrations, only: [:create] + before_action :check_accounts_limit, only: [:index] skip_before_action :require_authenticated_user!, only: :create override_rate_limit_headers :follow, family: :follows + def index + render json: @accounts, each_serializer: REST::AccountSerializer + end + def show cache_if_unauthenticated! render json: @account, serializer: REST::AccountSerializer @@ -79,6 +85,10 @@ class Api::V1::AccountsController < Api::BaseController @account = Account.find(params[:id]) end + def set_accounts + @accounts = Account.where(id: account_ids).without_unapproved + end + def check_account_approval raise(ActiveRecord::RecordNotFound) if @account.local? && @account.user_pending? end @@ -87,10 +97,22 @@ class Api::V1::AccountsController < Api::BaseController raise(ActiveRecord::RecordNotFound) if @account.local? && !@account.user_confirmed? end + def check_accounts_limit + raise(Mastodon::ValidationError) if account_ids.size > DEFAULT_ACCOUNTS_LIMIT + end + def relationships(**options) AccountRelationshipsPresenter.new([@account], current_user.account_id, **options) end + def account_ids + Array(accounts_params[:ids]).uniq.map(&:to_i) + end + + def accounts_params + params.permit(ids: []) + end + def account_params params.permit(:username, :email, :password, :agreement, :locale, :reason, :time_zone, :invite_code) end diff --git a/app/controllers/api/v1/admin/domain_blocks_controller.rb b/app/controllers/api/v1/admin/domain_blocks_controller.rb index b589d277d5..ae94ac59cd 100644 --- a/app/controllers/api/v1/admin/domain_blocks_controller.rb +++ b/app/controllers/api/v1/admin/domain_blocks_controller.rb @@ -29,10 +29,11 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController def create authorize :domain_block, :create? + @domain_block = DomainBlock.new(resource_params) existing_domain_block = resource_params[:domain].present? ? DomainBlock.rule_for(resource_params[:domain]) : nil - return render json: existing_domain_block, serializer: REST::Admin::ExistingDomainBlockErrorSerializer, status: 422 if existing_domain_block.present? + return render json: existing_domain_block, serializer: REST::Admin::ExistingDomainBlockErrorSerializer, status: 422 if conflicts_with_existing_block?(@domain_block, existing_domain_block) - @domain_block = DomainBlock.create!(resource_params) + @domain_block.save! DomainBlockWorker.perform_async(@domain_block.id) log_action :create, @domain_block render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer @@ -55,6 +56,10 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController private + def conflicts_with_existing_block?(domain_block, existing_domain_block) + existing_domain_block.present? && (existing_domain_block.domain == TagManager.instance.normalize_domain(domain_block.domain) || !domain_block.stricter_than?(existing_domain_block)) + end + def set_domain_blocks @domain_blocks = filtered_domain_blocks.order(id: :desc).to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) end diff --git a/app/controllers/api/v1/featured_tags/suggestions_controller.rb b/app/controllers/api/v1/featured_tags/suggestions_controller.rb index 4f732ed2d5..9c72e4380d 100644 --- a/app/controllers/api/v1/featured_tags/suggestions_controller.rb +++ b/app/controllers/api/v1/featured_tags/suggestions_controller.rb @@ -12,10 +12,6 @@ class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController private def set_recently_used_tags - @recently_used_tags = Tag.recently_used(current_account).where.not(id: featured_tag_ids).limit(10) - end - - def featured_tag_ids - current_account.featured_tags.pluck(:tag_id) + @recently_used_tags = Tag.suggestions_for_account(current_account).limit(10) end end diff --git a/app/controllers/api/v1/push/subscriptions_controller.rb b/app/controllers/api/v1/push/subscriptions_controller.rb index 3634acf956..e1ad89ee3e 100644 --- a/app/controllers/api/v1/push/subscriptions_controller.rb +++ b/app/controllers/api/v1/push/subscriptions_controller.rb @@ -1,9 +1,12 @@ # frozen_string_literal: true class Api::V1::Push::SubscriptionsController < Api::BaseController + include Redisable + include Lockable + before_action -> { doorkeeper_authorize! :push } before_action :require_user! - before_action :set_push_subscription + before_action :set_push_subscription, only: [:show, :update] before_action :check_push_subscription, only: [:show, :update] def show @@ -11,16 +14,18 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController end def create - @push_subscription&.destroy! + with_redis_lock("push_subscription:#{current_user.id}") do + destroy_web_push_subscriptions! - @push_subscription = Web::PushSubscription.create!( - endpoint: subscription_params[:endpoint], - key_p256dh: subscription_params[:keys][:p256dh], - key_auth: subscription_params[:keys][:auth], - data: data_params, - user_id: current_user.id, - access_token_id: doorkeeper_token.id - ) + @push_subscription = Web::PushSubscription.create!( + endpoint: subscription_params[:endpoint], + key_p256dh: subscription_params[:keys][:p256dh], + key_auth: subscription_params[:keys][:auth], + data: data_params, + user_id: current_user.id, + access_token_id: doorkeeper_token.id + ) + end render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer end @@ -31,14 +36,18 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController end def destroy - @push_subscription&.destroy! + destroy_web_push_subscriptions! render_empty end private + def destroy_web_push_subscriptions! + doorkeeper_token.web_push_subscriptions.destroy_all + end + def set_push_subscription - @push_subscription = Web::PushSubscription.find_by(access_token_id: doorkeeper_token.id) + @push_subscription = doorkeeper_token.web_push_subscriptions.first end def check_push_subscription diff --git a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb index eaa5ef7255..bac96b032b 100644 --- a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb +++ b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb @@ -23,7 +23,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::V1::Statuses::Base end def paginated_statuses - Status.where(reblog_of_id: @status.id).where(visibility: [:public, :unlisted]).paginate_by_max_id( + Status.where(reblog_of_id: @status.id).distributable_visibility.paginate_by_max_id( limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id] diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 75e075be74..6444f87a01 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -5,9 +5,11 @@ class Api::V1::StatusesController < Api::BaseController before_action -> { authorize_if_got_token! :read, :'read:statuses' }, except: [:create, :update, :destroy] before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :update, :destroy] - before_action :require_user!, except: [:show, :context] - before_action :set_status, only: [:show, :context] - before_action :set_thread, only: [:create] + before_action :require_user!, except: [:index, :show, :context] + before_action :set_statuses, only: [:index] + before_action :set_status, only: [:show, :context] + before_action :set_thread, only: [:create] + before_action :check_statuses_limit, only: [:index] override_rate_limit_headers :create, family: :statuses override_rate_limit_headers :update, family: :statuses @@ -23,6 +25,11 @@ class Api::V1::StatusesController < Api::BaseController DESCENDANTS_LIMIT = 60 DESCENDANTS_DEPTH_LIMIT = 20 + def index + @statuses = cache_collection(@statuses, Status) + render json: @statuses, each_serializer: REST::StatusSerializer + end + def show cache_if_unauthenticated! @status = cache_collection([@status], Status).first @@ -113,6 +120,10 @@ class Api::V1::StatusesController < Api::BaseController private + def set_statuses + @statuses = Status.permitted_statuses_from_ids(status_ids, current_account) + end + def set_status @status = Status.find(params[:id]) authorize @status, :show? @@ -127,6 +138,18 @@ class Api::V1::StatusesController < Api::BaseController render json: { error: I18n.t('statuses.errors.in_reply_not_found') }, status: 404 end + def check_statuses_limit + raise(Mastodon::ValidationError) if status_ids.size > DEFAULT_STATUSES_LIMIT + end + + def status_ids + Array(statuses_params[:ids]).uniq.map(&:to_i) + end + + def statuses_params + params.permit(ids: []) + end + def status_params params.permit( :status, diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c76110dba7..bd152dbb66 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -19,6 +19,7 @@ class ApplicationController < ActionController::Base helper_method :current_session helper_method :current_flavour helper_method :current_skin + helper_method :current_theme helper_method :single_user_mode? helper_method :use_seamless_external_login? helper_method :omniauth_only? @@ -164,10 +165,7 @@ class ApplicationController < ActionController::Base def respond_with_error(code) respond_to do |format| - format.any do - use_pack 'error' - render "errors/#{code}", layout: 'error', status: code, formats: [:html] - end + format.any { render "errors/#{code}", layout: 'error', status: code, formats: [:html] } format.json { render json: { error: Rack::Utils::HTTP_STATUS_CODES[code] }, status: code } end end @@ -176,10 +174,7 @@ class ApplicationController < ActionController::Base return unless self_destruct? respond_to do |format| - format.any do - use_pack 'error' - render 'errors/self_destruct', layout: 'auth', status: 410, formats: [:html] - end + format.any { render 'errors/self_destruct', layout: 'auth', status: 410, formats: [:html] } format.json { render json: { error: Rack::Utils::HTTP_STATUS_CODES[410] }, status: 410 } end end diff --git a/app/controllers/auth/challenges_controller.rb b/app/controllers/auth/challenges_controller.rb index 1e585de189..7ede420b51 100644 --- a/app/controllers/auth/challenges_controller.rb +++ b/app/controllers/auth/challenges_controller.rb @@ -5,7 +5,6 @@ class Auth::ChallengesController < ApplicationController layout 'auth' - before_action :set_pack before_action :authenticate_user! skip_before_action :check_self_destruct! @@ -21,10 +20,4 @@ class Auth::ChallengesController < ApplicationController render_challenge end end - - private - - def set_pack - use_pack 'auth' - end end diff --git a/app/controllers/auth/confirmations_controller.rb b/app/controllers/auth/confirmations_controller.rb index 8b8a27d260..7ca7be5f8e 100644 --- a/app/controllers/auth/confirmations_controller.rb +++ b/app/controllers/auth/confirmations_controller.rb @@ -6,7 +6,6 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController layout 'auth' before_action :set_body_classes - before_action :set_pack before_action :set_confirmation_user!, only: [:show, :confirm_captcha] before_action :redirect_confirmed_user, if: :signed_in_confirmed_user? @@ -66,10 +65,6 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController @confirmation_user.nil? || @confirmation_user.confirmed? end - def set_pack - use_pack 'auth' - end - def redirect_confirmed_user redirect_to(current_user.approved? ? root_path : edit_user_registration_path) end diff --git a/app/controllers/auth/passwords_controller.rb b/app/controllers/auth/passwords_controller.rb index f0d47bf774..de001f062b 100644 --- a/app/controllers/auth/passwords_controller.rb +++ b/app/controllers/auth/passwords_controller.rb @@ -3,7 +3,6 @@ class Auth::PasswordsController < Devise::PasswordsController skip_before_action :check_self_destruct! before_action :redirect_invalid_reset_token, only: :edit, unless: :reset_password_token_is_valid? - before_action :set_pack before_action :set_body_classes layout 'auth' @@ -32,8 +31,4 @@ class Auth::PasswordsController < Devise::PasswordsController def reset_password_token_is_valid? resource_class.with_reset_password_token(params[:reset_password_token]).present? end - - def set_pack - use_pack 'auth' - end end diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index 838869b2ee..acfc0af0d9 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -9,7 +9,6 @@ class Auth::RegistrationsController < Devise::RegistrationsController before_action :set_invite, only: [:new, :create] before_action :check_enabled_registrations, only: [:new, :create] before_action :configure_sign_up_params, only: [:create] - before_action :set_pack before_action :set_sessions, only: [:edit, :update] before_action :set_strikes, only: [:edit, :update] before_action :set_body_classes, only: [:new, :create, :edit, :update] @@ -97,10 +96,6 @@ class Auth::RegistrationsController < Devise::RegistrationsController private - def set_pack - use_pack %w(edit update).include?(action_name) ? 'admin' : 'auth' - end - def set_body_classes @body_classes = %w(edit update).include?(action_name) ? 'admin' : 'lighter' end diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb index 67d369b859..6ed7b2baac 100644 --- a/app/controllers/auth/sessions_controller.rb +++ b/app/controllers/auth/sessions_controller.rb @@ -12,7 +12,6 @@ class Auth::SessionsController < Devise::SessionsController skip_before_action :require_functional! skip_before_action :update_user_sign_in - prepend_before_action :set_pack prepend_before_action :check_suspicious!, only: [:create] include Auth::TwoFactorAuthenticationConcern @@ -104,10 +103,6 @@ class Auth::SessionsController < Devise::SessionsController private - def set_pack - use_pack 'auth' - end - def set_body_classes @body_classes = 'lighter' end diff --git a/app/controllers/auth/setup_controller.rb b/app/controllers/auth/setup_controller.rb index 8edca4d01b..40916d2887 100644 --- a/app/controllers/auth/setup_controller.rb +++ b/app/controllers/auth/setup_controller.rb @@ -3,7 +3,6 @@ class Auth::SetupController < ApplicationController layout 'auth' - before_action :set_pack before_action :authenticate_user! before_action :require_unconfirmed_or_pending! before_action :set_body_classes @@ -43,8 +42,4 @@ class Auth::SetupController < ApplicationController def user_params params.require(:user).permit(:email) end - - def set_pack - use_pack 'sign_up' - end end diff --git a/app/controllers/concerns/api/pagination.rb b/app/controllers/concerns/api/pagination.rb new file mode 100644 index 0000000000..d84a1d99f7 --- /dev/null +++ b/app/controllers/concerns/api/pagination.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Api::Pagination + extend ActiveSupport::Concern + + protected + + def pagination_max_id + pagination_collection.last.id + end + + def pagination_since_id + pagination_collection.first.id + end + + def set_pagination_headers(next_path = nil, prev_path = nil) + links = [] + links << [next_path, [%w(rel next)]] if next_path + links << [prev_path, [%w(rel prev)]] if prev_path + response.headers['Link'] = LinkHeader.new(links) unless links.empty? + end + + def require_valid_pagination_options! + render json: { error: 'Pagination values for `offset` and `limit` must be positive' }, status: 400 if pagination_options_invalid? + end + + private + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def pagination_options_invalid? + params.slice(:limit, :offset).values.map(&:to_i).any?(&:negative?) + end +end diff --git a/app/controllers/concerns/auth/two_factor_authentication_concern.rb b/app/controllers/concerns/auth/two_factor_authentication_concern.rb index edcdd2990f..404164751a 100644 --- a/app/controllers/concerns/auth/two_factor_authentication_concern.rb +++ b/app/controllers/concerns/auth/two_factor_authentication_concern.rb @@ -83,8 +83,6 @@ module Auth::TwoFactorAuthenticationConcern def prompt_for_two_factor(user) register_attempt_in_session(user) - use_pack 'auth' - @body_classes = 'lighter' @webauthn_enabled = user.webauthn_enabled? @scheme_type = if user.webauthn_enabled? && user_params[:otp_attempt].blank? diff --git a/app/controllers/concerns/cache_concern.rb b/app/controllers/concerns/cache_concern.rb index 62f763fe2f..4656539f85 100644 --- a/app/controllers/concerns/cache_concern.rb +++ b/app/controllers/concerns/cache_concern.rb @@ -46,27 +46,19 @@ module CacheConcern end end + # TODO: Rename this method, as it does not perform any caching anymore. def cache_collection(raw, klass) - return raw unless klass.respond_to?(:with_includes) + return raw unless klass.respond_to?(:preload_cacheable_associations) - raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation) - return [] if raw.empty? + records = raw.to_a - cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id) + klass.preload_cacheable_associations(records) - uncached_ids = raw.map(&:id) - cached_keys_with_value.keys - - klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!) - - unless uncached_ids.empty? - uncached = klass.where(id: uncached_ids).with_includes.index_by(&:id) - Rails.cache.write_multi(uncached.values.to_h { |i| [i, i] }) - end - - raw.filter_map { |item| cached_keys_with_value[item.id] || uncached[item.id] } + records end + # TODO: Rename this method, as it does not perform any caching anymore. def cache_collection_paginated_by_id(raw, klass, limit, options) - cache_collection raw.cache_ids.to_a_paginated_by_id(limit, options), klass + cache_collection raw.to_a_paginated_by_id(limit, options), klass end end diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb index 3155866271..68f09ee023 100644 --- a/app/controllers/concerns/signature_verification.rb +++ b/app/controllers/concerns/signature_verification.rb @@ -66,7 +66,7 @@ module SignatureVerification compare_signed_string = build_signed_string(include_query_string: false) return actor unless verify_signature(actor, signature, compare_signed_string).nil? - actor = stoplight_wrap_request { actor_refresh_key!(actor) } + actor = stoplight_wrapper.run { actor_refresh_key!(actor) } raise SignatureVerificationError, "Could not refresh public key #{signature_params['keyId']}" if actor.nil? @@ -226,10 +226,10 @@ module SignatureVerification end if key_id.start_with?('acct:') - stoplight_wrap_request { ResolveAccountService.new.call(key_id.delete_prefix('acct:'), suppress_errors: false) } + stoplight_wrapper.run { ResolveAccountService.new.call(key_id.delete_prefix('acct:'), suppress_errors: false) } elsif !ActivityPub::TagManager.instance.local_uri?(key_id) account = ActivityPub::TagManager.instance.uri_to_actor(key_id) - account ||= stoplight_wrap_request { ActivityPub::FetchRemoteKeyService.new.call(key_id, suppress_errors: false) } + account ||= stoplight_wrapper.run { ActivityPub::FetchRemoteKeyService.new.call(key_id, suppress_errors: false) } account end rescue Mastodon::PrivateNetworkAddressError => e @@ -238,12 +238,11 @@ module SignatureVerification raise SignatureVerificationError, e.message end - def stoplight_wrap_request(&block) - Stoplight("source:#{request.remote_ip}", &block) + def stoplight_wrapper + Stoplight("source:#{request.remote_ip}") .with_threshold(1) .with_cool_off_time(5.minutes.seconds) .with_error_handler { |error, handle| error.is_a?(HTTP::Error) || error.is_a?(OpenSSL::SSL::SSLError) ? handle.call(error) : raise(error) } - .run end def actor_refresh_key!(actor) diff --git a/app/controllers/concerns/theming_concern.rb b/app/controllers/concerns/theming_concern.rb index 82a53dbf51..38b31e932f 100644 --- a/app/controllers/concerns/theming_concern.rb +++ b/app/controllers/concerns/theming_concern.rb @@ -3,87 +3,22 @@ module ThemingConcern extend ActiveSupport::Concern - def use_pack(pack_name) - @core = resolve_pack_with_common(Themes.instance.core, pack_name) - @theme = resolve_pack_with_common(Themes.instance.flavour(current_flavour), pack_name, current_skin) - end - private def current_flavour - [current_user&.setting_flavour, Setting.flavour, 'glitch', 'vanilla'].find { |flavour| Themes.instance.flavours.include?(flavour) } + @current_flavour ||= [current_user&.setting_flavour, Setting.flavour, 'glitch', 'vanilla'].find { |flavour| Themes.instance.flavours.include?(flavour) } end def current_skin - skins = Themes.instance.skins_for(current_flavour) - [current_user&.setting_skin, Setting.skin, 'default'].find { |skin| skins.include?(skin) } - end - - def valid_pack_data?(data, pack_name) - data['pack'].is_a?(Hash) && data['pack'][pack_name].present? - end - - def nil_pack(data) - { - use_common: true, - flavour: data['name'], - pack: nil, - preload: nil, - skin: nil, - supported_locales: data['locales'], - } - end - - def pack(data, pack_name, skin) - pack_data = { - use_common: true, - flavour: data['name'], - pack: pack_name, - preload: nil, - skin: nil, - supported_locales: data['locales'], - } - - return pack_data unless data['pack'][pack_name].is_a?(Hash) - - pack_data[:use_common] = false if data['pack'][pack_name]['use_common'] == false - pack_data[:pack] = nil unless data['pack'][pack_name]['filename'] - - preloads = data['pack'][pack_name]['preload'] - pack_data[:preload] = [preloads] if preloads.is_a?(String) - pack_data[:preload] = preloads if preloads.is_a?(Array) - - if skin != 'default' && data['skin'][skin] - pack_data[:skin] = skin if data['skin'][skin].include?(pack_name) - elsif data['pack'][pack_name]['stylesheet'] - pack_data[:skin] = 'default' + @current_skin ||= begin + skins = Themes.instance.skins_for(current_flavour) + [current_user&.setting_skin, Setting.skin, 'system', 'default'].find { |skin| skins.include?(skin) } end - - pack_data end - def resolve_pack(data, pack_name, skin) - return pack(data, pack_name, skin) if valid_pack_data?(data, pack_name) - return if data['name'].blank? - - fallbacks = [] - if data.key?('fallback') - fallbacks = data['fallback'] if data['fallback'].is_a?(Array) - fallbacks = [data['fallback']] if data['fallback'].is_a?(String) - elsif data['name'] != Setting.default_settings['flavour'] - fallbacks = [Setting.default_settings['flavour']] - end - - fallbacks.each do |fallback| - return resolve_pack(Themes.instance.flavour(fallback), pack_name, skin) if Themes.instance.flavour(fallback) - end - - nil - end - - def resolve_pack_with_common(data, pack_name, skin = 'default') - result = resolve_pack(data, pack_name, skin) || nil_pack(data) - result[:common] = resolve_pack(data, 'common', skin) if result.delete(:use_common) - result + def current_theme + # NOTE: this is slightly different from upstream, as it's a derived value used + # for the sole purpose of pointing to the appropriate stylesheet pack + [current_flavour, current_skin] end end diff --git a/app/controllers/concerns/web_app_controller_concern.rb b/app/controllers/concerns/web_app_controller_concern.rb index a6af62af97..24cccf1667 100644 --- a/app/controllers/concerns/web_app_controller_concern.rb +++ b/app/controllers/concerns/web_app_controller_concern.rb @@ -7,7 +7,6 @@ module WebAppControllerConcern vary_by 'Accept, Accept-Language, Cookie' before_action :redirect_unauthenticated_to_permalinks! - before_action :set_pack before_action :set_app_body_class end @@ -37,8 +36,4 @@ module WebAppControllerConcern end end end - - def set_pack - use_pack 'home' - end end diff --git a/app/controllers/disputes/base_controller.rb b/app/controllers/disputes/base_controller.rb index f51f44c620..1054f3db80 100644 --- a/app/controllers/disputes/base_controller.rb +++ b/app/controllers/disputes/base_controller.rb @@ -9,15 +9,10 @@ class Disputes::BaseController < ApplicationController before_action :set_body_classes before_action :authenticate_user! - before_action :set_pack before_action :set_cache_headers private - def set_pack - use_pack 'admin' - end - def set_body_classes @body_classes = 'admin' end diff --git a/app/controllers/filters/statuses_controller.rb b/app/controllers/filters/statuses_controller.rb index 97206c7eda..94993f938b 100644 --- a/app/controllers/filters/statuses_controller.rb +++ b/app/controllers/filters/statuses_controller.rb @@ -6,7 +6,6 @@ class Filters::StatusesController < ApplicationController before_action :authenticate_user! before_action :set_filter before_action :set_status_filters - before_action :set_pack before_action :set_body_classes before_action :set_cache_headers @@ -27,10 +26,6 @@ class Filters::StatusesController < ApplicationController private - def set_pack - use_pack 'admin' - end - def set_filter @filter = current_account.custom_filters.find(params[:filter_id]) end diff --git a/app/controllers/filters_controller.rb b/app/controllers/filters_controller.rb index 1b19269b23..bd9964426b 100644 --- a/app/controllers/filters_controller.rb +++ b/app/controllers/filters_controller.rb @@ -5,7 +5,6 @@ class FiltersController < ApplicationController before_action :authenticate_user! before_action :set_filter, only: [:edit, :update, :destroy] - before_action :set_pack before_action :set_body_classes before_action :set_cache_headers @@ -45,10 +44,6 @@ class FiltersController < ApplicationController private - def set_pack - use_pack 'settings' - end - def set_filter @filter = current_account.custom_filters.find(params[:id]) end diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index 2db4bc5cbd..9bc5164d59 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -6,7 +6,6 @@ class InvitesController < ApplicationController layout 'admin' before_action :authenticate_user! - before_action :set_pack before_action :set_body_classes before_action :set_cache_headers @@ -40,10 +39,6 @@ class InvitesController < ApplicationController private - def set_pack - use_pack 'settings' - end - def invites current_user.invites.order(id: :desc) end diff --git a/app/controllers/media_controller.rb b/app/controllers/media_controller.rb index 4c028dbef0..53eee40012 100644 --- a/app/controllers/media_controller.rb +++ b/app/controllers/media_controller.rb @@ -10,7 +10,6 @@ class MediaController < ApplicationController before_action :verify_permitted_status! before_action :check_playable, only: :player before_action :allow_iframing, only: :player - before_action :set_pack, only: :player content_security_policy only: :player do |policy| policy.frame_ancestors(false) @@ -48,8 +47,4 @@ class MediaController < ApplicationController def allow_iframing response.headers.delete('X-Frame-Options') end - - def set_pack - use_pack 'public' - end end diff --git a/app/controllers/oauth/authorizations_controller.rb b/app/controllers/oauth/authorizations_controller.rb index 62fc9c1b0d..66e774425d 100644 --- a/app/controllers/oauth/authorizations_controller.rb +++ b/app/controllers/oauth/authorizations_controller.rb @@ -5,7 +5,6 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController before_action :store_current_location before_action :authenticate_resource_owner! - before_action :set_pack before_action :set_cache_headers content_security_policy do |p| @@ -20,10 +19,6 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController store_location_for(:user, request.url) end - def set_pack - use_pack 'auth' - end - def render_success if skip_authorization? || (matching_token? && !truthy_param?('force_login')) redirect_or_render authorize_response diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb index 778912c948..8440df6b7e 100644 --- a/app/controllers/oauth/authorized_applications_controller.rb +++ b/app/controllers/oauth/authorized_applications_controller.rb @@ -5,7 +5,6 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio before_action :store_current_location before_action :authenticate_resource_owner! - before_action :set_pack before_action :require_not_suspended!, only: :destroy before_action :set_body_classes before_action :set_cache_headers @@ -31,10 +30,6 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio store_location_for(:user, request.url) end - def set_pack - use_pack 'settings' - end - def require_not_suspended! forbidden if current_account.unavailable? end diff --git a/app/controllers/redirect/base_controller.rb b/app/controllers/redirect/base_controller.rb index ce55cfc53a..90894ec1ed 100644 --- a/app/controllers/redirect/base_controller.rb +++ b/app/controllers/redirect/base_controller.rb @@ -3,7 +3,6 @@ class Redirect::BaseController < ApplicationController vary_by 'Accept-Language' - before_action :set_pack before_action :set_resource before_action :set_app_body_class @@ -22,8 +21,4 @@ class Redirect::BaseController < ApplicationController def set_resource raise NotImplementedError end - - def set_pack - use_pack 'public' - end end diff --git a/app/controllers/relationships_controller.rb b/app/controllers/relationships_controller.rb index e6e7c24752..dd794f3199 100644 --- a/app/controllers/relationships_controller.rb +++ b/app/controllers/relationships_controller.rb @@ -5,7 +5,6 @@ class RelationshipsController < ApplicationController before_action :authenticate_user! before_action :set_accounts, only: :show - before_action :set_pack before_action :set_relationships, only: :show before_action :set_body_classes before_action :set_cache_headers @@ -73,10 +72,6 @@ class RelationshipsController < ApplicationController @body_classes = 'admin' end - def set_pack - use_pack 'admin' - end - def set_cache_headers response.cache_control.replace(private: true, no_store: true) end diff --git a/app/controllers/settings/base_controller.rb b/app/controllers/settings/base_controller.rb index 4d3d6e6a1b..f15140aa2b 100644 --- a/app/controllers/settings/base_controller.rb +++ b/app/controllers/settings/base_controller.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class Settings::BaseController < ApplicationController - before_action :set_pack layout 'admin' before_action :authenticate_user! @@ -10,10 +9,6 @@ class Settings::BaseController < ApplicationController private - def set_pack - use_pack 'settings' - end - def set_body_classes @body_classes = 'admin' end diff --git a/app/controllers/settings/featured_tags_controller.rb b/app/controllers/settings/featured_tags_controller.rb index c384402650..90c112e219 100644 --- a/app/controllers/settings/featured_tags_controller.rb +++ b/app/controllers/settings/featured_tags_controller.rb @@ -38,7 +38,7 @@ class Settings::FeaturedTagsController < Settings::BaseController end def set_recently_used_tags - @recently_used_tags = Tag.recently_used(current_account).where.not(id: @featured_tags.map(&:id)).limit(10) + @recently_used_tags = Tag.suggestions_for_account(current_account).limit(10) end def featured_tag_params diff --git a/app/controllers/settings/imports_controller.rb b/app/controllers/settings/imports_controller.rb index 983caf22fa..569aa07c53 100644 --- a/app/controllers/settings/imports_controller.rb +++ b/app/controllers/settings/imports_controller.rb @@ -31,7 +31,7 @@ class Settings::ImportsController < Settings::BaseController def show; end def failures - @bulk_import = current_account.bulk_imports.where(state: :finished).find(params[:id]) + @bulk_import = current_account.bulk_imports.state_finished.find(params[:id]) respond_to do |format| format.csv do @@ -92,7 +92,7 @@ class Settings::ImportsController < Settings::BaseController end def set_bulk_import - @bulk_import = current_account.bulk_imports.where(state: :unconfirmed).find(params[:id]) + @bulk_import = current_account.bulk_imports.state_unconfirmed.find(params[:id]) end def set_recent_imports diff --git a/app/controllers/settings/login_activities_controller.rb b/app/controllers/settings/login_activities_controller.rb index 018fc7e950..50e2d70cb9 100644 --- a/app/controllers/settings/login_activities_controller.rb +++ b/app/controllers/settings/login_activities_controller.rb @@ -7,10 +7,4 @@ class Settings::LoginActivitiesController < Settings::BaseController def index @login_activities = LoginActivity.where(user: current_user).order(id: :desc).page(params[:page]) end - - private - - def set_pack - use_pack 'settings' - end end diff --git a/app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb b/app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb index aed5af6e79..9714d54f95 100644 --- a/app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb +++ b/app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb @@ -85,10 +85,6 @@ module Settings private - def set_pack - use_pack 'auth' - end - def redirect_invalid_otp flash[:error] = t('webauthn_credentials.otp_required') redirect_to settings_two_factor_authentication_methods_path diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb index e13e7e8b65..6546b84978 100644 --- a/app/controllers/shares_controller.rb +++ b/app/controllers/shares_controller.rb @@ -4,17 +4,12 @@ class SharesController < ApplicationController layout 'modal' before_action :authenticate_user! - before_action :set_pack before_action :set_body_classes def show; end private - def set_pack - use_pack 'share' - end - def set_body_classes @body_classes = 'modal-layout compose-standalone' end diff --git a/app/controllers/statuses_cleanup_controller.rb b/app/controllers/statuses_cleanup_controller.rb index 88cf26d74d..4a3fc10ca4 100644 --- a/app/controllers/statuses_cleanup_controller.rb +++ b/app/controllers/statuses_cleanup_controller.rb @@ -6,7 +6,6 @@ class StatusesCleanupController < ApplicationController before_action :authenticate_user! before_action :set_policy before_action :set_body_classes - before_action :set_pack before_action :set_cache_headers def show; end @@ -27,10 +26,6 @@ class StatusesCleanupController < ApplicationController private - def set_pack - use_pack 'settings' - end - def set_policy @policy = current_account.statuses_cleanup_policy || current_account.build_statuses_cleanup_policy(enabled: false) end diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 02fea13502..db7eddd78b 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -41,7 +41,6 @@ class StatusesController < ApplicationController end def embed - use_pack 'embed' return not_found if @status.hidden? || @status.reblog? expires_in 180, public: true diff --git a/app/controllers/well_known/oauth_metadata_controller.rb b/app/controllers/well_known/oauth_metadata_controller.rb new file mode 100644 index 0000000000..c80be2d652 --- /dev/null +++ b/app/controllers/well_known/oauth_metadata_controller.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module WellKnown + class OauthMetadataController < ActionController::Base # rubocop:disable Rails/ApplicationController + include CacheConcern + + # Prevent `active_model_serializer`'s `ActionController::Serialization` from calling `current_user` + # and thus re-issuing session cookies + serialization_scope nil + + def show + # Due to this document potentially changing between Mastodon versions (as + # new OAuth scopes are added), we don't use expires_in to cache upstream, + # instead just caching in the rails cache: + render_with_cache( + json: ::OauthMetadataPresenter.new, + serializer: ::OauthMetadataSerializer, + content_type: 'application/json', + expires_in: 15.minutes + ) + end + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 233c242e4a..c52bbb846e 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -113,6 +113,14 @@ module ApplicationHelper content_tag(:i, nil, attributes.merge(class: class_names.join(' '))) end + def material_symbol(icon, attributes = {}) + inline_svg_tag( + "400-24px/#{icon}.svg", + class: %w(icon).concat(attributes[:class].to_s.split), + role: :img + ) + end + def check_icon inline_svg_tag 'check.svg' end @@ -233,6 +241,32 @@ module ApplicationHelper EmojiFormatter.new(html, custom_emojis, other_options.merge(animate: prefers_autoplay?)).to_s end + def site_icon_path(type, size = '48') + icon = SiteUpload.find_by(var: type) + return nil unless icon + + icon.file.url(size) + end + + # glitch-soc addition to handle the multiple flavors + def preload_locale_pack + supported_locales = Themes.instance.flavour(current_flavour)['locales'] + preload_pack_asset "locales/#{current_flavour}/#{I18n.locale}-json.js" if supported_locales.include?(I18n.locale.to_s) + end + + def flavoured_javascript_pack_tag(pack_name, **options) + javascript_pack_tag("flavours/#{current_flavour}/#{pack_name}", **options) + end + + def flavoured_stylesheet_pack_tag(pack_name, **options) + stylesheet_pack_tag("flavours/#{current_flavour}/#{pack_name}", **options) + end + + def preload_signed_in_js_packs + preload_files = Themes.instance.flavour(current_flavour)&.fetch('signed_in_preload', nil) || [] + safe_join(preload_files.map { |entry| preload_pack_asset entry }) + end + private def storage_host_var diff --git a/app/helpers/branding_helper.rb b/app/helpers/branding_helper.rb index f72d6df5d9..8201f36e3c 100644 --- a/app/helpers/branding_helper.rb +++ b/app/helpers/branding_helper.rb @@ -19,6 +19,6 @@ module BrandingHelper end def render_logo - image_pack_tag('logo.svg', alt: 'Mastodon', class: 'logo logo--icon') + image_tag(frontend_asset_path('images/logo.svg'), alt: 'Mastodon', class: 'logo logo--icon') end end diff --git a/app/helpers/context_helper.rb b/app/helpers/context_helper.rb index 412be4f48d..d70b2a88fd 100644 --- a/app/helpers/context_helper.rb +++ b/app/helpers/context_helper.rb @@ -49,13 +49,11 @@ module ContextHelper end def serialized_context(named_contexts_map, context_extensions_map) - context_array = [] - named_contexts = named_contexts_map.keys context_extensions = context_extensions_map.keys - named_contexts.each do |key| - context_array << NAMED_CONTEXT_MAP[key] + context_array = named_contexts.map do |key| + NAMED_CONTEXT_MAP[key] end extensions = context_extensions.each_with_object({}) do |key, h| diff --git a/app/helpers/theme_helper.rb b/app/helpers/theme_helper.rb new file mode 100644 index 0000000000..bb8ef4547c --- /dev/null +++ b/app/helpers/theme_helper.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module ThemeHelper + def theme_style_tags(flavour_and_skin) + flavour, theme = flavour_and_skin + + if theme == 'system' + stylesheet_pack_tag("skins/#{flavour}/mastodon-light", media: 'not all and (prefers-color-scheme: dark)', crossorigin: 'anonymous') + + stylesheet_pack_tag("skins/#{flavour}/default", media: '(prefers-color-scheme: dark)', crossorigin: 'anonymous') + else + stylesheet_pack_tag "skins/#{flavour}/#{theme}", media: 'all', crossorigin: 'anonymous' + end + end + + def theme_color_tags(flavour_and_skin) + _, theme = flavour_and_skin + + if theme == 'system' + tag.meta(name: 'theme-color', content: Themes::THEME_COLORS[:dark], media: '(prefers-color-scheme: dark)') + + tag.meta(name: 'theme-color', content: Themes::THEME_COLORS[:light], media: '(prefers-color-scheme: light)') + else + tag.meta name: 'theme-color', content: theme_color_for(theme) + end + end + + private + + def theme_color_for(theme) + theme == 'mastodon-light' ? Themes::THEME_COLORS[:light] : Themes::THEME_COLORS[:dark] + end +end diff --git a/app/javascript/core/auth.js b/app/javascript/core/auth.js deleted file mode 100644 index d1d14d99e8..0000000000 --- a/app/javascript/core/auth.js +++ /dev/null @@ -1,3 +0,0 @@ -import 'packs/public-path'; -import './settings'; -import './two_factor_authentication'; diff --git a/app/javascript/core/common.js b/app/javascript/core/common.js deleted file mode 100644 index 1cee2f6036..0000000000 --- a/app/javascript/core/common.js +++ /dev/null @@ -1,6 +0,0 @@ -// This file will be loaded on all pages, regardless of theme. - -import 'packs/public-path'; -import 'font-awesome/css/font-awesome.css'; - -require.context('../images/', true); diff --git a/app/javascript/core/embed.ts b/app/javascript/core/embed.ts deleted file mode 100644 index 6766cd7788..0000000000 --- a/app/javascript/core/embed.ts +++ /dev/null @@ -1,41 +0,0 @@ -// This file will be loaded on embed pages, regardless of theme. - -import 'packs/public-path'; -import ready from '../mastodon/ready'; - -interface SetHeightMessage { - type: 'setHeight'; - id: string; - height: number; -} - -function isSetHeightMessage(data: unknown): data is SetHeightMessage { - if ( - data && - typeof data === 'object' && - 'type' in data && - data.type === 'setHeight' - ) - return true; - else return false; -} - -window.addEventListener('message', (e) => { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- typings are not correct, it can be null in very rare cases - if (!e.data || !isSetHeightMessage(e.data) || !window.parent) return; - - const data = e.data; - - ready(() => { - window.parent.postMessage( - { - type: 'setHeight', - id: data.id, - height: document.getElementsByTagName('html')[0].scrollHeight, - }, - '*', - ); - }).catch((e) => { - console.error('Error in setHeightMessage postMessage', e); - }); -}); diff --git a/app/javascript/core/settings.ts b/app/javascript/core/settings.ts deleted file mode 100644 index ea6a99ec80..0000000000 --- a/app/javascript/core/settings.ts +++ /dev/null @@ -1,70 +0,0 @@ -// This file will be loaded on settings pages, regardless of theme. - -import 'packs/public-path'; -import Rails from '@rails/ujs'; - -Rails.delegate( - document, - '#edit_profile input[type=file]', - 'change', - ({ target }) => { - if (!(target instanceof HTMLInputElement)) return; - - const avatar = document.querySelector( - `img#${target.id}-preview`, - ); - - if (!avatar) return; - - let file: File | undefined; - if (target.files) file = target.files[0]; - - const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc; - - if (url) avatar.src = url; - }, -); - -Rails.delegate(document, '.input-copy input', 'click', ({ target }) => { - if (!(target instanceof HTMLInputElement)) return; - - target.focus(); - target.select(); - target.setSelectionRange(0, target.value.length); -}); - -Rails.delegate(document, '.input-copy button', 'click', ({ target }) => { - if (!(target instanceof HTMLButtonElement)) return; - - const input = target.parentNode?.querySelector( - '.input-copy__wrapper input', - ); - - if (!input) return; - - const oldReadOnly = input.readOnly; - - input.readOnly = false; - input.focus(); - input.select(); - input.setSelectionRange(0, input.value.length); - - try { - if (document.execCommand('copy')) { - input.blur(); - - const parent = target.parentElement; - - if (!parent) return; - parent.classList.add('copied'); - - setTimeout(() => { - parent.classList.remove('copied'); - }, 700); - } - } catch (err) { - console.error(err); - } - - input.readOnly = oldReadOnly; -}); diff --git a/app/javascript/core/theme.yml b/app/javascript/core/theme.yml deleted file mode 100644 index 12c23e2035..0000000000 --- a/app/javascript/core/theme.yml +++ /dev/null @@ -1,24 +0,0 @@ -# These packs will be loaded on every appropriate page, regardless of -# theme. -pack: - about: - admin: admin.ts - auth: auth.js - common: - filename: common.js - stylesheet: true - embed: embed.ts - error: - home: - inert: - filename: inert.js - stylesheet: true - mailer: - filename: mailer.js - stylesheet: true - modal: - public: - settings: settings.ts - sign_up: - share: - remote_interaction_helper: remote_interaction_helper.ts diff --git a/app/javascript/core/two_factor_authentication.js b/app/javascript/core/two_factor_authentication.js deleted file mode 100644 index e76700a480..0000000000 --- a/app/javascript/core/two_factor_authentication.js +++ /dev/null @@ -1,121 +0,0 @@ -import 'packs/public-path'; - -import * as WebAuthnJSON from '@github/webauthn-json'; -import axios from 'axios'; - -import ready from '../mastodon/ready'; -import 'regenerator-runtime/runtime'; - -function getCSRFToken() { - var CSRFSelector = document.querySelector('meta[name="csrf-token"]'); - if (CSRFSelector) { - return CSRFSelector.getAttribute('content'); - } else { - return null; - } -} - -function hideFlashMessages() { - Array.from(document.getElementsByClassName('flash-message')).forEach(function(flashMessage) { - flashMessage.classList.add('hidden'); - }); -} - -function callback(url, body) { - axios.post(url, JSON.stringify(body), { - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'X-CSRF-Token': getCSRFToken(), - }, - credentials: 'same-origin', - }).then(function(response) { - window.location.replace(response.data.redirect_path); - }).catch(function(error) { - if (error.response.status === 422) { - const errorMessage = document.getElementById('security-key-error-message'); - errorMessage.classList.remove('hidden'); - console.error(error.response.data.error); - } else { - console.error(error); - } - }); -} - -ready(() => { - if (!WebAuthnJSON.supported()) { - const unsupported_browser_message = document.getElementById('unsupported-browser-message'); - if (unsupported_browser_message) { - unsupported_browser_message.classList.remove('hidden'); - document.querySelector('.btn.js-webauthn').disabled = true; - } - } - - - const webAuthnCredentialRegistrationForm = document.getElementById('new_webauthn_credential'); - if (webAuthnCredentialRegistrationForm) { - webAuthnCredentialRegistrationForm.addEventListener('submit', (event) => { - event.preventDefault(); - - var nickname = event.target.querySelector('input[name="new_webauthn_credential[nickname]"]'); - if (nickname.value) { - axios.get('/settings/security_keys/options') - .then((response) => { - const credentialOptions = response.data; - - WebAuthnJSON.create({ 'publicKey': credentialOptions }).then((credential) => { - var params = { 'credential': credential, 'nickname': nickname.value }; - callback('/settings/security_keys', params); - }).catch((error) => { - const errorMessage = document.getElementById('security-key-error-message'); - errorMessage.classList.remove('hidden'); - console.error(error); - }); - }).catch((error) => { - console.error(error.response.data.error); - }); - } else { - nickname.focus(); - } - }); - } - - const webAuthnCredentialAuthenticationForm = document.getElementById('webauthn-form'); - if (webAuthnCredentialAuthenticationForm) { - webAuthnCredentialAuthenticationForm.addEventListener('submit', (event) => { - event.preventDefault(); - - axios.get('sessions/security_key_options') - .then((response) => { - const credentialOptions = response.data; - - WebAuthnJSON.get({ 'publicKey': credentialOptions }).then((credential) => { - var params = { 'user': { 'credential': credential } }; - callback('sign_in', params); - }).catch((error) => { - const errorMessage = document.getElementById('security-key-error-message'); - errorMessage.classList.remove('hidden'); - console.error(error); - }); - }).catch((error) => { - console.error(error.response.data.error); - }); - }); - - const otpAuthenticationForm = document.getElementById('otp-authentication-form'); - - const linkToOtp = document.getElementById('link-to-otp'); - linkToOtp.addEventListener('click', () => { - webAuthnCredentialAuthenticationForm.classList.add('hidden'); - otpAuthenticationForm.classList.remove('hidden'); - hideFlashMessages(); - }); - - const linkToWebAuthn = document.getElementById('link-to-webauthn'); - linkToWebAuthn.addEventListener('click', () => { - otpAuthenticationForm.classList.add('hidden'); - webAuthnCredentialAuthenticationForm.classList.remove('hidden'); - hideFlashMessages(); - }); - } -}); diff --git a/app/javascript/core/admin.ts b/app/javascript/entrypoints/admin.tsx similarity index 91% rename from app/javascript/core/admin.ts rename to app/javascript/entrypoints/admin.tsx index 0642affef0..225cb16330 100644 --- a/app/javascript/core/admin.ts +++ b/app/javascript/entrypoints/admin.tsx @@ -1,6 +1,5 @@ -// This file will be loaded on admin pages, regardless of theme. - -import 'packs/public-path'; +import './public-path'; +import { createRoot } from 'react-dom/client'; import Rails from '@rails/ujs'; @@ -261,6 +260,31 @@ Rails.delegate( }, ); +async function mountReactComponent(element: Element) { + const componentName = element.getAttribute('data-admin-component'); + const stringProps = element.getAttribute('data-props'); + + if (!stringProps) return; + + const componentProps = JSON.parse(stringProps) as object; + + const { default: AdminComponent } = await import( + '@/mastodon/containers/admin_component' + ); + + const { default: Component } = (await import( + `@/mastodon/components/admin/${componentName}` + )) as { default: React.ComponentType }; + + const root = createRoot(element); + + root.render( + + + , + ); +} + ready(() => { const domainBlockSeveritySelect = document.querySelector( 'select#domain_block_severity', @@ -335,6 +359,10 @@ ready(() => { if (announcementStartsAt) { setAnnouncementEndsAttributes(announcementStartsAt); } -}).catch((reason) => { + + document.querySelectorAll('[data-admin-component]').forEach((element) => { + void mountReactComponent(element); + }); +}).catch((reason: unknown) => { throw reason; }); diff --git a/app/javascript/packs/application.js b/app/javascript/entrypoints/application.ts similarity index 81% rename from app/javascript/packs/application.js rename to app/javascript/entrypoints/application.ts index d13388b479..1087b1c4cb 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/entrypoints/application.ts @@ -1,5 +1,5 @@ import './public-path'; -import main from "mastodon/main"; +import main from 'mastodon/main'; import { start } from '../mastodon/common'; import { loadLocale } from '../mastodon/locales'; @@ -10,6 +10,6 @@ start(); loadPolyfills() .then(loadLocale) .then(main) - .catch(e => { + .catch((e: unknown) => { console.error(e); }); diff --git a/app/javascript/entrypoints/common.js b/app/javascript/entrypoints/common.js new file mode 100644 index 0000000000..489041458f --- /dev/null +++ b/app/javascript/entrypoints/common.js @@ -0,0 +1,5 @@ +/* This file is a hack to have something more reliable than the upstream `common` tag + that is implicitly generated as the common chunk through webpack's `splitChunks` config */ + +import './public-path'; +import 'font-awesome/css/font-awesome.css'; diff --git a/app/javascript/packs/error.js b/app/javascript/entrypoints/error.ts similarity index 64% rename from app/javascript/packs/error.js rename to app/javascript/entrypoints/error.ts index 6376dc2f5d..db68484f3a 100644 --- a/app/javascript/packs/error.js +++ b/app/javascript/entrypoints/error.ts @@ -2,7 +2,9 @@ import './public-path'; import ready from '../mastodon/ready'; ready(() => { - const image = document.querySelector('img'); + const image = document.querySelector('img'); + + if (!image) return; image.addEventListener('mouseenter', () => { image.src = '/oops.gif'; @@ -11,4 +13,6 @@ ready(() => { image.addEventListener('mouseleave', () => { image.src = '/oops.png'; }); +}).catch((e: unknown) => { + console.error(e); }); diff --git a/app/javascript/core/inert.js b/app/javascript/entrypoints/inert.ts similarity index 100% rename from app/javascript/core/inert.js rename to app/javascript/entrypoints/inert.ts diff --git a/app/javascript/core/mailer.js b/app/javascript/entrypoints/mailer.ts similarity index 100% rename from app/javascript/core/mailer.js rename to app/javascript/entrypoints/mailer.ts diff --git a/app/javascript/packs/public-path.js b/app/javascript/entrypoints/public-path.ts similarity index 69% rename from app/javascript/packs/public-path.js rename to app/javascript/entrypoints/public-path.ts index f4d166a771..ac4b9355b9 100644 --- a/app/javascript/packs/public-path.js +++ b/app/javascript/entrypoints/public-path.ts @@ -2,7 +2,7 @@ // to share the same assets regardless of instance configuration. // See https://webpack.js.org/guides/public-path/#on-the-fly -function removeOuterSlashes(string) { +function removeOuterSlashes(string: string) { return string.replace(/^\/*/, '').replace(/\/*$/, ''); } @@ -15,7 +15,9 @@ function formatPublicPath(host = '', path = '') { return `${formattedHost}/${formattedPath}/`; } -const cdnHost = document.querySelector('meta[name=cdn-host]'); +const cdnHost = document.querySelector('meta[name=cdn-host]'); -// eslint-disable-next-line no-undef -__webpack_public_path__ = formatPublicPath(cdnHost ? cdnHost.content : '', process.env.PUBLIC_OUTPUT_PATH); +__webpack_public_path__ = formatPublicPath( + cdnHost ? cdnHost.content : '', + process.env.PUBLIC_OUTPUT_PATH, +); diff --git a/app/javascript/packs/public.tsx b/app/javascript/entrypoints/public.tsx similarity index 80% rename from app/javascript/packs/public.tsx rename to app/javascript/entrypoints/public.tsx index 17befbd224..d45927226c 100644 --- a/app/javascript/packs/public.tsx +++ b/app/javascript/entrypoints/public.tsx @@ -37,6 +37,43 @@ const messages = defineMessages({ }, }); +interface SetHeightMessage { + type: 'setHeight'; + id: string; + height: number; +} + +function isSetHeightMessage(data: unknown): data is SetHeightMessage { + if ( + data && + typeof data === 'object' && + 'type' in data && + data.type === 'setHeight' + ) + return true; + else return false; +} + +window.addEventListener('message', (e) => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- typings are not correct, it can be null in very rare cases + if (!e.data || !isSetHeightMessage(e.data) || !window.parent) return; + + const data = e.data; + + ready(() => { + window.parent.postMessage( + { + type: 'setHeight', + id: data.id, + height: document.getElementsByTagName('html')[0].scrollHeight, + }, + '*', + ); + }).catch((e: unknown) => { + console.error('Error in setHeightMessage postMessage', e); + }); +}); + function loaded() { const { messages: localeData } = getLocale(); @@ -169,7 +206,7 @@ function loaded() { return true; }) - .catch((error) => { + .catch((error: unknown) => { console.error(error); }); } @@ -288,6 +325,72 @@ function loaded() { }); } +Rails.delegate( + document, + '#edit_profile input[type=file]', + 'change', + ({ target }) => { + if (!(target instanceof HTMLInputElement)) return; + + const avatar = document.querySelector( + `img#${target.id}-preview`, + ); + + if (!avatar) return; + + let file: File | undefined; + if (target.files) file = target.files[0]; + + const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc; + + if (url) avatar.src = url; + }, +); + +Rails.delegate(document, '.input-copy input', 'click', ({ target }) => { + if (!(target instanceof HTMLInputElement)) return; + + target.focus(); + target.select(); + target.setSelectionRange(0, target.value.length); +}); + +Rails.delegate(document, '.input-copy button', 'click', ({ target }) => { + if (!(target instanceof HTMLButtonElement)) return; + + const input = target.parentNode?.querySelector( + '.input-copy__wrapper input', + ); + + if (!input) return; + + const oldReadOnly = input.readOnly; + + input.readOnly = false; + input.focus(); + input.select(); + input.setSelectionRange(0, input.value.length); + + try { + if (document.execCommand('copy')) { + input.blur(); + + const parent = target.parentElement; + + if (!parent) return; + parent.classList.add('copied'); + + setTimeout(() => { + parent.classList.remove('copied'); + }, 700); + } + } catch (err) { + console.error(err); + } + + input.readOnly = oldReadOnly; +}); + const toggleSidebar = () => { const sidebar = document.querySelector('.sidebar ul'); const toggleButton = document.querySelector( @@ -345,7 +448,7 @@ Rails.delegate(document, '#registration_new_user,#new_user', 'submit', () => { }); function main() { - ready(loaded).catch((error) => { + ready(loaded).catch((error: unknown) => { console.error(error); }); } @@ -354,6 +457,6 @@ loadPolyfills() .then(loadLocale) .then(main) .then(loadKeyboardExtensions) - .catch((error) => { + .catch((error: unknown) => { console.error(error); }); diff --git a/app/javascript/core/remote_interaction_helper.ts b/app/javascript/entrypoints/remote_interaction_helper.ts similarity index 99% rename from app/javascript/core/remote_interaction_helper.ts rename to app/javascript/entrypoints/remote_interaction_helper.ts index 4da4d49f6e..d5834c6c3d 100644 --- a/app/javascript/core/remote_interaction_helper.ts +++ b/app/javascript/entrypoints/remote_interaction_helper.ts @@ -8,7 +8,7 @@ and performs no other task. */ -import 'packs/public-path'; +import './public-path'; import axios from 'axios'; diff --git a/app/javascript/packs/share.jsx b/app/javascript/entrypoints/share.tsx similarity index 64% rename from app/javascript/packs/share.jsx rename to app/javascript/entrypoints/share.tsx index 7b5723091c..7926250851 100644 --- a/app/javascript/packs/share.jsx +++ b/app/javascript/entrypoints/share.tsx @@ -2,7 +2,7 @@ import './public-path'; import { createRoot } from 'react-dom/client'; import { start } from '../mastodon/common'; -import ComposeContainer from '../mastodon/containers/compose_container'; +import ComposeContainer from '../mastodon/containers/compose_container'; import { loadPolyfills } from '../mastodon/polyfills'; import ready from '../mastodon/ready'; @@ -16,7 +16,7 @@ function loaded() { if (!attr) return; - const props = JSON.parse(attr); + const props = JSON.parse(attr) as object; const root = createRoot(mountNode); root.render(); @@ -24,9 +24,13 @@ function loaded() { } function main() { - ready(loaded); + ready(loaded).catch((error: unknown) => { + console.error(error); + }); } -loadPolyfills().then(main).catch(error => { - console.error(error); -}); +loadPolyfills() + .then(main) + .catch((error: unknown) => { + console.error(error); + }); diff --git a/app/javascript/entrypoints/sign_up.ts b/app/javascript/entrypoints/sign_up.ts new file mode 100644 index 0000000000..880738fcb7 --- /dev/null +++ b/app/javascript/entrypoints/sign_up.ts @@ -0,0 +1,48 @@ +import './public-path'; +import axios from 'axios'; + +import ready from '../mastodon/ready'; + +async function checkConfirmation() { + const response = await axios.get('/api/v1/emails/check_confirmation'); + + if (response.data) { + window.location.href = '/start'; + } +} + +ready(() => { + setInterval(() => { + void checkConfirmation(); + }, 5000); + + document + .querySelectorAll('button.timer-button') + .forEach((button) => { + let counter = 30; + + const container = document.createElement('span'); + + const updateCounter = () => { + container.innerText = ` (${counter})`; + }; + + updateCounter(); + + const countdown = setInterval(() => { + counter--; + + if (counter === 0) { + button.disabled = false; + button.removeChild(container); + clearInterval(countdown); + } else { + updateCounter(); + } + }, 1000); + + button.appendChild(container); + }); +}).catch((e: unknown) => { + throw e; +}); diff --git a/app/javascript/entrypoints/two_factor_authentication.ts b/app/javascript/entrypoints/two_factor_authentication.ts new file mode 100644 index 0000000000..981481694b --- /dev/null +++ b/app/javascript/entrypoints/two_factor_authentication.ts @@ -0,0 +1,197 @@ +import * as WebAuthnJSON from '@github/webauthn-json'; +import axios, { AxiosError } from 'axios'; + +import ready from '../mastodon/ready'; + +import 'regenerator-runtime/runtime'; + +type PublicKeyCredentialCreationOptionsJSON = + WebAuthnJSON.CredentialCreationOptionsJSON['publicKey']; + +function exceptionHasAxiosError( + error: unknown, +): error is AxiosError<{ error: unknown }> { + return ( + error instanceof AxiosError && + typeof error.response?.data === 'object' && + 'error' in error.response.data + ); +} + +function logAxiosResponseError(error: unknown) { + if (exceptionHasAxiosError(error)) console.error(error); +} + +function getCSRFToken() { + return document + .querySelector('meta[name="csrf-token"]') + ?.getAttribute('content'); +} + +function hideFlashMessages() { + document.querySelectorAll('.flash-message').forEach((flashMessage) => { + flashMessage.classList.add('hidden'); + }); +} + +async function callback( + url: string, + body: + | { + credential: WebAuthnJSON.PublicKeyCredentialWithAttestationJSON; + nickname: string; + } + | { + user: { credential: WebAuthnJSON.PublicKeyCredentialWithAssertionJSON }; + }, +) { + try { + const response = await axios.post<{ redirect_path: string }>( + url, + JSON.stringify(body), + { + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + 'X-CSRF-Token': getCSRFToken(), + }, + }, + ); + + window.location.replace(response.data.redirect_path); + } catch (error) { + if (error instanceof AxiosError && error.response?.status === 422) { + const errorMessage = document.getElementById( + 'security-key-error-message', + ); + errorMessage?.classList.remove('hidden'); + + logAxiosResponseError(error); + } else { + console.error(error); + } + } +} + +async function handleWebauthnCredentialRegistration(nickname: string) { + try { + const response = await axios.get( + '/settings/security_keys/options', + ); + + const credentialOptions = response.data; + + try { + const credential = await WebAuthnJSON.create({ + publicKey: credentialOptions, + }); + + const params = { + credential: credential, + nickname: nickname, + }; + + await callback('/settings/security_keys', params); + } catch (error) { + const errorMessage = document.getElementById( + 'security-key-error-message', + ); + errorMessage?.classList.remove('hidden'); + console.error(error); + } + } catch (error) { + logAxiosResponseError(error); + } +} + +async function handleWebauthnCredentialAuthentication() { + try { + const response = await axios.get( + 'sessions/security_key_options', + ); + + const credentialOptions = response.data; + + try { + const credential = await WebAuthnJSON.get({ + publicKey: credentialOptions, + }); + + const params = { user: { credential: credential } }; + void callback('sign_in', params); + } catch (error) { + const errorMessage = document.getElementById( + 'security-key-error-message', + ); + errorMessage?.classList.remove('hidden'); + console.error(error); + } + } catch (error) { + logAxiosResponseError(error); + } +} + +ready(() => { + if (!WebAuthnJSON.supported()) { + const unsupported_browser_message = document.getElementById( + 'unsupported-browser-message', + ); + if (unsupported_browser_message) { + unsupported_browser_message.classList.remove('hidden'); + const button = document.querySelector( + 'button.btn.js-webauthn', + ); + if (button) button.disabled = true; + } + } + + const webAuthnCredentialRegistrationForm = + document.querySelector('form#new_webauthn_credential'); + if (webAuthnCredentialRegistrationForm) { + webAuthnCredentialRegistrationForm.addEventListener('submit', (event) => { + event.preventDefault(); + + if (!(event.target instanceof HTMLFormElement)) return; + + const nickname = event.target.querySelector( + 'input[name="new_webauthn_credential[nickname]"]', + ); + + if (nickname?.value) { + void handleWebauthnCredentialRegistration(nickname.value); + } else { + nickname?.focus(); + } + }); + } + + const webAuthnCredentialAuthenticationForm = + document.getElementById('webauthn-form'); + if (webAuthnCredentialAuthenticationForm) { + webAuthnCredentialAuthenticationForm.addEventListener('submit', (event) => { + event.preventDefault(); + void handleWebauthnCredentialAuthentication(); + }); + + const otpAuthenticationForm = document.getElementById( + 'otp-authentication-form', + ); + + const linkToOtp = document.getElementById('link-to-otp'); + + linkToOtp?.addEventListener('click', () => { + webAuthnCredentialAuthenticationForm.classList.add('hidden'); + otpAuthenticationForm?.classList.remove('hidden'); + hideFlashMessages(); + }); + + const linkToWebAuthn = document.getElementById('link-to-webauthn'); + linkToWebAuthn?.addEventListener('click', () => { + otpAuthenticationForm?.classList.add('hidden'); + webAuthnCredentialAuthenticationForm.classList.remove('hidden'); + hideFlashMessages(); + }); + } +}).catch((e: unknown) => { + throw e; +}); diff --git a/app/javascript/flavours/glitch/actions/boosts.js b/app/javascript/flavours/glitch/actions/boosts.js deleted file mode 100644 index 1fc2e391e2..0000000000 --- a/app/javascript/flavours/glitch/actions/boosts.js +++ /dev/null @@ -1,32 +0,0 @@ -import { openModal } from './modal'; - -export const BOOSTS_INIT_MODAL = 'BOOSTS_INIT_MODAL'; -export const BOOSTS_CHANGE_PRIVACY = 'BOOSTS_CHANGE_PRIVACY'; - -export function initBoostModal(props) { - return (dispatch, getState) => { - const default_privacy = getState().getIn(['compose', 'default_privacy']); - - const privacy = props.status.get('visibility') === 'private' ? 'private' : default_privacy; - - dispatch({ - type: BOOSTS_INIT_MODAL, - privacy, - }); - - dispatch(openModal({ - modalType: 'BOOST', - modalProps: props, - })); - }; -} - - -export function changeBoostPrivacy(privacy) { - return dispatch => { - dispatch({ - type: BOOSTS_CHANGE_PRIVACY, - privacy, - }); - }; -} diff --git a/app/javascript/flavours/glitch/actions/markers.js b/app/javascript/flavours/glitch/actions/markers.js deleted file mode 100644 index ccb1b23d6f..0000000000 --- a/app/javascript/flavours/glitch/actions/markers.js +++ /dev/null @@ -1,152 +0,0 @@ -import { List as ImmutableList } from 'immutable'; - -import { debounce } from 'lodash'; - -import api from '../api'; -import { compareId } from '../compare_id'; - -export const MARKERS_FETCH_REQUEST = 'MARKERS_FETCH_REQUEST'; -export const MARKERS_FETCH_SUCCESS = 'MARKERS_FETCH_SUCCESS'; -export const MARKERS_FETCH_FAIL = 'MARKERS_FETCH_FAIL'; -export const MARKERS_SUBMIT_SUCCESS = 'MARKERS_SUBMIT_SUCCESS'; - -export const synchronouslySubmitMarkers = () => (dispatch, getState) => { - const accessToken = getState().getIn(['meta', 'access_token'], ''); - const params = _buildParams(getState()); - - if (Object.keys(params).length === 0 || accessToken === '') { - return; - } - - // The Fetch API allows us to perform requests that will be carried out - // after the page closes. But that only works if the `keepalive` attribute - // is supported. - if (window.fetch && 'keepalive' in new Request('')) { - fetch('/api/v1/markers', { - keepalive: true, - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}`, - }, - body: JSON.stringify(params), - }); - - return; - } else if (navigator && navigator.sendBeacon) { - // Failing that, we can use sendBeacon, but we have to encode the data as - // FormData for DoorKeeper to recognize the token. - const formData = new FormData(); - - formData.append('bearer_token', accessToken); - - for (const [id, value] of Object.entries(params)) { - formData.append(`${id}[last_read_id]`, value.last_read_id); - } - - if (navigator.sendBeacon('/api/v1/markers', formData)) { - return; - } - } - - // If neither Fetch nor sendBeacon worked, try to perform a synchronous - // request. - try { - const client = new XMLHttpRequest(); - - client.open('POST', '/api/v1/markers', false); - client.setRequestHeader('Content-Type', 'application/json'); - client.setRequestHeader('Authorization', `Bearer ${accessToken}`); - client.send(JSON.stringify(params)); - } catch (e) { - // Do not make the BeforeUnload handler error out - } -}; - -const _buildParams = (state) => { - const params = {}; - - const lastHomeId = state.getIn(['timelines', 'home', 'items'], ImmutableList()).find(item => item !== null); - const lastNotificationId = state.getIn(['notifications', 'lastReadId']); - - if (lastHomeId && compareId(lastHomeId, state.getIn(['markers', 'home'])) > 0) { - params.home = { - last_read_id: lastHomeId, - }; - } - - if (lastNotificationId && lastNotificationId !== '0' && compareId(lastNotificationId, state.getIn(['markers', 'notifications'])) > 0) { - params.notifications = { - last_read_id: lastNotificationId, - }; - } - - return params; -}; - -const debouncedSubmitMarkers = debounce((dispatch, getState) => { - const accessToken = getState().getIn(['meta', 'access_token'], ''); - const params = _buildParams(getState()); - - if (Object.keys(params).length === 0 || accessToken === '') { - return; - } - - api(getState).post('/api/v1/markers', params).then(() => { - dispatch(submitMarkersSuccess(params)); - }).catch(() => {}); -}, 300000, { leading: true, trailing: true }); - -export function submitMarkersSuccess({ home, notifications }) { - return { - type: MARKERS_SUBMIT_SUCCESS, - home: (home || {}).last_read_id, - notifications: (notifications || {}).last_read_id, - }; -} - -export function submitMarkers(params = {}) { - const result = (dispatch, getState) => debouncedSubmitMarkers(dispatch, getState); - - if (params.immediate === true) { - debouncedSubmitMarkers.flush(); - } - - return result; -} - -export const fetchMarkers = () => (dispatch, getState) => { - const params = { timeline: ['notifications'] }; - - dispatch(fetchMarkersRequest()); - - api(getState).get('/api/v1/markers', { params }).then(response => { - dispatch(fetchMarkersSuccess(response.data)); - }).catch(error => { - dispatch(fetchMarkersFail(error)); - }); -}; - -export function fetchMarkersRequest() { - return { - type: MARKERS_FETCH_REQUEST, - skipLoading: true, - }; -} - -export function fetchMarkersSuccess(markers) { - return { - type: MARKERS_FETCH_SUCCESS, - markers, - skipLoading: true, - }; -} - -export function fetchMarkersFail(error) { - return { - type: MARKERS_FETCH_FAIL, - error, - skipLoading: true, - skipAlert: true, - }; -} diff --git a/app/javascript/flavours/glitch/actions/markers.ts b/app/javascript/flavours/glitch/actions/markers.ts new file mode 100644 index 0000000000..3f810cf99a --- /dev/null +++ b/app/javascript/flavours/glitch/actions/markers.ts @@ -0,0 +1,147 @@ +import { debounce } from 'lodash'; + +import type { MarkerJSON } from 'flavours/glitch/api_types/markers'; +import type { AppDispatch, RootState } from 'flavours/glitch/store'; +import { createAppAsyncThunk } from 'flavours/glitch/store/typed_functions'; + +import api, { authorizationTokenFromState } from '../api'; +import { compareId } from '../compare_id'; + +export const synchronouslySubmitMarkers = createAppAsyncThunk( + 'markers/submit', + async (_args, { getState }) => { + const accessToken = authorizationTokenFromState(getState); + const params = buildPostMarkersParams(getState()); + + if (Object.keys(params).length === 0 || !accessToken) { + return; + } + + // The Fetch API allows us to perform requests that will be carried out + // after the page closes. But that only works if the `keepalive` attribute + // is supported. + if ('fetch' in window && 'keepalive' in new Request('')) { + await fetch('/api/v1/markers', { + keepalive: true, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${accessToken}`, + }, + body: JSON.stringify(params), + }); + + return; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + } else if ('navigator' && 'sendBeacon' in navigator) { + // Failing that, we can use sendBeacon, but we have to encode the data as + // FormData for DoorKeeper to recognize the token. + const formData = new FormData(); + + formData.append('bearer_token', accessToken); + + for (const [id, value] of Object.entries(params)) { + if (value.last_read_id) + formData.append(`${id}[last_read_id]`, value.last_read_id); + } + + if (navigator.sendBeacon('/api/v1/markers', formData)) { + return; + } + } + + // If neither Fetch nor sendBeacon worked, try to perform a synchronous + // request. + try { + const client = new XMLHttpRequest(); + + client.open('POST', '/api/v1/markers', false); + client.setRequestHeader('Content-Type', 'application/json'); + client.setRequestHeader('Authorization', `Bearer ${accessToken}`); + client.send(JSON.stringify(params)); + } catch (e) { + // Do not make the BeforeUnload handler error out + } + }, +); + +interface MarkerParam { + last_read_id?: string; +} + +function getLastNotificationId(state: RootState): string | undefined { + // @ts-expect-error state.notifications is not yet typed + // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call + return state.getIn(['notifications', 'lastReadId']); +} + +const buildPostMarkersParams = (state: RootState) => { + const params = {} as { home?: MarkerParam; notifications?: MarkerParam }; + + const lastNotificationId = getLastNotificationId(state); + + if ( + lastNotificationId && + lastNotificationId !== '0' && + compareId(lastNotificationId, state.markers.notifications) > 0 + ) { + params.notifications = { + last_read_id: lastNotificationId, + }; + } + + return params; +}; + +export const submitMarkersAction = createAppAsyncThunk<{ + home: string | undefined; + notifications: string | undefined; +}>('markers/submitAction', async (_args, { getState }) => { + const accessToken = authorizationTokenFromState(getState); + const params = buildPostMarkersParams(getState()); + + if (Object.keys(params).length === 0 || accessToken === '') { + return { home: undefined, notifications: undefined }; + } + + await api(getState).post('/api/v1/markers', params); + + return { + home: params.home?.last_read_id, + notifications: params.notifications?.last_read_id, + }; +}); + +const debouncedSubmitMarkers = debounce( + (dispatch: AppDispatch) => { + void dispatch(submitMarkersAction()); + }, + 300000, + { + leading: true, + trailing: true, + }, +); + +export const submitMarkers = createAppAsyncThunk( + 'markers/submit', + (params: { immediate?: boolean }, { dispatch }) => { + debouncedSubmitMarkers(dispatch); + + if (params.immediate) { + debouncedSubmitMarkers.flush(); + } + }, +); + +export const fetchMarkers = createAppAsyncThunk( + 'markers/fetch', + async (_args, { getState }) => { + const response = await api(getState).get>( + `/api/v1/markers`, + { params: { timeline: ['notifications'] } }, + ); + + return { markers: response.data }; + }, +); diff --git a/app/javascript/flavours/glitch/actions/picture_in_picture.js b/app/javascript/flavours/glitch/actions/picture_in_picture.js deleted file mode 100644 index 898375abeb..0000000000 --- a/app/javascript/flavours/glitch/actions/picture_in_picture.js +++ /dev/null @@ -1,46 +0,0 @@ -// @ts-check - -export const PICTURE_IN_PICTURE_DEPLOY = 'PICTURE_IN_PICTURE_DEPLOY'; -export const PICTURE_IN_PICTURE_REMOVE = 'PICTURE_IN_PICTURE_REMOVE'; - -/** - * @typedef MediaProps - * @property {string} src - * @property {boolean} muted - * @property {number} volume - * @property {number} currentTime - * @property {string} poster - * @property {string} backgroundColor - * @property {string} foregroundColor - * @property {string} accentColor - */ - -/** - * @param {string} statusId - * @param {string} accountId - * @param {string} playerType - * @param {MediaProps} props - * @returns {object} - */ -export const deployPictureInPicture = (statusId, accountId, playerType, props) => { - // @ts-expect-error - return (dispatch, getState) => { - // Do not open a player for a toot that does not exist - if (getState().hasIn(['statuses', statusId])) { - dispatch({ - type: PICTURE_IN_PICTURE_DEPLOY, - statusId, - accountId, - playerType, - props, - }); - } - }; -}; - -/* - * @return {object} - */ -export const removePictureInPicture = () => ({ - type: PICTURE_IN_PICTURE_REMOVE, -}); diff --git a/app/javascript/flavours/glitch/actions/picture_in_picture.ts b/app/javascript/flavours/glitch/actions/picture_in_picture.ts new file mode 100644 index 0000000000..9ad88ae29b --- /dev/null +++ b/app/javascript/flavours/glitch/actions/picture_in_picture.ts @@ -0,0 +1,31 @@ +import { createAction } from '@reduxjs/toolkit'; + +import type { PIPMediaProps } from 'flavours/glitch/reducers/picture_in_picture'; +import { createAppAsyncThunk } from 'flavours/glitch/store/typed_functions'; + +interface DeployParams { + statusId: string; + accountId: string; + playerType: 'audio' | 'video'; + props: PIPMediaProps; +} + +export const removePictureInPicture = createAction('pip/remove'); + +export const deployPictureInPictureAction = + createAction('pip/deploy'); + +export const deployPictureInPicture = createAppAsyncThunk( + 'pip/deploy', + (args: DeployParams, { dispatch, getState }) => { + const { statusId } = args; + + // Do not open a player for a toot that does not exist + + // @ts-expect-error state.statuses is not yet typed + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + if (getState().hasIn(['statuses', statusId])) { + dispatch(deployPictureInPictureAction(args)); + } + }, +); diff --git a/app/javascript/flavours/glitch/api.ts b/app/javascript/flavours/glitch/api.ts index f262fd8570..de597a3e3b 100644 --- a/app/javascript/flavours/glitch/api.ts +++ b/app/javascript/flavours/glitch/api.ts @@ -29,9 +29,14 @@ const setCSRFHeader = () => { void ready(setCSRFHeader); +export const authorizationTokenFromState = (getState?: GetState) => { + return ( + getState && (getState().meta.get('access_token', '') as string | false) + ); +}; + const authorizationHeaderFromState = (getState?: GetState) => { - const accessToken = - getState && (getState().meta.get('access_token', '') as string); + const accessToken = authorizationTokenFromState(getState); if (!accessToken) { return {}; diff --git a/app/javascript/flavours/glitch/api_types/markers.ts b/app/javascript/flavours/glitch/api_types/markers.ts new file mode 100644 index 0000000000..f7664fd7c1 --- /dev/null +++ b/app/javascript/flavours/glitch/api_types/markers.ts @@ -0,0 +1,7 @@ +// See app/serializers/rest/account_serializer.rb + +export interface MarkerJSON { + last_read_id: string; + version: string; + updated_at: string; +} diff --git a/app/javascript/flavours/glitch/api_types/media_attachments.ts b/app/javascript/flavours/glitch/api_types/media_attachments.ts new file mode 100644 index 0000000000..fc027ccd2a --- /dev/null +++ b/app/javascript/flavours/glitch/api_types/media_attachments.ts @@ -0,0 +1,22 @@ +// See app/serializers/rest/media_attachment_serializer.rb + +export type MediaAttachmentType = + | 'image' + | 'gifv' + | 'video' + | 'unknown' + | 'audio'; + +export interface ApiMediaAttachmentJSON { + id: string; + type: MediaAttachmentType; + url: string; + preview_url: string; + remoteUrl: string; + preview_remote_url: string; + text_url: string; + // TODO: how to define this? + meta: unknown; + description?: string; + blurhash: string; +} diff --git a/app/javascript/flavours/glitch/api_types/polls.ts b/app/javascript/flavours/glitch/api_types/polls.ts new file mode 100644 index 0000000000..8181f7b813 --- /dev/null +++ b/app/javascript/flavours/glitch/api_types/polls.ts @@ -0,0 +1,23 @@ +import type { ApiCustomEmojiJSON } from './custom_emoji'; + +// See app/serializers/rest/poll_serializer.rb + +export interface ApiPollOptionJSON { + title: string; + votes_count: number; +} + +export interface ApiPollJSON { + id: string; + expires_at: string; + expired: boolean; + multiple: boolean; + votes_count: number; + voters_count: number; + + options: ApiPollOptionJSON[]; + emojis: ApiCustomEmojiJSON[]; + + voted: boolean; + own_votes: number[]; +} diff --git a/app/javascript/flavours/glitch/api_types/statuses.ts b/app/javascript/flavours/glitch/api_types/statuses.ts new file mode 100644 index 0000000000..d63441873d --- /dev/null +++ b/app/javascript/flavours/glitch/api_types/statuses.ts @@ -0,0 +1,95 @@ +// See app/serializers/rest/status_serializer.rb + +import type { ApiAccountJSON } from './accounts'; +import type { ApiCustomEmojiJSON } from './custom_emoji'; +import type { ApiMediaAttachmentJSON } from './media_attachments'; +import type { ApiPollJSON } from './polls'; + +// See app/modals/status.rb +export type StatusVisibility = + | 'public' + | 'unlisted' + | 'private' + // | 'limited' // This is never exposed to the API (they become `private`) + | 'direct'; + +export interface ApiStatusApplicationJSON { + name: string; + website: string; +} + +export interface ApiTagJSON { + name: string; + url: string; +} + +export interface ApiMentionJSON { + id: string; + username: string; + url: string; + acct: string; +} + +export interface ApiPreviewCardJSON { + url: string; + title: string; + description: string; + language: string; + type: string; + author_name: string; + author_url: string; + provider_name: string; + provider_url: string; + html: string; + width: number; + height: number; + image: string; + image_description: string; + embed_url: string; + blurhash: string; + published_at: string; +} + +export interface ApiStatusJSON { + id: string; + created_at: string; + in_reply_to_id?: string; + in_reply_to_account_id?: string; + sensitive: boolean; + spoiler_text?: string; + visibility: StatusVisibility; + language: string; + uri: string; + url: string; + replies_count: number; + reblogs_count: number; + favorites_count: number; + edited_at?: string; + + favorited?: boolean; + reblogged?: boolean; + muted?: boolean; + bookmarked?: boolean; + pinned?: boolean; + + // filtered: FilterResult[] + filtered: unknown; // TODO + content?: string; + text?: string; + + reblog?: ApiStatusJSON; + application?: ApiStatusApplicationJSON; + account: ApiAccountJSON; + media_attachments: ApiMediaAttachmentJSON[]; + mentions: ApiMentionJSON[]; + + tags: ApiTagJSON[]; + emojis: ApiCustomEmojiJSON[]; + + card?: ApiPreviewCardJSON; + poll?: ApiPollJSON; + + // glitch-soc additions + local_only?: boolean; + content_type?: string; +} diff --git a/app/javascript/flavours/glitch/common.js b/app/javascript/flavours/glitch/common.js new file mode 100644 index 0000000000..1bcb1d00f1 --- /dev/null +++ b/app/javascript/flavours/glitch/common.js @@ -0,0 +1,12 @@ +import Rails from '@rails/ujs'; +import 'font-awesome/css/font-awesome.css'; + +export function start() { + require.context('@/images/', true, /\.(jpg|png|svg)$/); + + try { + Rails.start(); + } catch (e) { + // If called twice + } +} diff --git a/app/javascript/flavours/glitch/components/account.jsx b/app/javascript/flavours/glitch/components/account.jsx index 7e5209653e..326d4fcb69 100644 --- a/app/javascript/flavours/glitch/components/account.jsx +++ b/app/javascript/flavours/glitch/components/account.jsx @@ -1,16 +1,18 @@ import PropTypes from 'prop-types'; +import { useCallback } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; +import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import { EmptyAccount } from 'flavours/glitch/components/empty_account'; import { ShortNumber } from 'flavours/glitch/components/short_number'; import { VerifiedBadge } from 'flavours/glitch/components/verified_badge'; +import DropdownMenuContainer from '../containers/dropdown_menu_container'; import { me } from '../initial_state'; import { Avatar } from './avatar'; @@ -30,153 +32,153 @@ const messages = defineMessages({ unmute_notifications: { id: 'account.unmute_notifications_short', defaultMessage: 'Unmute notifications' }, mute: { id: 'account.mute_short', defaultMessage: 'Mute' }, block: { id: 'account.block_short', defaultMessage: 'Block' }, + more: { id: 'status.more', defaultMessage: 'More' }, }); -class Account extends ImmutablePureComponent { +const Account = ({ size = 46, account, onFollow, onBlock, onMute, onMuteNotifications, hidden, minimal, defaultAction, withBio }) => { + const intl = useIntl(); - static propTypes = { - size: PropTypes.number, - account: ImmutablePropTypes.record, - onFollow: PropTypes.func, - onBlock: PropTypes.func, - onMute: PropTypes.func, - onMuteNotifications: PropTypes.func, - intl: PropTypes.object.isRequired, - hidden: PropTypes.bool, - minimal: PropTypes.bool, - defaultAction: PropTypes.string, - withBio: PropTypes.bool, - }; + const handleFollow = useCallback(() => { + onFollow(account); + }, [onFollow, account]); - static defaultProps = { - size: 46, - }; + const handleBlock = useCallback(() => { + onBlock(account); + }, [onBlock, account]); - handleFollow = () => { - this.props.onFollow(this.props.account); - }; + const handleMute = useCallback(() => { + onMute(account); + }, [onMute, account]); - handleBlock = () => { - this.props.onBlock(this.props.account); - }; + const handleMuteNotifications = useCallback(() => { + onMuteNotifications(account, true); + }, [onMuteNotifications, account]); - handleMute = () => { - this.props.onMute(this.props.account); - }; + const handleUnmuteNotifications = useCallback(() => { + onMuteNotifications(account, false); + }, [onMuteNotifications, account]); - handleMuteNotifications = () => { - this.props.onMuteNotifications(this.props.account, true); - }; - - handleUnmuteNotifications = () => { - this.props.onMuteNotifications(this.props.account, false); - }; - - render () { - const { account, intl, hidden, withBio, defaultAction, size, minimal } = this.props; - - if (!account) { - return ; - } - - if (hidden) { - return ( - <> - {account.get('display_name')} - {account.get('username')} - - ); - } - - let buttons; - - if (account.get('id') !== me && account.get('relationship', null) !== null) { - const following = account.getIn(['relationship', 'following']); - const requested = account.getIn(['relationship', 'requested']); - const blocking = account.getIn(['relationship', 'blocking']); - const muting = account.getIn(['relationship', 'muting']); - - if (requested) { - buttons = .", + "domain_pill.who_you_are": "Protože handle říká kdo jsi a kde jsi, mohou s tebou lidé komunikovat napříč sociálními weby .", + "domain_pill.your_handle": "Tvůj handle:", + "domain_pill.your_server": "Tvůj digitální domov, kde žijí všechny tvé příspěvky. Nelíbí se ti? Kdykoliv se přesuň na jiný server a vezmi si sebou i své sledující.", + "domain_pill.your_username": "Tvůj jedinečný identifikátor na tomto serveru. Je možné najít uživatele se stejným uživatelským jménem na jiných serverech.", "embed.instructions": "Pro přidání příspěvku na vaši webovou stránku zkopírujte níže uvedený kód.", "embed.preview": "Takhle to bude vypadat:", "emoji_button.activity": "Aktivita", @@ -236,6 +266,7 @@ "empty_column.list": "V tomto seznamu zatím nic není. Až nějaký člen z tohoto seznamu zveřejní nový příspěvek, objeví se zde.", "empty_column.lists": "Zatím nemáte žádné seznamy. Až nějaký vytvoříte, zobrazí se zde.", "empty_column.mutes": "Zatím jste neskryli žádného uživatele.", + "empty_column.notification_requests": "Vyčištěno! Nic tu není. Jakmile obdržíš nové notifikace, objeví se zde podle tvého nastavení.", "empty_column.notifications": "Zatím nemáte žádná oznámení. Až s vámi někdo bude interagovat, uvidíte to zde.", "empty_column.public": "Tady nic není! Napište něco veřejně, nebo začněte ručně sledovat uživatele z jiných serverů, aby tu něco přibylo", "error.unexpected_crash.explanation": "Kvůli chybě v našem kódu nebo problému s kompatibilitou prohlížeče nemohla být tato stránka správně zobrazena.", @@ -266,6 +297,9 @@ "filter_modal.select_filter.subtitle": "Použít existující kategorii nebo vytvořit novou kategorii", "filter_modal.select_filter.title": "Filtrovat tento příspěvek", "filter_modal.title.status": "Filtrovat příspěvek", + "filtered_notifications_banner.mentions": "{count, plural, one {zmínka} few {zmínky} many {zmínek} other {zmínek}}", + "filtered_notifications_banner.pending_requests": "Oznámení od {count, plural, =0 {nikoho} one {jednoho člověka, kterého znáte} few {# lidí, které znáte} many {# lidí, které znáte} other {# lidí, které znáte}}", + "filtered_notifications_banner.title": "Filtrovaná oznámení", "firehose.all": "Vše", "firehose.local": "Tento server", "firehose.remote": "Ostatní servery", @@ -274,6 +308,8 @@ "follow_requests.unlocked_explanation": "Přestože váš účet není uzamčen, personál {domain} usoudil, že byste mohli chtít tyto požadavky na sledování zkontrolovat ručně.", "follow_suggestions.curated_suggestion": "Výběr personálů", "follow_suggestions.dismiss": "Znovu nezobrazovat", + "follow_suggestions.featured_longer": "Ručně vybráno týmem {domain}", + "follow_suggestions.friends_of_friends_longer": "Populární mezi lidmi, které sledujete", "follow_suggestions.hints.featured": "Tento profil byl ručně vybrán týmem {domain}.", "follow_suggestions.hints.friends_of_friends": "Tento profil je populární mezi lidmi, které sledujete.", "follow_suggestions.hints.most_followed": "Tento profil je jedním z nejvíce sledovaných na {domain}.", @@ -281,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Tento profil je podobný profilům, které jste nedávno sledovali.", "follow_suggestions.personalized_suggestion": "Přizpůsobený návrh", "follow_suggestions.popular_suggestion": "Populární návrh", + "follow_suggestions.popular_suggestion_longer": "Populární na {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Podobné profilům, které jste nedávno sledovali", "follow_suggestions.view_all": "Zobrazit vše", "follow_suggestions.who_to_follow": "Koho sledovat", "followed_tags": "Sledované hashtagy", @@ -394,6 +432,15 @@ "loading_indicator.label": "Načítání…", "media_gallery.toggle_visible": "{number, plural, one {Skrýt obrázek} few {Skrýt obrázky} many {Skrýt obrázky} other {Skrýt obrázky}}", "moved_to_account_banner.text": "Váš účet {disabledAccount} je momentálně deaktivován, protože jste se přesunul/a na {movedToAccount}.", + "mute_modal.hide_from_notifications": "Skrýt z notifikací", + "mute_modal.hide_options": "Skrýt možnosti", + "mute_modal.indefinite": "Dokud je neodkryju", + "mute_modal.show_options": "Zobrazit možnosti", + "mute_modal.they_can_mention_and_follow": "Mohou vás zmínit a sledovat, ale neuvidíte je.", + "mute_modal.they_wont_know": "Nebudou vědět, že byli skryti.", + "mute_modal.title": "Ztlumit uživatele?", + "mute_modal.you_wont_see_mentions": "Neuvidíte příspěvky, které je zmiňují.", + "mute_modal.you_wont_see_posts": "Stále budou moci vidět vaše příspěvky, ale vy jejich neuvidíte.", "navigation_bar.about": "O aplikaci", "navigation_bar.advanced_interface": "Otevřít pokročilé webové rozhraní", "navigation_bar.blocks": "Blokovaní uživatelé", @@ -426,17 +473,37 @@ "notification.follow": "Uživatel {name} vás začal sledovat", "notification.follow_request": "Uživatel {name} požádal o povolení vás sledovat", "notification.mention": "Uživatel {name} vás zmínil", + "notification.moderation-warning.learn_more": "Zjistit více", + "notification.moderation_warning": "Obdrželi jste moderační varování", + "notification.moderation_warning.action_delete_statuses": "Některé z vašich příspěvků byly odstraněny.", + "notification.moderation_warning.action_disable": "Váš účet je zablokován.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Některé z vašich příspěvků byly označeny jako citlivé.", + "notification.moderation_warning.action_none": "Váš účet obdržel moderační varování.", + "notification.moderation_warning.action_sensitive": "Vaše příspěvky budou od nynějška označeny jako citlivé.", + "notification.moderation_warning.action_silence": "Váš účet byl omezen.", + "notification.moderation_warning.action_suspend": "Váš účet byl pozastaven.", "notification.own_poll": "Vaše anketa skončila", "notification.poll": "Anketa, ve které jste hlasovali, skončila", "notification.reblog": "Uživatel {name} boostnul váš příspěvek", + "notification.relationships_severance_event": "Kontakt ztracen s {name}", + "notification.relationships_severance_event.account_suspension": "Administrátor z {from} pozastavil {target}, což znamená, že již od nich nemůžete přijímat aktualizace nebo s nimi interagovat.", + "notification.relationships_severance_event.domain_block": "Administrátor z {from} pozastavil {target}, včetně {followersCount} z vašich sledujících a {followingCount, plural, one {# účet, který sledujete} few {# účty, které sledujete} many {# účtů, které sledujete} other {# účtů, které sledujete}}.", + "notification.relationships_severance_event.learn_more": "Zjistit více", + "notification.relationships_severance_event.user_domain_block": "Zablokovali jste {target}, čímž jste odebrali {followersCount} z vašich sledujících a {followingCount, plural, one {# účet, který sledujete} few {# účty, které sledujete} many {# účtů, které sledujete} other {# účtů, které sledujete}}.", "notification.status": "Uživatel {name} právě přidal příspěvek", "notification.update": "Uživatel {name} upravil příspěvek", + "notification_requests.accept": "Přijmout", + "notification_requests.dismiss": "Zamítnout", + "notification_requests.notifications_from": "Oznámení od {name}", + "notification_requests.title": "Vyfiltrovaná oznámení", "notifications.clear": "Vyčistit oznámení", "notifications.clear_confirmation": "Opravdu chcete trvale smazat všechna vaše oznámení?", "notifications.column_settings.admin.report": "Nová hlášení:", "notifications.column_settings.admin.sign_up": "Nové registrace:", "notifications.column_settings.alert": "Oznámení na počítači", "notifications.column_settings.favourite": "Oblíbené:", + "notifications.column_settings.filter_bar.advanced": "Zobrazit všechny kategorie", + "notifications.column_settings.filter_bar.category": "Panel rychlého filtrování", "notifications.column_settings.follow": "Noví sledující:", "notifications.column_settings.follow_request": "Nové žádosti o sledování:", "notifications.column_settings.mention": "Zmínky:", @@ -462,6 +529,15 @@ "notifications.permission_denied": "Oznámení na ploše nejsou k dispozici, protože byla zamítnuta žádost o oprávnění je zobrazovat", "notifications.permission_denied_alert": "Oznámení na ploše není možné zapnout, protože oprávnění bylo v minulosti zamítnuto", "notifications.permission_required": "Oznámení na ploše nejsou k dispozici, protože nebylo uděleno potřebné oprávnění.", + "notifications.policy.filter_new_accounts.hint": "Vytvořeno během {days, plural, one {včerejška} few {posledních # dnů} many {posledních # dní} other {posledních # dní}}", + "notifications.policy.filter_new_accounts_title": "Nové účty", + "notifications.policy.filter_not_followers_hint": "Včetně lidí, kteří vás sledovali méně než {days, plural, one {jeden den} few {# dny} many {# dní} other {# dní}}", + "notifications.policy.filter_not_followers_title": "Lidé, kteří vás nesledují", + "notifications.policy.filter_not_following_hint": "Dokud je ručně neschválíte", + "notifications.policy.filter_not_following_title": "Lidé, které nesledujete", + "notifications.policy.filter_private_mentions_hint": "Vyfiltrováno, pokud to není odpověď na vaši zmínku nebo pokud sledujete odesílatele", + "notifications.policy.filter_private_mentions_title": "Nevyžádané soukromé zmínky", + "notifications.policy.title": "Vyfiltrovat oznámení od…", "notifications_permission_banner.enable": "Povolit oznámení na ploše", "notifications_permission_banner.how_to_control": "Chcete-li dostávat oznámení, i když nemáte Mastodon otevřený, povolte oznámení na ploše. Můžete si zvolit, o kterých druzích interakcí chcete být oznámením na ploše informování pod tlačítkem {icon} výše.", "notifications_permission_banner.title": "Nenechte si nic uniknout", @@ -638,9 +714,11 @@ "status.direct": "Soukromě zmínit @{name}", "status.direct_indicator": "Soukromá zmínka", "status.edit": "Upravit", + "status.edited": "Naposledy upraveno {date}", "status.edited_x_times": "Upraveno {count, plural, one {{count}krát} few {{count}krát} many {{count}krát} other {{count}krát}}", "status.embed": "Vložit na web", "status.favourite": "Oblíbit", + "status.favourites": "{count, plural, one {oblíbený} few {oblíbené} many {oblíbených} other {oblíbených}}", "status.filter": "Filtrovat tento příspěvek", "status.filtered": "Filtrováno", "status.hide": "Skrýt příspěvek", @@ -661,6 +739,7 @@ "status.reblog": "Boostnout", "status.reblog_private": "Boostnout s původní viditelností", "status.reblogged_by": "Uživatel {name} boostnul", + "status.reblogs": "{count, plural, one {boost} few {boosty} many {boostů} other {boostů}}", "status.reblogs.empty": "Tento příspěvek ještě nikdo neboostnul. Pokud to někdo udělá, zobrazí se zde.", "status.redraft": "Smazat a přepsat", "status.remove_bookmark": "Odstranit ze záložek", diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json index d2731b6294..fd8fc74be8 100644 --- a/app/javascript/mastodon/locales/cy.json +++ b/app/javascript/mastodon/locales/cy.json @@ -89,6 +89,14 @@ "announcement.announcement": "Cyhoeddiad", "attachments_list.unprocessed": "(heb eu prosesu)", "audio.hide": "Cuddio sain", + "block_modal.remote_users_caveat": "Byddwn yn gofyn i'r gweinydd {domain} barchu eich penderfyniad. Fodd bynnag, nid yw cydymffurfiad wedi'i warantu gan y gall rhai gweinyddwyr drin rhwystro mewn ffyrdd gwahanol. Mae'n bosibl y bydd postiadau cyhoeddus yn dal i fod yn weladwy i ddefnyddwyr nad ydynt wedi mewngofnodi.", + "block_modal.show_less": "Dangos llai", + "block_modal.show_more": "Dangos mwy", + "block_modal.they_cant_mention": "Nid ydynt yn gallu eich crybwyll na'ch dilyn.", + "block_modal.they_cant_see_posts": "Nid ydynt yn gallu gweld eich postiadau ac ni fyddwch yn gweld eu rhai hwy.", + "block_modal.they_will_know": "Gallant weld eu bod wedi'u rhwystro.", + "block_modal.title": "Rhwystro defnyddiwr?", + "block_modal.you_wont_see_mentions": "Ni welwch bostiadau sy'n sôn amdanynt.", "boost_modal.combo": "Mae modd pwyso {combo} er mwyn hepgor hyn tro nesa", "bundle_column_error.copy_stacktrace": "Copïo'r adroddiad gwall", "bundle_column_error.error.body": "Nid oedd modd cynhyrchu'r dudalen honno. Gall fod oherwydd gwall yn ein cod neu fater cydnawsedd porwr.", @@ -169,6 +177,7 @@ "confirmations.delete_list.message": "Ydych chi'n siŵr eich bod eisiau dileu'r rhestr hwn am byth?", "confirmations.discard_edit_media.confirm": "Dileu", "confirmations.discard_edit_media.message": "Mae gennych newidiadau heb eu cadw i'r disgrifiad cyfryngau neu'r rhagolwg - eu dileu beth bynnag?", + "confirmations.domain_block.confirm": "Rhwystro gweinydd", "confirmations.domain_block.message": "Ydych chi wir, wir eisiau blocio'r holl {domain}? Fel arfer, mae blocio neu dewi pobl penodol yn broses mwy effeithiol. Fyddwch chi ddim yn gweld cynnwys o'r parth hwnnw mewn ffrydiau cyhoeddus neu yn eich hysbysiadau. Bydd eich dilynwyr o'r parth hwnnw yn cael eu ddileu.", "confirmations.edit.confirm": "Golygu", "confirmations.edit.message": "Bydd golygu nawr yn trosysgrifennu'r neges rydych yn ei ysgrifennu ar hyn o bryd. Ydych chi'n siŵr eich bod eisiau gwneud hyn?", @@ -200,6 +209,27 @@ "dismissable_banner.explore_statuses": "Mae'r rhain yn bostiadau o bob rhan o'r we gymdeithasol sydd ar gynnydd heddiw. Mae postiadau mwy diweddar sydd â mwy o hybiau a ffefrynu'n cael eu graddio'n uwch.", "dismissable_banner.explore_tags": "Mae'r rhain yn hashnodau sydd ar gynnydd ar y we gymdeithasol heddiw. Mae hashnodau sy'n cael eu defnyddio gan fwy o unigolion gwahanol yn cael eu graddio'n uwch.", "dismissable_banner.public_timeline": "Dyma'r postiadau cyhoeddus diweddaraf gan bobl ar y we gymdeithasol y mae pobl ar {domain} yn eu dilyn.", + "domain_block_modal.block": "Rhwystro gweinydd", + "domain_block_modal.block_account_instead": "Rhwystro @{name} yn lle hynny", + "domain_block_modal.they_can_interact_with_old_posts": "Gall pobl o'r gweinydd hwn ryngweithio â'ch hen bostiadau.", + "domain_block_modal.they_cant_follow": "Ni all neb o'r gweinydd hwn eich dilyn.", + "domain_block_modal.they_wont_know": "Fyddan nhw ddim yn gwybod eu bod wedi cael eu rhwystro.", + "domain_block_modal.title": "Rhwystro parth?", + "domain_block_modal.you_will_lose_followers": "Bydd eich holl ddilynwyr o'r gweinydd hwn yn cael eu tynnu.", + "domain_block_modal.you_wont_see_posts": "Fyddwch chi ddim yn gweld postiadau na hysbysiadau gan ddefnyddwyr ar y gweinydd hwn.", + "domain_pill.activitypub_lets_connect": "Mae'n caniatáu ichi gysylltu a rhyngweithio â phobl nid yn unig ar Mastodon, ond ar draws gwahanol apiau cymdeithasol hefyd.", + "domain_pill.activitypub_like_language": "Mae ActivityPub fel yr iaith y mae Mastodon yn ei siarad â rhwydweithiau cymdeithasol eraill.", + "domain_pill.server": "Gweinydd", + "domain_pill.their_handle": "Eu handlen:", + "domain_pill.their_server": "Eu cartref digidol, lle mae eu holl negeseuon yn byw.", + "domain_pill.their_username": "Eu dynodwr unigryw ar eu gweinydd. Mae'n bosibl dod o hyd i ddefnyddwyr gyda'r un enw defnyddiwr ar wahanol weinyddion.", + "domain_pill.username": "Enw Defnyddiwr", + "domain_pill.whats_in_a_handle": "Beth sydd mewn handlen?", + "domain_pill.who_they_are": "Gan fod handlen yn dweud pwy yw rhywun a ble maen nhw, gallwch chi ryngweithio â phobl ar draws gwe gymdeithasol .", + "domain_pill.who_you_are": "Oherwydd bod eich handlen yn dweud pwy ydych chi a ble rydych chi, gall pobl ryngweithio â chi ar draws gwe gymdeithasol .", + "domain_pill.your_handle": "Eich handlen:", + "domain_pill.your_server": "Eich cartref digidol, lle mae'ch holl bostiadau'n byw. Ddim yn hoffi'r un hon? Trosglwyddwch weinyddion ar unrhyw adeg a dewch â'ch dilynwyr hefyd.", + "domain_pill.your_username": "Eich dynodwr unigryw ar y gweinydd hwn. Mae'n bosibl dod o hyd i ddefnyddwyr gyda'r un enw defnyddiwr ar wahanol weinyddion.", "embed.instructions": "Gosodwch y post hwn ar eich gwefan drwy gopïo'r côd isod.", "embed.preview": "Dyma sut olwg fydd arno:", "emoji_button.activity": "Gweithgarwch", @@ -267,6 +297,8 @@ "filter_modal.select_filter.subtitle": "Defnyddiwch gategori sy'n bodoli eisoes neu crëu un newydd", "filter_modal.select_filter.title": "Hidlo'r postiad hwn", "filter_modal.title.status": "Hidlo postiad", + "filtered_notifications_banner.mentions": "{count, plural, one {crybwylliad} other {crybwylliad}}", + "filtered_notifications_banner.pending_requests": "Hysbysiadau gan {count, plural, =0 {neb} one {un person} other {# person}} efallai y gwyddoch amdanyn nhw", "filtered_notifications_banner.title": "Hysbysiadau wedi'u hidlo", "firehose.all": "Popeth", "firehose.local": "Gweinydd hwn", @@ -276,6 +308,8 @@ "follow_requests.unlocked_explanation": "Er nid yw eich cyfrif wedi'i gloi, roedd y staff {domain} yn meddwl efallai hoffech adolygu ceisiadau dilyn o'r cyfrifau rhain wrth law.", "follow_suggestions.curated_suggestion": "Dewis staff", "follow_suggestions.dismiss": "Peidio â dangos hwn eto", + "follow_suggestions.featured_longer": "Wedi'i ddewis â llaw gan dîm {domain}", + "follow_suggestions.friends_of_friends_longer": "Yn boblogaidd ymhlith y bobl rydych chi'n eu dilyn", "follow_suggestions.hints.featured": "Mae'r proffil hwn wedi'i ddewis yn arbennig gan dîm {domain}.", "follow_suggestions.hints.friends_of_friends": "Mae'r proffil hwn yn boblogaidd ymhlith y bobl rydych chi'n eu dilyn.", "follow_suggestions.hints.most_followed": "Mae'r proffil hwn yn un o'r rhai sy'n cael ei ddilyn fwyaf ar {domain}.", @@ -283,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Mae'r proffil hwn yn debyg i'r proffiliau rydych chi wedi'u dilyn yn fwyaf diweddar.", "follow_suggestions.personalized_suggestion": "Awgrym personol", "follow_suggestions.popular_suggestion": "Awgrym poblogaidd", + "follow_suggestions.popular_suggestion_longer": "Yn boblogaidd ar {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Yn debyg i broffiliau y gwnaethoch chi eu dilyn yn ddiweddar", "follow_suggestions.view_all": "Gweld y cyfan", "follow_suggestions.who_to_follow": "Pwy i ddilyn", "followed_tags": "Hashnodau rydych yn eu dilyn", @@ -396,6 +432,15 @@ "loading_indicator.label": "Yn llwytho…", "media_gallery.toggle_visible": "{number, plural, one {Cuddio delwedd} other {Cuddio delwedd}}", "moved_to_account_banner.text": "Ar hyn y bryd, mae eich cyfrif {disabledAccount} wedi ei analluogi am i chi symud i {movedToAccount}.", + "mute_modal.hide_from_notifications": "Cuddio rhag hysbysiadau", + "mute_modal.hide_options": "Cuddio'r dewis", + "mute_modal.indefinite": "Nes i mi eu dad-dewi", + "mute_modal.show_options": "Dangos y dewis", + "mute_modal.they_can_mention_and_follow": "Gallan nhw eich crybwyll a'ch dilyn, ond fyddwch chi ddim yn eu gweld.", + "mute_modal.they_wont_know": "Fyddan nhw ddim yn gwybod eu bod wedi cael eu tawelu.", + "mute_modal.title": "Tewi defnyddiwr?", + "mute_modal.you_wont_see_mentions": "Welwch chi ddim postiadau sy'n sôn amdanyn nhw.", + "mute_modal.you_wont_see_posts": "Gallan nhw weld eich postiadau o hyd, ond fyddwch chi ddim yn gweld eu rhai hwy.", "navigation_bar.about": "Ynghylch", "navigation_bar.advanced_interface": "Agor mewn rhyngwyneb gwe uwch", "navigation_bar.blocks": "Defnyddwyr wedi eu blocio", @@ -428,9 +473,23 @@ "notification.follow": "Dilynodd {name} chi", "notification.follow_request": "Mae {name} wedi gwneud cais i'ch dilyn", "notification.mention": "Crybwyllodd {name} amdanoch chi", + "notification.moderation-warning.learn_more": "Dysgu mwy", + "notification.moderation_warning": "Rydych wedi derbyn rhybudd cymedroli", + "notification.moderation_warning.action_delete_statuses": "Mae rhai o'ch postiadau wedi'u dileu.", + "notification.moderation_warning.action_disable": "Mae eich cyfrif wedi'i analluogi.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Mae rhai o'ch postiadau wedi'u marcio'n sensitif.", + "notification.moderation_warning.action_none": "Mae eich cyfrif wedi derbyn rhybudd cymedroli.", + "notification.moderation_warning.action_sensitive": "Bydd eich postiadau'n cael eu marcio'n sensitif o hyn ymlaen.", + "notification.moderation_warning.action_silence": "Mae eich cyfrif wedi'i gyfyngu.", + "notification.moderation_warning.action_suspend": "Mae eich cyfrif wedi'i hatal.", "notification.own_poll": "Mae eich pleidlais wedi dod i ben", "notification.poll": "Mae pleidlais rydych wedi pleidleisio ynddi wedi dod i ben", "notification.reblog": "Hybodd {name} eich post", + "notification.relationships_severance_event": "Wedi colli cysylltiad â {name}", + "notification.relationships_severance_event.account_suspension": "Mae gweinyddwr o {from} wedi atal {target}, sy'n golygu na allwch dderbyn diweddariadau ganddynt mwyach na rhyngweithio â nhw.", + "notification.relationships_severance_event.domain_block": "Mae gweinyddwr o {from} wedi rhwystro {target}, gan gynnwys {followersCount} o'ch dilynwyr a {followingCount, plural, one {# cyfrif} other {# cyfrif}} arall rydych chi'n ei ddilyn.", + "notification.relationships_severance_event.learn_more": "Dysgu mwy", + "notification.relationships_severance_event.user_domain_block": "Rydych wedi rhwystro {target}, gan ddileu {followersCount} o'ch dilynwyr a {followingCount, plural, one {# cyfrif} other {#cyfrifon}} arall rydych yn ei ddilyn.", "notification.status": "{name} newydd ei bostio", "notification.update": "Golygodd {name} bostiad", "notification_requests.accept": "Derbyn", @@ -443,6 +502,8 @@ "notifications.column_settings.admin.sign_up": "Cofrestriadau newydd:", "notifications.column_settings.alert": "Hysbysiadau bwrdd gwaith", "notifications.column_settings.favourite": "Ffefrynnau:", + "notifications.column_settings.filter_bar.advanced": "Dangos pob categori", + "notifications.column_settings.filter_bar.category": "Bar hidlo cyflym", "notifications.column_settings.follow": "Dilynwyr newydd:", "notifications.column_settings.follow_request": "Ceisiadau dilyn newydd:", "notifications.column_settings.mention": "Crybwylliadau:", @@ -653,9 +714,11 @@ "status.direct": "Crybwyll yn breifat @{name}", "status.direct_indicator": "Crybwyll preifat", "status.edit": "Golygu", + "status.edited": "Golygwyd ddiwethaf {date}", "status.edited_x_times": "Golygwyd {count, plural, one {count} two {count} other {{count} gwaith}}", "status.embed": "Mewnblannu", "status.favourite": "Hoffi", + "status.favourites": "{count, plural, one {ffefryn} other {ffefryn}}", "status.filter": "Hidlo'r postiad hwn", "status.filtered": "Wedi'i hidlo", "status.hide": "Cuddio'r postiad", @@ -676,6 +739,7 @@ "status.reblog": "Hybu", "status.reblog_private": "Hybu i'r gynulleidfa wreiddiol", "status.reblogged_by": "Hybodd {name}", + "status.reblogs": "{count, plural, one {hwb} other {hwb}}", "status.reblogs.empty": "Does neb wedi hybio'r post yma eto. Pan y bydd rhywun yn gwneud, byddent yn ymddangos yma.", "status.redraft": "Dileu ac ailddrafftio", "status.remove_bookmark": "Tynnu nod tudalen", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 97468c6272..a23d537331 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -295,6 +295,7 @@ "filter_modal.select_filter.subtitle": "Vælg en eksisterende kategori eller opret en ny", "filter_modal.select_filter.title": "Filtrér dette indlæg", "filter_modal.title.status": "Filtrér et indlæg", + "filtered_notifications_banner.mentions": "{count, plural, one {omtale} other {omtaler}}", "filtered_notifications_banner.pending_requests": "Notifikationer fra {count, plural, =0 {ingen} one {én person} other {# personer}} du måske kender", "filtered_notifications_banner.title": "Filtrerede notifikationer", "firehose.all": "Alle", @@ -305,6 +306,8 @@ "follow_requests.unlocked_explanation": "Selvom din konto ikke er låst, synes {domain}-personalet, du måske bør gennemgå disse anmodninger manuelt.", "follow_suggestions.curated_suggestion": "Personaleudvalgt", "follow_suggestions.dismiss": "Vis ikke igen", + "follow_suggestions.featured_longer": "Håndplukket af {domain}-teamet", + "follow_suggestions.friends_of_friends_longer": "Populært blandt personer, som følges", "follow_suggestions.hints.featured": "Denne profil er håndplukket af {domain}-teamet.", "follow_suggestions.hints.friends_of_friends": "Denne profil er populær blandt de personer, som følges.", "follow_suggestions.hints.most_followed": "Denne profil er en af de mest fulgte på {domain}.", @@ -312,6 +315,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Denne profil svarer til de profiler, som senest er blevet fulgt.", "follow_suggestions.personalized_suggestion": "Personligt forslag", "follow_suggestions.popular_suggestion": "Populært forslag", + "follow_suggestions.popular_suggestion_longer": "Populært på {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Svarende til profiler, som for nylig er fulgt", "follow_suggestions.view_all": "Vis alle", "follow_suggestions.who_to_follow": "Hvem, som skal følges", "followed_tags": "Hashtag, som følges", @@ -466,9 +471,23 @@ "notification.follow": "{name} begyndte at følge dig", "notification.follow_request": "{name} har anmodet om at følge dig", "notification.mention": "{name} nævnte dig", + "notification.moderation-warning.learn_more": "Læs mere", + "notification.moderation_warning": "Du er tildelt en moderationsadvarsel", + "notification.moderation_warning.action_delete_statuses": "Nogle af dine indlæg er blevet fjernet.", + "notification.moderation_warning.action_disable": "Din konto er blevet deaktiveret.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Nogle af dine indlæg er blevet markeret som sensitive.", + "notification.moderation_warning.action_none": "Din konto er tildelt en moderationsadvarsel.", + "notification.moderation_warning.action_sensitive": "Dine indlæg markeres fra nu af som sensitive.", + "notification.moderation_warning.action_silence": "Din konto er blevet begrænset.", + "notification.moderation_warning.action_suspend": "Din konto er suspenderet.", "notification.own_poll": "Din afstemning er afsluttet", "notification.poll": "En afstemning, hvori du stemte, er slut", "notification.reblog": "{name} boostede dit indlæg", + "notification.relationships_severance_event": "Mistede forbindelser med {name}", + "notification.relationships_severance_event.account_suspension": "En admin fra {from} har suspenderet {target}, hvofor opdateringer herfra eller interaktion hermed ikke længer er mulig.", + "notification.relationships_severance_event.domain_block": "En admin fra {from} har blokeret {target}, herunder {followersCount} tilhængere og {followingCount, plural, one {# konto, der} other {# konti, som}} følges.", + "notification.relationships_severance_event.learn_more": "Læs mere", + "notification.relationships_severance_event.user_domain_block": "{target} er blevet blokeret, og {followersCount} tilhængere samt {followingCount, plural, one {# konto, der} other {# konti, som}} følges, er hermed fjernet.", "notification.status": "{name} har netop postet", "notification.update": "{name} redigerede et indlæg", "notification_requests.accept": "Acceptér", @@ -587,12 +606,6 @@ "refresh": "Genindlæs", "regeneration_indicator.label": "Indlæser…", "regeneration_indicator.sublabel": "Din hjemmetidslinje klargøres!", - "relationship_severance_notification.purged_data": "renset af administratorer", - "relationship_severance_notification.relationships": "{count, plural, one {# forhold} other {# forhold}}", - "relationship_severance_notification.types.account_suspension": "Konto er blevet suspenderet", - "relationship_severance_notification.types.domain_block": "Domæne er blevet suspenderet", - "relationship_severance_notification.types.user_domain_block": "Dette domæne blev blokeret", - "relationship_severance_notification.view": "Vis", "relative_time.days": "{number}d", "relative_time.full.days": "{number, plural, one {# dag} other {# dage}} siden", "relative_time.full.hours": "{number, plural, one {# time} other {# timer}} siden", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 8cc77c0725..5776641079 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -85,7 +85,7 @@ "alert.rate_limited.message": "Bitte versuche es nach {retry_time, time, medium} erneut.", "alert.rate_limited.title": "Anfragelimit überschritten", "alert.unexpected.message": "Ein unerwarteter Fehler ist aufgetreten.", - "alert.unexpected.title": "Ups!", + "alert.unexpected.title": "Oha!", "announcement.announcement": "Ankündigung", "attachments_list.unprocessed": "(ausstehend)", "audio.hide": "Audio ausblenden", @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "Einem vorhandenen Filter hinzufügen oder einen neuen erstellen", "filter_modal.select_filter.title": "Diesen Beitrag filtern", "filter_modal.title.status": "Beitrag per Filter ausblenden", + "filtered_notifications_banner.mentions": "{count, plural, one {Erwähnung} other {Erwähnungen}}", "filtered_notifications_banner.pending_requests": "Benachrichtigungen von {count, plural, =0 {keinem Profil, das du möglicherweise kennst} one {einem Profil, das du möglicherweise kennst} other {# Profilen, die du möglicherweise kennst}}", "filtered_notifications_banner.title": "Gefilterte Benachrichtigungen", "firehose.all": "Alles", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "Auch wenn dein Konto öffentlich bzw. nicht geschützt ist, haben die Moderator*innen von {domain} gedacht, dass du diesen Follower lieber manuell bestätigen solltest.", "follow_suggestions.curated_suggestion": "Vom Server-Team empfohlen", "follow_suggestions.dismiss": "Nicht mehr anzeigen", + "follow_suggestions.featured_longer": "Vom {domain}-Team ausgewählt", + "follow_suggestions.friends_of_friends_longer": "Beliebt bei Leuten, denen du folgst", "follow_suggestions.hints.featured": "Dieses Profil wurde vom {domain}-Team ausgewählt.", "follow_suggestions.hints.friends_of_friends": "Dieses Profil ist bei deinen Followern beliebt.", "follow_suggestions.hints.most_followed": "Dieses Profil ist eines der am meisten gefolgten auf {domain}.", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Dieses Profil ähnelt den Profilen, denen du in letzter Zeit gefolgt hast.", "follow_suggestions.personalized_suggestion": "Persönliche Empfehlung", "follow_suggestions.popular_suggestion": "Beliebte Empfehlung", + "follow_suggestions.popular_suggestion_longer": "Beliebt auf {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Ähnlich zu Profilen, denen du seit kurzem folgst", "follow_suggestions.view_all": "Alle anzeigen", "follow_suggestions.who_to_follow": "Empfohlene Profile", "followed_tags": "Gefolgte Hashtags", @@ -326,7 +331,7 @@ "footer.source_code": "Quellcode anzeigen", "footer.status": "Status", "generic.saved": "Gespeichert", - "getting_started.heading": "Auf geht’s!", + "getting_started.heading": "Auf gehts!", "hashtag.column_header.tag_mode.all": "und {additional}", "hashtag.column_header.tag_mode.any": "oder {additional}", "hashtag.column_header.tag_mode.none": "ohne {additional}", @@ -395,7 +400,7 @@ "keyboard_shortcuts.requests": "Liste der Follower-Anfragen aufrufen", "keyboard_shortcuts.search": "Suchleiste fokussieren", "keyboard_shortcuts.spoilers": "Feld für Inhaltswarnung anzeigen/ausblenden", - "keyboard_shortcuts.start": "„Auf geht’s!“ öffnen", + "keyboard_shortcuts.start": "„Auf gehts!“ öffnen", "keyboard_shortcuts.toggle_hidden": "Beitragstext hinter der Inhaltswarnung anzeigen/ausblenden", "keyboard_shortcuts.toggle_sensitivity": "Medien anzeigen/ausblenden", "keyboard_shortcuts.toot": "Neuen Beitrag erstellen", @@ -468,10 +473,23 @@ "notification.follow": "{name} folgt dir", "notification.follow_request": "{name} möchte dir folgen", "notification.mention": "{name} erwähnte dich", + "notification.moderation-warning.learn_more": "Mehr erfahren", + "notification.moderation_warning": "Du wurdest von den Moderator*innen verwarnt", + "notification.moderation_warning.action_delete_statuses": "Einige deiner Beiträge sind entfernt worden.", + "notification.moderation_warning.action_disable": "Dein Konto wurde deaktiviert.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Einige deiner Beiträge wurden mit einer Inhaltswarnung versehen.", + "notification.moderation_warning.action_none": "Dein Konto ist von den Moderator*innen verwarnt worden.", + "notification.moderation_warning.action_sensitive": "Deine zukünftigen Beiträge werden mit einer Inhaltswarnung versehen.", + "notification.moderation_warning.action_silence": "Dein Konto wurde eingeschränkt.", + "notification.moderation_warning.action_suspend": "Dein Konto wurde gesperrt.", "notification.own_poll": "Deine Umfrage ist beendet", "notification.poll": "Eine Umfrage, an der du teilgenommen hast, ist beendet", "notification.reblog": "{name} teilte deinen Beitrag", - "notification.severed_relationships": "Beziehungen zu {name} getrennt", + "notification.relationships_severance_event": "Verbindungen mit {name} verloren", + "notification.relationships_severance_event.account_suspension": "Ein Admin von {from} hat {target} gesperrt. Du wirst von diesem Profil keine Updates mehr erhalten und auch nicht mit ihm interagieren können.", + "notification.relationships_severance_event.domain_block": "Ein Admin von {from} hat {target} blockiert – darunter {followersCount} deiner Follower und {followingCount, plural, one {# Konto, dem} other {# Konten, denen}} du folgst.", + "notification.relationships_severance_event.learn_more": "Mehr erfahren", + "notification.relationships_severance_event.user_domain_block": "Du hast {target} blockiert – {followersCount} deiner Follower und {followingCount, plural, one {# Konto, dem} other {# Konten, denen}} du folgst, wurden entfernt.", "notification.status": "{name} hat gerade etwas gepostet", "notification.update": "{name} bearbeitete einen Beitrag", "notification_requests.accept": "Akzeptieren", @@ -531,11 +549,11 @@ "onboarding.follows.empty": "Bedauerlicherweise können aktuell keine Ergebnisse angezeigt werden. Du kannst die Suche verwenden oder den Reiter „Entdecken“ auswählen, um neue Leute zum Folgen zu finden – oder du versuchst es später erneut.", "onboarding.follows.lead": "Deine Startseite ist der primäre Anlaufpunkt, um Mastodon zu erleben. Je mehr Profilen du folgst, umso aktiver und interessanter wird sie. Damit du direkt loslegen kannst, gibt es hier ein paar Vorschläge:", "onboarding.follows.title": "Personalisiere deine Startseite", - "onboarding.profile.discoverable": "Mein Profil auffindbar machen", + "onboarding.profile.discoverable": "Mein Profil darf entdeckt werden", "onboarding.profile.discoverable_hint": "Wenn du entdeckt werden möchtest, dann können deine Beiträge in Suchergebnissen und Trends erscheinen. Dein Profil kann ebenfalls anderen mit ähnlichen Interessen vorgeschlagen werden.", "onboarding.profile.display_name": "Anzeigename", "onboarding.profile.display_name_hint": "Dein richtiger Name oder dein Fantasiename …", - "onboarding.profile.lead": "Du kannst das später in den Einstellungen vervollständigen, wo noch mehr Anpassungsmöglichkeiten zur Verfügung stehen.", + "onboarding.profile.lead": "Du kannst dein Profil später in den Einstellungen vervollständigen. Dort stehen weitere Anpassungsmöglichkeiten zur Verfügung.", "onboarding.profile.note": "Über mich", "onboarding.profile.note_hint": "Du kannst andere @Profile erwähnen oder #Hashtags verwenden …", "onboarding.profile.save_and_continue": "Speichern und fortfahren", @@ -551,16 +569,16 @@ "onboarding.start.title": "Du hast es geschafft!", "onboarding.steps.follow_people.body": "Interessanten Profilen zu folgen ist das, was Mastodon ausmacht.", "onboarding.steps.follow_people.title": "Personalisiere deine Startseite", - "onboarding.steps.publish_status.body": "Begrüße die Welt mit Text, Fotos, Videos oder Umfragen {emoji}", + "onboarding.steps.publish_status.body": "Begrüße die Welt mit Text, Fotos, Videos oder Umfragen. {emoji}", "onboarding.steps.publish_status.title": "Erstelle deinen ersten Beitrag", "onboarding.steps.setup_profile.body": "Mit einem vollständigen Profil interagieren andere eher mit dir.", "onboarding.steps.setup_profile.title": "Personalisiere dein Profil", - "onboarding.steps.share_profile.body": "Lass deine Freund*innen wissen, wie sie dich auf Mastodon finden können", + "onboarding.steps.share_profile.body": "Lass deine Freund*innen wissen, wie sie dich auf Mastodon finden können.", "onboarding.steps.share_profile.title": "Teile dein Mastodon-Profil", "onboarding.tips.2fa": "Wusstest du schon? Du kannst die Sicherheit deines Kontos erhöhen, indem du die Zwei-Faktor-Authentisierung in deinen Kontoeinstellungen aktivierst. Dafür ist keine Telefonnummer notwendig und es funktioniert jede beliebige TOTP-App!", "onboarding.tips.accounts_from_other_servers": "Wusstest du schon? Da Mastodon dezentralisiert ist, werden einige Profile, denen du begegnest, auf anderen Servern als deinem bereitgestellt. Und trotzdem kannst du uneingeschränkt mit ihnen interagieren! Der Servername befindet sich in der zweiten Hälfte ihres Profilnamens!", "onboarding.tips.migration": "Wusstest du schon? Wenn du das Gefühl hast, dass {domain} in Zukunft nicht die richtige Serverwahl für dich ist, kannst du auf einen anderen Mastodon-Server umziehen, ohne deine Follower zu verlieren. Du kannst sogar deinen eigenen Server betreiben!", - "onboarding.tips.verification": "Wusstest du schon? Du kannst dein Konto verifizieren, indem du auf deiner Website auf dein Mastodon-Profil verlinkst und den Link deiner Website zu deinem Profil hinzufügst. Keine Gebühren oder Dokumente erforderlich!", + "onboarding.tips.verification": "Wusstest du schon? Du kannst dein Konto verifizieren, indem du auf deiner Website auf dein Mastodon-Profil verlinkst und den Link deiner Website zu deinem Profil hinzufügst. Völlig kostenlos und ohne Dokumente einsenden zu müssen!", "password_confirmation.exceeds_maxlength": "Passwortbestätigung überschreitet die maximal erlaubte Zeichenanzahl", "password_confirmation.mismatching": "Passwortbestätigung stimmt nicht überein", "picture_in_picture.restore": "Zurücksetzen", @@ -590,12 +608,6 @@ "refresh": "Aktualisieren", "regeneration_indicator.label": "Wird geladen …", "regeneration_indicator.sublabel": "Deine Startseite wird gerade vorbereitet!", - "relationship_severance_notification.purged_data": "von Administrator*innen entfernt", - "relationship_severance_notification.relationships": "{count, plural, one {# Beziehung} other {# Beziehungen}}", - "relationship_severance_notification.types.account_suspension": "Konto wurde gesperrt", - "relationship_severance_notification.types.domain_block": "Domain wurde gesperrt", - "relationship_severance_notification.types.user_domain_block": "Du hast diese Domain blockiert", - "relationship_severance_notification.view": "Anzeigen", "relative_time.days": "{number} T.", "relative_time.full.days": "vor {number, plural, one {# Tag} other {# Tagen}}", "relative_time.full.hours": "vor {number, plural, one {# Stunde} other {# Stunden}}", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index 228a16def2..6c24d5a261 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -89,6 +89,14 @@ "announcement.announcement": "Announcement", "attachments_list.unprocessed": "(unprocessed)", "audio.hide": "Hide audio", + "block_modal.remote_users_caveat": "We will ask the server {domain} to respect your decision. However, compliance is not guaranteed since some servers may handle blocks differently. Public posts may still be visible to non-logged-in users.", + "block_modal.show_less": "Show less", + "block_modal.show_more": "Show more", + "block_modal.they_cant_mention": "They can't mention or follow you.", + "block_modal.they_cant_see_posts": "They can't see your posts and you won't see theirs.", + "block_modal.they_will_know": "They can see that they're blocked.", + "block_modal.title": "Block user?", + "block_modal.you_wont_see_mentions": "You won't see posts that mention them.", "boost_modal.combo": "You can press {combo} to skip this next time", "bundle_column_error.copy_stacktrace": "Copy error report", "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.", @@ -169,6 +177,7 @@ "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", "confirmations.discard_edit_media.confirm": "Discard", "confirmations.discard_edit_media.message": "You have unsaved changes to the media description or preview, discard them anyway?", + "confirmations.domain_block.confirm": "Block server", "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.", "confirmations.edit.confirm": "Edit", "confirmations.edit.message": "Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?", @@ -200,6 +209,27 @@ "dismissable_banner.explore_statuses": "These are posts from across the social web that are gaining traction today. Newer posts with more boosts and favourites are ranked higher.", "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralised network right now.", "dismissable_banner.public_timeline": "These are the most recent public posts from people on the social web that people on {domain} follow.", + "domain_block_modal.block": "Block server", + "domain_block_modal.block_account_instead": "Block @{name} instead", + "domain_block_modal.they_can_interact_with_old_posts": "People from this server can interact with your old posts.", + "domain_block_modal.they_cant_follow": "Nobody from this server can follow you.", + "domain_block_modal.they_wont_know": "They won't know they've been blocked.", + "domain_block_modal.title": "Block domain?", + "domain_block_modal.you_will_lose_followers": "All your followers from this server will be removed.", + "domain_block_modal.you_wont_see_posts": "You won't see posts or notifications from users on this server.", + "domain_pill.activitypub_lets_connect": "It lets you connect and interact with people not just on Mastodon, but across different social apps too.", + "domain_pill.activitypub_like_language": "ActivityPub is like the language Mastodon speaks with other social networks.", + "domain_pill.server": "Server", + "domain_pill.their_handle": "Their handle:", + "domain_pill.their_server": "Their digital home, where all of their posts live.", + "domain_pill.their_username": "Their unique identifier on their server. It’s possible to find users with the same username on different servers.", + "domain_pill.username": "Username", + "domain_pill.whats_in_a_handle": "What's in a handle?", + "domain_pill.who_they_are": "Since handles say who someone is and where they are, you can interact with people across the social web of .", + "domain_pill.who_you_are": "Because your handle says who you are and where you are, people can interact with you across the social web of .", + "domain_pill.your_handle": "Your handle:", + "domain_pill.your_server": "Your digital home, where all of your posts live. Don’t like this one? Transfer servers at any time and bring your followers, too.", + "domain_pill.your_username": "Your unique identifier on this server. It’s possible to find users with the same username on different servers.", "embed.instructions": "Embed this post on your website by copying the code below.", "embed.preview": "Here is what it will look like:", "emoji_button.activity": "Activity", @@ -236,6 +266,7 @@ "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.", "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.", "empty_column.mutes": "You haven't muted any users yet.", + "empty_column.notification_requests": "All clear! There is nothing here. When you receive new notifications, they will appear here according to your settings.", "empty_column.notifications": "You don't have any notifications yet. When other people interact with you, you will see it here.", "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.", @@ -266,13 +297,22 @@ "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", + "filtered_notifications_banner.mentions": "{count, plural, one {mention} other {mentions}}", + "filtered_notifications_banner.pending_requests": "Notifications from {count, plural, =0 {no one} one {one person} other {# people}} you may know", + "filtered_notifications_banner.title": "Filtered notifications", "firehose.all": "All", "firehose.local": "This server", "firehose.remote": "Other servers", "follow_request.authorize": "Authorise", "follow_request.reject": "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.", + "follow_suggestions.curated_suggestion": "Staff pick", "follow_suggestions.dismiss": "Don't show again", + "follow_suggestions.hints.featured": "This profile has been hand-picked by the {domain} team.", + "follow_suggestions.hints.friends_of_friends": "This profile is popular among the people you follow.", + "follow_suggestions.hints.most_followed": "This profile is one of the most followed on {domain}.", + "follow_suggestions.hints.most_interactions": "This profile has been recently getting a lot of attention on {domain}.", + "follow_suggestions.hints.similar_to_recently_followed": "This profile is similar to the profiles you have most recently followed.", "follow_suggestions.personalized_suggestion": "Personalised suggestion", "follow_suggestions.popular_suggestion": "Popular suggestion", "follow_suggestions.view_all": "View all", @@ -388,6 +428,15 @@ "loading_indicator.label": "Loading…", "media_gallery.toggle_visible": "{number, plural, one {Hide image} other {Hide images}}", "moved_to_account_banner.text": "Your account {disabledAccount} is currently disabled because you moved to {movedToAccount}.", + "mute_modal.hide_from_notifications": "Hide from notifications", + "mute_modal.hide_options": "Hide options", + "mute_modal.indefinite": "Until I unmute them", + "mute_modal.show_options": "Show options", + "mute_modal.they_can_mention_and_follow": "They can mention and follow you, but you won't see them.", + "mute_modal.they_wont_know": "They won't know they've been muted.", + "mute_modal.title": "Mute user?", + "mute_modal.you_wont_see_mentions": "You won't see posts that mention them.", + "mute_modal.you_wont_see_posts": "They can still see your posts, but you won't see theirs.", "navigation_bar.about": "About", "navigation_bar.advanced_interface": "Open in advanced web interface", "navigation_bar.blocks": "Blocked users", @@ -423,14 +472,25 @@ "notification.own_poll": "Your poll has ended", "notification.poll": "A poll you have voted in has ended", "notification.reblog": "{name} boosted your status", + "notification.relationships_severance_event": "Lost connections with {name}", + "notification.relationships_severance_event.account_suspension": "An admin from {from} has suspended {target}, which means you can no longer receive updates from them or interact with them.", + "notification.relationships_severance_event.domain_block": "An admin from {from} has blocked {target}, including {followersCount} of your followers and {followingCount, plural, one {# account} other {# accounts}} you follow.", + "notification.relationships_severance_event.learn_more": "Learn more", + "notification.relationships_severance_event.user_domain_block": "You have blocked {target}, removing {followersCount} of your followers and {followingCount, plural, one {# account} other {# accounts}} you follow.", "notification.status": "{name} just posted", "notification.update": "{name} edited a post", + "notification_requests.accept": "Accept", + "notification_requests.dismiss": "Dismiss", + "notification_requests.notifications_from": "Notifications from {name}", + "notification_requests.title": "Filtered notifications", "notifications.clear": "Clear notifications", "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?", "notifications.column_settings.admin.report": "New reports:", "notifications.column_settings.admin.sign_up": "New sign-ups:", "notifications.column_settings.alert": "Desktop notifications", "notifications.column_settings.favourite": "Favourites:", + "notifications.column_settings.filter_bar.advanced": "Display all categories", + "notifications.column_settings.filter_bar.category": "Quick filter bar", "notifications.column_settings.follow": "New followers:", "notifications.column_settings.follow_request": "New follow requests:", "notifications.column_settings.mention": "Mentions:", @@ -456,6 +516,15 @@ "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.", + "notifications.policy.filter_new_accounts.hint": "Created within the past {days, plural, one {one day} other {# days}}", + "notifications.policy.filter_new_accounts_title": "New accounts", + "notifications.policy.filter_not_followers_hint": "Including people who have been following you fewer than {days, plural, one {one day} other {# days}}", + "notifications.policy.filter_not_followers_title": "People not following you", + "notifications.policy.filter_not_following_hint": "Until you manually approve them", + "notifications.policy.filter_not_following_title": "People you don't follow", + "notifications.policy.filter_private_mentions_hint": "Filtered unless it's in reply to your own mention or if you follow the sender", + "notifications.policy.filter_private_mentions_title": "Unsolicited private mentions", + "notifications.policy.title": "Filter out notifications from…", "notifications_permission_banner.enable": "Enable desktop notifications", "notifications_permission_banner.how_to_control": "To receive notifications when Mastodon isn't open, enable desktop notifications. You can control precisely which types of interactions generate desktop notifications through the {icon} button above once they're enabled.", "notifications_permission_banner.title": "Never miss a thing", @@ -632,9 +701,11 @@ "status.direct": "Privately mention @{name}", "status.direct_indicator": "Private mention", "status.edit": "Edit", + "status.edited": "Last edited {date}", "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}", "status.embed": "Embed", "status.favourite": "Favourite", + "status.favourites": "{count, plural, one {favorite} other {favorites}}", "status.filter": "Filter this post", "status.filtered": "Filtered", "status.hide": "Hide post", @@ -655,6 +726,7 @@ "status.reblog": "Boost", "status.reblog_private": "Boost with original visibility", "status.reblogged_by": "{name} boosted", + "status.reblogs": "{count, plural, one {boost} other {boosts}}", "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", diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 881ed19e0e..9d127b6b03 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -297,8 +297,8 @@ "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", + "filtered_notifications_banner.mentions": "{count, plural, one {mention} other {mentions}}", "filtered_notifications_banner.pending_requests": "Notifications from {count, plural, =0 {no one} one {one person} other {# people}} you may know", - "filtered_notifications_banner.private_mentions": "{count, plural, one {private mention} other {private mentions}}", "filtered_notifications_banner.title": "Filtered notifications", "firehose.all": "All", "firehose.local": "This server", @@ -308,6 +308,8 @@ "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.", "follow_suggestions.curated_suggestion": "Staff pick", "follow_suggestions.dismiss": "Don't show again", + "follow_suggestions.featured_longer": "Hand-picked by the {domain} team", + "follow_suggestions.friends_of_friends_longer": "Popular among people you follow", "follow_suggestions.hints.featured": "This profile has been hand-picked by the {domain} team.", "follow_suggestions.hints.friends_of_friends": "This profile is popular among the people you follow.", "follow_suggestions.hints.most_followed": "This profile is one of the most followed on {domain}.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "This profile is similar to the profiles you have most recently followed.", "follow_suggestions.personalized_suggestion": "Personalized suggestion", "follow_suggestions.popular_suggestion": "Popular suggestion", + "follow_suggestions.popular_suggestion_longer": "Popular on {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Similar to profiles you recently followed", "follow_suggestions.view_all": "View all", "follow_suggestions.who_to_follow": "Who to follow", "followed_tags": "Followed hashtags", @@ -469,6 +473,15 @@ "notification.follow": "{name} followed you", "notification.follow_request": "{name} has requested to follow you", "notification.mention": "{name} mentioned you", + "notification.moderation-warning.learn_more": "Learn more", + "notification.moderation_warning": "Your have received a moderation warning", + "notification.moderation_warning.action_delete_statuses": "Some of your posts have been removed.", + "notification.moderation_warning.action_disable": "Your account has been disabled.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Some of your posts have been marked as sensitive.", + "notification.moderation_warning.action_none": "Your account has received a moderation warning.", + "notification.moderation_warning.action_sensitive": "Your posts will be marked as sensitive from now on.", + "notification.moderation_warning.action_silence": "Your account has been limited.", + "notification.moderation_warning.action_suspend": "Your account has been suspended.", "notification.own_poll": "Your poll has ended", "notification.poll": "A poll you have voted in has ended", "notification.reblog": "{name} boosted your post", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index a9db8831e2..2d42b3e949 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -241,7 +241,7 @@ "emoji_button.nature": "Naturaleza", "emoji_button.not_found": "No se encontraron emojis coincidentes", "emoji_button.objects": "Objetos", - "emoji_button.people": "Gente", + "emoji_button.people": "Cuentas", "emoji_button.recent": "Usados frecuentemente", "emoji_button.search": "Buscar...", "emoji_button.search_results": "Resultados de búsqueda", @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "Usar una categoría existente o crear una nueva", "filter_modal.select_filter.title": "Filtrar este mensaje", "filter_modal.title.status": "Filtrar un mensaje", + "filtered_notifications_banner.mentions": "{count, plural, one {mención} other {menciones}}", "filtered_notifications_banner.pending_requests": "Notificaciones de {count, plural, =0 {nadie} one {una persona} other {# personas}} que podrías conocer", "filtered_notifications_banner.title": "Notificaciones filtradas", "firehose.all": "Todos", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "A pesar de que tu cuenta no es privada, el equipo de {domain} pensó que podrías querer revisar manualmente las solicitudes de seguimiento de estas cuentas.", "follow_suggestions.curated_suggestion": "Selección del equipo", "follow_suggestions.dismiss": "No mostrar de nuevo", + "follow_suggestions.featured_longer": "Seleccionada a mano por el equipo de {domain}", + "follow_suggestions.friends_of_friends_longer": "Populares entre las cuentas que seguís", "follow_suggestions.hints.featured": "Este perfil fue seleccionado a mano por el equipo de {domain}.", "follow_suggestions.hints.friends_of_friends": "Este perfil es popular entre las cuentas que seguís.", "follow_suggestions.hints.most_followed": "Este perfil es uno de los más seguidos en {domain}.", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Este perfil es similar a los que comenzaste a seguir.", "follow_suggestions.personalized_suggestion": "Sugerencia personalizada", "follow_suggestions.popular_suggestion": "Sugerencia popular", + "follow_suggestions.popular_suggestion_longer": "Popular en {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Similares a perfiles que comenzaste a seguir recientemente", "follow_suggestions.view_all": "Ver todo", "follow_suggestions.who_to_follow": "A quién seguir", "followed_tags": "Etiquetas seguidas", @@ -468,10 +473,23 @@ "notification.follow": "{name} te empezó a seguir", "notification.follow_request": "{name} solicitó seguirte", "notification.mention": "{name} te mencionó", + "notification.moderation-warning.learn_more": "Aprendé más", + "notification.moderation_warning": "Recibiste una advertencia de moderación", + "notification.moderation_warning.action_delete_statuses": "Se eliminaron algunos de tus mensajes.", + "notification.moderation_warning.action_disable": "Se deshabilitó tu cuenta.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Se marcaron como sensibles a algunos de tus mensajes.", + "notification.moderation_warning.action_none": "Tu cuenta recibió una advertencia de moderación.", + "notification.moderation_warning.action_sensitive": "A partir de ahora, tus mensajes serán marcados como sensibles.", + "notification.moderation_warning.action_silence": "Tu cuenta fue limitada.", + "notification.moderation_warning.action_suspend": "Tu cuenta fue suspendida.", "notification.own_poll": "Tu encuesta finalizó", "notification.poll": "Finalizó una encuesta en la que votaste", "notification.reblog": "{name} adhirió a tu mensaje", - "notification.severed_relationships": "Relaciones con {name} cortadas", + "notification.relationships_severance_event": "Conexiones perdidas con {name}", + "notification.relationships_severance_event.account_suspension": "Un administrador de {from} suspendió a {target}, lo que significa que ya no podés recibir actualizaciones de esa cuenta o interactuar con la misma.", + "notification.relationships_severance_event.domain_block": "Un administrador de {from} bloqueó a {target}, incluyendo {followersCount} de tus seguidores y {followingCount, plural, one {# cuenta} other {# cuentas}} que seguís.", + "notification.relationships_severance_event.learn_more": "Aprendé más", + "notification.relationships_severance_event.user_domain_block": "Bloqueaste a {target}, eliminando {followersCount} de tus seguidores y {followingCount, plural, one {# cuenta} other {# cuentas}} que seguís.", "notification.status": "{name} acaba de enviar un mensaje", "notification.update": "{name} editó un mensaje", "notification_requests.accept": "Aceptar", @@ -590,12 +608,6 @@ "refresh": "Refrescar", "regeneration_indicator.label": "Cargando…", "regeneration_indicator.sublabel": "¡Se está preparando tu línea temporal principal!", - "relationship_severance_notification.purged_data": "purgada por administradores", - "relationship_severance_notification.relationships": "{count, plural, one {# relación} other {# relaciones}}", - "relationship_severance_notification.types.account_suspension": "La cuenta fue suspendida", - "relationship_severance_notification.types.domain_block": "El dominio fue suspendido", - "relationship_severance_notification.types.user_domain_block": "Bloqueaste este dominio", - "relationship_severance_notification.view": "Ver", "relative_time.days": "{number}d", "relative_time.full.days": "{number, plural,one {hace # día} other {hace # días}}", "relative_time.full.hours": "{number, plural,one {hace # hora} other {hace # horas}}", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index dea71a9351..b529f48ebe 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "Usar una categoría existente o crear una nueva", "filter_modal.select_filter.title": "Filtrar esta publicación", "filter_modal.title.status": "Filtrar una publicación", + "filtered_notifications_banner.mentions": "{count, plural, one {mención} other {menciones}}", "filtered_notifications_banner.pending_requests": "Notificaciones de {count, plural, =0 {nadie} one {una persona} other {# personas}} que podrías conocer", "filtered_notifications_banner.title": "Notificaciones filtradas", "firehose.all": "Todas", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "A pesar de que tu cuenta no es privada, el personal de {domain} ha pensado que quizás deberías revisar manualmente las solicitudes de seguimiento de estas cuentas.", "follow_suggestions.curated_suggestion": "Recomendaciones del equipo", "follow_suggestions.dismiss": "No mostrar de nuevo", + "follow_suggestions.featured_longer": "Escogidos por el equipo de {domain}", + "follow_suggestions.friends_of_friends_longer": "Populares entre las personas a las que sigues", "follow_suggestions.hints.featured": "Este perfil ha sido seleccionado a mano por el equipo de {domain}.", "follow_suggestions.hints.friends_of_friends": "Este perfil es popular entre las personas que sigues.", "follow_suggestions.hints.most_followed": "Este perfil es uno de los más seguidos en {domain}.", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Este perfil es similar a los perfiles que has seguido recientemente.", "follow_suggestions.personalized_suggestion": "Sugerencia personalizada", "follow_suggestions.popular_suggestion": "Sugerencia popular", + "follow_suggestions.popular_suggestion_longer": "Populares en {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Similares a los perfiles que has seguido recientemente", "follow_suggestions.view_all": "Ver todo", "follow_suggestions.who_to_follow": "Recomendamos seguir", "followed_tags": "Hashtags seguidos", @@ -468,10 +473,23 @@ "notification.follow": "{name} te empezó a seguir", "notification.follow_request": "{name} ha solicitado seguirte", "notification.mention": "{name} te ha mencionado", + "notification.moderation-warning.learn_more": "Saber más", + "notification.moderation_warning": "Has recibido una advertencia de moderación", + "notification.moderation_warning.action_delete_statuses": "Se han eliminado algunas de tus publicaciones.", + "notification.moderation_warning.action_disable": "Se ha desactivado su cuenta.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Se han marcado como sensibles algunas de tus publicaciones.", + "notification.moderation_warning.action_none": "Tu cuenta ha recibido un aviso de moderación.", + "notification.moderation_warning.action_sensitive": "De ahora en adelante, todas tus publicaciones se marcarán como sensibles.", + "notification.moderation_warning.action_silence": "Se ha limitado tu cuenta.", + "notification.moderation_warning.action_suspend": "Se ha suspendido tu cuenta.", "notification.own_poll": "Tu encuesta ha terminado", "notification.poll": "Una encuesta en la que has votado ha terminado", "notification.reblog": "{name} ha retooteado tu estado", - "notification.severed_relationships": "Se han cortado las relaciones con {name}", + "notification.relationships_severance_event": "Conexiones perdidas con {name}", + "notification.relationships_severance_event.account_suspension": "Un administrador de {from} ha suspendido {target}, lo que significa que ya no puedes recibir actualizaciones de sus cuentas o interactuar con ellas.", + "notification.relationships_severance_event.domain_block": "Un administrador de {from} ha bloqueado {target}, incluyendo {followersCount} de tus seguidores y {followingCount, plural, one {# cuenta} other {# cuentas}} que sigues.", + "notification.relationships_severance_event.learn_more": "Más información", + "notification.relationships_severance_event.user_domain_block": "Has bloqueado {target}, eliminando {followersCount} de tus seguidores y {followingCount, plural, one {# cuenta} other {# cuentas}} que sigues.", "notification.status": "{name} acaba de publicar", "notification.update": "{name} editó una publicación", "notification_requests.accept": "Aceptar", @@ -590,12 +608,6 @@ "refresh": "Actualizar", "regeneration_indicator.label": "Cargando…", "regeneration_indicator.sublabel": "¡Tu historia de inicio se está preparando!", - "relationship_severance_notification.purged_data": "purgado por administradores", - "relationship_severance_notification.relationships": "{count, plural, one {# relación} other {# relaciones}}", - "relationship_severance_notification.types.account_suspension": "La cuenta ha sido suspendida", - "relationship_severance_notification.types.domain_block": "El dominio ha sido suspendido", - "relationship_severance_notification.types.user_domain_block": "Bloqueaste este dominio", - "relationship_severance_notification.view": "Ver", "relative_time.days": "{number} d", "relative_time.full.days": "{number, plural, one {# día} other {# días hace}}", "relative_time.full.hours": "{number, plural, one {# hora} other {# horas}} hace", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 64bcf7d9c0..ed01a33375 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "Usar una categoría existente o crear una nueva", "filter_modal.select_filter.title": "Filtrar esta publicación", "filter_modal.title.status": "Filtrar una publicación", + "filtered_notifications_banner.mentions": "{count, plural, one {mención} other {menciones}}", "filtered_notifications_banner.pending_requests": "Notificaciones de {count, plural, =0 {nadie} one {una persona} other {# personas}} que podrías conocer", "filtered_notifications_banner.title": "Notificaciones filtradas", "firehose.all": "Todas", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "A pesar de que tu cuenta no es privada, el personal de {domain} ha pensado que quizás deberías revisar manualmente las solicitudes de seguimiento de estas cuentas.", "follow_suggestions.curated_suggestion": "Recomendaciones del equipo", "follow_suggestions.dismiss": "No mostrar de nuevo", + "follow_suggestions.featured_longer": "Escogidos por el equipo de {domain}", + "follow_suggestions.friends_of_friends_longer": "Populares entre las personas a las que sigues", "follow_suggestions.hints.featured": "Este perfil ha sido elegido a mano por el equipo de {domain}.", "follow_suggestions.hints.friends_of_friends": "Este perfil es popular entre las personas que sigues.", "follow_suggestions.hints.most_followed": "Este perfil es uno de los más seguidos en {domain}.", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Este perfil es similar a los perfiles que has seguido recientemente.", "follow_suggestions.personalized_suggestion": "Sugerencia personalizada", "follow_suggestions.popular_suggestion": "Sugerencia popular", + "follow_suggestions.popular_suggestion_longer": "Populares en {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Similares a los perfiles que has seguido recientemente", "follow_suggestions.view_all": "Ver todo", "follow_suggestions.who_to_follow": "A quién seguir", "followed_tags": "Etiquetas seguidas", @@ -468,10 +473,23 @@ "notification.follow": "{name} te empezó a seguir", "notification.follow_request": "{name} ha solicitado seguirte", "notification.mention": "{name} te ha mencionado", + "notification.moderation-warning.learn_more": "Saber más", + "notification.moderation_warning": "Has recibido una advertencia de moderación", + "notification.moderation_warning.action_delete_statuses": "Se han eliminado algunas de tus publicaciones.", + "notification.moderation_warning.action_disable": "Se ha desactivado su cuenta.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Se han marcado como sensibles algunas de tus publicaciones.", + "notification.moderation_warning.action_none": "Tu cuenta ha recibido un aviso de moderación.", + "notification.moderation_warning.action_sensitive": "De ahora en adelante, todas tus publicaciones se marcarán como sensibles.", + "notification.moderation_warning.action_silence": "Se ha limitado tu cuenta.", + "notification.moderation_warning.action_suspend": "Se ha suspendido tu cuenta.", "notification.own_poll": "Tu encuesta ha terminado", "notification.poll": "Una encuesta en la que has votado ha terminado", "notification.reblog": "{name} ha impulsado tu publicación", - "notification.severed_relationships": "Se han cortado las relaciones con {name}", + "notification.relationships_severance_event": "Conexiones perdidas con {name}", + "notification.relationships_severance_event.account_suspension": "Un administrador de {from} ha suspendido {target}, lo que significa que ya no puedes recibir actualizaciones de sus cuentas o interactuar con ellas.", + "notification.relationships_severance_event.domain_block": "Un administrador de {from} ha bloqueado {target}, incluyendo {followersCount} de tus seguidores y {followingCount, plural, one {# cuenta} other {# cuentas}} que sigues.", + "notification.relationships_severance_event.learn_more": "Más información", + "notification.relationships_severance_event.user_domain_block": "Has bloqueado {target}, eliminando {followersCount} de tus seguidores y {followingCount, plural, one {# cuenta} other {# cuentas}} que sigues.", "notification.status": "{name} acaba de publicar", "notification.update": "{name} editó una publicación", "notification_requests.accept": "Aceptar", @@ -590,12 +608,6 @@ "refresh": "Actualizar", "regeneration_indicator.label": "Cargando…", "regeneration_indicator.sublabel": "¡Tu historia de inicio se está preparando!", - "relationship_severance_notification.purged_data": "purgado por administradores", - "relationship_severance_notification.relationships": "{count, plural, one {# relación} other {# relaciones}}", - "relationship_severance_notification.types.account_suspension": "La cuenta ha sido suspendida", - "relationship_severance_notification.types.domain_block": "El dominio ha sido suspendido", - "relationship_severance_notification.types.user_domain_block": "Bloqueaste este dominio", - "relationship_severance_notification.view": "Ver", "relative_time.days": "{number} d", "relative_time.full.days": "hace {number, plural, one {# día} other {# días}}", "relative_time.full.hours": "hace {number, plural, one {# hora} other {# horas}}", @@ -643,7 +655,7 @@ "report.statuses.subtitle": "Selecciona todos los que correspondan", "report.statuses.title": "¿Hay alguna publicación que respalde este informe?", "report.submit": "Enviar", - "report.target": "Reportando", + "report.target": "Reportando {target}", "report.thanks.take_action": "Aquí están tus opciones para controlar lo que ves en Mastodon:", "report.thanks.take_action_actionable": "Mientras revisamos esto, puedes tomar medidas contra @{name}:", "report.thanks.title": "¿No quieres esto?", diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json index f617ced6ea..b2759d66c6 100644 --- a/app/javascript/mastodon/locales/et.json +++ b/app/javascript/mastodon/locales/et.json @@ -92,7 +92,11 @@ "block_modal.remote_users_caveat": "Serverile {domain} edastatakse palve otsust järgida. Ometi pole see tagatud, kuna mõned serverid võivad blokeeringuid käsitleda omal moel. Avalikud postitused võivad tuvastamata kasutajatele endiselt näha olla.", "block_modal.show_less": "Kuva vähem", "block_modal.show_more": "Kuva rohkem", + "block_modal.they_cant_mention": "Ta ei saa mainida sind ega jälgida.", + "block_modal.they_cant_see_posts": "Ta ei näe sinu postitusi ja sa ei näe tema omi.", + "block_modal.they_will_know": "Ta näeb, et ta on blokeeritud.", "block_modal.title": "Blokeeri kasutaja?", + "block_modal.you_wont_see_mentions": "Sa ei näe postitusi, mis mainivad teda.", "boost_modal.combo": "Vajutades {combo}, saab selle edaspidi vahele jätta", "bundle_column_error.copy_stacktrace": "Kopeeri veateade", "bundle_column_error.error.body": "Soovitud lehte ei õnnestunud esitada. See võib olla meie koodiviga või probleem brauseri ühilduvusega.", @@ -206,8 +210,26 @@ "dismissable_banner.explore_tags": "Need sildid siit ja teistes serveritest detsentraliseeritud võrgus koguvad tähelepanu just praegu selles serveris.", "dismissable_banner.public_timeline": "Need on kõige uuemad avalikud postitused inimestelt sotsiaalvõrgustikus, mida {domain} inimesed jälgivad.", "domain_block_modal.block": "Blokeeri server", + "domain_block_modal.block_account_instead": "Selle asemel blokeeri @{name}", + "domain_block_modal.they_can_interact_with_old_posts": "Inimesed sellest serverist saavad interakteeruda sinu vanade postitustega.", + "domain_block_modal.they_cant_follow": "Sellest serverist ei saa keegi sind jälgida.", + "domain_block_modal.they_wont_know": "Nad ei tea, et nad on blokeeritud.", + "domain_block_modal.title": "Blokeerida domeen?", + "domain_block_modal.you_will_lose_followers": "Kõik sinu sellest serverist pärit jälgijad eemaldatakse.", + "domain_block_modal.you_wont_see_posts": "Sa ei näe selle serveri kasutajate postitusi ega teavitusi.", + "domain_pill.activitypub_lets_connect": "See võimaldab sul ühenduda inimestega ja nendega suhelda mitte ainult Mastodonis, vaid ka teistes suhtlusrakendustes.", + "domain_pill.activitypub_like_language": "ActivityPub on nagu keel, mida Mastodon räägib teiste suhtlusvõrgustikega.", "domain_pill.server": "Server", + "domain_pill.their_handle": "Tema tunnus:", + "domain_pill.their_server": "Tema digitaalne kodu, kus kõik tema postitused on.", + "domain_pill.their_username": "Tema unikaalne tunnus tema serveris. On võimalik, et mingites teistes serverites on sama kasutajanimega kasutajaid.", "domain_pill.username": "Kasutajanimi", + "domain_pill.whats_in_a_handle": "Mis on tunnuses?", + "domain_pill.who_they_are": "Kuna tunnus ütleb, kes keegi on ja kus, saad suhelda inimestega üle .", + "domain_pill.who_you_are": "Kuna tunnus ütleb, kes sa oled ja kus, saavad inimesed sinuga suhelda üle .", + "domain_pill.your_handle": "Sinu tunnus:", + "domain_pill.your_server": "Sinu digitaalne kodu, kus on kõik sinu postitused. Sulle ei meeldi see? Vaheta mistahes ajal serverit ja võta jälgijad ka.", + "domain_pill.your_username": "Sinu unikaalne identifikaator siin serveris. On võimalik, et leiad teistes serverites samasuguse kasutajanimega kasutajaid.", "embed.instructions": "Lisa see postitus oma veebilehele, kopeerides alloleva koodi.", "embed.preview": "Nii näeb see välja:", "emoji_button.activity": "Tegevus", @@ -244,6 +266,7 @@ "empty_column.list": "Siin loetelus pole veel midagi. Kui loetelu liikmed teevad uusi postitusi, näed neid siin.", "empty_column.lists": "Pole veel ühtegi nimekirja. Kui lood mõne, näed neid siin.", "empty_column.mutes": "Sa pole veel ühtegi kasutajat vaigistanud.", + "empty_column.notification_requests": "Kõik tühi! Siin pole mitte midagi. Kui saad uusi teavitusi, ilmuvad need siin vastavalt sinu seadistustele.", "empty_column.notifications": "Ei ole veel teateid. Kui keegi suhtleb sinuga, näed seda siin.", "empty_column.public": "Siin pole midagi! Kirjuta midagi avalikku või jälgi ise kasutajaid täitmaks seda ruumi", "error.unexpected_crash.explanation": "Meie poolse probleemi või veebilehitseja ühilduvusprobleemi tõttu ei suutnud me seda lehekülge korrektselt näidata.", @@ -275,6 +298,7 @@ "filter_modal.select_filter.title": "Filtreeri seda postitust", "filter_modal.title.status": "Postituse filtreerimine", "filtered_notifications_banner.pending_requests": "Teateid {count, plural, =0 {mitte üheltki} one {ühelt} other {#}} inimeselt, keda võid teada", + "filtered_notifications_banner.title": "Filtreeritud teavitused", "firehose.all": "Kõik", "firehose.local": "See server", "firehose.remote": "Teised serverid", @@ -403,8 +427,15 @@ "loading_indicator.label": "Laadimine…", "media_gallery.toggle_visible": "{number, plural, one {Varja pilt} other {Varja pildid}}", "moved_to_account_banner.text": "Kontot {disabledAccount} ei ole praegu võimalik kasutada, sest kolisid kontole {movedToAccount}.", + "mute_modal.hide_from_notifications": "Peida teavituste hulgast", "mute_modal.hide_options": "Peida valikud", + "mute_modal.indefinite": "Kuni eemaldan neilt vaigistuse", "mute_modal.show_options": "Kuva valikud", + "mute_modal.they_can_mention_and_follow": "Ta saab sind mainida ja sind jälgida, kuid sa ei näe teda.", + "mute_modal.they_wont_know": "Ta ei tea, et ta on vaigistatud.", + "mute_modal.title": "Vaigistada kasutaja?", + "mute_modal.you_wont_see_mentions": "Sa ei näe postitusi, mis teda mainivad.", + "mute_modal.you_wont_see_posts": "Ta näeb jätkuvalt sinu postitusi, kuid sa ei näe tema omi.", "navigation_bar.about": "Teave", "navigation_bar.advanced_interface": "Ava kohandatud veebiliides", "navigation_bar.blocks": "Blokeeritud kasutajad", @@ -440,16 +471,25 @@ "notification.own_poll": "Su küsitlus on lõppenud", "notification.poll": "Küsitlus, milles osalesid, on lõppenud", "notification.reblog": "{name} jagas edasi postitust", + "notification.relationships_severance_event": "Kadunud ühendus kasutajaga {name}", + "notification.relationships_severance_event.account_suspension": "{from} admin on kustutanud {target}, mis tähendab, et sa ei saa enam neilt uuendusi või suhelda nendega.", + "notification.relationships_severance_event.domain_block": "{from} admin on blokeerinud {target}, sealhulgas {followersCount} sinu jälgijat ja {followingCount, plural, one {# konto} other {# kontot}}, mida jälgid.", + "notification.relationships_severance_event.learn_more": "Saa rohkem teada", + "notification.relationships_severance_event.user_domain_block": "Blokeerisid {target}, eemaldades oma jälgijate hulgast {followersCount} ja jälgitavate hulgast {followingCount, plural, one {# konto} other {# kontot}}.", "notification.status": "{name} just postitas", "notification.update": "{name} muutis postitust", "notification_requests.accept": "Nõus", "notification_requests.dismiss": "Hülga", + "notification_requests.notifications_from": "Teavitus kasutajalt {name}", + "notification_requests.title": "Filtreeritud teavitused", "notifications.clear": "Puhasta teated", "notifications.clear_confirmation": "Oled kindel, et soovid püsivalt kõik oma teated eemaldada?", "notifications.column_settings.admin.report": "Uued teavitused:", "notifications.column_settings.admin.sign_up": "Uued kasutajad:", "notifications.column_settings.alert": "Töölauateated", "notifications.column_settings.favourite": "Lemmikud:", + "notifications.column_settings.filter_bar.advanced": "Näita kõiki kategooriaid", + "notifications.column_settings.filter_bar.category": "Kiirfiltri riba", "notifications.column_settings.follow": "Uued jälgijad:", "notifications.column_settings.follow_request": "Uued jälgimistaotlused:", "notifications.column_settings.mention": "Mainimised:", @@ -475,7 +515,15 @@ "notifications.permission_denied": "Töölauamärguanded pole saadaval, kuna eelnevalt keelduti lehitsejale teavituste luba andmast", "notifications.permission_denied_alert": "Töölaua märguandeid ei saa lubada, kuna brauseri luba on varem keeldutud", "notifications.permission_required": "Töölaua märguanded ei ole saadaval, kuna vajalik luba pole antud.", + "notifications.policy.filter_new_accounts.hint": "Loodud viimase {days, plural, one {ühe päeva} other {# päeva}} jooksul", "notifications.policy.filter_new_accounts_title": "Uued kontod", + "notifications.policy.filter_not_followers_hint": "Kaasates kasutajad, kes on sind jälginud vähem kui {days, plural, one {ühe päeva} other {# päeva}}", + "notifications.policy.filter_not_followers_title": "Sind mittejälgivad kasutajad", + "notifications.policy.filter_not_following_hint": "Kuni sa nad käsitsi kinnitad", + "notifications.policy.filter_not_following_title": "Inimesed, keda sa ei jälgi", + "notifications.policy.filter_private_mentions_hint": "Filtreeritud, kui see pole vastus sinupoolt mainimisele või kui jälgid saatjat", + "notifications.policy.filter_private_mentions_title": "Soovimatud privaatsed mainimised", + "notifications.policy.title": "Filtreeri välja teavitused kohast…", "notifications_permission_banner.enable": "Luba töölaua märguanded", "notifications_permission_banner.how_to_control": "Et saada teateid, ajal mil Mastodon pole avatud, luba töölauamärguanded. Saad täpselt määrata, mis tüüpi tegevused tekitavad märguandeid, kasutates peale teadaannete sisse lülitamist üleval olevat nuppu {icon}.", "notifications_permission_banner.title": "Ära jää millestki ilma", @@ -652,9 +700,11 @@ "status.direct": "Maini privaatselt @{name}", "status.direct_indicator": "Privaatne mainimine", "status.edit": "Muuda", + "status.edited": "Viimati muudetud {date}", "status.edited_x_times": "Muudetud {count, plural, one{{count} kord} other {{count} korda}}", "status.embed": "Manustamine", "status.favourite": "Lemmik", + "status.favourites": "{count, plural, one {lemmik} other {lemmikud}}", "status.filter": "Filtreeri seda postitust", "status.filtered": "Filtreeritud", "status.hide": "Peida postitus", @@ -675,6 +725,7 @@ "status.reblog": "Jaga", "status.reblog_private": "Jaga algse nähtavusega", "status.reblogged_by": "{name} jagas", + "status.reblogs": "{count, plural, one {jagamine} other {jagamist}}", "status.reblogs.empty": "Keegi pole seda postitust veel jaganud. Kui keegi seda teeb, näeb seda siin.", "status.redraft": "Kustuta & alga uuesti", "status.remove_bookmark": "Eemalda järjehoidja", diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json index 8c3c5f9845..9fae074878 100644 --- a/app/javascript/mastodon/locales/eu.json +++ b/app/javascript/mastodon/locales/eu.json @@ -148,7 +148,7 @@ "compose.published.open": "Ireki", "compose.saved.body": "Argitalpena gorde da.", "compose_form.direct_message_warning_learn_more": "Ikasi gehiago", - "compose_form.encryption_warning": "Mastodoneko bidalketak ez daude muturretik muturrera enkriptatuta. Ez partekatu informazio sentikorrik Mastodonen.", + "compose_form.encryption_warning": "Mastodon-go bidalketak ez daude muturretik muturrera enkriptatuta. Ez partekatu informazio sentikorrik Mastodonen.", "compose_form.hashtag_warning": "Tut hau ez da inolako traolatan zerrendatuko, ez baita publikoa. Tut publikoak soilik traolen bitartez bila daitezke.", "compose_form.lock_disclaimer": "Zure kontua ez dago {locked}. Edonork jarraitu zaitzake zure jarraitzaileentzako soilik diren bidalketak ikusteko.", "compose_form.lock_disclaimer.lock": "giltzapetuta", @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "Hautatu lehendik dagoen kategoria bat edo sortu berria", "filter_modal.select_filter.title": "Iragazi bidalketa hau", "filter_modal.title.status": "Iragazi bidalketa bat", + "filtered_notifications_banner.mentions": "{count, plural, one {aipamen} other {aipamen}}", "filtered_notifications_banner.pending_requests": "Ezagutu {count, plural, =0 {dezakezun inoren} one {dezakezun pertsona baten} other {ditzakezun # pertsonen}} jakinarazpenak", "filtered_notifications_banner.title": "Iragazitako jakinarazpenak", "firehose.all": "Guztiak", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "Zure kontua blokeatuta ez badago ere, {domain} domeinuko arduradunek uste dute kontu hauetako jarraipen eskaerak agian eskuz begiratu nahiko dituzula.", "follow_suggestions.curated_suggestion": "Domeinuaren iradokizuna", "follow_suggestions.dismiss": "Ez erakutsi berriro", + "follow_suggestions.featured_longer": "{domain} domeinuko taldeak hautaturikoak", + "follow_suggestions.friends_of_friends_longer": "Jarraitzen duzun jendearen artean ezagunak direnak", "follow_suggestions.hints.featured": "Profil hau {domain} domeinuko taldeak eskuz aukeratu du.", "follow_suggestions.hints.friends_of_friends": "Profil hau ezaguna da jarraitzen duzun jendearen artean.", "follow_suggestions.hints.most_followed": "Profil hau {domain} domeinuan gehien jarraitzen den profiletako bat da.", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Profil hau duela gutxi jarraitu dituzun profil askoren antzekoa da.", "follow_suggestions.personalized_suggestion": "Iradokizun pertsonalizatua", "follow_suggestions.popular_suggestion": "Iradokizun ezaguna", + "follow_suggestions.popular_suggestion_longer": "{domain} domeinuan ezagunak direnak", + "follow_suggestions.similar_to_recently_followed_longer": "Berriki jarraitu dituzun profilen antzekoa", "follow_suggestions.view_all": "Ikusi denak", "follow_suggestions.who_to_follow": "Zein jarraitu", "followed_tags": "Jarraitutako traolak", @@ -345,7 +350,7 @@ "home.column_settings.show_reblogs": "Erakutsi bultzadak", "home.column_settings.show_replies": "Erakutsi erantzunak", "home.hide_announcements": "Ezkutatu iragarpenak", - "home.pending_critical_update.body": "Eguneratu zure Mastodoneko zerbitzaria leheinbailehen!", + "home.pending_critical_update.body": "Eguneratu zure Mastodon-go zerbitzaria leheinbailehen!", "home.pending_critical_update.link": "Ikusi eguneraketak", "home.pending_critical_update.title": "Segurtasun eguneraketa kritikoa eskuragarri!", "home.show_announcements": "Erakutsi iragarpenak", @@ -408,7 +413,7 @@ "lightbox.previous": "Aurrekoa", "limited_account_hint.action": "Erakutsi profila hala ere", "limited_account_hint.title": "Profil hau ezkutatu egin dute {domain} zerbitzariko moderatzaileek.", - "link_preview.author": "{name}(r)en eskutik", + "link_preview.author": "Egilea: {name}", "lists.account.add": "Gehitu zerrendara", "lists.account.remove": "Kendu zerrendatik", "lists.delete": "Ezabatu zerrenda", @@ -468,10 +473,21 @@ "notification.follow": "{name}(e)k jarraitzen dizu", "notification.follow_request": "{name}(e)k zu jarraitzeko eskaera egin du", "notification.mention": "{name}(e)k aipatu zaitu", + "notification.moderation-warning.learn_more": "Informazio gehiago", + "notification.moderation_warning": "Moderazio-abisu bat jaso duzu", + "notification.moderation_warning.action_delete_statuses": "Argitalpen batzuk kendu dira.", + "notification.moderation_warning.action_disable": "Zure kontua desaktibatua izan da.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Argitalpen batzuk hunkigarri gisa ezarri dira.", + "notification.moderation_warning.action_none": "Kontuak moderazio-abisu bat jaso du.", + "notification.moderation_warning.action_sensitive": "Argitalpenak hunkigarri gisa markatuko dira hemendik aurrera.", + "notification.moderation_warning.action_silence": "Kontua murriztu egin da.", + "notification.moderation_warning.action_suspend": "Kontua itxi da.", "notification.own_poll": "Zure inkesta amaitu da", "notification.poll": "Zuk erantzun duzun inkesta bat bukatu da", "notification.reblog": "{name}(e)k bultzada eman dio zure bidalketari", - "notification.severed_relationships": "{name} erabiltzailearekin zenuen erlazioa galdu da", + "notification.relationships_severance_event": "{name} erabiltzailearekin galdutako konexioak", + "notification.relationships_severance_event.account_suspension": "{from} zerbitzariko administratzaile batek {target} bertan behera utzi du, hau da, ezin izango dituzu jaso hango eguneratzerik edo hangoekin elkarreragin.", + "notification.relationships_severance_event.learn_more": "Informazio gehiago", "notification.status": "{name} erabiltzaileak bidalketa egin berri du", "notification.update": "{name} erabiltzaileak bidalketa bat editatu du", "notification_requests.accept": "Onartu", @@ -546,7 +562,7 @@ "onboarding.share.message": "{username} naiz #Mastodon-en! Jarrai nazazu hemen: {url}", "onboarding.share.next_steps": "Hurrengo urrats posibleak:", "onboarding.share.title": "Partekatu zure profila", - "onboarding.start.lead": "Mastodonen parte zara orain, bakarra eta deszentralizatua den sare-sozialaren plataforma, non zuk, eta ez algoritmo batek, zeure esperientzia pertsonaliza dezakezun. Igaro ezazu muga soziala:", + "onboarding.start.lead": "Mastodon-en parte zara orain, bakarra eta deszentralizatua den sare sozialaren plataforma, non zuk, eta ez algoritmo batek, zeure esperientzia pertsonaliza dezakezun. Igaro ezazu muga soziala:", "onboarding.start.skip": "Urrats guztiak saltatu nahi dituzu?", "onboarding.start.title": "Lortu duzu!", "onboarding.steps.follow_people.body": "Zure jarioa zuk pertsonalizatzen duzu. Bete dezagun jende interesgarriaz.", @@ -560,7 +576,7 @@ "onboarding.tips.2fa": "Bazenekien? Zure kontua babes dezakezu, bi faktoreko autentifikazioa zure kontuko ezarpenetan ezarriaz. Edozein TOTP aplikaziorekin dabil, ez da telefono-zenbakirik behar!", "onboarding.tips.accounts_from_other_servers": "Badakizu? Mastodon deszentralizatua denez, beste zerbitzarietako profilak topatuko dituzu. Eta, hala ere, arazorik gabe jardun dezakezu haiekin! Haien zerbitzaria erabiltzaile-izenaren bigarren erdian dago!", "onboarding.tips.migration": "Bazenekien? Uste baduzu {domain} ez dela aukera on bat zuretzako etorkizunari begira, beste Mastodon zerbitzari batera alda zaitezke, zure jarraitzaileak galdu gabe. Zure zerbitzaria propioa ere ostata dezakezu!", - "onboarding.tips.verification": "Bazenekien? Zure kontua egiazta dezakezu zure webgunean zure Mastodoneko profilaren esteka jarriz, eta profilean webgunea gehituz. Ordainketa edo dokumenturik gabe!", + "onboarding.tips.verification": "Bazenekien? Zure kontua egiazta dezakezu zure webgunean zure Mastodon-go profilaren esteka jarriz, eta profilean webgunea gehituz. Ordainketa edo dokumenturik gabe!", "password_confirmation.exceeds_maxlength": "Pasahitzaren berrespenak pasahitzaren gehienezko luzera gainditzen du", "password_confirmation.mismatching": "Pasahitzaren berrespena ez dator bat", "picture_in_picture.restore": "Leheneratu", @@ -590,12 +606,6 @@ "refresh": "Berritu", "regeneration_indicator.label": "Kargatzen…", "regeneration_indicator.sublabel": "Zure hasiera-jarioa prestatzen ari da!", - "relationship_severance_notification.purged_data": "administratzaileek kendua", - "relationship_severance_notification.relationships": "{count, plural, one {Erlazio #} other {# erlazio}}", - "relationship_severance_notification.types.account_suspension": "Kontua bertan behera utzi da", - "relationship_severance_notification.types.domain_block": "Domeinua bertan behera utzi da", - "relationship_severance_notification.types.user_domain_block": "Domeinu hau blokeatu duzu", - "relationship_severance_notification.view": "Ikusi", "relative_time.days": "{number}e", "relative_time.full.days": "Duela {number, plural, one {egun #} other {# egun}}", "relative_time.full.hours": "Duela {number, plural, one {ordu #} other {# ordu}}", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 16670d9ffe..bae714b1d2 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -297,7 +297,8 @@ "filter_modal.select_filter.subtitle": "Käytä olemassa olevaa luokkaa tai luo uusi", "filter_modal.select_filter.title": "Suodata tämä julkaisu", "filter_modal.title.status": "Suodata julkaisu", - "filtered_notifications_banner.pending_requests": "Ilmoitukset {count, plural, =0 {ei keltään} one {yhdeltä henkilöltä} other {# henkilöltä}}, jonka saatat tuntea", + "filtered_notifications_banner.mentions": "{count, plural, one {maininta} other {mainintaa}}", + "filtered_notifications_banner.pending_requests": "Ilmoituksia {count, plural, =0 {ei ole} one {1 henkilöltä} other {# henkilöltä}}, jonka saatat tuntea", "filtered_notifications_banner.title": "Suodatetut ilmoitukset", "firehose.all": "Kaikki", "firehose.local": "Tämä palvelin", @@ -305,15 +306,19 @@ "follow_request.authorize": "Valtuuta", "follow_request.reject": "Hylkää", "follow_requests.unlocked_explanation": "Vaikkei tiliäsi ole lukittu, palvelimen {domain} ylläpito on arvioinut, että saatat olla halukas tarkistamaan nämä seuraamispyynnöt erikseen.", - "follow_suggestions.curated_suggestion": "Ylläpidon valinta", + "follow_suggestions.curated_suggestion": "Ehdotus ylläpidolta", "follow_suggestions.dismiss": "Älä näytä uudelleen", + "follow_suggestions.featured_longer": "Valinnut käsin palvelimen {domain} tiimi", + "follow_suggestions.friends_of_friends_longer": "Suosittu seuraamiesi ihmisten keskuudessa", "follow_suggestions.hints.featured": "Tämän profiilin on valinnut palvelimen {domain} tiimi.", - "follow_suggestions.hints.friends_of_friends": "Tämä profiili on suosittu seuraamiesi henkilöiden parissa.", - "follow_suggestions.hints.most_followed": "Tämä profiili on yksi seuratuimmista palvelimella {domain}.", + "follow_suggestions.hints.friends_of_friends": "Seuraamasi käyttäjät suosivat tätä profiilia.", + "follow_suggestions.hints.most_followed": "Tämä profiili on palvelimen {domain} seuratuimpia.", "follow_suggestions.hints.most_interactions": "Tämä profiili on viime aikoina saanut paljon huomiota palvelimella {domain}.", "follow_suggestions.hints.similar_to_recently_followed": "Tämä profiili on samankaltainen kuin profiilit, joita olet viimeksi seurannut.", "follow_suggestions.personalized_suggestion": "Mukautettu ehdotus", "follow_suggestions.popular_suggestion": "Suosittu ehdotus", + "follow_suggestions.popular_suggestion_longer": "Suosittu palvelimella {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Samankaltainen kuin äskettäin seuraamasi profiilit", "follow_suggestions.view_all": "Näytä kaikki", "follow_suggestions.who_to_follow": "Ehdotuksia seurattavaksi", "followed_tags": "Seuratut aihetunnisteet", @@ -468,10 +473,23 @@ "notification.follow": "{name} seurasi sinua", "notification.follow_request": "{name} on pyytänyt lupaa saada seurata sinua", "notification.mention": "{name} mainitsi sinut", + "notification.moderation-warning.learn_more": "Lue lisää", + "notification.moderation_warning": "Olet saanut moderointivaroituksen", + "notification.moderation_warning.action_delete_statuses": "Jotkin julkaisusi on poistettu.", + "notification.moderation_warning.action_disable": "Tilisi on poistettu käytöstä.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Jotkin julkaisusi on merkitty arkaluonteisiksi.", + "notification.moderation_warning.action_none": "Tilisi on saanut moderointivaroituksen.", + "notification.moderation_warning.action_sensitive": "Tästä lähtien julkaisusi merkitään arkaluonteisiksi.", + "notification.moderation_warning.action_silence": "Tiliäsi on rajoitettu.", + "notification.moderation_warning.action_suspend": "Tilisi on jäädytetty.", "notification.own_poll": "Äänestyksesi on päättynyt", "notification.poll": "Kysely, johon osallistuit, on päättynyt", "notification.reblog": "{name} tehosti julkaisuasi", - "notification.severed_relationships": "Suhteet palvelimeen {name} katkenneet", + "notification.relationships_severance_event": "Menetettiin yhteydet palvelimeen {name}", + "notification.relationships_severance_event.account_suspension": "Palvelimen {from} ylläpitäjä on jäädyttänyt palvelimen {target} vuorovaikutuksen. Enää et voi siis vastaanottaa päivityksiä heiltä tai olla yhteyksissä heidän kanssaan.", + "notification.relationships_severance_event.domain_block": "Palvelimen {from} ylläpitäjä on estänyt palvelimen {target} vuorovaikutuksen – mukaan lukien {followersCount} seuraajistasi ja {followingCount, plural, one {# seuratuistasi} other {# seuratuistasi}}.", + "notification.relationships_severance_event.learn_more": "Lue lisää", + "notification.relationships_severance_event.user_domain_block": "Olet estänyt palvelimen {target}, mikä poisti {followersCount} seuraajistasi ja {followingCount, plural, one {# seuratuistasi} other {# seuratuistasi}}.", "notification.status": "{name} julkaisi juuri", "notification.update": "{name} muokkasi julkaisua", "notification_requests.accept": "Hyväksy", @@ -590,12 +608,6 @@ "refresh": "Päivitä", "regeneration_indicator.label": "Ladataan…", "regeneration_indicator.sublabel": "Kotisyötettäsi valmistellaan!", - "relationship_severance_notification.purged_data": "ylläpitäjien tyhjentämä", - "relationship_severance_notification.relationships": "{count, plural, one {# suhde} other {# suhdetta}}", - "relationship_severance_notification.types.account_suspension": "Tili on jäädytetty", - "relationship_severance_notification.types.domain_block": "Verkkotunnus on jäädytetty", - "relationship_severance_notification.types.user_domain_block": "Estit tämän verkkotunnuksen", - "relationship_severance_notification.view": "Näytä", "relative_time.days": "{number} pv", "relative_time.full.days": "{number, plural, one {# päivä} other {# päivää}} sitten", "relative_time.full.hours": "{number, plural, one {# tunti} other {# tuntia}} sitten", diff --git a/app/javascript/mastodon/locales/fil.json b/app/javascript/mastodon/locales/fil.json index afa0e5fbc2..894d73c8cc 100644 --- a/app/javascript/mastodon/locales/fil.json +++ b/app/javascript/mastodon/locales/fil.json @@ -50,6 +50,7 @@ "admin.dashboard.retention.cohort_size": "Mga bagong tagagamit", "alert.rate_limited.message": "Mangyaring subukan muli pagkatapos ng {retry_time, time, medium}.", "audio.hide": "Itago ang tunog", + "block_modal.title": "Harangan ang tagagamit?", "bundle_column_error.error.title": "Naku!", "bundle_column_error.network.body": "Nagkaroon ng kamalian habang sinusubukang i-karga ang pahinang ito. Maaaring dahil ito sa pansamantalang problema ng iyong koneksyon sa internet o ang server na ito.", "bundle_column_error.network.title": "Kamaliang network", @@ -104,6 +105,7 @@ "compose_form.poll.multiple": "Maraming pagpipilian", "compose_form.poll.single": "Piliin ang isa", "compose_form.reply": "Tumugon", + "compose_form.spoiler.marked": "Tanggalin ang babala sa nilalaman", "compose_form.spoiler.unmarked": "Idagdag ang babala sa nilalaman", "confirmation_modal.cancel": "Pagpaliban", "confirmations.block.confirm": "Harangan", @@ -115,6 +117,7 @@ "confirmations.discard_edit_media.confirm": "Ipagpaliban", "confirmations.edit.confirm": "Baguhin", "confirmations.reply.confirm": "Tumugon", + "conversation.mark_as_read": "Markahan bilang nabasa na", "copy_icon_button.copied": "Sinipi sa clipboard", "copypaste.copied": "Sinipi", "copypaste.copy_to_clipboard": "I-sipi sa clipboard", @@ -130,6 +133,10 @@ "dismissable_banner.explore_statuses": "Ito ang mga sumisikat na mga post sa iba't ibang bahagi ng social web ngayon. Ang mga mas bagong post na mas marami ang mga pagpapalakas at paborito ay tinataasan ng antas.", "dismissable_banner.explore_tags": "Ito ang mga sumisikat na mga hashtag sa iba't ibang bahagi ng social web ngayon. Ang mga hashtag ginagamit ng mas maraming mga iba't ibang tao ay tinataasan ng antas.", "dismissable_banner.public_timeline": "Ito ang mga pinakamakailang nakapublikong post mula sa mga taong nasa social web na sinusundan ng mga tao sa {domain}.", + "domain_block_modal.block": "Harangan ang serbiro", + "domain_block_modal.title": "Harangan ang domain?", + "domain_block_modal.you_will_lose_followers": "Mabubura ang iyong mga tagasunod mula sa serbirong ito.", + "domain_pill.server": "Serbiro", "embed.instructions": "I-embed ang post na ito sa iyong pook-sapot sa pamamagitan ng pagsipi ng kodigo sa ilalim.", "embed.preview": "Ito ang magiging itsura:", "emoji_button.activity": "Aktibidad", @@ -166,6 +173,8 @@ "empty_column.list": "Wala pang laman ang listahang ito. Kapag naglathala ng mga bagong post ang mga miyembro ng listahang ito, makikita iyon dito.", "empty_column.lists": "Wala ka pang mga listahan. Kapag gumawa ka ng isa, makikita yun dito.", "explore.search_results": "Mga resulta ng paghahanap", + "explore.title": "Tuklasin", + "explore.trending_links": "Mga balita", "filter_modal.select_filter.search": "Hanapin o gumawa", "firehose.all": "Lahat", "firehose.local": "Itong serbiro", @@ -180,6 +189,7 @@ "generic.saved": "Nakaimbak", "hashtag.column_header.tag_mode.all": "at {additional}", "hashtag.column_header.tag_mode.any": "o {additional}", + "home.column_settings.show_replies": "Ipakita ang mga tugon", "home.pending_critical_update.body": "Mangyaring i-update ang iyong serbiro ng Mastodon sa lalong madaling panahon!", "interaction_modal.login.action": "Iuwi mo ako", "interaction_modal.no_account_yet": "Wala sa Mastodon?", @@ -216,14 +226,21 @@ "notification.follow": "Sinundan ka ni {name}", "notification.follow_request": "Hinihiling ni {name} na sundan ka", "notification.mention": "Binanggit ka ni {name}", + "notification.relationships_severance_event.learn_more": "Matuto nang higit pa", + "notification_requests.accept": "Tanggapin", + "notification_requests.notifications_from": "Mga abiso mula kay/sa {name}", "notifications.clear": "Burahin mga abiso", "notifications.column_settings.admin.report": "Mga bagong ulat:", + "notifications.column_settings.alert": "Mga abiso sa Desktop", "notifications.column_settings.favourite": "Mga paborito:", "notifications.column_settings.follow": "Mga bagong tagasunod:", "notifications.column_settings.unread_notifications.category": "Hindi Nabasang mga Abiso", "notifications.column_settings.update": "Mga pagbago:", "notifications.filter.all": "Lahat", + "notifications.filter.favourites": "Mga paborito", "notifications.mark_as_read": "Markahan lahat ng abiso bilang nabasa na", + "notifications.policy.filter_not_followers_title": "Mga taong hindi ka susundan", + "notifications.policy.filter_not_following_title": "Mga taong hindi mo sinusundan", "onboarding.action.back": "Ibalik mo ako", "onboarding.actions.back": "Ibalik mo ako", "onboarding.profile.note_hint": "Maaari mong @bangitin ang ibang mga tao o mga #hashtag…", @@ -236,6 +253,8 @@ "privacy.direct.long": "Lahat ng mga binanggit sa post", "privacy.private.long": "Mga tagasunod mo lamang", "privacy.private.short": "Mga tagasunod", + "privacy.public.long": "Sinumang nasa loob at labas ng Mastodon", + "regeneration_indicator.label": "Kumakarga…", "relative_time.days": "{number}a", "relative_time.full.days": "{number, plural, one {# araw} other {# na araw}} ang nakalipas", "relative_time.full.hours": "{number, plural, one {# oras} other {# na oras}} ang nakalipas", @@ -261,6 +280,10 @@ "report.thanks.title": "Ayaw mo bang makita ito?", "report.thanks.title_actionable": "Salamat sa pag-uulat, titingnan namin ito.", "report_notification.categories.other": "Iba pa", + "search.quick_action.open_url": "Buksan ang URL sa Mastodon", + "search.search_or_paste": "Maghanap o ilagay ang URL", + "search_popout.full_text_search_disabled_message": "Hindi magagamit sa {domain}.", + "search_popout.full_text_search_logged_out_message": "Magagamit lamang kapag naka-log in.", "search_results.all": "Lahat", "search_results.see_all": "Ipakita lahat", "server_banner.learn_more": "Matuto nang higit pa", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index c86829ee30..77257413fd 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "Brúka ein verandi bólk ella skapa ein nýggjan", "filter_modal.select_filter.title": "Filtrera hendan postin", "filter_modal.title.status": "Filtrera ein post", + "filtered_notifications_banner.mentions": "{count, plural, one {umrøða} other {umrøður}}", "filtered_notifications_banner.pending_requests": "Fráboðanir frá {count, plural, =0 {ongum} one {einum persóni} other {# persónum}}, sum tú kanska kennir", "filtered_notifications_banner.title": "Sáldaðar fráboðanir", "firehose.all": "Allar", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "Sjálvt um konta tín ikki er læst, so hugsa {domain} starvsfólkini, at tú kanska hevur hug at kanna umbønir um at fylgja frá hesum kontum við hond.", "follow_suggestions.curated_suggestion": "Val hjá ábyrgdarfólki", "follow_suggestions.dismiss": "Lat vera við at vísa", + "follow_suggestions.featured_longer": "Vald burturúr av {domain} toyminum", + "follow_suggestions.friends_of_friends_longer": "Vælumtókt millum fólk, sum tú fylgir", "follow_suggestions.hints.featured": "Hesin vangin er úrvaldur av toyminum handan {domain}.", "follow_suggestions.hints.friends_of_friends": "Hesin vangin er vælumtóktur millum tey, tú fylgir.", "follow_suggestions.hints.most_followed": "Hesin vangin er ein av teimum, sum er mest fylgdur á {domain}.", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Hesin vangin líkist teimum, sum tú nýliga hevur fylgt.", "follow_suggestions.personalized_suggestion": "Persónligt uppskot", "follow_suggestions.popular_suggestion": "Vælumtókt uppskot", + "follow_suggestions.popular_suggestion_longer": "Vælumtókt á {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Líkist vangum, sum tú nýliga hevur fylgt", "follow_suggestions.view_all": "Vís øll", "follow_suggestions.who_to_follow": "Hvørji tú átti at fylgt", "followed_tags": "Fylgd frámerki", @@ -468,10 +473,23 @@ "notification.follow": "{name} fylgdi tær", "notification.follow_request": "{name} biður um at fylgja tær", "notification.mention": "{name} nevndi teg", + "notification.moderation-warning.learn_more": "Lær meira", + "notification.moderation_warning": "Tú hevur móttikið eina umsjónarávarðing", + "notification.moderation_warning.action_delete_statuses": "Onkrir av tínum postum eru strikaðir.", + "notification.moderation_warning.action_disable": "Konta tín er gjørd óvirkin.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Nakrir av postum tínum eru merktir sum viðkvæmir.", + "notification.moderation_warning.action_none": "Konta tín hevur móttikið eina umsjónarávarðing.", + "notification.moderation_warning.action_sensitive": "Postar tínir verða merktir sum viðkvæmir frá nú av.", + "notification.moderation_warning.action_silence": "Konta tín er avmarkað.", + "notification.moderation_warning.action_suspend": "Konta tín er ógildað.", "notification.own_poll": "Tín atkvøðugreiðsla er endað", "notification.poll": "Ein atkvøðugreiðsla, har tú hevur atkvøtt, er endað", "notification.reblog": "{name} lyfti tín post", - "notification.severed_relationships": "Tilknýti við {name} avbrotið", + "notification.relationships_severance_event": "Mist sambond við {name}", + "notification.relationships_severance_event.account_suspension": "Ein umsitari frá {from} hevur gjørt {target} óvirkna, sum merkir, at tú ikki kanst móttaka dagføringar ella virka saman við teimum longur.", + "notification.relationships_severance_event.domain_block": "Ein umsitari frá {from} hevur blokerað {target}, íroknað {followersCount} av tínum fylgjarum og {followingCount, plural, one {# kontu} other {# kontur}}, sum tú fylgir.", + "notification.relationships_severance_event.learn_more": "Lær meira", + "notification.relationships_severance_event.user_domain_block": "Tú hevur blokerað {target}, strikað {followersCount} av tínum fylgjarum og {followingCount, plural, one {# kontu} other {# kontur}}, sum tú fylgir.", "notification.status": "{name} hevur júst postað", "notification.update": "{name} rættaði ein post", "notification_requests.accept": "Góðtak", @@ -590,12 +608,6 @@ "refresh": "Endurles", "regeneration_indicator.label": "Innlesur…", "regeneration_indicator.sublabel": "Tín heimarás verður gjørd klár!", - "relationship_severance_notification.purged_data": "reinsað av umsitarum", - "relationship_severance_notification.relationships": "{count, plural, one {# tilknýti} other {# tilknýti}}", - "relationship_severance_notification.types.account_suspension": "Kontan er ógildað", - "relationship_severance_notification.types.domain_block": "Økisnavn er ógildað", - "relationship_severance_notification.types.user_domain_block": "Tú hevur forðað hesum økisnavni", - "relationship_severance_notification.view": "Vís", "relative_time.days": "{number}d", "relative_time.full.days": "{number, plural, one {# dagur} other {# dagar}} síðani", "relative_time.full.hours": "{number, plural, one {# tími} other {# tímar}} síðani", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index 1e1c78f29d..9e29852904 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -222,14 +222,14 @@ "domain_pill.server": "Serveur", "domain_pill.their_handle": "Son identifiant :", "domain_pill.their_server": "Son foyer numérique, là où tous ses posts résident.", - "domain_pill.their_username": "Son identifiant unique sur leur serveur. Il est possible de rencontrer des utilisateurs avec le même nom sur différents serveurs.", + "domain_pill.their_username": "Son identifiant unique sur leur serveur. Il est possible de rencontrer des utilisateur·rice·s avec le même nom sur différents serveurs.", "domain_pill.username": "Nom d’utilisateur", "domain_pill.whats_in_a_handle": "Qu'est-ce qu'un identifiant ?", "domain_pill.who_they_are": "Comme un identifiant contient le nom et le service hébergeant une personne, vous pouvez interagir sur .", "domain_pill.who_you_are": "Comme un identifiant indique votre nom et le service vous hébergeant, vous pouvez interagir avec .", "domain_pill.your_handle": "Votre identifiant :", "domain_pill.your_server": "Votre foyer numérique, là où vos messages résident. Vous souhaitez changer ? Lancez un transfert vers un autre serveur quand vous le voulez et vos abonné·e·s suivront automatiquement.", - "domain_pill.your_username": "Votre identifiant unique sur ce serveur. Il est possible de trouver des utilisateurs ayant le même nom d'utilisateur sur différents serveurs.", + "domain_pill.your_username": "Votre identifiant unique sur ce serveur. Il est possible de rencontrer des utilisateur·rice·s ayant le même nom d'utilisateur sur différents serveurs.", "embed.instructions": "Intégrez cette publication à votre site en copiant le code ci-dessous.", "embed.preview": "Voici comment il apparaîtra:", "emoji_button.activity": "Activité", @@ -471,7 +471,11 @@ "notification.own_poll": "Votre sondage est terminé", "notification.poll": "Un sondage auquel vous avez participé est terminé", "notification.reblog": "{name} a boosté votre message", - "notification.severed_relationships": "Relation avec {name} rompues", + "notification.relationships_severance_event": "Connexions perdues avec {name}", + "notification.relationships_severance_event.account_suspension": "Un·e administrateur·rice de {from} a suspendu {target}, ce qui signifie que vous ne pourrez plus recevoir de mises à jour ou interagir avec lui.", + "notification.relationships_severance_event.domain_block": "Un·e administrateur·rice de {from} en a bloqué {target}, comprenant {followersCount} de vos abonné·e·s et {followingCount, plural, one {# compte} other {# comptes}} vous suivez.", + "notification.relationships_severance_event.learn_more": "En savoir plus", + "notification.relationships_severance_event.user_domain_block": "Vous avez bloqué {target}, en supprimant {followersCount} de vos abonnés et {followingCount, plural, one {# compte} other {# comptes}} que vous suivez.", "notification.status": "{name} vient de publier", "notification.update": "{name} a modifié une publication", "notification_requests.accept": "Accepter", @@ -484,6 +488,8 @@ "notifications.column_settings.admin.sign_up": "Nouvelles inscriptions:", "notifications.column_settings.alert": "Notifications navigateur", "notifications.column_settings.favourite": "Favoris:", + "notifications.column_settings.filter_bar.advanced": "Afficher toutes les catégories", + "notifications.column_settings.filter_bar.category": "Barre de filtre rapide", "notifications.column_settings.follow": "Nouveaux⋅elles abonné⋅e⋅s:", "notifications.column_settings.follow_request": "Nouvelles demandes d’abonnement:", "notifications.column_settings.mention": "Mentions:", @@ -588,12 +594,6 @@ "refresh": "Actualiser", "regeneration_indicator.label": "Chargement…", "regeneration_indicator.sublabel": "Votre fil d'accueil est en cours de préparation!", - "relationship_severance_notification.purged_data": "supprimées par les administrateurs", - "relationship_severance_notification.relationships": "{count, plural, one {# relation} other {# relations}}", - "relationship_severance_notification.types.account_suspension": "Le compte a été suspendu", - "relationship_severance_notification.types.domain_block": "Le domaine a été suspendu", - "relationship_severance_notification.types.user_domain_block": "Vous avez bloqué ce domaine", - "relationship_severance_notification.view": "Afficher", "relative_time.days": "{number} j", "relative_time.full.days": "il y a {number, plural, one {# jour} other {# jours}}", "relative_time.full.hours": "il y a {number, plural, one {# heure} other {# heures}}", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index af5ed66bd8..1a5803623f 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -222,14 +222,14 @@ "domain_pill.server": "Serveur", "domain_pill.their_handle": "Son identifiant :", "domain_pill.their_server": "Son foyer numérique, là où tous ses posts résident.", - "domain_pill.their_username": "Son identifiant unique sur leur serveur. Il est possible de rencontrer des utilisateurs avec le même nom sur différents serveurs.", + "domain_pill.their_username": "Son identifiant unique sur leur serveur. Il est possible de rencontrer des utilisateur·rice·s avec le même nom sur différents serveurs.", "domain_pill.username": "Nom d’utilisateur", "domain_pill.whats_in_a_handle": "Qu'est-ce qu'un identifiant ?", "domain_pill.who_they_are": "Comme un identifiant contient le nom et le service hébergeant une personne, vous pouvez interagir sur .", "domain_pill.who_you_are": "Comme un identifiant indique votre nom et le service vous hébergeant, vous pouvez interagir avec .", "domain_pill.your_handle": "Votre identifiant :", "domain_pill.your_server": "Votre foyer numérique, là où vos messages résident. Vous souhaitez changer ? Lancez un transfert vers un autre serveur quand vous le voulez et vos abonné·e·s suivront automatiquement.", - "domain_pill.your_username": "Votre identifiant unique sur ce serveur. Il est possible de trouver des utilisateurs ayant le même nom d'utilisateur sur différents serveurs.", + "domain_pill.your_username": "Votre identifiant unique sur ce serveur. Il est possible de rencontrer des utilisateur·rice·s ayant le même nom d'utilisateur sur différents serveurs.", "embed.instructions": "Intégrez ce message à votre site en copiant le code ci-dessous.", "embed.preview": "Il apparaîtra comme cela :", "emoji_button.activity": "Activités", @@ -471,7 +471,11 @@ "notification.own_poll": "Votre sondage est terminé", "notification.poll": "Un sondage auquel vous avez participé vient de se terminer", "notification.reblog": "{name} a partagé votre message", - "notification.severed_relationships": "Relation avec {name} rompues", + "notification.relationships_severance_event": "Connexions perdues avec {name}", + "notification.relationships_severance_event.account_suspension": "Un·e administrateur·rice de {from} a suspendu {target}, ce qui signifie que vous ne pourrez plus recevoir de mises à jour ou interagir avec lui.", + "notification.relationships_severance_event.domain_block": "Un·e administrateur·rice de {from} en a bloqué {target}, comprenant {followersCount} de vos abonné·e·s et {followingCount, plural, one {# compte} other {# comptes}} vous suivez.", + "notification.relationships_severance_event.learn_more": "En savoir plus", + "notification.relationships_severance_event.user_domain_block": "Vous avez bloqué {target}, en supprimant {followersCount} de vos abonnés et {followingCount, plural, one {# compte} other {# comptes}} que vous suivez.", "notification.status": "{name} vient de publier", "notification.update": "{name} a modifié un message", "notification_requests.accept": "Accepter", @@ -484,6 +488,8 @@ "notifications.column_settings.admin.sign_up": "Nouvelles inscriptions :", "notifications.column_settings.alert": "Notifications du navigateur", "notifications.column_settings.favourite": "Favoris :", + "notifications.column_settings.filter_bar.advanced": "Afficher toutes les catégories", + "notifications.column_settings.filter_bar.category": "Barre de filtre rapide", "notifications.column_settings.follow": "Nouveaux·elles abonné·e·s :", "notifications.column_settings.follow_request": "Nouvelles demandes d’abonnement :", "notifications.column_settings.mention": "Mentions :", @@ -527,7 +533,7 @@ "onboarding.actions.go_to_home": "Allers vers mon flux principal", "onboarding.compose.template": "Bonjour #Mastodon !", "onboarding.follows.empty": "Malheureusement, aucun résultat ne peut être affiché pour le moment. Vous pouvez essayer d'utiliser la recherche ou parcourir la page de découverte pour trouver des personnes à suivre, ou réessayez plus tard.", - "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!", + "onboarding.follows.lead": "Votre flux principal est le principal moyen de découvrir Mastodon. Plus vous suivez de personnes, plus il sera actif et intéressant. Pour commencer, voici quelques suggestions :", "onboarding.follows.title": "Personnaliser votre flux principal", "onboarding.profile.discoverable": "Rendre mon profil découvrable", "onboarding.profile.discoverable_hint": "Lorsque vous acceptez d'être découvert sur Mastodon, vos messages peuvent apparaître dans les résultats de recherche et les tendances, et votre profil peut être suggéré à des personnes ayant des intérêts similaires aux vôtres.", @@ -544,10 +550,10 @@ "onboarding.share.message": "Je suis {username} sur #Mastodon ! Suivez-moi sur {url}", "onboarding.share.next_steps": "Étapes suivantes possibles :", "onboarding.share.title": "Partager votre profil", - "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:", + "onboarding.start.lead": "Vous faites désormais partie de Mastodon, une plateforme de médias sociaux unique et décentralisée où c'est vous, et non un algorithme, qui créez votre propre expérience. Nous allons vous aider à vous lancer dans cette nouvelle frontière sociale :", "onboarding.start.skip": "Vous n’avez donc pas besoin d’aide pour commencer ?", "onboarding.start.title": "Vous avez réussi !", - "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.", + "onboarding.steps.follow_people.body": "Suivre des personnes intéressantes, c'est la raison d'être de Mastodon.", "onboarding.steps.follow_people.title": "Personnaliser votre flux principal", "onboarding.steps.publish_status.body": "Dites bonjour au monde avec du texte, des photos, des vidéos ou des sondages {emoji}", "onboarding.steps.publish_status.title": "Rédigez votre premier message", @@ -588,12 +594,6 @@ "refresh": "Actualiser", "regeneration_indicator.label": "Chargement…", "regeneration_indicator.sublabel": "Votre fil principal est en cours de préparation !", - "relationship_severance_notification.purged_data": "supprimées par les administrateurs", - "relationship_severance_notification.relationships": "{count, plural, one {# relation} other {# relations}}", - "relationship_severance_notification.types.account_suspension": "Le compte a été suspendu", - "relationship_severance_notification.types.domain_block": "Le domaine a été suspendu", - "relationship_severance_notification.types.user_domain_block": "Vous avez bloqué ce domaine", - "relationship_severance_notification.view": "Afficher", "relative_time.days": "{number} j", "relative_time.full.days": "il y a {number, plural, one {# jour} other {# jours}}", "relative_time.full.hours": "il y a {number, plural, one {# heure} other {# heures}}", diff --git a/app/javascript/mastodon/locales/fy.json b/app/javascript/mastodon/locales/fy.json index 97119e30c0..a22f4767a6 100644 --- a/app/javascript/mastodon/locales/fy.json +++ b/app/javascript/mastodon/locales/fy.json @@ -13,7 +13,7 @@ "about.rules": "Serverrigels", "account.account_note_header": "Opmerking", "account.add_or_remove_from_list": "Tafoegje oan of fuortsmite út listen", - "account.badges.bot": "Bot", + "account.badges.bot": "Automatisearre", "account.badges.group": "Groep", "account.block": "@{name} blokkearje", "account.block_domain": "Domein {domain} blokkearje", @@ -89,6 +89,11 @@ "announcement.announcement": "Oankundiging", "attachments_list.unprocessed": "(net ferwurke)", "audio.hide": "Audio ferstopje", + "block_modal.show_less": "Minder toane", + "block_modal.show_more": "Mear toane", + "block_modal.they_cant_mention": "Sy kinne jo net fermelde of folgje.", + "block_modal.title": "Brûker blokkearje?", + "block_modal.you_wont_see_mentions": "Jo sjogge gjin berjochten mear dy’t dizze account fermelde.", "boost_modal.combo": "Jo kinne op {combo} drukke om dit de folgjende kear oer te slaan", "bundle_column_error.copy_stacktrace": "Flaterrapport kopiearje", "bundle_column_error.error.body": "De opfrege side koe net werjûn wurde. It kin wêze troch in flater yn ús koade, of in probleem mei browserkompatibiliteit.", @@ -169,6 +174,7 @@ "confirmations.delete_list.message": "Binne jo wis dat jo dizze list foar permanint fuortsmite wolle?", "confirmations.discard_edit_media.confirm": "Fuortsmite", "confirmations.discard_edit_media.message": "Jo hawwe net-bewarre wizigingen yn de mediabeskriuwing of foarfertoaning, wolle jo dizze dochs fuortsmite?", + "confirmations.domain_block.confirm": "Server blokkearje", "confirmations.domain_block.message": "Binne jo echt wis dat jo alles fan {domain} negearje wolle? Yn de measte gefallen is it blokkearjen of negearjen fan in pear spesifike persoanen genôch en better. Jo sille gjin berjochten fan dizze server op iepenbiere tiidlinen sjen of yn jo meldingen. Jo folgers fan dizze server wurde fuortsmiten.", "confirmations.edit.confirm": "Bewurkje", "confirmations.edit.message": "Troch no te bewurkjen sil it berjocht dat jo no oan it skriuwen binne oerskreaun wurde. Wolle jo trochgean?", @@ -200,6 +206,20 @@ "dismissable_banner.explore_statuses": "Dizze berjochten winne oan populariteit op dizze en oare servers binnen it desintrale netwurk. Nijere berjochten mei mear boosts en favoriten stean heger.", "dismissable_banner.explore_tags": "Dizze hashtags winne oan populariteit op dizze en oare servers binnen it desintrale netwurk.", "dismissable_banner.public_timeline": "Dit binne de meast resinte iepenbiere berjochten fan accounts op it sosjale web dy’t troch minsken op {domain} folge wurde.", + "domain_block_modal.block": "Server blokkearje", + "domain_block_modal.block_account_instead": "Yn stee hjirfan {name} blokkearje", + "domain_block_modal.they_can_interact_with_old_posts": "Minsken op dizze server kinne ynteraksje hawwe mei jo âlde berjochten.", + "domain_block_modal.they_cant_follow": "Net ien op dizze server kin jo folgje.", + "domain_block_modal.they_wont_know": "Se krije net te witten dat se blokkearre wurde.", + "domain_block_modal.title": "Domein blokkearje?", + "domain_block_modal.you_will_lose_followers": "Al jo folgers fan dizze server wurde ûntfolge.", + "domain_block_modal.you_wont_see_posts": "Jo sjogge gjin berjochten of meldingen mear fan brûkers op dizze server.", + "domain_pill.server": "Server", + "domain_pill.their_handle": "Harren fediverse-adres:", + "domain_pill.their_server": "Harren digitale thús, wer’t al harren berjochten binne.", + "domain_pill.username": "Brûkersnamme", + "domain_pill.whats_in_a_handle": "Wat is in fediverse-adres?", + "domain_pill.your_handle": "Jo fediverse-adres:", "embed.instructions": "Embed this status on your website by copying the code below.", "embed.preview": "Sa komt it der út te sjen:", "emoji_button.activity": "Aktiviteiten", @@ -266,6 +286,7 @@ "filter_modal.select_filter.subtitle": "In besteande kategory brûke of in nije oanmeitsje", "filter_modal.select_filter.title": "Dit berjocht filterje", "filter_modal.title.status": "In berjocht filterje", + "filtered_notifications_banner.title": "Filtere meldingen", "firehose.all": "Alles", "firehose.local": "Dizze server", "firehose.remote": "Oare servers", @@ -394,6 +415,9 @@ "loading_indicator.label": "Lade…", "media_gallery.toggle_visible": "{number, plural, one {ôfbylding ferstopje} other {ôfbyldingen ferstopje}}", "moved_to_account_banner.text": "Omdat jo nei {movedToAccount} ferhuze binne is jo account {disabledAccount} op dit stuit útskeakele.", + "mute_modal.hide_options": "Opsjes ferstopje", + "mute_modal.indefinite": "Oant ik se net mear negearje", + "mute_modal.show_options": "Opsjes toane", "navigation_bar.about": "Oer", "navigation_bar.advanced_interface": "Yn avansearre webomjouwing iepenje", "navigation_bar.blocks": "Blokkearre brûkers", @@ -429,14 +453,20 @@ "notification.own_poll": "Jo poll is beëinige", "notification.poll": "In enkête dêr’t jo yn stimd hawwe is beëinige", "notification.reblog": "{name} hat jo berjocht boost", + "notification.relationships_severance_event.learn_more": "Mear ynfo", "notification.status": "{name} hat in berjocht pleatst", "notification.update": "{name} hat in berjocht bewurke", + "notification_requests.accept": "Akseptearje", + "notification_requests.dismiss": "Ofwize", + "notification_requests.notifications_from": "Meldingen fan {name}", + "notification_requests.title": "Filtere meldingen", "notifications.clear": "Meldingen wiskje", "notifications.clear_confirmation": "Binne jo wis dat jo al jo meldingen permanint fuortsmite wolle?", "notifications.column_settings.admin.report": "Nije rapportaazjes:", "notifications.column_settings.admin.sign_up": "Nije registraasjes:", "notifications.column_settings.alert": "Desktopmeldingen", "notifications.column_settings.favourite": "Favoriten:", + "notifications.column_settings.filter_bar.advanced": "Alle kategoryen toane", "notifications.column_settings.follow": "Nije folgers:", "notifications.column_settings.follow_request": "Nij folchfersyk:", "notifications.column_settings.mention": "Fermeldingen:", diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json index 612e363774..9e79793de1 100644 --- a/app/javascript/mastodon/locales/gd.json +++ b/app/javascript/mastodon/locales/gd.json @@ -12,7 +12,7 @@ "about.powered_by": "Lìonra sòisealta sgaoilte le cumhachd {mastodon}", "about.rules": "Riaghailtean an fhrithealaiche", "account.account_note_header": "Nòta", - "account.add_or_remove_from_list": "Cuir ris no thoir air falbh o na liostaichean", + "account.add_or_remove_from_list": "Cuir ris no thoir air falbh o liostaichean", "account.badges.bot": "Fèin-obrachail", "account.badges.group": "Buidheann", "account.block": "Bac @{name}", @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "Cleachd roinn-seòrsa a tha ann no cruthaich tè ùr", "filter_modal.select_filter.title": "Criathraich am post seo", "filter_modal.title.status": "Criathraich post", + "filtered_notifications_banner.mentions": "{count, plural, one {iomradh} two {iomradh} few {iomraidhean} other {iomradh}}", "filtered_notifications_banner.pending_requests": "{count, plural, =0 {Chan eil brath ann o dhaoine} one {Tha brathan ann o # neach} two {Tha brathan ann o # neach} few {Tha brathan ann o # daoine} other {Tha brathan ann o # duine}} air a bheil thu eòlach ’s dòcha", "filtered_notifications_banner.title": "Brathan criathraichte", "firehose.all": "Na h-uile", @@ -307,6 +308,7 @@ "follow_requests.unlocked_explanation": "Ged nach eil an cunntas agad glaiste, tha sgioba {domain} dhen bheachd gum b’ fheàirrde thu lèirmheas a dhèanamh air na h-iarrtasan leantainn o na cunntasan seo a làimh.", "follow_suggestions.curated_suggestion": "Roghainn an sgioba", "follow_suggestions.dismiss": "Na seall seo a-rithist", + "follow_suggestions.friends_of_friends_longer": "Fèill mhòr am measg nan daoine a leanas tu", "follow_suggestions.hints.featured": "Chaidh a’ phròifil seo a thaghadh le sgioba {domain} a làimh.", "follow_suggestions.hints.friends_of_friends": "Tha fèill mhòr air a’ phròifil seo am measg nan daoine a leanas tu.", "follow_suggestions.hints.most_followed": "Tha a’ phròifil seo am measg an fheadhainn a leanar as trice air {domain}.", @@ -314,6 +316,7 @@ "follow_suggestions.hints.similar_to_recently_followed": "Tha a’ phròifil seo coltach ris na pròifilean air an lean thu o chionn goirid.", "follow_suggestions.personalized_suggestion": "Moladh pearsanaichte", "follow_suggestions.popular_suggestion": "Moladh air a bheil fèill mhòr", + "follow_suggestions.popular_suggestion_longer": "Fèill mhòr air {domain}", "follow_suggestions.view_all": "Seall na h-uile", "follow_suggestions.who_to_follow": "Molaidhean leantainn", "followed_tags": "Tagaichean hais ’gan leantainn", @@ -468,10 +471,15 @@ "notification.follow": "Tha {name} ’gad leantainn a-nis", "notification.follow_request": "Dh’iarr {name} ’gad leantainn", "notification.mention": "Thug {name} iomradh ort", + "notification.moderation-warning.learn_more": "Barrachd fiosrachaidh", "notification.own_poll": "Thàinig an cunntas-bheachd agad gu crìoch", "notification.poll": "Thàinig cunntas-bheachd sa bhòt thu gu crìoch", "notification.reblog": "Bhrosnaich {name} am post agad", - "notification.severed_relationships": "Chaidh na dàimhean le {name} a dhealachadh", + "notification.relationships_severance_event": "Chaill thu dàimhean le {name}", + "notification.relationships_severance_event.account_suspension": "Chuir rianaire aig {from} {target} à rèim agus is ciall dha sin nach fhaigh thu naidheachdan uapa ’s nach urrainn dhut conaltradh leotha.", + "notification.relationships_severance_event.domain_block": "Bhac rianaire aig {from} {target}, a’ gabhail a-staigh {followersCount} dhen luchd-leantainn agad agus {followingCount, plural, one {# chunntas} two {# chunntas} few {# cunntasan} other {# cunntas}} a tha thu fhèin a’ leantainn.", + "notification.relationships_severance_event.learn_more": "Barrachd fiosrachaidh", + "notification.relationships_severance_event.user_domain_block": "Bhac thu {target} agus thug sin air falbh {followersCount} dhen luchd-leantainn agad agus {followingCount, plural, one {# chunntas} two {# chunntas} few {# cunntasan} other {# cunntas}} a tha thu fhèin a’ leantainn.", "notification.status": "Phostaich {name} rud", "notification.update": "Dheasaich {name} post", "notification_requests.accept": "Gabh ris", @@ -484,6 +492,8 @@ "notifications.column_settings.admin.sign_up": "Clàraidhean ùra:", "notifications.column_settings.alert": "Brathan deasga", "notifications.column_settings.favourite": "Annsachdan:", + "notifications.column_settings.filter_bar.advanced": "Seall a h-uile roinn-seòrsa", + "notifications.column_settings.filter_bar.category": "Bàr-criathraidh luath", "notifications.column_settings.follow": "Luchd-leantainn ùr:", "notifications.column_settings.follow_request": "Iarrtasan leantainn ùra:", "notifications.column_settings.mention": "Iomraidhean:", @@ -509,7 +519,7 @@ "notifications.permission_denied": "Chan eil brathan deasga ri fhaighinn on a chaidh iarrtas ceadan a’ bhrabhsair a dhiùltadh cheana", "notifications.permission_denied_alert": "Cha ghabh brathan deasga a chur an comas on a chaidh iarrtas ceadan a’ bhrabhsair a dhiùltadh cheana", "notifications.permission_required": "Chan eil brathan deasga ri fhaighinn on nach deach an cead riatanach a thoirt seachad.", - "notifications.policy.filter_new_accounts.hint": "Chaidh a chruthachadh o chionn {days, plural, one {# latha} two {# latha} few {# làithean} other {# latha}}", + "notifications.policy.filter_new_accounts.hint": "A chaidh a chruthachadh o chionn {days, plural, one {# latha} two {# latha} few {# làithean} other {# latha}}", "notifications.policy.filter_new_accounts_title": "Cunntasan ùra", "notifications.policy.filter_not_followers_hint": "A’ gabhail a-staigh an fheadhainn a lean ort nas lugha na {days, plural, one {# latha} two {# latha} few {# làithean} other {# latha}} seo chaidh", "notifications.policy.filter_not_followers_title": "Daoine nach eil gad leantainn", @@ -588,12 +598,6 @@ "refresh": "Ath-nuadhaich", "regeneration_indicator.label": "’Ga luchdadh…", "regeneration_indicator.sublabel": "Tha do dhachaigh ’ga ullachadh!", - "relationship_severance_notification.purged_data": "chaidh a phurgaideachadh leis na rianairean", - "relationship_severance_notification.relationships": "{count, plural, one {# dàimh} two {# dhàimh} few {# dàimhean} other {# dàimh}}", - "relationship_severance_notification.types.account_suspension": "Chaidh cunntas a chur à rèim", - "relationship_severance_notification.types.domain_block": "Chaidh àrainn a chur à rèim", - "relationship_severance_notification.types.user_domain_block": "Bhac thu an àrainn seo", - "relationship_severance_notification.view": "Seall", "relative_time.days": "{number}l", "relative_time.full.days": "{number, plural, one {# latha} two {# latha} few {# làithean} other {# latha}} air ais", "relative_time.full.hours": "{number, plural, one {# uair a thìde} two {# uair a thìde} few {# uairean a thìde} other {# uair a thìde}} air ais", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 57d13205fb..49802ac488 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "Usar unha categoría existente ou crear unha nova", "filter_modal.select_filter.title": "Filtrar esta publicación", "filter_modal.title.status": "Filtrar unha publicación", + "filtered_notifications_banner.mentions": "{count, plural, one {mención} other {mencións}}", "filtered_notifications_banner.pending_requests": "Notificacións de {count, plural, =0 {ninguén} one {unha persoa} other {# persoas}} que poderías coñecer", "filtered_notifications_banner.title": "Notificacións filtradas", "firehose.all": "Todo", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "Malia que a túa conta non é privada, a administración de {domain} pensou que quizabes terías que revisar de xeito manual as solicitudes de seguiminto.", "follow_suggestions.curated_suggestion": "Suxestións do Servidor", "follow_suggestions.dismiss": "Non mostrar máis", + "follow_suggestions.featured_longer": "Elección persoal do equipo de {domain}", + "follow_suggestions.friends_of_friends_longer": "Popular entre as persoas que sigues", "follow_suggestions.hints.featured": "Este perfil foi escollido pola administración de {domain}.", "follow_suggestions.hints.friends_of_friends": "Este perfil é popular entre as persoas que segues.", "follow_suggestions.hints.most_followed": "Este perfil é un dos máis seguidos en {domain}.", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Este perfil ten semellanzas cos perfís que ti seguiches últimamente.", "follow_suggestions.personalized_suggestion": "Suxestión personalizada", "follow_suggestions.popular_suggestion": "Suxestión popular", + "follow_suggestions.popular_suggestion_longer": "Popular en {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Perfís semellantes aos que seguiches recentemente", "follow_suggestions.view_all": "Ver todas", "follow_suggestions.who_to_follow": "A quen seguir", "followed_tags": "Cancelos seguidos", @@ -468,10 +473,23 @@ "notification.follow": "{name} comezou a seguirte", "notification.follow_request": "{name} solicitou seguirte", "notification.mention": "{name} mencionoute", + "notification.moderation-warning.learn_more": "Saber máis", + "notification.moderation_warning": "Recibiches unha advertencia da moderación", + "notification.moderation_warning.action_delete_statuses": "Algunha das túas publicacións foron eliminadas.", + "notification.moderation_warning.action_disable": "A túa conta foi desactivada.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Algunha das túas publicacións foron marcadas como sensibles.", + "notification.moderation_warning.action_none": "A túa conta recibeu unha advertencia da moderación.", + "notification.moderation_warning.action_sensitive": "De agora en diante as túas publicacións van ser marcadas como sensibles.", + "notification.moderation_warning.action_silence": "A túa conta foi limitada.", + "notification.moderation_warning.action_suspend": "A túa conta foi suspendida.", "notification.own_poll": "A túa enquisa rematou", "notification.poll": "Rematou a enquisa na que votaches", "notification.reblog": "{name} compartiu a túa publicación", - "notification.severed_relationships": "Cortouse a relación con {name}", + "notification.relationships_severance_event": "Perdeuse a conexión con {name}", + "notification.relationships_severance_event.account_suspension": "A administración de {from} suspendeu a {target}, o que significa que xa non vas recibir actualizacións de esa conta ou interactuar con ela.", + "notification.relationships_severance_event.domain_block": "A administración de {from} bloqueou a {target}, que inclúe a {followersCount} das túas seguidoras e a {followingCount, plural, one {# conta} other {# contas}} que sigues.", + "notification.relationships_severance_event.learn_more": "Saber máis", + "notification.relationships_severance_event.user_domain_block": "Bloqueaches a {target}, eliminando a {followersCount} das túas seguidoras e a {followingCount, plural, one {# conta} other {# contas}} que sigues.", "notification.status": "{name} publicou", "notification.update": "{name} editou unha publicación", "notification_requests.accept": "Aceptar", @@ -484,6 +502,8 @@ "notifications.column_settings.admin.sign_up": "Novas usuarias:", "notifications.column_settings.alert": "Notificacións de escritorio", "notifications.column_settings.favourite": "Favoritas:", + "notifications.column_settings.filter_bar.advanced": "Mostrar todas as categorías", + "notifications.column_settings.filter_bar.category": "Barra de filtrado rápido", "notifications.column_settings.follow": "Novas seguidoras:", "notifications.column_settings.follow_request": "Novas peticións de seguimento:", "notifications.column_settings.mention": "Mencións:", @@ -588,12 +608,6 @@ "refresh": "Actualizar", "regeneration_indicator.label": "Estase a cargar…", "regeneration_indicator.sublabel": "Estase a preparar a túa cronoloxía de inicio!", - "relationship_severance_notification.purged_data": "purgada pola administración", - "relationship_severance_notification.relationships": "{count, plural, one {# relación} other {# relacións}}", - "relationship_severance_notification.types.account_suspension": "A conta foi suspendida", - "relationship_severance_notification.types.domain_block": "O dominio foi suspendido", - "relationship_severance_notification.types.user_domain_block": "Bloqueaches este dominio", - "relationship_severance_notification.view": "Ver", "relative_time.days": "{number}d", "relative_time.full.days": "hai {number, plural, one {# día} other {# días}}", "relative_time.full.hours": "hai {number, plural, one {# hora} other {# horas}}", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index 82dec2d863..600de39597 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "שימוש בקטגורייה קיימת או יצירת אחת חדשה", "filter_modal.select_filter.title": "סינון ההודעה הזו", "filter_modal.title.status": "סנן הודעה", + "filtered_notifications_banner.mentions": "{count, plural, one {איזכור} other {איזכורים} two {איזכוריים}}", "filtered_notifications_banner.pending_requests": "{count, plural,=0 {אין התראות ממשתמשים ה}one {התראה אחת ממישהו/מישהי ה}two {יש התראותיים ממשתמשים }other {יש # התראות ממשתמשים }}מוכרים לך", "filtered_notifications_banner.title": "התראות מסוננות", "firehose.all": "הכל", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "למרות שחשבונך אינו נעול, צוות {domain} חושב שאולי כדאי לוודא את בקשות המעקב האלה ידנית.", "follow_suggestions.curated_suggestion": "בחירת הצוות", "follow_suggestions.dismiss": "לא להציג שוב", + "follow_suggestions.featured_longer": "נבחר ידנית על ידי הצוות של {domain}", + "follow_suggestions.friends_of_friends_longer": "פופולרי בין הנעקבים שלך", "follow_suggestions.hints.featured": "החשבון הזה נבחר אישית על ידי צוות {domain}.", "follow_suggestions.hints.friends_of_friends": "חשבון זה פופולרי בין הנעקבים שלך.", "follow_suggestions.hints.most_followed": "חשבון זה הוא מבין הנעקבים ביותר בשרת {domain}.", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "חשבון זה דומה לחשבונות אחרים שאחריהם התחלת לעקוב לאחרונה.", "follow_suggestions.personalized_suggestion": "הצעות מותאמות אישית", "follow_suggestions.popular_suggestion": "הצעה פופולרית", + "follow_suggestions.popular_suggestion_longer": "פופולרי בקהילת {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "דומה למשתמשות.ים שעקבת אחריהן.ם לאחרונה", "follow_suggestions.view_all": "צפיה בכל", "follow_suggestions.who_to_follow": "אחרי מי לעקוב", "followed_tags": "התגיות שהחשבון שלך עוקב אחריהן", @@ -468,10 +473,23 @@ "notification.follow": "{name} במעקב אחרייך", "notification.follow_request": "{name} ביקשו לעקוב אחריך", "notification.mention": "אוזכרת על ידי {name}", + "notification.moderation-warning.learn_more": "למידע נוסף", + "notification.moderation_warning": "קיבלת אזהרה מצוות ניהול התוכן", + "notification.moderation_warning.action_delete_statuses": "חלק מהודעותיך הוסרו.", + "notification.moderation_warning.action_disable": "חשבונך הושבת.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "חלק מהודעותיך סומנו כרגישות.", + "notification.moderation_warning.action_none": "חשבונך קיבל אזהרה מצוות ניהול התוכן.", + "notification.moderation_warning.action_sensitive": "הודעותיך יסומנו כרגישות מעתה ואילך.", + "notification.moderation_warning.action_silence": "חשבונך הוגבל.", + "notification.moderation_warning.action_suspend": "חשבונך הושעה.", "notification.own_poll": "הסקר שלך הסתיים", "notification.poll": "סקר שהצבעת בו הסתיים", "notification.reblog": "הודעתך הודהדה על ידי {name}", - "notification.severed_relationships": "חתכתם כל קשר עם {name}", + "notification.relationships_severance_event": "אבד הקשר עם {name}", + "notification.relationships_severance_event.account_suspension": "מנהל.ת משרת {from} השע(ת)ה את {target}, ולפיכך לא תעודכנו יותר על ידם ולא תוכלו להיות איתם בקשר.", + "notification.relationships_severance_event.domain_block": "מנהל.ת מאתר {from} חסמו את {target} ובכלל זה {followersCount} מעוקביך וגם {followingCount, plural, one {חשבון אחד} two {שני חשבונות} many {# חשבונות} other {# חשבונות}} מבין נעקביך.", + "notification.relationships_severance_event.learn_more": "למידע נוסף", + "notification.relationships_severance_event.user_domain_block": "חסמת את {target} ובכלל זה {followersCount} מעוקביך וגם {followingCount, plural, one {חשבון אחד} two {שני חשבונות} many {# חשבונות} other {# חשבונות}} מבין נעקביך.", "notification.status": "{name} הרגע פרסמו", "notification.update": "{name} ערכו הודעה", "notification_requests.accept": "לקבל", @@ -590,12 +608,6 @@ "refresh": "רענון", "regeneration_indicator.label": "טוען…", "regeneration_indicator.sublabel": "פיד הבית שלך בהכנה!", - "relationship_severance_notification.purged_data": "המידע נמחק על ידי ההנהלה", - "relationship_severance_notification.relationships": "{count, plural, one {קשר אחד} other {# קשרים}}", - "relationship_severance_notification.types.account_suspension": "החשבון הושעה", - "relationship_severance_notification.types.domain_block": "השרת הושעה", - "relationship_severance_notification.types.user_domain_block": "חסמת שרת זה", - "relationship_severance_notification.view": "הצג", "relative_time.days": "{number} ימים", "relative_time.full.days": "לפני {number, plural, one {# יום} other {# ימים}}", "relative_time.full.hours": "לפני {number, plural, one {# שעה} other {# שעות}}", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index d9dfd01a26..ba7fd6ddc2 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -124,7 +124,7 @@ "column.domain_blocks": "Letiltott domainek", "column.favourites": "Kedvencek", "column.firehose": "Hírfolyamok", - "column.follow_requests": "Követési kérelmek", + "column.follow_requests": "Követési kérések", "column.home": "Kezdőlap", "column.lists": "Listák", "column.mutes": "Némított felhasználók", @@ -133,8 +133,8 @@ "column.public": "Föderációs idővonal", "column_back_button.label": "Vissza", "column_header.hide_settings": "Beállítások elrejtése", - "column_header.moveLeft_settings": "Oszlop elmozdítása balra", - "column_header.moveRight_settings": "Oszlop elmozdítása jobbra", + "column_header.moveLeft_settings": "Oszlop áthelyezése balra", + "column_header.moveRight_settings": "Oszlop áthelyezése jobbra", "column_header.pin": "Kitűzés", "column_header.show_settings": "Beállítások megjelenítése", "column_header.unpin": "Kitűzés eltávolítása", @@ -143,7 +143,7 @@ "community.column_settings.media_only": "Csak média", "community.column_settings.remote_only": "Csak távoli", "compose.language.change": "Nyelv megváltoztatása", - "compose.language.search": "Nyelv keresése...", + "compose.language.search": "Nyelvek keresése…", "compose.published.body": "A bejegyzés publikálásra került.", "compose.published.open": "Megnyitás", "compose.saved.body": "A bejegyzés mentve.", @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "Válassz egy meglévő kategóriát, vagy hozz létre egy újat", "filter_modal.select_filter.title": "E bejegyzés szűrése", "filter_modal.title.status": "Egy bejegyzés szűrése", + "filtered_notifications_banner.mentions": "{count, plural, one {említés} other {említés}}", "filtered_notifications_banner.pending_requests": "Értesítések {count, plural, =0 {nincsenek} one {egy valósztínűleg ismerős személytől} other {# valószínűleg ismerős személytől}}", "filtered_notifications_banner.title": "Szűrt értesítések", "firehose.all": "Összes", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "Bár a fiókod nincs zárolva, a(z) {domain} csapata úgy gondolta, hogy talán kézzel szeretnéd ellenőrizni ezen fiókok követési kéréseit.", "follow_suggestions.curated_suggestion": "A stáb választása", "follow_suggestions.dismiss": "Ne jelenjen meg újra", + "follow_suggestions.featured_longer": "A {domain} csapata által kézzel kiválasztott", + "follow_suggestions.friends_of_friends_longer": "Népszerű az általad követett emberek körében", "follow_suggestions.hints.featured": "Ezt a profilt a(z) {domain} csapata választotta ki.", "follow_suggestions.hints.friends_of_friends": "Ez a profil népszerű az általad követett emberek körében.", "follow_suggestions.hints.most_followed": "Ez a profil a leginkább követett a(z) {domain} oldalon.", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Ez a profil hasonló azokhoz a profilokhoz, melyeket nemrég kezdtél el követni.", "follow_suggestions.personalized_suggestion": "Személyre szabott javaslat", "follow_suggestions.popular_suggestion": "Népszerű javaslat", + "follow_suggestions.popular_suggestion_longer": "Népszerű itt: {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Hasonló azokhoz a profilokhoz, melyeket nemrég követtél be", "follow_suggestions.view_all": "Összes megtekintése", "follow_suggestions.who_to_follow": "Kit érdemes követni", "followed_tags": "Követett hashtagek", @@ -468,10 +473,23 @@ "notification.follow": "{name} követ téged", "notification.follow_request": "{name} követni szeretne téged", "notification.mention": "{name} megemlített", + "notification.moderation-warning.learn_more": "További információ", + "notification.moderation_warning": "Moderációs figyelmeztetést kaptál", + "notification.moderation_warning.action_delete_statuses": "Néhány bejegyzésedet eltávolították.", + "notification.moderation_warning.action_disable": "A fiókod le van tiltva.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Néhány bejegyzésedet kényesnek jelölték.", + "notification.moderation_warning.action_none": "A fiókod moderációs figyelmeztetést kapott.", + "notification.moderation_warning.action_sensitive": "A bejegyzéseid mostantól érzékenynek lesznek jelölve.", + "notification.moderation_warning.action_silence": "A fiókod korlátozásra került.", + "notification.moderation_warning.action_suspend": "A fiókod felfüggesztésre került.", "notification.own_poll": "A szavazásod véget ért", "notification.poll": "Egy szavazás, melyben részt vettél, véget ért", "notification.reblog": "{name} megtolta a bejegyzésedet", - "notification.severed_relationships": "A kapcsolatok megszakítva ezzel: {name}", + "notification.relationships_severance_event": "Elvesztek a kapcsolatok vele: {name}", + "notification.relationships_severance_event.account_suspension": "Egy admin a(z) {from} kiszolgálóról felfüggesztette {target} fiókját, ami azt jelenti, hogy mostantól nem fogsz róla értesítést kapni, és nem fogsz tudni vele kapcsolatba lépni.", + "notification.relationships_severance_event.domain_block": "Egy admin a(z) {from} kiszolgálón letiltotta {target} domaint, beleértve {followersCount} követőt és {followingCount, plural, one {#} other {#}} követett fiókot.", + "notification.relationships_severance_event.learn_more": "További információk", + "notification.relationships_severance_event.user_domain_block": "Letiltottad a(z) {target} domaint, ezzel eltávolítva {followersCount} követőt és {followingCount, plural, one {#} other {#}} követett fiókot.", "notification.status": "{name} bejegyzést tett közzé", "notification.update": "{name} szerkesztett egy bejegyzést", "notification_requests.accept": "Elfogadás", @@ -484,8 +502,10 @@ "notifications.column_settings.admin.sign_up": "Új regisztrálók:", "notifications.column_settings.alert": "Asztali értesítések", "notifications.column_settings.favourite": "Kedvencek:", + "notifications.column_settings.filter_bar.advanced": "Minden kategória megjelenítése", + "notifications.column_settings.filter_bar.category": "Gyorsszűrő sáv", "notifications.column_settings.follow": "Új követők:", - "notifications.column_settings.follow_request": "Új követési kérelmek:", + "notifications.column_settings.follow_request": "Új követési kérések:", "notifications.column_settings.mention": "Megemlítések:", "notifications.column_settings.poll": "Szavazási eredmények:", "notifications.column_settings.push": "Leküldéses értesítések", @@ -545,14 +565,14 @@ "onboarding.share.next_steps": "Lehetséges következő lépések:", "onboarding.share.title": "Profil megosztása", "onboarding.start.lead": "Az új Mastodon-fiók használatra kész. Így hozhatod ki belőle a legtöbbet:", - "onboarding.start.skip": "Szeretnél előreugrani?", + "onboarding.start.skip": "Nincs szükséged segítségre a kezdéshez?", "onboarding.start.title": "Ez sikerült!", "onboarding.steps.follow_people.body": "A Mastodon az érdekes emberek követéséről szól.", - "onboarding.steps.follow_people.title": "{count, plural, one {egy ember} other {# ember}} követése", - "onboarding.steps.publish_status.body": "Üdvözöljük a világot.", + "onboarding.steps.follow_people.title": "Szabd személyre a kezdőlapodat", + "onboarding.steps.publish_status.body": "Köszöntsd a világot szöveggel, fotókkal, videókkal vagy szavazásokkal {emoji}", "onboarding.steps.publish_status.title": "Az első bejegyzés létrehozása", - "onboarding.steps.setup_profile.body": "Mások nagyobb valószínűséggel lépnek kapcsolatba veled egy kitöltött profil esetén.", - "onboarding.steps.setup_profile.title": "Profilod testreszabása", + "onboarding.steps.setup_profile.body": "Növeld az interakciók számát a profilod részletesebb kitöltésével.", + "onboarding.steps.setup_profile.title": "Szabd személyre a profilodat", "onboarding.steps.share_profile.body": "Tudasd az ismerőseiddel, hogyan találhatnak meg a Mastodonon", "onboarding.steps.share_profile.title": "Oszd meg a Mastodon profilodat", "onboarding.tips.2fa": "Tudtad? A fiókod biztonságossá teheted, ha a fiók beállításaiban beállítod a kétlépcsős hitelesítést. Bármilyen választott TOTP alkalmazással működik, nincs szükség telefonszámra!", @@ -588,12 +608,6 @@ "refresh": "Frissítés", "regeneration_indicator.label": "Betöltés…", "regeneration_indicator.sublabel": "A saját idővonalad épp készül!", - "relationship_severance_notification.purged_data": "rendszergazdák által véglegesen törölve", - "relationship_severance_notification.relationships": "{count, plural, one {# kapcsolat} other {# kapcsolat}}", - "relationship_severance_notification.types.account_suspension": "A fiók fel van függesztve", - "relationship_severance_notification.types.domain_block": "A domain fel van függesztve", - "relationship_severance_notification.types.user_domain_block": "Blokkoltad ezt a domaint", - "relationship_severance_notification.view": "Megtekintés", "relative_time.days": "{number}n", "relative_time.full.days": "{number, plural, one {# napja} other {# napja}}", "relative_time.full.hours": "{number, plural, one {# órája} other {# órája}}", @@ -786,9 +800,9 @@ "upload_modal.hint": "Kattints vagy húzd a kört az előnézetben arra a fókuszpontra, mely minden bélyegképen látható kell, hogy legyen.", "upload_modal.preparing_ocr": "OCR előkészítése…", "upload_modal.preview_label": "Előnézet ({ratio})", - "upload_progress.label": "Feltöltés...", + "upload_progress.label": "Feltöltés…", "upload_progress.processing": "Feldolgozás…", - "username.taken": "Ez a felhasználónév foglalt. Válassz másikat", + "username.taken": "Ez a felhasználónév foglalt. Válassz másikat.", "video.close": "Videó bezárása", "video.download": "Fájl letöltése", "video.exit_fullscreen": "Kilépés teljes képernyőből", @@ -798,5 +812,5 @@ "video.mute": "Hang némítása", "video.pause": "Szünet", "video.play": "Lejátszás", - "video.unmute": "Hang némításának vége" + "video.unmute": "Hang némításának feloldása" } diff --git a/app/javascript/mastodon/locales/ia.json b/app/javascript/mastodon/locales/ia.json index e587dbc52c..d30038d9cc 100644 --- a/app/javascript/mastodon/locales/ia.json +++ b/app/javascript/mastodon/locales/ia.json @@ -3,59 +3,66 @@ "about.contact": "Contacto:", "about.disclaimer": "Mastodon es software libere, de codice aperte, e un marca de Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Ration non disponibile", - "about.domain_blocks.preamble": "Mastodon generalmente permitte vider contento ab e interacter con usatores ab ulle altere servitor in le fediverso. Iste es le exceptiones que ha essite facite in iste servitor particular.", - "about.domain_blocks.silenced.explanation": "Generalmente non videras perfiles e contento de iste servitor, a minus que tu expressemente lo cerca o opta pro lo per sequer.", + "about.domain_blocks.preamble": "Mastodon generalmente permitte vider le contento de, e interager con, usatores de qualcunque altere servitor in le fediverso. Istes es le exceptiones que ha essite facite sur iste servitor particular.", + "about.domain_blocks.silenced.explanation": "Generalmente, tu non videra le profilos e le contento de iste servitor, excepte si tu expressemente cerca le contento o seque le profilos.", "about.domain_blocks.silenced.title": "Limitate", - "about.domain_blocks.suspended.explanation": "Nulle data de iste servitor essera processate, immagazinate o scambiate, faciente qualcunque interaction o communication con usatores de iste servitor impossibile.", + "about.domain_blocks.suspended.explanation": "Nulle datos de iste servitor essera processate, immagazinate o excambiate, rendente omne interaction o communication con usatores de iste servitor impossibile.", "about.domain_blocks.suspended.title": "Suspendite", - "about.not_available": "Iste information non faceva disponibile in iste servitor.", + "about.not_available": "Iste information non ha essite rendite disponibile sur iste servitor.", + "about.powered_by": "Rete social decentralisate, actionate per {mastodon}", "about.rules": "Regulas del servitor", "account.account_note_header": "Nota", - "account.add_or_remove_from_list": "Adder o remover ab listas", + "account.add_or_remove_from_list": "Adder a, o remover de listas", + "account.badges.bot": "Automatisate", "account.badges.group": "Gruppo", "account.block": "Blocar @{name}", "account.block_domain": "Blocar dominio {domain}", "account.block_short": "Blocar", "account.blocked": "Blocate", "account.browse_more_on_origin_server": "Navigar plus sur le profilo original", + "account.cancel_follow_request": "Cancellar sequimento", "account.copy": "Copiar ligamine a profilo", - "account.direct": "Mentionar privatemente a @{name}", - "account.disable_notifications": "Stoppar le notificationes quando @{name} publica", + "account.direct": "Mentionar privatemente @{name}", + "account.disable_notifications": "Non plus notificar me quando @{name} publica", "account.domain_blocked": "Dominio blocate", "account.edit_profile": "Modificar profilo", - "account.enable_notifications": "Notifica me quando @{name} publica", + "account.enable_notifications": "Notificar me quando @{name} publica", "account.endorse": "Evidentiar sur le profilo", - "account.featured_tags.last_status_at": "Ultime message in {date}", - "account.featured_tags.last_status_never": "Necun messages", + "account.featured_tags.last_status_at": "Ultime message publicate le {date}", + "account.featured_tags.last_status_never": "Necun message", "account.featured_tags.title": "Hashtags eminente de {name}", "account.follow": "Sequer", - "account.follow_back": "Sequer etiam", + "account.follow_back": "Sequer in retorno", "account.followers": "Sequitores", - "account.followers.empty": "Iste usator ancora non ha sequitores.", + "account.followers.empty": "Necuno seque ancora iste usator.", "account.followers_counter": "{count, plural, one {{counter} sequitor} other {{counter} sequitores}}", "account.following": "Sequente", - "account.follows.empty": "Iste usator ancora non seque nemo.", + "account.following_counter": "{count, plural, one {{counter} sequite} other {{counter} sequites}}", + "account.follows.empty": "Iste usator non seque ancora alcuno.", "account.go_to_profile": "Vader al profilo", - "account.hide_reblogs": "Celar boosts de @{name}", - "account.in_memoriam": "In Memoriam.", + "account.hide_reblogs": "Celar impulsos de @{name}", + "account.in_memoriam": "In memoriam.", + "account.joined_short": "Inscribite", "account.languages": "Cambiar le linguas subscribite", - "account.link_verified_on": "Le proprietate de iste ligamine esseva verificate le {date}", - "account.locked_info": "Le stato de confidentialitate de iste conto es definite a blocate. Le proprietario revisa manualmente qui pote sequer lo.", + "account.link_verified_on": "Le proprietate de iste ligamine ha essite verificate le {date}", + "account.locked_info": "Le stato de confidentialitate de iste conto es definite como serrate. Le proprietario determina manualmente qui pote sequer le.", "account.media": "Multimedia", "account.mention": "Mentionar @{name}", - "account.moved_to": "{name} indicava que lor nove conto ora es:", + "account.moved_to": "{name} ha indicate que su nove conto ora es:", "account.mute": "Silentiar @{name}", "account.mute_notifications_short": "Silentiar le notificationes", "account.mute_short": "Silentiar", "account.muted": "Silentiate", + "account.mutual": "Mutue", "account.no_bio": "Nulle description fornite.", "account.open_original_page": "Aperir le pagina original", "account.posts": "Messages", "account.posts_with_replies": "Messages e responsas", + "account.report": "Signalar @{name}", "account.requested": "Attendente le approbation. Clicca pro cancellar le requesta de sequer", - "account.requested_follow": "{name} habeva requestate sequer te", + "account.requested_follow": "{name} ha requestate de sequer te", "account.share": "Compartir profilo de @{name}", - "account.show_reblogs": "Monstrar responsas de @{name}", + "account.show_reblogs": "Monstrar impulsos de @{name}", "account.statuses_counter": "{count, plural, one {{counter} message} other {{counter} messages}}", "account.unblock": "Disblocar @{name}", "account.unblock_domain": "Disblocar dominio {domain}", @@ -66,23 +73,49 @@ "account.unmute_notifications_short": "Non plus silentiar le notificationes", "account.unmute_short": "Non plus silentiar", "account_note.placeholder": "Clicca pro adder un nota", - "admin.dashboard.retention.average": "Median", + "admin.dashboard.daily_retention": "Retention de usatores per die post inscription", + "admin.dashboard.monthly_retention": "Retention de usatores per mense post inscription", + "admin.dashboard.retention.average": "Media", + "admin.dashboard.retention.cohort": "Mense de inscription", "admin.dashboard.retention.cohort_size": "Nove usatores", - "admin.impact_report.instance_followers": "Sequitores que nostre usatores poterea perder", - "admin.impact_report.instance_follows": "Sequitores que lor usatores poterea perder", - "alert.rate_limited.message": "Retenta depost {retry_time, time, medium}.", - "alert.unexpected.message": "Ocurreva un error inexpectate.", + "admin.impact_report.instance_accounts": "Numero de contos que isto delerea", + "admin.impact_report.instance_followers": "Sequitores que nostre usatores perderea", + "admin.impact_report.instance_follows": "Sequitores que lor usatores perderea", + "admin.impact_report.title": "Summario de impacto", + "alert.rate_limited.message": "Per favor retenta post {retry_time, time, medium}.", + "alert.rate_limited.title": "Excesso de requestas", + "alert.unexpected.message": "Un error inexpectate ha occurrite.", + "alert.unexpected.title": "Ups!", "announcement.announcement": "Annuncio", + "attachments_list.unprocessed": "(non processate)", "audio.hide": "Celar audio", - "bundle_column_error.error.title": "Oh, non!", + "block_modal.remote_users_caveat": "Nos demandera al servitor {domain} de respectar tu decision. Nonobstante, le conformitate non es garantite perque alcun servitores pote tractar le blocadas de maniera differente. Le messages public pote esser totevia visibile pro le usatores non authenticate.", + "block_modal.show_less": "Monstrar minus", + "block_modal.show_more": "Monstrar plus", + "block_modal.they_cant_mention": "Le persona non pote mentionar te o sequer te.", + "block_modal.they_cant_see_posts": "Iste persona non potera vider tu messages e tu non videra le sues.", + "block_modal.they_will_know": "Le persona pote saper de esser blocate.", + "block_modal.title": "Blocar usator?", + "block_modal.you_wont_see_mentions": "Tu non videra le messages que mentiona iste persona.", + "boost_modal.combo": "Tu pote premer {combo} pro saltar isto le proxime vice", + "bundle_column_error.copy_stacktrace": "Copiar reporto de error", + "bundle_column_error.error.body": "Le pagina requestate non pote esser visualisate. Pote esser a causa de un defecto in nostre codice o de un problema de compatibilitate del navigator.", + "bundle_column_error.error.title": "Oh, no!", + "bundle_column_error.network.body": "Un error ha occurrite durante le cargamento de iste pagina. Isto pote esser a causa de un problema temporari con tu connexion a internet o con iste servitor.", "bundle_column_error.network.title": "Error de rete", "bundle_column_error.retry": "Tentar novemente", "bundle_column_error.return": "Retornar al initio", + "bundle_column_error.routing.body": "Le pagina requestate non pote esser trovate. Es tu secur que le URL in le barra de adresse es correcte?", + "bundle_column_error.routing.title": "404", "bundle_modal_error.close": "Clauder", + "bundle_modal_error.message": "Un error ha occurrite durante le cargamento de iste componente.", "bundle_modal_error.retry": "Tentar novemente", - "closed_registrations_modal.description": "Crear un conto in {domain} actualmente non es possibile, ma considera que tu non besonia un conto specific in {domain} pro usar Mastodon.", - "closed_registrations_modal.find_another_server": "Trovar altere servitor", - "column.about": "A proposito de", + "closed_registrations.other_server_instructions": "Perque Mastodon es decentralisate, tu pote crear un conto sur un altere servitor e totevia interager con iste servitor.", + "closed_registrations_modal.description": "Crear un conto in {domain} actualmente non es possibile, ma considera que non es necessari haber un conto specificamente sur {domain} pro usar Mastodon.", + "closed_registrations_modal.find_another_server": "Cercar un altere servitor", + "closed_registrations_modal.preamble": "Mastodon es decentralisate, dunque, non importa ubi tu crea tu conto, tu pote sequer e communicar con omne persona sur iste servitor. Tu pote mesmo hospitar tu proprie servitor!", + "closed_registrations_modal.title": "Crear un conto sur Mastodon", + "column.about": "A proposito", "column.blocks": "Usatores blocate", "column.bookmarks": "Marcapaginas", "column.community": "Chronologia local", @@ -90,35 +123,43 @@ "column.directory": "Navigar profilos", "column.domain_blocks": "Dominios blocate", "column.favourites": "Favoritos", - "column.firehose": "Fluxos in directe", + "column.firehose": "Fluxos in directo", + "column.follow_requests": "Requestas de sequimento", "column.home": "Initio", "column.lists": "Listas", "column.mutes": "Usatores silentiate", "column.notifications": "Notificationes", + "column.pins": "Messages fixate", "column.public": "Chronologia federate", "column_back_button.label": "Retro", "column_header.hide_settings": "Celar le parametros", "column_header.moveLeft_settings": "Mover columna al sinistra", "column_header.moveRight_settings": "Mover columna al dextra", + "column_header.pin": "Fixar", "column_header.show_settings": "Monstrar le parametros", + "column_header.unpin": "Disfixar", "column_subheading.settings": "Parametros", "community.column_settings.local_only": "Solmente local", - "community.column_settings.media_only": "Solmente medios", + "community.column_settings.media_only": "Solmente multimedia", + "community.column_settings.remote_only": "A distantia solmente", "compose.language.change": "Cambiar le lingua", "compose.language.search": "Cercar linguas...", "compose.published.body": "Message publicate.", "compose.published.open": "Aperir", "compose.saved.body": "Message salvate.", "compose_form.direct_message_warning_learn_more": "Apprender plus", + "compose_form.encryption_warning": "Le messages sur Mastodon non es cryptate de puncta a puncta. Non condivide alcun information sensibile usante Mastodon.", + "compose_form.hashtag_warning": "Iste message non essera listate sub alcun hashtag perque illo non es public. Solmente le messages public pote esser cercate per hashtag.", "compose_form.lock_disclaimer": "Tu conto non es {locked}. Quicunque pote sequer te pro vider tu messages solo pro sequitores.", - "compose_form.lock_disclaimer.lock": "blocate", - "compose_form.poll.duration": "Duration del inquesta", + "compose_form.lock_disclaimer.lock": "serrate", + "compose_form.placeholder": "Que ha tu in mente?", + "compose_form.poll.duration": "Durata del sondage", "compose_form.poll.multiple": "Selection multiple", "compose_form.poll.option_placeholder": "Option {number}", "compose_form.poll.single": "Seliger un", - "compose_form.poll.switch_to_multiple": "Cambiar inquesta pro permitter selectiones multiple", - "compose_form.poll.switch_to_single": "Cambiar inquesta pro permitter selection singule", - "compose_form.poll.type": "Stylo", + "compose_form.poll.switch_to_multiple": "Cambiar le sondage pro permitter selectiones multiple", + "compose_form.poll.switch_to_single": "Cambiar le sondage pro permitter selection singule", + "compose_form.poll.type": "Stilo", "compose_form.publish": "Publicar", "compose_form.publish_form": "Nove message", "compose_form.reply": "Responder", @@ -129,18 +170,26 @@ "confirmation_modal.cancel": "Cancellar", "confirmations.block.confirm": "Blocar", "confirmations.cancel_follow_request.confirm": "Retirar requesta", - "confirmations.cancel_follow_request.message": "Es tu secur que tu vole retirar tu requesta a sequer a {name}?", + "confirmations.cancel_follow_request.message": "Es tu secur que tu vole retirar tu requesta de sequer {name}?", "confirmations.delete.confirm": "Deler", "confirmations.delete.message": "Es tu secur que tu vole deler iste message?", "confirmations.delete_list.confirm": "Deler", "confirmations.delete_list.message": "Es tu secur que tu vole deler permanentemente iste lista?", + "confirmations.discard_edit_media.confirm": "Abandonar", + "confirmations.discard_edit_media.message": "Tu ha cambiamentos non salvate in le description o previsualisation del objecto multimedial. Abandonar los?", + "confirmations.domain_block.confirm": "Blocar le servitor", + "confirmations.domain_block.message": "Es tu realmente, absolutemente secur de voler blocar tote le dominio {domain}? In le major parte del casos es preferibile blocar o silentiar alcun personas specific. Si tu bloca tote le dominio, tu non videra alcun contento de ille dominio in alcun chronologia public o in tu notificationes, e tu sequitores de ille dominio essera removite.", "confirmations.edit.confirm": "Modificar", - "confirmations.logout.confirm": "Clauder le session", + "confirmations.edit.message": "Si tu modifica isto ora, le message in curso de composition essera perdite. Es tu secur de voler continuar?", + "confirmations.logout.confirm": "Clauder session", "confirmations.logout.message": "Es tu secur que tu vole clauder le session?", "confirmations.mute.confirm": "Silentiar", + "confirmations.redraft.confirm": "Deler e rescriber", + "confirmations.redraft.message": "Es tu secur de voler deler iste message e rescriber lo? Le favorites e le impulsos essera perdite, e le responsas al message original essera orphanate.", "confirmations.reply.confirm": "Responder", + "confirmations.reply.message": "Si tu responde ora, le message in curso de composition essera perdite. Es tu secur de voler continuar?", "confirmations.unfollow.confirm": "Non plus sequer", - "confirmations.unfollow.message": "Es tu secur que tu vole non plus sequer {name}?", + "confirmations.unfollow.message": "Es tu secur que tu vole cessar de sequer {name}?", "conversation.delete": "Deler conversation", "conversation.mark_as_read": "Marcar como legite", "conversation.open": "Vider conversation", @@ -154,9 +203,35 @@ "directory.recently_active": "Recentemente active", "disabled_account_banner.account_settings": "Parametros de conto", "disabled_account_banner.text": "Tu conto {disabledAccount} es actualmente disactivate.", + "dismissable_banner.community_timeline": "Ecce le messages public le plus recente del personas con contos sur {domain}.", "dismissable_banner.dismiss": "Dimitter", + "dismissable_banner.explore_links": "Istes es le articulos de novas que se condivide le plus sur le rete social hodie. Le articulos de novas le plus recente, publicate per plus personas differente, se classifica plus in alto.", + "dismissable_banner.explore_statuses": "Ecce le messages de tote le rete social que gania popularitate hodie. Le messages plus nove con plus impulsos e favorites se classifica plus in alto.", + "dismissable_banner.explore_tags": "Ecce le hashtags que gania popularitate sur le rete social hodie. Le hashtags usate per plus personas differente se classifica plus in alto.", + "dismissable_banner.public_timeline": "Istes es le messages public le plus recente del personas sur le rete social que le gente sur {domain} seque.", + "domain_block_modal.block": "Blocar le servitor", + "domain_block_modal.block_account_instead": "Blocar @{name} in su loco", + "domain_block_modal.they_can_interact_with_old_posts": "Le personas de iste servitor pote interager con tu messages ancian.", + "domain_block_modal.they_cant_follow": "Nulle persona ab iste servitor pote sequer te.", + "domain_block_modal.they_wont_know": "Illes non sapera que illes ha essite blocate.", + "domain_block_modal.title": "Blocar dominio?", + "domain_block_modal.you_will_lose_followers": "Omne sequitores ab iste servitor essera removite.", + "domain_block_modal.you_wont_see_posts": "Tu non videra messages e notificationes ab usatores sur iste servitor.", + "domain_pill.activitypub_lets_connect": "Illo te permitte connecter e interager con personas non solmente sur Mastodon, ma tamben sur altere applicationes social.", + "domain_pill.activitypub_like_language": "ActivityPub es como le linguage commun que Mastodon parla con altere retes social.", + "domain_pill.server": "Servitor", + "domain_pill.their_handle": "Su pseudonymo:", + "domain_pill.their_server": "Su casa digital, ubi vive tote su messages.", + "domain_pill.their_username": "Su identificator unic sur su servitor. Es possibile trovar usatores con le mesme nomine de usator sur servitores differente.", "domain_pill.username": "Nomine de usator", - "embed.preview": "Hic es como il parera:", + "domain_pill.whats_in_a_handle": "Que significa un pseudonymo?", + "domain_pill.who_they_are": "Un pseudonymo indica qui un persona es e ubi se trova, de maniera que tu pote interager con personas sur tote le rete social de .", + "domain_pill.who_you_are": "Perque tu pseudonymo indica qui tu es e ubi tu te trova, le gente pote interager con te desde tote le rete social de .", + "domain_pill.your_handle": "Tu pseudonymo:", + "domain_pill.your_server": "Tu casa digital, ubi vive tote tu messages. Non te place? Cambia de servitor a omne momento e porta tu sequitores con te.", + "domain_pill.your_username": "Tu identificator unic sur iste servitor. Es possibile trovar usatores con le mesme nomine de usator sur servitores differente.", + "embed.instructions": "Incorpora iste message sur tu sito web con le codice sequente.", + "embed.preview": "Ecce como illlo parera:", "emoji_button.activity": "Activitate", "emoji_button.clear": "Rader", "emoji_button.custom": "Personalisate", @@ -164,22 +239,41 @@ "emoji_button.food": "Alimentos e bibitas", "emoji_button.label": "Inserer emoji", "emoji_button.nature": "Natura", + "emoji_button.not_found": "Necun emoji correspondente trovate", + "emoji_button.objects": "Objectos", "emoji_button.people": "Personas", "emoji_button.recent": "Frequentemente usate", "emoji_button.search": "Cercar...", "emoji_button.search_results": "Resultatos de recerca", "emoji_button.symbols": "Symbolos", "emoji_button.travel": "Viages e locos", - "empty_column.account_hides_collections": "Le usator ha seligite non facer iste information disponibile", + "empty_column.account_hides_collections": "Le usator non ha rendite iste information disponibile", "empty_column.account_suspended": "Conto suspendite", "empty_column.account_timeline": "Nulle messages hic!", "empty_column.account_unavailable": "Profilo non disponibile", "empty_column.blocks": "Tu non ha blocate alcun usator ancora.", + "empty_column.bookmarked_statuses": "Tu non ha ancora messages in marcapaginas. Quando tu adde un message al marcapaginas, illo apparera hic.", + "empty_column.community": "Le chronologia local es vacue. Scribe qualcosa public pro poner le cosas in marcha!", + "empty_column.direct": "Tu non ha ancora mentiones private. Quando tu invia o recipe un mention, illo apparera hic.", "empty_column.domain_blocks": "Il non ha dominios blocate ancora.", - "empty_column.explore_statuses": "Nihil es in tendentias ora mesme. Retorna postea!", - "empty_column.favourited_statuses": "Tu non ha necun messages favorite ancora. Quando tu marca un como favorito, ille essera monstrate hic.", - "empty_column.followed_tags": "Tu ancora non ha sequite necun hashtags. Quando tu lo face, illes essera monstrate hic.", - "empty_column.hashtag": "Ancora non il ha nihil in iste hashtag.", + "empty_column.explore_statuses": "Il non ha tendentias in iste momento. Reveni plus tarde!", + "empty_column.favourited_statuses": "Tu non ha alcun message favorite ancora. Quando tu marca un message como favorite, illo apparera hic.", + "empty_column.favourites": "Necuno ha ancora marcate iste message como favorite. Quando alcuno lo face, ille apparera hic.", + "empty_column.follow_requests": "Tu non ha ancora requestas de sequimento. Quando tu recipe un, illo apparera hic.", + "empty_column.followed_tags": "Tu non ha ancora sequite alcun hashtags. Quando tu lo face, illos apparera hic.", + "empty_column.hashtag": "Il non ha ancora alcun cosa in iste hashtag.", + "empty_column.home": "Tu chronologia de initio es vacue! Seque plus personas pro plenar lo.", + "empty_column.list": "Iste lista es ancora vacue. Quando le membros de iste lista publica nove messages, illos apparera hic.", + "empty_column.lists": "Tu non ha ancora listas. Quando tu crea un, illo apparera hic.", + "empty_column.mutes": "Tu non ha ancora silentiate alcun usator.", + "empty_column.notification_requests": "Iste lista es toto vacue! Quando tu recipe notificationes, illos apparera hic como configurate in tu parametros.", + "empty_column.notifications": "Tu non ha ancora notificationes. Quando altere personas interage con te, tu lo videra hic.", + "empty_column.public": "Il ha nihil hic! Scribe qualcosa public, o manualmente seque usatores de altere servitores, pro plenar lo", + "error.unexpected_crash.explanation": "A causa de un defecto in nostre codice o de un problema de compatibilitate del navigator, iste pagina non pote esser visualisate correctemente.", + "error.unexpected_crash.explanation_addons": "Iste pagina non pote esser visualisate correctemente. Iste error es probabilemente causate per un additivo al navigator o per un utensile de traduction automatic.", + "error.unexpected_crash.next_steps": "Tenta refrescar le pagina. Si isto non remedia le problema, es possibile que tu pote totevia usar Mastodon per medio de un altere navigator o application native.", + "error.unexpected_crash.next_steps_addons": "Tenta disactivar istes e refrescar le pagina. Si isto non remedia le problema, es possibile que tu pote totevia usar Mastodon per medio de un altere navigator o application native.", + "errors.unexpected_crash.copy_stacktrace": "Copiar le traciamento del pila al area de transferentia", "errors.unexpected_crash.report_issue": "Signalar un defecto", "explore.search_results": "Resultatos de recerca", "explore.suggested_follows": "Personas", @@ -187,25 +281,50 @@ "explore.trending_links": "Novas", "explore.trending_statuses": "Messages", "explore.trending_tags": "Hashtags", + "filter_modal.added.context_mismatch_explanation": "Iste categoria de filtros non se applica al contexto in le qual tu ha accedite a iste message. Pro filtrar le message in iste contexto tamben, modifica le filtro.", + "filter_modal.added.context_mismatch_title": "Contexto incoherente!", + "filter_modal.added.expired_explanation": "Iste categoria de filtros ha expirate. Tu debe modificar le data de expiration pro applicar lo.", + "filter_modal.added.expired_title": "Filtro expirate!", + "filter_modal.added.review_and_configure": "Pro revider e configurar ulteriormente iste categoria de filtros, visita le {settings_link}.", "filter_modal.added.review_and_configure_title": "Parametros de filtro", "filter_modal.added.settings_link": "pagina de parametros", - "filter_modal.added.short_explanation": "Iste message esseva addite al sequente categoria de filtros: {title}.", + "filter_modal.added.short_explanation": "Iste message ha essite addite al sequente categoria de filtros: {title}.", "filter_modal.added.title": "Filtro addite!", + "filter_modal.select_filter.context_mismatch": "non se applica a iste contexto", + "filter_modal.select_filter.expired": "expirate", "filter_modal.select_filter.prompt_new": "Nove categoria: {name}", "filter_modal.select_filter.search": "Cercar o crear", + "filter_modal.select_filter.subtitle": "Usa un categoria existente o crea un nove", "filter_modal.select_filter.title": "Filtrar iste message", "filter_modal.title.status": "Filtrar un message", + "filtered_notifications_banner.mentions": "{count, plural, one {mention} other {mentiones}}", + "filtered_notifications_banner.pending_requests": "Notificationes ab {count, plural, =0 {nemo} one {un persona} other {# personas}} tu poterea cognoscer", + "filtered_notifications_banner.title": "Notificationes filtrate", "firehose.all": "Toto", "firehose.local": "Iste servitor", "firehose.remote": "Altere servitores", + "follow_request.authorize": "Autorisar", "follow_request.reject": "Rejectar", + "follow_requests.unlocked_explanation": "Benque tu conto non es serrate, le personal de {domain} pensa que es un bon idea que tu revide manualmente le sequente requestas de iste contos.", + "follow_suggestions.curated_suggestion": "Selection del equipa", "follow_suggestions.dismiss": "Non monstrar novemente", + "follow_suggestions.featured_longer": "Seligite con cura per le equipa de {domain}", + "follow_suggestions.friends_of_friends_longer": "Popular inter le gente que tu seque", + "follow_suggestions.hints.featured": "Iste profilo ha essite seligite manualmente per le equipa de {domain}.", + "follow_suggestions.hints.friends_of_friends": "Iste profilo es popular inter le gente que tu seque.", + "follow_suggestions.hints.most_followed": "Iste profilo es un del plus sequites sur {domain}.", + "follow_suggestions.hints.most_interactions": "Iste profilo ha recentemente recipite multe attention sur {domain}.", + "follow_suggestions.hints.similar_to_recently_followed": "Iste profilo es similar al profilos que tu ha recentemente sequite.", "follow_suggestions.personalized_suggestion": "Suggestion personalisate", "follow_suggestions.popular_suggestion": "Suggestion personalisate", + "follow_suggestions.popular_suggestion_longer": "Popular sur {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Similar al profilos que tu ha sequite recentemente", "follow_suggestions.view_all": "Vider toto", - "footer.about": "A proposito de", + "follow_suggestions.who_to_follow": "Qui sequer", + "followed_tags": "Hashtags sequite", + "footer.about": "A proposito", "footer.directory": "Directorio de profilos", - "footer.get_app": "Obtene le application", + "footer.get_app": "Obtener le application", "footer.invite": "Invitar personas", "footer.keyboard_shortcuts": "Accessos directe de claviero", "footer.privacy_policy": "Politica de confidentialitate", @@ -218,60 +337,112 @@ "hashtag.column_header.tag_mode.none": "sin {additional}", "hashtag.column_settings.select.no_options_message": "Nulle suggestiones trovate", "hashtag.column_settings.select.placeholder": "Insere hashtags…", + "hashtag.column_settings.tag_mode.all": "Tote istes", + "hashtag.column_settings.tag_mode.any": "Un o plus de istes", + "hashtag.column_settings.tag_mode.none": "Necun de istes", + "hashtag.column_settings.tag_toggle": "Includer etiquettas additional pro iste columna", + "hashtag.counter_by_accounts": "{count, plural, one {{counter} participante} other {{counter} participantes}}", + "hashtag.counter_by_uses": "{count, plural, one {{counter} message} other {{counter} messages}}", + "hashtag.counter_by_uses_today": "{count, plural, one {{counter} message} other {{counter} messages}} hodie", "hashtag.follow": "Sequer hashtag", "hashtag.unfollow": "Non sequer plus le hashtag", "hashtags.and_other": "…e {count, plural, one {}other {# plus}}", - "home.column_settings.show_reblogs": "Monstrar boosts", + "home.column_settings.show_reblogs": "Monstrar impulsos", "home.column_settings.show_replies": "Monstrar responsas", "home.hide_announcements": "Celar annuncios", "home.pending_critical_update.body": "Actualisa tu servitor de Mastodon le plus tosto possibile!", "home.pending_critical_update.link": "Vider actualisationes", "home.pending_critical_update.title": "Actualisation de securitate critic disponibile!", "home.show_announcements": "Monstrar annuncios", - "interaction_modal.login.prompt": "Dominio de tu servitor, p.e. mastodon.social", + "interaction_modal.description.favourite": "Con un conto sur Mastodon, tu pote marcar iste message como favorite pro informar le autor que tu lo apprecia e salveguarda pro plus tarde.", + "interaction_modal.description.follow": "Con un conto sur Mastodon, tu pote sequer {name} e reciper su messages in tu fluxo de initio.", + "interaction_modal.description.reblog": "Con un conto sur Mastodon, tu pote impulsar iste message pro condivider lo con tu proprie sequitores.", + "interaction_modal.description.reply": "Con un conto sur Mastodon, tu pote responder a iste message.", + "interaction_modal.login.action": "Porta me a casa", + "interaction_modal.login.prompt": "Dominio de tu servitor, p.ex. mastodon.social", "interaction_modal.no_account_yet": "Non sur Mstodon?", - "interaction_modal.on_another_server": "In un servitor differente", - "interaction_modal.on_this_server": "In iste servitor", + "interaction_modal.on_another_server": "Sur un altere servitor", + "interaction_modal.on_this_server": "Sur iste servitor", + "interaction_modal.sign_in": "Tu non es in session sur iste servitor. Sur qual servitor se trova tu conto?", + "interaction_modal.sign_in_hint": "Consilio: Se tracta del sito web ubi tu te ha inscribite. Si tu non te lo rememora, cerca le e-mail de benvenita in tu cassa de entrata. Tu pote etiam inserer tu pseudonymo complete! (p.ex. @Mastodon@mastodon.social)", + "interaction_modal.title.favourite": "Marcar le message de {name} como favorite", "interaction_modal.title.follow": "Sequer {name}", - "interaction_modal.title.reblog": "Facer boost al message de {name}", + "interaction_modal.title.reblog": "Impulsar le message de {name}", "interaction_modal.title.reply": "Responder al message de {name}", + "intervals.full.days": "{number, plural, one {# die} other {# dies}}", + "intervals.full.hours": "{number, plural, one {# hora} other {# horas}}", + "intervals.full.minutes": "{number, plural, one {# minuta} other {# minutas}}", + "keyboard_shortcuts.back": "Navigar retro", "keyboard_shortcuts.blocked": "Aperir lista de usatores blocate", - "keyboard_shortcuts.boost": "Facer boost al message", + "keyboard_shortcuts.boost": "Impulsar le message", + "keyboard_shortcuts.column": "Focalisar al columna", + "keyboard_shortcuts.compose": "Focalisar al area de composition de texto", "keyboard_shortcuts.description": "Description", + "keyboard_shortcuts.direct": "aperir le columna de mentiones private", + "keyboard_shortcuts.down": "Displaciar a basso in le lista", "keyboard_shortcuts.enter": "Aperir message", + "keyboard_shortcuts.favourite": "Message favorite", "keyboard_shortcuts.favourites": "Aperir lista de favoritos", "keyboard_shortcuts.federated": "Aperir le chronologia federate", "keyboard_shortcuts.heading": "Accessos directe de claviero", "keyboard_shortcuts.home": "Aperir le chronologia de initio", + "keyboard_shortcuts.hotkey": "Clave accelerator", + "keyboard_shortcuts.legend": "Monstrar iste legenda", "keyboard_shortcuts.local": "Aperir le chronologia local", + "keyboard_shortcuts.mention": "Mentionar le author", "keyboard_shortcuts.muted": "Aperir lista de usatores silentiate", "keyboard_shortcuts.my_profile": "Aperir tu profilo", "keyboard_shortcuts.notifications": "Aperir columna de notificationes", - "keyboard_shortcuts.open_media": "Aperir medio", + "keyboard_shortcuts.open_media": "Aperir multimedia", + "keyboard_shortcuts.pinned": "Aperir le lista de messages fixate", "keyboard_shortcuts.profile": "Aperir le profilo del autor", "keyboard_shortcuts.reply": "Responder al message", + "keyboard_shortcuts.requests": "Aperir le lista de requestas de sequimento", + "keyboard_shortcuts.search": "Focalisar barra de recerca", "keyboard_shortcuts.spoilers": "Monstrar/celar le campo CW", - "keyboard_shortcuts.toggle_sensitivity": "Monstrar/celar medios", + "keyboard_shortcuts.start": "Aperir columna “comenciar”", + "keyboard_shortcuts.toggle_hidden": "Monstrar/celar texto detra advertimento de contento", + "keyboard_shortcuts.toggle_sensitivity": "Monstrar/celar multimedia", "keyboard_shortcuts.toot": "Initiar un nove message", + "keyboard_shortcuts.unfocus": "Disfocalisar le area de composition de texto/de recerca", + "keyboard_shortcuts.up": "Displaciar in alto in le lista", "lightbox.close": "Clauder", + "lightbox.compress": "Comprimer le quadro de visualisation de imagine", + "lightbox.expand": "Expander le quadro de visualisation de imagine", "lightbox.next": "Sequente", "lightbox.previous": "Precedente", + "limited_account_hint.action": "Monstrar profilo in omne caso", + "limited_account_hint.title": "Iste profilo esseva celate per le moderatores de {domain}.", "link_preview.author": "Per {name}", "lists.account.add": "Adder al lista", - "lists.account.remove": "Remover ab le lista", + "lists.account.remove": "Remover del lista", "lists.delete": "Deler lista", "lists.edit": "Modificar lista", "lists.edit.submit": "Cambiar titulo", - "lists.exclusive": "Celar iste messages ab le initio", + "lists.exclusive": "Celar iste messages sur le pagina de initio", "lists.new.create": "Adder lista", "lists.new.title_placeholder": "Nove titulo del lista", + "lists.replies_policy.followed": "Qualcunque usator sequite", + "lists.replies_policy.list": "Membros del lista", "lists.replies_policy.none": "Nemo", "lists.replies_policy.title": "Monstrar responsas a:", + "lists.search": "Cercar inter le gente que tu seque", "lists.subheading": "Tu listas", + "load_pending": "{count, plural, one {# nove entrata} other {# nove entratas}}", "loading_indicator.label": "Cargante…", "media_gallery.toggle_visible": "{number, plural, one {Celar imagine} other {Celar imagines}}", - "navigation_bar.about": "A proposito de", - "navigation_bar.advanced_interface": "Aperir in un interfacie web avantiate", + "moved_to_account_banner.text": "Tu conto {disabledAccount} es actualmente disactivate perque tu ha cambiate de conto a {movedToAccount}.", + "mute_modal.hide_from_notifications": "Celar ab notificationes", + "mute_modal.hide_options": "Celar optiones", + "mute_modal.indefinite": "Usque io dissilentia iste persona", + "mute_modal.show_options": "Monstrar optiones", + "mute_modal.they_can_mention_and_follow": "Illes pote mentionar te e sequer te, ma tu non potera vider los.", + "mute_modal.they_wont_know": "Illes non sapera que illes ha essite silentiate.", + "mute_modal.title": "Silentiar le usator?", + "mute_modal.you_wont_see_mentions": "Tu non videra le messages que mentiona iste persona.", + "mute_modal.you_wont_see_posts": "Iste persona pote totevia vider tu messages, ma tu non videra le sues.", + "navigation_bar.about": "A proposito", + "navigation_bar.advanced_interface": "Aperir in le interfacie web avantiate", "navigation_bar.blocks": "Usatores blocate", "navigation_bar.bookmarks": "Marcapaginas", "navigation_bar.community_timeline": "Chronologia local", @@ -282,116 +453,360 @@ "navigation_bar.explore": "Explorar", "navigation_bar.favourites": "Favoritos", "navigation_bar.filters": "Parolas silentiate", + "navigation_bar.follow_requests": "Requestas de sequimento", + "navigation_bar.followed_tags": "Hashtags sequite", + "navigation_bar.follows_and_followers": "Sequites e sequitores", "navigation_bar.lists": "Listas", "navigation_bar.logout": "Clauder le session", "navigation_bar.mutes": "Usatores silentiate", "navigation_bar.opened_in_classic_interface": "Messages, contos e altere paginas specific es aperite per predefinition in le interfacie web classic.", "navigation_bar.personal": "Personal", + "navigation_bar.pins": "Messages fixate", "navigation_bar.preferences": "Preferentias", "navigation_bar.public_timeline": "Chronologia federate", "navigation_bar.search": "Cercar", "navigation_bar.security": "Securitate", - "notification.own_poll": "Tu inquesta finiva", - "notification.update": "{name} modificava un message", + "not_signed_in_indicator.not_signed_in": "Es necessari aperir session pro acceder a iste ressource.", + "notification.admin.report": "{name} ha signalate {target}", + "notification.admin.sign_up": "{name} se ha inscribite", + "notification.favourite": "{name} ha marcate tu message como favorite", + "notification.follow": "{name} te ha sequite", + "notification.follow_request": "{name} ha requestate de sequer te", + "notification.mention": "{name} te ha mentionate", + "notification.moderation-warning.learn_more": "Apprender plus", + "notification.moderation_warning": "Tu ha recipite un advertimento de moderation", + "notification.moderation_warning.action_delete_statuses": "Alcunes de tu messages ha essite removite.", + "notification.moderation_warning.action_disable": "Tu conto ha essite disactivate.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Alcunes de tu messages ha essite marcate como sensibile.", + "notification.moderation_warning.action_none": "Tu conto ha recipite un advertimento de moderation.", + "notification.moderation_warning.action_sensitive": "Tu messages essera marcate como sensibile a partir de ora.", + "notification.moderation_warning.action_silence": "Tu conto ha essite limitate.", + "notification.moderation_warning.action_suspend": "Tu conto ha essite suspendite.", + "notification.own_poll": "Tu sondage ha finite", + "notification.poll": "Un sondage in le qual tu ha votate ha finite", + "notification.reblog": "{name} ha impulsate tu message", + "notification.relationships_severance_event": "Connexiones perdite con {name}", + "notification.relationships_severance_event.account_suspension": "Un administrator de {from} ha suspendiute {target}. Isto significa que tu non pote plus reciper actualisationes de iste persona o interager con ille.", + "notification.relationships_severance_event.domain_block": "Un administrator de {from} ha blocate {target}, includente {followersCount} de tu sequitores e {followingCount, plural, one {# conto} other {# contos}} que tu seque.", + "notification.relationships_severance_event.learn_more": "Apprender plus", + "notification.relationships_severance_event.user_domain_block": "Tu ha blocate {target}, assi removente {followersCount} de tu sequitores e {followingCount, plural, one {# conto} other {# contos}} que tu seque.", + "notification.status": "{name} ha justo ora publicate", + "notification.update": "{name} ha modificate un message", + "notification_requests.accept": "Acceptar", + "notification_requests.dismiss": "Dimitter", + "notification_requests.notifications_from": "Notificationes de {name}", + "notification_requests.title": "Notificationes filtrate", "notifications.clear": "Rader notificationes", + "notifications.clear_confirmation": "Es tu secur que tu vole rader permanentemente tote tu notificationes?", + "notifications.column_settings.admin.report": "Nove signalationes:", + "notifications.column_settings.admin.sign_up": "Nove inscriptiones:", "notifications.column_settings.alert": "Notificationes de scriptorio", "notifications.column_settings.favourite": "Favoritos:", + "notifications.column_settings.filter_bar.advanced": "Monstrar tote le categorias", + "notifications.column_settings.filter_bar.category": "Barra de filtro rapide", "notifications.column_settings.follow": "Nove sequitores:", + "notifications.column_settings.follow_request": "Nove requestas de sequimento:", "notifications.column_settings.mention": "Mentiones:", - "notifications.column_settings.poll": "Resultatos del inquesta:", + "notifications.column_settings.poll": "Resultatos del sondage:", "notifications.column_settings.push": "Notificationes push", + "notifications.column_settings.reblog": "Impulsos:", "notifications.column_settings.show": "Monstrar in columna", "notifications.column_settings.sound": "Reproducer sono", "notifications.column_settings.status": "Nove messages:", "notifications.column_settings.unread_notifications.category": "Notificationes non legite", + "notifications.column_settings.unread_notifications.highlight": "Marcar le notificationes non legite", + "notifications.column_settings.update": "Modificationes:", "notifications.filter.all": "Toto", + "notifications.filter.boosts": "Impulsos", "notifications.filter.favourites": "Favoritos", + "notifications.filter.follows": "Sequites", "notifications.filter.mentions": "Mentiones", - "notifications.filter.polls": "Resultatos del inquesta", + "notifications.filter.polls": "Resultatos del sondage", "notifications.filter.statuses": "Actualisationes de personas que tu seque", "notifications.grant_permission": "Conceder permission.", "notifications.group": "{count} notificationes", + "notifications.mark_as_read": "Marcar cata notification como legite", + "notifications.permission_denied": "Le notificationes de scriptorio es indisponibile a causa de un requesta anteriormente refusate de permissiones del navigator", + "notifications.permission_denied_alert": "Le notificationes de scriptorio non pote esser activate perque le permission del navigator ha essite refusate anteriormente", + "notifications.permission_required": "Le notificationes de scriptorio es indisponibile perque le permission necessari non ha essite concedite.", + "notifications.policy.filter_new_accounts.hint": "Create in le ultime {days, plural, one {die} other {# dies}}", + "notifications.policy.filter_new_accounts_title": "Nove contos", + "notifications.policy.filter_not_followers_hint": "Includente le personas que te ha sequite durante minus de {days, plural, one {un die} other {# dies}}", + "notifications.policy.filter_not_followers_title": "Personas qui non te seque", + "notifications.policy.filter_not_following_hint": "Usque tu les approba manualmente", + "notifications.policy.filter_not_following_title": "Personas que tu non seque", + "notifications.policy.filter_private_mentions_hint": "Filtrate, excepte si es in responsa a tu proprie mention o si tu seque le expeditor", + "notifications.policy.filter_private_mentions_title": "Mentiones private indesirate", + "notifications.policy.title": "Filtrar notificationes de…", "notifications_permission_banner.enable": "Activar notificationes de scriptorio", + "notifications_permission_banner.how_to_control": "Pro reciper notificationes quando Mastodon non es aperte, activa le notificationes de scriptorio. Post lor activation, es possibile controlar precisemente qual typos de interaction genera notificationes de scriptorio per medio del button {icon} hic supra.", + "notifications_permission_banner.title": "Non mancar jammais a un cosa", + "onboarding.action.back": "Porta me retro", + "onboarding.actions.back": "Porta me retro", + "onboarding.actions.go_to_explore": "Porta me al tendentias", + "onboarding.actions.go_to_home": "Porta me a mi fluxo de initio", "onboarding.compose.template": "Salute #Mastodon!", + "onboarding.follows.empty": "Regrettabilemente, non es possibile monstrar resultatos al momento. Tu pote tentar usar le recerca o percurrer le pagina de exploration pro cercar personas a sequer, o tentar lo de novo plus tarde.", + "onboarding.follows.lead": "Le fluxo de initio es le maniera principal de discoperir Mastodon. Quanto plus personas tu seque, tanto plus active e interessante illo essera. Pro comenciar, ecce alcun suggestiones:", + "onboarding.follows.title": "Personalisar tu fluxo de initio", + "onboarding.profile.discoverable": "Render mi profilo discoperibile", + "onboarding.profile.discoverable_hint": "Quando tu opta pro devenir discoperibile sur Mastodon, tu messages pote apparer in resultatos de recerca e in tendentias, e tu profilo pote esser suggerite al personas con interesses simile al tues.", + "onboarding.profile.display_name": "Nomine a monstrar", + "onboarding.profile.display_name_hint": "Tu nomine complete o tu supernomine…", + "onboarding.profile.lead": "Tu pote sempre completar isto plus tarde in le parametros, ubi se trova mesmo plus optiones de personalisation.", + "onboarding.profile.note": "Bio", + "onboarding.profile.note_hint": "Tu pote @mentionar altere personas o #hashtags…", "onboarding.profile.save_and_continue": "Salvar e continuar", + "onboarding.profile.title": "Configuration del profilo", + "onboarding.profile.upload_avatar": "Incargar imagine de profilo", + "onboarding.profile.upload_header": "Actualisar capite de profilo", + "onboarding.share.lead": "Face saper al gente como illes pote trovar te sur Mastodon!", + "onboarding.share.message": "Io es {username} sur Mastodon! Veni sequer me a {url}", "onboarding.share.next_steps": "Sequente passos possibile:", "onboarding.share.title": "Compartir tu profilo", - "onboarding.steps.follow_people.title": "Personalisa tu fluxo de initio", + "onboarding.start.lead": "Tu face ora parte de Mastodon, un platteforma de medios social unic e decentralisate ubi es tu, e non un algorithmo, qui crea tu proprie experientia. Nos va adjutar te a lancear te in iste nove frontiera social:", + "onboarding.start.skip": "Non require adjuta a comenciar?", + "onboarding.start.title": "Tu ha arrivate!", + "onboarding.steps.follow_people.body": "Sequer personas interessante es le ration de esser de Mastodon.", + "onboarding.steps.follow_people.title": "Personalisar tu fluxo de initio", + "onboarding.steps.publish_status.body": "Saluta le mundo con texto, photos, videos o sondages {emoji}", "onboarding.steps.publish_status.title": "Face tu prime message", + "onboarding.steps.setup_profile.body": "Impulsa tu interactiones con un profilo comprehensive.", "onboarding.steps.setup_profile.title": "Personalisa tu profilo", + "onboarding.steps.share_profile.body": "Face saper a tu amicos como trovar te sur Mastodon", "onboarding.steps.share_profile.title": "Compartir tu profilo de Mastodon", + "onboarding.tips.2fa": "Lo sapeva tu? Tu pote securisar tu conto configurante le authentication bifactorial in le parametros de tu conto. Isto functiona con le application TOTP de tu preferentia, sin necessitate de un numero de telephono!", + "onboarding.tips.accounts_from_other_servers": "Lo sapeva tu? Perque Mastodon es decentralisate, le profilos que tu incontra pote esser hospitate sur servitores altere que le tue. Nonobstante, tu pote interager con illos sin problema! Lor servitor se trova in le secunde medietate de lor nomine de usator!", + "onboarding.tips.migration": "Lo sapeva tu? Si tu pensa que {domain} non es un bon servitor pro te in le futuro, tu pote cambiar a un altere servitor Mastodon sin perder tu sequitores. Tu pote mesmo hospitar tu proprie servitor!", + "onboarding.tips.verification": "Lo sapeva tu? Pro verificar tu conto, insere un ligamine a tu profilo Mastodon sur tu proprie sito web e adde le sito web a tu profilo. Nulle moneta o documentos necessari!", + "password_confirmation.exceeds_maxlength": "Le confirmation del contrasigno excede le longitude maxime del contrasigno", + "password_confirmation.mismatching": "Le confirmation del contrasigno non corresponde", + "picture_in_picture.restore": "Restaurar", "poll.closed": "Claudite", + "poll.refresh": "Refrescar", "poll.reveal": "Vider le resultatos", - "privacy.change": "Cambiar privacitate del message", + "poll.total_people": "{count, plural, one {# persona} other {# personas}}", + "poll.total_votes": "{count, plural, one {# voto} other {# votos}}", + "poll.vote": "Votar", + "poll.voted": "Tu ha votate pro iste responsa", + "poll.votes": "{votes, plural, one {# voto} other {# votos}}", + "poll_button.add_poll": "Adder un inquesta", + "poll_button.remove_poll": "Remover un inquesta", + "privacy.change": "Cambiar le confidentialitate del message", + "privacy.direct.long": "Tote le personas mentionate in le message", + "privacy.direct.short": "Personas specific", + "privacy.private.long": "Solmente tu sequitores", + "privacy.private.short": "Sequitores", + "privacy.public.long": "Quicunque, sur Mastodon o non", "privacy.public.short": "Public", + "privacy.unlisted.additional": "Isto es exactemente como public, excepte que le message non apparera in fluxos in directo, in hashtags, in Explorar, o in le recerca de Mastodon, mesmo si tu ha optate pro render tote le conto discoperibile.", + "privacy.unlisted.long": "Minus fanfares algorithmic", + "privacy.unlisted.short": "Public, non listate", "privacy_policy.last_updated": "Ultime actualisation {date}", "privacy_policy.title": "Politica de confidentialitate", + "recommended": "Recommendate", + "refresh": "Refrescar", + "regeneration_indicator.label": "Cargamento…", + "regeneration_indicator.sublabel": "Tu fluxo de initio es in preparation!", + "relative_time.days": "{number}d", + "relative_time.full.days": "{number, plural, one {# die} other {# dies}} retro", + "relative_time.full.hours": "{number, plural, one {# hora} other {# horas}} retro", + "relative_time.full.just_now": "justo ora", + "relative_time.full.minutes": "{number, plural, one {# minuta} other {# minutas}} retro", + "relative_time.full.seconds": "{number, plural, one {# secunda} other {# secundas}} retro", + "relative_time.hours": "{number}h", "relative_time.just_now": "ora", + "relative_time.minutes": "{number}m", + "relative_time.seconds": "{number}s", "relative_time.today": "hodie", + "reply_indicator.attachments": "{count, plural, one {# annexo} other {# annexos}}", "reply_indicator.cancel": "Cancellar", + "reply_indicator.poll": "Inquesta", "report.block": "Blocar", + "report.block_explanation": "Tu non videra le messages de iste persona. Ille non potera vider tu messages o sequer te. Ille potera saper de esser blocate.", + "report.categories.legal": "Juridic", "report.categories.other": "Alteres", + "report.categories.spam": "Spam", + "report.categories.violation": "Le contento viola un o plus regulas del servitor", + "report.category.subtitle": "Elige le option plus adequate", + "report.category.title": "Describe le problema con iste {type}", "report.category.title_account": "profilo", "report.category.title_status": "message", - "report.close": "Preste", + "report.close": "Facite", + "report.comment.title": "Ha il altere cosas que nos deberea saper?", + "report.forward": "Reinviar a {target}", + "report.forward_hint": "Le conto es de un altere servitor. Inviar un copia anonymisate del signalation a illo tamben?", "report.mute": "Silentiar", + "report.mute_explanation": "Tu non videra le messages de iste persona. Ille pote totevia sequer te e vider tu messages e non sapera de esser silentiate.", "report.next": "Sequente", "report.placeholder": "Commentos additional", "report.reasons.dislike": "Non me place", + "report.reasons.dislike_description": "Non es qualcosa que tu vole vider", + "report.reasons.legal": "Es illegal", + "report.reasons.legal_description": "Tu crede que viola le lege de tu pais o del pais del servitor", + "report.reasons.other": "Es altere cosa", + "report.reasons.other_description": "Le problema non entra in altere categorias", + "report.reasons.spam": "Es spam", + "report.reasons.spam_description": "Ligamines malevolente, interaction false, o responsas repetitive", + "report.reasons.violation": "Viola le regulas del servitor", + "report.reasons.violation_description": "Tu sape que viola regulas specific", + "report.rules.subtitle": "Selige tote le responsas appropriate", + "report.rules.title": "Qual regulas es violate?", + "report.statuses.subtitle": "Selige tote le responsas appropriate", + "report.statuses.title": "Existe alcun messages que appoia iste reporto?", + "report.submit": "Submitter", + "report.target": "Signalamento de {target}", + "report.thanks.take_action": "Ecce tu optiones pro controlar lo que tu vide sur Mastodon:", + "report.thanks.take_action_actionable": "Durante que nos revide isto, tu pote prender mesuras contra @{name}:", + "report.thanks.title": "Non vole vider isto?", + "report.thanks.title_actionable": "Gratias pro signalar, nos investigara isto.", + "report.unfollow": "Cessar de sequer @{name}", + "report.unfollow_explanation": "Tu seque iste conto. Pro non plus vider su messages in tu fluxo de initio, cessa de sequer lo.", + "report_notification.attached_statuses": "{count, plural, one {{count} message} other {{count} messages}} annexate", + "report_notification.categories.legal": "Juridic", "report_notification.categories.other": "Alteres", + "report_notification.categories.spam": "Spam", + "report_notification.categories.violation": "Violation del regulas", "report_notification.open": "Aperir reporto", "search.no_recent_searches": "Nulle recercas recente", + "search.placeholder": "Cercar", + "search.quick_action.account_search": "Profilos correspondente a {x}", "search.quick_action.go_to_account": "Vader al profilo {x}", "search.quick_action.go_to_hashtag": "Vader al hashtag {x}", "search.quick_action.open_url": "Aperir URL in Mastodon", + "search.quick_action.status_search": "Messages correspondente a {x}", + "search.search_or_paste": "Cerca o colla un URL", "search_popout.full_text_search_disabled_message": "Non disponibile sur {domain}.", + "search_popout.full_text_search_logged_out_message": "Solmente disponibile al initiar le session.", "search_popout.language_code": "Codice de lingua ISO", "search_popout.options": "Optiones de recerca", "search_popout.quick_actions": "Actiones rapide", "search_popout.recent": "Recercas recente", + "search_popout.specific_date": "data specific", "search_popout.user": "usator", "search_results.accounts": "Profilos", + "search_results.all": "Toto", "search_results.hashtags": "Hashtags", + "search_results.nothing_found": "Nihil trovate pro iste terminos de recerca", "search_results.see_all": "Vider toto", "search_results.statuses": "Messages", + "search_results.title": "Cercar {q}", + "server_banner.about_active_users": "Personas que ha usate iste servitor in le ultime 30 dies (usatores active per mense)", "server_banner.active_users": "usatores active", + "server_banner.administered_by": "Administrate per:", + "server_banner.introduction": "{domain} face parte del rete social decentralisate actionate per {mastodon}.", "server_banner.learn_more": "Apprender plus", "server_banner.server_stats": "Statos del servitor:", "sign_in_banner.create_account": "Crear un conto", - "sign_in_banner.sign_in": "Initiar le session", + "sign_in_banner.sign_in": "Aperir session", + "sign_in_banner.sso_redirect": "Aperir session o crear conto", + "sign_in_banner.text": "Aperi session pro sequer profilos o hashtags, marcar messages como favorite, e condivider e responder a messages. Tu pote etiam interager desde tu conto sur un altere servitor.", + "status.admin_account": "Aperir le interfacie de moderation pro @{name}", + "status.admin_domain": "Aperir le interfacie de moderation pro {domain}", + "status.admin_status": "Aperir iste message in le interfacie de moderation", "status.block": "Blocar @{name}", + "status.bookmark": "Adder al marcapaginas", + "status.cancel_reblog_private": "Disfacer impulso", + "status.cannot_reblog": "Iste message non pote esser impulsate", "status.copy": "Copiar ligamine a message", "status.delete": "Deler", - "status.direct": "Mentionar privatemente a @{name}", + "status.detailed_status": "Vista detaliate del conversation", + "status.direct": "Mentionar privatemente @{name}", "status.direct_indicator": "Mention private", "status.edit": "Modificar", - "status.edited_x_times": "Modificate {count, plural, one {{count} tempore} other {{count} tempores}}", + "status.edited": "Ultime modification le {date}", + "status.edited_x_times": "Modificate {count, plural, one {{count} vice} other {{count} vices}}", + "status.embed": "Incastrar", "status.favourite": "Adder al favoritos", + "status.favourites": "{count, plural, one {favorite} other {favorites}}", "status.filter": "Filtrar iste message", + "status.filtered": "Filtrate", "status.hide": "Celar le message", "status.history.created": "create per {name} le {date}", "status.history.edited": "modificate per {name} le {date}", + "status.load_more": "Cargar plus", "status.media.open": "Clicca pro aperir", "status.media.show": "Clicca pro monstrar", + "status.media_hidden": "Medios celate", + "status.mention": "Mentionar @{name}", "status.more": "Plus", + "status.mute": "Silentiar @{name}", "status.mute_conversation": "Silentiar conversation", + "status.open": "Expander iste message", + "status.pin": "Fixar sur profilo", + "status.pinned": "Message fixate", "status.read_more": "Leger plus", + "status.reblog": "Impulsar", + "status.reblog_private": "Impulsar con visibilitate original", + "status.reblogged_by": "Impulsate per {name}", + "status.reblogs": "{count, plural, one {impulso} other {impulsos}}", + "status.reblogs.empty": "Necuno ha ancora impulsate iste message. Quando alcuno lo face, le impulsos apparera hic.", + "status.redraft": "Deler e reconciper", + "status.remove_bookmark": "Remover marcapagina", + "status.replied_to": "Respondite a {name}", + "status.reply": "Responder", + "status.replyAll": "Responder al discussion", + "status.report": "Signalar @{name}", + "status.sensitive_warning": "Contento sensibile", "status.share": "Compartir", + "status.show_filter_reason": "Monstrar in omne caso", "status.show_less": "Monstrar minus", + "status.show_less_all": "Monstrar minus pro totes", "status.show_more": "Monstrar plus", + "status.show_more_all": "Monstrar plus pro totes", + "status.show_original": "Monstrar original", + "status.title.with_attachments": "{user} ha publicate {attachmentCount, plural, one {un annexo} other {{attachmentCount} annexos}}", "status.translate": "Traducer", "status.translated_from_with": "Traducite ab {lang} usante {provider}", "status.uncached_media_warning": "Previsualisation non disponibile", + "status.unmute_conversation": "Non plus silentiar conversation", + "status.unpin": "Disfixar del profilo", + "subscribed_languages.lead": "Solmente le messages in le linguas seligite apparera in tu chronologias de initio e de listas post le cambiamento. Selige necun pro reciper messages in tote le linguas.", "subscribed_languages.save": "Salveguardar le cambiamentos", + "subscribed_languages.target": "Cambiar le linguas subscribite pro {target}", "tabs_bar.home": "Initio", "tabs_bar.notifications": "Notificationes", + "time_remaining.days": "{number, plural, one {# die} other {# dies}} restante", + "time_remaining.hours": "{number, plural, one {# hora} other {# horas}} restante", + "time_remaining.minutes": "{number, plural, one {# minuta} other {# minutas}} restante", + "time_remaining.moments": "Qualque momentos restante", + "time_remaining.seconds": "{number, plural, one {# secunda} other {# secundas}} restante", + "timeline_hint.remote_resource_not_displayed": "Le {resource} de altere servitores non appare hic.", + "timeline_hint.resources.followers": "Sequitores", + "timeline_hint.resources.follows": "Sequites", "timeline_hint.resources.statuses": "Messages ancian", + "trends.counter_by_accounts": "{count, plural, one {{counter} persona} other {{counter} personas}} in le passate {days, plural, one {die} other {{days} dies}}", "trends.trending_now": "Ora in tendentias", + "ui.beforeunload": "Tu esbosso essera predite si tu exi de Mastodon.", + "units.short.billion": "{count}B", + "units.short.million": "{count}M", + "units.short.thousand": "{count}K", + "upload_area.title": "Traher e deponer pro incargar", "upload_button.label": "Adde imagines, un video o un file de audio", + "upload_error.limit": "Limite de incargamento de files excedite.", + "upload_error.poll": "Incargamento de files non permittite con sondages.", + "upload_form.audio_description": "Describe lo pro le gente con difficultates auditive", + "upload_form.description": "Describe lo pro le gente con difficultates visual", + "upload_form.edit": "Modificar", + "upload_form.thumbnail": "Cambiar le miniatura", + "upload_form.video_description": "Describe lo pro le gente con difficultates auditive o visual", + "upload_modal.analyzing_picture": "Analysa imagine…", + "upload_modal.apply": "Applicar", + "upload_modal.applying": "Applicante…", "upload_modal.choose_image": "Seliger un imagine", - "upload_modal.detect_text": "Deteger texto ab un pictura", + "upload_modal.description_placeholder": "Cinque expertos del zoo jam bibeva whisky frigide", + "upload_modal.detect_text": "Deteger texto de un imagine", + "upload_modal.edit_media": "Modificar le medio", + "upload_modal.hint": "Clicca o trahe le circulo sur le previsualisation pro eliger le puncto focal que essera sempre visibile sur tote le miniaturas.", + "upload_modal.preparing_ocr": "Preparation del OCR…", + "upload_modal.preview_label": "Previsualisation ({ratio})", + "upload_progress.label": "Incargante...", + "upload_progress.processing": "Processante…", + "username.taken": "Iste nomine de usator es ja in uso. Proba con un altere", "video.close": "Clauder le video", "video.download": "Discargar le file", + "video.exit_fullscreen": "Sortir del schermo plen", + "video.expand": "Expander video", "video.fullscreen": "Schermo plen", "video.hide": "Celar video", "video.mute": "Silentiar le sono", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index 764c081119..33161f8882 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -21,6 +21,7 @@ "account.blocked": "Terblokir", "account.browse_more_on_origin_server": "Lihat lebih lanjut di profil asli", "account.cancel_follow_request": "Batalkan permintaan ikut", + "account.copy": "Salin tautan ke profil", "account.direct": "Sebut secara pribadi @{name}", "account.disable_notifications": "Berhenti memberitahu saya ketika @{name} memposting", "account.domain_blocked": "Domain diblokir", @@ -31,6 +32,7 @@ "account.featured_tags.last_status_never": "Tidak ada kiriman", "account.featured_tags.title": "Tagar {name} yang difiturkan", "account.follow": "Ikuti", + "account.follow_back": "Ikuti balik", "account.followers": "Pengikut", "account.followers.empty": "Pengguna ini belum ada pengikut.", "account.followers_counter": "{count, plural, other {{counter} Pengikut}}", @@ -51,6 +53,7 @@ "account.mute_notifications_short": "Senyapkan Notifikasi", "account.mute_short": "Senyapkan", "account.muted": "Dibisukan", + "account.mutual": "Saling ikuti", "account.no_bio": "Tidak ada deskripsi yang diberikan.", "account.open_original_page": "Buka halaman asli", "account.posts": "Kiriman", @@ -75,6 +78,10 @@ "admin.dashboard.retention.average": "Rata-rata", "admin.dashboard.retention.cohort": "Bulan pendaftaran", "admin.dashboard.retention.cohort_size": "Pengguna baru", + "admin.impact_report.instance_accounts": "Akun profil yang akan terhapus", + "admin.impact_report.instance_followers": "Pengikut yang akan kehilangan oleh pengguna kita", + "admin.impact_report.instance_follows": "Pengikut yang akan kehilangan oleh pengguna lain", + "admin.impact_report.title": "Ringkasan dampak", "alert.rate_limited.message": "Mohon ulangi setelah {retry_time, time, medium}.", "alert.rate_limited.title": "Jumlah akses dibatasi", "alert.unexpected.message": "Terjadi kesalahan yang tidak terduga.", @@ -82,6 +89,14 @@ "announcement.announcement": "Pengumuman", "attachments_list.unprocessed": "(tidak diproses)", "audio.hide": "Sembunyikan audio", + "block_modal.remote_users_caveat": "Kami akan meminta {domain} server untuk menghargai keputusan Anda. Namun, kepatuhan tak dapat dipastikan karena beberapa server dapat menangani blokir secara beragam. Postingan publik masih dapat terlihat oleh pengguna tanpa masuk.", + "block_modal.show_less": "Tampilkan lebih sedikit", + "block_modal.show_more": "Tampilkan lebih banyak", + "block_modal.they_cant_mention": "Mereka tidak dapat menyebut atau mengikuti Anda.", + "block_modal.they_cant_see_posts": "Mereka tidak dapat melihat postingan Anda dan Anda tidak dapat melihat postingan mereka.", + "block_modal.they_will_know": "Mereka dapat melihat bahwa mereka diblokir.", + "block_modal.title": "Blokir pengguna?", + "block_modal.you_wont_see_mentions": "Anda tidak akan melihat kiriman yang menyebutkan mereka.", "boost_modal.combo": "Anda dapat menekan {combo} untuk melewati ini", "bundle_column_error.copy_stacktrace": "Salin laporan kesalahan", "bundle_column_error.error.body": "Laman yang diminta tidak dapat ditampilkan. Mungkin karena sebuah kutu dalam kode kami, atau masalah kompatibilitas peramban.", @@ -104,8 +119,11 @@ "column.blocks": "Pengguna yang diblokir", "column.bookmarks": "Markah", "column.community": "Linimasa Lokal", + "column.direct": "Sebut secara pribadi", "column.directory": "Jelajahi profil", "column.domain_blocks": "Domain tersembunyi", + "column.favourites": "Favorit", + "column.firehose": "Feed yang sedang berlangsung", "column.follow_requests": "Permintaan mengikuti", "column.home": "Beranda", "column.lists": "List", @@ -126,7 +144,9 @@ "community.column_settings.remote_only": "Hanya jarak jauh", "compose.language.change": "Ganti bahasa", "compose.language.search": "Telusuri bahasa...", + "compose.published.body": "Postingan diterbitkan.", "compose.published.open": "Buka", + "compose.saved.body": "Postingan tersimpan.", "compose_form.direct_message_warning_learn_more": "Pelajari lebih lanjut", "compose_form.encryption_warning": "Kiriman di Mastodon tidak dienkripsi secara end-to-end. Jangan bagikan informasi sensitif melalui Mastodon.", "compose_form.hashtag_warning": "Kiriman ini tidak akan didaftarkan di bawah tagar apapun selama tidak diatur ke publik. Hanya kiriman publik yang dapat dicari dengan tagar.", @@ -134,11 +154,19 @@ "compose_form.lock_disclaimer.lock": "terkunci", "compose_form.placeholder": "Apa yang ada di pikiran Anda?", "compose_form.poll.duration": "Durasi japat", + "compose_form.poll.multiple": "Pilihan ganda", + "compose_form.poll.option_placeholder": "Opsi {number}", + "compose_form.poll.single": "Pilih Satu", "compose_form.poll.switch_to_multiple": "Ubah japat menjadi pilihan ganda", "compose_form.poll.switch_to_single": "Ubah japat menjadi pilihan tunggal", + "compose_form.poll.type": "Gaya", + "compose_form.publish": "Postingan", "compose_form.publish_form": "Terbitkan", + "compose_form.reply": "Balas", + "compose_form.save_changes": "Perbarui", "compose_form.spoiler.marked": "Hapus peringatan tentang isi konten", "compose_form.spoiler.unmarked": "Tambahkan peringatan tentang isi konten", + "compose_form.spoiler_placeholder": "Peringatan konten (opsional)", "confirmation_modal.cancel": "Batal", "confirmations.block.confirm": "Blokir", "confirmations.cancel_follow_request.confirm": "Batalkan permintaan", @@ -149,12 +177,15 @@ "confirmations.delete_list.message": "Apakah Anda yakin untuk menghapus daftar ini secara permanen?", "confirmations.discard_edit_media.confirm": "Buang", "confirmations.discard_edit_media.message": "Anda belum menyimpan perubahan deskripsi atau pratinjau media, buang saja?", + "confirmations.domain_block.confirm": "Blokir server", "confirmations.domain_block.message": "Apakah Anda benar-benar yakin untuk memblokir keseluruhan {domain}? Dalam kasus tertentu beberapa pemblokiran atau penyembunyian lebih baik.", "confirmations.edit.confirm": "Ubah", + "confirmations.edit.message": "Mengubah akan menimpa pesan yang sedang anda tulis. Apakah anda yakin ingin melanjutkan?", "confirmations.logout.confirm": "Keluar", "confirmations.logout.message": "Apakah Anda yakin ingin keluar?", "confirmations.mute.confirm": "Bisukan", "confirmations.redraft.confirm": "Hapus dan susun ulang", + "confirmations.redraft.message": "Apakah anda yakin ingin menghapus postingan ini dan menyusun ulang postingan ini? Favorit dan peningkatan akan hilang, dan balasan ke postingan asli tidak akan terhubung ke postingan manapun.", "confirmations.reply.confirm": "Balas", "confirmations.reply.message": "Membalas sekarang akan menimpa pesan yang sedang Anda buat. Anda yakin ingin melanjutkan?", "confirmations.unfollow.confirm": "Berhenti mengikuti", @@ -163,6 +194,7 @@ "conversation.mark_as_read": "Tandai sudah dibaca", "conversation.open": "Lihat percakapan", "conversation.with": "Dengan {names}", + "copy_icon_button.copied": "Disalin ke clipboard", "copypaste.copied": "Disalin", "copypaste.copy_to_clipboard": "Salin ke clipboard", "directory.federated": "Dari fediverse yang dikenal", @@ -174,7 +206,27 @@ "dismissable_banner.community_timeline": "Ini adalah kiriman publik terkini dari orang yang akunnya berada di {domain}.", "dismissable_banner.dismiss": "Abaikan", "dismissable_banner.explore_links": "Cerita berita ini sekarang sedang dibicarakan oleh orang di server ini dan lainnya dalam jaringan terdesentralisasi.", + "dismissable_banner.explore_statuses": "Ini adalah postingan dari seluruh web sosial yang mendapatkan daya tarik saat ini. Postingan baru dengan lebih banyak peningkatan dan favorit memiliki peringkat lebih tinggi.", "dismissable_banner.explore_tags": "Tagar ini sekarang sedang tren di antara orang di server ini dan lainnya dalam jaringan terdesentralisasi.", + "dismissable_banner.public_timeline": "Ini adalah postingan publik dari orang-orang di web sosial yang diikuti oleh {domain}.", + "domain_block_modal.block": "Blokir server", + "domain_block_modal.block_account_instead": "Blokir @{name} saja", + "domain_block_modal.they_can_interact_with_old_posts": "Orang-orang dari server ini dapat berinteraksi dengan kiriman lama anda.", + "domain_block_modal.they_cant_follow": "Tidak ada seorangpun dari server ini yang dapat mengikuti anda.", + "domain_block_modal.they_wont_know": "Mereka tidak akan tahu bahwa mereka diblokir.", + "domain_block_modal.title": "Blokir domain?", + "domain_block_modal.you_will_lose_followers": "Semua pengikut anda dari server ini akan dihapus.", + "domain_block_modal.you_wont_see_posts": "Anda tidak akan melihat postingan atau notifikasi dari pengguna di server ini.", + "domain_pill.activitypub_lets_connect": "Ini memungkinkan anda terhubung dan berinteraksi dengan orang-orang tidak hanya di Mastodon, tetapi juga di berbagai aplikasi sosial.", + "domain_pill.activitypub_like_language": "ActivityPub seperti bahasa yang digunakan Mastodon dengan jejaring sosial lainnya.", + "domain_pill.server": "Server", + "domain_pill.their_handle": "Nama penggunanya:", + "domain_pill.their_server": "Rumah digital mereka, di mana semua postingan mereka tersedia.", + "domain_pill.their_username": "Pengenal unik mereka di server tersebut. Itu memungkinkan dapat mencari pengguna dengan nama yang sama di server lain.", + "domain_pill.username": "Nama pengguna", + "domain_pill.whats_in_a_handle": "Apa itu nama pengguna?", + "domain_pill.who_they_are": "Karena nama pengguna menunjukkan siapa seseorang dan di mana server mereka berada, anda dapat berinteraksi dengan orang-orang di seluruh web sosial .", + "domain_pill.your_handle": "Nama pengguna anda:", "embed.instructions": "Sematkan kiriman ini di situs web Anda dengan menyalin kode di bawah ini.", "embed.preview": "Tampilan akan seperti ini nantinya:", "emoji_button.activity": "Aktivitas", @@ -243,6 +295,10 @@ "follow_request.authorize": "Izinkan", "follow_request.reject": "Tolak", "follow_requests.unlocked_explanation": "Meskipun akun Anda tidak dikunci, staf {domain} menyarankan Anda untuk meninjau permintaan mengikuti dari akun-akun ini secara manual.", + "follow_suggestions.curated_suggestion": "Pilihan staf", + "follow_suggestions.dismiss": "Jangan tampilkan lagi", + "follow_suggestions.hints.featured": "Profil ini telah dipilih sendiri oleh tim {domain}.", + "follow_suggestions.hints.friends_of_friends": "Profil ini populer di kalangan orang yang anda ikuti.", "followed_tags": "Tagar yang diikuti", "footer.about": "Tentang", "footer.directory": "Direktori profil", diff --git a/app/javascript/mastodon/locales/ie.json b/app/javascript/mastodon/locales/ie.json index 17f312418d..8d491412c0 100644 --- a/app/javascript/mastodon/locales/ie.json +++ b/app/javascript/mastodon/locales/ie.json @@ -91,6 +91,9 @@ "audio.hide": "Celar audio", "block_modal.show_less": "Monstrar minu", "block_modal.show_more": "Monstrar plu", + "block_modal.they_cant_mention": "Ne posse mentionar ni sequer te.", + "block_modal.they_cant_see_posts": "Ne posse vider tui postas e inversi.", + "block_modal.they_will_know": "Va esser conscient que tu ha bloccat.", "block_modal.title": "Bloccar usator?", "block_modal.you_wont_see_mentions": "Tu ne va vider postas mentionant li usator.", "boost_modal.combo": "Li proxim vez tu posse pressar {combo} por passar to-ci", @@ -206,6 +209,16 @@ "dismissable_banner.explore_tags": "Tis-ci es hashtags queles es popular che li social retage hodie. Hashtags usat de plu mult persones diferent es monstrat plu alt.", "dismissable_banner.public_timeline": "Tis-ci es li max recent public postas de persones che li social retage quem gente che {domain} seque.", "domain_block_modal.block": "Bloccar servitor", + "domain_block_modal.block_account_instead": "Altrimen, bloccar @{name}", + "domain_block_modal.they_can_interact_with_old_posts": "Persones de ti servitor posse interacter con tui old postas.", + "domain_block_modal.they_cant_follow": "Nequi de ti-ci servitor posse sequer te.", + "domain_pill.activitypub_like_language": "ActivityPub es li lingue usat de Mastodon por parlar con altri social retages.", + "domain_pill.server": "Servitor", + "domain_pill.their_handle": "Identificator:", + "domain_pill.their_server": "Su digital hem e omni su postas.", + "domain_pill.username": "Usator-nómine", + "domain_pill.whats_in_a_handle": "Ex quo consiste un identificator?", + "domain_pill.your_handle": "Tui identificator:", "embed.instructions": "Inbedar ti-ci posta per copiar li code in infra.", "embed.preview": "Vi qualmen it va aspecter:", "emoji_button.activity": "Activitá", @@ -242,6 +255,7 @@ "empty_column.list": "Ancor ne hay quocunc in ti-ci liste. Quande membres de ti-ci liste publica nov postas, ili va aparir ci.", "empty_column.lists": "Tu ancor have null listes. Quande tu crea un, it va aparir ci.", "empty_column.mutes": "Tu ancor ha silentiat null usatores.", + "empty_column.notification_requests": "Omnicos clar! Hay necos ci. Nov notificationes va venir ci quande tu recive les secun tui parametres.", "empty_column.notifications": "Tu have null notificationes. Quande altri persones interacte con te, tu va vider it ci.", "empty_column.public": "Hay nullcos ci! Scri alquo publicmen, o manualmen seque usatores de altri servitores por plenar to-ci", "error.unexpected_crash.explanation": "Pro un error in nor code o un problema de compatibilitá in li navigator, ti-ci págine ne posset esser monstrat correctmen.", @@ -272,6 +286,8 @@ "filter_modal.select_filter.subtitle": "Usar un existent categorie o crear nov", "filter_modal.select_filter.title": "Filtrar ti-ci posta", "filter_modal.title.status": "Filtrar un posta", + "filtered_notifications_banner.pending_requests": "Notificationes de {count, plural, =0 {nequi} one {un person} other {# persones}} quel tu possibilmen conosse", + "filtered_notifications_banner.title": "Filtrat notificationes", "firehose.all": "Omno", "firehose.local": "Ti-ci servitor", "firehose.remote": "Altri servitores", @@ -400,6 +416,13 @@ "loading_indicator.label": "Cargant…", "media_gallery.toggle_visible": "{number, plural, one {Celar image} other {Celar images}}", "moved_to_account_banner.text": "Tui conto {disabledAccount} es actualmen desactivisat pro que tu movet te a {movedToAccount}.", + "mute_modal.hide_from_notifications": "Celar de notificationes", + "mute_modal.hide_options": "Celar optiones", + "mute_modal.indefinite": "Til quande yo dessilentia li usator", + "mute_modal.show_options": "Monstrar optiones", + "mute_modal.they_can_mention_and_follow": "Posse mentionar e sequer te, ma va esser ínvisibil a te.", + "mute_modal.they_wont_know": "Ne va esser conscient pri li silentation.", + "mute_modal.title": "Silentiar usator?", "navigation_bar.about": "Information", "navigation_bar.advanced_interface": "Aperter in li web-interfacie avansat", "navigation_bar.blocks": "Bloccat usatores", @@ -435,14 +458,20 @@ "notification.own_poll": "Tui balotation ha finit", "notification.poll": "Un balotation in quel tu votat ha finit", "notification.reblog": "{name} boostat tui posta", + "notification.relationships_severance_event.learn_more": "Aprender plu", "notification.status": "{name} just postat", "notification.update": "{name} modificat un posta", + "notification_requests.accept": "Acceptar", + "notification_requests.dismiss": "Demisser", + "notification_requests.notifications_from": "Notificationes de {name}", + "notification_requests.title": "Filtrat notificationes", "notifications.clear": "Aclarar notificationes", "notifications.clear_confirmation": "Vole tu vermen permanentmen aclarar omni tui notificationes?", "notifications.column_settings.admin.report": "Nov raportas:", "notifications.column_settings.admin.sign_up": "Nov registrationes:", "notifications.column_settings.alert": "Notificationes sur li computator", "notifications.column_settings.favourite": "Favorites:", + "notifications.column_settings.filter_bar.advanced": "Monstrar omni categories", "notifications.column_settings.follow": "Nov sequitores:", "notifications.column_settings.follow_request": "Nov petitiones de sequer:", "notifications.column_settings.mention": "Mentiones:", @@ -468,6 +497,15 @@ "notifications.permission_denied": "Notificationes sur li computator es índisponibil pro que on ha previamen rejectet un petition por navigator-permissiones", "notifications.permission_denied_alert": "Notificationes sur li computator ne posse esser activisat, pro que navigator-permission ha esset previamen rejectet", "notifications.permission_required": "Notificationes sur li computator es índisponibil pro que li besonat permission ne ha esset dat.", + "notifications.policy.filter_new_accounts.hint": "Creat durant li passat {days, plural, one {un die} other {# dies}}", + "notifications.policy.filter_new_accounts_title": "Nov contos", + "notifications.policy.filter_not_followers_hint": "Includente tis qui ha sequet te minu quam {days, plural, one {un die} other {# dies}}", + "notifications.policy.filter_not_followers_title": "Persones qui ne seque te", + "notifications.policy.filter_not_following_hint": "Til quande tu manualmen aproba", + "notifications.policy.filter_not_following_title": "Persones queles tu ne seque", + "notifications.policy.filter_private_mentions_hint": "Filtrat except si it es un response a tui propri mention o si tu seque li missor", + "notifications.policy.filter_private_mentions_title": "Ínsolicitat privat mentiones", + "notifications.policy.title": "Filtrar notificationes de…", "notifications_permission_banner.enable": "Activisar notificationes sur li computator", "notifications_permission_banner.how_to_control": "Por reciver notificationes quande Mastodon ne es apert, activisa notificationes sur li computator. Tu posse decider precisimen quel species de interactiones genera notificationes per li buton {icon} in-supra quande ili es activisat.", "notifications_permission_banner.title": "Nequande preterlassa quocunc", @@ -644,9 +682,11 @@ "status.direct": "Privatmen mentionar @{name}", "status.direct_indicator": "Privat mention", "status.edit": "Modificar", + "status.edited": "Ultimmen actualisat ye {date}", "status.edited_x_times": "Modificat {count, plural, one {{count} vez} other {{count} vezes}}", "status.embed": "Inbedar", "status.favourite": "Favoritisar", + "status.favourites": "{count, plural, one {favorit} other {favorites}}", "status.filter": "Filtrar ti-ci posta", "status.filtered": "Filtrat", "status.hide": "Celar posta", diff --git a/app/javascript/mastodon/locales/ig.json b/app/javascript/mastodon/locales/ig.json index a9b300fa4f..90253743fc 100644 --- a/app/javascript/mastodon/locales/ig.json +++ b/app/javascript/mastodon/locales/ig.json @@ -20,6 +20,7 @@ "column.bookmarks": "Ebenrụtụakā", "column.home": "Be", "column.lists": "Ndepụta", + "column.notifications": "Nziọkwà", "column.pins": "Pinned post", "column_header.pin": "Gbado na profaịlụ gị", "column_subheading.settings": "Mwube", @@ -42,17 +43,28 @@ "confirmations.reply.confirm": "Zaa", "confirmations.unfollow.confirm": "Kwụsị iso", "conversation.delete": "Hichapụ nkata", + "disabled_account_banner.account_settings": "Mwube akaụntụ", "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.", "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.", + "domain_pill.username": "Ahaojiaru", "embed.instructions": "Embed this status on your website by copying the code below.", + "emoji_button.activity": "Mmemme", + "emoji_button.label": "Tibanye emoji", "emoji_button.search": "Chọọ...", + "emoji_button.symbols": "Ọdịmara", "empty_column.account_timeline": "No posts found", "empty_column.home": "Your home timeline is empty! Follow more people to fill it up. {suggestions}", "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.", "errors.unexpected_crash.report_issue": "Kpesa nsogbu", + "explore.trending_links": "Akụkọ", + "firehose.all": "Ha niine", + "follow_request.authorize": "Nye ikike", "footer.privacy_policy": "Iwu nzuzu", "getting_started.heading": "Mbido", "hashtag.column_settings.tag_toggle": "Include additional tags in this column", + "home.column_settings.show_replies": "Gosi nzaghachị", + "home.hide_announcements": "Zoo ọkwa", + "home.show_announcements": "Gosi ọkwa", "keyboard_shortcuts.back": "to navigate back", "keyboard_shortcuts.blocked": "to open blocked users list", "keyboard_shortcuts.boost": "to boost", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index b65df2fc51..f5bf7c3281 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "Notaðu fyrirliggjandi flokk eða útbúðu nýjan", "filter_modal.select_filter.title": "Sía þessa færslu", "filter_modal.title.status": "Sía færslu", + "filtered_notifications_banner.mentions": "{count, plural, one {tilvísun} other {tilvísanir}}", "filtered_notifications_banner.pending_requests": "Tilkynningar frá {count, plural, =0 {engum} one {einum aðila} other {# aðilum}} sem þú gætir þekkt", "filtered_notifications_banner.title": "Síaðar tilkynningar", "firehose.all": "Allt", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "Jafnvel þótt aðgangurinn þinn sé ekki læstur, hafa umsjónarmenn {domain} ímyndað sér að þú gætir viljað yfirfara handvirkt fylgjendabeiðnir frá þessum notendum.", "follow_suggestions.curated_suggestion": "Úrval starfsfólks", "follow_suggestions.dismiss": "Ekki birta þetta aftur", + "follow_suggestions.featured_longer": "Handvalið af {domain}-teyminu", + "follow_suggestions.friends_of_friends_longer": "Vinsælt hjá fólki sem þú fylgist með", "follow_suggestions.hints.featured": "Þetta notandasnið hefur verið handvalið af {domain}-teyminu.", "follow_suggestions.hints.friends_of_friends": "Þetta notandasnið er vinsælt hjá fólki sem þú fylgist með.", "follow_suggestions.hints.most_followed": "Þetta notandasnið er eitt af þeim sem mest er fylgst með á {domain}.", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Þetta notandasnið er líkt þeim sniðum sem þú hefur valið að fylgjast með að undanförnu.", "follow_suggestions.personalized_suggestion": "Persónuaðlöguð tillaga", "follow_suggestions.popular_suggestion": "Vinsæl tillaga", + "follow_suggestions.popular_suggestion_longer": "Vinsælt á {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Svipar til notenda sem þú hefur nýlega farið að fylgjast með", "follow_suggestions.view_all": "Skoða allt", "follow_suggestions.who_to_follow": "Hverjum á að fylgjast með", "followed_tags": "Myllumerki sem fylgst er með", @@ -468,9 +473,23 @@ "notification.follow": "{name} fylgist með þér", "notification.follow_request": "{name} hefur beðið um að fylgjast með þér", "notification.mention": "{name} minntist á þig", + "notification.moderation-warning.learn_more": "Kanna nánar", + "notification.moderation_warning": "Þú hefur fengið aðvörun frá umsjónarmanni", + "notification.moderation_warning.action_delete_statuses": "Sumar færslurnar þínar hafa verið fjarlægðar.", + "notification.moderation_warning.action_disable": "Aðgangurinn þinn hefur verið gerður óvirkur.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Sumar færslurnar þínar hafa verið merktar sem viðkvæmt efni.", + "notification.moderation_warning.action_none": "Aðgangurinn þinn hefur fengið aðvörun frá umsjónarmanni.", + "notification.moderation_warning.action_sensitive": "Færslur þínar á verða héðan í frá merktar sem viðkvæmar.", + "notification.moderation_warning.action_silence": "Notandaaðgangurinn þinn hefur verið takmarkaður.", + "notification.moderation_warning.action_suspend": "Notandaaðgangurinn þinn hefur verið settur í frysti.", "notification.own_poll": "Könnuninni þinni er lokið", "notification.poll": "Könnun sem þú tókst þátt í er lokið", "notification.reblog": "{name} endurbirti færsluna þína", + "notification.relationships_severance_event": "Missti tengingar við {name}", + "notification.relationships_severance_event.account_suspension": "Stjórnandi á {from} hefur fryst {target}, sem þýðir að þú færð ekki lengur skilaboð frá viðkomandi né átt í samskiptum við viðkomandi.", + "notification.relationships_severance_event.domain_block": "Stjórnandi á {from} hefur lokað á {target} og þar með {followersCount} fylgjendur þína auk {followingCount, plural, one {# aðgangs} other {# aðganga}} sem þú fylgist með.", + "notification.relationships_severance_event.learn_more": "Kanna nánar", + "notification.relationships_severance_event.user_domain_block": "Þú hefur lokað á {target} og þar með fjarlægt {followersCount} fylgjendur þína auk {followingCount, plural, one {# aðgangs} other {# aðganga}} sem þú fylgist með.", "notification.status": "{name} sendi inn rétt í þessu", "notification.update": "{name} breytti færslu", "notification_requests.accept": "Samþykkja", @@ -589,8 +608,6 @@ "refresh": "Endurlesa", "regeneration_indicator.label": "Hleð inn…", "regeneration_indicator.sublabel": "Verið er að útbúa heimastreymið þitt!", - "relationship_severance_notification.relationships": "{count, plural, one {# tengsl} other {# tengsl}}", - "relationship_severance_notification.view": "Skoða", "relative_time.days": "{number}d", "relative_time.full.days": "Fyrir {number, plural, one {# degi} other {# dögum}} síðan", "relative_time.full.hours": "Fyrir {number, plural, one {# klukkustund} other {# klukkustundum}} síðan", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 090278b83b..8ab5db1b17 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -83,7 +83,7 @@ "admin.impact_report.instance_follows": "I seguaci che i loro utenti perderebbero", "admin.impact_report.title": "Riepilogo dell'impatto", "alert.rate_limited.message": "Sei pregato di riprovare dopo le {retry_time, time, medium}.", - "alert.rate_limited.title": "Tasso limitato", + "alert.rate_limited.title": "Limitazione per eccesso di richieste", "alert.unexpected.message": "Si è verificato un errore imprevisto.", "alert.unexpected.title": "Oops!", "announcement.announcement": "Annuncio", @@ -174,7 +174,7 @@ "confirmations.delete.confirm": "Elimina", "confirmations.delete.message": "Sei sicuro di voler eliminare questo post?", "confirmations.delete_list.confirm": "Elimina", - "confirmations.delete_list.message": "Sei sicuro di voler eliminare permanentemente questa lista?", + "confirmations.delete_list.message": "Sei sicuro/a di voler eliminare permanentemente questo elenco?", "confirmations.discard_edit_media.confirm": "Scarta", "confirmations.discard_edit_media.message": "Hai delle modifiche non salvate alla descrizione o anteprima del media, scartarle comunque?", "confirmations.domain_block.confirm": "Blocca il server", @@ -263,8 +263,8 @@ "empty_column.followed_tags": "Non hai ancora seguito alcun hashtag. Quando lo farai, appariranno qui.", "empty_column.hashtag": "Non c'è ancora nulla in questo hashtag.", "empty_column.home": "La cronologia della tua home è vuota! Segui altre persone per riempirla. {suggestions}", - "empty_column.list": "Non c'è ancora nulla in questa lista. Quando i membri di questa lista pubblicheranno dei nuovi post, appariranno qui.", - "empty_column.lists": "Non hai ancora alcuna lista. Quando ne creerai una, apparirà qui.", + "empty_column.list": "Non c'è ancora nulla in questo elenco. Quando i membri di questo elenco pubblicheranno nuovi post, appariranno qui.", + "empty_column.lists": "Non hai ancora nessun elenco. Quando ne creerai uno, apparirà qui.", "empty_column.mutes": "Non hai ancora silenziato alcun utente.", "empty_column.notification_requests": "Tutto chiaro! Non c'è niente qui. Quando ricevi nuove notifiche, verranno visualizzate qui in base alle tue impostazioni.", "empty_column.notifications": "Non hai ancora nessuna notifica. Quando altre persone interagiranno con te, le vedrai qui.", @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "Usa una categoria esistente o creane una nuova", "filter_modal.select_filter.title": "Filtra questo post", "filter_modal.title.status": "Filtra un post", + "filtered_notifications_banner.mentions": "{count, plural, one {menzione} other {menzioni}}", "filtered_notifications_banner.pending_requests": "Notifiche da {count, plural, =0 {nessuno} one {una persona} other {# persone}} che potresti conoscere", "filtered_notifications_banner.title": "Notifiche filtrate", "firehose.all": "Tutto", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "Anche se il tuo profilo non è privato, lo staff di {domain} ha pensato che potresti voler revisionare manualmente le richieste di seguirti da questi profili.", "follow_suggestions.curated_suggestion": "Scelta personale", "follow_suggestions.dismiss": "Non visualizzare più", + "follow_suggestions.featured_longer": "Selezionato personalmente dal team di {domain}", + "follow_suggestions.friends_of_friends_longer": "Popolare tra le persone che segui", "follow_suggestions.hints.featured": "Questo profilo è stato selezionato personalmente dal team di {domain}.", "follow_suggestions.hints.friends_of_friends": "Questo profilo è popolare tra le persone che segui.", "follow_suggestions.hints.most_followed": "Questo profilo è uno dei più seguiti su {domain}.", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Questo profilo è simile ai profili che hai seguito più recentemente.", "follow_suggestions.personalized_suggestion": "Suggerimenti personalizzati", "follow_suggestions.popular_suggestion": "Suggerimento frequente", + "follow_suggestions.popular_suggestion_longer": "Popolare su {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Simile ai profili che hai seguito di recente", "follow_suggestions.view_all": "Vedi tutto", "follow_suggestions.who_to_follow": "Chi seguire", "followed_tags": "Hashtag seguiti", @@ -353,7 +358,7 @@ "interaction_modal.description.follow": "Con un profilo di Mastodon, puoi seguire {name} per ricevere i suoi post nel feed della tua home.", "interaction_modal.description.reblog": "Con un profilo di Mastodon, puoi rebloggare questo post per condividerlo con i tuoi seguaci.", "interaction_modal.description.reply": "Con un profilo di Mastodon, puoi rispondere a questo post.", - "interaction_modal.login.action": "Torna all’inizio", + "interaction_modal.login.action": "Portami alla pagina iniziale", "interaction_modal.login.prompt": "Dominio del tuo server principale, ad esempio mastodon.social", "interaction_modal.no_account_yet": "Non su Mastodon?", "interaction_modal.on_another_server": "Su un altro server", @@ -409,20 +414,20 @@ "limited_account_hint.action": "Mostra comunque il profilo", "limited_account_hint.title": "Questo profilo è stato nascosto dai moderatori di {domain}.", "link_preview.author": "Di {name}", - "lists.account.add": "Aggiungi alla lista", - "lists.account.remove": "Togli dalla lista", - "lists.delete": "Elimina lista", - "lists.edit": "Modifica lista", + "lists.account.add": "Aggiungi all'elenco", + "lists.account.remove": "Rimuovi dall'elenco", + "lists.delete": "Elimina elenco", + "lists.edit": "Modifica elenco", "lists.edit.submit": "Cambia il titolo", "lists.exclusive": "Nascondi questi post dalla home", "lists.new.create": "Aggiungi lista", - "lists.new.title_placeholder": "Titolo della nuova lista", + "lists.new.title_placeholder": "Titolo del nuovo elenco", "lists.replies_policy.followed": "Qualsiasi utente seguito", - "lists.replies_policy.list": "Membri della lista", + "lists.replies_policy.list": "Membri dell'elenco", "lists.replies_policy.none": "Nessuno", "lists.replies_policy.title": "Mostra risposte a:", "lists.search": "Cerca tra le persone che segui", - "lists.subheading": "Le tue liste", + "lists.subheading": "I tuoi elenchi", "load_pending": "{count, plural, one {# nuovo oggetto} other {# nuovi oggetti}}", "loading_indicator.label": "Caricamento…", "media_gallery.toggle_visible": "{number, plural, one {Nascondi immagine} other {Nascondi immagini}}", @@ -468,10 +473,23 @@ "notification.follow": "{name} ha iniziato a seguirti", "notification.follow_request": "{name} ha richiesto di seguirti", "notification.mention": "{name} ti ha menzionato", + "notification.moderation-warning.learn_more": "Scopri di più", + "notification.moderation_warning": "Hai ricevuto un avviso di moderazione", + "notification.moderation_warning.action_delete_statuses": "Alcuni dei tuoi post sono stati rimossi.", + "notification.moderation_warning.action_disable": "Il tuo account è stato disattivato.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Alcuni dei tuoi post sono stati contrassegnati come sensibili.", + "notification.moderation_warning.action_none": "Il tuo account ha ricevuto un avviso di moderazione.", + "notification.moderation_warning.action_sensitive": "I tuoi post d'ora in poi saranno contrassegnati come sensibili.", + "notification.moderation_warning.action_silence": "Il tuo account è stato limitato.", + "notification.moderation_warning.action_suspend": "Il tuo account è stato sospeso.", "notification.own_poll": "Il tuo sondaggio è terminato", "notification.poll": "Un sondaggio in cui hai votato è terminato", "notification.reblog": "{name} ha rebloggato il tuo post", - "notification.severed_relationships": "Relazioni interrotte con {name}", + "notification.relationships_severance_event": "Connessioni perse con {name}", + "notification.relationships_severance_event.account_suspension": "Un amministratore da {from} ha sospeso {target}, il che significa che non puoi più ricevere aggiornamenti da loro o interagire con loro.", + "notification.relationships_severance_event.domain_block": "Un amministratore da {from} ha bloccato {target}, inclusi {followersCount} dei tuoi seguaci e {followingCount, plural, one {# account} other {# account}} che segui.", + "notification.relationships_severance_event.learn_more": "Scopri di più", + "notification.relationships_severance_event.user_domain_block": "Tu hai bloccato {target}, rimuovendo {followersCount} dei tuoi seguaci e {followingCount, plural, one {# account} other {# account}} che segui.", "notification.status": "{name} ha appena pubblicato un post", "notification.update": "{name} ha modificato un post", "notification_requests.accept": "Accetta", @@ -590,12 +608,6 @@ "refresh": "Ricarica", "regeneration_indicator.label": "Caricamento…", "regeneration_indicator.sublabel": "Il feed della tua home è in preparazione!", - "relationship_severance_notification.purged_data": "rimossi dagli amministratori", - "relationship_severance_notification.relationships": "{count, plural,one {# relazione} other {# relazioni}}", - "relationship_severance_notification.types.account_suspension": "L'account è stato sospeso", - "relationship_severance_notification.types.domain_block": "Il dominio è stato sospeso", - "relationship_severance_notification.types.user_domain_block": "Hai bloccato questo dominio", - "relationship_severance_notification.view": "Visualizza", "relative_time.days": "{number}g", "relative_time.full.days": "{number, plural, one {# giorno} other {# giorni}} fa", "relative_time.full.hours": "{number, plural, one {# ora} other {# ore}} fa", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 1f1065f5fd..6e590678fb 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -177,6 +177,7 @@ "confirmations.delete_list.message": "本当にこのリストを完全に削除しますか?", "confirmations.discard_edit_media.confirm": "破棄", "confirmations.discard_edit_media.message": "メディアの説明またはプレビューに保存されていない変更があります。それでも破棄しますか?", + "confirmations.domain_block.confirm": "サーバーをブロック", "confirmations.domain_block.message": "本当に{domain}全体を非表示にしますか? 多くの場合は個別にブロックやミュートするだけで充分であり、また好ましいです。公開タイムラインにそのドメインのコンテンツが表示されなくなり、通知も届かなくなります。そのドメインのフォロワーはアンフォローされます。", "confirmations.edit.confirm": "編集", "confirmations.edit.message": "今編集すると現在作成中のメッセージが上書きされます。本当に実行しますか?", @@ -216,6 +217,19 @@ "domain_block_modal.title": "ドメインをブロックしますか?", "domain_block_modal.you_will_lose_followers": "このサーバーのフォロワーはすべてフォロー解除されます。", "domain_block_modal.you_wont_see_posts": "このサーバーのユーザーからの投稿や通知が閲覧できなくなります。", + "domain_pill.activitypub_lets_connect": "Mastodonからほかのソーシャルアプリのユーザーへ、そのまた別のアプリのユーザーへと、それぞれが互いにつながり関わり合うことをこのActivityPubの仕組みが実現しています。", + "domain_pill.activitypub_like_language": "ActivityPubとは、Mastodonがほかのサーバーと会話をするときにしゃべる「言葉」のようなものです。", + "domain_pill.server": "サーバー", + "domain_pill.their_handle": "このユーザーのユーザーID:", + "domain_pill.their_server": "ユーザーの仮想の住所です。そのユーザーIDによるすべての投稿を保持しています。", + "domain_pill.their_username": "ユーザーを識別する名前です。ユーザー名はひとつのサーバー内においては唯一無二の名前ですが、ほかのサーバーには同名のユーザーがいることもあります。", + "domain_pill.username": "ユーザー名", + "domain_pill.whats_in_a_handle": "ユーザーIDについて", + "domain_pill.who_they_are": "そのユーザーが「誰であるか」「どこに住んでいるか」はユーザーIDから知ることができます。これによりの集まりからなるネットワークを介してそれぞれのユーザーと関わり合うことができます。", + "domain_pill.who_you_are": "ほかのユーザーはあなたが「誰であるか」「どこに住んでいるか」をユーザーIDから認識でき、これによりの集まりからなるネットワークを介してあなたと関わり合うことができます。", + "domain_pill.your_handle": "あなたのユーザーID:", + "domain_pill.your_server": "あなたの仮想の住所です。投稿した内容はすべてここに保持されます。もし今いるサーバーが気に入っていない場合は、フォロワーを引き継いで別のサーバーに引っ越すこともできます。", + "domain_pill.your_username": "あなたを識別する名前です。ユーザー名はひとつのサーバー内においては唯一無二の名前ですが、ほかのサーバーには同名のユーザーがいることもあります。", "embed.instructions": "下記のコードをコピーしてウェブサイトに埋め込みます。", "embed.preview": "表示例:", "emoji_button.activity": "活動", @@ -283,8 +297,9 @@ "filter_modal.select_filter.subtitle": "既存のカテゴリーを使用するか新規作成します", "filter_modal.select_filter.title": "この投稿をフィルターする", "filter_modal.title.status": "投稿をフィルターする", - "filtered_notifications_banner.pending_requests": "{count, plural, =0 {アカウント} other {#アカウント}}からの通知がブロックされています", - "filtered_notifications_banner.title": "ブロック済みの通知", + "filtered_notifications_banner.mentions": "{count, plural, one {メンション} other {メンション}}", + "filtered_notifications_banner.pending_requests": "{count, plural, =0 {通知がブロックされているアカウントはありません} other {#アカウントからの通知がブロックされています}}", + "filtered_notifications_banner.title": "保留中の通知", "firehose.all": "すべて", "firehose.local": "このサーバー", "firehose.remote": "ほかのサーバー", @@ -293,6 +308,8 @@ "follow_requests.unlocked_explanation": "あなたのアカウントは承認制ではありませんが、{domain}のスタッフはこれらのアカウントからのフォローリクエストの確認が必要であると判断しました。", "follow_suggestions.curated_suggestion": "サーバースタッフ公認", "follow_suggestions.dismiss": "今後表示しない", + "follow_suggestions.featured_longer": "{domain} スタッフ公認", + "follow_suggestions.friends_of_friends_longer": "フォロー中のユーザーに人気", "follow_suggestions.hints.featured": "{domain} の運営スタッフが選んだアカウントです。", "follow_suggestions.hints.friends_of_friends": "フォロー中のユーザーのあいだで人気のアカウントです。", "follow_suggestions.hints.most_followed": "{domain} でもっともフォローされているアカウントのひとつです。", @@ -300,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "最近フォローしたユーザーに似ているアカウントです。", "follow_suggestions.personalized_suggestion": "フォローに基づく提案", "follow_suggestions.popular_suggestion": "人気のアカウント", + "follow_suggestions.popular_suggestion_longer": "{domain} で人気", + "follow_suggestions.similar_to_recently_followed_longer": "最近フォローしたユーザーと似ているアカウント", "follow_suggestions.view_all": "すべて表示", "follow_suggestions.who_to_follow": "フォローを増やしてみませんか?", "followed_tags": "フォロー中のハッシュタグ", @@ -454,21 +473,37 @@ "notification.follow": "{name}さんにフォローされました", "notification.follow_request": "{name}さんがあなたにフォローリクエストしました", "notification.mention": "{name}さんがあなたに返信しました", + "notification.moderation-warning.learn_more": "さらに詳しく", + "notification.moderation_warning": "あなたは管理者からの警告を受けています。", + "notification.moderation_warning.action_delete_statuses": "あなたによるいくつかの投稿が削除されました。", + "notification.moderation_warning.action_disable": "あなたのアカウントは無効になりました。", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "あなたの投稿のいくつかは閲覧注意として判定されています。", + "notification.moderation_warning.action_none": "あなたのアカウントは管理者からの警告を受けています。", + "notification.moderation_warning.action_sensitive": "あなたの投稿はこれから閲覧注意としてマークされます。", + "notification.moderation_warning.action_silence": "あなたのアカウントは制限されています。", + "notification.moderation_warning.action_suspend": "あなたのアカウントは停止されました。", "notification.own_poll": "アンケートが終了しました", "notification.poll": "アンケートが終了しました", "notification.reblog": "{name}さんがあなたの投稿をブーストしました", + "notification.relationships_severance_event": "{name} との関係が失われました", + "notification.relationships_severance_event.account_suspension": "{from} の管理者が {target} さんを停止したため、今後このユーザーとの交流や新しい投稿の受け取りができなくなりました。", + "notification.relationships_severance_event.domain_block": "{from} の管理者が {target} をブロックしました。これにより{followersCount}フォロワーと{followingCount, plural, other {#フォロー}}が失われました。", + "notification.relationships_severance_event.learn_more": "詳細を確認", + "notification.relationships_severance_event.user_domain_block": "{target} のブロックにより{followersCount}フォロワーと{followingCount, plural, other {#フォロー}}が解除されました。", "notification.status": "{name}さんが投稿しました", "notification.update": "{name}さんが投稿を編集しました", "notification_requests.accept": "受け入れる", "notification_requests.dismiss": "無視", "notification_requests.notifications_from": "{name}からの通知", - "notification_requests.title": "ブロック済みの通知", + "notification_requests.title": "保留中の通知", "notifications.clear": "通知を消去", "notifications.clear_confirmation": "本当に通知を消去しますか?", "notifications.column_settings.admin.report": "新しい通報:", "notifications.column_settings.admin.sign_up": "新規登録:", "notifications.column_settings.alert": "デスクトップ通知", "notifications.column_settings.favourite": "お気に入り:", + "notifications.column_settings.filter_bar.advanced": "すべてのカテゴリを表示", + "notifications.column_settings.filter_bar.category": "クイックフィルターバー:", "notifications.column_settings.follow": "新しいフォロワー:", "notifications.column_settings.follow_request": "新しいフォローリクエスト:", "notifications.column_settings.mention": "返信:", @@ -679,9 +714,11 @@ "status.direct": "@{name}さんに非公開で投稿", "status.direct_indicator": "非公開の返信", "status.edit": "編集", + "status.edited": "最終更新日 {date}", "status.edited_x_times": "{count}回編集", "status.embed": "埋め込み", "status.favourite": "お気に入り", + "status.favourites": "{count, plural, one {お気に入り} other {お気に入り}}", "status.filter": "この投稿をフィルターする", "status.filtered": "フィルターされました", "status.hide": "投稿を非表示", @@ -702,6 +739,7 @@ "status.reblog": "ブースト", "status.reblog_private": "ブースト", "status.reblogged_by": "{name}さんがブースト", + "status.reblogs": "{count, plural, one {ブースト} other {ブースト}}", "status.reblogs.empty": "まだ誰もブーストしていません。ブーストされるとここに表示されます。", "status.redraft": "削除して下書きに戻す", "status.remove_bookmark": "ブックマークを削除", diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json index fbe60c3bdd..9fdf602992 100644 --- a/app/javascript/mastodon/locales/kab.json +++ b/app/javascript/mastodon/locales/kab.json @@ -221,6 +221,7 @@ "filter_modal.select_filter.prompt_new": "Taggayt tamaynutt : {name}", "filter_modal.select_filter.search": "Nadi neɣ snulfu-d", "filter_modal.select_filter.title": "Sizdeg tassufeɣt-a", + "filter_modal.title.status": "Sizdeg tassufeɣt", "firehose.all": "Akk", "firehose.local": "Deg uqeddac-ayi", "firehose.remote": "Iqeddacen nniḍen", @@ -335,6 +336,7 @@ "mute_modal.show_options": "Sken-d tinefrunin", "mute_modal.title": "Sgugem aseqdac?", "navigation_bar.about": "Ɣef", + "navigation_bar.advanced_interface": "Ldi deg ugrudem n web leqqayen", "navigation_bar.blocks": "Iseqdacen yettusḥebsen", "navigation_bar.bookmarks": "Ticraḍ", "navigation_bar.community_timeline": "Tasuddemt tadigant", @@ -364,6 +366,7 @@ "notification.own_poll": "Tafrant-ik·im tfuk", "notification.poll": "Tfukk tefrant ideg tettekkaḍ", "notification.reblog": "{name} yebḍa tajewwiqt-ik i tikelt-nniḍen", + "notification.relationships_severance_event.learn_more": "Issin ugar", "notification.status": "{name} akken i d-yessufeɣ", "notification_requests.accept": "Qbel", "notification_requests.dismiss": "Agi", @@ -372,6 +375,8 @@ "notifications.clear_confirmation": "Tebɣiḍ s tidet ad tekkseḍ akk tilɣa-inek·em i lebda?", "notifications.column_settings.alert": "Tilɣa n tnarit", "notifications.column_settings.favourite": "Imenyafen:", + "notifications.column_settings.filter_bar.advanced": "Sken-d akk taggayin", + "notifications.column_settings.filter_bar.category": "Iri n usizdeg uzrib", "notifications.column_settings.follow": "Imeḍfaṛen imaynuten:", "notifications.column_settings.follow_request": "Isuturen imaynuten n teḍfeṛt:", "notifications.column_settings.mention": "Abdar:", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 5553636f26..52ce9455a9 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "기존의 카테고리를 사용하거나 새로 하나를 만듧니다", "filter_modal.select_filter.title": "이 게시물을 필터", "filter_modal.title.status": "게시물 필터", + "filtered_notifications_banner.mentions": "{count, plural, other {멘션}}", "filtered_notifications_banner.pending_requests": "알 수도 있는 {count, plural, =0 {0 명} one {한 명} other {# 명}}의 사람들로부터의 알림", "filtered_notifications_banner.title": "걸러진 알림", "firehose.all": "모두", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "귀하의 계정이 잠긴 계정이 아닐지라도, {domain} 스태프는 이 계정들의 팔로우 요청을 수동으로 처리해 주시면 좋겠다고 생각했습니다.", "follow_suggestions.curated_suggestion": "스태프의 추천", "follow_suggestions.dismiss": "다시 보지 않기", + "follow_suggestions.featured_longer": "{domain} 팀이 손수 고름", + "follow_suggestions.friends_of_friends_longer": "내가 팔로우 하는 사람들 사이에서 인기", "follow_suggestions.hints.featured": "이 프로필은 {domain} 팀이 손수 선택했습니다.", "follow_suggestions.hints.friends_of_friends": "이 프로필은 내가 팔로우 하는 사람들에게서 유명합니다.", "follow_suggestions.hints.most_followed": "이 프로필은 {domain}에서 가장 많이 팔로우 된 사람들 중 하나입니다.", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "이 프로필은 내가 최근에 팔로우 한 프로필들과 유사합니다.", "follow_suggestions.personalized_suggestion": "개인화된 추천", "follow_suggestions.popular_suggestion": "인기있는 추천", + "follow_suggestions.popular_suggestion_longer": "{domain}에서 인기", + "follow_suggestions.similar_to_recently_followed_longer": "내가 최근에 팔로우 한 프로필들과 유사", "follow_suggestions.view_all": "모두 보기", "follow_suggestions.who_to_follow": "팔로우할 만한 사람", "followed_tags": "팔로우 중인 해시태그", @@ -468,10 +473,23 @@ "notification.follow": "{name} 님이 나를 팔로우했습니다", "notification.follow_request": "{name} 님이 팔로우 요청을 보냈습니다", "notification.mention": "{name} 님의 멘션", + "notification.moderation-warning.learn_more": "더 알아보기", + "notification.moderation_warning": "중재 경고를 받았습니다", + "notification.moderation_warning.action_delete_statuses": "게시물 몇 개가 삭제되었습니다.", + "notification.moderation_warning.action_disable": "계정이 비활성화되었습니다.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "게시물 몇 개가 민감함 처리되었습니다.", + "notification.moderation_warning.action_none": "계정에 중재 경고를 받았습니다.", + "notification.moderation_warning.action_sensitive": "앞으로의 게시물을 민감한 것으로 표시됩니다.", + "notification.moderation_warning.action_silence": "계정이 제한되었습니다.", + "notification.moderation_warning.action_suspend": "계정이 정지되었습니다.", "notification.own_poll": "설문을 마침", "notification.poll": "참여한 설문이 종료됨", "notification.reblog": "{name} 님이 부스트했습니다", - "notification.severed_relationships": "{name} 님과의 관계가 단절되었습니다", + "notification.relationships_severance_event": "{name} 님과의 연결이 끊어졌습니다", + "notification.relationships_severance_event.account_suspension": "{from}의 관리자가 {target}를 정지시켰기 때문에 그들과 더이상 상호작용 할 수 없고 정보를 받아볼 수 없습니다.", + "notification.relationships_severance_event.domain_block": "{from}의 관리자가 {target}를 차단하였고 여기에는 나의 {followersCount} 명의 팔로워와 {followingCount, plural, other {#}} 명의 팔로우가 포함되었습니다.", + "notification.relationships_severance_event.learn_more": "더 알아보기", + "notification.relationships_severance_event.user_domain_block": "내가 {target}를 차단하여 {followersCount} 명의 팔로워와 {followingCount, plural, other {#}} 명의 팔로우가 제거되었습니다.", "notification.status": "{name} 님이 방금 게시물을 올렸습니다", "notification.update": "{name} 님이 게시물을 수정했습니다", "notification_requests.accept": "수락", @@ -485,6 +503,7 @@ "notifications.column_settings.alert": "데스크탑 알림", "notifications.column_settings.favourite": "좋아요:", "notifications.column_settings.filter_bar.advanced": "모든 범주 표시", + "notifications.column_settings.filter_bar.category": "빠른 필터 막대", "notifications.column_settings.follow": "새 팔로워:", "notifications.column_settings.follow_request": "새 팔로우 요청:", "notifications.column_settings.mention": "멘션:", @@ -589,12 +608,6 @@ "refresh": "새로고침", "regeneration_indicator.label": "불러오는 중…", "regeneration_indicator.sublabel": "홈 피드를 준비하고 있습니다!", - "relationship_severance_notification.purged_data": "관리자에 의해 제거되었습니다", - "relationship_severance_notification.relationships": "{count, plural, other {# 건의 관계}}", - "relationship_severance_notification.types.account_suspension": "계정이 정지되었습니다", - "relationship_severance_notification.types.domain_block": "도메인이 정지되었습니다", - "relationship_severance_notification.types.user_domain_block": "내가 이 도메인을 차단했습니다", - "relationship_severance_notification.view": "보기", "relative_time.days": "{number}일 전", "relative_time.full.days": "{number} 일 전", "relative_time.full.hours": "{number} 시간 전", @@ -701,7 +714,7 @@ "status.direct": "@{name} 님에게 개인적으로 멘션", "status.direct_indicator": "개인적인 멘션", "status.edit": "수정", - "status.edited": "%{date}에 마지막으로 편집됨", + "status.edited": "{date}에 마지막으로 편집됨", "status.edited_x_times": "{count}번 수정됨", "status.embed": "임베드", "status.favourite": "좋아요", diff --git a/app/javascript/mastodon/locales/lad.json b/app/javascript/mastodon/locales/lad.json index 1eb8aa4abc..533f074004 100644 --- a/app/javascript/mastodon/locales/lad.json +++ b/app/javascript/mastodon/locales/lad.json @@ -295,6 +295,7 @@ "follow_requests.unlocked_explanation": "Aunke tu kuento no esta serrado, la taifa de {domain} kreye ke talvez keres revizar manualmente las solisitudes de segimento de estos kuentos.", "follow_suggestions.curated_suggestion": "Seleksyon de la taifa", "follow_suggestions.dismiss": "No amostra mas", + "follow_suggestions.friends_of_friends_longer": "Popular entre personas a las kualas siges", "follow_suggestions.hints.featured": "Este profil tiene sido eskojido por la taifa de {domain}.", "follow_suggestions.hints.friends_of_friends": "Este profil es popular entre las personas ke siges.", "follow_suggestions.hints.most_followed": "Este profil es uno de los mas segidos en {domain}.", @@ -454,9 +455,14 @@ "notification.follow": "{name} te ampeso a segir", "notification.follow_request": "{name} tiene solisitado segirte", "notification.mention": "{name} te enmento", + "notification.moderation-warning.learn_more": "Ambezate mas", + "notification.moderation_warning.action_silence": "Tu kuento tiene sido limitado.", + "notification.moderation_warning.action_suspend": "Tu kuento tiene sido suspendido.", "notification.own_poll": "Tu anketa eskapo", "notification.poll": "Anketa en ke votates eskapo", "notification.reblog": "{name} repartajo tu publikasyon", + "notification.relationships_severance_event": "Koneksyones pedridas kon {name}", + "notification.relationships_severance_event.learn_more": "Ambezate mas", "notification.status": "{name} publiko algo", "notification.update": "{name} edito una publikasyon", "notification_requests.accept": "Acheta", @@ -469,6 +475,8 @@ "notifications.column_settings.admin.sign_up": "Muevas enrejistrasyones:", "notifications.column_settings.alert": "Avizos de ensimameza", "notifications.column_settings.favourite": "Te plazen:", + "notifications.column_settings.filter_bar.advanced": "Amostra todas las kategorias", + "notifications.column_settings.filter_bar.category": "Vara de filtrado rapido", "notifications.column_settings.follow": "Muevos suivantes:", "notifications.column_settings.follow_request": "Muevas solisitudes de segimiento:", "notifications.column_settings.mention": "Enmentaduras:", @@ -571,8 +579,6 @@ "refresh": "Arefreska", "regeneration_indicator.label": "Eskargando…", "regeneration_indicator.sublabel": "Tu linya de tiempo prinsipala esta preparando!", - "relationship_severance_notification.types.user_domain_block": "Blokates este domeno", - "relationship_severance_notification.view": "Mira", "relative_time.days": "{number} d", "relative_time.full.days": "antes {number, plural, one {# diya} other {# diyas}}", "relative_time.full.hours": "antes {number, plural, one {# ora} other {# oras}}", diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json index 728485b2bc..083a922012 100644 --- a/app/javascript/mastodon/locales/lt.json +++ b/app/javascript/mastodon/locales/lt.json @@ -83,12 +83,15 @@ "admin.impact_report.instance_follows": "Sekėjai, kuriuos prarastų jų naudotojai", "admin.impact_report.title": "Poveikio apibendrinimas", "alert.rate_limited.message": "Pabandyk vėliau po {retry_time, time, medium}.", - "alert.rate_limited.title": "Sparta ribota", + "alert.rate_limited.title": "Sparta ribota.", "alert.unexpected.message": "Įvyko netikėta klaida.", "alert.unexpected.title": "Ups!", "announcement.announcement": "Skelbimas", "attachments_list.unprocessed": "(neapdorotas)", "audio.hide": "Slėpti garsą", + "block_modal.remote_users_caveat": "Paprašysime serverio {domain} gerbti tavo sprendimą. Tačiau atitiktis negarantuojama, nes kai kurie serveriai gali skirtingai tvarkyti blokavimus. Vieši įrašai vis tiek gali būti matomi neprisijungusiems naudotojams.", + "block_modal.show_less": "Rodyti mažiau", + "block_modal.show_more": "Rodyti daugiau", "boost_modal.combo": "Galima paspausti {combo}, kad praleisti kitą kartą.", "bundle_column_error.copy_stacktrace": "Kopijuoti klaidos ataskaitą", "bundle_column_error.error.body": "Paprašytos puslapio nepavyko atvaizduoti. Tai gali būti dėl mūsų kodo klaidos arba naršyklės suderinamumo problemos.", @@ -140,7 +143,7 @@ "compose.published.open": "Atidaryti", "compose.saved.body": "Įrašas išsaugotas.", "compose_form.direct_message_warning_learn_more": "Sužinoti daugiau", - "compose_form.encryption_warning": "Mastodon įrašai nėra šifruojami nuo galo iki galo. Per Mastodon nesidalyk jokia slapta informacija.", + "compose_form.encryption_warning": "Mastodon įrašai nėra visapusiškai šifruojami. Per Mastodon nesidalyk jokia slapta informacija.", "compose_form.hashtag_warning": "Šis įrašas nebus įtraukta į jokį saitažodį, nes ji nėra vieša. Tik viešų įrašų galima ieškoti pagal saitažodį.", "compose_form.lock_disclaimer": "Tavo paskyra nėra {locked}. Bet kas gali sekti tave ir peržiūrėti tik sekėjams skirtus įrašus.", "compose_form.lock_disclaimer.lock": "užrakinta", @@ -279,6 +282,7 @@ "filter_modal.select_filter.subtitle": "Naudok esamą kategoriją arba sukurk naują.", "filter_modal.select_filter.title": "Filtruoti šį įrašą", "filter_modal.title.status": "Filtruoti įrašą", + "filtered_notifications_banner.mentions": "{count, plural, one {paminėjimas} few {paminėjimai} many {paminėjimo} other {paminėjimų}}", "firehose.all": "Visi", "firehose.local": "Šis serveris", "firehose.remote": "Kiti serveriai", @@ -287,6 +291,8 @@ "follow_requests.unlocked_explanation": "Nors tavo paskyra neužrakinta, {domain} personalas mano, kad galbūt norėsi rankiniu būdu patikrinti šių paskyrų sekimo prašymus.", "follow_suggestions.curated_suggestion": "Personalo pasirinkimai", "follow_suggestions.dismiss": "Daugiau nerodyti", + "follow_suggestions.featured_longer": "Rankomis atrinkta {domain} komanda", + "follow_suggestions.friends_of_friends_longer": "Populiarus tarp žmonių, kurių seki", "follow_suggestions.hints.featured": "Šį profilį atrinko {domain} komanda.", "follow_suggestions.hints.friends_of_friends": "Šis profilis yra populiarus tarp žmonių, kuriuos seki.", "follow_suggestions.hints.most_followed": "Šis profilis yra vienas iš labiausiai sekamų {domain}.", @@ -294,6 +300,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Šis profilis panašus į profilius, kuriuos neseniai sekei.", "follow_suggestions.personalized_suggestion": "Suasmenintas pasiūlymas", "follow_suggestions.popular_suggestion": "Populiarus pasiūlymas", + "follow_suggestions.popular_suggestion_longer": "Populiarus domene {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Panašūs į profilius, kuriuos neseniai seki", "follow_suggestions.view_all": "Peržiūrėti viską", "follow_suggestions.who_to_follow": "Ką sekti", "followed_tags": "Sekami saitažodžiai", @@ -407,6 +415,7 @@ "loading_indicator.label": "Kraunama…", "media_gallery.toggle_visible": "{number, plural, one {Slėpti vaizdą} few {Slėpti vaizdus} many {Slėpti vaizdo} other {Slėpti vaizdų}}", "moved_to_account_banner.text": "Tavo paskyra {disabledAccount} šiuo metu išjungta, nes persikėlei į {movedToAccount}.", + "mute_modal.show_options": "Rodyti parinktis", "navigation_bar.about": "Apie", "navigation_bar.advanced_interface": "Atidaryti išplėstinę žiniatinklio sąsają", "navigation_bar.blocks": "Užblokuoti naudotojai", @@ -439,9 +448,20 @@ "notification.follow": "{name} seka tave", "notification.follow_request": "{name} paprašė tave sekti", "notification.mention": "{name} paminėjo tave", + "notification.moderation-warning.learn_more": "Sužinoti daugiau", + "notification.moderation_warning": "Gavai prižiūrėjimo įspėjimą", + "notification.moderation_warning.action_delete_statuses": "Kai kurie tavo įrašai buvo pašalintos.", + "notification.moderation_warning.action_disable": "Tavo paskyra buvo išjungta.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Kai kurie tavo įrašai buvo pažymėtos kaip jautrios.", + "notification.moderation_warning.action_none": "Tavo paskyra gavo prižiūrėjimo įspėjimą.", + "notification.moderation_warning.action_sensitive": "Nuo šiol tavo įrašai bus pažymėti kaip jautrūs.", + "notification.moderation_warning.action_silence": "Tavo paskyra buvo apribota.", + "notification.moderation_warning.action_suspend": "Tavo paskyra buvo sustabdyta.", "notification.own_poll": "Tavo apklausa baigėsi", "notification.poll": "Apklausa, kurioje balsavai, pasibaigė", "notification.reblog": "{name} pakėlė tavo įrašą", + "notification.relationships_severance_event.learn_more": "Sužinoti daugiau", + "notification.relationships_severance_event.user_domain_block": "Tu užblokavai {target}. Pašalinama {followersCount} savo sekėjų ir {followingCount, plural, one {# paskyrą} few {# paskyrai} many {# paskyros} other {# paskyrų}}, kurios seki.", "notification.status": "{name} ką tik paskelbė", "notification.update": "{name} redagavo įrašą", "notification_requests.accept": "Priimti", @@ -454,6 +474,8 @@ "notifications.column_settings.admin.sign_up": "Naujos registracijos:", "notifications.column_settings.alert": "Darbalaukio pranešimai", "notifications.column_settings.favourite": "Mėgstami:", + "notifications.column_settings.filter_bar.advanced": "Rodyti visas kategorijas", + "notifications.column_settings.filter_bar.category": "Spartaus filtro juosta", "notifications.column_settings.follow": "Nauji sekėjai:", "notifications.column_settings.follow_request": "Nauji sekimo prašymai:", "notifications.column_settings.mention": "Paminėjimai:", @@ -555,9 +577,6 @@ "refresh": "Atnaujinti", "regeneration_indicator.label": "Kraunama…", "regeneration_indicator.sublabel": "Ruošiamas tavo pagrindinis srautas!", - "relationship_severance_notification.relationships": "{count, plural, one {# santykis} few {# santykiai} many {# santykio} other {# santykių}}", - "relationship_severance_notification.types.user_domain_block": "Užblokavai šį domeną", - "relationship_severance_notification.view": "Peržiūrėti", "relative_time.days": "{number} d.", "relative_time.full.days": "prieš {number, plural, one {# dieną} few {# dienas} many {# dienos} other {# dienų}}", "relative_time.full.hours": "prieš {number, plural, one {# valandą} few {# valandas} many {# valandos} other {# valandų}}", diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json index 21fa46faa6..55ceb564b6 100644 --- a/app/javascript/mastodon/locales/lv.json +++ b/app/javascript/mastodon/locales/lv.json @@ -626,7 +626,7 @@ "status.direct_indicator": "Pieminēts privāti", "status.edit": "Labot", "status.edited_x_times": "Labots {count, plural, one {{count} reizi} other {{count} reizes}}", - "status.embed": "Iestrādāt", + "status.embed": "Iegult", "status.favourite": "Izlasē", "status.filter": "Filtrē šo ziņu", "status.filtered": "Filtrēts", @@ -634,7 +634,7 @@ "status.history.created": "{name} izveidoja {date}", "status.history.edited": "{name} laboja {date}", "status.load_more": "Ielādēt vairāk", - "status.media.open": "Noklikšķini, lai atvērtu", + "status.media.open": "Jānoklikšķina, lai atvērtu", "status.media.show": "Noklikšķini, lai parādītu", "status.media_hidden": "Multivides ir paslēpts", "status.mention": "Pieminēt @{name}", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 1e710bda45..1958e4a6a0 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -225,8 +225,8 @@ "domain_pill.their_username": "Hun unieke identificatie-adres op hun server. Het is mogelijk dat er gebruikers met dezelfde gebruikersnaam op verschillende servers te vinden zijn.", "domain_pill.username": "Gebruikersnaam", "domain_pill.whats_in_a_handle": "Wat is een fediverse-adres?", - "domain_pill.who_they_are": "Omdat je aan een fediverse-adres kunt zien wie iemand is en waar die zich bevindt, kun je met mensen op het door sociale web (fediverse) communiceren.", - "domain_pill.who_you_are": "Omdat je aan jouw fediverse-adres kunt zien wie jij bent is en waar je je bevindt, kunnen mensen op het door sociale web (fediverse) met jou communiceren.", + "domain_pill.who_they_are": "Omdat je aan een fediverse-adres kunt zien hoe iemand heet en op welke server die zich bevindt, kun je met mensen op het door sociale web (fediverse) communiceren.", + "domain_pill.who_you_are": "Omdat je aan jouw fediverse-adres kunt zien hoe je heet en op welke server je je bevindt, kunnen mensen op het door sociale web (fediverse) met jou communiceren.", "domain_pill.your_handle": "Jouw fediverse-adres:", "domain_pill.your_server": "Jouw digitale thuis, waar al jouw berichten zich bevinden. Is deze server toch niet naar jouw wens? Dan kun je op elk moment naar een andere server verhuizen en ook jouw volgers overbrengen.", "domain_pill.your_username": "Jouw unieke identificatie-adres op deze server. Het is mogelijk dat er gebruikers met dezelfde gebruikersnaam op verschillende servers te vinden zijn.", @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "Een bestaande categorie gebruiken of een nieuwe aanmaken", "filter_modal.select_filter.title": "Dit bericht filteren", "filter_modal.title.status": "Een bericht filteren", + "filtered_notifications_banner.mentions": "{count, plural, one {vermelding} other {vermeldingen}}", "filtered_notifications_banner.pending_requests": "Meldingen van {count, plural, =0 {niemand} one {één persoon} other {# mensen}} die je misschien kent", "filtered_notifications_banner.title": "Gefilterde meldingen", "firehose.all": "Alles", @@ -307,13 +308,17 @@ "follow_requests.unlocked_explanation": "Ook al is jouw account niet besloten, de medewerkers van {domain} denken dat jij misschien de volgende volgverzoeken handmatig wil controleren.", "follow_suggestions.curated_suggestion": "Speciaal geselecteerd", "follow_suggestions.dismiss": "Niet meer weergeven", - "follow_suggestions.hints.featured": "Deze gebruiker is geselecteerd door het team van {domain}.", + "follow_suggestions.featured_longer": "Handmatig geselecteerd door het team van {domain}", + "follow_suggestions.friends_of_friends_longer": "Populair onder mensen die je volgt", + "follow_suggestions.hints.featured": "Deze gebruiker is handmatig geselecteerd door het team van {domain}.", "follow_suggestions.hints.friends_of_friends": "Deze gebruiker is populair onder de mensen die jij volgt.", "follow_suggestions.hints.most_followed": "Deze gebruiker is een van de meest gevolgde gebruikers op {domain}.", "follow_suggestions.hints.most_interactions": "Deze gebruiker is de laatste tijd erg populair op {domain}.", "follow_suggestions.hints.similar_to_recently_followed": "Deze gebruiker is vergelijkbaar met gebruikers die je recentelijk hebt gevolgd.", "follow_suggestions.personalized_suggestion": "Gepersonaliseerde aanbeveling", "follow_suggestions.popular_suggestion": "Populaire aanbeveling", + "follow_suggestions.popular_suggestion_longer": "Populair op {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Vergelijkbaar met accounts die je recentelijk bent gaan volgen", "follow_suggestions.view_all": "Alles weergeven", "follow_suggestions.who_to_follow": "Wie te volgen", "followed_tags": "Gevolgde hashtags", @@ -468,10 +473,23 @@ "notification.follow": "{name} volgt jou nu", "notification.follow_request": "{name} wil jou graag volgen", "notification.mention": "{name} vermeldde jou", + "notification.moderation-warning.learn_more": "Meer informatie", + "notification.moderation_warning": "Je hebt een moderatie-waarschuwing ontvangen", + "notification.moderation_warning.action_delete_statuses": "Sommige van je berichten zijn verwijderd.", + "notification.moderation_warning.action_disable": "Je account is uitgeschakeld.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Sommige van je berichten zijn gemarkeerd als gevoelig.", + "notification.moderation_warning.action_none": "Jouw account heeft een moderatie-waarschuwing ontvangen.", + "notification.moderation_warning.action_sensitive": "Je berichten worden vanaf nu als gevoelig gemarkeerd.", + "notification.moderation_warning.action_silence": "Jouw account is beperkt.", + "notification.moderation_warning.action_suspend": "Jouw account is opgeschort.", "notification.own_poll": "Jouw peiling is beëindigd", "notification.poll": "Een peiling waaraan jij hebt meegedaan is beëindigd", "notification.reblog": "{name} boostte jouw bericht", - "notification.severed_relationships": "Volgrelaties met {name} verbroken", + "notification.relationships_severance_event": "Verloren verbindingen met {name}", + "notification.relationships_severance_event.account_suspension": "Een beheerder van {from} heeft {target} geschorst, wat betekent dat je geen updates meer van hen kunt ontvangen of met hen kunt communiceren.", + "notification.relationships_severance_event.domain_block": "Een beheerder van {from} heeft {target} geblokkeerd, inclusief {followersCount} van jouw volgers en {followingCount, plural, one {# account} other {# accounts}} die jij volgt.", + "notification.relationships_severance_event.learn_more": "Meer informatie", + "notification.relationships_severance_event.user_domain_block": "Je hebt {target} geblokkeerd, waarmee je {followersCount} van je volgers en {followingCount, plural, one {# account} other {# accounts}} die jij volgt, bent verloren.", "notification.status": "{name} heeft zojuist een bericht geplaatst", "notification.update": "{name} heeft een bericht bewerkt", "notification_requests.accept": "Accepteren", @@ -590,12 +608,6 @@ "refresh": "Vernieuwen", "regeneration_indicator.label": "Aan het laden…", "regeneration_indicator.sublabel": "Jouw starttijdlijn wordt aangemaakt!", - "relationship_severance_notification.purged_data": "verwijderd door beheerders", - "relationship_severance_notification.relationships": "{count, plural, one {# volgrelatie} other {# volgrelaties}}", - "relationship_severance_notification.types.account_suspension": "Account is opgeschort", - "relationship_severance_notification.types.domain_block": "Domein is opgeschort", - "relationship_severance_notification.types.user_domain_block": "Je hebt dit domein geblokkeerd", - "relationship_severance_notification.view": "Weergeven", "relative_time.days": "{number}d", "relative_time.full.days": "{number, plural, one {# dag} other {# dagen}} geleden", "relative_time.full.hours": "{number, plural, one {# uur} other {# uur}} geleden", @@ -706,7 +718,7 @@ "status.edited_x_times": "{count, plural, one {{count} keer} other {{count} keer}} bewerkt", "status.embed": "Embedden", "status.favourite": "Favoriet", - "status.favourites": "{count, plural, one {# favoriet} other {# favorieten}}", + "status.favourites": "{count, plural, one {favoriet} other {favorieten}}", "status.filter": "Dit bericht filteren", "status.filtered": "Gefilterd", "status.hide": "Bericht verbergen", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index 7742d83e17..14b355233b 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -468,10 +468,23 @@ "notification.follow": "{name} fylgde deg", "notification.follow_request": "{name} har bedt om å fylgja deg", "notification.mention": "{name} nemnde deg", + "notification.moderation-warning.learn_more": "Lær meir", + "notification.moderation_warning": "Du har mottatt ei moderasjonsåtvaring", + "notification.moderation_warning.action_delete_statuses": "Nokre av innlegga dine har blitt fjerna.", + "notification.moderation_warning.action_disable": "Kontoen din har blitt deaktivert.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Nokre av innlegga dine har blitt markert som sensitive.", + "notification.moderation_warning.action_none": "Kontoen din har mottatt ei moderasjonsåtvaring.", + "notification.moderation_warning.action_sensitive": "Innlegga dine vil bli markerte som sensitive frå no av.", + "notification.moderation_warning.action_silence": "Kontoen din har blitt avgrensa.", + "notification.moderation_warning.action_suspend": "Kontoen din har blitt suspendert.", "notification.own_poll": "Rundspørjinga di er ferdig", "notification.poll": "Ei rundspørjing du har røysta i er ferdig", "notification.reblog": "{name} framheva innlegget ditt", - "notification.severed_relationships": "Forholda med {name} er brotne", + "notification.relationships_severance_event": "Tapte samband med {name}", + "notification.relationships_severance_event.account_suspension": "Ein administrator på {from} har utvist {target}, som tyder at du ikkje lenger får oppdateringar frå dei eller kan samhandla med dei.", + "notification.relationships_severance_event.domain_block": "Ein administrator på {from} har blokkert {target}, inkludert {followersCount} av fylgjarane dine og {followingCount, plural, one {# konto} other {# kontoar}} du fylgjer.", + "notification.relationships_severance_event.learn_more": "Lær meir", + "notification.relationships_severance_event.user_domain_block": "Du har blokkert {target}, fjerna {followersCount} av fylgjarane dine og {followingCount, plural, one {# konto} other {# kontoar}} du fylgjer.", "notification.status": "{name} la nettopp ut", "notification.update": "{name} redigerte eit innlegg", "notification_requests.accept": "Godkjenn", @@ -484,6 +497,8 @@ "notifications.column_settings.admin.sign_up": "Nyleg registrerte:", "notifications.column_settings.alert": "Skrivebordsvarsel", "notifications.column_settings.favourite": "Favorittar:", + "notifications.column_settings.filter_bar.advanced": "Vis alle kategoriar", + "notifications.column_settings.filter_bar.category": "Snøggfilterline", "notifications.column_settings.follow": "Nye fylgjarar:", "notifications.column_settings.follow_request": "Ny fylgjarførespurnader:", "notifications.column_settings.mention": "Omtalar:", @@ -588,12 +603,6 @@ "refresh": "Oppdater", "regeneration_indicator.label": "Lastar…", "regeneration_indicator.sublabel": "Heimetidslina di vert førebudd!", - "relationship_severance_notification.purged_data": "sletta av administratorar", - "relationship_severance_notification.relationships": "{count, plural, one {# forhold} other {# forhold}}", - "relationship_severance_notification.types.account_suspension": "Kontoen er utvist", - "relationship_severance_notification.types.domain_block": "Domenet er utestengt", - "relationship_severance_notification.types.user_domain_block": "Du blokkerte dette domenet", - "relationship_severance_notification.view": "Sjå", "relative_time.days": "{number}dg", "relative_time.full.days": "{number, plural, one {# dag} other {# dagar}} sidan", "relative_time.full.hours": "{number, plural, one {# time} other {# timar}} sidan", diff --git a/app/javascript/mastodon/locales/pa.json b/app/javascript/mastodon/locales/pa.json index e09dd9067a..c693c24721 100644 --- a/app/javascript/mastodon/locales/pa.json +++ b/app/javascript/mastodon/locales/pa.json @@ -15,21 +15,32 @@ "account.cancel_follow_request": "ਫ਼ਾਲੋ ਕਰਨ ਨੂੰ ਰੱਦ ਕਰੋ", "account.copy": "ਪਰੋਫਾਇਲ ਲਈ ਲਿੰਕ ਕਾਪੀ ਕਰੋ", "account.direct": "ਨਿੱਜੀ ਜ਼ਿਕਰ @{name}", + "account.domain_blocked": "ਡੋਮੇਨ ਉੱਤੇ ਪਾਬੰਦੀ", "account.edit_profile": "ਪਰੋਫਾਈਲ ਨੂੰ ਸੋਧੋ", + "account.enable_notifications": "ਜਦੋਂ {name} ਪੋਸਟ ਕਰੇ ਤਾਂ ਮੈਨੂੰ ਸੂਚਨਾ ਦਿਓ", + "account.endorse": "ਪਰੋਫਾਇਲ ਉੱਤੇ ਫ਼ੀਚਰ", "account.featured_tags.last_status_at": "{date} ਨੂੰ ਆਖਰੀ ਪੋਸਟ", "account.featured_tags.last_status_never": "ਕੋਈ ਪੋਸਟ ਨਹੀਂ", "account.follow": "ਫ਼ਾਲੋ", + "account.follow_back": "ਵਾਪਸ ਫਾਲ਼ੋ ਕਰੋ", "account.followers": "ਫ਼ਾਲੋਅਰ", "account.followers.empty": "ਇਸ ਵਰਤੋਂਕਾਰ ਨੂੰ ਹਾਲੇ ਕੋਈ ਫ਼ਾਲੋ ਨਹੀਂ ਕਰਦਾ ਹੈ।", + "account.followers_counter": "{count, plural, one {{counter} ਫ਼ਾਲੋਅਰ} other {{counter} ਫ਼ਾਲੋਅਰ}}", "account.following": "ਫ਼ਾਲੋ ਕੀਤਾ", + "account.following_counter": "{count, plural, one {{counter} ਨੂੰ ਫ਼ਾਲੋ} other {{counter} ਨੂੰ ਫ਼ਾਲੋ}}", "account.follows.empty": "ਇਹ ਵਰਤੋਂਕਾਰ ਹਾਲੇ ਕਿਸੇ ਨੂੰ ਫ਼ਾਲੋ ਨਹੀਂ ਕਰਦਾ ਹੈ।", "account.go_to_profile": "ਪਰੋਫਾਇਲ ਉੱਤੇ ਜਾਓ", "account.media": "ਮੀਡੀਆ", "account.muted": "ਮੌਨ ਕੀਤੀਆਂ", + "account.mutual": "ਸਾਂਝੇ", + "account.no_bio": "ਕੋਈ ਵਰਣਨ ਨਹੀਂ ਦਿੱਤਾ।", + "account.open_original_page": "ਅਸਲ ਸਫ਼ੇ ਨੂੰ ਖੋਲ੍ਹੋ", "account.posts": "ਪੋਸਟਾਂ", "account.posts_with_replies": "ਪੋਸਤਾਂ ਅਤੇ ਜਵਾਬ", + "account.report": "{name} ਬਾਰੇ ਰਿਪੋਰਟ ਕਰੋ", "account.requested": "ਮਨਜ਼ੂਰੀ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ। ਫ਼ਾਲੋ ਬੇਨਤੀਆਂ ਨੂੰ ਰੱਦ ਕਰਨ ਲਈ ਕਲਿੱਕ ਕਰੋ", "account.requested_follow": "{name} ਨੇ ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕਰਨ ਦੀ ਬੇਨਤੀ ਕੀਤੀ ਹੈ", + "account.share": "{name} ਦਾ ਪਰੋਫ਼ਾਇਲ ਸਾਂਝਾ ਕਰੋ", "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unblock": "@{name} ਤੋਂ ਪਾਬੰਦੀ ਹਟਾਓ", "account.unblock_domain": "{domain} ਡੋਮੇਨ ਤੋਂ ਪਾਬੰਦੀ ਹਟਾਓ", @@ -41,6 +52,9 @@ "admin.dashboard.retention.cohort_size": "ਨਵੇਂ ਵਰਤੋਂਕਾਰ", "alert.unexpected.title": "ਓਹੋ!", "announcement.announcement": "ਹੋਕਾ", + "block_modal.show_less": "ਘੱਟ ਦਿਖਾਓ", + "block_modal.show_more": "ਵੱਧ ਦਿਖਾਓ", + "block_modal.title": "ਵਰਤੋਂਕਾਰ ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਉਣੀ ਹੈ?", "bundle_column_error.error.title": "ਓਹ ਹੋ!", "bundle_column_error.network.title": "ਨੈੱਟਵਰਕ ਦੀ ਸਮੱਸਿਆ", "bundle_column_error.retry": "ਮੁੜ-ਕੋਸ਼ਿਸ਼ ਕਰੋ", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 3ee134d6c5..b763f740a2 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "Użyj istniejącej kategorii lub utwórz nową", "filter_modal.select_filter.title": "Filtruj ten wpis", "filter_modal.title.status": "Filtruj wpis", + "filtered_notifications_banner.mentions": "{count, plural, one {wzmianka} few {wzmianki} other {wzmianek}}", "filtered_notifications_banner.pending_requests": "Powiadomienia od {count, plural, =0 {żadnej osoby którą możesz znać} one {# osoby którą możesz znać} other {# osób które możesz znać}}", "filtered_notifications_banner.title": "Powiadomienia filtrowane", "firehose.all": "Wszystko", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "Mimo że Twoje konto nie jest zablokowane, zespół {domain} uznał że możesz chcieć ręcznie przejrzeć prośby o możliwość obserwacji.", "follow_suggestions.curated_suggestion": "Wybrane przez personel", "follow_suggestions.dismiss": "Nie pokazuj ponownie", + "follow_suggestions.featured_longer": "Wybrane przez zespół {domain}", + "follow_suggestions.friends_of_friends_longer": "Popularni wśród ludzi których obserwujesz", "follow_suggestions.hints.featured": "Ten profil został wybrany przez zespół {domain}.", "follow_suggestions.hints.friends_of_friends": "Ten profil jest popularny w gronie użytkowników, których obserwujesz.", "follow_suggestions.hints.most_followed": "Ten profil jest jednym z najczęściej obserwowanych na {domain}.", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Ten profil jest podobny do profili ostatnio przez ciebie zaobserwowanych.", "follow_suggestions.personalized_suggestion": "Sugestia spersonalizowana", "follow_suggestions.popular_suggestion": "Sugestia popularna", + "follow_suggestions.popular_suggestion_longer": "Popularni na {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Podobne do ostatnio zaobserwowanych przez ciebie profilów", "follow_suggestions.view_all": "Pokaż wszystkie", "follow_suggestions.who_to_follow": "Kogo obserwować", "followed_tags": "Obserwowane hasztagi", @@ -468,10 +473,22 @@ "notification.follow": "{name} obserwuje Cię", "notification.follow_request": "{name} chce cię zaobserwować", "notification.mention": "Wspomniało o Tobie przez {name}", + "notification.moderation-warning.learn_more": "Dowiedz się więcej", + "notification.moderation_warning": "Otrzymałeś/-łaś ostrzeżenie moderacyjne", + "notification.moderation_warning.action_delete_statuses": "Niektóre twoje wpisy zostały usunięte.", + "notification.moderation_warning.action_disable": "Twoje konto zostało wyłączone.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Niektóre twoje wpisy zostały oznaczone jako wrażliwe.", + "notification.moderation_warning.action_none": "Twoje konto otrzymało ostrzeżenie moderacyjne.", + "notification.moderation_warning.action_sensitive": "Twoje wpisy będą od teraz oznaczane jako wrażliwe.", + "notification.moderation_warning.action_silence": "Twoje konto zostało ograniczone.", + "notification.moderation_warning.action_suspend": "Twoje konto zostało zawieszone.", "notification.own_poll": "Twoje głosowanie zakończyło się", "notification.poll": "Głosowanie w którym brałeś(-aś) udział zakończyło się", "notification.reblog": "Twój post został podbity przez {name}", - "notification.severed_relationships": "Zerwano związki z {name}", + "notification.relationships_severance_event": "Utracone związki z {name}", + "notification.relationships_severance_event.account_suspension": "Administrator z {from} zawiesił {target}, więc nie dostaniesz wieści ani nie wejdziesz w interakcje z użytkownikami z tego serwera.", + "notification.relationships_severance_event.domain_block": "Administrator z {from} zablokował {target}, w tym {followersCount} z Twoich obserwujących i {followingCount, plural, one {# konto} other {# konta}} które obserwujesz.", + "notification.relationships_severance_event.learn_more": "Dowiedz się więcej", "notification.status": "{name} opublikował(a) nowy wpis", "notification.update": "{name} edytował(a) post", "notification_requests.accept": "Akceptuj", @@ -590,12 +607,6 @@ "refresh": "Odśwież", "regeneration_indicator.label": "Ładuję…", "regeneration_indicator.sublabel": "Twoja oś czasu jest przygotowywana!", - "relationship_severance_notification.purged_data": "wyczyszczone przez administratorów", - "relationship_severance_notification.relationships": "{count, plural, one {# związek} few {# związki} other {# związków}}", - "relationship_severance_notification.types.account_suspension": "Konto zostało zawieszone", - "relationship_severance_notification.types.domain_block": "Domena została zawieszona", - "relationship_severance_notification.types.user_domain_block": "Domena przez ciebie blokowana", - "relationship_severance_notification.view": "Pokaż", "relative_time.days": "{number} dni", "relative_time.full.days": "{number, plural, one {# dzień} few {# dni} many {# dni} other {# dni}} temu", "relative_time.full.hours": "{number, plural, one {# godzinę} few {# godziny} many {# godzin} other {# godzin}} temu", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 28a18667ae..b11daeaaa7 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -89,8 +89,12 @@ "announcement.announcement": "Comunicados", "attachments_list.unprocessed": "(não processado)", "audio.hide": "Ocultar áudio", + "block_modal.remote_users_caveat": "Pediremos ao servidor {domínio} que respeite sua decisão. No entanto, a conformidade não é garantida pois alguns servidores podem lidar com os blocos de maneira diferente. As postagens públicas ainda podem estar visíveis para usuários não logados.", "block_modal.show_less": "Mostrar menos", "block_modal.show_more": "Mostrar mais", + "block_modal.they_cant_mention": "Eles não podem mencionar ou seguir você.", + "block_modal.they_cant_see_posts": "Eles não podem ver suas postagens e você não verá as deles.", + "block_modal.they_will_know": "Eles podem ver que estão bloqueados.", "block_modal.title": "Bloquear usuário?", "block_modal.you_wont_see_mentions": "Você não verá publicações que os mencionem.", "boost_modal.combo": "Pressione {combo} para pular isso na próxima vez", @@ -173,6 +177,7 @@ "confirmations.delete_list.message": "Você tem certeza de que deseja excluir esta lista?", "confirmations.discard_edit_media.confirm": "Descartar", "confirmations.discard_edit_media.message": "Há mudanças não salvas na descrição ou pré-visualização da mídia. Descartar assim mesmo?", + "confirmations.domain_block.confirm": "Servidor de blocos", "confirmations.domain_block.message": "Você tem certeza de que deseja bloquear tudo de {domain}? Você não verá mais o conteúdo desta instância em nenhuma linha do tempo pública ou nas suas notificações. Seus seguidores desta instância serão removidos.", "confirmations.edit.confirm": "Editar", "confirmations.edit.message": "Editar agora irá substituir a mensagem que está sendo criando. Tem certeza de que deseja continuar?", @@ -204,8 +209,27 @@ "dismissable_banner.explore_statuses": "Estas são postagens de toda a rede social que estão ganhando tração hoje. Postagens mais recentes com mais impulsos e favoritos têm classificações mais altas.", "dismissable_banner.explore_tags": "Estas hashtags estão ganhando popularidade no momento entre as pessoas deste e de outros servidores da rede descentralizada.", "dismissable_banner.public_timeline": "Estas são as publicações públicas mais recentes de pessoas na rede social que pessoas em {domain} seguem.", + "domain_block_modal.block": "Servidor de blocos.", + "domain_block_modal.block_account_instead": "Bloco @(nome)", "domain_block_modal.they_can_interact_with_old_posts": "Pessoas deste servidor podem interagir com suas publicações antigas.", "domain_block_modal.they_cant_follow": "Ninguém deste servidor pode lhe seguir.", + "domain_block_modal.they_wont_know": "Eles não saberão que foram bloqueados.", + "domain_block_modal.title": "Dominio do bloco", + "domain_block_modal.you_will_lose_followers": "Todos os seus seguidores deste servidor serão removidos.", + "domain_block_modal.you_wont_see_posts": "Você não verá postagens ou notificações de usuários neste servidor.", + "domain_pill.activitypub_lets_connect": "Ele permite que você se conecte e interaja com pessoas não apenas no Mastodon, mas também em diferentes aplicativos sociais.", + "domain_pill.activitypub_like_language": "ActivityPub é como a linguagem que o Mastodon fala com outras redes sociais.", + "domain_pill.server": "Servidor", + "domain_pill.their_handle": "Seu identificador:", + "domain_pill.their_server": "Sua casa digital, onde ficam todas as suas postagens.", + "domain_pill.their_username": "Seu identificador exclusivo em seu servidor. É possível encontrar usuários com o mesmo nome de usuário em servidores diferentes.", + "domain_pill.username": "Nome de usuário", + "domain_pill.whats_in_a_handle": "O que há em uma alça?", + "domain_pill.who_they_are": "Como os identificadores indicam quem alguém é e onde está, você pode interagir com pessoas na web social de .", + "domain_pill.who_you_are": "Como seu identificador indica quem você é e onde está, as pessoas podem interagir com você nas redes sociais das .", + "domain_pill.your_handle": "Seu identificador:", + "domain_pill.your_server": "Sua casa digital, onde ficam todas as suas postagens. Não gosta deste? Transfira servidores a qualquer momento e traga seus seguidores também.", + "domain_pill.your_username": "Seu identificador exclusivo neste servidor. É possível encontrar usuários com o mesmo nome de usuário em servidores diferentes.", "embed.instructions": "Incorpore este toot no seu site ao copiar o código abaixo.", "embed.preview": "Aqui está como vai ficar:", "emoji_button.activity": "Atividade", @@ -273,6 +297,8 @@ "filter_modal.select_filter.subtitle": "Use uma categoria existente ou crie uma nova", "filter_modal.select_filter.title": "Filtrar esta publicação", "filter_modal.title.status": "Filtrar uma publicação", + "filtered_notifications_banner.mentions": "{count, plural, one {menção} other {menções}}", + "filtered_notifications_banner.pending_requests": "Notificações de {count, plural, =0 {no one} one {one person} other {# people}} que você talvez conheça", "filtered_notifications_banner.title": "Notificações filtradas", "firehose.all": "Tudo", "firehose.local": "Este servidor", @@ -282,6 +308,8 @@ "follow_requests.unlocked_explanation": "Apesar de seu perfil não ser trancado, {domain} exige que você revise a solicitação para te seguir destes perfis manualmente.", "follow_suggestions.curated_suggestion": "Escolha da equipe", "follow_suggestions.dismiss": "Não mostrar novamente", + "follow_suggestions.featured_longer": "Escolhido à mão pela equipe de {domain}", + "follow_suggestions.friends_of_friends_longer": "Popular entre as pessoas que você segue", "follow_suggestions.hints.featured": "Este perfil foi escolhido a dedo pela equipe {domain}.", "follow_suggestions.hints.friends_of_friends": "Este perfil é popular entre as pessoas que você segue.", "follow_suggestions.hints.most_followed": "Este perfil é um dos mais seguidos em {domain}.", @@ -289,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Este perfil é semelhante aos perfis que você seguiu recentemente.", "follow_suggestions.personalized_suggestion": "Sugestão personalizada", "follow_suggestions.popular_suggestion": "Sugestão popular", + "follow_suggestions.popular_suggestion_longer": "Popular em {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Similar a perfis que você seguiu recentemente", "follow_suggestions.view_all": "Visualizar tudo", "follow_suggestions.who_to_follow": "Quem seguir", "followed_tags": "Hashtags seguidas", @@ -402,7 +432,9 @@ "loading_indicator.label": "Carregando…", "media_gallery.toggle_visible": "{number, plural, one {Ocultar mídia} other {Ocultar mídias}}", "moved_to_account_banner.text": "Sua conta {disabledAccount} está desativada porque você a moveu para {movedToAccount}.", + "mute_modal.hide_from_notifications": "Ocultar das notificações", "mute_modal.hide_options": "Ocultar opções", + "mute_modal.indefinite": "Até que eu os ative", "mute_modal.show_options": "Mostrar opções", "mute_modal.they_can_mention_and_follow": "Eles podem mencionar e seguir você, mas você não os verá.", "mute_modal.they_wont_know": "Eles não saberão que foram silenciados.", @@ -441,9 +473,23 @@ "notification.follow": "{name} te seguiu", "notification.follow_request": "{name} quer te seguir", "notification.mention": "{name} te mencionou", + "notification.moderation-warning.learn_more": "Aprender mais", + "notification.moderation_warning": "Você recebeu um aviso de moderação", + "notification.moderation_warning.action_delete_statuses": "Algumas das suas publicações foram removidas.", + "notification.moderation_warning.action_disable": "Sua conta foi desativada.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Algumas de suas publicações foram marcadas por ter conteúdo sensível.", + "notification.moderation_warning.action_none": "Sua conta recebeu um aviso de moderação.", + "notification.moderation_warning.action_sensitive": "Suas publicações serão marcadas como sensíveis a partir de agora.", + "notification.moderation_warning.action_silence": "Sua conta foi limitada.", + "notification.moderation_warning.action_suspend": "Sua conta foi suspensa.", "notification.own_poll": "Sua enquete terminou", "notification.poll": "Uma enquete que você votou terminou", "notification.reblog": "{name} deu boost no teu toot", + "notification.relationships_severance_event": "Conexões perdidas com {name}", + "notification.relationships_severance_event.account_suspension": "Um administrador de {from} suspendeu {target}, o que significa que você não pode mais receber atualizações deles ou interagir com eles.", + "notification.relationships_severance_event.domain_block": "An admin from {from} has blocked {target}, including {followersCount} of your followers and {followingCount, plural, one {# account} other {# accounts}} you follow.", + "notification.relationships_severance_event.learn_more": "Saber mais", + "notification.relationships_severance_event.user_domain_block": "You have blocked {target}, removing {followersCount} of your followers and {followingCount, plural, one {# account} other {# accounts}} you follow.", "notification.status": "{name} acabou de tootar", "notification.update": "{name} editou uma publicação", "notification_requests.accept": "Aceitar", @@ -456,6 +502,8 @@ "notifications.column_settings.admin.sign_up": "Novas inscrições:", "notifications.column_settings.alert": "Notificações no computador", "notifications.column_settings.favourite": "Favoritos:", + "notifications.column_settings.filter_bar.advanced": "Exibir todas as categorias", + "notifications.column_settings.filter_bar.category": "Barra de filtro rápido", "notifications.column_settings.follow": "Seguidores:", "notifications.column_settings.follow_request": "Seguidores pendentes:", "notifications.column_settings.mention": "Menções:", @@ -481,7 +529,9 @@ "notifications.permission_denied": "Navegador não tem permissão para ativar notificações no computador.", "notifications.permission_denied_alert": "Verifique a permissão do navegador para ativar notificações no computador.", "notifications.permission_required": "Ativar notificações no computador exige permissão do navegador.", + "notifications.policy.filter_new_accounts.hint": "Created within the past {days, plural, one {one day} other {# days}}", "notifications.policy.filter_new_accounts_title": "Novas contas", + "notifications.policy.filter_not_followers_hint": "Including people who have been following you fewer than {days, plural, one {one day} other {# days}}", "notifications.policy.filter_not_followers_title": "Pessoas que não estão te seguindo", "notifications.policy.filter_not_following_hint": "Até que você os aprove manualmente", "notifications.policy.filter_not_following_title": "Pessoas que você não segue", @@ -569,6 +619,7 @@ "relative_time.minutes": "{number}m", "relative_time.seconds": "{number}s", "relative_time.today": "hoje", + "reply_indicator.attachments": "{count, plural, one {# attachment} other {# attachments}}", "reply_indicator.cancel": "Cancelar", "reply_indicator.poll": "Enquete", "report.block": "Bloquear", @@ -667,6 +718,7 @@ "status.edited_x_times": "Editado {count, plural, one {{count} hora} other {{count} vezes}}", "status.embed": "Incorporar", "status.favourite": "Favorita", + "status.favourites": "{count, plural, one {favorite} other {favorites}}", "status.filter": "Filtrar esta publicação", "status.filtered": "Filtrado", "status.hide": "Ocultar publicação", @@ -687,6 +739,7 @@ "status.reblog": "Dar boost", "status.reblog_private": "Dar boost para o mesmo público", "status.reblogged_by": "{name} deu boost", + "status.reblogs": "{count, plural, one {boost} other {boosts}}", "status.reblogs.empty": "Nada aqui. Quando alguém der boost, o usuário aparecerá aqui.", "status.redraft": "Excluir e rascunhar", "status.remove_bookmark": "Remover do Salvos", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index 6732973d5a..70903065da 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "Utilize uma categoria existente ou crie uma nova", "filter_modal.select_filter.title": "Filtrar esta publicação", "filter_modal.title.status": "Filtrar uma publicação", + "filtered_notifications_banner.mentions": "{count, plural, one {menção} other {menções}}", "filtered_notifications_banner.pending_requests": "Notificações de {count, plural, =0 {ninguém} one {uma pessoa} other {# pessoas}} que talvez conheça", "filtered_notifications_banner.title": "Notificações filtradas", "firehose.all": "Todas", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "Apesar de a sua não ser privada, a administração de {domain} pensa que poderá querer rever manualmente os pedidos de seguimento dessas contas.", "follow_suggestions.curated_suggestion": "Escolha da equipe", "follow_suggestions.dismiss": "Não mostrar novamente", + "follow_suggestions.featured_longer": "Escolhido a dedo pela equipa de {domain}", + "follow_suggestions.friends_of_friends_longer": "Popular entre as pessoas que segue", "follow_suggestions.hints.featured": "Este perfil foi escolhido a dedo pela equipe {domain}.", "follow_suggestions.hints.friends_of_friends": "Este perfil é popular entre as pessoas que você segue.", "follow_suggestions.hints.most_followed": "Este perfil é um dos mais seguidos no {domain}.", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Este perfil é semelhante aos perfis que você seguiu mais recentemente.", "follow_suggestions.personalized_suggestion": "Sugestão personalizada", "follow_suggestions.popular_suggestion": "Sugestão popular", + "follow_suggestions.popular_suggestion_longer": "Popular em {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Semelhantes aos perfis que seguiu recentemente", "follow_suggestions.view_all": "Ver tudo", "follow_suggestions.who_to_follow": "Quem seguir", "followed_tags": "Hashtags seguidas", @@ -468,10 +473,23 @@ "notification.follow": "{name} começou a seguir-te", "notification.follow_request": "{name} pediu para segui-lo", "notification.mention": "{name} mencionou-te", + "notification.moderation-warning.learn_more": "Saber mais", + "notification.moderation_warning": "Recebeu um aviso de moderação", + "notification.moderation_warning.action_delete_statuses": "Algumas das suas publicações foram removidas.", + "notification.moderation_warning.action_disable": "A sua conta foi desativada.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Algumas das suas publicações foram assinaladas como sensíveis.", + "notification.moderation_warning.action_none": "A sua conta recebeu um aviso de moderação.", + "notification.moderation_warning.action_sensitive": "As suas publicações serão, a partir de agora, assinaladas como sensíveis.", + "notification.moderation_warning.action_silence": "A sua conta foi limitada.", + "notification.moderation_warning.action_suspend": "A sua conta foi suspensa.", "notification.own_poll": "A sua votação terminou", "notification.poll": "Uma votação em que participaste chegou ao fim", "notification.reblog": "{name} reforçou a tua publicação", - "notification.severed_relationships": "Relações com {name} cessadas", + "notification.relationships_severance_event": "Perdeu as ligações com {name}", + "notification.relationships_severance_event.account_suspension": "Um administrador de {from} suspendeu {target}, o que significa que já não pode receber atualizações dele ou interagir com ele.", + "notification.relationships_severance_event.domain_block": "Um administrador de {from} bloqueou {target}, incluindo {followersCount} dos seus seguidores e {followingCount, plural, one {# conta} other {# contas}} que segue.", + "notification.relationships_severance_event.learn_more": "Saber mais", + "notification.relationships_severance_event.user_domain_block": "Bloqueou {target}, removendo {followersCount} dos seus seguidores e {followingCount, plural, one {# conta} other {# contas}} que segue.", "notification.status": "{name} acabou de publicar", "notification.update": "{name} editou uma publicação", "notification_requests.accept": "Aceitar", @@ -590,12 +608,6 @@ "refresh": "Actualizar", "regeneration_indicator.label": "A carregar…", "regeneration_indicator.sublabel": "A tua página inicial está a ser preparada!", - "relationship_severance_notification.purged_data": "purgado pelos administradores", - "relationship_severance_notification.relationships": "{count, plural,one {# relação} other {# relações}}", - "relationship_severance_notification.types.account_suspension": "A conta foi suspensa", - "relationship_severance_notification.types.domain_block": "O domínio foi suspenso", - "relationship_severance_notification.types.user_domain_block": "Bloqueou este domínio", - "relationship_severance_notification.view": "Visualizar", "relative_time.days": "{number}d", "relative_time.full.days": "{number, plural,one {# dia} other {# dias}} atrás", "relative_time.full.hours": "{number, plural,one {# hora}other {# horas}} atrás", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index 7a60f3bb7c..cd09a505b3 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -89,6 +89,14 @@ "announcement.announcement": "Объявление", "attachments_list.unprocessed": "(не обработан)", "audio.hide": "Скрыть аудио", + "block_modal.remote_users_caveat": "Мы попросим сервер {domain} уважать ваше решение. Однако, соблюдение требований не гарантировано, поскольку некоторые серверы могут работать с блокировками по-разному. Публичные записи по-прежнему могут быть видны неавторизованным пользователям.", + "block_modal.show_less": "Показать меньше", + "block_modal.show_more": "Показать больше", + "block_modal.they_cant_mention": "Он не может упоминать или подписываться на вас.", + "block_modal.they_cant_see_posts": "Он не может видеть ваши сообщения, и вы не увидите его.", + "block_modal.they_will_know": "Он может видеть, что он заблокирован.", + "block_modal.title": "Заблокировать пользователя?", + "block_modal.you_wont_see_mentions": "Вы не увидите записи, которые упоминают его.", "boost_modal.combo": "{combo}, чтобы пропустить это в следующий раз", "bundle_column_error.copy_stacktrace": "Скопировать отчет об ошибке", "bundle_column_error.error.body": "Запрошенная страница не может быть отображена. Это может быть вызвано ошибкой в нашем коде или проблемой совместимости браузера.", @@ -169,6 +177,7 @@ "confirmations.delete_list.message": "Вы действительно хотите навсегда удалить этот список?", "confirmations.discard_edit_media.confirm": "Отменить", "confirmations.discard_edit_media.message": "У вас есть несохранённые изменения описания мультимедиа или предпросмотра, отменить их?", + "confirmations.domain_block.confirm": "Заблокировать сервер", "confirmations.domain_block.message": "Вы точно уверены, что хотите заблокировать {domain} полностью? В большинстве случаев нескольких блокировок и игнорирований вполне достаточно. Вы перестанете видеть публичную ленту и уведомления оттуда. Ваши подписчики из этого домена будут удалены.", "confirmations.edit.confirm": "Редактировать", "confirmations.edit.message": "В данный момент, редактирование перезапишет составляемое вами сообщение. Вы уверены, что хотите продолжить?", @@ -200,6 +209,27 @@ "dismissable_banner.explore_statuses": "Эти сообщения со связанных серверов сети сейчас набирают популярность.", "dismissable_banner.explore_tags": "Эти хэштеги привлекают людей на этом и других серверах децентрализованной сети прямо сейчас.", "dismissable_banner.public_timeline": "Это самые последние публичные сообщения от людей в социальной сети, за которыми подписались пользователи {domain}.", + "domain_block_modal.block": "Заблокировать сервер", + "domain_block_modal.block_account_instead": "Заблокировать @{name} вместо", + "domain_block_modal.they_can_interact_with_old_posts": "Люди с этого сервера могут взаимодействовать с вашими старыми записями.", + "domain_block_modal.they_cant_follow": "Никто из этого сервера не может подписываться на вас.", + "domain_block_modal.they_wont_know": "Он не будет знать, что его заблокировали.", + "domain_block_modal.title": "Заблокировать домен?", + "domain_block_modal.you_will_lose_followers": "Все ваши подписчики с этого сервера будут удалены.", + "domain_block_modal.you_wont_see_posts": "Вы не будете видеть записи или уведомления от пользователей на этом сервере.", + "domain_pill.activitypub_lets_connect": "Это позволяет вам общаться и взаимодействовать с людьми не только на Mastodon, но и в различных социальных приложениях.", + "domain_pill.activitypub_like_language": "ActivityPub как язык Mastodon говорит с другими социальными сетями.", + "domain_pill.server": "Сервер", + "domain_pill.their_handle": "Его бейдж:", + "domain_pill.their_server": "Цифровой дом, где находятся все записи.", + "domain_pill.their_username": "Уникальный идентификатор на сервере. Возможно найти пользователей с одним и тем же именем пользователя на разных серверах.", + "domain_pill.username": "Имя пользователя", + "domain_pill.whats_in_a_handle": "Что такое бейдж?", + "domain_pill.who_they_are": "Поскольку бейджи говорят о том, кто и где находится, вы можете взаимодействовать с людьми в социальной сети .", + "domain_pill.who_you_are": "Поскольку ваш бейдж говорит о том, кто вы и где находитесь, люди могут взаимодействовать с вами через социальную сеть .", + "domain_pill.your_handle": "Ваш бейдж:", + "domain_pill.your_server": "Сервер, где живут все ваши посты. Этот не нравится? Поменяй сервер в любое время вместе со своими подписчиками.", + "domain_pill.your_username": "Ваш уникальный идентификатор на этом сервере. Вы можете найти пользователей с одним именем пользователя на разных серверах.", "embed.instructions": "Встройте этот пост на свой сайт, скопировав следующий код:", "embed.preview": "Так это будет выглядеть:", "emoji_button.activity": "Занятия", @@ -236,6 +266,7 @@ "empty_column.list": "В этом списке пока ничего нет.", "empty_column.lists": "У вас ещё нет списков. Созданные вами списки будут показаны здесь.", "empty_column.mutes": "Вы ещё никого не добавляли в список игнорируемых.", + "empty_column.notification_requests": "Здесь ничего нет! Когда вы получите новые уведомления, они здесь появятся согласно вашим настройкам.", "empty_column.notifications": "У вас пока нет уведомлений. Взаимодействуйте с другими, чтобы завести разговор.", "empty_column.public": "Здесь ничего нет! Опубликуйте что-нибудь или подпишитесь на пользователей с других узлов, чтобы заполнить ленту", "error.unexpected_crash.explanation": "Из-за несовместимого браузера или ошибки в нашем коде, эта страница не может быть корректно отображена.", @@ -266,13 +297,21 @@ "filter_modal.select_filter.subtitle": "Используйте существующую категорию или создайте новую", "filter_modal.select_filter.title": "Фильтровать этот пост", "filter_modal.title.status": "Фильтровать пост", + "filtered_notifications_banner.pending_requests": "Уведомления от {count, plural, =0 {никого} one {# человека} other {# других людей, с кем вы можете быть знакомы}}", + "filtered_notifications_banner.title": "Отфильтрованные уведомления", "firehose.all": "Все", "firehose.local": "Текущий сервер", "firehose.remote": "Другие серверы", "follow_request.authorize": "Авторизовать", "follow_request.reject": "Отказать", "follow_requests.unlocked_explanation": "Хотя ваша учетная запись не закрыта, команда {domain} подумала, что вы захотите просмотреть запросы от этих учетных записей вручную.", + "follow_suggestions.curated_suggestion": "Выбор администрации", "follow_suggestions.dismiss": "Больше не показывать", + "follow_suggestions.hints.featured": "Этот профиль был вручную выбран командой {domain}.", + "follow_suggestions.hints.friends_of_friends": "Этот профиль популярен среди людей, на которых вы подписаны.", + "follow_suggestions.hints.most_followed": "Этот профиль один из самых отслеживаемых на {domain}.", + "follow_suggestions.hints.most_interactions": "Этот профиль в последнее время привлекает много внимания на {domain}.", + "follow_suggestions.hints.similar_to_recently_followed": "Этот профиль похож на другие профили, на которые вы подписывались в последнее время.", "follow_suggestions.personalized_suggestion": "Персонализированное предложение", "follow_suggestions.popular_suggestion": "Популярное предложение", "follow_suggestions.view_all": "Посмотреть все", @@ -388,6 +427,15 @@ "loading_indicator.label": "Загрузка…", "media_gallery.toggle_visible": "Показать/скрыть {number, plural, =1 {изображение} other {изображения}}", "moved_to_account_banner.text": "Ваша учетная запись {disabledAccount} в настоящее время заморожена, потому что вы переехали на {movedToAccount}.", + "mute_modal.hide_from_notifications": "Скрыть из уведомлений", + "mute_modal.hide_options": "Скрыть параметры", + "mute_modal.indefinite": "Пока я не разблокирую их", + "mute_modal.show_options": "Показать опции", + "mute_modal.they_can_mention_and_follow": "Они могут упоминать и следить за вами, но вы не будете их видеть.", + "mute_modal.they_wont_know": "Они не будут знать, что их заглушили.", + "mute_modal.title": "Заглушить пользователя?", + "mute_modal.you_wont_see_mentions": "Вы не увидите постов, которые их упоминают.", + "mute_modal.you_wont_see_posts": "Они по-прежнему смогут видеть ваши посты, но вы не сможете видеть их посты.", "navigation_bar.about": "О проекте", "navigation_bar.advanced_interface": "Включить многоколоночный интерфейс", "navigation_bar.blocks": "Заблокированные пользователи", @@ -423,8 +471,17 @@ "notification.own_poll": "Ваш опрос закончился", "notification.poll": "Опрос, в котором вы приняли участие, завершился", "notification.reblog": "{name} продвинул(а) ваш пост", + "notification.relationships_severance_event": "Потеряно соединение с {name}", + "notification.relationships_severance_event.account_suspension": "Администратор {from} заблокировал {target}, что означает, что вы больше не сможете получать обновления от них или взаймодествовать с ними.", + "notification.relationships_severance_event.domain_block": "Администратор {from} заблокировал {target} включая {followersCount} ваших подписчиков и {followingCount, plural, one {# аккаунт} few {# аккаунта} other {# аккаунтов}}, на которые вы подписаны.", + "notification.relationships_severance_event.learn_more": "Узнать больше", + "notification.relationships_severance_event.user_domain_block": "Вы заблокировали {target} включая {followersCount} ваших подписчиков и {followingCount, plural, one {# аккаунт} few {# аккаунта} other {# аккаунтов}}, на которые вы подписаны.", "notification.status": "{name} только что запостил", "notification.update": "{name} изменил(а) пост", + "notification_requests.accept": "Принять", + "notification_requests.dismiss": "Отклонить", + "notification_requests.notifications_from": "Уведомления от {name}", + "notification_requests.title": "Отфильтрованные уведомления", "notifications.clear": "Очистить уведомления", "notifications.clear_confirmation": "Вы уверены, что хотите очистить все уведомления?", "notifications.column_settings.admin.report": "Новые жалобы:", @@ -456,6 +513,7 @@ "notifications.permission_denied": "Уведомления на рабочем столе недоступны, так как вы запретили их отправку в браузере. Проверьте настройки для сайта, чтобы включить их обратно.", "notifications.permission_denied_alert": "Уведомления на рабочем столе недоступны, так как вы ранее отклонили запрос на их отправку.", "notifications.permission_required": "Чтобы включить уведомления на рабочем столе, необходимо разрешить их в браузере.", + "notifications.policy.filter_new_accounts_title": "Новые учётные записи", "notifications_permission_banner.enable": "Включить уведомления", "notifications_permission_banner.how_to_control": "Получайте уведомления даже когда Mastodon закрыт, включив уведомления на рабочем столе. А чтобы лишний шум не отвлекал, вы можете настроить какие уведомления вы хотите получать, нажав на кнопку {icon} выше.", "notifications_permission_banner.title": "Будьте в курсе происходящего", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index 0ab1475699..2863442415 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -246,6 +246,7 @@ "empty_column.list": "Tento zoznam je zatiaľ prázdny. Keď ale členovia tohoto zoznamu uverejnia nové príspevky, objavia sa tu.", "empty_column.lists": "Zatiaľ nemáte žiadne zoznamy. Keď nejaký vytvoríte, zobrazí sa tu.", "empty_column.mutes": "Zatiaľ ste si nikoho nestíšili.", + "empty_column.notification_requests": "Všetko čisté! Nič tu nieje. Keď dostaneš nové oboznámenia, zobrazia sa tu podľa tvojich nastavení.", "empty_column.notifications": "Zatiaľ nemáte žiadne upozornenia. Začnú vám pribúdať, keď s vami začnú interagovať ostatní.", "empty_column.public": "Zatiaľ tu nič nie je. Napíšte niečo verejné alebo začnite sledovať účty z iných serverov, aby tu niečo pribudlo.", "error.unexpected_crash.explanation": "Pre chybu v našom kóde alebo problém s kompatibilitou prehliadača nebolo túto stránku možné zobraziť správne.", @@ -293,6 +294,7 @@ "follow_suggestions.hints.similar_to_recently_followed": "Tento profil je podobný profilom, ktoré ste nedávno začali sledovať.", "follow_suggestions.personalized_suggestion": "Prispôsobený návrh", "follow_suggestions.popular_suggestion": "Obľúbený návrh", + "follow_suggestions.popular_suggestion_longer": "Populárne na {domain}", "follow_suggestions.view_all": "Zobraziť všetky", "follow_suggestions.who_to_follow": "Koho sledovať", "followed_tags": "Sledované hashtagy", @@ -442,9 +444,12 @@ "notification.follow": "{name} vás sleduje", "notification.follow_request": "{name} vás žiada sledovať", "notification.mention": "{name} vás spomína", + "notification.moderation-warning.learn_more": "Zisti viac", "notification.own_poll": "Vaša anketa sa skončila", "notification.poll": "Anketa, v ktorej ste hlasovali, sa skončila", "notification.reblog": "{name} zdieľa váš príspevok", + "notification.relationships_severance_event": "Stratené prepojenia s {name}", + "notification.relationships_severance_event.learn_more": "Zisti viac", "notification.status": "{name} uverejňuje niečo nové", "notification.update": "{name} upravuje príspevok", "notification_requests.accept": "Prijať", @@ -486,6 +491,7 @@ "notifications.policy.filter_new_accounts_title": "Nové účty", "notifications.policy.filter_not_followers_title": "Ľudia, ktorí ťa nenasledujú", "notifications.policy.filter_not_following_title": "Ľudia, ktorých nenasleduješ", + "notifications.policy.filter_private_mentions_title": "Nevyžiadané priame spomenutia", "notifications.policy.title": "Filtrovať oznámenia od…", "notifications_permission_banner.enable": "Povoliť upozornenia na ploche", "notifications_permission_banner.how_to_control": "Ak chcete dostávať upozornenia, keď Mastodon nie je otvorený, povoľte upozornenia na ploche. Po ich zapnutí môžete presne kontrolovať, ktoré typy interakcií generujú upozornenia na ploche, a to prostredníctvom tlačidla {icon} vyššie.", @@ -557,8 +563,6 @@ "refresh": "Obnoviť", "regeneration_indicator.label": "Načítavanie…", "regeneration_indicator.sublabel": "Váš domovský kanál sa pripravuje.", - "relationship_severance_notification.types.user_domain_block": "Túto doménu si zablokoval/a", - "relationship_severance_notification.view": "Zobraziť", "relative_time.days": "{number} dní", "relative_time.full.days": "Pred {number, plural, one {# dňom} other {# dňami}}", "relative_time.full.hours": "Pred {number, plural, one {# hodinou} other {# hodinami}}", diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json index d31b410804..7806abc6b5 100644 --- a/app/javascript/mastodon/locales/sl.json +++ b/app/javascript/mastodon/locales/sl.json @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "Uporabite obstoječo kategorijo ali ustvarite novo", "filter_modal.select_filter.title": "Filtriraj to objavo", "filter_modal.title.status": "Filtrirajte objavo", + "filtered_notifications_banner.mentions": "{count, plural, one {omemba} two {omembi} few {omembe} other {omemb}}", "filtered_notifications_banner.pending_requests": "Obvestila od {count, plural, =0 {nikogar, ki bi ga} one {# človeka, ki bi ga} two {# ljudi, ki bi ju} few {# ljudi, ki bi jih} other {# ljudi, ki bi jih}} lahko poznali", "filtered_notifications_banner.title": "Filtrirana obvestila", "firehose.all": "Vse", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "Čeprav vaš račun ni zaklenjen, zaposleni pri {domain} menijo, da bi morda želeli pregledati zahteve za sledenje teh računov ročno.", "follow_suggestions.curated_suggestion": "Izbor osebja", "follow_suggestions.dismiss": "Ne pokaži več", + "follow_suggestions.featured_longer": "Osebno izbrala ekipa {domain}", + "follow_suggestions.friends_of_friends_longer": "Priljubljeno med osebami, ki jim sledite", "follow_suggestions.hints.featured": "Ta profil so izbrali skrbniki strežnika {domain}.", "follow_suggestions.hints.friends_of_friends": "Ta profil je priljubljen med osebami, ki jim sledite.", "follow_suggestions.hints.most_followed": "Ta profil na strežniku {domain} je en izmed najbolj sledenih.", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Ta profil je podoben profilom, ki ste jim nedavno začeli slediti.", "follow_suggestions.personalized_suggestion": "Osebno prilagojen predlog", "follow_suggestions.popular_suggestion": "Priljubljen predlog", + "follow_suggestions.popular_suggestion_longer": "Priljubljeno na {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Podobno profilom, ki ste jim pred kratkim sledili", "follow_suggestions.view_all": "Pokaži vse", "follow_suggestions.who_to_follow": "Komu slediti", "followed_tags": "Sledeni ključniki", @@ -468,10 +473,23 @@ "notification.follow": "{name} vam sledi", "notification.follow_request": "{name} vam želi slediti", "notification.mention": "{name} vas je omenil/a", + "notification.moderation-warning.learn_more": "Več o tem", + "notification.moderation_warning": "Prejeli ste opozorilo moderatorjev", + "notification.moderation_warning.action_delete_statuses": "Nekatere vaše objave so odstranjene.", + "notification.moderation_warning.action_disable": "Vaš račun je bil onemogočen.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Nekatere vaše objave so bile označene kot občutljive.", + "notification.moderation_warning.action_none": "Vaš račun je prejel opozorilo moderatorjev.", + "notification.moderation_warning.action_sensitive": "Vaše objave bodo odslej označene kot občutljive.", + "notification.moderation_warning.action_silence": "Vaš račun je bil omejen.", + "notification.moderation_warning.action_suspend": "Vaš račun je bil suspendiran.", "notification.own_poll": "Vaša anketa je zaključena", "notification.poll": "Anketa, v kateri ste sodelovali, je zaključena", "notification.reblog": "{name} je izpostavila/a vašo objavo", - "notification.severed_relationships": "Veze z {name} prekinjene", + "notification.relationships_severance_event": "Povezave z {name} prekinjene", + "notification.relationships_severance_event.account_suspension": "Skrbnik na {from} je suspendiral račun {target}, kar pomeni, da od računa ne morete več prejemati posodobitev ali imeti z njim interakcij.", + "notification.relationships_severance_event.domain_block": "Skrbnik na {from} je blokiral domeno {target}, vključno z vašimi sledilci ({followersCount}) in {followingCount, plural, one {# računom, ki mu sledite} two {# računoma, ki jima sledite} few {# računi, ki jim sledite} other {# računi, ki jim sledite}}.", + "notification.relationships_severance_event.learn_more": "Več o tem", + "notification.relationships_severance_event.user_domain_block": "Blokirali ste domeno {target}, vključno z vašimi sledilci ({followersCount}) in {followingCount, plural, one {# računom, ki mu sledite} two {# računoma, ki jima sledite} few {# računi, ki jim sledite} other {# računi, ki jim sledite}}.", "notification.status": "{name} je pravkar objavil/a", "notification.update": "{name} je uredil(a) objavo", "notification_requests.accept": "Sprejmi", @@ -484,6 +502,8 @@ "notifications.column_settings.admin.sign_up": "Novi vpisi:", "notifications.column_settings.alert": "Namizna obvestila", "notifications.column_settings.favourite": "Priljubljeni:", + "notifications.column_settings.filter_bar.advanced": "Prikaži vse kategorije", + "notifications.column_settings.filter_bar.category": "Vrstica za hitro filtriranje", "notifications.column_settings.follow": "Novi sledilci:", "notifications.column_settings.follow_request": "Nove prošnje za sledenje:", "notifications.column_settings.mention": "Omembe:", @@ -588,12 +608,6 @@ "refresh": "Osveži", "regeneration_indicator.label": "Nalaganje …", "regeneration_indicator.sublabel": "Vaš domači vir se pripravlja!", - "relationship_severance_notification.purged_data": "očistili skrbniki", - "relationship_severance_notification.relationships": "{count, plural, one {# veza} two {# vezi} few {# veze} other {# vez}}", - "relationship_severance_notification.types.account_suspension": "Račun je bil suspendiran", - "relationship_severance_notification.types.domain_block": "Domena je bila suspendirana", - "relationship_severance_notification.types.user_domain_block": "Domeno ste blokirali", - "relationship_severance_notification.view": "Pogled", "relative_time.days": "{number} d", "relative_time.full.days": "{number, plural, one {pred # dnem} two {pred # dnevoma} few {pred # dnevi} other {pred # dnevi}}", "relative_time.full.hours": "{number, plural, one {pred # uro} two {pred # urama} few {pred # urami} other {pred # urami}}", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index d69231d823..a25eab9cbf 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -297,6 +297,8 @@ "filter_modal.select_filter.subtitle": "Përdorni një kategori ekzistuese, ose krijoni një të re", "filter_modal.select_filter.title": "Filtroje këtë postim", "filter_modal.title.status": "Filtroni një postim", + "filtered_notifications_banner.mentions": "{count, plural, one {përmendje} other {përmendje}}", + "filtered_notifications_banner.pending_requests": "Njoftime prej {count, plural, =0 {askujt} one {një personi} other {# vetësh}} që mund të njihni", "filtered_notifications_banner.title": "Njoftime të filtruar", "firehose.all": "Krejt", "firehose.local": "Këtë shërbyes", @@ -306,6 +308,8 @@ "follow_requests.unlocked_explanation": "Edhe pse llogaria juaj s’është e kyçur, ekipi i {domain} mendoi se mund të donit të shqyrtonit dorazi kërkesa ndjekjeje prej këtyre llogarive.", "follow_suggestions.curated_suggestion": "Zgjedhur nga ekipi", "follow_suggestions.dismiss": "Mos shfaq më", + "follow_suggestions.featured_longer": "Zgjedhur enkas nga ekipi {domain}", + "follow_suggestions.friends_of_friends_longer": "Popullore mes personash që ndiqni", "follow_suggestions.hints.featured": "Ky profil është zgjedhur nga ekipi {domain}.", "follow_suggestions.hints.friends_of_friends": "Ky profil është popullor mes personave që ndiqni.", "follow_suggestions.hints.most_followed": "Ky profil është një nga më të ndjekur në {domain}.", @@ -313,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Ky profil është i ngjashëm me profile që keni ndjekur tani afër.", "follow_suggestions.personalized_suggestion": "Sugjerim i personalizuar", "follow_suggestions.popular_suggestion": "Sugjerim popullor", + "follow_suggestions.popular_suggestion_longer": "Popullore në {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "I ngjashëm me profile që keni zënë të ndiqni së fundi", "follow_suggestions.view_all": "Shihni krejt", "follow_suggestions.who_to_follow": "Cilët të ndiqen", "followed_tags": "Hashtag-ë të ndjekur", @@ -467,10 +473,23 @@ "notification.follow": "{name} zuri t’ju ndjekë", "notification.follow_request": "{name} ka kërkuar t’ju ndjekë", "notification.mention": "{name} ju ka përmendur", + "notification.moderation-warning.learn_more": "Mësoni më tepër", + "notification.moderation_warning": "Keni marrë një sinjalizim moderimi", + "notification.moderation_warning.action_delete_statuses": "Disa nga postimet tuaja janë hequr.", + "notification.moderation_warning.action_disable": "Llogaria juaj është çaktivizuar.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Disa prej postimeve tuaja u është vënë shenjë si me spec.", + "notification.moderation_warning.action_none": "Llogaria juaj ka marrë një sinjalizim moderimi.", + "notification.moderation_warning.action_sensitive": "Postimeve tuaja do t’u vihet shenjë si me spec, nga tani e tutje.", + "notification.moderation_warning.action_silence": "Llogaria juaj është kufizuar.", + "notification.moderation_warning.action_suspend": "Llogaria juaj është pezulluar.", "notification.own_poll": "Pyetësori juaj ka përfunduar", "notification.poll": "Ka përfunduar një pyetësor ku keni votuar", "notification.reblog": "{name} përforcoi mesazhin tuaj", - "notification.severed_relationships": "Marrëdhëniet me {name} u ndërprenë", + "notification.relationships_severance_event": "Lidhje të humbura me {name}", + "notification.relationships_severance_event.account_suspension": "Një përgjegjës nga {from} ka pezulluar {target}, që do të thotë se s’mund të merrni më përditësime prej tij, apo të ndërveproni me të.", + "notification.relationships_severance_event.domain_block": "Një përgjegjës nga {from} ka bllokuar {target}, përfshi {followersCount} të ndjekësve tuaj dhe {followingCount, plural, one {# llogari} other {# llogari}} që ndiqni.", + "notification.relationships_severance_event.learn_more": "Mësoni më tepër", + "notification.relationships_severance_event.user_domain_block": "Keni bllokuar {target}, duke hequr {followersCount} nga ndjekësit tuaj dhe {followingCount, plural, one {# llogari} other {# llogari}} që ndiqni.", "notification.status": "{name} sapo postoi", "notification.update": "{name} përpunoi një postim", "notification_requests.accept": "Pranoje", @@ -510,7 +529,9 @@ "notifications.permission_denied": "S’merren dot njoftime në desktop, ngaqë më herët shfletuesit i janë mohuar lejet për këtë", "notifications.permission_denied_alert": "S’mund të aktivizohen njoftimet në desktop, ngaqë lejet e shfletuesit për këtë janë mohuar më herët", "notifications.permission_required": "S’merren dot njoftime desktop, ngaqë s’është akorduar leja përkatëse.", + "notifications.policy.filter_new_accounts.hint": "Krijuar brenda {days, plural, one {një dite} other {# ditësh}} të shkuara", "notifications.policy.filter_new_accounts_title": "Llogari të reja", + "notifications.policy.filter_not_followers_hint": "Përfshi persona që ju kanë ndjekur brenda më pak se {days, plural, one {një dite} other {# ditësh}}", "notifications.policy.filter_not_followers_title": "Persona që s’ju ndjekin", "notifications.policy.filter_not_following_hint": "Deri sa t’i miratoni dorazi", "notifications.policy.filter_not_following_title": "Persona që s’i ndiqni", @@ -587,12 +608,6 @@ "refresh": "Rifreskoje", "regeneration_indicator.label": "Po ngarkohet…", "regeneration_indicator.sublabel": "Prurja juaj vetjake po përgatitet!", - "relationship_severance_notification.purged_data": "spastruar nga përgjegjës", - "relationship_severance_notification.relationships": "{count, plural, one {# marrëdhënie} other {# marrëdhënie}}", - "relationship_severance_notification.types.account_suspension": "Llogaria është pezulluar", - "relationship_severance_notification.types.domain_block": "Përkatësia është pezulluar", - "relationship_severance_notification.types.user_domain_block": "E bllokuat këtë përkatësi", - "relationship_severance_notification.view": "Shiheni", "relative_time.days": "{number}d", "relative_time.full.days": "{number, plural, one {# ditë} other {# ditë}} më parë", "relative_time.full.hours": "{number, plural, one {# orë} other {# orë}} më parë", diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json index 2d795c76cc..67b706fa13 100644 --- a/app/javascript/mastodon/locales/sr-Latn.json +++ b/app/javascript/mastodon/locales/sr-Latn.json @@ -13,14 +13,14 @@ "about.rules": "Pravila servera", "account.account_note_header": "Napomena", "account.add_or_remove_from_list": "Dodaj ili ukloni sa lista", - "account.badges.bot": "Bot", + "account.badges.bot": "Automatizovano", "account.badges.group": "Grupa", "account.block": "Blokiraj @{name}", "account.block_domain": "Blokiraj domen {domain}", "account.block_short": "Blokiraj", "account.blocked": "Blokiran", "account.browse_more_on_origin_server": "Pregledajte još na originalnom profilu", - "account.cancel_follow_request": "Povuci zahtev za praćenje", + "account.cancel_follow_request": "Otkaži praćenje", "account.copy": "Kopiraj vezu u profil", "account.direct": "Privatno pomeni @{name}", "account.disable_notifications": "Zaustavi obaveštavanje za objave korisnika @{name}", @@ -89,6 +89,14 @@ "announcement.announcement": "Najava", "attachments_list.unprocessed": "(neobrađeno)", "audio.hide": "Sakrij audio", + "block_modal.remote_users_caveat": "Zamolićemo server {domain} da poštuje vašu odluku. Međutim, usklađenost nije zagarantovana jer neki serveri mogu drugačije da obrađuju blokove. Javne objave mogu i dalje biti vidljive korisnicima koji nisu prijavljeni.", + "block_modal.show_less": "Prikaži manje", + "block_modal.show_more": "Prikaži više", + "block_modal.they_cant_mention": "Ne mogu da vas pominju ili prate.", + "block_modal.they_cant_see_posts": "Ne mogu da vide vaše objave, a vi nećete videti njihove.", + "block_modal.they_will_know": "Mogu da vide da su blokirani.", + "block_modal.title": "Blokirati korisnika?", + "block_modal.you_wont_see_mentions": "Nećete videti objave koje ih pominju.", "boost_modal.combo": "Možete pritisnuti {combo} da preskočite ovo sledeći put", "bundle_column_error.copy_stacktrace": "Kopiraj izveštaj o grešci", "bundle_column_error.error.body": "Nije moguće prikazati traženu stranicu. Razlog može biti greška u našem kodu ili problem sa kompatibilnošću pretraživača.", @@ -153,7 +161,7 @@ "compose_form.poll.switch_to_single": "Promenite anketu da biste omogućili jedan izbor", "compose_form.poll.type": "Stil", "compose_form.publish": "Objavi", - "compose_form.publish_form": "Objavi", + "compose_form.publish_form": "Nova objava", "compose_form.reply": "Odgovori", "compose_form.save_changes": "Ažuriraj", "compose_form.spoiler.marked": "Ukloni upozorenje o sadržaju", @@ -169,6 +177,7 @@ "confirmations.delete_list.message": "Da li ste sigurni da želite da trajno izbrišete ovu listu?", "confirmations.discard_edit_media.confirm": "Odbaci", "confirmations.discard_edit_media.message": "Imate nesačuvane promene u opisu ili pregledu medija, da li ipak hoćete da ih odbacite?", + "confirmations.domain_block.confirm": "Blokiraj server", "confirmations.domain_block.message": "Da li ste zaista sigurni da želite da blokirate ceo domen {domain}? U većini slučajeva, dovoljno je i poželjno nekoliko ciljanih blokiranja ili ignorisanja. Nećete videti sadržaj sa tog domena ni u jednoj javnoj vremenskoj liniji ili u vašim obaveštenjima. Vaši pratioci sa tog domena će biti uklonjeni.", "confirmations.edit.confirm": "Uredi", "confirmations.edit.message": "Uređivanjem će se obrisati poruka koju trenutno sastavljate. Da li ste sigurni da želite da nastavite?", @@ -196,10 +205,31 @@ "disabled_account_banner.text": "Vaš nalog {disabledAccount} je trenutno onemogućen.", "dismissable_banner.community_timeline": "Ovo su najnovije javne objave ljudi čije naloge hostuje {domain}.", "dismissable_banner.dismiss": "Odbaci", - "dismissable_banner.explore_links": "O ovim vestima trenutno razgovaraju ljudi na ovom i drugim serverima decentralizovane mreže.", + "dismissable_banner.explore_links": "Ovo su vesti koje se danas najviše dele na društvenoj mreži. Novije vesti koje je objavilo više različitih ljudi su bolje rangirane.", "dismissable_banner.explore_statuses": "Ovo su objave širom društvenog veba koje danas postaju sve popularnije. Novije objave sa više podržavanja i omiljene su rangirane više.", - "dismissable_banner.explore_tags": "Ove heš oznake postaju sve popularnije među ljudima na ovom i drugim serverima decentralizovane mreže.", + "dismissable_banner.explore_tags": "Ovo su heš oznake koje danas postaju sve popularnije na društvenoj mreži. Heš oznake koje koristi više različitih ljudi su rangirane više.", "dismissable_banner.public_timeline": "Ovo su najnovije javne objave ljudi sa društvenog veba koje ljudi na {domain}-u prate.", + "domain_block_modal.block": "Blokiraj server", + "domain_block_modal.block_account_instead": "Umesto toga, blokiraj @{name}", + "domain_block_modal.they_can_interact_with_old_posts": "Ljudi sa ovog servera mogu da imaju interakciju sa vašim starim objavama.", + "domain_block_modal.they_cant_follow": "Niko sa ovog servera ne može da vas prati.", + "domain_block_modal.they_wont_know": "Neće znati da su blokirani.", + "domain_block_modal.title": "Blokirati domen?", + "domain_block_modal.you_will_lose_followers": "Svi vaši pratioci sa ovog servera će biti uklonjeni.", + "domain_block_modal.you_wont_see_posts": "Nećete videti objave ili obaveštenja korisnika na ovom serveru.", + "domain_pill.activitypub_lets_connect": "Omogućuje vam da se povežete i komunicirate sa ljudima ne samo na Mastodon-u, već i u različitim društvenim aplikacijama.", + "domain_pill.activitypub_like_language": "ActivityPub je kao jezik kojim Mastodon govori sa drugim društvenim mrežama.", + "domain_pill.server": "Server", + "domain_pill.their_handle": "Njegov regulator:", + "domain_pill.their_server": "Njihov digitalni dom, gde žive sve njihove objave.", + "domain_pill.their_username": "Njihov jedinstveni identifikator na njihovom serveru. Moguće je pronaći korisnike sa istim korisničkim imenom na različitim serverima.", + "domain_pill.username": "Korisničko ime", + "domain_pill.whats_in_a_handle": "Šta je regulator?", + "domain_pill.who_they_are": "Pošto regulatori govore ko je neko i gde se nalazi, možete da komunicirate sa ljudima širom društvenog veba na .", + "domain_pill.who_you_are": "Pošto vaši regulatori govori ko ste i gde ste, ljudi mogu da komuniciraju sa vama širom društvenog veba na .", + "domain_pill.your_handle": "Vaš regulator:", + "domain_pill.your_server": "Vaš digitalni dom, gde žive sve vaše objave. Ne sviđa vam se ovaj? Prenesite servere u bilo koje vreme i dovedite i svoje pratioce.", + "domain_pill.your_username": "Njihov jedinstveni identifikator na njihovom serveru. Moguće je pronaći korisnike sa istim korisničkim imenom na različitim serverima.", "embed.instructions": "Ugradite ovu objavu na svoj veb sajt kopiranjem koda ispod.", "embed.preview": "Evo kako će to izgledati:", "emoji_button.activity": "Aktivnosti", @@ -232,11 +262,11 @@ "empty_column.follow_requests": "Još uvek nemate nijedan zahtev za praćenje. Kada primite zahtev, on će se pojaviti ovde.", "empty_column.followed_tags": "Još uvek niste zapratili nijednu heš oznaku. Kada to uradite, one će se pojaviti ovde.", "empty_column.hashtag": "Još uvek nema ničega u ovoj heš oznaci.", - "empty_column.home": "Vaša početna vremenska linija je prazna! Pratite više ljudi da biste je popunili. {suggestions}", + "empty_column.home": "Vaša početna vremenska linija je prazna! Pratite više ljudi da biste je popunili.", "empty_column.list": "U ovoj listi još nema ničega. Kada članovi ove liste objave nešto novo, pojaviće se ovde.", "empty_column.lists": "Još uvek nemate nijednu listu. Kada napravite jednu, ona će se pojaviti ovde.", "empty_column.mutes": "Još uvek ne ignorišete nijednog korisnika.", - "empty_column.notification_requests": "Sve je čisto! Ovde nema ničega. Kada dobijete nova obaveštenja, ona će se pojaviti ovde u skladu sa vašim podešavanjima.", + "empty_column.notification_requests": "Sve je čisto! Ovde nema ničega. Kada dobijete nova obaveštenja, ona će se pojaviti ovde u skladu sa vašim podešavanjima.", "empty_column.notifications": "Još uvek nemate nikakva obaveštenja. Kada drugi ljudi budu u interakciji sa vama, videćete to ovde.", "empty_column.public": "Ovde nema ničega! Napišite nešto javno ili ručno pratite korisnike sa drugih servera da biste ovo popunili", "error.unexpected_crash.explanation": "Zbog greške u našem kodu ili problema sa kompatibilnošću pregledača, ova stranica se nije mogla pravilno prikazati.", @@ -267,6 +297,7 @@ "filter_modal.select_filter.subtitle": "Koristite postojeću kategoriju ili kreirajte novu", "filter_modal.select_filter.title": "Filtriraj ovu objavu", "filter_modal.title.status": "Filtriraj objavu", + "filtered_notifications_banner.mentions": "{count, plural, one {pominjanje} few {pominjanja} other {pominjanja}}", "filtered_notifications_banner.pending_requests": "Obaveštenja od {count, plural, =0 {nikoga koga možda poznajete} one {# osobe koju možda poznajete} few {# osobe koje možda poznajete} other {# osoba koje možda poznajete}}", "filtered_notifications_banner.title": "Filtrirana obaveštenja", "firehose.all": "Sve", @@ -277,13 +308,17 @@ "follow_requests.unlocked_explanation": "Iako vaš nalog nije zaključan, osoblje {domain} smatra da biste možda želeli da ručno pregledate zahteve za praćenje sa ovih naloga.", "follow_suggestions.curated_suggestion": "Izbor osoblja", "follow_suggestions.dismiss": "Ne prikazuj ponovo", + "follow_suggestions.featured_longer": "Ručno odabrao tim {domain}", + "follow_suggestions.friends_of_friends_longer": "Popularno među ljudima koje pratite", "follow_suggestions.hints.featured": "Ovaj profil je ručno izabrao tim {domain}.", "follow_suggestions.hints.friends_of_friends": "Ovaj profil je popularan među ljudima koje pratite.", - "follow_suggestions.hints.most_followed": "Ovaj profil je jedan od najpraćenijih na {domain}.", + "follow_suggestions.hints.most_followed": "Ovaj profil je jedan od najpraćenijih na {domain}.", "follow_suggestions.hints.most_interactions": "Ovaj profil je nedavno dobio veliku pažnju na {domain}.", "follow_suggestions.hints.similar_to_recently_followed": "Ovaj profil je sličan profilima koje ste nedavno zapratili.", "follow_suggestions.personalized_suggestion": "Personalizovani predlog", "follow_suggestions.popular_suggestion": "Popularni predlog", + "follow_suggestions.popular_suggestion_longer": "Popularno na {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Slično profilima koje ste nedavno zapratili", "follow_suggestions.view_all": "Prikaži sve", "follow_suggestions.who_to_follow": "Koga pratiti", "followed_tags": "Praćene heš oznake", @@ -315,7 +350,7 @@ "home.column_settings.show_reblogs": "Prikaži podržavanja", "home.column_settings.show_replies": "Prikaži odgovore", "home.hide_announcements": "Sakrij najave", - "home.pending_critical_update.body": "Ažurirajte svoj Mastodon server što je pre moguće!", + "home.pending_critical_update.body": "Ažurirajte svoj Mastodon server što je pre moguće!", "home.pending_critical_update.link": "Pogledajte ažuriranja", "home.pending_critical_update.title": "Dostupno je kritično bezbednosno ažuriranje!", "home.show_announcements": "Prijaži najave", @@ -329,7 +364,7 @@ "interaction_modal.on_another_server": "Na drugom serveru", "interaction_modal.on_this_server": "Na ovom serveru", "interaction_modal.sign_in": "Niste prijavljeni na ovaj server. Gde je hostovan vaš nalog?", - "interaction_modal.sign_in_hint": "Savet: To je veb sajt na kome ste se registrovali. Ako se ne sećate, potražite e-poruku dobrodošlice u svom prijemnom sandučetu. Takođe možete uneti svoje puno korisničko ime! (npr. @Mastodon@mastodon.social)", + "interaction_modal.sign_in_hint": "Savet: To je veb sajt na kome ste se registrovali. Ako se ne sećate, potražite e-poruku dobrodošlice u svom prijemnom sandučetu. Takođe možete uneti svoje puno korisničko ime! (npr. @Mastodon@mastodon.social)", "interaction_modal.title.favourite": "Označi objavu korisnika {name} kao omiljenu", "interaction_modal.title.follow": "Zaprati {name}", "interaction_modal.title.reblog": "Podrži objavu korisnika {name}", @@ -397,6 +432,15 @@ "loading_indicator.label": "Učitavanje…", "media_gallery.toggle_visible": "{number, plural, one {Sakrij sliku} few {Sakrij slike} other {Sakrij slike}}", "moved_to_account_banner.text": "Vaš nalog {disabledAccount} je trenutno onemogućen jer ste prešli na {movedToAccount}.", + "mute_modal.hide_from_notifications": "Sakrij iz obaveštenja", + "mute_modal.hide_options": "Sakrij opcije", + "mute_modal.indefinite": "Dok ih ne uklonim iz ignorisanih", + "mute_modal.show_options": "Prikaži opcije", + "mute_modal.they_can_mention_and_follow": "Mogu da vas pominju i prate, ali ih nećete videti.", + "mute_modal.they_wont_know": "Neće znati da su ignorisani.", + "mute_modal.title": "Ignorisati korisnika?", + "mute_modal.you_wont_see_mentions": "Nećete videti objave koje ga pominju.", + "mute_modal.you_wont_see_posts": "I dalje može da vidi vaše objave, ali vi nećete videti njegove.", "navigation_bar.about": "Osnovni podaci", "navigation_bar.advanced_interface": "Otvori u naprednom veb okruženju", "navigation_bar.blocks": "Blokirani korisnici", @@ -429,9 +473,23 @@ "notification.follow": "{name} vas je zapratio", "notification.follow_request": "{name} je zatražio da vas prati", "notification.mention": "{name} vas je pomenuo", + "notification.moderation-warning.learn_more": "Saznajte više", + "notification.moderation_warning": "Dobili ste moderatorsko upozorenje", + "notification.moderation_warning.action_delete_statuses": "Neke od vaših objava su uklonjene.", + "notification.moderation_warning.action_disable": "Vaš nalog je onemogućen.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Neke od vaših objava su obeležene kao osetljive.", + "notification.moderation_warning.action_none": "Vaš nalog je dobio moderatorsko upozorenje.", + "notification.moderation_warning.action_sensitive": "Vaše objave će ubuduće biti označene kao osetljive.", + "notification.moderation_warning.action_silence": "Vaš nalog je ograničen.", + "notification.moderation_warning.action_suspend": "Vaš nalog je suspendovan.", "notification.own_poll": "Vaša anketa je završena", "notification.poll": "Završena je anketa u kojoj ste glasali", "notification.reblog": "{name} je podržao vašu objavu", + "notification.relationships_severance_event": "Izgubljena veza sa {name}", + "notification.relationships_severance_event.account_suspension": "Administrator sa {from} je suspendovao {target}, što znači da više ne možete da primate ažuriranja od njih niti da komunicirate sa njima.", + "notification.relationships_severance_event.domain_block": "Administrator sa {from} je blokirao {target}, uključujući vaše pratioce: {followersCount} i {followingCount, plural, one {# nalog} few {# naloga} other {# naloga}} koje pratite.", + "notification.relationships_severance_event.learn_more": "Saznajte više", + "notification.relationships_severance_event.user_domain_block": "Blokirači ste {target}, uključujući vaše pratioce: {followersCount} i {followingCount, plural, one {# nalog} few {# naloga} other {# naloga}} koje pratite.", "notification.status": "{name} je upravo objavio", "notification.update": "{name} je uredio objavu", "notification_requests.accept": "Prihvati", @@ -444,6 +502,8 @@ "notifications.column_settings.admin.sign_up": "Nove ragistracije:", "notifications.column_settings.alert": "Obaveštenja na radnoj površini", "notifications.column_settings.favourite": "Omiljeno:", + "notifications.column_settings.filter_bar.advanced": "Prikaži sve kategorije", + "notifications.column_settings.filter_bar.category": "Traka za brzo filtriranje", "notifications.column_settings.follow": "Novi pratioci:", "notifications.column_settings.follow_request": "Novi zahtevi za praćenje:", "notifications.column_settings.mention": "Pominjanja:", @@ -471,7 +531,7 @@ "notifications.permission_required": "Obaveštenja na radnoj površini nisu dostupna jer potrebna dozvola nije dodeljena.", "notifications.policy.filter_new_accounts.hint": "Kreirano {days, plural, one {u poslednjeg # dana} few {u poslednja # dana} other {u poslednjih # dana}}", "notifications.policy.filter_new_accounts_title": "Novi nalozi", - "notifications.policy.filter_not_followers_hint": "Uključujući ljude koji su vas pratili manje od {days, plural, one {# dana} few {# dana} other {# dana}}", + "notifications.policy.filter_not_followers_hint": "Uključujući ljude koji su vas pratili manje od {days, plural, one {# dana} few {# dana} other {# dana}}", "notifications.policy.filter_not_followers_title": "Ljudi koji vas ne prate", "notifications.policy.filter_not_following_hint": "Dok ih ručno ne odobrite", "notifications.policy.filter_not_following_title": "Ljudi koje ne pratite", @@ -483,14 +543,14 @@ "notifications_permission_banner.title": "Nikada ništa ne propustite", "onboarding.action.back": "Vrati me nazad", "onboarding.actions.back": "Vrati me nazad", - "onboarding.actions.go_to_explore": "Pogledaj šta je u trendu", - "onboarding.actions.go_to_home": "Idi na početnu stranicu", + "onboarding.actions.go_to_explore": "Odvedi me u trending", + "onboarding.actions.go_to_home": "Odvedi me na početnu stranicu", "onboarding.compose.template": "Zdravo #Mastodon!", "onboarding.follows.empty": "Nažalost, trenutno se ne mogu prikazati rezultati. Možete pokušati sa korišćenjem pretrage ili pregledanjem stranice za istraživanje da biste pronašli ljude koje ćete pratiti ili pokušajte ponovo kasnije.", - "onboarding.follows.lead": "Vi sami birate svoju početnu stranicu. Što više ljudi pratite, to će biti aktivnije i zanimljivije. Ovi profili mogu biti dobra polazna tačka—uvek možete da ih prestanete pratiti kasnije!", + "onboarding.follows.lead": "Vaša početna stranica je primarni način da doživite Mastodon. Što više ljudi budete pratili, to će biti aktivnije i zanimljivije. Da biste započeli, evo nekoliko predloga:", "onboarding.follows.title": "Personalizujte svoju početnu stranicu", "onboarding.profile.discoverable": "Neka se moj profil može otkriti drugima", - "onboarding.profile.discoverable_hint": "Kada omogućite mogućnost otkrivanja na Mastodon-u, vaše objave se mogu pojaviti u rezultatima pretrage i u trendu, a vaš profil može biti predložen ljudima sa sličnim interesovanjima.", + "onboarding.profile.discoverable_hint": "Kada omogućite mogućnost otkrivanja na Mastodon-u, vaše objave se mogu pojaviti u rezultatima pretrage i u trendu, a vaš profil može biti predložen ljudima sa sličnim interesovanjima.", "onboarding.profile.display_name": "Ime za prikaz", "onboarding.profile.display_name_hint": "Vaše puno ime ili nadimak…", "onboarding.profile.lead": "Ovo možete uvek dovršiti kasnije u podešavanjima, gde je dostupno još više opcija prilagođavanja.", @@ -504,17 +564,17 @@ "onboarding.share.message": "Ja sam {username} na #Mastodon-u! Pratite me na {url}", "onboarding.share.next_steps": "Mogući sledeći koraci:", "onboarding.share.title": "Podelite svoj profil", - "onboarding.start.lead": "Vaš novi Mastodon nalog je spreman. Evo kako to možete iskoristiti na najbolji način:", - "onboarding.start.skip": "Želite da preskočite?", + "onboarding.start.lead": "Sada ste deo Mastodon-a, jedinstvene, decentralizovane platforme društvenih medija na kojoj vi – a ne algoritam – birate svoje iskustvo. Hajde da počnemo na ovoj novoj društvenoj granici:", + "onboarding.start.skip": "Ne treba vam pomoć za početak?", "onboarding.start.title": "Uspeli ste!", - "onboarding.steps.follow_people.body": "Vi sami birate svoju početnu stranicu. Hajde da ga ispunimo zanimljivim ljudima.", + "onboarding.steps.follow_people.body": "Praćenje zanimljivih ljudi je ono o čemu se radi u Mastodon-u.", "onboarding.steps.follow_people.title": "Personalizujte svoju početnu stranicu", - "onboarding.steps.publish_status.body": "Reci zdravo svetu.", + "onboarding.steps.publish_status.body": "Pozdravite svet tekstom, slikama, video snimcima ili anketama {emoji}", "onboarding.steps.publish_status.title": "Napišite svoju prvu objavu", - "onboarding.steps.setup_profile.body": "Veća je verovatnoća da će drugi komunicirati sa vama sa popunjenim profilom.", - "onboarding.steps.setup_profile.title": "Prilagodite svoj profil", + "onboarding.steps.setup_profile.body": "Pojačajte svoje interakcije tako što ćete imati sveobuhvatan profil.", + "onboarding.steps.setup_profile.title": "Personalizujte svoj profil", "onboarding.steps.share_profile.body": "Neka vaši prijatelji znaju kako da vas pronađu na Mastodon-u!", - "onboarding.steps.share_profile.title": "Podelite svoj profil", + "onboarding.steps.share_profile.title": "Podelite svoj Mastodon profil", "onboarding.tips.2fa": "Da li ste znali? Možete da zaštitite svoj nalog podešavanjem dvostruke potvrde identiteta u podešavanjima naloga. Radi sa bilo kojom TOTP aplikacijom po vašem izboru, nije potreban broj telefona!", "onboarding.tips.accounts_from_other_servers": "Da li ste znali? Pošto je Mastodon decentralizovan, neki profili na koje naiđete biće smešteni na serverima različitim od vašeg. A ipak možete da komunicirate sa njima besprekorno! Njihov server je u drugoj polovini njihovog korisničkog imena!", "onboarding.tips.migration": "Da li ste znali? Ako smatrate da {domain} nije odličan izbor servera za vas u budućnosti, možete da pređete na drugi Mastodon server bez gubitka pratilaca. Možete čak i da hostujete sopstveni server!", @@ -539,7 +599,7 @@ "privacy.private.short": "Pratioci", "privacy.public.long": "Bilo ko na Mastodon-u i van njega", "privacy.public.short": "Javno", - "privacy.unlisted.additional": "Ovo se ponaša potpuno kao javno, osim što se objava neće pojavljivati u izvorima uživo ili heš oznakama, istraživanjima ili pretrazi Mastodon-a, čak i ako ste uključeni u celom nalogu.", + "privacy.unlisted.additional": "Ovo se ponaša potpuno kao javno, osim što se objava neće pojavljivati u izvorima uživo ili heš oznakama, istraživanjima ili pretrazi Mastodon-a, čak i ako ste uključeni u celom nalogu.", "privacy.unlisted.long": "Manje algoritamskih fanfara", "privacy.unlisted.short": "Tiha javnost", "privacy_policy.last_updated": "Poslednje ažuriranje {date}", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index 347a3f65a3..9898a10a3e 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -220,7 +220,16 @@ "domain_pill.activitypub_lets_connect": "Омогућује вам да се повежете и комуницирате са људима не само на Mastodon-у, већ и у различитим друштвеним апликацијама.", "domain_pill.activitypub_like_language": "ActivityPub је као језик којим Mastodon говори са другим друштвеним мрежама.", "domain_pill.server": "Сервер", + "domain_pill.their_handle": "Његов регулатор:", + "domain_pill.their_server": "Њихов дигитални дом, где живе све њихове објаве.", + "domain_pill.their_username": "Њихов јединствени идентификатор на њиховом серверу. Могуће је пронаћи кориснике са истим корисничким именом на различитим серверима.", "domain_pill.username": "Корисничко име", + "domain_pill.whats_in_a_handle": "Шта је регулатор?", + "domain_pill.who_they_are": "Пошто регулатори говоре ко је неко и где се налази, можете да комуницирате са људима широм друштвеног веба на .", + "domain_pill.who_you_are": "Пошто ваши регулатори говори ко сте и где сте, људи могу да комуницирају са вама широм друштвеног веба на .", + "domain_pill.your_handle": "Ваш регулатор:", + "domain_pill.your_server": "Ваш дигитални дом, где живе све ваше објаве. Не свиђа вам се овај? Пренесите сервере у било које време и доведите и своје пратиоце.", + "domain_pill.your_username": "Њихов јединствени идентификатор на њиховом серверу. Могуће је пронаћи кориснике са истим корисничким именом на различитим серверима.", "embed.instructions": "Уградите ову објаву на свој веб сајт копирањем кода испод.", "embed.preview": "Ево како ће то изгледати:", "emoji_button.activity": "Активности", @@ -288,6 +297,7 @@ "filter_modal.select_filter.subtitle": "Користите постојећу категорију или креирајте нову", "filter_modal.select_filter.title": "Филтрирај ову објаву", "filter_modal.title.status": "Филтрирај објаву", + "filtered_notifications_banner.mentions": "{count, plural, one {помињање} few {помињања} other {помињања}}", "filtered_notifications_banner.pending_requests": "Обавештења од {count, plural, =0 {никога кога можда познајете} one {# особе коју можда познајете} few {# особе које можда познајете} other {# особа које можда познајете}}", "filtered_notifications_banner.title": "Филтрирана обавештења", "firehose.all": "Све", @@ -298,6 +308,8 @@ "follow_requests.unlocked_explanation": "Иако ваш налог није закључан, особље {domain} сматра да бисте можда желели да ручно прегледате захтеве за праћење са ових налога.", "follow_suggestions.curated_suggestion": "Избор особља", "follow_suggestions.dismiss": "Не приказуј поново", + "follow_suggestions.featured_longer": "Ручно одабрао тим {domain}", + "follow_suggestions.friends_of_friends_longer": "Популарно међу људима које пратите", "follow_suggestions.hints.featured": "Овај профил је ручно изабрао тим {domain}.", "follow_suggestions.hints.friends_of_friends": "Овај профил је популаран међу људима које пратите.", "follow_suggestions.hints.most_followed": "Овај профил је један од најпраћенијих на {domain}.", @@ -305,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Овај профил је сличан профилима које сте недавно запратили.", "follow_suggestions.personalized_suggestion": "Персонализовани предлог", "follow_suggestions.popular_suggestion": "Популарни предлог", + "follow_suggestions.popular_suggestion_longer": "Популарно на {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Слично профилима које сте недавно запратили", "follow_suggestions.view_all": "Прикажи све", "follow_suggestions.who_to_follow": "Кога пратити", "followed_tags": "Праћене хеш ознаке", @@ -459,9 +473,23 @@ "notification.follow": "{name} вас је запратио", "notification.follow_request": "{name} је затражио да вас прати", "notification.mention": "{name} вас је поменуо", + "notification.moderation-warning.learn_more": "Сазнајте више", + "notification.moderation_warning": "Добили сте модераторско упозорење", + "notification.moderation_warning.action_delete_statuses": "Неке од ваших објава су уклоњене.", + "notification.moderation_warning.action_disable": "Ваш налог је онемогућен.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Неке од ваших објава су обележене као осетљиве.", + "notification.moderation_warning.action_none": "Ваш налог је добио модераторско упозорење.", + "notification.moderation_warning.action_sensitive": "Ваше објаве ће убудуће бити означене као осетљиве.", + "notification.moderation_warning.action_silence": "Ваш налог је ограничен.", + "notification.moderation_warning.action_suspend": "Ваш налог је суспендован.", "notification.own_poll": "Ваша анкета је завршена", "notification.poll": "Завршена је анкета у којој сте гласали", "notification.reblog": "{name} је подржао вашу објаву", + "notification.relationships_severance_event": "Изгубљена веза са {name}", + "notification.relationships_severance_event.account_suspension": "Администратор са {from} је суспендовао {target}, што значи да више не можете да примате ажурирања од њих нити да комуницирате са њима.", + "notification.relationships_severance_event.domain_block": "Администратор са {from} је блокирао {target}, укључујући ваше пратиоце: {followersCount} и {followingCount, plural, one {# налог} few {# налогa} other {# налога}} које пратите.", + "notification.relationships_severance_event.learn_more": "Сазнајте више", + "notification.relationships_severance_event.user_domain_block": "Блокирачи сте {target}, укључујући ваше пратиоце: {followersCount} и {followingCount, plural, one {# налог} few {# налогa} other {# налога}} које пратите.", "notification.status": "{name} је управо објавио", "notification.update": "{name} је уредио објаву", "notification_requests.accept": "Прихвати", @@ -474,6 +502,8 @@ "notifications.column_settings.admin.sign_up": "Нове рагистрације:", "notifications.column_settings.alert": "Обавештења на радној површини", "notifications.column_settings.favourite": "Омиљено:", + "notifications.column_settings.filter_bar.advanced": "Прикажи све категорије", + "notifications.column_settings.filter_bar.category": "Трака за брзо филтрирање", "notifications.column_settings.follow": "Нови пратиоци:", "notifications.column_settings.follow_request": "Нови захтеви за праћење:", "notifications.column_settings.mention": "Помињања:", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index b28b4826d4..5ac4b4648f 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -220,8 +220,15 @@ "domain_pill.activitypub_lets_connect": "Det låter dig ansluta och interagera med människor inte bara på Mastodon, men även på andra sociala appar.", "domain_pill.activitypub_like_language": "ActivityPub är som språket Mastodon talar med andra sociala nätverk.", "domain_pill.server": "Server", + "domain_pill.their_handle": "Deras handtag:", + "domain_pill.their_server": "Deras digitala hem, där alla deras tjänster bor.", "domain_pill.their_username": "Deras unika identifierare på deras server. Det är möjligt att hitta användare med samma användarnamn på olika servrar.", "domain_pill.username": "Användarnamn", + "domain_pill.whats_in_a_handle": "Vad finns i ett handtag?", + "domain_pill.who_they_are": "Eftersom handtag säger vem någon är och var de är, kan du interagera med människor på det sociala nätet av .", + "domain_pill.who_you_are": "Eftersom handtag säger vem någon är och var de är, kan människor interagera med dig på det sociala nätet av .", + "domain_pill.your_handle": "Ditt handtag:", + "domain_pill.your_server": "Ditt digitala hem, där alla dina inlägg bor. Gillar du inte just denna? Byt server när som helst och ta med dina anhängare också.", "domain_pill.your_username": "Din unika identifierare på denna server. Det är möjligt att hitta användare med samma användarnamn på olika servrar.", "embed.instructions": "Bädda in detta inlägg på din webbplats genom att kopiera koden nedan.", "embed.preview": "Så här kommer det att se ut:", @@ -259,6 +266,7 @@ "empty_column.list": "Det finns inget i denna lista än. När listmedlemmar publicerar nya inlägg kommer de synas här.", "empty_column.lists": "Du har inga listor än. När skapar en kommer den dyka upp här.", "empty_column.mutes": "Du har ännu inte tystat några användare.", + "empty_column.notification_requests": "Allt klart! Det finns inget mer här. När du får nya meddelanden visas de här enligt dina inställningar.", "empty_column.notifications": "Du har inga meddelanden än. Interagera med andra för att starta konversationen.", "empty_column.public": "Det finns inget här! Skriv något offentligt, eller följ manuellt användarna från andra instanser för att fylla på det", "error.unexpected_crash.explanation": "På grund av en bugg i vår kod eller kompatiblitetsproblem i webbläsaren kan den här sidan inte visas korrekt.", @@ -289,6 +297,9 @@ "filter_modal.select_filter.subtitle": "Använd en befintlig kategori eller skapa en ny", "filter_modal.select_filter.title": "Filtrera detta inlägg", "filter_modal.title.status": "Filtrera ett inlägg", + "filtered_notifications_banner.mentions": "{count, plural, one {omnämning} other {omnämnanden}}", + "filtered_notifications_banner.pending_requests": "Aviseringar från {count, plural, =0 {ingen} one {en person} other {# personer}} du kanske känner", + "filtered_notifications_banner.title": "Filtrerade aviseringar", "firehose.all": "Allt", "firehose.local": "Denna server", "firehose.remote": "Andra servrar", @@ -297,6 +308,8 @@ "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.", "follow_suggestions.curated_suggestion": "Utvald av personalen", "follow_suggestions.dismiss": "Visa inte igen", + "follow_suggestions.featured_longer": "Handplockad av {domain}-teamet", + "follow_suggestions.friends_of_friends_longer": "Populärt bland personer du följer", "follow_suggestions.hints.featured": "Denna profil är handplockad av {domain}-teamet.", "follow_suggestions.hints.friends_of_friends": "Denna profil är populär bland de personer du följer.", "follow_suggestions.hints.most_followed": "Denna profil är en av de mest följda på {domain}.", @@ -304,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Denna profil liknar de profiler som du nyligen har följt.", "follow_suggestions.personalized_suggestion": "Personligt förslag", "follow_suggestions.popular_suggestion": "Populärt förslag", + "follow_suggestions.popular_suggestion_longer": "Populärt på {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Liknar profiler du nyligen följde", "follow_suggestions.view_all": "Visa alla", "follow_suggestions.who_to_follow": "Rekommenderade profiler", "followed_tags": "Följda hashtags", @@ -417,7 +432,9 @@ "loading_indicator.label": "Laddar…", "media_gallery.toggle_visible": "Växla synlighet", "moved_to_account_banner.text": "Ditt konto {disabledAccount} är för närvarande inaktiverat eftersom du flyttat till {movedToAccount}.", + "mute_modal.hide_from_notifications": "Dölj från aviseringslistan", "mute_modal.hide_options": "Dölj alternativ", + "mute_modal.indefinite": "Tills jag avtystar dem", "mute_modal.show_options": "Visa alternativ", "mute_modal.they_can_mention_and_follow": "De kan nämna och följa dig, men du ser dem inte.", "mute_modal.they_wont_know": "De vet inte att de har blivit tysta.", @@ -456,9 +473,23 @@ "notification.follow": "{name} följer dig", "notification.follow_request": "{name} har begärt att följa dig", "notification.mention": "{name} nämnde dig", + "notification.moderation-warning.learn_more": "Läs mer", + "notification.moderation_warning": "Du har mottagit en modereringsvarning", + "notification.moderation_warning.action_delete_statuses": "Några av dina inlägg har tagits bort.", + "notification.moderation_warning.action_disable": "Ditt konto har inaktiverats.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Några av dina inlägg har markerats som känsliga.", + "notification.moderation_warning.action_none": "Ditt konto har mottagit en modereringsvarning.", + "notification.moderation_warning.action_sensitive": "Dina inlägg kommer markeras som känsliga från och med nu.", + "notification.moderation_warning.action_silence": "Ditt konto har begränsats.", + "notification.moderation_warning.action_suspend": "Ditt konto har stängts av.", "notification.own_poll": "Din röstning har avslutats", "notification.poll": "En omröstning du röstat i har avslutats", "notification.reblog": "{name} boostade ditt inlägg", + "notification.relationships_severance_event": "Förlorade kontakter med {name}", + "notification.relationships_severance_event.account_suspension": "En administratör från {from} har stängt av {target}, vilket innebär att du inte längre kan ta emot uppdateringar från dem eller interagera med dem.", + "notification.relationships_severance_event.domain_block": "En administratör från {from} har blockerat {target}, inklusive {followersCount} av dina följare och {followingCount, plural, one {# konto} other {# konton}} du följer.", + "notification.relationships_severance_event.learn_more": "Läs mer", + "notification.relationships_severance_event.user_domain_block": "Du har blockerat {target} och tar därmed bort {followersCount} av dina följare samt {followingCount, plural, one {# konto} other {# konton}} du följer.", "notification.status": "{name} publicerade just ett inlägg", "notification.update": "{name} redigerade ett inlägg", "notification_requests.accept": "Godkänn", @@ -472,6 +503,7 @@ "notifications.column_settings.alert": "Skrivbordsaviseringar", "notifications.column_settings.favourite": "Favoriter:", "notifications.column_settings.filter_bar.advanced": "Visa alla kategorier", + "notifications.column_settings.filter_bar.category": "Snabbfilter", "notifications.column_settings.follow": "Nya följare:", "notifications.column_settings.follow_request": "Ny följ-förfrågan:", "notifications.column_settings.mention": "Omnämningar:", @@ -499,8 +531,11 @@ "notifications.permission_required": "Skrivbordsaviseringar är otillgängliga eftersom att rättigheten som krävs inte har godkänts.", "notifications.policy.filter_new_accounts.hint": "Skapad inom de senaste {days, plural, one {dagen} other {# dagarna}}", "notifications.policy.filter_new_accounts_title": "Nya konton", + "notifications.policy.filter_not_followers_hint": "Inklusive personer som har följt dig kortare än {days, plural, one {en dag} other {# dagar}}", "notifications.policy.filter_not_followers_title": "Personer som inte följer dig", + "notifications.policy.filter_not_following_hint": "Tills du manuellt godkänner dem", "notifications.policy.filter_not_following_title": "Personer du inte följer", + "notifications.policy.filter_private_mentions_hint": "Filtrerat om det inte är som svar på ditt eget omnämnande eller om du följer avsändaren", "notifications.policy.filter_private_mentions_title": "Oombedda privata omnämnanden", "notifications.policy.title": "Filtrera ut aviseringar från…", "notifications_permission_banner.enable": "Aktivera skrivbordsaviseringar", @@ -573,11 +608,6 @@ "refresh": "Läs om", "regeneration_indicator.label": "Laddar…", "regeneration_indicator.sublabel": "Ditt hemmaflöde förbereds!", - "relationship_severance_notification.purged_data": "rensad av administratörer", - "relationship_severance_notification.types.account_suspension": "Ditt konto har blivit avstängt", - "relationship_severance_notification.types.domain_block": "Domänen har stängts av", - "relationship_severance_notification.types.user_domain_block": "Du blockerade denna domän", - "relationship_severance_notification.view": "Visa", "relative_time.days": "{number}d", "relative_time.full.days": "{number, plural, one {# dag} other {# dagar}} sedan", "relative_time.full.hours": "{number, plural, one {# timme} other {# timmar}} sedan", @@ -709,6 +739,7 @@ "status.reblog": "Boosta", "status.reblog_private": "Boosta med ursprunglig synlighet", "status.reblogged_by": "{name} boostade", + "status.reblogs": "{count, plural, one {# röst} other {# röster}}", "status.reblogs.empty": "Ingen har boostat detta inlägg än. När någon gör det kommer de synas här.", "status.redraft": "Radera & gör om", "status.remove_bookmark": "Ta bort bokmärke", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index 4490f038ca..7c6b2ade4e 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "ใช้หมวดหมู่ที่มีอยู่หรือสร้างหมวดหมู่ใหม่", "filter_modal.select_filter.title": "กรองโพสต์นี้", "filter_modal.title.status": "กรองโพสต์", + "filtered_notifications_banner.mentions": "{count, plural, other {การกล่าวถึง}}", "filtered_notifications_banner.pending_requests": "การแจ้งเตือนจาก {count, plural, =0 {ไม่มีใคร} other {# คน}} ที่คุณอาจรู้จัก", "filtered_notifications_banner.title": "การแจ้งเตือนที่กรองอยู่", "firehose.all": "ทั้งหมด", @@ -307,13 +308,17 @@ "follow_requests.unlocked_explanation": "แม้ว่าไม่มีการล็อคบัญชีของคุณ พนักงานของ {domain} คิดว่าคุณอาจต้องการตรวจทานคำขอติดตามจากบัญชีเหล่านี้ด้วยตนเอง", "follow_suggestions.curated_suggestion": "คัดสรรโดยพนักงาน", "follow_suggestions.dismiss": "ไม่ต้องแสดงอีก", + "follow_suggestions.featured_longer": "คัดสรรโดยทีม {domain}", + "follow_suggestions.friends_of_friends_longer": "เป็นที่นิยมในหมู่ผู้คนที่คุณติดตาม", "follow_suggestions.hints.featured": "โปรไฟล์นี้ได้รับการคัดสรรโดยทีม {domain}", - "follow_suggestions.hints.friends_of_friends": "โปรไฟล์นี้ได้รับความนิยมในหมู่ผู้คนที่คุณติดตาม", + "follow_suggestions.hints.friends_of_friends": "โปรไฟล์นี้เป็นที่นิยมในหมู่ผู้คนที่คุณติดตาม", "follow_suggestions.hints.most_followed": "โปรไฟล์นี้เป็นหนึ่งในโปรไฟล์ที่ได้รับการติดตามมากที่สุดใน {domain}", "follow_suggestions.hints.most_interactions": "โปรไฟล์นี้เพิ่งได้รับความสนใจอย่างมากใน {domain}", "follow_suggestions.hints.similar_to_recently_followed": "โปรไฟล์นี้คล้ายกับโปรไฟล์ที่คุณได้ติดตามล่าสุด", "follow_suggestions.personalized_suggestion": "ข้อเสนอแนะเฉพาะบุคคล", "follow_suggestions.popular_suggestion": "ข้อเสนอแนะยอดนิยม", + "follow_suggestions.popular_suggestion_longer": "เป็นที่นิยมใน {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "คล้ายกับโปรไฟล์ที่คุณได้ติดตามล่าสุด", "follow_suggestions.view_all": "ดูทั้งหมด", "follow_suggestions.who_to_follow": "ติดตามใครดี", "followed_tags": "แฮชแท็กที่ติดตาม", @@ -468,10 +473,23 @@ "notification.follow": "{name} ได้ติดตามคุณ", "notification.follow_request": "{name} ได้ขอติดตามคุณ", "notification.mention": "{name} ได้กล่าวถึงคุณ", + "notification.moderation-warning.learn_more": "เรียนรู้เพิ่มเติม", + "notification.moderation_warning": "คุณได้รับคำเตือนการกลั่นกรอง", + "notification.moderation_warning.action_delete_statuses": "เอาโพสต์บางส่วนของคุณออกแล้ว", + "notification.moderation_warning.action_disable": "ปิดใช้งานบัญชีของคุณแล้ว", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "ทำเครื่องหมายโพสต์บางส่วนของคุณว่าละเอียดอ่อนแล้ว", + "notification.moderation_warning.action_none": "บัญชีของคุณได้รับคำเตือนการกลั่นกรอง", + "notification.moderation_warning.action_sensitive": "จะทำเครื่องหมายโพสต์ของคุณว่าละเอียดอ่อนนับจากนี้ไป", + "notification.moderation_warning.action_silence": "จำกัดบัญชีของคุณแล้ว", + "notification.moderation_warning.action_suspend": "ระงับบัญชีของคุณแล้ว", "notification.own_poll": "การสำรวจความคิดเห็นของคุณได้สิ้นสุดแล้ว", "notification.poll": "การสำรวจความคิดเห็นที่คุณได้ลงคะแนนได้สิ้นสุดแล้ว", "notification.reblog": "{name} ได้ดันโพสต์ของคุณ", - "notification.severed_relationships": "ตัดขาดความสัมพันธ์กับ {name} แล้ว", + "notification.relationships_severance_event": "สูญเสียการเชื่อมต่อกับ {name}", + "notification.relationships_severance_event.account_suspension": "ผู้ดูแลจาก {from} ได้ระงับ {target} ซึ่งหมายความว่าคุณจะไม่สามารถรับการอัปเดตจากเขาหรือโต้ตอบกับเขาได้อีกต่อไป", + "notification.relationships_severance_event.domain_block": "ผู้ดูแลจาก {from} ได้ปิดกั้น {target} รวมถึง {followersCount} ผู้ติดตามของคุณและ {followingCount, plural, other {# บัญชี}}ที่คุณติดตาม", + "notification.relationships_severance_event.learn_more": "เรียนรู้เพิ่มเติม", + "notification.relationships_severance_event.user_domain_block": "คุณได้ปิดกั้น {target} เอา {followersCount} ผู้ติดตามของคุณและ {followingCount, plural, other {# บัญชี}}ที่คุณติดตามออก", "notification.status": "{name} เพิ่งโพสต์", "notification.update": "{name} ได้แก้ไขโพสต์", "notification_requests.accept": "ยอมรับ", @@ -590,12 +608,6 @@ "refresh": "รีเฟรช", "regeneration_indicator.label": "กำลังโหลด…", "regeneration_indicator.sublabel": "กำลังเตรียมฟีดหน้าแรกของคุณ!", - "relationship_severance_notification.purged_data": "ล้างข้อมูลโดยผู้ดูแล", - "relationship_severance_notification.relationships": "{count, plural, other {# ความสัมพันธ์}}", - "relationship_severance_notification.types.account_suspension": "ระงับบัญชีแล้ว", - "relationship_severance_notification.types.domain_block": "ระงับโดเมนแล้ว", - "relationship_severance_notification.types.user_domain_block": "คุณได้ปิดกั้นโดเมนนี้", - "relationship_severance_notification.view": "ดู", "relative_time.days": "{number} วัน", "relative_time.full.days": "{number, plural, other {# วัน}}ที่แล้ว", "relative_time.full.hours": "{number, plural, other {# ชั่วโมง}}ที่แล้ว", diff --git a/app/javascript/mastodon/locales/tok.json b/app/javascript/mastodon/locales/tok.json index c3f184e15d..80d412a20b 100644 --- a/app/javascript/mastodon/locales/tok.json +++ b/app/javascript/mastodon/locales/tok.json @@ -36,6 +36,7 @@ "account.go_to_profile": "o tawa lipu jan", "account.hide_reblogs": "o lukin ala e pana toki tan @{name}", "account.in_memoriam": "jan ni li moli. pona o tawa ona.", + "account.joined_short": "li kama", "account.languages": "sina wile lukin e sitelen pi toki seme", "account.locked_info": "sina wile kute e jan ni la ona o toki e ken", "account.media": "sitelen", @@ -70,6 +71,10 @@ "alert.unexpected.title": "pakala a!", "announcement.announcement": "toki suli", "audio.hide": "o len e kalama", + "block_modal.show_less": "o lili e lukin", + "block_modal.show_more": "o mute e lukin", + "block_modal.they_cant_mention": "ona li ken ala toki e sina li ken ala alasa e sina", + "block_modal.title": "o weka ala weka e jan", "boost_modal.combo": "sina ken luka e nena {combo} tawa ni: sina wile ala luka e nena lon tenpo kama", "bundle_column_error.copy_stacktrace": "o awen e sona pakala lon ilo sina", "bundle_column_error.error.body": "ilo li ken ala pana e lipu ni. ni li ken tan pakala ilo.", @@ -86,10 +91,15 @@ "column.about": "sona", "column.blocks": "kulupu pi jan weka", "column.bookmarks": "awen toki", + "column.community": "linja tenpo pi ma ni", + "column.favourites": "ijo pona", + "column.firehose": "toki pi tenpo ni", + "column.follow_requests": "wile alasa pi jan ante", "column.home": "lipu open", "column.lists": "kulupu lipu", "column.mutes": "jan len", "column.pins": "toki sewi", + "column_back_button.label": "o tawa monsi", "column_header.hide_settings": "o len e lawa", "column_header.pin": "o sewi", "column_header.show_settings": "o lukin e lawa", @@ -157,6 +167,9 @@ "dismissable_banner.explore_statuses": "suni ni la jan mute li lukin e toki ni. jan mute li wawa e toki li suli e toki la toki ni li lon sewi. toki li sin la toki ni li lon sewi.", "dismissable_banner.explore_tags": "suni ni la jan mute li lukin e toki pi toki ni. jan mute li kepeken toki la toki ni li lon sewi.", "dismissable_banner.public_timeline": "toki ni li sin. jan li pali e toki ni la jan ante mute pi ma {domain} li kute e jan ni.", + "domain_block_modal.block": "o weka e ma", + "domain_block_modal.you_will_lose_followers": "ma ni la jan alasa ale sina li weka", + "domain_block_modal.you_wont_see_posts": "sina ken ala lukin e toki tan jan pi ma ni", "embed.preview": "ni li jo e sitelen ni:", "emoji_button.activity": "musi", "emoji_button.flags": "len ma", @@ -232,7 +245,7 @@ "keyboard_shortcuts.boost": "o pana sin e toki", "keyboard_shortcuts.down": "o tawa anpa lon lipu", "keyboard_shortcuts.enter": "o lukin e toki", - "keyboard_shortcuts.favourite": "o suli e toki", + "keyboard_shortcuts.favourite": "o sitelen pona e toki", "keyboard_shortcuts.favourites": "o lukin e lipu sina pi toki suli", "keyboard_shortcuts.muted": "o lukin e lipu sina pi jan len", "keyboard_shortcuts.my_profile": "o lukin e lipu sina", @@ -264,7 +277,7 @@ "navigation_bar.about": "sona", "navigation_bar.blocks": "jan weka", "navigation_bar.compose": "o pali e toki sin", - "navigation_bar.favourites": "toki suli", + "navigation_bar.favourites": "ijo pona", "navigation_bar.filters": "nimi len", "navigation_bar.lists": "kulupu lipu", "navigation_bar.mutes": "sina wile ala kute e jan ni", @@ -273,7 +286,7 @@ "navigation_bar.search": "o alasa", "notification.admin.report": "jan {name} li toki e jan {target} tawa lawa", "notification.admin.sign_up": "{name} li kama", - "notification.favourite": "{name} li suli e toki sina", + "notification.favourite": "toki sina li pona tawa {name}", "notification.follow": " {name} li kute e sina", "notification.follow_request": "{name} li wile kute e sina", "notification.mention": "jan {name} li toki e sina", @@ -281,11 +294,13 @@ "notification.reblog": "{name} li wawa e toki sina", "notification.status": "{name} li toki", "notification.update": "{name} li ante e toki", + "notifications.column_settings.favourite": "ijo pona:", "notifications.column_settings.follow": "jan kute sin", "notifications.column_settings.poll": "pana lon pana ni:", "notifications.column_settings.reblog": "wawa:", "notifications.column_settings.update": "ante toki:", "notifications.filter.all": "ale", + "notifications.filter.favourites": "ijo pona", "notifications.filter.polls": "pana lon pana ni", "onboarding.compose.template": "toki a, #Mastodon o!", "onboarding.profile.display_name": "nimi tawa jan ante", @@ -333,7 +348,7 @@ "status.delete": "o weka", "status.edit": "o ante", "status.embed": "ni o lon insa pi lipu ante", - "status.favourite": "o suli", + "status.favourite": "o sitelen pona", "status.hide": "o len", "status.history.created": "{name} li pali e ni lon {date}", "status.history.edited": "{name} li ante lon {date}", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index 12d82c70ec..c46080cfb2 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "Mevcut bir kategoriyi kullan veya yeni bir tane oluştur", "filter_modal.select_filter.title": "Bu gönderiyi süzgeçle", "filter_modal.title.status": "Bir gönderi süzgeçle", + "filtered_notifications_banner.mentions": "{count, plural, one {bahsetme} other {bahsetme}}", "filtered_notifications_banner.pending_requests": "Bildiğiniz {count, plural, =0 {hiç kimseden} one {bir kişiden} other {# kişiden}} bildirim", "filtered_notifications_banner.title": "Filtrelenmiş bildirimler", "firehose.all": "Tümü", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "Hesabınız kilitli olmasa da, {domain} personeli bu hesaplardan gelen takip isteklerini gözden geçirmek isteyebileceğinizi düşündü.", "follow_suggestions.curated_suggestion": "Çalışanların seçtikleri", "follow_suggestions.dismiss": "Tekrar gösterme", + "follow_suggestions.featured_longer": "{domain} takımı tarafından elle seçildi", + "follow_suggestions.friends_of_friends_longer": "Takip ettiğiniz kişiler arasında popüler", "follow_suggestions.hints.featured": "Bu profil {domain} ekibi tarafından elle seçilmiştir.", "follow_suggestions.hints.friends_of_friends": "Bu profil takip ettiğiniz insanlar arasında popülerdir.", "follow_suggestions.hints.most_followed": "Bu, {domain} sunucusunda en fazla izlenen profildir.", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Bu profil, son zamanlarda takip ettiğiniz profillere benziyor.", "follow_suggestions.personalized_suggestion": "Kişiselleşmiş öneriler", "follow_suggestions.popular_suggestion": "Popüler öneriler", + "follow_suggestions.popular_suggestion_longer": "{domain} üzerinde popüler", + "follow_suggestions.similar_to_recently_followed_longer": "Yakın zamanda takip ettiğiniz hesaplara benziyor", "follow_suggestions.view_all": "Tümünü gör", "follow_suggestions.who_to_follow": "Takip edebileceklerin", "followed_tags": "Takip edilen etiketler", @@ -467,11 +472,24 @@ "notification.favourite": "{name} gönderinizi beğendi", "notification.follow": "{name} seni takip etti", "notification.follow_request": "{name} size takip isteği gönderdi", - "notification.mention": "{name} sana değindi", + "notification.mention": "{name} senden bahsetti", + "notification.moderation-warning.learn_more": "Daha fazlası", + "notification.moderation_warning": "Bir denetim uyarısı aldınız", + "notification.moderation_warning.action_delete_statuses": "Bazı gönderileriniz kaldırıldı.", + "notification.moderation_warning.action_disable": "Hesabınız devre dışı bırakıldı.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Bazı gönderileriniz hassas olarak işaretlendi.", + "notification.moderation_warning.action_none": "Hesabınız bir denetim uyarısı aldı.", + "notification.moderation_warning.action_sensitive": "Gönderileriniz artık hassas olarak işaretlenecek.", + "notification.moderation_warning.action_silence": "Hesabınız sınırlandırıldı.", + "notification.moderation_warning.action_suspend": "Hesabınız askıya alındı.", "notification.own_poll": "Anketiniz sona erdi", "notification.poll": "Oy verdiğiniz bir anket sona erdi", "notification.reblog": "{name} gönderini yeniden paylaştı", - "notification.severed_relationships": "{name} ile ilişkiler koptu", + "notification.relationships_severance_event": "{name} ile bağlantılar koptu", + "notification.relationships_severance_event.account_suspension": "{from} yöneticisi, {target} askıya aldı, bunun anlamı onlardan artık güncelleme alamayacak veya etkileşemeyeceksiniz demektir.", + "notification.relationships_severance_event.domain_block": "{from} yöneticisi {target} engelledi, {followersCount} takipçiniz ve takip ettiğiniz {followingCount, plural, one {# hesap} other {# hesap}} buna dahil.", + "notification.relationships_severance_event.learn_more": "Daha fazlası", + "notification.relationships_severance_event.user_domain_block": "{target} engellediniz, takipçilerinizden {followersCount} ve takip eden {followingCount, plural, one {# hesap} other {# hesap}} kaldırılıyor.", "notification.status": "{name} az önce gönderdi", "notification.update": "{name} bir gönderiyi düzenledi", "notification_requests.accept": "Onayla", @@ -488,7 +506,7 @@ "notifications.column_settings.filter_bar.category": "Hızlı filtre çubuğu", "notifications.column_settings.follow": "Yeni takipçiler:", "notifications.column_settings.follow_request": "Yeni takip istekleri:", - "notifications.column_settings.mention": "Değinmeler:", + "notifications.column_settings.mention": "Bahsetmeler:", "notifications.column_settings.poll": "Anket sonuçları:", "notifications.column_settings.push": "Anlık bildirimler", "notifications.column_settings.reblog": "Yeniden paylaşanlar:", @@ -502,7 +520,7 @@ "notifications.filter.boosts": "Yeniden paylaşımlar", "notifications.filter.favourites": "Favorilerin", "notifications.filter.follows": "Takip edilenler", - "notifications.filter.mentions": "Değinmeler", + "notifications.filter.mentions": "Bahsetmeler", "notifications.filter.polls": "Anket sonuçları", "notifications.filter.statuses": "Takip ettiğiniz kişilerden gelen güncellemeler", "notifications.grant_permission": "İzin ver.", @@ -590,12 +608,6 @@ "refresh": "Yenile", "regeneration_indicator.label": "Yükleniyor…", "regeneration_indicator.sublabel": "Ana akışın hazırlanıyor!", - "relationship_severance_notification.purged_data": "yöneticiler tarafından temizlendi", - "relationship_severance_notification.relationships": "{count, plural, one {# ilişki} other {# ilişki}}", - "relationship_severance_notification.types.account_suspension": "Hesap askıya alındı", - "relationship_severance_notification.types.domain_block": "Alan adı askıya alındı", - "relationship_severance_notification.types.user_domain_block": "Bu alan adını engellediniz", - "relationship_severance_notification.view": "Görüntüle", "relative_time.days": "{number}d", "relative_time.full.days": "{number, plural, one {# gün} other {# gün}} önce", "relative_time.full.hours": "{number, plural, one {# saat} other {# saat}} önce", @@ -778,10 +790,10 @@ "upload_form.edit": "Düzenle", "upload_form.thumbnail": "Küçük resmi değiştir", "upload_form.video_description": "İşitme kaybı veya görme engeli olan kişiler için açıklama ekleyiniz", - "upload_modal.analyzing_picture": "Resim analiz ediliyor…", + "upload_modal.analyzing_picture": "Görsel analiz ediliyor…", "upload_modal.apply": "Uygula", "upload_modal.applying": "Uygulanıyor…", - "upload_modal.choose_image": "Resim seç", + "upload_modal.choose_image": "Görsel seç", "upload_modal.description_placeholder": "Pijamalı hasta yağız şoföre çabucak güvendi", "upload_modal.detect_text": "Resimdeki metni algıla", "upload_modal.edit_media": "Medyayı düzenle", diff --git a/app/javascript/mastodon/locales/tt.json b/app/javascript/mastodon/locales/tt.json index 82d45205c2..9a402472d9 100644 --- a/app/javascript/mastodon/locales/tt.json +++ b/app/javascript/mastodon/locales/tt.json @@ -449,9 +449,9 @@ "time_remaining.seconds": "{number, plural, one {# секунд} other {# секунд}} калды", "timeline_hint.resources.statuses": "Older toots", "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}", - "units.short.billion": "{count}Млрд", - "units.short.million": "{count}Млн", - "units.short.thousand": "{count}М", + "units.short.billion": "{count} млрд", + "units.short.million": "{count} млн", + "units.short.thousand": "{count} мең", "upload_form.audio_description": "Describe for people with hearing loss", "upload_form.description": "Describe for the visually impaired", "upload_form.edit": "Үзгәртү", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index f2e58f0937..3a7f63206d 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -217,8 +217,19 @@ "domain_block_modal.title": "Заблокувати домен?", "domain_block_modal.you_will_lose_followers": "Усіх ваших підписників з цього сервера буде вилучено.", "domain_block_modal.you_wont_see_posts": "Ви не бачитимете дописів і сповіщень від користувачів на цьому сервері.", + "domain_pill.activitypub_lets_connect": "Це дозволяє вам спілкуватися та взаємодіяти з людьми не лише на Mastodon, але й у різних соціальних додатках.", + "domain_pill.activitypub_like_language": "ActivityPub - це як мова, якою Мастодонт розмовляє з іншими соціальними мережами.", "domain_pill.server": "Сервер", + "domain_pill.their_handle": "Їхня адреса:", + "domain_pill.their_server": "Їхній цифровий дім, де живуть усі їхні пости.", + "domain_pill.their_username": "Їхній унікальний ідентифікатор на їхньому сервері. Ви можете знайти користувачів з однаковими іменами на різних серверах.", "domain_pill.username": "Ім'я користувача", + "domain_pill.whats_in_a_handle": "Що є в адресі?", + "domain_pill.who_they_are": "Оскільки дескриптори вказують, хто це і де він знаходиться, ви можете взаємодіяти з людьми через соціальну мережу платформ на основі .", + "domain_pill.who_you_are": "Оскільки ваш нікнейм вказує, хто ви та де ви, люди можуть взаємодіяти з вами через соціальну мережу платформ на основі .", + "domain_pill.your_handle": "Ваша адреса:", + "domain_pill.your_server": "Ваш цифровий дім, де живуть усі ваші публікації. Не подобається цей? Перенесіть сервери в будь-який час і залучайте своїх підписників.", + "domain_pill.your_username": "Ваш унікальний ідентифікатор на цьому сервері. Ви можете знайти користувачів з однаковими іменами на різних серверах.", "embed.instructions": "Вбудуйте цей допис до вашого вебсайту, скопіювавши код нижче.", "embed.preview": "Ось який вигляд це матиме:", "emoji_button.activity": "Діяльність", @@ -286,6 +297,7 @@ "filter_modal.select_filter.subtitle": "Використати наявну категорію або створити нову", "filter_modal.select_filter.title": "Фільтрувати цей допис", "filter_modal.title.status": "Фільтрувати допис", + "filtered_notifications_banner.mentions": "{count, plural, one {mention} other {mentions}}", "filtered_notifications_banner.pending_requests": "Сповіщення від {count, plural, =0 {жодної особи} one {однієї особи} few {# осіб} many {# осіб} other {# особи}}, котрих ви можете знати", "filtered_notifications_banner.title": "Відфільтровані сповіщення", "firehose.all": "Всі", @@ -296,6 +308,8 @@ "follow_requests.unlocked_explanation": "Хоча ваш обліковий запис не заблоковано, персонал {domain} припускає, що, можливо, ви хотіли б переглянути ці запити на підписку.", "follow_suggestions.curated_suggestion": "Відібрано командою", "follow_suggestions.dismiss": "Більше не показувати", + "follow_suggestions.featured_longer": "Вибрано командою {domain} вручну", + "follow_suggestions.friends_of_friends_longer": "Популярні серед людей, за якими ви слідкуєте", "follow_suggestions.hints.featured": "Цей профіль був обраний командою {domain}.", "follow_suggestions.hints.friends_of_friends": "Цей профіль популярний серед тих людей, на яких ви підписані.", "follow_suggestions.hints.most_followed": "За цим профілем один з найпопулярніших на {domain}.", @@ -303,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Цей профіль схожий на профілі, за якими ви стежили останнім часом.", "follow_suggestions.personalized_suggestion": "Персоналізована пропозиція", "follow_suggestions.popular_suggestion": "Популярна пропозиція", + "follow_suggestions.popular_suggestion_longer": "Популярно на {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Схожі на профілі, за якими ви нещодавно стежили", "follow_suggestions.view_all": "Переглянути все", "follow_suggestions.who_to_follow": "На кого підписатися", "followed_tags": "Відстежувані хештеґи", @@ -457,10 +473,23 @@ "notification.follow": "{name} підписалися на вас", "notification.follow_request": "{name} відправили запит на підписку", "notification.mention": "{name} згадали вас", + "notification.moderation-warning.learn_more": "Дізнатися більше", + "notification.moderation_warning": "Ви отримали попередження модерації", + "notification.moderation_warning.action_delete_statuses": "Деякі з ваших дописів було видалено.", + "notification.moderation_warning.action_disable": "Ваш обліковий запис було вимкнено.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Деякі з ваших дописів були позначені як чутливі.", + "notification.moderation_warning.action_none": "Ваш обліковий запис отримав попередження модерації.", + "notification.moderation_warning.action_sensitive": "Відтепер ваші дописи будуть позначені як чутливі.", + "notification.moderation_warning.action_silence": "Ваш обліковий запис було обмежено.", + "notification.moderation_warning.action_suspend": "Ваш обліковий запис було заблоковано.", "notification.own_poll": "Ваше опитування завершилося", "notification.poll": "Опитування, у якому ви голосували, скінчилося", "notification.reblog": "{name} поширює ваш допис", - "notification.severed_relationships": "Зв'язки з {name} розірвані", + "notification.relationships_severance_event": "Втрачено з'єднання з {name}", + "notification.relationships_severance_event.account_suspension": "Адміністратор з {from} призупинив {target}, що означає, що ви більше не можете отримувати оновлення від них або взаємодіяти з ними.", + "notification.relationships_severance_event.domain_block": "Адміністратор з {from} заблокував {target}, включаючи {followersCount} ваших підписників і {{followingCount, plural, one {# account} other {# accounts}}, на які ви підписані.", + "notification.relationships_severance_event.learn_more": "Дізнатися більше", + "notification.relationships_severance_event.user_domain_block": "Ви заблокували {target}, видаливши {followersCount} ваших підписників і {followingCount, plural, one {# account} other {# accounts}}, за якими ви стежите.", "notification.status": "{name} щойно дописує", "notification.update": "{name} змінює допис", "notification_requests.accept": "Прийняти", @@ -474,7 +503,7 @@ "notifications.column_settings.alert": "Сповіщення стільниці", "notifications.column_settings.favourite": "Уподобане:", "notifications.column_settings.filter_bar.advanced": "Показати всі категорії", - "notifications.column_settings.filter_bar.category": "Панель швидкого фільтру", + "notifications.column_settings.filter_bar.category": "Панель швидкого фільтра", "notifications.column_settings.follow": "Нові підписники:", "notifications.column_settings.follow_request": "Нові запити на підписку:", "notifications.column_settings.mention": "Згадки:", @@ -502,9 +531,13 @@ "notifications.permission_required": "Сповіщення на стільниці не доступні, оскільки необхідний дозвіл не надано.", "notifications.policy.filter_new_accounts.hint": "Створено впродовж {days, plural, one {одного} few {# днів} many {# днів} other {# дня}}", "notifications.policy.filter_new_accounts_title": "Нові облікові записи", + "notifications.policy.filter_not_followers_hint": "Включаючи людей, які стежать за вами менше {days, plural, one {one day} other {# days}}", "notifications.policy.filter_not_followers_title": "Люди не підписані на вас", "notifications.policy.filter_not_following_hint": "Доки ви не схвалюєте їх вручну", "notifications.policy.filter_not_following_title": "Люди, на яких ви не підписані", + "notifications.policy.filter_private_mentions_hint": "Відфільтровується, якщо це не відповідь на вашу власну згадку або якщо ви відстежуєте відправника", + "notifications.policy.filter_private_mentions_title": "Небажані приватні згадки", + "notifications.policy.title": "Відфільтрувати сповіщення від…", "notifications_permission_banner.enable": "Увімкнути сповіщення стільниці", "notifications_permission_banner.how_to_control": "Щоб отримувати сповіщення, коли Mastodon не відкрито, увімкніть сповіщення стільниці. Ви можете контролювати, які типи взаємодій створюють сповіщення через кнопку {icon} вгорі після їхнього увімкнення.", "notifications_permission_banner.title": "Не проґавте нічого", @@ -575,12 +608,6 @@ "refresh": "Оновити", "regeneration_indicator.label": "Завантаження…", "regeneration_indicator.sublabel": "Хвилинку, ми готуємо вашу стрічку!", - "relationship_severance_notification.purged_data": "очищено адміністраторами", - "relationship_severance_notification.relationships": "{count, plural, one {# зв'язок} few {# зв'язки} many {# зв'язків} other {# зв'язок}}", - "relationship_severance_notification.types.account_suspension": "Обліковий запис призупинено", - "relationship_severance_notification.types.domain_block": "Домен призупинено", - "relationship_severance_notification.types.user_domain_block": "Ви заблокували цей домен", - "relationship_severance_notification.view": "Вигляд", "relative_time.days": "{number}д", "relative_time.full.days": "{number, plural, one {# день} few {# дні} other {# днів}} тому", "relative_time.full.hours": "{number, plural, one {# година} few {# години} other {# годин}} тому", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 9b09d21450..b188488f0b 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "Sử dụng một danh mục hiện có hoặc tạo một danh mục mới", "filter_modal.select_filter.title": "Lọc tút này", "filter_modal.title.status": "Lọc một tút", + "filtered_notifications_banner.mentions": "{count, plural, other {lượt nhắc}}", "filtered_notifications_banner.pending_requests": "Thông báo từ {count, plural, =0 {không ai} other {# người}} bạn có thể biết", "filtered_notifications_banner.title": "Thông báo đã lọc", "firehose.all": "Toàn bộ", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "Mặc dù tài khoản của bạn đang ở chế độ công khai, quản trị viên của {domain} vẫn tin rằng bạn sẽ muốn xem lại yêu cầu theo dõi từ những người khác.", "follow_suggestions.curated_suggestion": "Gợi ý từ máy chủ", "follow_suggestions.dismiss": "Không hiện lại", + "follow_suggestions.featured_longer": "Tuyển chọn bởi {domain}", + "follow_suggestions.friends_of_friends_longer": "Nổi tiếng với những người mà bạn theo dõi", "follow_suggestions.hints.featured": "Người này được đội ngũ {domain} đề xuất.", "follow_suggestions.hints.friends_of_friends": "Người này nổi tiếng với những người bạn theo dõi.", "follow_suggestions.hints.most_followed": "Người này được theo dõi nhiều nhất trên {domain}.", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Người này có nét giống những người mà bạn theo dõi gần đây.", "follow_suggestions.personalized_suggestion": "Gợi ý cá nhân hóa", "follow_suggestions.popular_suggestion": "Những người nổi tiếng", + "follow_suggestions.popular_suggestion_longer": "Nổi tiếng trên {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Tương tự những người mà bạn theo dõi gần đây", "follow_suggestions.view_all": "Xem tất cả", "follow_suggestions.who_to_follow": "Gợi ý theo dõi", "followed_tags": "Hashtag theo dõi", @@ -468,10 +473,23 @@ "notification.follow": "{name} theo dõi bạn", "notification.follow_request": "{name} yêu cầu theo dõi bạn", "notification.mention": "{name} nhắc đến bạn", + "notification.moderation-warning.learn_more": "Tìm hiểu", + "notification.moderation_warning": "Bạn đã nhận một cảnh báo kiểm duyệt", + "notification.moderation_warning.action_delete_statuses": "Một vài tút của bạn bị gỡ.", + "notification.moderation_warning.action_disable": "Tài khoản của bạn đã bị vô hiệu hóa.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Vài tút bạn bị đánh dấu nhạy cảm.", + "notification.moderation_warning.action_none": "Bạn đã nhận một cảnh báo kiểm duyệt.", + "notification.moderation_warning.action_sensitive": "Tút của bạn sẽ bị đánh dấu nhạy cảm kể từ bây giờ.", + "notification.moderation_warning.action_silence": "Tài khoản của bạn đã bị hạn chế.", + "notification.moderation_warning.action_suspend": "Tài khoản của bạn đã bị vô hiệu hóa.", "notification.own_poll": "Cuộc bình chọn của bạn đã kết thúc", "notification.poll": "Cuộc bình chọn đã kết thúc", "notification.reblog": "{name} đăng lại tút của bạn", - "notification.severed_relationships": "Mối quan hệ với {name} bị cắt đứt", + "notification.relationships_severance_event": "Mất kết nối với {name}", + "notification.relationships_severance_event.account_suspension": "Quản trị viên {from} đã vô hiệu hóa {target}, điều này có nghĩa là bạn không còn có thể nhận được cập nhật từ họ hoặc tương tác với họ nữa.", + "notification.relationships_severance_event.domain_block": "Quản trị viên {from} đã chặn {target}, bao gồm {followersCount} người theo dõi bạn và {followingCount, plural, other {# người}} mà bạn theo dõi.", + "notification.relationships_severance_event.learn_more": "Tìm hiểu thêm", + "notification.relationships_severance_event.user_domain_block": "Bạn đã chặn {target}, xóa {followersCount} người theo dõi bạn và {followingCount, plural, other {# người}} theo dõi bạn.", "notification.status": "{name} đăng tút mới", "notification.update": "{name} đã sửa tút", "notification_requests.accept": "Chấp nhận", @@ -484,6 +502,8 @@ "notifications.column_settings.admin.sign_up": "Người mới tham gia:", "notifications.column_settings.alert": "Báo trên máy tính", "notifications.column_settings.favourite": "Lượt thích:", + "notifications.column_settings.filter_bar.advanced": "Toàn bộ", + "notifications.column_settings.filter_bar.category": "Thanh lọc nhanh", "notifications.column_settings.follow": "Người theo dõi:", "notifications.column_settings.follow_request": "Yêu cầu theo dõi:", "notifications.column_settings.mention": "Lượt nhắc đến:", @@ -588,12 +608,6 @@ "refresh": "Làm mới", "regeneration_indicator.label": "Đang tải…", "regeneration_indicator.sublabel": "Trang chủ của bạn đang được cập nhật!", - "relationship_severance_notification.purged_data": "bị quản trị viên xóa", - "relationship_severance_notification.relationships": "{count, plural, other {# mối quan hệ}}", - "relationship_severance_notification.types.account_suspension": "Người này đã bị vô hiệu hóa", - "relationship_severance_notification.types.domain_block": "Máy chủ này đã bị vô hiệu hóa", - "relationship_severance_notification.types.user_domain_block": "Bạn đã chặn máy chủ này", - "relationship_severance_notification.view": "Chi tiết", "relative_time.days": "{number} ngày", "relative_time.full.days": "{number, plural, other {# ngày}}", "relative_time.full.hours": "{number, plural, other {# giờ}}", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 52a98d0005..ab6fe0bd70 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "使用一个已存在类别,或创建一个新类别", "filter_modal.select_filter.title": "过滤此嘟文", "filter_modal.title.status": "过滤一条嘟文", + "filtered_notifications_banner.mentions": "{count, plural, other {提及}}", "filtered_notifications_banner.pending_requests": "来自你可能认识的 {count, plural, =0 {0 个人} other {# 个人}}的通知", "filtered_notifications_banner.title": "通知(已过滤)", "firehose.all": "全部", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "尽管你没有锁嘟,但是 {domain} 的工作人员认为你也许会想手动审核审核这些账号的关注请求。", "follow_suggestions.curated_suggestion": "站务人员精选", "follow_suggestions.dismiss": "不再显示", + "follow_suggestions.featured_longer": "由 {domain} 管理团队精选", + "follow_suggestions.friends_of_friends_longer": "在你关注的人中很受欢迎", "follow_suggestions.hints.featured": "该用户已被 {domain} 管理团队精选。", "follow_suggestions.hints.friends_of_friends": "该用户在您关注的人中很受欢迎。", "follow_suggestions.hints.most_followed": "该用户是 {domain} 上关注度最高的用户之一。", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "该用户与您最近关注的用户类似。", "follow_suggestions.personalized_suggestion": "个性化建议", "follow_suggestions.popular_suggestion": "热门建议", + "follow_suggestions.popular_suggestion_longer": "在 {domain} 上很受欢迎", + "follow_suggestions.similar_to_recently_followed_longer": "与你近期关注的用户相似", "follow_suggestions.view_all": "查看全部", "follow_suggestions.who_to_follow": "推荐关注", "followed_tags": "关注的话题标签", @@ -468,10 +473,23 @@ "notification.follow": "{name} 开始关注你", "notification.follow_request": "{name} 向你发送了关注请求", "notification.mention": "{name} 提及了你", + "notification.moderation-warning.learn_more": "了解更多", + "notification.moderation_warning": "你收到了一条管理警告", + "notification.moderation_warning.action_delete_statuses": "你的一些嘟文已被移除。", + "notification.moderation_warning.action_disable": "你的账号已被禁用。", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "你的一些嘟文已被标记为敏感内容。", + "notification.moderation_warning.action_none": "你的账号收到了管理警告。", + "notification.moderation_warning.action_sensitive": "今后你的嘟文都会被标记为敏感内容。", + "notification.moderation_warning.action_silence": "你的账号已被限制。", + "notification.moderation_warning.action_suspend": "你的账号已被封禁.", "notification.own_poll": "你的投票已经结束", "notification.poll": "你参与的一个投票已经结束", "notification.reblog": "{name} 转发了你的嘟文", - "notification.severed_relationships": "与 {name} 的关系已被切断", + "notification.relationships_severance_event": "与 {name} 的联系已断开", + "notification.relationships_severance_event.account_suspension": "一名来自 {from} 的管理员已经封禁了{target},这意味着你将无法再收到他们的更新或与他们互动。", + "notification.relationships_severance_event.domain_block": "一名来自 {from} 的管理员已经屏蔽了 {target},其中包括你的 {followersCount} 个关注者和 {followingCount, plural, other {# 个关注}}。", + "notification.relationships_severance_event.learn_more": "了解更多", + "notification.relationships_severance_event.user_domain_block": "你已经屏蔽了 {target},移除了你的 {followersCount} 个关注者和 {followingCount, plural, other {# 个关注}}。", "notification.status": "{name} 刚刚发布嘟文", "notification.update": "{name} 编辑了嘟文", "notification_requests.accept": "接受", @@ -590,12 +608,6 @@ "refresh": "刷新", "regeneration_indicator.label": "加载中…", "regeneration_indicator.sublabel": "你的主页动态正在准备中!", - "relationship_severance_notification.purged_data": "被管理员清除", - "relationship_severance_notification.relationships": "{count, plural, other {# 条关系}}", - "relationship_severance_notification.types.account_suspension": "账户已被封禁", - "relationship_severance_notification.types.domain_block": "域名已被封禁", - "relationship_severance_notification.types.user_domain_block": "你屏蔽了这个域名", - "relationship_severance_notification.view": "查看", "relative_time.days": "{number} 天前", "relative_time.full.days": "{number, plural, one {# 天} other {# 天}}前", "relative_time.full.hours": "{number, plural, one {# 小时} other {# 小时}}前", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index 68be293df9..6b08e40284 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "使用既有類別,或創建一個新類別", "filter_modal.select_filter.title": "過濾此帖文", "filter_modal.title.status": "過濾一則帖文", + "filtered_notifications_banner.mentions": "{count, plural, one {則提及} other {則提及}}", "filtered_notifications_banner.pending_requests": "來自 {count, plural, =0 {0 位} other {# 位}}你可能認識的人的通知", "filtered_notifications_banner.title": "已過濾之通知", "firehose.all": "全部", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "即使您的帳號未上鎖,{domain} 的工作人員認為您可能會想手動審核來自這些帳號的追蹤請求。", "follow_suggestions.curated_suggestion": "編輯精選", "follow_suggestions.dismiss": "不再顯示", + "follow_suggestions.featured_longer": "{domain} 團隊精選", + "follow_suggestions.friends_of_friends_longer": "受你的追蹤對象歡迎", "follow_suggestions.hints.featured": "這個人檔案是由 {domain} 團隊精挑細選。", "follow_suggestions.hints.friends_of_friends": "這個人檔案在你追蹤的人當中很受歡迎。", "follow_suggestions.hints.most_followed": "這個人檔案是在 {domain} 上最多追蹤之一。", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "這個人檔案與你最近追蹤的類似。", "follow_suggestions.personalized_suggestion": "個人化推薦", "follow_suggestions.popular_suggestion": "熱門推薦", + "follow_suggestions.popular_suggestion_longer": "{domain} 熱門", + "follow_suggestions.similar_to_recently_followed_longer": "與你最近追蹤的帳號相似", "follow_suggestions.view_all": "查看所有", "follow_suggestions.who_to_follow": "追蹤對象", "followed_tags": "已追蹤標籤", @@ -468,10 +473,23 @@ "notification.follow": "{name} 開始追蹤你", "notification.follow_request": "{name} 要求追蹤你", "notification.mention": "{name} 提及你", + "notification.moderation-warning.learn_more": "了解更多", + "notification.moderation_warning": "你收到一則審核警告", + "notification.moderation_warning.action_delete_statuses": "你的部份帖文已被刪除。", + "notification.moderation_warning.action_disable": "你的帳號已被停用。", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "你某些帖文已被標記為敏感內容。", + "notification.moderation_warning.action_none": "你的帳號收到一則審核警告。", + "notification.moderation_warning.action_sensitive": "從現在起,你的帖文將被標記為敏感內容。", + "notification.moderation_warning.action_silence": "你的帳號已受到限制。", + "notification.moderation_warning.action_suspend": "你的帳號已被停權。", "notification.own_poll": "你的投票已結束", "notification.poll": "你參與過的一個投票已經結束", "notification.reblog": "{name} 轉推你的文章", - "notification.severed_relationships": "已斷絕與 {name} 的關係", + "notification.relationships_severance_event": "失去與 {name} 的連結", + "notification.relationships_severance_event.account_suspension": "{from} 的管理員已將 {target} 停權,這表示你無法再收到他們的更新或與他們互動。", + "notification.relationships_severance_event.domain_block": "{from} 的管理員已封鎖 {target},包括你的 {followersCount} 位追蹤者和 {followingCount, plural, other {# 個你追蹤的帳號}}。", + "notification.relationships_severance_event.learn_more": "了解更多", + "notification.relationships_severance_event.user_domain_block": "你已封鎖 {target},並移除了你的 {followersCount} 位追蹤者和你追蹤的 {followingCount, plural, other {# 個帳號}}。", "notification.status": "{name} 剛發表了文章", "notification.update": "{name} 編輯了帖文", "notification_requests.accept": "接受", @@ -484,6 +502,8 @@ "notifications.column_settings.admin.sign_up": "新註冊:", "notifications.column_settings.alert": "顯示桌面通知", "notifications.column_settings.favourite": "最愛:", + "notifications.column_settings.filter_bar.advanced": "顯示所有分類", + "notifications.column_settings.filter_bar.category": "快速篩選欄", "notifications.column_settings.follow": "新追蹤者:", "notifications.column_settings.follow_request": "新的追蹤請求:", "notifications.column_settings.mention": "提及你:", @@ -588,12 +608,6 @@ "refresh": "重新整理", "regeneration_indicator.label": "載入中……", "regeneration_indicator.sublabel": "你的主頁時間軸正在準備中!", - "relationship_severance_notification.purged_data": "已被管理員清除", - "relationship_severance_notification.relationships": "{count, plural, one {# 個關係} other {# 個關係}}", - "relationship_severance_notification.types.account_suspension": "帳號已被停權", - "relationship_severance_notification.types.domain_block": "網域已被停權", - "relationship_severance_notification.types.user_domain_block": "你封鎖了此網域", - "relationship_severance_notification.view": "查看", "relative_time.days": "{number}日前", "relative_time.full.days": "{number, plural, one {# 天} other {# 天}}前", "relative_time.full.hours": "{number, plural, one {# 小時} other {# 小時}}前", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index d8e1e1e32b..f00d62c076 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "使用既有的類別或是新增", "filter_modal.select_filter.title": "過濾此嘟文", "filter_modal.title.status": "過濾一則嘟文", + "filtered_notifications_banner.mentions": "{count, plural, other {# 則提及}}", "filtered_notifications_banner.pending_requests": "來自您可能認識的 {count, plural, =0 {0 人} other {# 人}} 之通知", "filtered_notifications_banner.title": "已過濾之通知", "firehose.all": "全部", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "即便您的帳號未被鎖定,{domain} 的管理員認為您可能想要自己審核這些帳號的跟隨請求。", "follow_suggestions.curated_suggestion": "精選內容", "follow_suggestions.dismiss": "不再顯示", + "follow_suggestions.featured_longer": "{domain} 團隊精選", + "follow_suggestions.friends_of_friends_longer": "受您跟隨之使用者愛戴的風雲人物", "follow_suggestions.hints.featured": "這個個人檔案是 {domain} 管理團隊精心挑選。", "follow_suggestions.hints.friends_of_friends": "這個個人檔案於您跟隨的帳號中很受歡迎。", "follow_suggestions.hints.most_followed": "這個個人檔案是 {domain} 中最受歡迎的帳號之一。", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "這個個人檔案與您最近跟隨之帳號類似。", "follow_suggestions.personalized_suggestion": "個人化推薦", "follow_suggestions.popular_suggestion": "熱門推薦", + "follow_suggestions.popular_suggestion_longer": "{domain} 上的人氣王", + "follow_suggestions.similar_to_recently_followed_longer": "與您近日跟隨相近之帳號", "follow_suggestions.view_all": "檢視全部", "follow_suggestions.who_to_follow": "推薦跟隨帳號", "followed_tags": "已跟隨主題標籤", @@ -468,10 +473,23 @@ "notification.follow": "{name} 已跟隨您", "notification.follow_request": "{name} 要求跟隨您", "notification.mention": "{name} 已提到您", + "notification.moderation-warning.learn_more": "了解更多", + "notification.moderation_warning": "您已收到管理員警告", + "notification.moderation_warning.action_delete_statuses": "某些您的嘟文已被刪除。", + "notification.moderation_warning.action_disable": "您的帳號已被停用。", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "某些您的嘟文已被標記為敏感內容。", + "notification.moderation_warning.action_none": "您的帳號已收到管理員警告。", + "notification.moderation_warning.action_sensitive": "即日起,您的嘟文將會被標記為敏感內容。", + "notification.moderation_warning.action_silence": "您的帳號已被限制。", + "notification.moderation_warning.action_suspend": "您的帳號已被停權。", "notification.own_poll": "您的投票已結束", "notification.poll": "您曾投過的投票已經結束", "notification.reblog": "{name} 已轉嘟您的嘟文", - "notification.severed_relationships": "已斷絕與 {name} 之服務關係", + "notification.relationships_severance_event": "與 {name} 失去連結", + "notification.relationships_severance_event.account_suspension": "{from} 之管理員已將 {target} 停權,意味著您將不再收到來自他們的更新或與之互動。", + "notification.relationships_severance_event.domain_block": "{from} 之管理員已將 {target} 封鎖,包含 {followersCount} 名您的跟隨者及 {followingCount, plural, other {#}} 名您跟隨的帳號。", + "notification.relationships_severance_event.learn_more": "了解更多", + "notification.relationships_severance_event.user_domain_block": "您已將 {target} 封鎖,將移除 {followersCount} 名您的跟隨者及 {followingCount, plural, other {#}} 名您跟隨的帳號。", "notification.status": "{name} 剛剛嘟文", "notification.update": "{name} 已編輯嘟文", "notification_requests.accept": "接受", @@ -590,12 +608,6 @@ "refresh": "重新整理", "regeneration_indicator.label": "載入中…", "regeneration_indicator.sublabel": "您的首頁時間軸正在準備中!", - "relationship_severance_notification.purged_data": "已被管理員清除", - "relationship_severance_notification.relationships": "{count, plural, other {# 個服務關係}}", - "relationship_severance_notification.types.account_suspension": "該帳號已被停權", - "relationship_severance_notification.types.domain_block": "該網域已被停權", - "relationship_severance_notification.types.user_domain_block": "您已封鎖此網域", - "relationship_severance_notification.view": "檢視", "relative_time.days": "{number} 天", "relative_time.full.days": "{number, plural, other {# 天}}前", "relative_time.full.hours": "{number, plural, one {# 小時} other {# 小時}}前", @@ -731,7 +743,7 @@ "status.reblogs.empty": "還沒有人轉嘟過這則嘟文。當有人轉嘟時,它將於此顯示。", "status.redraft": "刪除並重新編輯", "status.remove_bookmark": "移除書籤", - "status.replied_to": "回覆給 {name}", + "status.replied_to": "回覆 {name}", "status.reply": "回覆", "status.replyAll": "回覆討論串", "status.report": "檢舉 @{name}", diff --git a/app/javascript/mastodon/models/status.ts b/app/javascript/mastodon/models/status.ts new file mode 100644 index 0000000000..7907fc34f8 --- /dev/null +++ b/app/javascript/mastodon/models/status.ts @@ -0,0 +1,4 @@ +export type { StatusVisibility } from 'mastodon/api_types/statuses'; + +// Temporary until we type it correctly +export type Status = Immutable.Map; diff --git a/app/javascript/mastodon/reducers/boosts.js b/app/javascript/mastodon/reducers/boosts.js deleted file mode 100644 index d0d825057c..0000000000 --- a/app/javascript/mastodon/reducers/boosts.js +++ /dev/null @@ -1,25 +0,0 @@ -import Immutable from 'immutable'; - -import { - BOOSTS_INIT_MODAL, - BOOSTS_CHANGE_PRIVACY, -} from 'mastodon/actions/boosts'; - -const initialState = Immutable.Map({ - new: Immutable.Map({ - privacy: 'public', - }), -}); - -export default function mutes(state = initialState, action) { - switch (action.type) { - case BOOSTS_INIT_MODAL: - return state.withMutations((state) => { - state.setIn(['new', 'privacy'], action.privacy); - }); - case BOOSTS_CHANGE_PRIVACY: - return state.setIn(['new', 'privacy'], action.privacy); - default: - return state; - } -} diff --git a/app/javascript/mastodon/reducers/index.ts b/app/javascript/mastodon/reducers/index.ts index 3d42cc83f8..6296ef2026 100644 --- a/app/javascript/mastodon/reducers/index.ts +++ b/app/javascript/mastodon/reducers/index.ts @@ -7,7 +7,6 @@ import { accountsReducer } from './accounts'; import accounts_map from './accounts_map'; import alerts from './alerts'; import announcements from './announcements'; -import boosts from './boosts'; import compose from './compose'; import contexts from './contexts'; import conversations from './conversations'; @@ -21,14 +20,14 @@ import history from './history'; import listAdder from './list_adder'; import listEditor from './list_editor'; import lists from './lists'; -import markers from './markers'; +import { markersReducer } from './markers'; import media_attachments from './media_attachments'; import meta from './meta'; import { modalReducer } from './modal'; import { notificationPolicyReducer } from './notification_policy'; import { notificationRequestsReducer } from './notification_requests'; import notifications from './notifications'; -import picture_in_picture from './picture_in_picture'; +import { pictureInPictureReducer } from './picture_in_picture'; import polls from './polls'; import push_notifications from './push_notifications'; import { relationshipsReducer } from './relationships'; @@ -60,7 +59,6 @@ const reducers = { relationships: relationshipsReducer, settings, push_notifications, - boosts, server, contexts, compose, @@ -77,8 +75,8 @@ const reducers = { suggestions, polls, trends, - markers, - picture_in_picture, + markers: markersReducer, + picture_in_picture: pictureInPictureReducer, history, tags, followed_tags, diff --git a/app/javascript/mastodon/reducers/markers.js b/app/javascript/mastodon/reducers/markers.js deleted file mode 100644 index c7c5d99f61..0000000000 --- a/app/javascript/mastodon/reducers/markers.js +++ /dev/null @@ -1,26 +0,0 @@ -import { Map as ImmutableMap } from 'immutable'; - -import { - MARKERS_SUBMIT_SUCCESS, -} from '../actions/markers'; - - -const initialState = ImmutableMap({ - home: '0', - notifications: '0', -}); - -export default function markers(state = initialState, action) { - switch(action.type) { - case MARKERS_SUBMIT_SUCCESS: - if (action.home) { - state = state.set('home', action.home); - } - if (action.notifications) { - state = state.set('notifications', action.notifications); - } - return state; - default: - return state; - } -} diff --git a/app/javascript/mastodon/reducers/markers.ts b/app/javascript/mastodon/reducers/markers.ts new file mode 100644 index 0000000000..ec85d0f173 --- /dev/null +++ b/app/javascript/mastodon/reducers/markers.ts @@ -0,0 +1,18 @@ +import { createReducer } from '@reduxjs/toolkit'; + +import { submitMarkersAction } from 'mastodon/actions/markers'; + +const initialState = { + home: '0', + notifications: '0', +}; + +export const markersReducer = createReducer(initialState, (builder) => { + builder.addCase( + submitMarkersAction.fulfilled, + (state, { payload: { home, notifications } }) => { + if (home) state.home = home; + if (notifications) state.notifications = notifications; + }, + ); +}); diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js index bc85936439..64cddcb666 100644 --- a/app/javascript/mastodon/reducers/notifications.js +++ b/app/javascript/mastodon/reducers/notifications.js @@ -13,7 +13,7 @@ import { unfocusApp, } from '../actions/app'; import { - MARKERS_FETCH_SUCCESS, + fetchMarkers, } from '../actions/markers'; import { notificationsUpdate, @@ -56,6 +56,7 @@ export const notificationToMap = notification => ImmutableMap({ status: notification.status ? notification.status.id : null, report: notification.report ? fromJS(notification.report) : null, event: notification.event ? fromJS(notification.event) : null, + moderation_warning: notification.moderation_warning ? fromJS(notification.moderation_warning) : null, }); const normalizeNotification = (state, notification, usePendingItems) => { @@ -255,8 +256,8 @@ const recountUnread = (state, last_read_id) => { export default function notifications(state = initialState, action) { switch(action.type) { - case MARKERS_FETCH_SUCCESS: - return action.markers.notifications ? recountUnread(state, action.markers.notifications.last_read_id) : state; + case fetchMarkers.fulfilled.type: + return action.payload.markers.notifications ? recountUnread(state, action.payload.markers.notifications.last_read_id) : state; case NOTIFICATIONS_MOUNT: return updateMounted(state); case NOTIFICATIONS_UNMOUNT: diff --git a/app/javascript/mastodon/reducers/picture_in_picture.js b/app/javascript/mastodon/reducers/picture_in_picture.js deleted file mode 100644 index 6824ad9303..0000000000 --- a/app/javascript/mastodon/reducers/picture_in_picture.js +++ /dev/null @@ -1,26 +0,0 @@ -import { PICTURE_IN_PICTURE_DEPLOY, PICTURE_IN_PICTURE_REMOVE } from 'mastodon/actions/picture_in_picture'; - -import { TIMELINE_DELETE } from '../actions/timelines'; - -const initialState = { - statusId: null, - accountId: null, - type: null, - src: null, - muted: false, - volume: 0, - currentTime: 0, -}; - -export default function pictureInPicture(state = initialState, action) { - switch(action.type) { - case PICTURE_IN_PICTURE_DEPLOY: - return { statusId: action.statusId, accountId: action.accountId, type: action.playerType, ...action.props }; - case PICTURE_IN_PICTURE_REMOVE: - return { ...initialState }; - case TIMELINE_DELETE: - return (state.statusId === action.id) ? { ...initialState } : state; - default: - return state; - } -} diff --git a/app/javascript/mastodon/reducers/picture_in_picture.ts b/app/javascript/mastodon/reducers/picture_in_picture.ts new file mode 100644 index 0000000000..0feddcb706 --- /dev/null +++ b/app/javascript/mastodon/reducers/picture_in_picture.ts @@ -0,0 +1,56 @@ +import type { Reducer } from '@reduxjs/toolkit'; + +import { + deployPictureInPictureAction, + removePictureInPicture, +} from 'mastodon/actions/picture_in_picture'; + +import { TIMELINE_DELETE } from '../actions/timelines'; + +export interface PIPMediaProps { + src: string; + muted: boolean; + volume: number; + currentTime: number; + poster: string; + backgroundColor: string; + foregroundColor: string; + accentColor: string; +} + +interface PIPStateWithValue extends Partial { + statusId: string; + accountId: string; + type: 'audio' | 'video'; +} + +interface PIPStateEmpty extends Partial { + type: null; +} + +type PIPState = PIPStateWithValue | PIPStateEmpty; + +const initialState = { + type: null, + muted: false, + volume: 0, + currentTime: 0, +}; + +export const pictureInPictureReducer: Reducer = ( + state = initialState, + action, +) => { + if (deployPictureInPictureAction.match(action)) + return { + statusId: action.payload.statusId, + accountId: action.payload.accountId, + type: action.payload.playerType, + ...action.payload.props, + }; + else if (removePictureInPicture.match(action)) return initialState; + else if (action.type === TIMELINE_DELETE) + if (state.type && state.statusId === action.id) return initialState; + + return state; +}; diff --git a/app/javascript/mastodon/reducers/search.js b/app/javascript/mastodon/reducers/search.js index 72835eb917..7828d49eee 100644 --- a/app/javascript/mastodon/reducers/search.js +++ b/app/javascript/mastodon/reducers/search.js @@ -50,6 +50,7 @@ export default function search(state = initialState, action) { return state.set('hidden', true); case SEARCH_FETCH_REQUEST: return state.withMutations(map => { + map.set('results', ImmutableMap()); map.set('isLoading', true); map.set('submitted', true); map.set('type', action.searchType); diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js index b1c60403e9..bd9b53919c 100644 --- a/app/javascript/mastodon/selectors/index.js +++ b/app/javascript/mastodon/selectors/index.js @@ -60,7 +60,7 @@ export const makeGetStatus = () => { export const makeGetPictureInPicture = () => { return createSelector([ - (state, { id }) => state.get('picture_in_picture').statusId === id, + (state, { id }) => state.picture_in_picture.statusId === id, (state) => state.getIn(['meta', 'layout']) !== 'mobile', ], (inUse, available) => ImmutableMap({ inUse: inUse && available, diff --git a/app/javascript/mastodon/store/middlewares/errors.ts b/app/javascript/mastodon/store/middlewares/errors.ts index e11aa78178..e77cec34ed 100644 --- a/app/javascript/mastodon/store/middlewares/errors.ts +++ b/app/javascript/mastodon/store/middlewares/errors.ts @@ -1,16 +1,27 @@ -import { isAction } from '@reduxjs/toolkit'; +import { + isAction, + isAsyncThunkAction, + isRejectedWithValue, +} from '@reduxjs/toolkit'; import type { Action, Middleware } from '@reduxjs/toolkit'; import type { RootState } from '..'; import { showAlertForError } from '../../actions/alerts'; +import type { AsyncThunkRejectValue } from '../typed_functions'; const defaultFailSuffix = 'FAIL'; const isFailedAction = new RegExp(`${defaultFailSuffix}$`, 'g'); -interface ActionWithMaybeAlertParams extends Action { - skipAlert?: boolean; - skipNotFound?: boolean; - error?: unknown; +interface ActionWithMaybeAlertParams extends Action, AsyncThunkRejectValue {} + +interface RejectedAction extends Action { + payload: AsyncThunkRejectValue; +} + +function isRejectedActionWithPayload( + action: unknown, +): action is RejectedAction { + return isAsyncThunkAction(action) && isRejectedWithValue(action); } function isActionWithmaybeAlertParams( @@ -19,11 +30,16 @@ function isActionWithmaybeAlertParams( return isAction(action); } -export const errorsMiddleware: Middleware, RootState> = +// eslint-disable-next-line @typescript-eslint/ban-types -- we need to use `{}` here to ensure the dispatch types can be merged +export const errorsMiddleware: Middleware<{}, RootState> = ({ dispatch }) => (next) => (action) => { - if ( + if (isRejectedActionWithPayload(action) && !action.payload.skipAlert) { + dispatch( + showAlertForError(action.payload.error, action.payload.skipNotFound), + ); + } else if ( isActionWithmaybeAlertParams(action) && !action.skipAlert && action.type.match(isFailedAction) diff --git a/app/javascript/mastodon/store/middlewares/sounds.ts b/app/javascript/mastodon/store/middlewares/sounds.ts index 51839f427a..720ee163e9 100644 --- a/app/javascript/mastodon/store/middlewares/sounds.ts +++ b/app/javascript/mastodon/store/middlewares/sounds.ts @@ -51,7 +51,8 @@ const play = (audio: HTMLAudioElement) => { }; export const soundsMiddleware = (): Middleware< - Record, + // eslint-disable-next-line @typescript-eslint/ban-types -- we need to use `{}` here to ensure the dispatch types can be merged + {}, RootState > => { const soundCache: Record = {}; diff --git a/app/javascript/mastodon/store/typed_functions.ts b/app/javascript/mastodon/store/typed_functions.ts index 4859b82651..b66d7545c5 100644 --- a/app/javascript/mastodon/store/typed_functions.ts +++ b/app/javascript/mastodon/store/typed_functions.ts @@ -7,8 +7,14 @@ import type { AppDispatch, RootState } from './store'; export const useAppDispatch = useDispatch.withTypes(); export const useAppSelector = useSelector.withTypes(); +export interface AsyncThunkRejectValue { + skipAlert?: boolean; + skipNotFound?: boolean; + error?: unknown; +} + export const createAppAsyncThunk = createAsyncThunk.withTypes<{ state: RootState; dispatch: AppDispatch; - rejectValue: string; + rejectValue: AsyncThunkRejectValue; }>(); diff --git a/app/javascript/mastodon/utils/__tests__/html-test.s b/app/javascript/mastodon/utils/__tests__/html-test.ts similarity index 67% rename from app/javascript/mastodon/utils/__tests__/html-test.s rename to app/javascript/mastodon/utils/__tests__/html-test.ts index d948cf4c5d..99bfdcb801 100644 --- a/app/javascript/mastodon/utils/__tests__/html-test.s +++ b/app/javascript/mastodon/utils/__tests__/html-test.ts @@ -3,7 +3,9 @@ import * as html from '../html'; describe('html', () => { describe('unescapeHTML', () => { it('returns unescaped HTML', () => { - const output = html.unescapeHTML('

lorem

ipsum


<br>'); + const output = html.unescapeHTML( + '

lorem

ipsum


<br>', + ); expect(output).toEqual('lorem\n\nipsum\n
'); }); }); diff --git a/app/javascript/mastodon/utils/__tests__/numbers.ts b/app/javascript/mastodon/utils/__tests__/numbers.ts new file mode 100644 index 0000000000..d1d1444e8a --- /dev/null +++ b/app/javascript/mastodon/utils/__tests__/numbers.ts @@ -0,0 +1,24 @@ +import { DECIMAL_UNITS, toShortNumber } from '../numbers'; + +interface TableRow { + input: number; + base: number; + unit: number; + digits: number; +} + +describe.each` + input | base | unit | digits + ${10_000_000} | ${10} | ${DECIMAL_UNITS.MILLION} | ${0} + ${2_789_123} | ${2.789123} | ${DECIMAL_UNITS.MILLION} | ${1} + ${12_345_789} | ${12.345789} | ${DECIMAL_UNITS.MILLION} | ${0} + ${10_000_000_000} | ${10} | ${DECIMAL_UNITS.BILLION} | ${0} + ${12} | ${12} | ${DECIMAL_UNITS.ONE} | ${0} + ${123} | ${123} | ${DECIMAL_UNITS.ONE} | ${0} + ${1234} | ${1.234} | ${DECIMAL_UNITS.THOUSAND} | ${1} + ${6666} | ${6.666} | ${DECIMAL_UNITS.THOUSAND} | ${1} +`('toShortNumber', ({ input, base, unit, digits }: TableRow) => { + test(`correctly formats ${input}`, () => { + expect(toShortNumber(input)).toEqual([base, unit, digits]); + }); +}); diff --git a/app/javascript/mastodon/utils/numbers.ts b/app/javascript/mastodon/utils/numbers.ts index ee2dabf566..0c11c268dd 100644 --- a/app/javascript/mastodon/utils/numbers.ts +++ b/app/javascript/mastodon/utils/numbers.ts @@ -70,10 +70,10 @@ export function roundTo10(num: number): number { return Math.round(num * 0.1) / 0.1; } -export function toCappedNumber(num: string): string { - if (parseInt(num) > 99) { - return '99+'; +export function toCappedNumber(num: number, max = 99): string { + if (num > max) { + return `${max}+`; } else { - return num; + return num.toString(); } } diff --git a/app/javascript/mastodon/uuid.ts b/app/javascript/mastodon/uuid.ts index 4d0a8a8036..0b4d55beb6 100644 --- a/app/javascript/mastodon/uuid.ts +++ b/app/javascript/mastodon/uuid.ts @@ -4,5 +4,6 @@ export function uuid(a?: string): string { (a as unknown as number) ^ ((Math.random() * 16) >> ((a as unknown as number) / 4)) ).toString(16) - : ('' + 1e7 + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, uuid); + : // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + ('' + 1e7 + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, uuid); } diff --git a/app/javascript/material-icons/400-24px/gavel-fill.svg b/app/javascript/material-icons/400-24px/gavel-fill.svg new file mode 100644 index 0000000000..9699b8480a --- /dev/null +++ b/app/javascript/material-icons/400-24px/gavel-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/gavel.svg b/app/javascript/material-icons/400-24px/gavel.svg new file mode 100644 index 0000000000..9699b8480a --- /dev/null +++ b/app/javascript/material-icons/400-24px/gavel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/packs/admin.tsx b/app/javascript/packs/admin.tsx deleted file mode 100644 index 13e740b190..0000000000 --- a/app/javascript/packs/admin.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import './public-path'; -import { createRoot } from 'react-dom/client'; - -import ready from '../mastodon/ready'; - -async function mountReactComponent(element: Element) { - const componentName = element.getAttribute('data-admin-component'); - const stringProps = element.getAttribute('data-props'); - - if (!stringProps) return; - - const componentProps = JSON.parse(stringProps) as object; - - const { default: AdminComponent } = await import( - '@/mastodon/containers/admin_component' - ); - - const { default: Component } = (await import( - `@/mastodon/components/admin/${componentName}` - )) as { default: React.ComponentType }; - - const root = createRoot(element); - - root.render( - - - , - ); -} - -ready(() => { - document.querySelectorAll('[data-admin-component]').forEach((element) => { - void mountReactComponent(element); - }); -}).catch((reason) => { - throw reason; -}); diff --git a/app/javascript/packs/common.js b/app/javascript/packs/common.js deleted file mode 100644 index 05dff8e494..0000000000 --- a/app/javascript/packs/common.js +++ /dev/null @@ -1,2 +0,0 @@ -import './public-path'; -import 'styles/application.scss'; diff --git a/app/javascript/packs/sign_up.js b/app/javascript/packs/sign_up.js deleted file mode 100644 index cf9c837773..0000000000 --- a/app/javascript/packs/sign_up.js +++ /dev/null @@ -1,42 +0,0 @@ -import './public-path'; -import axios from 'axios'; - -import ready from '../mastodon/ready'; - -ready(() => { - setInterval(() => { - axios.get('/api/v1/emails/check_confirmation').then((response) => { - if (response.data) { - window.location = '/start'; - } - }).catch(error => { - console.error(error); - }); - }, 5000); - - document.querySelectorAll('.timer-button').forEach(button => { - let counter = 30; - - const container = document.createElement('span'); - - const updateCounter = () => { - container.innerText = ` (${counter})`; - }; - - updateCounter(); - - const countdown = setInterval(() => { - counter--; - - if (counter === 0) { - button.disabled = false; - button.removeChild(container); - clearInterval(countdown); - } else { - updateCounter(); - } - }, 1000); - - button.appendChild(container); - }); -}); diff --git a/app/javascript/skins/glitch/default/common.scss b/app/javascript/skins/glitch/default/common.scss new file mode 100644 index 0000000000..ff0d0831e0 --- /dev/null +++ b/app/javascript/skins/glitch/default/common.scss @@ -0,0 +1 @@ +@import 'flavours/glitch/styles/application'; diff --git a/app/javascript/skins/vanilla/default/common.scss b/app/javascript/skins/vanilla/default/common.scss new file mode 100644 index 0000000000..71ac01180a --- /dev/null +++ b/app/javascript/skins/vanilla/default/common.scss @@ -0,0 +1 @@ +@import 'styles/application'; diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss index 493e377d61..07e9d9868b 100644 --- a/app/javascript/styles/mastodon-light/diff.scss +++ b/app/javascript/styles/mastodon-light/diff.scss @@ -80,6 +80,7 @@ html { } .search__input, +.search__popout, .setting-text, .report-dialog-modal__textarea, .audio-player { @@ -439,7 +440,8 @@ html { .directory__tag > div, .card > a, .page-header, -.compose-form .compose-form__warning { +.compose-form, +.compose-form__warning { box-shadow: none; } @@ -481,6 +483,10 @@ html { .compose-form .autosuggest-textarea__textarea, .compose-form__highlightable, +.search__input, +.search__popout, +.emoji-mart-search input, +.language-dropdown__dropdown .emoji-mart-search input, .poll__option input[type='text'] { background: darken($ui-base-color, 10%); } diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 7ae1bbdd6c..1556b69e9d 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -10,6 +10,13 @@ $content-width: 840px; width: 100%; min-height: 100vh; + .icon { + width: 16px; + height: 16px; + vertical-align: middle; + margin: 0 2px; + } + .sidebar-wrapper { min-height: 100vh; overflow: hidden; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index b596d70c5b..af41ab3bcc 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -2015,7 +2015,22 @@ a .account__avatar { white-space: nowrap; display: flex; align-items: center; - gap: 4px; + gap: 8px; +} + +.account__relationship, +.explore__suggestions__card { + .icon-button { + border: 1px solid var(--background-border-color); + border-radius: 4px; + box-sizing: content-box; + padding: 5px; + + .icon { + width: 24px; + height: 24px; + } + } } .account-authorize { @@ -2165,7 +2180,8 @@ a.account__display-name { } } -.notification__relationships-severance-event { +.notification__relationships-severance-event, +.notification__moderation-warning { display: flex; gap: 16px; color: $secondary-text-color; @@ -2244,14 +2260,14 @@ a.account__display-name { &.activate { & > .icon { animation: spring-rotate-in 1s linear; - transform-origin: 50% 55%; + transform-origin: 50% 52%; } } &.deactivate { & > .icon { animation: spring-rotate-out 1s linear; - transform-origin: 50% 55%; + transform-origin: 50% 52%; } } } @@ -2612,6 +2628,7 @@ a.account__display-name { } $ui-header-height: 55px; +$ui-header-logo-wordmark-width: 99px; .ui__header { display: none; @@ -2627,6 +2644,10 @@ $ui-header-height: 55px; &__logo { display: inline-flex; padding: 15px; + flex-grow: 1; + flex-shrink: 1; + overflow: hidden; + container: header-logo / inline-size; .logo { height: $ui-header-height - 30px; @@ -2637,7 +2658,7 @@ $ui-header-height: 55px; display: none; } - @media screen and (width >= 320px) { + @container header-logo (min-width: #{$ui-header-logo-wordmark-width}) { .logo--wordmark { display: block; } @@ -2654,6 +2675,7 @@ $ui-header-height: 55px; gap: 10px; padding: 0 10px; overflow: hidden; + flex-shrink: 0; .button { flex: 0 0 auto; @@ -2671,7 +2693,7 @@ $ui-header-height: 55px; } .tabs-bar__wrapper { - background: var(--background-color-tint); + background: var(--background-color); backdrop-filter: var(--background-filter); position: sticky; top: $ui-header-height; @@ -2890,24 +2912,31 @@ $ui-header-height: 55px; } } - .layout-single-column .ui__header { - display: flex; - background: var(--background-color-tint); - border-bottom: 1px solid var(--background-border-color); - } + .layout-single-column { + .ui__header { + display: flex; + background: var(--background-color); + border-bottom: 1px solid var(--background-border-color); + } - .column > .scrollable, - .tabs-bar__wrapper .column-header, - .tabs-bar__wrapper .column-back-button { - border-left: 0; - border-right: 0; - } + .column > .scrollable, + .tabs-bar__wrapper .column-header, + .tabs-bar__wrapper .column-back-button { + border-left: 0; + border-right: 0; + } - .column-header, - .column-back-button, - .scrollable, - .error-column { - border-radius: 0 !important; + .column-header, + .column-back-button, + .scrollable, + .error-column { + border-radius: 0 !important; + } + + .column-header, + .column-back-button { + border-top: 0; + } } } @@ -2939,6 +2968,75 @@ $ui-header-height: 55px; display: none; } +.explore__suggestions__card { + padding: 12px 16px; + gap: 8px; + display: flex; + flex-direction: column; + border-bottom: 1px solid var(--background-border-color); + + &:last-child { + border-bottom: 0; + } + + &__source { + padding-inline-start: 60px; + font-size: 13px; + line-height: 16px; + color: $dark-text-color; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + &__body { + display: flex; + gap: 12px; + align-items: center; + + &__main { + flex: 1 1 auto; + display: flex; + flex-direction: column; + gap: 8px; + min-width: 0; + + &__name-button { + display: flex; + align-items: center; + gap: 8px; + + &__name { + display: block; + color: inherit; + text-decoration: none; + flex: 1 1 auto; + min-width: 0; + } + + .button { + min-width: 80px; + } + + .display-name { + font-size: 15px; + line-height: 20px; + color: $secondary-text-color; + + strong { + font-weight: 700; + } + + &__account { + color: $darker-text-color; + display: block; + } + } + } + } + } +} + @media screen and (max-width: $no-gap-breakpoint - 1px) { .columns-area__panels__pane--compositional { display: none; @@ -3466,10 +3564,6 @@ $ui-header-height: 55px; &:hover { text-decoration: underline; } - - @media screen and (max-width: $no-gap-breakpoint) { - border-top: 0; - } } .column-header__back-button { @@ -4233,10 +4327,6 @@ a.status-card { padding-top: 16px; } } - - @media screen and (max-width: $no-gap-breakpoint) { - border-top: 0; - } } .column-header__buttons { @@ -5085,6 +5175,7 @@ a.status-card { .language-dropdown__dropdown { box-shadow: var(--dropdown-shadow); background: var(--dropdown-background-color); + backdrop-filter: var(--background-filter); border: 1px solid var(--dropdown-border-color); padding: 4px; border-radius: 4px; @@ -5525,7 +5616,7 @@ a.status-card { user-select: text; display: flex; - @media screen and (max-width: $no-gap-breakpoint) { + @media screen and (width <= 630px) { margin-top: auto; } } @@ -7275,10 +7366,11 @@ a.status-card { content: ''; position: absolute; bottom: -1px; - left: 0; - width: 100%; + left: 50%; + transform: translateX(-50%); + width: 40px; height: 3px; - border-radius: 4px; + border-radius: 4px 4px 0 0; background: $highlight-text-color; } } @@ -7857,8 +7949,13 @@ noscript { } } - @container account-header (max-width: 372px) { - .optional { + .optional { + @container account-header (max-width: 372px) { + display: none; + } + + // Fallback for older browsers with no container queries support + @media screen and (max-width: 372px + 55px) { display: none; } } @@ -10048,6 +10145,7 @@ noscript { font-weight: 500; font-size: 11px; line-height: 16px; + word-break: keep-all; &__badge { background: $ui-button-background-color; @@ -10098,9 +10196,10 @@ noscript { } .filtered-notifications-banner__badge { - background-color: $highlight-text-color; + background: $ui-button-background-color; border-radius: 4px; padding: 1px 6px; + color: $white; } } diff --git a/app/javascript/styles/mastodon/emoji_picker.scss b/app/javascript/styles/mastodon/emoji_picker.scss index 14ce6a14b5..b9fdaa5847 100644 --- a/app/javascript/styles/mastodon/emoji_picker.scss +++ b/app/javascript/styles/mastodon/emoji_picker.scss @@ -112,10 +112,11 @@ border: 0; } - &::-moz-focus-inner, - &:focus, - &:active { - outline: 0 !important; + &:active, + &:focus { + outline: none !important; + border-width: 1px !important; + border-color: $ui-button-background-color; } &::-webkit-search-cancel-button { diff --git a/app/lib/access_token_extension.rb b/app/lib/access_token_extension.rb index f51bde4927..4e9585dd1e 100644 --- a/app/lib/access_token_extension.rb +++ b/app/lib/access_token_extension.rb @@ -6,6 +6,8 @@ module AccessTokenExtension included do include Redisable + has_many :web_push_subscriptions, class_name: 'Web::PushSubscription', inverse_of: :access_token + after_commit :push_to_streaming_api end diff --git a/app/lib/account_statuses_filter.rb b/app/lib/account_statuses_filter.rb index 2744ad4143..e5c2409148 100644 --- a/app/lib/account_statuses_filter.rb +++ b/app/lib/account_statuses_filter.rb @@ -35,7 +35,7 @@ class AccountStatusesFilter return Status.none if account.unavailable? if anonymous? - account.statuses.not_local_only.where(visibility: %i(public unlisted)) + account.statuses.not_local_only.distributable_visibility elsif author? account.statuses.all # NOTE: #merge! does not work without the #all elsif blocked? diff --git a/app/lib/admin/metrics/measure/tag_servers_measure.rb b/app/lib/admin/metrics/measure/tag_servers_measure.rb index e0f1bf3440..f273d739d0 100644 --- a/app/lib/admin/metrics/measure/tag_servers_measure.rb +++ b/app/lib/admin/metrics/measure/tag_servers_measure.rb @@ -46,11 +46,11 @@ class Admin::Metrics::Measure::TagServersMeasure < Admin::Metrics::Measure::Base end def earliest_status_id - Mastodon::Snowflake.id_at(@start_at, with_random: false) + Mastodon::Snowflake.id_at(@start_at.beginning_of_day, with_random: false) end def latest_status_id - Mastodon::Snowflake.id_at(@end_at, with_random: false) + Mastodon::Snowflake.id_at(@end_at.end_of_day, with_random: false) end def tag diff --git a/app/lib/annual_report/top_statuses.rb b/app/lib/annual_report/top_statuses.rb index 112e5591ce..1ab1709523 100644 --- a/app/lib/annual_report/top_statuses.rb +++ b/app/lib/annual_report/top_statuses.rb @@ -16,6 +16,6 @@ class AnnualReport::TopStatuses < AnnualReport::Source end def base_scope - @account.statuses.with_public_visibility.joins(:status_stat).where(id: year_as_snowflake_range).reorder(nil) + @account.statuses.public_visibility.joins(:status_stat).where(id: year_as_snowflake_range).reorder(nil) end end diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index 5e36511897..6c51fe2536 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -18,7 +18,7 @@ class FeedManager # @yield [Account] # @return [void] def with_active_accounts(&block) - Account.joins(:user).where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago).find_each(&block) + Account.joins(:user).merge(User.signed_in_recently).find_each(&block) end # Redis key of a feed @@ -134,7 +134,7 @@ class FeedManager def merge_into_home(from_account, into_account) timeline_key = key(:home, into_account.id) aggregate = into_account.user&.aggregates_reblogs? - query = from_account.statuses.where(visibility: [:public, :unlisted, :private]).includes(:preloadable_poll, :media_attachments, reblog: :account).limit(FeedManager::MAX_ITEMS / 4) + query = from_account.statuses.list_eligible_visibility.includes(:preloadable_poll, :media_attachments, reblog: :account).limit(FeedManager::MAX_ITEMS / 4) if redis.zcard(timeline_key) >= FeedManager::MAX_ITEMS / 4 oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true).first.last.to_i @@ -160,7 +160,7 @@ class FeedManager def merge_into_list(from_account, list) timeline_key = key(:list, list.id) aggregate = list.account.user&.aggregates_reblogs? - query = from_account.statuses.where(visibility: [:public, :unlisted, :private]).includes(:preloadable_poll, :media_attachments, reblog: :account).limit(FeedManager::MAX_ITEMS / 4) + query = from_account.statuses.list_eligible_visibility.includes(:preloadable_poll, :media_attachments, reblog: :account).limit(FeedManager::MAX_ITEMS / 4) if redis.zcard(timeline_key) >= FeedManager::MAX_ITEMS / 4 oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true).first.last.to_i @@ -299,7 +299,7 @@ class FeedManager next if last_status_score < oldest_home_score end - statuses = target_account.statuses.where(visibility: [:public, :unlisted, :private]).includes(:preloadable_poll, :media_attachments, :account, reblog: :account).limit(limit) + statuses = target_account.statuses.list_eligible_visibility.includes(:preloadable_poll, :media_attachments, :account, reblog: :account).limit(limit) crutches = build_crutches(account.id, statuses) statuses.each do |status| @@ -491,10 +491,10 @@ class FeedManager # @param [List] list # @return [Boolean] def filter_from_list?(status, list) - if status.reply? && status.in_reply_to_account_id != status.account_id - should_filter = status.in_reply_to_account_id != list.account_id - should_filter &&= !list.show_followed? - should_filter &&= !(list.show_list? && ListAccount.exists?(list_id: list.id, account_id: status.in_reply_to_account_id)) + if status.reply? && status.in_reply_to_account_id != status.account_id # Status is a reply to account other than status account + should_filter = status.in_reply_to_account_id != list.account_id # Status replies to account id other than list account + should_filter &&= !list.show_followed? # List show_followed? is false + should_filter &&= !(list.show_list? && ListAccount.exists?(list_id: list.id, account_id: status.in_reply_to_account_id)) # If show_list? true, check for a ListAccount with list and reply to account return !!should_filter end @@ -509,7 +509,11 @@ class FeedManager # @param [Hash] crutches # @return [Boolean] def filter_from_tags?(status, receiver_id, crutches) - receiver_id == status.account_id || ((crutches[:active_mentions][status.id] || []) + [status.account_id]).any? { |target_account_id| crutches[:blocking][target_account_id] || crutches[:muting][target_account_id] } || crutches[:blocked_by][status.account_id] || crutches[:domain_blocking][status.account.domain] + receiver_id == status.account_id || # Receiver is status account? + ((crutches[:active_mentions][status.id] || []) + [status.account_id]) # For mentioned accounts or status account: + .any? { |target_account_id| crutches[:blocking][target_account_id] || crutches[:muting][target_account_id] } || # - Target account is muted or blocked? + crutches[:blocked_by][status.account_id] || # Blocked by status account? + crutches[:domain_blocking][status.account.domain] # Blocking domain of status account? end # Adds a status to an account's feed, returning true if a status was diff --git a/app/lib/link_details_extractor.rb b/app/lib/link_details_extractor.rb index bb031986d6..07776c3699 100644 --- a/app/lib/link_details_extractor.rb +++ b/app/lib/link_details_extractor.rb @@ -156,7 +156,7 @@ class LinkDetailsExtractor end def title - html_entities.decode(structured_data&.headline || opengraph_tag('og:title') || document.xpath('//title').map(&:content).first) + html_entities.decode(structured_data&.headline || opengraph_tag('og:title') || document.xpath('//title').map(&:content).first).strip end def description @@ -282,6 +282,6 @@ class LinkDetailsExtractor end def html_entities - @html_entities ||= HTMLEntities.new + @html_entities ||= HTMLEntities.new(:expanded) end end diff --git a/app/lib/themes.rb b/app/lib/themes.rb index 45ba47780b..a87323d8e9 100644 --- a/app/lib/themes.rb +++ b/app/lib/themes.rb @@ -6,14 +6,17 @@ require 'yaml' class Themes include Singleton - def initialize - core = YAML.load_file(Rails.root.join('app', 'javascript', 'core', 'theme.yml')) - core['pack'] = {} unless core['pack'] + THEME_COLORS = { + dark: '#191b22', + light: '#f3f5f7', + }.freeze + + def initialize + @flavours = {} - result = {} Rails.root.glob('app/javascript/flavours/*/theme.yml') do |pathname| data = YAML.load_file(pathname) - next unless data['pack'] + next unless data['pack_directory'] dir = pathname.dirname name = dir.basename.to_s @@ -38,45 +41,35 @@ class Themes data['name'] = name data['locales'] = locales data['screenshot'] = screenshots - data['skin'] = { 'default' => [] } - result[name] = data + data['skins'] = [] + @flavours[name] = data end Rails.root.glob('app/javascript/skins/*/*') do |pathname| ext = pathname.extname.to_s skin = pathname.basename.to_s name = pathname.dirname.basename.to_s - next unless result[name] + next unless @flavours[name] if pathname.directory? - pack = [] - pathname.glob('*.{css,scss}') do |sheet| - pack.push(sheet.basename(sheet.extname).to_s) - end + @flavours[name]['skins'] << skin if pathname.glob('{common,index,application}.{css,scss}').any? elsif /^\.s?css$/i.match?(ext) - skin = pathname.basename(ext).to_s - pack = ['common'] + @flavours[name]['skins'] << pathname.basename(ext).to_s end - - result[name]['skin'][skin] = pack if skin != 'default' end - - @core = core - @conf = result end - attr_reader :core - def flavour(name) - @conf[name] + @flavours[name] end def flavours - @conf.keys + @flavours.keys end def skins_for(name) - @conf[name]['skin'].keys + skins = @flavours[name]['skins'] + skins.include?('default') && skins.include?('mastodon-light') ? ['system'] + skins : skins end def flavours_and_skins diff --git a/app/lib/vacuum/feeds_vacuum.rb b/app/lib/vacuum/feeds_vacuum.rb index b0246bc0d2..fd55be7bfe 100644 --- a/app/lib/vacuum/feeds_vacuum.rb +++ b/app/lib/vacuum/feeds_vacuum.rb @@ -28,7 +28,7 @@ class Vacuum::FeedsVacuum end def inactive_users - User.confirmed.inactive + User.confirmed.not_signed_in_recently end def inactive_users_lists diff --git a/app/lib/vacuum/imports_vacuum.rb b/app/lib/vacuum/imports_vacuum.rb index ffb9449a42..8c8bb783ac 100644 --- a/app/lib/vacuum/imports_vacuum.rb +++ b/app/lib/vacuum/imports_vacuum.rb @@ -9,7 +9,7 @@ class Vacuum::ImportsVacuum private def clean_unconfirmed_imports! - BulkImport.where(state: :unconfirmed).where('created_at <= ?', 10.minutes.ago).reorder(nil).in_batches.delete_all + BulkImport.state_unconfirmed.where('created_at <= ?', 10.minutes.ago).reorder(nil).in_batches.delete_all end def clean_old_imports! diff --git a/app/lib/video_metadata_extractor.rb b/app/lib/video_metadata_extractor.rb index f27d34868a..df5409375f 100644 --- a/app/lib/video_metadata_extractor.rb +++ b/app/lib/video_metadata_extractor.rb @@ -22,7 +22,7 @@ class VideoMetadataExtractor private def ffmpeg_command_output - command = Terrapin::CommandLine.new('ffprobe', '-i :path -print_format :format -show_format -show_streams -show_error -loglevel :loglevel') + command = Terrapin::CommandLine.new(Rails.configuration.x.ffprobe_binary, '-i :path -print_format :format -show_format -show_streams -show_error -loglevel :loglevel') command.run(path: @path, format: 'json', loglevel: 'fatal') end diff --git a/app/mailers/admin_mailer.rb b/app/mailers/admin_mailer.rb index 8990b2a847..8dd7b6e59f 100644 --- a/app/mailers/admin_mailer.rb +++ b/app/mailers/admin_mailer.rb @@ -46,12 +46,16 @@ class AdminMailer < ApplicationMailer end def new_software_updates + @software_updates = SoftwareUpdate.all.to_a.sort_by(&:gem_version) + locale_for_account(@me) do mail subject: default_i18n_subject(instance: @instance) end end def new_critical_software_updates + @software_updates = SoftwareUpdate.where(urgent: true).to_a.sort_by(&:gem_version) + headers['Priority'] = 'urgent' headers['X-Priority'] = '1' headers['Importance'] = 'high' diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 96fcd51efa..f8c1c9a8d0 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -5,10 +5,11 @@ class UserMailer < Devise::Mailer helper :accounts helper :application - helper :instance - helper :statuses + helper :mascot helper :formatting + helper :instance helper :routing + helper :statuses before_action :set_instance diff --git a/app/models/account.rb b/app/models/account.rb index b1142d5da0..69ff3a6c8d 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -64,12 +64,16 @@ class Account < ApplicationRecord ) BACKGROUND_REFRESH_INTERVAL = 1.week.freeze + DEFAULT_FIELDS_SIZE = (ENV['MAX_PROFILE_FIELDS'] || 4).to_i INSTANCE_ACTOR_ID = -99 USERNAME_RE = /[a-z0-9_]+([a-z0-9_.-]+[a-z0-9_]+)?/i MENTION_RE = %r{(? { local? && will_save_change_to_username? && actor_type != 'Application' } + validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: USERNAME_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_username? && actor_type != 'Application' } validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? && actor_type != 'Application' } - validates :display_name, length: { maximum: MAX_DISPLAY_NAME_LENGTH }, if: -> { local? && will_save_change_to_display_name? } - validates :note, note_length: { maximum: MAX_NOTE_LENGTH }, if: -> { local? && will_save_change_to_note? } + validates :display_name, length: { maximum: DISPLAY_NAME_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_display_name? } + validates :note, note_length: { maximum: NOTE_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_note? } validates :fields, length: { maximum: DEFAULT_FIELDS_SIZE }, if: -> { local? && will_save_change_to_fields? } validates :uri, absence: true, if: :local?, on: :create validates :inbox_url, absence: true, if: :local?, on: :create @@ -136,12 +136,12 @@ class Account < ApplicationRecord scope :auditable, -> { where(id: Admin::ActionLog.select(:account_id).distinct) } scope :searchable, -> { without_unapproved.without_suspended.where(moved_to_account_id: nil) } scope :discoverable, -> { searchable.without_silenced.where(discoverable: true).joins(:account_stat) } - scope :by_recent_status, -> { includes(:account_stat).merge(AccountStat.order('last_status_at DESC NULLS LAST')).references(:account_stat) } + scope :by_recent_status, -> { includes(:account_stat).merge(AccountStat.by_recent_status).references(:account_stat) } scope :by_recent_activity, -> { left_joins(:user, :account_stat).order(coalesced_activity_timestamps.desc).order(id: :desc) } - scope :popular, -> { order('account_stats.followers_count desc') } scope :by_domain_and_subdomains, ->(domain) { where(domain: Instance.by_domain_and_subdomains(domain).select(:domain)) } scope :not_excluded_by_account, ->(account) { where.not(id: account.excluded_from_timeline_account_ids) } scope :not_domain_blocked_by_account, ->(account) { where(arel_table[:domain].eq(nil).or(arel_table[:domain].not_in(account.excluded_from_timeline_domains))) } + scope :dormant, -> { joins(:account_stat).merge(AccountStat.without_recent_activity) } after_update_commit :trigger_update_webhooks diff --git a/app/models/account_moderation_note.rb b/app/models/account_moderation_note.rb index ff399bab0c..ad49b24229 100644 --- a/app/models/account_moderation_note.rb +++ b/app/models/account_moderation_note.rb @@ -13,10 +13,12 @@ # class AccountModerationNote < ApplicationRecord + CONTENT_SIZE_LIMIT = 500 + belongs_to :account belongs_to :target_account, class_name: 'Account' scope :latest, -> { reorder('created_at DESC') } - validates :content, presence: true, length: { maximum: 500 } + validates :content, presence: true, length: { maximum: CONTENT_SIZE_LIMIT } end diff --git a/app/models/account_note.rb b/app/models/account_note.rb index 9bc704d988..317e6873fa 100644 --- a/app/models/account_note.rb +++ b/app/models/account_note.rb @@ -14,9 +14,11 @@ class AccountNote < ApplicationRecord include RelationshipCacheable + COMMENT_SIZE_LIMIT = 2_000 + belongs_to :account belongs_to :target_account, class_name: 'Account' validates :account_id, uniqueness: { scope: :target_account_id } - validates :comment, length: { maximum: 2_000 } + validates :comment, length: { maximum: COMMENT_SIZE_LIMIT } end diff --git a/app/models/account_stat.rb b/app/models/account_stat.rb index 0fea7732e9..14aa7ef800 100644 --- a/app/models/account_stat.rb +++ b/app/models/account_stat.rb @@ -20,6 +20,9 @@ class AccountStat < ApplicationRecord belongs_to :account, inverse_of: :account_stat + scope :by_recent_status, -> { order(arel_table[:last_status_at].desc.nulls_last) } + scope :without_recent_activity, -> { where(last_status_at: [nil, ...1.month.ago]) } + update_index('accounts', :account) def following_count diff --git a/app/models/account_warning.rb b/app/models/account_warning.rb index a54387a562..7aa474887b 100644 --- a/app/models/account_warning.rb +++ b/app/models/account_warning.rb @@ -27,6 +27,8 @@ class AccountWarning < ApplicationRecord suspend: 4_000, }, suffix: :action + RECENT_PERIOD = 3.months.freeze + normalizes :text, with: ->(text) { text.to_s }, apply_to_nil: true belongs_to :account, inverse_of: :account_warnings @@ -37,7 +39,7 @@ class AccountWarning < ApplicationRecord scope :latest, -> { order(id: :desc) } scope :custom, -> { where.not(text: '') } - scope :recent, -> { where('account_warnings.created_at >= ?', 3.months.ago) } + scope :recent, -> { where(created_at: RECENT_PERIOD.ago..) } def statuses Status.with_discarded.where(id: status_ids || []) diff --git a/app/models/admin/account_action.rb b/app/models/admin/account_action.rb index 2b5560e2eb..3700ce4cd6 100644 --- a/app/models/admin/account_action.rb +++ b/app/models/admin/account_action.rb @@ -52,7 +52,7 @@ class Admin::AccountAction process_reports! end - process_email! + process_notification! process_queue! end @@ -158,8 +158,11 @@ class Admin::AccountAction queue_suspension_worker! if type == 'suspend' end - def process_email! - UserMailer.warning(target_account.user, warning).deliver_later! if warnable? + def process_notification! + return unless warnable? + + UserMailer.warning(target_account.user, warning).deliver_later! + LocalNotificationWorker.perform_async(target_account.id, warning.id, 'AccountWarning', 'moderation_warning') end def warnable? diff --git a/app/models/admin/import.rb b/app/models/admin/import.rb index 0fd4bdb824..c12c9a86b9 100644 --- a/app/models/admin/import.rb +++ b/app/models/admin/import.rb @@ -30,12 +30,14 @@ class Admin::Import csv_converter = lambda do |field, field_info| case field_info.header - when '#domain', '#public_comment' + when '#domain' + field&.downcase&.strip + when '#public_comment' field&.strip when '#severity' - field&.strip&.to_sym + field&.downcase&.strip&.to_sym when '#reject_media', '#reject_reports', '#obfuscate' - ActiveModel::Type::Boolean.new.cast(field) + ActiveModel::Type::Boolean.new.cast(field&.downcase) else field end diff --git a/app/models/admin/status_batch_action.rb b/app/models/admin/status_batch_action.rb index 8a8e2fa378..4a10001935 100644 --- a/app/models/admin/status_batch_action.rb +++ b/app/models/admin/status_batch_action.rb @@ -65,7 +65,8 @@ class Admin::StatusBatchAction statuses.each { |status| Tombstone.find_or_create_by(uri: status.uri, account: status.account, by_moderator: true) } unless target_account.local? end - UserMailer.warning(target_account.user, @warning).deliver_later! if warnable? + process_notification! + RemovalWorker.push_bulk(status_ids) { |status_id| [status_id, { 'preserve' => target_account.local?, 'immediate' => !target_account.local? }] } end @@ -101,7 +102,7 @@ class Admin::StatusBatchAction text: text ) - UserMailer.warning(target_account.user, @warning).deliver_later! if warnable? + process_notification! end def handle_report! @@ -127,6 +128,13 @@ class Admin::StatusBatchAction !report.nil? end + def process_notification! + return unless warnable? + + UserMailer.warning(target_account.user, @warning).deliver_later! + LocalNotificationWorker.perform_async(target_account.id, @warning.id, 'AccountWarning', 'moderation_warning') + end + def warnable? send_email_notification && target_account.local? end diff --git a/app/models/admin/status_filter.rb b/app/models/admin/status_filter.rb index 4708785e7d..8d20e4f6ab 100644 --- a/app/models/admin/status_filter.rb +++ b/app/models/admin/status_filter.rb @@ -16,7 +16,7 @@ class Admin::StatusFilter end def results - scope = @account.statuses.where(visibility: [:public, :unlisted]) + scope = @account.statuses.distributable_visibility params.each do |key, value| next if IGNORED_PARAMS.include?(key.to_s) diff --git a/app/models/announcement.rb b/app/models/announcement.rb index e630570020..54923ed081 100644 --- a/app/models/announcement.rb +++ b/app/models/announcement.rb @@ -59,11 +59,13 @@ class Announcement < ApplicationRecord end def statuses - @statuses ||= if status_ids.nil? - [] - else - Status.where(id: status_ids, visibility: [:public, :unlisted]) - end + @statuses ||= begin + if status_ids.nil? + [] + else + Status.with_includes.distributable_visibility.where(id: status_ids) + end + end end def tags diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 014a73997d..299aad6340 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -22,4 +22,10 @@ class ApplicationRecord < ActiveRecord::Base value end end + + # Prevent implicit serialization in ActiveModel::Serializer or other code paths. + # This is a hardening step to avoid accidental leaking of attributes. + def as_json + raise NotImplementedError + end end diff --git a/app/models/bulk_import.rb b/app/models/bulk_import.rb index 4cd228705a..9b3f4c8a3a 100644 --- a/app/models/bulk_import.rb +++ b/app/models/bulk_import.rb @@ -38,7 +38,7 @@ class BulkImport < ApplicationRecord scheduled: 1, in_progress: 2, finished: 3, - } + }, prefix: true validates :type, presence: true diff --git a/app/models/concerns/account/associations.rb b/app/models/concerns/account/associations.rb index 959406abe4..2bfd9fa54a 100644 --- a/app/models/concerns/account/associations.rb +++ b/app/models/concerns/account/associations.rb @@ -63,7 +63,7 @@ module Account::Associations has_many :aliases, class_name: 'AccountAlias', dependent: :destroy, inverse_of: :account # Hashtags - has_and_belongs_to_many :tags + has_and_belongs_to_many :tags # rubocop:disable Rails/HasAndBelongsToMany has_many :featured_tags, -> { includes(:tag) }, dependent: :destroy, inverse_of: :account # Account deletion requests diff --git a/app/models/concerns/account/counters.rb b/app/models/concerns/account/counters.rb index 4488398128..536d5ca7bc 100644 --- a/app/models/concerns/account/counters.rb +++ b/app/models/concerns/account/counters.rb @@ -35,40 +35,11 @@ module Account::Counters raise ArgumentError, "Invalid key #{key}" unless ALLOWED_COUNTER_KEYS.include?(key) raise ArgumentError, 'Do not call update_count! on dirty objects' if association(:account_stat).loaded? && account_stat&.changed? && account_stat.changed_attribute_names_to_save == %w(id) - value = value.to_i - default_value = value.positive? ? value : 0 - - # We do an upsert using manually written SQL, as Rails' upsert method does - # not seem to support writing expressions in the UPDATE clause, but only - # re-insert the provided values instead. - # Even ARel seem to be missing proper handling of upserts. - sql = if value.positive? && key == :statuses_count - <<-SQL.squish - INSERT INTO account_stats(account_id, #{key}, created_at, updated_at, last_status_at) - VALUES (:account_id, :default_value, now(), now(), now()) - ON CONFLICT (account_id) DO UPDATE - SET #{key} = account_stats.#{key} + :value, - last_status_at = now(), - updated_at = now() - RETURNING id; - SQL - else - <<-SQL.squish - INSERT INTO account_stats(account_id, #{key}, created_at, updated_at) - VALUES (:account_id, :default_value, now(), now()) - ON CONFLICT (account_id) DO UPDATE - SET #{key} = account_stats.#{key} + :value, - updated_at = now() - RETURNING id; - SQL - end - - sql = AccountStat.sanitize_sql([sql, account_id: id, default_value: default_value, value: value]) - account_stat_id = AccountStat.connection.exec_query(sql)[0]['id'] + result = updated_account_stat(key, value.to_i) # Reload account_stat if it was loaded, taking into account newly-created unsaved records if association(:account_stat).loaded? - account_stat.id = account_stat_id if account_stat.new_record? + account_stat.id = result.first['id'] if account_stat.new_record? account_stat.reload end end @@ -79,6 +50,28 @@ module Account::Counters private + def updated_account_stat(key, value) + AccountStat.upsert( + initial_values(key, value), + on_duplicate: Arel.sql( + duplicate_values(key, value).join(', ') + ), + unique_by: :account_id + ) + end + + def initial_values(key, value) + { :account_id => id, key => [value, 0].max }.tap do |values| + values.merge!(last_status_at: Time.current) if key == :statuses_count + end + end + + def duplicate_values(key, value) + ["#{key} = (account_stats.#{key} + #{value})", 'updated_at = CURRENT_TIMESTAMP'].tap do |values| + values << 'last_status_at = CURRENT_TIMESTAMP' if key == :statuses_count && value.positive? + end + end + def save_account_stat return unless association(:account_stat).loaded? && account_stat&.changed? diff --git a/app/models/concerns/account/interactions.rb b/app/models/concerns/account/interactions.rb index 6b28a6a447..9725d8aa38 100644 --- a/app/models/concerns/account/interactions.rb +++ b/app/models/concerns/account/interactions.rb @@ -259,13 +259,13 @@ module Account::Interactions def followers_for_local_distribution followers.local .joins(:user) - .where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago) + .merge(User.signed_in_recently) end def lists_for_local_distribution scope = lists.joins(account: :user) scope.where.not(list_accounts: { follow_id: nil }).or(scope.where(account_id: id)) - .where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago) + .merge(User.signed_in_recently) end def remote_followers_hash(url) diff --git a/app/models/concerns/cacheable.rb b/app/models/concerns/cacheable.rb index d7524cdfd0..0633f20c77 100644 --- a/app/models/concerns/cacheable.rb +++ b/app/models/concerns/cacheable.rb @@ -14,6 +14,10 @@ module Cacheable includes(@cache_associated) end + def preload_cacheable_associations(records) + ActiveRecord::Associations::Preloader.new(records: records, associations: @cache_associated).call + end + def cache_ids select(:id, :updated_at) end diff --git a/app/models/concerns/custom_filter_cache.rb b/app/models/concerns/custom_filter_cache.rb new file mode 100644 index 0000000000..79b22f11f1 --- /dev/null +++ b/app/models/concerns/custom_filter_cache.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module CustomFilterCache + extend ActiveSupport::Concern + + included do + after_commit :invalidate_cache! + before_destroy :prepare_cache_invalidation! + before_save :prepare_cache_invalidation! + + delegate( + :invalidate_cache!, + :prepare_cache_invalidation!, + to: :custom_filter + ) + end +end diff --git a/app/models/concerns/legacy_otp_secret.rb b/app/models/concerns/legacy_otp_secret.rb new file mode 100644 index 0000000000..466c4ec9bb --- /dev/null +++ b/app/models/concerns/legacy_otp_secret.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +# TODO: This file is here for legacy support during devise-two-factor upgrade. +# It should be removed after all records have been migrated. + +module LegacyOtpSecret + extend ActiveSupport::Concern + + private + + # Decrypt and return the `encrypted_otp_secret` attribute which was used in + # prior versions of devise-two-factor + # @return [String] The decrypted OTP secret + def legacy_otp_secret + return nil unless self[:encrypted_otp_secret] + return nil unless self.class.otp_secret_encryption_key + + hmac_iterations = 2000 # a default set by the Encryptor gem + key = self.class.otp_secret_encryption_key + salt = Base64.decode64(encrypted_otp_secret_salt) + iv = Base64.decode64(encrypted_otp_secret_iv) + + raw_cipher_text = Base64.decode64(encrypted_otp_secret) + # The last 16 bytes of the ciphertext are the authentication tag - we use + # Galois Counter Mode which is an authenticated encryption mode + cipher_text = raw_cipher_text[0..-17] + auth_tag = raw_cipher_text[-16..-1] # rubocop:disable Style/SlicingWithRange + + # this alrorithm lifted from + # https://github.com/attr-encrypted/encryptor/blob/master/lib/encryptor.rb#L54 + + # create an OpenSSL object which will decrypt the AES cipher with 256 bit + # keys in Galois Counter Mode (GCM). See + # https://ruby.github.io/openssl/OpenSSL/Cipher.html + cipher = OpenSSL::Cipher.new('aes-256-gcm') + + # tell the cipher we want to decrypt. Symmetric algorithms use a very + # similar process for encryption and decryption, hence the same object can + # do both. + cipher.decrypt + + # Use a Password-Based Key Derivation Function to generate the key actually + # used for encryptoin from the key we got as input. + cipher.key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(key, salt, hmac_iterations, cipher.key_len) + + # set the Initialization Vector (IV) + cipher.iv = iv + + # The tag must be set after calling Cipher#decrypt, Cipher#key= and + # Cipher#iv=, but before calling Cipher#final. After all decryption is + # performed, the tag is verified automatically in the call to Cipher#final. + # + # If the auth_tag does not verify, then #final will raise OpenSSL::Cipher::CipherError + cipher.auth_tag = auth_tag + + # auth_data must be set after auth_tag has been set when decrypting See + # http://ruby-doc.org/stdlib-2.0.0/libdoc/openssl/rdoc/OpenSSL/Cipher.html#method-i-auth_data-3D + # we are not adding any authenticated data but OpenSSL docs say this should + # still be called. + cipher.auth_data = '' + + # #update is (somewhat confusingly named) the method which actually + # performs the decryption on the given chunk of data. Our OTP secret is + # short so we only need to call it once. + # + # It is very important that we call #final because: + # + # 1. The authentication tag is checked during the call to #final + # 2. Block based cipher modes (e.g. CBC) work on fixed size chunks. We need + # to call #final to get it to process the last chunk properly. The output + # of #final should be appended to the decrypted value. This isn't + # required for streaming cipher modes but including it is a best practice + # so that your code will continue to function correctly even if you later + # change to a block cipher mode. + cipher.update(cipher_text) + cipher.final + end +end diff --git a/app/models/concerns/status/threading_concern.rb b/app/models/concerns/status/threading_concern.rb index 2606fd2f27..478a139d63 100644 --- a/app/models/concerns/status/threading_concern.rb +++ b/app/models/concerns/status/threading_concern.rb @@ -3,6 +3,23 @@ module Status::ThreadingConcern extend ActiveSupport::Concern + class_methods do + def permitted_statuses_from_ids(ids, account, stable: false) + statuses = Status.with_accounts(ids).to_a + account_ids = statuses.map(&:account_id).uniq + domains = statuses.filter_map(&:account_domain).uniq + relations = account&.relations_map(account_ids, domains) || {} + + statuses.reject! { |status| StatusFilter.new(status, account, relations).filtered? } + + if stable + statuses.sort_by! { |status| ids.index(status.id) } + else + statuses + end + end + end + def ancestors(limit, account = nil) find_statuses_from_tree_path(ancestor_ids(limit), account) end @@ -12,7 +29,7 @@ module Status::ThreadingConcern end def self_replies(limit) - account.statuses.where(in_reply_to_id: id, visibility: [:public, :unlisted]).reorder(id: :asc).limit(limit) + account.statuses.distributable_visibility.where(in_reply_to_id: id).reorder(id: :asc).limit(limit) end private @@ -76,15 +93,7 @@ module Status::ThreadingConcern end def find_statuses_from_tree_path(ids, account, promote: false) - statuses = Status.with_accounts(ids).to_a - account_ids = statuses.map(&:account_id).uniq - domains = statuses.filter_map(&:account_domain).uniq - relations = account&.relations_map(account_ids, domains) || {} - - statuses.reject! { |status| StatusFilter.new(status, account, relations).filtered? } - - # Order ancestors/descendants by tree path - statuses.sort_by! { |status| ids.index(status.id) } + statuses = Status.permitted_statuses_from_ids(ids, account, stable: true) # Bring self-replies to the top if promote diff --git a/app/models/concerns/user/ldap_authenticable.rb b/app/models/concerns/user/ldap_authenticable.rb index 180df9d310..c8e9fa9348 100644 --- a/app/models/concerns/user/ldap_authenticable.rb +++ b/app/models/concerns/user/ldap_authenticable.rb @@ -22,7 +22,7 @@ module User::LdapAuthenticable safe_username = safe_username.gsub(keys, replacement) end - resource = joins(:account).find_by(accounts: { username: safe_username }) + resource = joins(:account).merge(Account.where(Account.arel_table[:username].lower.eq safe_username.downcase)).take if resource.blank? resource = new( diff --git a/app/models/custom_filter.rb b/app/models/custom_filter.rb index 7c148e877f..bacf158261 100644 --- a/app/models/custom_filter.rb +++ b/app/models/custom_filter.rb @@ -28,6 +28,8 @@ class CustomFilter < ApplicationRecord account ).freeze + EXPIRATION_DURATIONS = [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].freeze + include Expireable include Redisable @@ -42,16 +44,16 @@ class CustomFilter < ApplicationRecord validate :context_must_be_valid normalizes :context, with: ->(context) { context.map(&:strip).filter_map(&:presence) } + scope :unexpired, -> { where(expires_at: nil).or where.not(expires_at: ..Time.zone.now) } before_save :prepare_cache_invalidation! before_destroy :prepare_cache_invalidation! after_commit :invalidate_cache! def expires_in - return @expires_in if defined?(@expires_in) return nil if expires_at.nil? - [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].find { |expires_in| expires_in.from_now >= expires_at } + EXPIRATION_DURATIONS.find { |expires_in| expires_in.from_now >= expires_at } end def irreversible=(value) @@ -66,14 +68,16 @@ class CustomFilter < ApplicationRecord active_filters = Rails.cache.fetch("filters:v3:#{account_id}") do filters_hash = {} - scope = CustomFilterKeyword.includes(:custom_filter).where(custom_filter: { account_id: account_id }).where(Arel.sql('expires_at IS NULL OR expires_at > NOW()')) + scope = CustomFilterKeyword.left_outer_joins(:custom_filter).merge(unexpired.where(account_id: account_id)) + scope.to_a.group_by(&:custom_filter).each do |filter, keywords| keywords.map!(&:to_regex) filters_hash[filter.id] = { keywords: Regexp.union(keywords), filter: filter } end.to_h - scope = CustomFilterStatus.includes(:custom_filter).where(custom_filter: { account_id: account_id }).where(Arel.sql('expires_at IS NULL OR expires_at > NOW()')) + scope = CustomFilterStatus.left_outer_joins(:custom_filter).merge(unexpired.where(account_id: account_id)) + scope.to_a.group_by(&:custom_filter).each do |filter, statuses| filters_hash[filter.id] ||= { filter: filter } filters_hash[filter.id].merge!(status_ids: statuses.map(&:status_id)) diff --git a/app/models/custom_filter_keyword.rb b/app/models/custom_filter_keyword.rb index 979d0b822e..112798b10a 100644 --- a/app/models/custom_filter_keyword.rb +++ b/app/models/custom_filter_keyword.rb @@ -13,16 +13,14 @@ # class CustomFilterKeyword < ApplicationRecord + include CustomFilterCache + belongs_to :custom_filter validates :keyword, presence: true alias_attribute :phrase, :keyword - before_save :prepare_cache_invalidation! - before_destroy :prepare_cache_invalidation! - after_commit :invalidate_cache! - def to_regex if whole_word? /(?mix:#{to_regex_sb}#{Regexp.escape(keyword)}#{to_regex_eb})/ @@ -40,12 +38,4 @@ class CustomFilterKeyword < ApplicationRecord def to_regex_eb /[[:word:]]\z/.match?(keyword) ? '\b' : '' end - - def prepare_cache_invalidation! - custom_filter.prepare_cache_invalidation! - end - - def invalidate_cache! - custom_filter.invalidate_cache! - end end diff --git a/app/models/custom_filter_status.rb b/app/models/custom_filter_status.rb index 0a5650204a..58b61cd79d 100644 --- a/app/models/custom_filter_status.rb +++ b/app/models/custom_filter_status.rb @@ -12,27 +12,17 @@ # class CustomFilterStatus < ApplicationRecord + include CustomFilterCache + belongs_to :custom_filter belongs_to :status validates :status, uniqueness: { scope: :custom_filter } validate :validate_status_access - before_save :prepare_cache_invalidation! - before_destroy :prepare_cache_invalidation! - after_commit :invalidate_cache! - private def validate_status_access errors.add(:status_id, :invalid) unless StatusPolicy.new(custom_filter.account, status).show? end - - def prepare_cache_invalidation! - custom_filter.prepare_cache_invalidation! - end - - def invalidate_cache! - custom_filter.invalidate_cache! - end end diff --git a/app/models/featured_tag.rb b/app/models/featured_tag.rb index ea8aa4787c..cdd97205eb 100644 --- a/app/models/featured_tag.rb +++ b/app/models/featured_tag.rb @@ -74,6 +74,6 @@ class FeaturedTag < ApplicationRecord end def visible_tagged_account_statuses - account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag) + account.statuses.distributable_visibility.tagged_with(tag) end end diff --git a/app/models/feed.rb b/app/models/feed.rb index d9cab2cd1e..27e1280994 100644 --- a/app/models/feed.rb +++ b/app/models/feed.rb @@ -28,7 +28,7 @@ class Feed unhydrated = redis.zrangebyscore(key, "(#{min_id}", "(#{max_id}", limit: [0, limit], with_scores: true).map { |id| id.first.to_i } end - Status.where(id: unhydrated).cache_ids + Status.where(id: unhydrated) end def key diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index 56abd64ed5..f89413c4d5 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -44,6 +44,8 @@ class Form::AdminSettings status_page_url captcha_enabled authorized_fetch + app_icon + favicon reject_pattern reject_blurhash ).freeze @@ -76,6 +78,8 @@ class Form::AdminSettings UPLOAD_KEYS = %i( thumbnail mascot + app_icon + favicon ).freeze PSEUDO_KEYS = %i( diff --git a/app/models/form/import.rb b/app/models/form/import.rb index fc83d9c58c..3cc4af064f 100644 --- a/app/models/form/import.rb +++ b/app/models/form/import.rb @@ -111,12 +111,14 @@ class Form::Import csv_converter = lambda do |field, field_info| case field_info.header when 'Show boosts', 'Notify on new posts', 'Hide notifications' - ActiveModel::Type::Boolean.new.cast(field) + ActiveModel::Type::Boolean.new.cast(field&.downcase) when 'Languages' field&.split(',')&.map(&:strip)&.presence when 'Account address' field.strip.gsub(/\A@/, '') - when '#domain', '#uri', 'List name' + when '#domain' + field&.strip&.downcase + when '#uri', 'List name' field.strip else field diff --git a/app/models/invite.rb b/app/models/invite.rb index c0cbc58458..2fe9f22fbe 100644 --- a/app/models/invite.rb +++ b/app/models/invite.rb @@ -19,12 +19,14 @@ class Invite < ApplicationRecord include Expireable + COMMENT_SIZE_LIMIT = 420 + belongs_to :user, inverse_of: :invites has_many :users, inverse_of: :invite, dependent: nil scope :available, -> { where(expires_at: nil).or(where('expires_at >= ?', Time.now.utc)) } - validates :comment, length: { maximum: 420 } + validates :comment, length: { maximum: COMMENT_SIZE_LIMIT } before_validation :set_code diff --git a/app/models/notification.rb b/app/models/notification.rb index b6b4c208e1..eff983f4e3 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -61,6 +61,9 @@ class Notification < ApplicationRecord severed_relationships: { filterable: false, }.freeze, + moderation_warning: { + filterable: false, + }.freeze, 'admin.sign_up': { filterable: false, }.freeze, @@ -96,6 +99,7 @@ class Notification < ApplicationRecord belongs_to :poll, inverse_of: false belongs_to :report, inverse_of: false belongs_to :account_relationship_severance_event, inverse_of: false + belongs_to :account_warning, inverse_of: false end validates :type, inclusion: { in: TYPES } @@ -198,7 +202,7 @@ class Notification < ApplicationRecord self.from_account_id = activity&.status&.account_id when 'Account' self.from_account_id = activity&.id - when 'AccountRelationshipSeveranceEvent' + when 'AccountRelationshipSeveranceEvent', 'AccountWarning' # These do not really have an originating account, but this is mandatory # in the data model, and the recipient's account will by definition # always exist diff --git a/app/models/preview_card_trend.rb b/app/models/preview_card_trend.rb index da2ea2f8c8..58155971a3 100644 --- a/app/models/preview_card_trend.rb +++ b/app/models/preview_card_trend.rb @@ -15,5 +15,7 @@ class PreviewCardTrend < ApplicationRecord include RankedTrend belongs_to :preview_card + scope :allowed, -> { where(allowed: true) } + scope :not_allowed, -> { where(allowed: false) } end diff --git a/app/models/public_feed.rb b/app/models/public_feed.rb index 6146bf6172..72e6249c39 100644 --- a/app/models/public_feed.rb +++ b/app/models/public_feed.rb @@ -31,7 +31,7 @@ class PublicFeed scope.merge!(media_only_scope) if media_only? scope.merge!(language_scope) if account&.chosen_languages.present? - scope.cache_ids.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id) + scope.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id) end private diff --git a/app/models/relationship_filter.rb b/app/models/relationship_filter.rb index d686f9ed89..828610e46a 100644 --- a/app/models/relationship_filter.rb +++ b/app/models/relationship_filter.rb @@ -114,7 +114,7 @@ class RelationshipFilter def activity_scope(value) case value when 'dormant' - Account.joins(:account_stat).where(account_stat: { last_status_at: [nil, ...1.month.ago] }) + Account.dormant else raise Mastodon::InvalidParameterError, "Unknown activity: #{value}" end diff --git a/app/models/report.rb b/app/models/report.rb index df7e3d2efc..3df5a20e18 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -26,6 +26,8 @@ class Report < ApplicationRecord include Paginable include RateLimitable + COMMENT_SIZE_LIMIT = 1_000 + rate_limit by: :account, family: :reports belongs_to :account @@ -46,7 +48,7 @@ class Report < ApplicationRecord # A report is considered local if the reporter is local delegate :local?, to: :account - validates :comment, length: { maximum: 1_000 }, if: :local? + validates :comment, length: { maximum: COMMENT_SIZE_LIMIT }, if: :local? validates :rule_ids, absence: true, if: -> { (category_changed? || rule_ids_changed?) && !violation? } validate :validate_rule_ids, if: -> { (category_changed? || rule_ids_changed?) && violation? } diff --git a/app/models/report_note.rb b/app/models/report_note.rb index 74b46027e8..b5c40a18b1 100644 --- a/app/models/report_note.rb +++ b/app/models/report_note.rb @@ -13,10 +13,12 @@ # class ReportNote < ApplicationRecord + CONTENT_SIZE_LIMIT = 500 + belongs_to :account belongs_to :report, inverse_of: :notes, touch: true scope :latest, -> { reorder(created_at: :desc) } - validates :content, presence: true, length: { maximum: 500 } + validates :content, presence: true, length: { maximum: CONTENT_SIZE_LIMIT } end diff --git a/app/models/rule.rb b/app/models/rule.rb index f28dc2ffeb..99a36397aa 100644 --- a/app/models/rule.rb +++ b/app/models/rule.rb @@ -15,9 +15,11 @@ class Rule < ApplicationRecord include Discard::Model + TEXT_SIZE_LIMIT = 300 + self.discard_column = :deleted_at - validates :text, presence: true, length: { maximum: 300 } + validates :text, presence: true, length: { maximum: TEXT_SIZE_LIMIT } scope :ordered, -> { kept.order(priority: :asc, id: :asc) } end diff --git a/app/models/site_upload.rb b/app/models/site_upload.rb index 03d472cdb2..6431d1007d 100644 --- a/app/models/site_upload.rb +++ b/app/models/site_upload.rb @@ -19,7 +19,30 @@ class SiteUpload < ApplicationRecord include Attachmentable + FAVICON_SIZES = [16, 32, 48].freeze + APPLE_ICON_SIZES = [57, 60, 72, 76, 114, 120, 144, 152, 167, 180, 1024].freeze + ANDROID_ICON_SIZES = [36, 48, 72, 96, 144, 192, 256, 384, 512].freeze + + APP_ICON_SIZES = (APPLE_ICON_SIZES + ANDROID_ICON_SIZES).uniq.freeze + STYLES = { + app_icon: + APP_ICON_SIZES.to_h do |size| + [:"#{size}", { format: 'png', geometry: "#{size}x#{size}#", file_geometry_parser: FastGeometryParser }] + end.freeze, + + favicon: { + ico: { + format: 'ico', + geometry: '48x48#', + file_geometry_parser: FastGeometryParser, + }.freeze, + }.merge( + FAVICON_SIZES.to_h do |size| + [:"#{size}", { format: 'png', geometry: "#{size}x#{size}#", file_geometry_parser: FastGeometryParser }] + end + ).freeze, + thumbnail: { '@1x': { format: 'png', diff --git a/app/models/status.rb b/app/models/status.rb index 4252aac1e3..ad5911e9ae 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -85,7 +85,7 @@ class Status < ApplicationRecord has_many :local_reblogged, -> { merge(Account.local) }, through: :reblogs, source: :account has_many :local_bookmarked, -> { merge(Account.local) }, through: :bookmarks, source: :account - has_and_belongs_to_many :tags + has_and_belongs_to_many :tags # rubocop:disable Rails/HasAndBelongsToMany has_one :preview_cards_status, inverse_of: :status, dependent: :delete @@ -110,11 +110,13 @@ class Status < ApplicationRecord scope :remote, -> { where(local: false).where.not(uri: nil) } scope :local, -> { where(local: true).or(where(uri: nil)) } scope :with_accounts, ->(ids) { where(id: ids).includes(:account) } - scope :without_replies, -> { where('statuses.reply = FALSE OR statuses.in_reply_to_account_id = statuses.account_id') } + scope :without_replies, -> { not_reply.or(reply_to_account) } + scope :not_reply, -> { where(reply: false) } + scope :reply_to_account, -> { where(arel_table[:in_reply_to_account_id].eq arel_table[:account_id]) } scope :without_reblogs, -> { where(statuses: { reblog_of_id: nil }) } scope :tagged_with, ->(tag_ids) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag_ids }) } scope :not_excluded_by_account, ->(account) { where.not(account_id: account.excluded_from_timeline_account_ids) } - scope :not_domain_blocked_by_account, ->(account) { account.excluded_from_timeline_domains.blank? ? left_outer_joins(:account) : left_outer_joins(:account).where('accounts.domain IS NULL OR accounts.domain NOT IN (?)', account.excluded_from_timeline_domains) } + scope :not_domain_blocked_by_account, ->(account) { account.excluded_from_timeline_domains.blank? ? left_outer_joins(:account) : left_outer_joins(:account).merge(Account.not_domain_blocked_by_account(account)) } scope :tagged_with_all, lambda { |tag_ids| Array(tag_ids).map(&:to_i).reduce(self) do |result, id| result.where(<<~SQL.squish, tag_id: id) @@ -125,6 +127,8 @@ class Status < ApplicationRecord scope :tagged_with_none, lambda { |tag_ids| where('NOT EXISTS (SELECT * FROM statuses_tags forbidden WHERE forbidden.status_id = statuses.id AND forbidden.tag_id IN (?))', tag_ids) } + scope :distributable_visibility, -> { where(visibility: %i(public unlisted)) } + scope :list_eligible_visibility, -> { where(visibility: %i(public unlisted private)) } scope :not_local_only, -> { where(local_only: [false, nil]) } @@ -270,7 +274,7 @@ class Status < ApplicationRecord end def reported? - @reported ||= Report.where(target_account: account).unresolved.exists?(['? = ANY(status_ids)', id]) + @reported ||= account.targeted_reports.unresolved.exists?(['? = ANY(status_ids)', id]) || account.strikes.exists?(['? = ANY(status_ids)', id.to_s]) end def emojis @@ -400,38 +404,6 @@ class Status < ApplicationRecord StatusPin.select('status_id').where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |p, h| h[p.status_id] = true } end - def reload_stale_associations!(cached_items) - account_ids = [] - - cached_items.each do |item| - account_ids << item.account_id - account_ids << item.reblog.account_id if item.reblog? - end - - account_ids.uniq! - - status_ids = cached_items.map { |item| item.reblog? ? item.reblog_of_id : item.id }.uniq - - return if account_ids.empty? - - accounts = Account.where(id: account_ids).includes(:account_stat, :user).index_by(&:id) - - status_stats = StatusStat.where(status_id: status_ids).index_by(&:status_id) - - cached_items.each do |item| - item.account = accounts[item.account_id] - item.reblog.account = accounts[item.reblog.account_id] if item.reblog? - - if item.reblog? - status_stat = status_stats[item.reblog.id] - item.reblog.status_stat = status_stat if status_stat.present? - else - status_stat = status_stats[item.id] - item.status_stat = status_stat if status_stat.present? - end - end - end - def from_text(text) return [] if text.blank? diff --git a/app/models/tag.rb b/app/models/tag.rb index f2168ae904..3f88cb0680 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -21,8 +21,10 @@ class Tag < ApplicationRecord include Paginable + # rubocop:disable Rails/HasAndBelongsToMany has_and_belongs_to_many :statuses has_and_belongs_to_many :accounts + # rubocop:enable Rails/HasAndBelongsToMany has_many :passive_relationships, class_name: 'TagFollow', inverse_of: :tag, dependent: :destroy has_many :featured_tags, dependent: :destroy, inverse_of: :tag @@ -35,7 +37,7 @@ class Tag < ApplicationRecord HASHTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)' HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}" - HASHTAG_RE = %r{(? { where(listable: [true, nil]) } scope :trendable, -> { Setting.trendable_by_default ? where(trendable: [true, nil]) : where(trendable: true) } scope :not_trendable, -> { where(trendable: false) } + scope :suggestions_for_account, ->(account) { recently_used(account).not_featured_by(account) } + scope :not_featured_by, ->(account) { where.not(id: account.featured_tags.select(:tag_id)) } scope :recently_used, lambda { |account| joins(:statuses) .where(statuses: { id: account.statuses.select(:id).limit(RECENT_STATUS_LIMIT) }) diff --git a/app/models/tag_feed.rb b/app/models/tag_feed.rb index 051b0d1306..a4d371e4c1 100644 --- a/app/models/tag_feed.rb +++ b/app/models/tag_feed.rb @@ -34,7 +34,7 @@ class TagFeed < PublicFeed scope.merge!(account_filters_scope) if account? scope.merge!(media_only_scope) if media_only? - scope.cache_ids.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id) + scope.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id) end private diff --git a/app/models/trends/links.rb b/app/models/trends/links.rb index 76e50aa7a2..0650c4109d 100644 --- a/app/models/trends/links.rb +++ b/app/models/trends/links.rb @@ -86,8 +86,8 @@ class Trends::Links < Trends::Base def request_review PreviewCardTrend.pluck('distinct language').flat_map do |language| - score_at_threshold = PreviewCardTrend.where(language: language, allowed: true).by_rank.ranked_below(options[:review_threshold]).first&.score || 0 - preview_card_trends = PreviewCardTrend.where(language: language, allowed: false).joins(:preview_card) + score_at_threshold = PreviewCardTrend.where(language: language).allowed.by_rank.ranked_below(options[:review_threshold]).first&.score || 0 + preview_card_trends = PreviewCardTrend.where(language: language).not_allowed.joins(:preview_card) preview_card_trends.filter_map do |trend| preview_card = trend.preview_card diff --git a/app/models/user.rb b/app/models/user.rb index a082c178e2..3bd87c2817 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -39,6 +39,7 @@ # role_id :bigint(8) # settings :text # time_zone :string +# otp_secret :string # class User < ApplicationRecord @@ -72,6 +73,8 @@ class User < ApplicationRecord devise :two_factor_authenticatable, otp_secret_encryption_key: Rails.configuration.x.otp_secret + include LegacyOtpSecret # Must be after the above `devise` line in order to override the legacy method + devise :two_factor_backupable, otp_number_of_backup_codes: 10 @@ -95,6 +98,8 @@ class User < ApplicationRecord accepts_nested_attributes_for :invite_request, reject_if: ->(attributes) { attributes['text'].blank? && !Setting.require_invite_text } validates :invite_request, presence: true, on: :create, if: :invite_text_required? + validates :email, presence: true, email_address: true + validates_with BlacklistedEmailValidator, if: -> { ENV['EMAIL_DOMAIN_LISTS_APPLY_AFTER_CONFIRMATION'] == 'true' || !confirmed? } validates_with EmailMxValidator, if: :validate_email_dns? validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create @@ -107,14 +112,16 @@ class User < ApplicationRecord validates :confirm_password, absence: true, on: :create validate :validate_role_elevation + scope :account_not_suspended, -> { joins(:account).merge(Account.without_suspended) } scope :recent, -> { order(id: :desc) } scope :pending, -> { where(approved: false) } scope :approved, -> { where(approved: true) } scope :confirmed, -> { where.not(confirmed_at: nil) } scope :enabled, -> { where(disabled: false) } scope :disabled, -> { where(disabled: true) } - scope :inactive, -> { where(arel_table[:current_sign_in_at].lt(ACTIVE_DURATION.ago)) } - scope :active, -> { confirmed.where(arel_table[:current_sign_in_at].gteq(ACTIVE_DURATION.ago)).joins(:account).where(accounts: { suspended_at: nil }) } + scope :active, -> { confirmed.signed_in_recently.account_not_suspended } + scope :signed_in_recently, -> { where(current_sign_in_at: ACTIVE_DURATION.ago..) } + scope :not_signed_in_recently, -> { where(current_sign_in_at: ...ACTIVE_DURATION.ago) } scope :matches_email, ->(value) { where(arel_table[:email].matches("#{value}%")) } scope :matches_ip, ->(value) { left_joins(:ips).where('user_ips.ip <<= ?', value).group('users.id') } @@ -127,11 +134,6 @@ class User < ApplicationRecord normalizes :time_zone, with: ->(time_zone) { ActiveSupport::TimeZone[time_zone].nil? ? nil : time_zone } normalizes :chosen_languages, with: ->(chosen_languages) { chosen_languages.compact_blank.presence } - # This avoids a deprecation warning from Rails 5.1 - # It seems possible that a future release of devise-two-factor will - # handle this itself, and this can be removed from our User class. - attribute :otp_secret - has_many :session_activations, dependent: :destroy delegate :can?, to: :role diff --git a/app/models/user_invite_request.rb b/app/models/user_invite_request.rb index 2b76c88b94..9dd6775166 100644 --- a/app/models/user_invite_request.rb +++ b/app/models/user_invite_request.rb @@ -12,6 +12,8 @@ # class UserInviteRequest < ApplicationRecord + TEXT_SIZE_LIMIT = 420 + belongs_to :user, inverse_of: :invite_request - validates :text, presence: true, length: { maximum: 420 } + validates :text, presence: true, length: { maximum: TEXT_SIZE_LIMIT } end diff --git a/app/models/user_ip.rb b/app/models/user_ip.rb index 87b86a24d4..a6da2c0740 100644 --- a/app/models/user_ip.rb +++ b/app/models/user_ip.rb @@ -15,4 +15,6 @@ class UserIp < ApplicationRecord self.primary_key = :user_id belongs_to :user + + scope :by_latest_used, -> { order(used_at: :desc) } end diff --git a/app/presenters/oauth_metadata_presenter.rb b/app/presenters/oauth_metadata_presenter.rb new file mode 100644 index 0000000000..546503bfcc --- /dev/null +++ b/app/presenters/oauth_metadata_presenter.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +class OauthMetadataPresenter < ActiveModelSerializers::Model + include RoutingHelper + + attributes :issuer, :authorization_endpoint, :token_endpoint, + :revocation_endpoint, :scopes_supported, + :response_types_supported, :response_modes_supported, + :grant_types_supported, :token_endpoint_auth_methods_supported, + :service_documentation, :app_registration_endpoint + + def issuer + root_url + end + + def service_documentation + 'https://docs.joinmastodon.org/' + end + + def authorization_endpoint + oauth_authorization_url + end + + def token_endpoint + oauth_token_url + end + + # As the api_v1_apps route doesn't technically conform to the specification + # for OAuth 2.0 Dynamic Client Registration defined in RFC 7591 we use a + # non-standard property for now to indicate the mastodon specific registration + # endpoint. See: https://datatracker.ietf.org/doc/html/rfc7591 + def app_registration_endpoint + api_v1_apps_url + end + + def revocation_endpoint + oauth_revoke_url + end + + def scopes_supported + doorkeeper.scopes + end + + def response_types_supported + doorkeeper.authorization_response_types + end + + def response_modes_supported + doorkeeper.authorization_response_flows.flat_map(&:response_mode_matches).uniq + end + + def grant_types_supported + grant_types_supported = doorkeeper.grant_flows.dup + grant_types_supported << 'refresh_token' if doorkeeper.refresh_token_enabled? + grant_types_supported + end + + def token_endpoint_auth_methods_supported + %w(client_secret_basic client_secret_post) + end + + private + + def doorkeeper + @doorkeeper ||= Doorkeeper.configuration + end +end diff --git a/app/serializers/manifest_serializer.rb b/app/serializers/manifest_serializer.rb index 1c1f7d0ad5..759490228c 100644 --- a/app/serializers/manifest_serializer.rb +++ b/app/serializers/manifest_serializer.rb @@ -1,21 +1,10 @@ # frozen_string_literal: true class ManifestSerializer < ActiveModel::Serializer + include ApplicationHelper include RoutingHelper include ActionView::Helpers::TextHelper - ICON_SIZES = %w( - 36 - 48 - 72 - 96 - 144 - 192 - 256 - 384 - 512 - ).freeze - attributes :id, :name, :short_name, :icons, :theme_color, :background_color, :display, :start_url, :scope, @@ -37,9 +26,12 @@ class ManifestSerializer < ActiveModel::Serializer end def icons - ICON_SIZES.map do |size| + SiteUpload::ANDROID_ICON_SIZES.map do |size| + src = site_icon_path('app_icon', size.to_i) + src = URI.join(root_url, src).to_s if src.present? + { - src: frontend_asset_url("icons/android-chrome-#{size}x#{size}.png"), + src: src || frontend_asset_url("icons/android-chrome-#{size}x#{size}.png"), sizes: "#{size}x#{size}", type: 'image/png', purpose: 'any maskable', diff --git a/app/serializers/oauth_metadata_serializer.rb b/app/serializers/oauth_metadata_serializer.rb new file mode 100644 index 0000000000..5f3dc7b87e --- /dev/null +++ b/app/serializers/oauth_metadata_serializer.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class OauthMetadataSerializer < ActiveModel::Serializer + attributes :issuer, :authorization_endpoint, :token_endpoint, + :revocation_endpoint, :scopes_supported, + :response_types_supported, :response_modes_supported, + :grant_types_supported, :token_endpoint_auth_methods_supported, + :service_documentation, :app_registration_endpoint +end diff --git a/app/serializers/rest/account_warning_serializer.rb b/app/serializers/rest/account_warning_serializer.rb new file mode 100644 index 0000000000..a0ef341d25 --- /dev/null +++ b/app/serializers/rest/account_warning_serializer.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class REST::AccountWarningSerializer < ActiveModel::Serializer + attributes :id, :action, :text, :status_ids, :created_at + + has_one :target_account, serializer: REST::AccountSerializer + has_one :appeal, serializer: REST::AppealSerializer + + def id + object.id.to_s + end + + def status_ids + object&.status_ids&.map(&:to_s) + end +end diff --git a/app/serializers/rest/announcement_serializer.rb b/app/serializers/rest/announcement_serializer.rb index 8cee271272..d1a011425d 100644 --- a/app/serializers/rest/announcement_serializer.rb +++ b/app/serializers/rest/announcement_serializer.rb @@ -9,7 +9,7 @@ class REST::AnnouncementSerializer < ActiveModel::Serializer attribute :read, if: :current_user? has_many :mentions - has_many :statuses + has_many :statuses, serializer: REST::StatusSerializer has_many :tags, serializer: REST::StatusSerializer::TagSerializer has_many :emojis, serializer: REST::CustomEmojiSerializer has_many :reactions, serializer: REST::ReactionSerializer @@ -49,16 +49,4 @@ class REST::AnnouncementSerializer < ActiveModel::Serializer object.pretty_acct end end - - class StatusSerializer < ActiveModel::Serializer - attributes :id, :url - - def id - object.id.to_s - end - - def url - ActivityPub::TagManager.instance.url_for(object) - end - end end diff --git a/app/serializers/rest/appeal_serializer.rb b/app/serializers/rest/appeal_serializer.rb new file mode 100644 index 0000000000..a24cabc272 --- /dev/null +++ b/app/serializers/rest/appeal_serializer.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class REST::AppealSerializer < ActiveModel::Serializer + attributes :text, :state + + def state + if object.approved? + 'approved' + elsif object.rejected? + 'rejected' + else + 'pending' + end + end +end diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb index ed1115e1b8..c09c84cf33 100644 --- a/app/serializers/rest/instance_serializer.rb +++ b/app/serializers/rest/instance_serializer.rb @@ -54,6 +54,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer accounts: { max_featured_tags: FeaturedTag::LIMIT, + max_pinned_statuses: StatusPinValidator::PIN_LIMIT, }, statuses: { diff --git a/app/serializers/rest/marker_serializer.rb b/app/serializers/rest/marker_serializer.rb index 2eaf3d5076..b0c1c553ff 100644 --- a/app/serializers/rest/marker_serializer.rb +++ b/app/serializers/rest/marker_serializer.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class REST::MarkerSerializer < ActiveModel::Serializer + # Please update `app/javascript/mastodon/api_types/markers.ts` when making changes to the attributes + attributes :last_read_id, :version, :updated_at def last_read_id diff --git a/app/serializers/rest/media_attachment_serializer.rb b/app/serializers/rest/media_attachment_serializer.rb index f27dda832a..90a2a97275 100644 --- a/app/serializers/rest/media_attachment_serializer.rb +++ b/app/serializers/rest/media_attachment_serializer.rb @@ -3,6 +3,8 @@ class REST::MediaAttachmentSerializer < ActiveModel::Serializer include RoutingHelper + # Please update `app/javascript/mastodon/api_types/media_attachments.ts` when making changes to the attributes + attributes :id, :type, :url, :preview_url, :remote_url, :preview_remote_url, :text_url, :meta, :description, :blurhash diff --git a/app/serializers/rest/notification_policy_serializer.rb b/app/serializers/rest/notification_policy_serializer.rb index 4967c3e320..a50ba9e66b 100644 --- a/app/serializers/rest/notification_policy_serializer.rb +++ b/app/serializers/rest/notification_policy_serializer.rb @@ -9,8 +9,8 @@ class REST::NotificationPolicySerializer < ActiveModel::Serializer def summary { - pending_requests_count: object.pending_requests_count.to_s, - pending_notifications_count: object.pending_notifications_count.to_s, + pending_requests_count: object.pending_requests_count.to_i, + pending_notifications_count: object.pending_notifications_count.to_i, } end end diff --git a/app/serializers/rest/notification_serializer.rb b/app/serializers/rest/notification_serializer.rb index 45205786e3..01a789eb14 100644 --- a/app/serializers/rest/notification_serializer.rb +++ b/app/serializers/rest/notification_serializer.rb @@ -7,6 +7,7 @@ class REST::NotificationSerializer < ActiveModel::Serializer belongs_to :target_status, key: :status, if: :status_type?, serializer: REST::StatusSerializer belongs_to :report, if: :report_type?, serializer: REST::ReportSerializer belongs_to :account_relationship_severance_event, key: :event, if: :relationship_severance_event?, serializer: REST::AccountRelationshipSeveranceEventSerializer + belongs_to :account_warning, key: :moderation_warning, if: :moderation_warning_event?, serializer: REST::AccountWarningSerializer def id object.id.to_s @@ -23,4 +24,8 @@ class REST::NotificationSerializer < ActiveModel::Serializer def relationship_severance_event? object.type == :severed_relationships end + + def moderation_warning_event? + object.type == :moderation_warning + end end diff --git a/app/serializers/rest/poll_serializer.rb b/app/serializers/rest/poll_serializer.rb index df6ebd0d44..6e00060735 100644 --- a/app/serializers/rest/poll_serializer.rb +++ b/app/serializers/rest/poll_serializer.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class REST::PollSerializer < ActiveModel::Serializer + # Please update `app/javascript/mastodon/api_types/polls.ts` when making changes to the attributes + attributes :id, :expires_at, :expired, :multiple, :votes_count, :voters_count diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index ae10dd8268..e3e8fde3b0 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -3,6 +3,8 @@ class REST::StatusSerializer < ActiveModel::Serializer include FormattingHelper + # Please update `app/javascript/mastodon/api_types/statuses.ts` when making changes to the attributes + attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id, :sensitive, :spoiler_text, :visibility, :language, :uri, :url, :replies_count, :reblogs_count, diff --git a/app/services/after_block_domain_from_account_service.rb b/app/services/after_block_domain_from_account_service.rb index adb17845cc..fc5dc65681 100644 --- a/app/services/after_block_domain_from_account_service.rb +++ b/app/services/after_block_domain_from_account_service.rb @@ -12,6 +12,7 @@ class AfterBlockDomainFromAccountService < BaseService @domain_block_event = nil clear_notifications! + clear_notification_permissions! remove_follows! reject_existing_followers! reject_pending_follow_requests! @@ -31,6 +32,10 @@ class AfterBlockDomainFromAccountService < BaseService Notification.where(account: @account).where(from_account: Account.where(domain: @domain)).in_batches.delete_all end + def clear_notification_permissions! + NotificationPermission.where(account: @account, from_account: Account.where(domain: @domain)).in_batches.delete_all + end + def reject_existing_followers! @account.passive_relationships.where(account: Account.where(domain: @domain)).includes(:account).reorder(nil).in_batches do |follows| domain_block_event.import_from_passive_follows!(follows) diff --git a/app/services/block_service.rb b/app/services/block_service.rb index 266a0f4b9d..98229d98c0 100644 --- a/app/services/block_service.rb +++ b/app/services/block_service.rb @@ -10,6 +10,8 @@ class BlockService < BaseService UnfollowService.new.call(target_account, account) if target_account.following?(account) RejectFollowService.new.call(target_account, account) if target_account.requested?(account) + NotificationPermission.where(account: account, from_account: target_account).destroy_all + block = account.block!(target_account) BlockWorker.perform_async(account.id, target_account.id) diff --git a/app/services/bulk_import_row_service.rb b/app/services/bulk_import_row_service.rb index ef4c18e789..26909dfe04 100644 --- a/app/services/bulk_import_row_service.rb +++ b/app/services/bulk_import_row_service.rb @@ -10,7 +10,7 @@ class BulkImportRowService when :following, :blocking, :muting, :lists target_acct = @data['acct'] target_domain = domain(target_acct) - @target_account = stoplight_wrap_request(target_domain) { ResolveAccountService.new.call(target_acct, { check_delivery_availability: true }) } + @target_account = stoplight_wrapper(target_domain).run { ResolveAccountService.new.call(target_acct, { check_delivery_availability: true }) } return false if @target_account.nil? when :bookmarks target_uri = @data['uri'] @@ -18,7 +18,7 @@ class BulkImportRowService @target_status = ActivityPub::TagManager.instance.uri_to_resource(target_uri, Status) return false if @target_status.nil? && ActivityPub::TagManager.instance.local_uri?(target_uri) - @target_status ||= stoplight_wrap_request(target_domain) { ActivityPub::FetchRemoteStatusService.new.call(target_uri) } + @target_status ||= stoplight_wrapper(target_domain).run { ActivityPub::FetchRemoteStatusService.new.call(target_uri) } return false if @target_status.nil? end @@ -51,16 +51,15 @@ class BulkImportRowService TagManager.instance.local_domain?(domain) ? nil : TagManager.instance.normalize_domain(domain) end - def stoplight_wrap_request(domain, &block) + def stoplight_wrapper(domain) if domain.present? - Stoplight("source:#{domain}", &block) + Stoplight("source:#{domain}") .with_fallback { nil } .with_threshold(1) .with_cool_off_time(5.minutes.seconds) .with_error_handler { |error, handle| error.is_a?(HTTP::Error) || error.is_a?(OpenSSL::SSL::SSLError) ? handle.call(error) : raise(error) } - .run else - yield + Stoplight('domain-blank') end end end diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index c83e4c017f..e56562c0a5 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -9,6 +9,7 @@ class NotifyService < BaseService update poll status + moderation_warning # TODO: this probably warrants an email notification severed_relationships ).freeze @@ -22,7 +23,7 @@ class NotifyService < BaseService def dismiss? blocked = @recipient.unavailable? - blocked ||= from_self? && @notification.type != :poll && @notification.type != :severed_relationships + blocked ||= from_self? && %i(poll severed_relationships moderation_warning).exclude?(@notification.type) return blocked if message? && from_staff? @@ -75,6 +76,7 @@ class NotifyService < BaseService admin.report poll update + account_warning ).freeze def initialize(notification) diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 964ca91a67..64bf28bdff 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -176,7 +176,7 @@ class PostStatusService < BaseService def idempotency_duplicate if scheduled? - @account.schedule_statuses.find(@idempotency_duplicate) + @account.scheduled_statuses.find(@idempotency_duplicate) else @account.statuses.find(@idempotency_duplicate) end @@ -228,7 +228,7 @@ class PostStatusService < BaseService end def scheduled_options - @options.tap do |options_hash| + @options.dup.tap do |options_hash| options_hash[:in_reply_to_id] = options_hash.delete(:thread)&.id options_hash[:application_id] = options_hash.delete(:application)&.id options_hash[:scheduled_at] = nil diff --git a/app/validators/email_address_validator.rb b/app/validators/email_address_validator.rb new file mode 100644 index 0000000000..ed0bb11652 --- /dev/null +++ b/app/validators/email_address_validator.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# NOTE: I initially wrote this as `EmailValidator` but it ended up clashing +# with an indirect dependency of ours, `validate_email`, which, turns out, +# has the same approach as we do, but with an extra check disallowing +# single-label domains. Decided to not switch to `validate_email` because +# we do want to allow at least `localhost`. + +class EmailAddressValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + value = value.strip + + address = Mail::Address.new(value) + record.errors.add(attribute, :invalid) if address.address != value + rescue Mail::Field::FieldError + record.errors.add(attribute, :invalid) + end +end diff --git a/app/views/admin/account_warnings/_account_warning.html.haml b/app/views/admin/account_warnings/_account_warning.html.haml index 5702e4f6d2..368e69e63e 100644 --- a/app/views/admin/account_warnings/_account_warning.html.haml +++ b/app/views/admin/account_warnings/_account_warning.html.haml @@ -2,7 +2,7 @@ .log-entry__header .log-entry__avatar .indicator-icon{ class: account_warning.overruled? ? 'success' : 'failure' } - = fa_icon 'warning' + = material_symbol 'warning' .log-entry__content .log-entry__title = t(account_warning.action, diff --git a/app/views/admin/accounts/_local_account.html.haml b/app/views/admin/accounts/_local_account.html.haml index 82197cda43..3ed392cd1a 100644 --- a/app/views/admin/accounts/_local_account.html.haml +++ b/app/views/admin/accounts/_local_account.html.haml @@ -62,13 +62,7 @@ %td %time.formatted{ datetime: account.created_at.iso8601, title: l(account.created_at) }= l account.created_at %td -- recent_ips = account.user.ips.order(used_at: :desc).to_a -- recent_ips.each_with_index do |recent_ip, i| - %tr - - if i.zero? - %th{ rowspan: recent_ips.size }= t('admin.accounts.most_recent_ip') - %td= recent_ip.ip - %td= table_link_to 'search', t('admin.accounts.search_same_ip'), admin_accounts_path(ip: recent_ip.ip) + = render partial: 'admin/accounts/user_ip', collection: account.user.ips.by_latest_used %tr %th= t('admin.accounts.most_recent_activity') %td diff --git a/app/views/admin/accounts/_remote_account.html.haml b/app/views/admin/accounts/_remote_account.html.haml index 99996e1d46..6755af2496 100644 --- a/app/views/admin/accounts/_remote_account.html.haml +++ b/app/views/admin/accounts/_remote_account.html.haml @@ -2,14 +2,14 @@ %th= t('admin.accounts.inbox_url') %td = account.inbox_url - = fa_icon DeliveryFailureTracker.available?(account.inbox_url) ? 'check' : 'times' + = material_symbol DeliveryFailureTracker.available?(account.inbox_url) ? 'check' : 'close' %td = table_link_to 'search', domain_block.present? ? t('admin.domain_blocks.view') : t('admin.accounts.view_domain'), admin_instance_path(account.domain) %tr %th= t('admin.accounts.shared_inbox_url') %td = account.shared_inbox_url - = fa_icon DeliveryFailureTracker.available?(account.shared_inbox_url) ? 'check' : 'times' + = material_symbol DeliveryFailureTracker.available?(account.shared_inbox_url) ? 'check' : 'close' %td - if domain_block.nil? = table_link_to 'ban', t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: account.domain) diff --git a/app/views/admin/accounts/_user_ip.html.haml b/app/views/admin/accounts/_user_ip.html.haml new file mode 100644 index 0000000000..1938cf7edf --- /dev/null +++ b/app/views/admin/accounts/_user_ip.html.haml @@ -0,0 +1,5 @@ +%tr + - if user_ip_iteration.first? + %th{ rowspan: user_ip_iteration.size }= t('admin.accounts.most_recent_ip') + %td= user_ip.ip + %td= table_link_to 'search', t('admin.accounts.search_same_ip'), admin_accounts_path(ip: user_ip.ip) diff --git a/app/views/admin/accounts/index.html.haml b/app/views/admin/accounts/index.html.haml index 0ca457f39e..01b072938d 100644 --- a/app/views/admin/accounts/index.html.haml +++ b/app/views/admin/accounts/index.html.haml @@ -53,19 +53,19 @@ = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - if @accounts.any?(&:user_pending?) - = f.button safe_join([fa_icon('check'), t('admin.accounts.approve')]), + = f.button safe_join([material_symbol('check'), t('admin.accounts.approve')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :approve, type: :submit - = f.button safe_join([fa_icon('times'), t('admin.accounts.reject')]), + = f.button safe_join([material_symbol('close'), t('admin.accounts.reject')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :reject, type: :submit - = f.button safe_join([fa_icon('lock'), t('admin.accounts.perform_full_suspension')]), + = f.button safe_join([material_symbol('lock'), t('admin.accounts.perform_full_suspension')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :suspend, diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index d380d807a3..41fcafa29d 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -20,7 +20,7 @@ %dd{ title: field.value, class: custom_field_classes(field) } - if field.verified? %span.verified__mark{ title: t('accounts.link_verified_on', date: l(field.verified_at)) } - = fa_icon 'check' + = material_symbol 'check' = prerender_custom_emojis(account_field_value_format(field, with_rel_me: false), account.emojis) - if account.note.present? diff --git a/app/views/admin/announcements/_form.html.haml b/app/views/admin/announcements/_form.html.haml new file mode 100644 index 0000000000..3a9b371907 --- /dev/null +++ b/app/views/admin/announcements/_form.html.haml @@ -0,0 +1,28 @@ +.fields-group + = form.input :starts_at, + html5: true, + include_blank: true, + input_html: { pattern: datetime_pattern, placeholder: datetime_placeholder }, + wrapper: :with_block_label + = form.input :ends_at, + html5: true, + include_blank: true, + input_html: { pattern: datetime_pattern, placeholder: datetime_placeholder }, + wrapper: :with_block_label + +.fields-group + = form.input :all_day, + as: :boolean, + wrapper: :with_label + +.fields-group + = form.input :text, + wrapper: :with_block_label + +- unless form.object.published? + .fields-group + = form.input :scheduled_at, + html5: true, + include_blank: true, + input_html: { pattern: datetime_pattern, placeholder: datetime_placeholder }, + wrapper: :with_block_label diff --git a/app/views/admin/announcements/edit.html.haml b/app/views/admin/announcements/edit.html.haml index 23c568a885..8cec7d36c2 100644 --- a/app/views/admin/announcements/edit.html.haml +++ b/app/views/admin/announcements/edit.html.haml @@ -1,37 +1,12 @@ - content_for :page_title do = t('.title') -= simple_form_for @announcement, url: admin_announcement_path(@announcement), html: { novalidate: false } do |f| += simple_form_for @announcement, url: admin_announcement_path(@announcement), html: { novalidate: false } do |form| = render 'shared/error_messages', object: @announcement - .fields-group - = f.input :starts_at, - html5: true, - include_blank: true, - input_html: { pattern: datetime_pattern, placeholder: datetime_placeholder }, - wrapper: :with_block_label - = f.input :ends_at, - html5: true, - include_blank: true, - input_html: { pattern: datetime_pattern, placeholder: datetime_placeholder }, - wrapper: :with_block_label - - .fields-group - = f.input :all_day, - as: :boolean, - wrapper: :with_label - - .fields-group - = f.input :text, - wrapper: :with_block_label - - - unless @announcement.published? - .fields-group - = f.input :scheduled_at, - html5: true, - include_blank: true, - input_html: { pattern: datetime_pattern, placeholder: datetime_placeholder }, - wrapper: :with_block_label + = render form .actions - = f.button :button, t('generic.save_changes'), type: :submit + = form.button :button, + t('generic.save_changes'), + type: :submit diff --git a/app/views/admin/announcements/new.html.haml b/app/views/admin/announcements/new.html.haml index a681ed789e..266ca65e80 100644 --- a/app/views/admin/announcements/new.html.haml +++ b/app/views/admin/announcements/new.html.haml @@ -1,38 +1,12 @@ - content_for :page_title do = t('.title') -= simple_form_for @announcement, url: admin_announcements_path, html: { novalidate: false } do |f| += simple_form_for @announcement, url: admin_announcements_path, html: { novalidate: false } do |form| = render 'shared/error_messages', object: @announcement - .fields-group - = f.input :starts_at, - html5: true, - include_blank: true, - input_html: { pattern: datetime_pattern, placeholder: datetime_placeholder }, - wrapper: :with_block_label - = f.input :ends_at, - html5: true, - include_blank: true, - input_html: { pattern: datetime_pattern, placeholder: datetime_placeholder }, - wrapper: :with_block_label - - .fields-group - = f.input :all_day, - as: :boolean, - wrapper: :with_label - - .fields-group - = f.input :text, - wrapper: :with_block_label - - .fields-group - = f.input :scheduled_at, - html5: true, - include_blank: true, - input_html: { pattern: datetime_pattern, placeholder: datetime_placeholder }, - wrapper: :with_block_label + = render form .actions - = f.button :button, - t('.create'), - type: :submit + = form.button :button, + t('.create'), + type: :submit diff --git a/app/views/admin/custom_emojis/index.html.haml b/app/views/admin/custom_emojis/index.html.haml index bea6a7cd21..e87dd41282 100644 --- a/app/views/admin/custom_emojis/index.html.haml +++ b/app/views/admin/custom_emojis/index.html.haml @@ -48,19 +48,19 @@ - if params[:local] == '1' = f.button safe_join([fa_icon('save'), t('generic.save_changes')]), name: :update, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - = f.button safe_join([fa_icon('eye'), t('admin.custom_emojis.list')]), name: :list, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([material_symbol('visibility'), t('admin.custom_emojis.list')]), name: :list, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - = f.button safe_join([fa_icon('eye-slash'), t('admin.custom_emojis.unlist')]), name: :unlist, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([material_symbol('visibility_off'), t('admin.custom_emojis.unlist')]), name: :unlist, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } = f.button safe_join([fa_icon('power-off'), t('admin.custom_emojis.enable')]), name: :enable, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } = f.button safe_join([fa_icon('power-off'), t('admin.custom_emojis.disable')]), name: :disable, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - if can?(:destroy, :custom_emoji) - = f.button safe_join([fa_icon('times'), t('admin.custom_emojis.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([material_symbol('close'), t('admin.custom_emojis.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - if can?(:copy, :custom_emoji) && params[:local] != '1' - = f.button safe_join([fa_icon('copy'), t('admin.custom_emojis.copy')]), name: :copy, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([material_symbol('content_copy'), t('admin.custom_emojis.copy')]), name: :copy, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - if params[:local] == '1' .batch-table__form.simple_form diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 8a80992785..8430dd3c4f 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -57,19 +57,19 @@ .dashboard__item = link_to admin_reports_path, class: 'dashboard__quick-access' do %span= t('admin.dashboard.pending_reports_html', count: @pending_reports_count) - = fa_icon 'chevron-right fw' + = material_symbol 'chevron_right' = link_to admin_accounts_path(status: 'pending'), class: 'dashboard__quick-access' do %span= t('admin.dashboard.pending_users_html', count: @pending_users_count) - = fa_icon 'chevron-right fw' + = material_symbol 'chevron_right' = link_to admin_trends_tags_path(status: 'pending_review'), class: 'dashboard__quick-access' do %span= t('admin.dashboard.pending_tags_html', count: @pending_tags_count) - = fa_icon 'chevron-right fw' + = material_symbol 'chevron_right' = link_to admin_disputes_appeals_path(status: 'pending'), class: 'dashboard__quick-access' do %span= t('admin.dashboard.pending_appeals_html', count: @pending_appeals_count) - = fa_icon 'chevron-right fw' + = material_symbol 'chevron_right' .dashboard__item = react_admin_component :dimension, dimension: 'sources', diff --git a/app/views/admin/domain_blocks/_form.html.haml b/app/views/admin/domain_blocks/_form.html.haml new file mode 100644 index 0000000000..f1785a1fdd --- /dev/null +++ b/app/views/admin/domain_blocks/_form.html.haml @@ -0,0 +1,46 @@ +.fields-row + .fields-row__column.fields-row__column-6.fields-group + = form.input :domain, + disabled: form.object.persisted?, + hint: t('admin.domain_blocks.new.hint'), + label: t('admin.domain_blocks.domain'), + readonly: form.object.persisted?, + required: true, + wrapper: :with_label + .fields-row__column.fields-row__column-6.fields-group + = form.input :severity, + collection: DomainBlock.severities.keys, + hint: t('admin.domain_blocks.new.severity.desc_html'), + include_blank: false, + label_method: ->(type) { t("admin.domain_blocks.new.severity.#{type}") }, + wrapper: :with_label +.fields-group + = form.input :reject_media, + as: :boolean, + hint: I18n.t('admin.domain_blocks.reject_media_hint'), + label: I18n.t('admin.domain_blocks.reject_media'), + wrapper: :with_label +.fields-group + = form.input :reject_reports, + as: :boolean, + hint: I18n.t('admin.domain_blocks.reject_reports_hint'), + label: I18n.t('admin.domain_blocks.reject_reports'), + wrapper: :with_label +.fields-group + = form.input :obfuscate, + as: :boolean, + hint: I18n.t('admin.domain_blocks.obfuscate_hint'), + label: I18n.t('admin.domain_blocks.obfuscate'), + wrapper: :with_label +.field-group + = form.input :private_comment, + as: :string, + hint: t('admin.domain_blocks.private_comment_hint'), + label: I18n.t('admin.domain_blocks.private_comment'), + wrapper: :with_label +.field-group + = form.input :public_comment, + as: :string, + hint: t('admin.domain_blocks.public_comment_hint'), + label: I18n.t('admin.domain_blocks.public_comment'), + wrapper: :with_label diff --git a/app/views/admin/domain_blocks/edit.html.haml b/app/views/admin/domain_blocks/edit.html.haml index 7c0a9823a0..40c46b8b6b 100644 --- a/app/views/admin/domain_blocks/edit.html.haml +++ b/app/views/admin/domain_blocks/edit.html.haml @@ -1,63 +1,12 @@ - content_for :page_title do = t('admin.domain_blocks.edit') -= simple_form_for @domain_block, url: admin_domain_block_path(@domain_block), method: :put do |f| += simple_form_for @domain_block, url: admin_domain_block_path(@domain_block), method: :put do |form| = render 'shared/error_messages', object: @domain_block - .fields-row - .fields-row__column.fields-row__column-6.fields-group - = f.input :domain, - disabled: true, - hint: t('admin.domain_blocks.new.hint'), - label: t('admin.domain_blocks.domain'), - readonly: true, - required: true, - wrapper: :with_label - - .fields-row__column.fields-row__column-6.fields-group - = f.input :severity, - collection: DomainBlock.severities.keys, - hint: t('admin.domain_blocks.new.severity.desc_html'), - include_blank: false, - label_method: ->(type) { t("admin.domain_blocks.new.severity.#{type}") }, - wrapper: :with_label - - .fields-group - = f.input :reject_media, - as: :boolean, - hint: I18n.t('admin.domain_blocks.reject_media_hint'), - label: I18n.t('admin.domain_blocks.reject_media'), - wrapper: :with_label - - .fields-group - = f.input :reject_reports, - as: :boolean, - hint: I18n.t('admin.domain_blocks.reject_reports_hint'), - label: I18n.t('admin.domain_blocks.reject_reports'), - wrapper: :with_label - - .fields-group - = f.input :obfuscate, - as: :boolean, - hint: I18n.t('admin.domain_blocks.obfuscate_hint'), - label: I18n.t('admin.domain_blocks.obfuscate'), - wrapper: :with_label - - .field-group - = f.input :private_comment, - as: :string, - hint: t('admin.domain_blocks.private_comment_hint'), - label: I18n.t('admin.domain_blocks.private_comment'), - wrapper: :with_label - - .field-group - = f.input :public_comment, - as: :string, - hint: t('admin.domain_blocks.public_comment_hint'), - label: I18n.t('admin.domain_blocks.public_comment'), - wrapper: :with_label + = render form .actions - = f.button :button, - t('generic.save_changes'), - type: :submit + = form.button :button, + t('generic.save_changes'), + type: :submit diff --git a/app/views/admin/domain_blocks/new.html.haml b/app/views/admin/domain_blocks/new.html.haml index 6a9855529f..78bcfcba8e 100644 --- a/app/views/admin/domain_blocks/new.html.haml +++ b/app/views/admin/domain_blocks/new.html.haml @@ -1,61 +1,12 @@ - content_for :page_title do = t('.title') -= simple_form_for @domain_block, url: admin_domain_blocks_path do |f| += simple_form_for @domain_block, url: admin_domain_blocks_path do |form| = render 'shared/error_messages', object: @domain_block - .fields-row - .fields-row__column.fields-row__column-6.fields-group - = f.input :domain, - hint: t('.hint'), - label: t('admin.domain_blocks.domain'), - required: true, - wrapper: :with_label - - .fields-row__column.fields-row__column-6.fields-group - = f.input :severity, - collection: DomainBlock.severities.keys, - hint: t('.severity.desc_html'), - include_blank: false, - label_method: ->(type) { t(".severity.#{type}") }, - wrapper: :with_label - - .fields-group - = f.input :reject_media, - as: :boolean, - hint: I18n.t('admin.domain_blocks.reject_media_hint'), - label: I18n.t('admin.domain_blocks.reject_media'), - wrapper: :with_label - - .fields-group - = f.input :reject_reports, - as: :boolean, - hint: I18n.t('admin.domain_blocks.reject_reports_hint'), - label: I18n.t('admin.domain_blocks.reject_reports'), - wrapper: :with_label - - .fields-group - = f.input :obfuscate, - as: :boolean, - hint: I18n.t('admin.domain_blocks.obfuscate_hint'), - label: I18n.t('admin.domain_blocks.obfuscate'), - wrapper: :with_label - - .field-group - = f.input :private_comment, - as: :string, - hint: t('admin.domain_blocks.private_comment_hint'), - label: I18n.t('admin.domain_blocks.private_comment'), - wrapper: :with_label - - .field-group - = f.input :public_comment, - as: :string, - hint: t('admin.domain_blocks.public_comment_hint'), - label: I18n.t('admin.domain_blocks.public_comment'), - wrapper: :with_label + = render form .actions - = f.button :button, - t('.create'), - type: :submit + = form.button :button, + t('.create'), + type: :submit diff --git a/app/views/admin/email_domain_blocks/index.html.haml b/app/views/admin/email_domain_blocks/index.html.haml index 59036f899f..684735c207 100644 --- a/app/views/admin/email_domain_blocks/index.html.haml +++ b/app/views/admin/email_domain_blocks/index.html.haml @@ -12,7 +12,7 @@ %label.batch-table__toolbar__select.batch-checkbox-all = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - = f.button safe_join([fa_icon('times'), t('admin.email_domain_blocks.delete')]), + = f.button safe_join([material_symbol('close'), t('admin.email_domain_blocks.delete')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :delete, diff --git a/app/views/admin/export_domain_blocks/_domain_block.html.haml b/app/views/admin/export_domain_blocks/_domain_block.html.haml index cdce4fd28a..79cc5595ca 100644 --- a/app/views/admin/export_domain_blocks/_domain_block.html.haml +++ b/app/views/admin/export_domain_blocks/_domain_block.html.haml @@ -23,5 +23,5 @@ = f.object.public_comment - if existing_relationships · - = fa_icon 'warning fw' + = material_symbol 'warning' = t('admin.export_domain_blocks.import.existing_relationships_warning') diff --git a/app/views/admin/export_domain_blocks/import.html.haml b/app/views/admin/export_domain_blocks/import.html.haml index 48016a9abe..52ffc3d465 100644 --- a/app/views/admin/export_domain_blocks/import.html.haml +++ b/app/views/admin/export_domain_blocks/import.html.haml @@ -12,7 +12,7 @@ %label.batch-table__toolbar__select.batch-checkbox-all = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - = f.button safe_join([fa_icon('copy'), t('admin.domain_blocks.import')]), + = f.button safe_join([material_symbol('content_copy'), t('admin.domain_blocks.import')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :save, diff --git a/app/views/admin/follow_recommendations/show.html.haml b/app/views/admin/follow_recommendations/show.html.haml index 9d23f9ba51..c8ad653a88 100644 --- a/app/views/admin/follow_recommendations/show.html.haml +++ b/app/views/admin/follow_recommendations/show.html.haml @@ -31,13 +31,13 @@ = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - if params[:status].blank? && can?(:suppress, :follow_recommendation) - = f.button safe_join([fa_icon('times'), t('admin.follow_recommendations.suppress')]), + = f.button safe_join([material_symbol('close'), t('admin.follow_recommendations.suppress')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :suppress, type: :submit - if params[:status] == 'suppressed' && can?(:unsuppress, :follow_recommendation) - = f.button safe_join([fa_icon('plus'), t('admin.follow_recommendations.unsuppress')]), + = f.button safe_join([material_symbol('add'), t('admin.follow_recommendations.unsuppress')]), class: 'table-action-link', name: :unsuppress, type: :submit diff --git a/app/views/admin/instances/show.html.haml b/app/views/admin/instances/show.html.haml index 5bf4e899f3..d916203d0c 100644 --- a/app/views/admin/instances/show.html.haml +++ b/app/views/admin/instances/show.html.haml @@ -9,7 +9,7 @@ - if @instance.persisted? %p - = fa_icon 'info fw' + = material_symbol 'info' = t('admin.instances.totals_time_period_hint_html') .dashboard diff --git a/app/views/admin/invites/_invite.html.haml b/app/views/admin/invites/_invite.html.haml index e6ad9de34c..f9cd6003f3 100644 --- a/app/views/admin/invites/_invite.html.haml +++ b/app/views/admin/invites/_invite.html.haml @@ -12,7 +12,7 @@ - if invite.valid_for_use? %td - = fa_icon 'user fw' + = material_symbol 'person' = invite.uses = " / #{invite.max_uses}" unless invite.max_uses.nil? %td diff --git a/app/views/admin/ip_blocks/index.html.haml b/app/views/admin/ip_blocks/index.html.haml index f1d2b3dc47..9eba6c68ff 100644 --- a/app/views/admin/ip_blocks/index.html.haml +++ b/app/views/admin/ip_blocks/index.html.haml @@ -14,7 +14,7 @@ = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - if can?(:destroy, :ip_block) - = f.button safe_join([fa_icon('times'), t('admin.ip_blocks.delete')]), + = f.button safe_join([material_symbol('close'), t('admin.ip_blocks.delete')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :delete, diff --git a/app/views/admin/relationships/index.html.haml b/app/views/admin/relationships/index.html.haml index 8260430d80..c2daefb424 100644 --- a/app/views/admin/relationships/index.html.haml +++ b/app/views/admin/relationships/index.html.haml @@ -19,7 +19,7 @@ .back-link = link_to admin_account_path(@account.id) do - = fa_icon 'chevron-left fw' + = material_symbol 'chevron_left' = t('admin.statuses.back_to_account') %hr.spacer/ @@ -30,7 +30,7 @@ %label.batch-table__toolbar__select.batch-checkbox-all = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - = f.button safe_join([fa_icon('lock'), t('admin.accounts.perform_full_suspension')]), + = f.button safe_join([material_symbol('lock'), t('admin.accounts.perform_full_suspension')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :suspend, diff --git a/app/views/admin/relays/_relay.html.haml b/app/views/admin/relays/_relay.html.haml index f1dd2b2dd1..0960124ccf 100644 --- a/app/views/admin/relays/_relay.html.haml +++ b/app/views/admin/relays/_relay.html.haml @@ -4,7 +4,7 @@ %td - if relay.accepted? %span.positive-hint - = fa_icon('check') + = material_symbol('check')   = t 'admin.relays.enabled' - elsif relay.pending? @@ -13,7 +13,7 @@ = t 'admin.relays.pending' - else %span.negative-hint - = fa_icon('times') + = material_symbol('close')   = t 'admin.relays.disabled' %td diff --git a/app/views/admin/reports/_header_card.html.haml b/app/views/admin/reports/_header_card.html.haml index e90e3f9c90..52e62b4499 100644 --- a/app/views/admin/reports/_header_card.html.haml +++ b/app/views/admin/reports/_header_card.html.haml @@ -16,7 +16,7 @@ %strong.emojify.p-name= display_name(report.target_account, custom_emojify: true) %span = acct(report.target_account) - = fa_icon('lock') if report.target_account.locked? + = material_symbol('lock') if report.target_account.locked? - if report.target_account.note.present? .account-card__bio.emojify = prerender_custom_emojis(account_bio_format(report.target_account), report.target_account.emojis) diff --git a/app/views/admin/reports/_status.html.haml b/app/views/admin/reports/_status.html.haml index 3775a1101c..66820f0a6e 100644 --- a/app/views/admin/reports/_status.html.haml +++ b/app/views/admin/reports/_status.html.haml @@ -37,5 +37,5 @@ = t("statuses.visibilities.#{status.visibility}") - if status.proper.sensitive? · - = fa_icon('eye-slash fw') + = material_symbol('visibility_off') = t('stream_entries.sensitive_content') diff --git a/app/views/admin/reports/actions/preview.html.haml b/app/views/admin/reports/actions/preview.html.haml index 8634bb215c..7a737d4f72 100644 --- a/app/views/admin/reports/actions/preview.html.haml +++ b/app/views/admin/reports/actions/preview.html.haml @@ -58,7 +58,7 @@ - status.ordered_media_attachments.each do |media_attachment| %abbr{ title: media_attachment.description } - = fa_icon 'link' + = material_symbol 'link' = media_attachment.file_file_name .strike-card__statuses-list__item__meta = link_to ActivityPub::TagManager.instance.url_for(status), target: '_blank', rel: 'noopener noreferrer' do diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index e37fa2590a..c880021cff 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -41,7 +41,7 @@ %p = t 'admin.reports.statuses_description_html' — - = link_to safe_join([fa_icon('plus'), t('admin.reports.add_to_report')]), + = link_to safe_join([material_symbol('add'), t('admin.reports.add_to_report')]), admin_account_statuses_path(@report.target_account_id, report_id: @report.id), class: 'table-action-link' @@ -52,7 +52,7 @@ = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - if !@statuses.empty? && @report.unresolved? - = f.button safe_join([fa_icon('times'), t('admin.statuses.batch.remove_from_report')]), name: :remove_from_report, class: 'table-action-link', type: :submit + = f.button safe_join([material_symbol('close'), t('admin.statuses.batch.remove_from_report')]), name: :remove_from_report, class: 'table-action-link', type: :submit .batch-table__body - if @statuses.empty? = nothing_here 'nothing-here--under-tabs' diff --git a/app/views/admin/roles/_role.html.haml b/app/views/admin/roles/_role.html.haml index d6c6b62c81..fd37644c83 100644 --- a/app/views/admin/roles/_role.html.haml +++ b/app/views/admin/roles/_role.html.haml @@ -2,7 +2,7 @@ - if can?(:update, role) = link_to edit_admin_role_path(role), class: 'announcements-list__item__title' do %span.user-role{ class: "user-role-#{role.id}" } - = fa_icon 'users fw' + = material_symbol 'group' - if role.everyone? = t('admin.roles.everyone') @@ -11,7 +11,7 @@ - else %span.announcements-list__item__title %span.user-role{ class: "user-role-#{role.id}" } - = fa_icon 'users fw' + = material_symbol 'group' - if role.everyone? = t('admin.roles.everyone') diff --git a/app/views/admin/rules/_form.html.haml b/app/views/admin/rules/_form.html.haml new file mode 100644 index 0000000000..9fc54e2887 --- /dev/null +++ b/app/views/admin/rules/_form.html.haml @@ -0,0 +1,7 @@ +.fields-group + = form.input :text, + wrapper: :with_block_label + +.fields-group + = form.input :hint, + wrapper: :with_block_label diff --git a/app/views/admin/rules/_rule.html.haml b/app/views/admin/rules/_rule.html.haml index a0896fe806..5f37f69354 100644 --- a/app/views/admin/rules/_rule.html.haml +++ b/app/views/admin/rules/_rule.html.haml @@ -5,7 +5,7 @@ .announcements-list__item__action-bar .announcements-list__item__meta - = rule.text + = rule.hint %div = table_link_to 'trash', t('admin.rules.delete'), admin_rule_path(rule), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:destroy, rule) diff --git a/app/views/admin/rules/edit.html.haml b/app/views/admin/rules/edit.html.haml index 77815588d2..9e3c915812 100644 --- a/app/views/admin/rules/edit.html.haml +++ b/app/views/admin/rules/edit.html.haml @@ -1,14 +1,10 @@ - content_for :page_title do = t('admin.rules.edit') -= simple_form_for @rule, url: admin_rule_path(@rule) do |f| += simple_form_for @rule, url: admin_rule_path(@rule) do |form| = render 'shared/error_messages', object: @rule - .fields-group - = f.input :text, wrapper: :with_block_label - - .fields-group - = f.input :hint, wrapper: :with_block_label + = render form .actions - = f.button :button, t('generic.save_changes'), type: :submit + = form.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/rules/index.html.haml b/app/views/admin/rules/index.html.haml index dd15ce03c0..5a2789edcf 100644 --- a/app/views/admin/rules/index.html.haml +++ b/app/views/admin/rules/index.html.haml @@ -6,17 +6,13 @@ %hr.spacer/ - if can? :create, :rule - = simple_form_for @rule, url: admin_rules_path do |f| + = simple_form_for @rule, url: admin_rules_path do |form| = render 'shared/error_messages', object: @rule - .fields-group - = f.input :text, wrapper: :with_block_label - - .fields-group - = f.input :hint, wrapper: :with_block_label + = render form .actions - = f.button :button, t('admin.rules.add_new'), type: :submit + = form.button :button, t('admin.rules.add_new'), type: :submit %hr.spacer/ diff --git a/app/views/admin/settings/branding/show.html.haml b/app/views/admin/settings/branding/show.html.haml index 769c0dafe8..71aac5ead1 100644 --- a/app/views/admin/settings/branding/show.html.haml +++ b/app/views/admin/settings/branding/show.html.haml @@ -40,5 +40,33 @@ = fa_icon 'trash fw' = t('admin.site_uploads.delete') + .fields-row + .fields-row__column.fields-row__column-6.fields-group + = f.input :favicon, + as: :file, + input_html: { accept: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].join(',') }, + wrapper: :with_block_label + + .fields-row__column.fields-row__column-6.fields-group + - if @admin_settings.favicon.persisted? + = image_tag @admin_settings.favicon.file.url('48'), class: 'fields-group__thumbnail' + = link_to admin_site_upload_path(@admin_settings.favicon), data: { method: :delete }, class: 'link-button link-button--destructive' do + = fa_icon 'trash fw' + = t('admin.site_uploads.delete') + + .fields-row + .fields-row__column.fields-row__column-6.fields-group + = f.input :app_icon, + as: :file, + input_html: { accept: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].join(',') }, + wrapper: :with_block_label + + .fields-row__column.fields-row__column-6.fields-group + - if @admin_settings.app_icon.persisted? + = image_tag @admin_settings.app_icon.file.url('48'), class: 'fields-group__thumbnail' + = link_to admin_site_upload_path(@admin_settings.app_icon), data: { method: :delete }, class: 'link-button link-button--destructive' do + = fa_icon 'trash fw' + = t('admin.site_uploads.delete') + .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/settings/content_retention/show.html.haml b/app/views/admin/settings/content_retention/show.html.haml index 4b2ee572e3..57485c1108 100644 --- a/app/views/admin/settings/content_retention/show.html.haml +++ b/app/views/admin/settings/content_retention/show.html.haml @@ -14,14 +14,18 @@ = f.input :media_cache_retention_period, input_html: { pattern: '[0-9]+' }, wrapper: :with_block_label + = f.input :backups_retention_period, + input_html: { pattern: '[0-9]+' }, + wrapper: :with_block_label + + %h4= t('admin.settings.content_retention.danger_zone') + + .fields-group = f.input :content_cache_retention_period, hint: false, input_html: { pattern: '[0-9]+' }, warning_hint: t('simple_form.hints.form_admin_settings.content_cache_retention_period'), wrapper: :with_block_label - = f.input :backups_retention_period, - input_html: { pattern: '[0-9]+' }, - wrapper: :with_block_label .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/settings/shared/_links.html.haml b/app/views/admin/settings/shared/_links.html.haml index 86513e9aaf..f9b41b5814 100644 --- a/app/views/admin/settings/shared/_links.html.haml +++ b/app/views/admin/settings/shared/_links.html.haml @@ -3,7 +3,7 @@ :ruby primary.item :branding, safe_join([fa_icon('pencil fw'), t('admin.settings.branding.title')]), admin_settings_branding_path primary.item :about, safe_join([fa_icon('file-text fw'), t('admin.settings.about.title')]), admin_settings_about_path - primary.item :registrations, safe_join([fa_icon('users fw'), t('admin.settings.registrations.title')]), admin_settings_registrations_path + primary.item :registrations, safe_join([material_symbol('group'), t('admin.settings.registrations.title')]), admin_settings_registrations_path primary.item :discovery, safe_join([fa_icon('search fw'), t('admin.settings.discovery.title')]), admin_settings_discovery_path primary.item :content_retention, safe_join([fa_icon('history fw'), t('admin.settings.content_retention.title')]), admin_settings_content_retention_path primary.item :appearance, safe_join([fa_icon('desktop fw'), t('admin.settings.appearance.title')]), admin_settings_appearance_path diff --git a/app/views/admin/status_edits/_status_edit.html.haml b/app/views/admin/status_edits/_status_edit.html.haml index 7254777213..0bec0159ee 100644 --- a/app/views/admin/status_edits/_status_edit.html.haml +++ b/app/views/admin/status_edits/_status_edit.html.haml @@ -26,5 +26,5 @@ - if status_edit.sensitive? · - = fa_icon('eye-slash fw') + = material_symbol('visibility_off') = t('stream_entries.sensitive_content') diff --git a/app/views/admin/statuses/index.html.haml b/app/views/admin/statuses/index.html.haml index 33a41bd365..a41a6332dd 100644 --- a/app/views/admin/statuses/index.html.haml +++ b/app/views/admin/statuses/index.html.haml @@ -12,11 +12,11 @@ .back-link - if params[:report_id] = link_to admin_report_path(params[:report_id].to_i) do - = fa_icon 'chevron-left fw' + = material_symbol 'chevron_left' = t('admin.statuses.back_to_report') - else = link_to admin_account_path(@account.id) do - = fa_icon 'chevron-left fw' + = material_symbol 'chevron_left' = t('admin.statuses.back_to_account') %hr.spacer/ diff --git a/app/views/admin/tags/show.html.haml b/app/views/admin/tags/show.html.haml index 2e4424bec6..f2d87b54b0 100644 --- a/app/views/admin/tags/show.html.haml +++ b/app/views/admin/tags/show.html.haml @@ -52,26 +52,26 @@ = link_to admin_tag_path(@tag.id), class: ['dashboard__quick-access', @tag.usable? ? 'positive' : 'negative'] do - if @tag.usable? %span= t('admin.trends.tags.usable') - = fa_icon 'check fw' + = material_symbol 'check' - else %span= t('admin.trends.tags.not_usable') - = fa_icon 'lock fw' + = material_symbol 'lock' = link_to admin_tag_path(@tag.id), class: ['dashboard__quick-access', @tag.trendable? ? 'positive' : 'negative'] do - if @tag.trendable? %span= t('admin.trends.tags.trendable') - = fa_icon 'check fw' + = material_symbol 'check' - else %span= t('admin.trends.tags.not_trendable') - = fa_icon 'lock fw' + = material_symbol 'lock' = link_to admin_tag_path(@tag.id), class: ['dashboard__quick-access', @tag.listable? ? 'positive' : 'negative'] do - if @tag.listable? %span= t('admin.trends.tags.listable') - = fa_icon 'check fw' + = material_symbol 'check' - else %span= t('admin.trends.tags.not_listable') - = fa_icon 'lock fw' + = material_symbol 'lock' %hr.spacer/ diff --git a/app/views/admin/trends/links/index.html.haml b/app/views/admin/trends/links/index.html.haml index 965d2b2e56..c503b2d396 100644 --- a/app/views/admin/trends/links/index.html.haml +++ b/app/views/admin/trends/links/index.html.haml @@ -24,7 +24,7 @@ .back-link = link_to admin_trends_links_preview_card_providers_path do = t('admin.trends.preview_card_providers.title') - = fa_icon 'chevron-right fw' + = material_symbol 'chevron_right' = form_for(@form, url: batch_admin_trends_links_path) do |f| = hidden_field_tag :page, params[:page] || 1 @@ -37,22 +37,22 @@ %label.batch-table__toolbar__select.batch-checkbox-all = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - = f.button safe_join([fa_icon('check'), t('admin.trends.links.allow')]), + = f.button safe_join([material_symbol('check'), t('admin.trends.links.allow')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :approve, type: :submit - = f.button safe_join([fa_icon('check'), t('admin.trends.links.allow_provider')]), + = f.button safe_join([material_symbol('check'), t('admin.trends.links.allow_provider')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :approve_providers, type: :submit - = f.button safe_join([fa_icon('times'), t('admin.trends.links.disallow')]), + = f.button safe_join([material_symbol('close'), t('admin.trends.links.disallow')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :reject, type: :submit - = f.button safe_join([fa_icon('times'), t('admin.trends.links.disallow_provider')]), + = f.button safe_join([material_symbol('close'), t('admin.trends.links.disallow_provider')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :reject_providers, diff --git a/app/views/admin/trends/links/preview_card_providers/index.html.haml b/app/views/admin/trends/links/preview_card_providers/index.html.haml index c91822fb74..706c607010 100644 --- a/app/views/admin/trends/links/preview_card_providers/index.html.haml +++ b/app/views/admin/trends/links/preview_card_providers/index.html.haml @@ -15,7 +15,7 @@ %li= filter_link_to safe_join([t('admin.accounts.moderation.pending'), "(#{PreviewCardProvider.pending_review.count})"], ' '), status: 'pending_review' .back-link = link_to admin_trends_links_path do - = fa_icon 'chevron-left fw' + = material_symbol 'chevron_left' = t('admin.trends.links.title') %hr.spacer/ @@ -31,12 +31,12 @@ %label.batch-table__toolbar__select.batch-checkbox-all = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - = f.button safe_join([fa_icon('check'), t('admin.trends.allow')]), + = f.button safe_join([material_symbol('check'), t('admin.trends.allow')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :approve, type: :submit - = f.button safe_join([fa_icon('times'), t('admin.trends.disallow')]), + = f.button safe_join([material_symbol('close'), t('admin.trends.disallow')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :reject, diff --git a/app/views/admin/trends/statuses/_status.html.haml b/app/views/admin/trends/statuses/_status.html.haml index 095f3f2187..09547ff036 100644 --- a/app/views/admin/trends/statuses/_status.html.haml +++ b/app/views/admin/trends/statuses/_status.html.haml @@ -11,7 +11,7 @@ - status.ordered_media_attachments.each do |media_attachment| %abbr{ title: media_attachment.description } - = fa_icon 'link' + = material_symbol 'link' = media_attachment.file_file_name = t 'admin.trends.statuses.shared_by', diff --git a/app/views/admin/trends/statuses/index.html.haml b/app/views/admin/trends/statuses/index.html.haml index 0891d15fcf..66151ad31e 100644 --- a/app/views/admin/trends/statuses/index.html.haml +++ b/app/views/admin/trends/statuses/index.html.haml @@ -31,22 +31,22 @@ %label.batch-table__toolbar__select.batch-checkbox-all = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - = f.button safe_join([fa_icon('check'), t('admin.trends.statuses.allow')]), + = f.button safe_join([material_symbol('check'), t('admin.trends.statuses.allow')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :approve, type: :submit - = f.button safe_join([fa_icon('check'), t('admin.trends.statuses.allow_account')]), + = f.button safe_join([material_symbol('check'), t('admin.trends.statuses.allow_account')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :approve_accounts, type: :submit - = f.button safe_join([fa_icon('times'), t('admin.trends.statuses.disallow')]), + = f.button safe_join([material_symbol('close'), t('admin.trends.statuses.disallow')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :reject, type: :submit - = f.button safe_join([fa_icon('times'), t('admin.trends.statuses.disallow_account')]), + = f.button safe_join([material_symbol('close'), t('admin.trends.statuses.disallow_account')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :reject_accounts, diff --git a/app/views/admin/trends/tags/_tag.html.haml b/app/views/admin/trends/tags/_tag.html.haml index 70c7d8dbd4..8cc0d713b9 100644 --- a/app/views/admin/trends/tags/_tag.html.haml +++ b/app/views/admin/trends/tags/_tag.html.haml @@ -5,7 +5,7 @@ .batch-table__row__content.pending-account .pending-account__header = link_to admin_tag_path(tag.id) do - = fa_icon 'hashtag' + = material_symbol 'tag' = tag.display_name %br/ diff --git a/app/views/admin/trends/tags/index.html.haml b/app/views/admin/trends/tags/index.html.haml index effde7b0ec..655955f7f6 100644 --- a/app/views/admin/trends/tags/index.html.haml +++ b/app/views/admin/trends/tags/index.html.haml @@ -25,12 +25,12 @@ %label.batch-table__toolbar__select.batch-checkbox-all = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - = f.button safe_join([fa_icon('check'), t('admin.trends.allow')]), + = f.button safe_join([material_symbol('check'), t('admin.trends.allow')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :approve, type: :submit - = f.button safe_join([fa_icon('times'), t('admin.trends.disallow')]), + = f.button safe_join([material_symbol('close'), t('admin.trends.disallow')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :reject, diff --git a/app/views/admin/warning_presets/_form.html.haml b/app/views/admin/warning_presets/_form.html.haml new file mode 100644 index 0000000000..cba74163c5 --- /dev/null +++ b/app/views/admin/warning_presets/_form.html.haml @@ -0,0 +1,7 @@ +.fields-group + = form.input :title, + wrapper: :with_block_label + +.fields-group + = form.input :text, + wrapper: :with_block_label diff --git a/app/views/admin/warning_presets/edit.html.haml b/app/views/admin/warning_presets/edit.html.haml index b5c5107ef4..f0bd9c12ec 100644 --- a/app/views/admin/warning_presets/edit.html.haml +++ b/app/views/admin/warning_presets/edit.html.haml @@ -1,14 +1,10 @@ - content_for :page_title do = t('admin.warning_presets.edit_preset') -= simple_form_for @warning_preset, url: admin_warning_preset_path(@warning_preset) do |f| += simple_form_for @warning_preset, url: admin_warning_preset_path(@warning_preset) do |form| = render 'shared/error_messages', object: @warning_preset - .fields-group - = f.input :title, wrapper: :with_block_label - - .fields-group - = f.input :text, wrapper: :with_block_label + = render form .actions - = f.button :button, t('generic.save_changes'), type: :submit + = form.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/warning_presets/index.html.haml b/app/views/admin/warning_presets/index.html.haml index b26a13d966..22fee21050 100644 --- a/app/views/admin/warning_presets/index.html.haml +++ b/app/views/admin/warning_presets/index.html.haml @@ -2,17 +2,13 @@ = t('admin.warning_presets.title') - if can? :create, :account_warning_preset - = simple_form_for @warning_preset, url: admin_warning_presets_path do |f| + = simple_form_for @warning_preset, url: admin_warning_presets_path do |form| = render 'shared/error_messages', object: @warning_preset - .fields-group - = f.input :title, wrapper: :with_block_label - - .fields-group - = f.input :text, wrapper: :with_block_label + = render form .actions - = f.button :button, t('admin.warning_presets.add_new'), type: :submit + = form.button :button, t('admin.warning_presets.add_new'), type: :submit %hr.spacer/ diff --git a/app/views/admin/webhooks/edit.html.haml b/app/views/admin/webhooks/edit.html.haml index 2c2a7aa034..abc9bdfabc 100644 --- a/app/views/admin/webhooks/edit.html.haml +++ b/app/views/admin/webhooks/edit.html.haml @@ -2,6 +2,6 @@ = t('admin.webhooks.edit') = simple_form_for @webhook, url: admin_webhook_path(@webhook) do |form| - = render partial: 'form', object: form + = render form .actions = form.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/webhooks/new.html.haml b/app/views/admin/webhooks/new.html.haml index f51b039ce8..50fcdc2be7 100644 --- a/app/views/admin/webhooks/new.html.haml +++ b/app/views/admin/webhooks/new.html.haml @@ -2,6 +2,6 @@ = t('admin.webhooks.new') = simple_form_for @webhook, url: admin_webhooks_path do |form| - = render partial: 'form', object: form + = render form .actions = form.button :button, t('admin.webhooks.add_new'), type: :submit diff --git a/app/views/admin_mailer/new_critical_software_updates.text.erb b/app/views/admin_mailer/new_critical_software_updates.text.erb index 63c170dc0d..a3f031bdad 100644 --- a/app/views/admin_mailer/new_critical_software_updates.text.erb +++ b/app/views/admin_mailer/new_critical_software_updates.text.erb @@ -1,3 +1,7 @@ <%= raw t('admin_mailer.new_critical_software_updates.body') %> +<% @software_updates.each do |update| %> +- Mastodon <%= update.version %>: <%= update.release_notes %> +<% end %> + <%= raw t('application_mailer.view')%> <%= admin_software_updates_url %> diff --git a/app/views/admin_mailer/new_software_updates.text.erb b/app/views/admin_mailer/new_software_updates.text.erb index 96808f3cb3..0eccbbf9ef 100644 --- a/app/views/admin_mailer/new_software_updates.text.erb +++ b/app/views/admin_mailer/new_software_updates.text.erb @@ -1,3 +1,7 @@ <%= raw t('admin_mailer.new_software_updates.body') %> +<% @software_updates.each do |update| %> +- Mastodon <%= update.version %>: <%= update.release_notes %> +<% end %> + <%= raw t('application_mailer.view')%> <%= admin_software_updates_url %> diff --git a/app/views/application/mailer/_checklist.html.haml b/app/views/application/mailer/_checklist.html.haml index 324fd7e6f8..91c7c98f26 100644 --- a/app/views/application/mailer/_checklist.html.haml +++ b/app/views/application/mailer/_checklist.html.haml @@ -16,16 +16,14 @@ = image_tag frontend_asset_url('images/mailer-new/welcome/checkbox-off.png'), alt: '', width: 20, height: 20 %td.email-checklist-icons-step-td - if defined?(key) - = image_tag frontend_asset_url("images/mailer-new/welcome-icons/#{key}-#{checked ? 'on' : 'off'}.png"), alt: '', width: 40, height: 40 + = image_tag frontend_asset_url("images/mailer-new/welcome-icons/#{key}_step-#{checked ? 'on' : 'off'}.png"), alt: '', width: 40, height: 40 %td.email-checklist-text-td .email-desktop-flex /[if mso]
%div - - if defined?(title) - %h3= title - - if defined?(text) - %p= text + %h3= t("user_mailer.welcome.#{key}_title") + %p= t("user_mailer.welcome.#{key}_step") /[if mso] %div diff --git a/app/views/application/mailer/_feature.html.haml b/app/views/application/mailer/_feature.html.haml index d051338a9c..94dd4b9cff 100644 --- a/app/views/application/mailer/_feature.html.haml +++ b/app/views/application/mailer/_feature.html.haml @@ -4,17 +4,15 @@ %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } %tr %td.email-feature-td - .email-desktop-flex{ class: ('email-dir-rtl' if defined?(text_first_on_desktop) && !text_first_on_desktop) } + .email-desktop-flex{ class: ('email-dir-rtl' if feature_iteration.index.odd?) } /[if mso]
.email-desktop-column %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } %tr %td.email-column-td - - if defined?(feature_title) - %h2.email-h2= feature_title - - if defined?(feature_text) - %p.email-p= feature_text + %h2.email-h2= t("user_mailer.welcome.feature_#{feature}_title") + %p.email-p= t("user_mailer.welcome.feature_#{feature}") - if defined?(feature_btn_url) = link_to '', href: feature_btn_url, class: 'email-link-with-arrow' do #{t('user_mailer.welcome.feature_action')}  @@ -25,8 +23,8 @@ %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } %tr %td.email-column-td - - if defined?(key) - %p{ class: ('email-desktop-text-right' if defined?(text_first_on_desktop) && text_first_on_desktop) } - = image_tag frontend_asset_url("images/mailer-new/welcome/#{key}.png"), alt: '', width: 240, height: 230 + - if defined?(feature) + %p{ class: ('email-desktop-text-right' if feature_iteration.index.even?) } + = image_tag frontend_asset_url("images/mailer-new/welcome/feature_#{feature}.png"), alt: '', width: 240, height: 230 /[if mso]
diff --git a/app/views/application/mailer/_hashtag.html.haml b/app/views/application/mailer/_hashtag.html.haml index fcedfa80a5..b740ba31b9 100644 --- a/app/views/application/mailer/_hashtag.html.haml +++ b/app/views/application/mailer/_hashtag.html.haml @@ -1,4 +1,4 @@ -- accounts = hashtag.statuses.with_public_visibility.joins(:account).merge(Account.without_suspended.without_silenced).includes(:account).limit(3).map(&:account) +- accounts = hashtag.statuses.public_visibility.joins(:account).merge(Account.without_suspended.without_silenced).includes(:account).limit(3).map(&:account) %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } %tr @@ -17,4 +17,5 @@ %span.email-mini-hashtag-img-span = image_tag full_asset_url(account.avatar.url), alt: '', width: 16, height: 16 %td - %p= t('user_mailer.welcome.hashtags_recent_count', people: number_with_delimiter(hashtag.history.aggregate(2.days.ago.to_date..Time.zone.today).accounts)) + - people = hashtag.history.aggregate(2.days.ago.to_date..Time.zone.today).accounts + %p= t('user_mailer.welcome.hashtags_recent_count', people: number_with_delimiter(people), count: people) diff --git a/app/views/auth/sessions/two_factor.html.haml b/app/views/auth/sessions/two_factor.html.haml index 0892101b57..63c8b0a2cb 100644 --- a/app/views/auth/sessions/two_factor.html.haml +++ b/app/views/auth/sessions/two_factor.html.haml @@ -1,6 +1,8 @@ - content_for :page_title do = t('auth.login') += flavoured_javascript_pack_tag 'two_factor_authentication', crossorigin: 'anonymous' + - if webauthn_enabled? = render partial: 'auth/sessions/two_factor/webauthn_form', locals: { hidden: @scheme_type != 'webauthn' } diff --git a/app/views/auth/setup/show.html.haml b/app/views/auth/setup/show.html.haml index 097e3416e2..6ba219e729 100644 --- a/app/views/auth/setup/show.html.haml +++ b/app/views/auth/setup/show.html.haml @@ -1,6 +1,8 @@ - content_for :page_title do = t('auth.setup.title') += flavoured_javascript_pack_tag 'sign_up', crossorigin: 'anonymous' + = simple_form_for(@user, url: auth_setup_path) do |f| = render 'auth/shared/progress', stage: 'confirm' diff --git a/app/views/filters/_filter_fields.html.haml b/app/views/filters/_filter_fields.html.haml index 5b297a6a9e..0f4049ffb6 100644 --- a/app/views/filters/_filter_fields.html.haml +++ b/app/views/filters/_filter_fields.html.haml @@ -6,7 +6,7 @@ wrapper: :with_label .fields-row__column.fields-row__column-6.fields-group = f.input :expires_in, - collection: [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].map(&:to_i), + collection: CustomFilter::EXPIRATION_DURATIONS.map(&:to_i), include_blank: I18n.t('invites.expires_in_prompt'), label_method: ->(i) { I18n.t("invites.expires_in.#{i}") }, wrapper: :with_label diff --git a/app/views/layouts/_theme.html.haml b/app/views/layouts/_theme.html.haml deleted file mode 100644 index 2b40d408f2..0000000000 --- a/app/views/layouts/_theme.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -- if theme - = render partial: 'layouts/theme', object: theme[:common] if theme[:pack] != 'common' && theme[:common] - - if theme[:pack] - - pack_path = theme[:flavour] ? "flavours/#{theme[:flavour]}/#{theme[:pack]}" : "core/#{theme[:pack]}" - = javascript_pack_tag pack_path, crossorigin: 'anonymous' - - if theme[:skin] - - if !theme[:flavour] || theme[:skin] == 'default' - = stylesheet_pack_tag pack_path, media: 'all', crossorigin: 'anonymous' - - else - = stylesheet_pack_tag "skins/#{theme[:flavour]}/#{theme[:skin]}/#{theme[:pack]}", media: 'all', crossorigin: 'anonymous' - - theme[:preload]&.each do |link| - %link{ href: asset_pack_path("#{link}.js"), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index 3048e0e6ad..a95d5caef5 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -1,5 +1,7 @@ - content_for :header_tags do = render_initial_state + = flavoured_javascript_pack_tag 'public', crossorigin: 'anonymous' + = flavoured_javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous' - content_for :content do .admin-wrapper diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 3e11e79052..ed436b729f 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -11,42 +11,36 @@ - if storage_host? %link{ rel: 'dns-prefetch', href: storage_host }/ - %link{ rel: 'icon', href: '/favicon.ico', type: 'image/x-icon' }/ + %link{ rel: 'icon', href: site_icon_path('favicon', 'ico') || '/favicon.ico', type: 'image/x-icon' }/ - - %w(16 32 48).each do |size| - %link{ rel: 'icon', sizes: "#{size}x#{size}", href: frontend_asset_path("icons/favicon-#{size}x#{size}.png"), type: 'image/png' }/ + - SiteUpload::FAVICON_SIZES.each do |size| + %link{ rel: 'icon', sizes: "#{size}x#{size}", href: site_icon_path('favicon', size.to_i) || frontend_asset_path("icons/favicon-#{size}x#{size}.png"), type: 'image/png' }/ - - %w(57 60 72 76 114 120 144 152 167 180 1024).each do |size| - %link{ rel: 'apple-touch-icon', sizes: "#{size}x#{size}", href: frontend_asset_path("icons/apple-touch-icon-#{size}x#{size}.png") }/ + - SiteUpload::APPLE_ICON_SIZES.each do |size| + %link{ rel: 'apple-touch-icon', sizes: "#{size}x#{size}", href: site_icon_path('app_icon', size.to_i) || frontend_asset_path("icons/apple-touch-icon-#{size}x#{size}.png") }/ %link{ rel: 'mask-icon', href: frontend_asset_path('images/logo-symbol-icon.svg'), color: '#6364FF' }/ %link{ rel: 'manifest', href: manifest_path(format: :json) }/ - %meta{ name: 'theme-color', content: '#191b22' }/ + = theme_color_tags current_theme %meta{ name: 'apple-mobile-web-app-capable', content: 'yes' }/ %title= html_title - = javascript_pack_tag 'common', crossorigin: 'anonymous' + = flavoured_stylesheet_pack_tag 'common', media: 'all', crossorigin: 'anonymous' # upstream uses `common` but that's implicitly defined + = theme_style_tags current_theme -# Needed for the wicg-inert polyfill. It needs to be on it's own