From 7e1a77ea51e6dc4aecbf678f8928aa96698fa072 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 14 Nov 2023 09:53:31 -0500 Subject: [PATCH 001/106] Add base class for `api/v1/timelines/*` controllers (#27840) --- .../api/v1/timelines/base_controller.rb | 33 +++++++++++++++++++ .../api/v1/timelines/home_controller.rb | 25 +++----------- .../api/v1/timelines/list_controller.rb | 24 +++----------- .../api/v1/timelines/public_controller.rb | 25 +++----------- .../api/v1/timelines/tag_controller.rb | 25 +++----------- 5 files changed, 52 insertions(+), 80 deletions(-) create mode 100644 app/controllers/api/v1/timelines/base_controller.rb diff --git a/app/controllers/api/v1/timelines/base_controller.rb b/app/controllers/api/v1/timelines/base_controller.rb new file mode 100644 index 0000000000..173e173cc9 --- /dev/null +++ b/app/controllers/api/v1/timelines/base_controller.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +class Api::V1::Timelines::BaseController < Api::BaseController + after_action :insert_pagination_headers, unless: -> { @statuses.empty? } + + private + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def pagination_max_id + @statuses.last.id + end + + def pagination_since_id + @statuses.first.id + end + + def next_path_params + permitted_params.merge(max_id: pagination_max_id) + end + + def prev_path_params + permitted_params.merge(min_id: pagination_since_id) + end + + def permitted_params + params + .slice(*self.class::PERMITTED_PARAMS) + .permit(*self.class::PERMITTED_PARAMS) + end +end diff --git a/app/controllers/api/v1/timelines/home_controller.rb b/app/controllers/api/v1/timelines/home_controller.rb index 83b8cb4c66..36fdbea647 100644 --- a/app/controllers/api/v1/timelines/home_controller.rb +++ b/app/controllers/api/v1/timelines/home_controller.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true -class Api::V1::Timelines::HomeController < Api::BaseController +class Api::V1::Timelines::HomeController < Api::V1::Timelines::BaseController before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: [:show] before_action :require_user!, only: [:show] - after_action :insert_pagination_headers, unless: -> { @statuses.empty? } + + PERMITTED_PARAMS = %i(local limit).freeze def show with_read_replica do @@ -40,27 +41,11 @@ class Api::V1::Timelines::HomeController < Api::BaseController HomeFeed.new(current_account) end - def insert_pagination_headers - set_pagination_headers(next_path, prev_path) - end - - def pagination_params(core_params) - params.slice(:local, :limit).permit(:local, :limit).merge(core_params) - end - def next_path - api_v1_timelines_home_url pagination_params(max_id: pagination_max_id) + api_v1_timelines_home_url next_path_params end def prev_path - api_v1_timelines_home_url pagination_params(min_id: pagination_since_id) - end - - def pagination_max_id - @statuses.last.id - end - - def pagination_since_id - @statuses.first.id + api_v1_timelines_home_url prev_path_params end end diff --git a/app/controllers/api/v1/timelines/list_controller.rb b/app/controllers/api/v1/timelines/list_controller.rb index a15eae468d..14b884ecd9 100644 --- a/app/controllers/api/v1/timelines/list_controller.rb +++ b/app/controllers/api/v1/timelines/list_controller.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true -class Api::V1::Timelines::ListController < Api::BaseController +class Api::V1::Timelines::ListController < Api::V1::Timelines::BaseController before_action -> { doorkeeper_authorize! :read, :'read:lists' } before_action :require_user! before_action :set_list before_action :set_statuses - after_action :insert_pagination_headers, unless: -> { @statuses.empty? } + PERMITTED_PARAMS = %i(limit).freeze def show render json: @statuses, @@ -41,27 +41,11 @@ class Api::V1::Timelines::ListController < Api::BaseController ListFeed.new(@list) end - def insert_pagination_headers - set_pagination_headers(next_path, prev_path) - end - - def pagination_params(core_params) - params.slice(:limit).permit(:limit).merge(core_params) - end - def next_path - api_v1_timelines_list_url params[:id], pagination_params(max_id: pagination_max_id) + api_v1_timelines_list_url params[:id], next_path_params end def prev_path - api_v1_timelines_list_url params[:id], pagination_params(min_id: pagination_since_id) - end - - def pagination_max_id - @statuses.last.id - end - - def pagination_since_id - @statuses.first.id + api_v1_timelines_list_url params[:id], prev_path_params end end diff --git a/app/controllers/api/v1/timelines/public_controller.rb b/app/controllers/api/v1/timelines/public_controller.rb index 5bbd92b9ee..35af8dc4b5 100644 --- a/app/controllers/api/v1/timelines/public_controller.rb +++ b/app/controllers/api/v1/timelines/public_controller.rb @@ -1,8 +1,9 @@ # frozen_string_literal: true -class Api::V1::Timelines::PublicController < Api::BaseController +class Api::V1::Timelines::PublicController < Api::V1::Timelines::BaseController before_action :require_user!, only: [:show], if: :require_auth? - after_action :insert_pagination_headers, unless: -> { @statuses.empty? } + + PERMITTED_PARAMS = %i(local remote limit only_media).freeze def show cache_if_unauthenticated! @@ -42,27 +43,11 @@ class Api::V1::Timelines::PublicController < Api::BaseController ) end - def insert_pagination_headers - set_pagination_headers(next_path, prev_path) - end - - def pagination_params(core_params) - params.slice(:local, :remote, :limit, :only_media).permit(:local, :remote, :limit, :only_media).merge(core_params) - end - def next_path - api_v1_timelines_public_url pagination_params(max_id: pagination_max_id) + api_v1_timelines_public_url next_path_params end def prev_path - api_v1_timelines_public_url pagination_params(min_id: pagination_since_id) - end - - def pagination_max_id - @statuses.last.id - end - - def pagination_since_id - @statuses.first.id + api_v1_timelines_public_url prev_path_params end end diff --git a/app/controllers/api/v1/timelines/tag_controller.rb b/app/controllers/api/v1/timelines/tag_controller.rb index a79d65c124..4ba439dbb2 100644 --- a/app/controllers/api/v1/timelines/tag_controller.rb +++ b/app/controllers/api/v1/timelines/tag_controller.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true -class Api::V1::Timelines::TagController < Api::BaseController +class Api::V1::Timelines::TagController < Api::V1::Timelines::BaseController before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: :show, if: :require_auth? before_action :load_tag - after_action :insert_pagination_headers, unless: -> { @statuses.empty? } + + PERMITTED_PARAMS = %i(local limit only_media).freeze def show cache_if_unauthenticated! @@ -51,27 +52,11 @@ class Api::V1::Timelines::TagController < Api::BaseController ) end - def insert_pagination_headers - set_pagination_headers(next_path, prev_path) - end - - def pagination_params(core_params) - params.slice(:local, :limit, :only_media).permit(:local, :limit, :only_media).merge(core_params) - end - def next_path - api_v1_timelines_tag_url params[:id], pagination_params(max_id: pagination_max_id) + api_v1_timelines_tag_url params[:id], next_path_params end def prev_path - api_v1_timelines_tag_url params[:id], pagination_params(min_id: pagination_since_id) - end - - def pagination_max_id - @statuses.last.id - end - - def pagination_since_id - @statuses.first.id + api_v1_timelines_tag_url params[:id], prev_path_params end end From 2b038b4f8951845bee470e18ffb11fab29aacb51 Mon Sep 17 00:00:00 2001 From: ppnplus <54897463+ppnplus@users.noreply.github.com> Date: Tue, 14 Nov 2023 22:33:59 +0700 Subject: [PATCH 002/106] Added Thai diacritics and tone marks in HASHTAG_INVALID_CHARS_RE (#26576) --- app/models/tag.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/tag.rb b/app/models/tag.rb index 8fab98fb5c..46e55d74f9 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -37,7 +37,7 @@ class Tag < ApplicationRecord HASHTAG_RE = %r{(? Date: Tue, 14 Nov 2023 17:53:15 +0100 Subject: [PATCH 003/106] Update dependency webpack-bundle-analyzer to v4.10.0 (#27852) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 58 +++++++++++++------------------------------------------ 1 file changed, 13 insertions(+), 45 deletions(-) diff --git a/yarn.lock b/yarn.lock index 1cb5a42d50..addbe638fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6430,6 +6430,13 @@ __metadata: languageName: node linkType: hard +"debounce@npm:^1.2.1": + version: 1.2.1 + resolution: "debounce@npm:1.2.1" + checksum: 6c9320aa0973fc42050814621a7a8a78146c1975799b5b3cc1becf1f77ba9a5aa583987884230da0842a03f385def452fad5d60db97c3d1c8b824e38a8edf500 + languageName: node + linkType: hard + "debug@npm:2.6.9, debug@npm:^2.2.0, debug@npm:^2.3.3": version: 2.6.9 resolution: "debug@npm:2.6.9" @@ -8903,7 +8910,7 @@ __metadata: languageName: node linkType: hard -"html-escaper@npm:^2.0.0": +"html-escaper@npm:^2.0.0, html-escaper@npm:^2.0.2": version: 2.0.2 resolution: "html-escaper@npm:2.0.2" checksum: 208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 @@ -11032,20 +11039,6 @@ __metadata: languageName: node linkType: hard -"lodash.escape@npm:^4.0.1": - version: 4.0.1 - resolution: "lodash.escape@npm:4.0.1" - checksum: 90ade409cec05b6869090476952fdfb84d4d87b1ff4a0e03ebd590f980d9a1248d93ba14579f10d80c6429e4d6af13ba137c28db64cae6dadb71442e54a3ad2b - languageName: node - linkType: hard - -"lodash.flatten@npm:^4.4.0": - version: 4.4.0 - resolution: "lodash.flatten@npm:4.4.0" - checksum: 97e8f0d6b61fe4723c02ad0c6e67e51784c4a2c48f56ef283483e556ad01594cf9cec9c773e177bbbdbdb5d19e99b09d2487cb6b6e5dc405c2693e93b125bd3a - languageName: node - linkType: hard - "lodash.get@npm:^4.0": version: 4.4.2 resolution: "lodash.get@npm:4.4.2" @@ -11060,13 +11053,6 @@ __metadata: languageName: node linkType: hard -"lodash.invokemap@npm:^4.6.0": - version: 4.6.0 - resolution: "lodash.invokemap@npm:4.6.0" - checksum: 2bcc5f4b8782a316d55ff139215eb797f576f0f6d3db2755ebba7b35fd6061f8cbe81702a72a30bc6d70073a5dcc461f7570eaddcc9184c2e42ec3023645c6a1 - languageName: node - linkType: hard - "lodash.isarguments@npm:^3.1.0": version: 3.1.0 resolution: "lodash.isarguments@npm:3.1.0" @@ -11109,13 +11095,6 @@ __metadata: languageName: node linkType: hard -"lodash.pullall@npm:^4.2.0": - version: 4.2.0 - resolution: "lodash.pullall@npm:4.2.0" - checksum: b129e8d879258c7db04a7dc1c23dd9e37c52f63a04e105faa8d2ab55e97b5a170d5e15cffbb732a36e7f48c4345c07b6fbddfe50e1f5ec301492b6f64a92040c - languageName: node - linkType: hard - "lodash.sortby@npm:^4.7.0": version: 4.7.0 resolution: "lodash.sortby@npm:4.7.0" @@ -11137,13 +11116,6 @@ __metadata: languageName: node linkType: hard -"lodash.uniqby@npm:^4.7.0": - version: 4.7.0 - resolution: "lodash.uniqby@npm:4.7.0" - checksum: c505c0de20ca759599a2ba38710e8fb95ff2d2028e24d86c901ef2c74be8056518571b9b754bfb75053b2818d30dd02243e4a4621a6940c206bbb3f7626db656 - languageName: node - linkType: hard - "lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.20, lodash@npm:^4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" @@ -17035,29 +17007,25 @@ __metadata: linkType: hard "webpack-bundle-analyzer@npm:^4.8.0": - version: 4.9.1 - resolution: "webpack-bundle-analyzer@npm:4.9.1" + version: 4.10.0 + resolution: "webpack-bundle-analyzer@npm:4.10.0" dependencies: "@discoveryjs/json-ext": "npm:0.5.7" acorn: "npm:^8.0.4" acorn-walk: "npm:^8.0.0" commander: "npm:^7.2.0" + debounce: "npm:^1.2.1" escape-string-regexp: "npm:^4.0.0" gzip-size: "npm:^6.0.0" + html-escaper: "npm:^2.0.2" is-plain-object: "npm:^5.0.0" - lodash.debounce: "npm:^4.0.8" - lodash.escape: "npm:^4.0.1" - lodash.flatten: "npm:^4.4.0" - lodash.invokemap: "npm:^4.6.0" - lodash.pullall: "npm:^4.2.0" - lodash.uniqby: "npm:^4.7.0" opener: "npm:^1.5.2" picocolors: "npm:^1.0.0" sirv: "npm:^2.0.3" ws: "npm:^7.3.1" bin: webpack-bundle-analyzer: lib/bin/analyzer.js - checksum: dd047c306471e6c389d6d4156ff22402e587140310a065a6191ee380f8251063f73a8ec6ac6d977c1cd634dbb717e2522b5d0b6cc9b0e847d4f15737fd9c65c9 + checksum: f812a8d3c0198ce518baf742bff656526f3eae69fb7a64c7f0c9cff202f6fb3380cabf3baaae965b8d6ffbbb6fb802eacb373fca03a596a38b01b84cfb2e8329 languageName: node linkType: hard From 36d7d1781f99c66c85cbbde75015b622124b1c3e Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Tue, 14 Nov 2023 11:53:38 -0500 Subject: [PATCH 004/106] Add CodeCov for Ruby coverage reports (#23868) --- .github/workflows/test-ruby.yml | 8 +++++++- Gemfile | 1 + Gemfile.lock | 2 ++ spec/spec_helper.rb | 8 ++++++++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index 07fd25fb1b..101de66ac7 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -94,7 +94,7 @@ jobs: DB_HOST: localhost DB_USER: postgres DB_PASS: postgres - DISABLE_SIMPLECOV: true + DISABLE_SIMPLECOV: ${{ matrix.ruby-version != '.ruby-version' }} RAILS_ENV: test ALLOW_NOPAM: true PAM_ENABLED: true @@ -137,6 +137,12 @@ jobs: - run: bin/rspec + - name: Upload coverage reports to Codecov + if: matrix.ruby-version == '.ruby-version' + uses: codecov/codecov-action@v3 + with: + files: coverage/lcov/mastodon.lcov + test-e2e: name: End to End testing runs-on: ubuntu-latest diff --git a/Gemfile b/Gemfile index 039e136754..add7b36066 100644 --- a/Gemfile +++ b/Gemfile @@ -139,6 +139,7 @@ group :test do # Coverage formatter for RSpec test if DISABLE_SIMPLECOV is false gem 'simplecov', '~> 0.22', require: false + gem 'simplecov-lcov', '~> 0.8', require: false # Stub web requests for specs gem 'webmock', '~> 3.18' diff --git a/Gemfile.lock b/Gemfile.lock index 84ad19b805..20c958e2e0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -725,6 +725,7 @@ GEM simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) simplecov-html (0.12.3) + simplecov-lcov (0.8.0) simplecov_json_formatter (0.1.4) smart_properties (1.17.0) sprockets (3.7.2) @@ -936,6 +937,7 @@ DEPENDENCIES simple-navigation (~> 4.4) simple_form (~> 5.2) simplecov (~> 0.22) + simplecov-lcov (~> 0.8) sprockets (~> 3.7.2) sprockets-rails (~> 3.4) stackprof diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 7c97d85953..f5dcefc789 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,13 +2,21 @@ if ENV['DISABLE_SIMPLECOV'] != 'true' require 'simplecov' + require 'simplecov-lcov' + SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true + SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter SimpleCov.start 'rails' do + enable_coverage :branch + enable_coverage_for_eval + add_filter 'lib/linter' add_group 'Policies', 'app/policies' add_group 'Presenters', 'app/presenters' add_group 'Serializers', 'app/serializers' add_group 'Services', 'app/services' add_group 'Validators', 'app/validators' + + add_group 'Libraries', 'lib' end end From 15b2d7eec59c745b418debf63907d8bd08c4a730 Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Tue, 14 Nov 2023 18:43:20 +0100 Subject: [PATCH 005/106] Split streaming server from web server (#24702) --- Procfile.dev | 2 +- package.json | 27 ++-------- streaming/index.js | 6 ++- streaming/package.json | 39 +++++++++++++++ yarn.lock | 109 ++++++++++++++++++++++------------------- 5 files changed, 108 insertions(+), 75 deletions(-) create mode 100644 streaming/package.json diff --git a/Procfile.dev b/Procfile.dev index fbb2c2de23..f81333b04c 100644 --- a/Procfile.dev +++ b/Procfile.dev @@ -1,4 +1,4 @@ web: env PORT=3000 RAILS_ENV=development bundle exec puma -C config/puma.rb sidekiq: env PORT=3000 RAILS_ENV=development bundle exec sidekiq -stream: env PORT=4000 yarn run start +stream: env PORT=4000 yarn workspace @mastodon/streaming start webpack: bin/webpack-dev-server diff --git a/package.json b/package.json index 7c73d17c12..bcd91e3fcc 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,13 @@ { "name": "@mastodon/mastodon", "license": "AGPL-3.0-or-later", + "packageManager": "yarn@4.0.2", "engines": { "node": ">=18" }, "workspaces": [ - "." + ".", + "streaming" ], "scripts": { "build:development": "cross-env RAILS_ENV=development NODE_ENV=development ./bin/webpack", @@ -71,10 +73,8 @@ "css-loader": "^5.2.7", "cssnano": "^6.0.1", "detect-passive-events": "^2.0.3", - "dotenv": "^16.0.3", "emoji-mart": "npm:emoji-mart-lazyload@latest", "escape-html": "^1.0.3", - "express": "^4.18.2", "file-loader": "^6.2.0", "font-awesome": "^4.7.0", "fuzzysort": "^2.0.4", @@ -85,21 +85,15 @@ "immutable": "^4.3.0", "imports-loader": "^1.2.0", "intl-messageformat": "^10.3.5", - "ioredis": "^5.3.2", "js-yaml": "^4.1.0", - "jsdom": "^22.1.0", "lodash": "^4.17.21", "mark-loader": "^0.1.6", "marky": "^1.2.5", "mini-css-extract-plugin": "^1.6.2", "mkdirp": "^3.0.1", - "npmlog": "^7.0.1", "path-complete-extname": "^1.0.0", - "pg": "^8.5.0", - "pg-connection-string": "^2.6.0", "postcss": "^8.4.24", "postcss-loader": "^4.3.0", - "prom-client": "^15.0.0", "prop-types": "^15.8.1", "punycode": "^2.3.0", "react": "^18.2.0", @@ -138,7 +132,6 @@ "tesseract.js": "^2.1.5", "tiny-queue": "^0.2.1", "twitter-text": "3.1.0", - "uuid": "^9.0.0", "webpack": "^4.47.0", "webpack-assets-manifest": "^4.0.6", "webpack-bundle-analyzer": "^4.8.0", @@ -150,8 +143,7 @@ "workbox-routing": "^7.0.0", "workbox-strategies": "^7.0.0", "workbox-webpack-plugin": "^7.0.0", - "workbox-window": "^7.0.0", - "ws": "^8.12.1" + "workbox-window": "^7.0.0" }, "devDependencies": { "@formatjs/cli": "^6.1.1", @@ -160,16 +152,13 @@ "@types/babel__core": "^7.20.1", "@types/emoji-mart": "^3.0.9", "@types/escape-html": "^1.0.2", - "@types/express": "^4.17.17", "@types/hoist-non-react-statics": "^3.3.1", "@types/http-link-header": "^1.0.3", "@types/intl": "^1.2.0", "@types/jest": "^29.5.2", "@types/js-yaml": "^4.0.5", "@types/lodash": "^4.14.195", - "@types/npmlog": "^4.1.4", "@types/object-assign": "^4.0.30", - "@types/pg": "^8.6.6", "@types/prop-types": "^15.7.5", "@types/punycode": "^2.1.0", "@types/react": "^18.2.7", @@ -188,7 +177,6 @@ "@types/react-toggle": "^4.0.3", "@types/redux-immutable": "^4.0.3", "@types/requestidlecallback": "^0.3.5", - "@types/uuid": "^9.0.0", "@types/webpack": "^4.41.33", "@types/yargs": "^17.0.24", "@typescript-eslint/eslint-plugin": "^6.0.0", @@ -232,15 +220,10 @@ "optional": true } }, - "optionalDependencies": { - "bufferutil": "^4.0.7", - "utf-8-validate": "^6.0.3" - }, "lint-staged": { "*": "prettier --ignore-unknown --write", "Capfile|Gemfile|*.{rb,ruby,ru,rake}": "bundle exec rubocop --force-exclusion -a", "*.{js,jsx,ts,tsx}": "eslint --fix", "*.{css,scss}": "stylelint --fix" - }, - "packageManager": "yarn@4.0.2" + } } diff --git a/streaming/index.js b/streaming/index.js index b3765531c6..e3b63b53a6 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -2,6 +2,7 @@ const fs = require('fs'); const http = require('http'); +const path = require('path'); const url = require('url'); const dotenv = require('dotenv'); @@ -17,8 +18,11 @@ const WebSocket = require('ws'); const environment = process.env.NODE_ENV || 'development'; +// Correctly detect and load .env or .env.production file based on environment: +const dotenvFile = environment === 'production' ? '.env.production' : '.env'; + dotenv.config({ - path: environment === 'production' ? '.env.production' : '.env', + path: path.resolve(__dirname, path.join('..', dotenvFile)) }); log.level = process.env.LOG_LEVEL || 'verbose'; diff --git a/streaming/package.json b/streaming/package.json new file mode 100644 index 0000000000..d3f2144329 --- /dev/null +++ b/streaming/package.json @@ -0,0 +1,39 @@ +{ + "name": "@mastodon/streaming", + "license": "AGPL-3.0-or-later", + "packageManager": "yarn@4.0.1", + "engines": { + "node": ">=18" + }, + "description": "Mastodon's Streaming Server", + "private": true, + "repository": { + "type": "git", + "url": "https://github.com/mastodon/mastodon.git" + }, + "scripts": { + "start": "node ./index.js" + }, + "dependencies": { + "dotenv": "^16.0.3", + "express": "^4.18.2", + "ioredis": "^5.3.2", + "jsdom": "^22.1.0", + "npmlog": "^7.0.1", + "pg": "^8.5.0", + "pg-connection-string": "^2.6.0", + "prom-client": "^15.0.0", + "uuid": "^9.0.0", + "ws": "^8.12.1" + }, + "devDependencies": { + "@types/express": "^4.17.17", + "@types/npmlog": "^4.1.4", + "@types/pg": "^8.6.6", + "@types/uuid": "^9.0.0" + }, + "optionalDependencies": { + "bufferutil": "^4.0.7", + "utf-8-validate": "^6.0.3" + } +} diff --git a/yarn.lock b/yarn.lock index addbe638fd..c88dd49d03 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2368,16 +2368,13 @@ __metadata: "@types/babel__core": "npm:^7.20.1" "@types/emoji-mart": "npm:^3.0.9" "@types/escape-html": "npm:^1.0.2" - "@types/express": "npm:^4.17.17" "@types/hoist-non-react-statics": "npm:^3.3.1" "@types/http-link-header": "npm:^1.0.3" "@types/intl": "npm:^1.2.0" "@types/jest": "npm:^29.5.2" "@types/js-yaml": "npm:^4.0.5" "@types/lodash": "npm:^4.14.195" - "@types/npmlog": "npm:^4.1.4" "@types/object-assign": "npm:^4.0.30" - "@types/pg": "npm:^8.6.6" "@types/prop-types": "npm:^15.7.5" "@types/punycode": "npm:^2.1.0" "@types/react": "npm:^18.2.7" @@ -2396,7 +2393,6 @@ __metadata: "@types/react-toggle": "npm:^4.0.3" "@types/redux-immutable": "npm:^4.0.3" "@types/requestidlecallback": "npm:^0.3.5" - "@types/uuid": "npm:^9.0.0" "@types/webpack": "npm:^4.41.33" "@types/yargs": "npm:^17.0.24" "@typescript-eslint/eslint-plugin": "npm:^6.0.0" @@ -2412,7 +2408,6 @@ __metadata: babel-plugin-preval: "npm:^5.1.0" babel-plugin-transform-react-remove-prop-types: "npm:^0.4.24" blurhash: "npm:^2.0.5" - bufferutil: "npm:^4.0.7" circular-dependency-plugin: "npm:^5.2.2" classnames: "npm:^2.3.2" cocoon-js-vanilla: "npm:^1.3.0" @@ -2423,7 +2418,6 @@ __metadata: css-loader: "npm:^5.2.7" cssnano: "npm:^6.0.1" detect-passive-events: "npm:^2.0.3" - dotenv: "npm:^16.0.3" emoji-mart: "npm:emoji-mart-lazyload@latest" escape-html: "npm:^1.0.3" eslint: "npm:^8.41.0" @@ -2437,7 +2431,6 @@ __metadata: eslint-plugin-promise: "npm:~6.1.1" eslint-plugin-react: "npm:~7.33.0" eslint-plugin-react-hooks: "npm:^4.6.0" - express: "npm:^4.18.2" file-loader: "npm:^6.2.0" font-awesome: "npm:^4.7.0" fuzzysort: "npm:^2.0.4" @@ -2449,25 +2442,19 @@ __metadata: immutable: "npm:^4.3.0" imports-loader: "npm:^1.2.0" intl-messageformat: "npm:^10.3.5" - ioredis: "npm:^5.3.2" jest: "npm:^29.5.0" jest-environment-jsdom: "npm:^29.5.0" js-yaml: "npm:^4.1.0" - jsdom: "npm:^22.1.0" lint-staged: "npm:^15.0.0" lodash: "npm:^4.17.21" mark-loader: "npm:^0.1.6" marky: "npm:^1.2.5" mini-css-extract-plugin: "npm:^1.6.2" mkdirp: "npm:^3.0.1" - npmlog: "npm:^7.0.1" path-complete-extname: "npm:^1.0.0" - pg: "npm:^8.5.0" - pg-connection-string: "npm:^2.6.0" postcss: "npm:^8.4.24" postcss-loader: "npm:^4.3.0" prettier: "npm:^3.0.0" - prom-client: "npm:^15.0.0" prop-types: "npm:^15.8.1" punycode: "npm:^2.3.0" react: "npm:^18.2.0" @@ -2510,8 +2497,6 @@ __metadata: tiny-queue: "npm:^0.2.1" twitter-text: "npm:3.1.0" typescript: "npm:^5.0.4" - utf-8-validate: "npm:^6.0.3" - uuid: "npm:^9.0.0" webpack: "npm:^4.47.0" webpack-assets-manifest: "npm:^4.0.6" webpack-bundle-analyzer: "npm:^4.8.0" @@ -2525,13 +2510,7 @@ __metadata: workbox-strategies: "npm:^7.0.0" workbox-webpack-plugin: "npm:^7.0.0" workbox-window: "npm:^7.0.0" - ws: "npm:^8.12.1" yargs: "npm:^17.7.2" - dependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true peerDependenciesMeta: react: optional: true @@ -2542,6 +2521,34 @@ __metadata: languageName: unknown linkType: soft +"@mastodon/streaming@workspace:streaming": + version: 0.0.0-use.local + resolution: "@mastodon/streaming@workspace:streaming" + dependencies: + "@types/express": "npm:^4.17.17" + "@types/npmlog": "npm:^4.1.4" + "@types/pg": "npm:^8.6.6" + "@types/uuid": "npm:^9.0.0" + bufferutil: "npm:^4.0.7" + dotenv: "npm:^16.0.3" + express: "npm:^4.18.2" + ioredis: "npm:^5.3.2" + jsdom: "npm:^22.1.0" + npmlog: "npm:^7.0.1" + pg: "npm:^8.5.0" + pg-connection-string: "npm:^2.6.0" + prom-client: "npm:^15.0.0" + utf-8-validate: "npm:^6.0.3" + uuid: "npm:^9.0.0" + ws: "npm:^8.12.1" + dependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + languageName: unknown + linkType: soft + "@material-symbols/svg-600@npm:^0.14.0": version: 0.14.0 resolution: "@material-symbols/svg-600@npm:0.14.0" @@ -3056,21 +3063,21 @@ __metadata: linkType: hard "@types/body-parser@npm:*": - version: 1.19.4 - resolution: "@types/body-parser@npm:1.19.4" + version: 1.19.5 + resolution: "@types/body-parser@npm:1.19.5" dependencies: "@types/connect": "npm:*" "@types/node": "npm:*" - checksum: bec2b8a97861a960ee415f7ab3c2aeb7f4d779fd364d27ddee46057897ea571735f1f854f5ee41682964315d4e3699f62427998b9c21851d773398ef535f0612 + checksum: aebeb200f25e8818d8cf39cd0209026750d77c9b85381cdd8deeb50913e4d18a1ebe4b74ca9b0b4d21952511eeaba5e9fbbf739b52731a2061e206ec60d568df languageName: node linkType: hard "@types/connect@npm:*": - version: 3.4.37 - resolution: "@types/connect@npm:3.4.37" + version: 3.4.38 + resolution: "@types/connect@npm:3.4.38" dependencies: "@types/node": "npm:*" - checksum: 79fd5c32a8bb5c9548369e6da3221b6a820f3a8c5396d50f6f642712b9f4c1c881ef86bdf48994a4a279e81998563410b8843c5a10dde5521d5ef6a8ae944c3b + checksum: 2e1cdba2c410f25649e77856505cd60223250fa12dff7a503e492208dbfdd25f62859918f28aba95315251fd1f5e1ffbfca1e25e73037189ab85dd3f8d0a148c languageName: node linkType: hard @@ -3115,14 +3122,14 @@ __metadata: linkType: hard "@types/express-serve-static-core@npm:^4.17.33": - version: 4.17.39 - resolution: "@types/express-serve-static-core@npm:4.17.39" + version: 4.17.41 + resolution: "@types/express-serve-static-core@npm:4.17.41" dependencies: "@types/node": "npm:*" "@types/qs": "npm:*" "@types/range-parser": "npm:*" "@types/send": "npm:*" - checksum: b23b005fddd2ba3f7142ec9713f06b5582c7712cdf99c3419d3972364903b348a103c3264d9a761d6497140e3b89bd416454684c4bdeff206b4c59b86e96428a + checksum: dc166cbf4475c00a81fbcab120bf7477c527184be11ae149df7f26d9c1082114c68f8d387a2926fe80291b06477c8bbd9231ff4f5775de328e887695aefce269 languageName: node linkType: hard @@ -3175,9 +3182,9 @@ __metadata: linkType: hard "@types/http-errors@npm:*": - version: 2.0.3 - resolution: "@types/http-errors@npm:2.0.3" - checksum: 717ce3e8f49a1facb7130fed934108fa8a51ab02089a1049c782e353e0e08e79bdfaac054c2a94db14ea400302e523276387363aa820eaf0031af8ba5d2941dc + version: 2.0.4 + resolution: "@types/http-errors@npm:2.0.4" + checksum: 494670a57ad4062fee6c575047ad5782506dd35a6b9ed3894cea65830a94367bd84ba302eb3dde331871f6d70ca287bfedb1b2cf658e6132cd2cbd427ab56836 languageName: node linkType: hard @@ -3279,16 +3286,16 @@ __metadata: linkType: hard "@types/mime@npm:*": - version: 3.0.3 - resolution: "@types/mime@npm:3.0.3" - checksum: cef99f8cdc42af9de698027c2a20ba5df12bc9a89dcf5513e70103ebb55e00c5f5c585d02411f4b42fde0e78488342f1b1d3e3546a59a3da42e95fdc616e01eb + version: 3.0.4 + resolution: "@types/mime@npm:3.0.4" + checksum: db478bc0f99e40f7b3e01d356a9bdf7817060808a294978111340317bcd80ca35382855578c5b60fbc84ae449674bd9bb38427b18417e1f8f19e4f72f8b242cd languageName: node linkType: hard "@types/mime@npm:^1": - version: 1.3.4 - resolution: "@types/mime@npm:1.3.4" - checksum: a0a16d26c0e70a1b133e26e7c46b70b3136b7e894396bdb7de1c642f4ac87fdbbba26bf56cf73f001312289d89de4f1c06ab745d9445850df45a5a802564c4d6 + version: 1.3.5 + resolution: "@types/mime@npm:1.3.5" + checksum: c2ee31cd9b993804df33a694d5aa3fa536511a49f2e06eeab0b484fef59b4483777dbb9e42a4198a0809ffbf698081fdbca1e5c2218b82b91603dfab10a10fbc languageName: node linkType: hard @@ -3392,16 +3399,16 @@ __metadata: linkType: hard "@types/qs@npm:*": - version: 6.9.9 - resolution: "@types/qs@npm:6.9.9" - checksum: aede2a4181a49ae8548a1354bac3f8235cb0c5aab066b10875a3e68e88a199e220f4284e7e2bb75a3c18e5d4ff6abe1a6ce0389ef31b63952cc45e0f4d885ba0 + version: 6.9.10 + resolution: "@types/qs@npm:6.9.10" + checksum: 6be12e5f062d1b41eb037d59bf9cb65bc9410cedd5e6da832dfd7c8e2b3f4c91e81c9b90b51811140770e5052c6c4e8361181bd9437ddcd4515dc128b7c00353 languageName: node linkType: hard "@types/range-parser@npm:*": - version: 1.2.6 - resolution: "@types/range-parser@npm:1.2.6" - checksum: 46e7fffc54cdacc8fb0cd576f8f9a6436453f0176205d6ec55434a460c7677e78e688673426d5db5e480501b2943ba08a16ececa3a354c222093551c7217fb8f + version: 1.2.7 + resolution: "@types/range-parser@npm:1.2.7" + checksum: 361bb3e964ec5133fa40644a0b942279ed5df1949f21321d77de79f48b728d39253e5ce0408c9c17e4e0fd95ca7899da36841686393b9f7a1e209916e9381a3c languageName: node linkType: hard @@ -3587,23 +3594,23 @@ __metadata: linkType: hard "@types/send@npm:*": - version: 0.17.3 - resolution: "@types/send@npm:0.17.3" + version: 0.17.4 + resolution: "@types/send@npm:0.17.4" dependencies: "@types/mime": "npm:^1" "@types/node": "npm:*" - checksum: 773a0cb55ea03eefbe9a0e6d42114e0f84968db30954a131aae9ba7e9ab984a4776915447ebdeab4412d7f11750126614b0b75e99413f75810045bdb3196554a + checksum: 7f17fa696cb83be0a104b04b424fdedc7eaba1c9a34b06027239aba513b398a0e2b7279778af521f516a397ced417c96960e5f50fcfce40c4bc4509fb1a5883c languageName: node linkType: hard "@types/serve-static@npm:*": - version: 1.15.4 - resolution: "@types/serve-static@npm:1.15.4" + version: 1.15.5 + resolution: "@types/serve-static@npm:1.15.5" dependencies: "@types/http-errors": "npm:*" "@types/mime": "npm:*" "@types/node": "npm:*" - checksum: 061b38993bf8f2b5033f57147c8ec90e1d1a0d6f734958ceb531ba7cc31192fd272c999cdbc57ede8672787e3aa171ec142dc65a467c04078e43823e7476eb49 + checksum: 811d1a2f7e74a872195e7a013bcd87a2fb1edf07eaedcb9dcfd20c1eb4bc56ad4ea0d52141c13192c91ccda7c8aeb8a530d8a7e60b9c27f5990d7e62e0fecb03 languageName: node linkType: hard From 998f0684994b9be5ccad986b83308039ff395ef6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 18:52:34 +0100 Subject: [PATCH 006/106] Update Yarn to v4.0.2 (#27857) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- streaming/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/streaming/package.json b/streaming/package.json index d3f2144329..a474e62263 100644 --- a/streaming/package.json +++ b/streaming/package.json @@ -1,7 +1,7 @@ { "name": "@mastodon/streaming", "license": "AGPL-3.0-or-later", - "packageManager": "yarn@4.0.1", + "packageManager": "yarn@4.0.2", "engines": { "node": ">=18" }, From 7c72944661c100118555045a87ced7d1fa7cc417 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 15 Nov 2023 04:11:02 -0500 Subject: [PATCH 007/106] Use `Lcov` simplecov formatter on CI and `HTML` elsewhere (#27859) --- spec/spec_helper.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f5dcefc789..0bb4f88cf9 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,9 +2,14 @@ if ENV['DISABLE_SIMPLECOV'] != 'true' require 'simplecov' - require 'simplecov-lcov' - SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true - SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter + + if ENV['CI'] + require 'simplecov-lcov' + SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true + SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter + else + SimpleCov.formatter = SimpleCov::Formatter::HTMLFormatter + end SimpleCov.start 'rails' do enable_coverage :branch enable_coverage_for_eval From b6f29106eab8c5deeb1c22051387292108bbb3c8 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Wed, 15 Nov 2023 10:20:24 +0100 Subject: [PATCH 008/106] Improve codecov config (#27860) --- .github/codecov.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/codecov.yml diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 0000000000..5532c49618 --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,13 @@ +coverage: + status: + project: + default: + # Github status check is not blocking + informational: true + patch: + default: + # Github status check is not blocking + informational: true +comment: + # Only write a comment in PR if there are changes + require_changes: true From 5d75799afaacb5b530d9abdb7464db779d3fcbe0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Nov 2023 09:21:25 +0000 Subject: [PATCH 009/106] Update dependency axios to v1.6.2 (#27861) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index c88dd49d03..e66f58f8b9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4690,13 +4690,13 @@ __metadata: linkType: hard "axios@npm:^1.4.0": - version: 1.6.1 - resolution: "axios@npm:1.6.1" + version: 1.6.2 + resolution: "axios@npm:1.6.2" dependencies: follow-redirects: "npm:^1.15.0" form-data: "npm:^4.0.0" proxy-from-env: "npm:^1.1.0" - checksum: ca2c6f56659a7f19e4a99082f549fe151952f6fd8aa72ed148559ab2d6a32ce37cd5dc72ce6d4d3cd91f0c1e2617c7c95c20077e5e244a79f319a6c0ce41204f + checksum: 9b77e030e85e4f9cbcba7bb52fbff67d6ce906c92d213e0bd932346a50140faf83733bf786f55bd58301bd92f9973885c7b87d6348023e10f7eaf286d0791a1d languageName: node linkType: hard From 922f086253c8bfcead9df895f4624580d5b61a9c Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Wed, 15 Nov 2023 18:29:10 +0900 Subject: [PATCH 010/106] Fix open status on media modal (#27867) --- .../mastodon/features/picture_in_picture/components/footer.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx b/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx index a4ea989fbd..9b26e2d753 100644 --- a/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx +++ b/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx @@ -166,7 +166,7 @@ class Footer extends ImmutablePureComponent { onClose(); } - history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`); + this.props.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`); }; render () { From d8074128f9f5f84bd9d5adc416ef67ae922f0289 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 15 Nov 2023 10:41:24 +0100 Subject: [PATCH 011/106] New Crowdin Translations (automated) (#27866) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/cs.json | 1 + app/javascript/mastodon/locales/eu.json | 2 +- app/javascript/mastodon/locales/it.json | 2 +- app/javascript/mastodon/locales/lt.json | 60 +++++++++++--- app/javascript/mastodon/locales/zh-TW.json | 36 ++++----- config/locales/activerecord.lt.yml | 45 +++++++++++ config/locales/be.yml | 6 +- config/locales/cs.yml | 7 ++ config/locales/devise.zh-TW.yml | 10 +-- config/locales/doorkeeper.zh-TW.yml | 2 +- config/locales/pl.yml | 4 +- config/locales/simple_form.zh-TW.yml | 50 ++++++------ config/locales/vi.yml | 1 + config/locales/zh-TW.yml | 92 +++++++++++----------- 14 files changed, 207 insertions(+), 111 deletions(-) diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index 33c7c31d9c..aaf50d67c5 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -222,6 +222,7 @@ "emoji_button.search_results": "Výsledky hledání", "emoji_button.symbols": "Symboly", "emoji_button.travel": "Cestování a místa", + "empty_column.account_hides_collections": "Tento uživatel se rozhodl nezveřejňovat tuto informaci", "empty_column.account_suspended": "Účet je pozastaven", "empty_column.account_timeline": "Nejsou tu žádné příspěvky!", "empty_column.account_unavailable": "Profil není dostupný", diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json index 7f109ea082..419589aba7 100644 --- a/app/javascript/mastodon/locales/eu.json +++ b/app/javascript/mastodon/locales/eu.json @@ -389,7 +389,7 @@ "lists.replies_policy.title": "Erakutsi erantzunak:", "lists.search": "Bilatu jarraitzen dituzun pertsonen artean", "lists.subheading": "Zure zerrendak", - "load_pending": "{count, plural, one {eleentuberri #} other {# elementu berri}}", + "load_pending": "{count, plural, one {elementu berri #} other {# elementu berri}}", "loading_indicator.label": "Kargatzen...", "media_gallery.toggle_visible": "Txandakatu ikusgaitasuna", "moved_to_account_banner.text": "Zure {disabledAccount} kontua desgaituta dago une honetan, {movedToAccount} kontura aldatu zinelako.", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 4a2f41ce64..8ad791bfef 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -252,7 +252,7 @@ "explore.search_results": "Risultati della ricerca", "explore.suggested_follows": "Persone", "explore.title": "Esplora", - "explore.trending_links": "Novità", + "explore.trending_links": "Notizie", "explore.trending_statuses": "Post", "explore.trending_tags": "Hashtag", "filter_modal.added.context_mismatch_explanation": "La categoria di questo filtro non si applica al contesto in cui hai acceduto a questo post. Se desideri che il post sia filtrato anche in questo contesto, dovrai modificare il filtro.", diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json index 5cdc575dee..5675799e77 100644 --- a/app/javascript/mastodon/locales/lt.json +++ b/app/javascript/mastodon/locales/lt.json @@ -1,7 +1,7 @@ { "about.blocks": "Prižiūrimi serveriai", "about.contact": "Kontaktuoti:", - "about.disclaimer": "Mastodon – nemokama atvirojo šaltinio programa ir Mastodon gGmbH prekės ženklas.", + "about.disclaimer": "Mastodon – nemokama atvirojo kodo programa ir Mastodon gGmbH prekės ženklas.", "about.domain_blocks.no_reason_available": "Priežastis nežinoma", "about.domain_blocks.preamble": "Mastodon paprastai leidžia peržiūrėti turinį ir bendrauti su naudotojais iš bet kurio kito fediverse esančio serverio. Šios yra išimtys, kurios buvo padarytos šiame konkrečiame serveryje.", "about.domain_blocks.silenced.explanation": "Paprastai nematysi profilių ir turinio iš šio serverio, nebent jį aiškiai ieškosi arba pasirinksi jį sekdamas (-a).", @@ -33,28 +33,46 @@ "account.followers.empty": "Šio naudotojo dar niekas neseka.", "account.followers_counter": "{count, plural, one {{counter} sekėjas (-a)} few {{counter} sekėjai} many {{counter} sekėjo} other {{counter} sekėjų}}", "account.following": "Seka", - "account.follows.empty": "Šis naudotojas (-a) dar nieko neseka.", + "account.follows.empty": "Šis (-i) naudotojas (-a) dar nieko neseka.", "account.follows_you": "Seka tave", "account.go_to_profile": "Eiti į profilį", "account.in_memoriam": "Atminimui.", "account.joined_short": "Prisijungė", "account.languages": "Keisti prenumeruojamas kalbas", + "account.link_verified_on": "Šios nuorodos nuosavybė buvo patikrinta {date}", "account.locked_info": "Šios paskyros privatumo būsena nustatyta kaip užrakinta. Savininkas (-ė) rankiniu būdu peržiūri, kas gali sekti.", "account.media": "Medija", "account.mention": "Paminėti @{name}", "account.moved_to": "{name} nurodė, kad dabar jų nauja paskyra yra:", "account.mute": "Užtildyti @{name}", + "account.mute_notifications_short": "Nutildyti pranešimus", + "account.mute_short": "Nutildyti", "account.muted": "Užtildytas", - "account.posts": "Toots", - "account.posts_with_replies": "Toots and replies", - "account.report": "Pranešti apie @{name}", - "account.requested": "Awaiting approval", + "account.no_bio": "Nėra pateikto aprašymo.", + "account.open_original_page": "Atidaryti originalinį tinklalapį", + "account.posts": "Įrašai", + "account.posts_with_replies": "Įrašai ir atsakymai", + "account.report": "Pranešti @{name}", + "account.requested": "Laukiama patvirtinimo. Spausk, kad atšaukti sekimo užklausą.", + "account.requested_follow": "{name} paprašė tave sekti", + "account.share": "Bendrinti @{name} profilį", "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unblock_domain": "Unhide {domain}", "account.unblock_short": "Atblokuoti", "account.unfollow": "Nebesekti", + "account.unmute": "Atitildyti @{name}", + "account.unmute_notifications_short": "Atitildyti pranešimus", "account.unmute_short": "Atitildyti", - "account_note.placeholder": "Click to add a note", + "account_note.placeholder": "Spausk norėdamas (-a) pridėti pastabą", + "admin.dashboard.retention.average": "Vidurkis", + "admin.dashboard.retention.cohort": "Registravimo mėnuo", + "admin.dashboard.retention.cohort_size": "Nauji naudotojai", + "admin.impact_report.instance_accounts": "Paskyrų profiliai, kuriuos tai ištrintų", + "admin.impact_report.instance_followers": "Sekėjai, kuriuos prarastų mūsų naudotojai", + "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": "Spartos ribojimas", "alert.unexpected.message": "Įvyko netikėta klaida.", "alert.unexpected.title": "Ups!", "announcement.announcement": "Skelbimas", @@ -65,6 +83,14 @@ "bundle_column_error.copy_stacktrace": "Kopijuoti klaidos ataskaitą", "bundle_column_error.error.body": "Užklausos puslapio nepavyko atvaizduoti. Tai gali būti dėl mūsų kodo klaidos arba naršyklės suderinamumo problemos.", "bundle_column_error.error.title": "O, ne!", + "bundle_column_error.network.body": "Bandant užkrauti šį puslapį įvyko klaida. Tai galėjo atsitikti dėl laikinos tavo interneto ryšio arba šio serverio problemos.", + "bundle_column_error.network.title": "Tinklo klaida", + "bundle_column_error.retry": "Bandyti dar kartą", + "bundle_column_error.return": "Grįžti į pradžią", + "bundle_column_error.routing.body": "Prašyto puslapio nepavyko rasti. Ar esi tikras (-a), kad adreso juostoje nurodytas URL adresas yra teisingas?", + "bundle_column_error.routing.title": "404", + "bundle_modal_error.close": "Uždaryti", + "closed_registrations_modal.find_another_server": "Rasti kitą serverį", "column.domain_blocks": "Hidden domains", "column.lists": "Sąrašai", "column.mutes": "Užtildyti vartotojai", @@ -81,18 +107,32 @@ "compose.published.body": "Įrašas paskelbtas.", "compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.", "compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.", - "compose_form.placeholder": "What is on your mind?", + "compose_form.placeholder": "Kas tavo mintyse?", + "compose_form.poll.add_option": "Pridėti pasirinkimą", + "compose_form.poll.duration": "Apklausos trukmė", + "compose_form.poll.option_placeholder": "Pasirinkimas {number}", + "compose_form.poll.remove_option": "Pašalinti šį pasirinkimą", + "compose_form.poll.switch_to_multiple": "Keisti apklausą, kad būtų galima pasirinkti kelis pasirinkimus", "compose_form.publish_form": "Publish", "compose_form.sensitive.hide": "{count, plural, one {Žymėti mediją kaip jautrią} few {Žymėti medijas kaip jautrias} many {Žymėti medijos kaip jautrios} other {Žymėti medijų kaip jautrių}}", "compose_form.sensitive.marked": "{count, plural, one {Medija pažymėta kaip jautri} few {Medijos pažymėtos kaip jautrios} many {Medijos pažymėta kaip jautrios} other {Medijų pažymėtos kaip jautrios}}", "compose_form.sensitive.unmarked": "{count, plural, one {Medija nepažymėta kaip jautri} few {Medijos nepažymėtos kaip jautrios} many {Medijos nepažymėta kaip jautri} other {Medijų nepažymėta kaip jautrios}}", "compose_form.spoiler.marked": "Text is hidden behind warning", - "compose_form.spoiler.unmarked": "Text is not hidden", + "compose_form.spoiler.unmarked": "Pridėti turinio įspėjimą", + "compose_form.spoiler_placeholder": "Rašyk savo įspėjimą čia", + "confirmation_modal.cancel": "Atšaukti", + "confirmations.block.block_and_report": "Blokuoti ir pranešti", + "confirmations.block.confirm": "Blokuoti", + "confirmations.block.message": "Ar tikrai nori užblokuoti {name}?", "confirmations.delete.confirm": "Ištrinti", "confirmations.delete.message": "Are you sure you want to delete this status?", "confirmations.discard_edit_media.confirm": "Atmesti", "confirmations.discard_edit_media.message": "Turi neišsaugotų medijos aprašymo ar peržiūros pakeitimų, vis tiek juos atmesti?", "confirmations.domain_block.confirm": "Hide entire domain", + "confirmations.logout.confirm": "Atsijungti", + "confirmations.logout.message": "Ar tikrai nori atsijungti?", + "confirmations.mute.confirm": "Nutildyti", + "confirmations.mute.explanation": "Tai paslėps jų įrašus ir įrašus, kuriuose jie menėmi, tačiau jie vis tiek galės matyti tavo įrašus ir sekti.", "confirmations.reply.confirm": "Atsakyti", "confirmations.reply.message": "Atsakydamas (-a) dabar perrašysi šiuo metu rašomą žinutę. Ar tikrai nori tęsti?", "confirmations.unfollow.confirm": "Nebesekti", @@ -219,6 +259,8 @@ "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}", "upload_form.audio_description": "Describe for people with hearing loss", "upload_form.description": "Describe for the visually impaired", + "upload_form.description_missing": "Nėra pridėto aprašymo", + "upload_form.edit": "Redaguoti", "upload_form.video_description": "Describe for people with hearing loss or visual impairment", "upload_modal.edit_media": "Redaguoti mediją", "upload_progress.label": "Uploading…" diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index db4fe4eab2..974096d2fa 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -12,7 +12,7 @@ "about.powered_by": "由 {mastodon} 提供的去中心化社群媒體", "about.rules": "伺服器規則", "account.account_note_header": "備註", - "account.add_or_remove_from_list": "從列表中新增或移除", + "account.add_or_remove_from_list": "自列表中新增或移除", "account.badges.bot": "機器人", "account.badges.group": "群組", "account.block": "封鎖 @{name}", @@ -26,7 +26,7 @@ "account.domain_blocked": "已封鎖網域", "account.edit_profile": "編輯個人檔案", "account.enable_notifications": "當 @{name} 嘟文時通知我", - "account.endorse": "在個人檔案推薦對方", + "account.endorse": "於個人檔案推薦對方", "account.featured_tags.last_status_at": "上次嘟文於 {date}", "account.featured_tags.last_status_never": "沒有嘟文", "account.featured_tags.title": "{name} 的推薦主題標籤", @@ -65,7 +65,7 @@ "account.unblock": "解除封鎖 @{name}", "account.unblock_domain": "解除封鎖網域 {domain}", "account.unblock_short": "解除封鎖", - "account.unendorse": "取消在個人檔案推薦對方", + "account.unendorse": "取消於個人檔案推薦對方", "account.unfollow": "取消跟隨", "account.unmute": "解除靜音 @{name}", "account.unmute_notifications_short": "取消靜音推播通知", @@ -102,7 +102,7 @@ "bundle_modal_error.message": "載入此元件時發生錯誤。", "bundle_modal_error.retry": "重試", "closed_registrations.other_server_instructions": "因為 Mastodon 是去中心化的,所以您也能於其他伺服器上建立帳號,並仍然與這個伺服器互動。", - "closed_registrations_modal.description": "目前無法在 {domain} 建立新帳號,但也請別忘了,您並不一定需要有 {domain} 伺服器的帳號,也能使用 Mastodon 。", + "closed_registrations_modal.description": "目前無法於 {domain} 建立新帳號,但也請別忘了,您並不一定需要有 {domain} 伺服器的帳號,也能使用 Mastodon 。", "closed_registrations_modal.find_another_server": "尋找另一個伺服器", "closed_registrations_modal.preamble": "Mastodon 是去中心化的,所以無論您於哪個伺服器新增帳號,都可以與此伺服器上的任何人跟隨及互動。您甚至能自行架一個自己的伺服器!", "closed_registrations_modal.title": "註冊 Mastodon", @@ -171,7 +171,7 @@ "confirmations.delete_list.confirm": "刪除", "confirmations.delete_list.message": "您確定要永久刪除此列表嗎?", "confirmations.discard_edit_media.confirm": "捨棄", - "confirmations.discard_edit_media.message": "您在媒體描述或預覽區塊有未儲存的變更。是否要捨棄這些變更?", + "confirmations.discard_edit_media.message": "您於媒體描述或預覽區塊有未儲存的變更。是否要捨棄這些變更?", "confirmations.domain_block.confirm": "封鎖整個網域", "confirmations.domain_block.message": "您真的非常確定要封鎖整個 {domain} 網域嗎?大部分情況下,封鎖或靜音少數特定的帳號就能滿足需求了。您將不能在任何公開的時間軸及通知中看到來自此網域的內容。您來自該網域的跟隨者也將被移除。", "confirmations.edit.confirm": "編輯", @@ -205,7 +205,7 @@ "dismissable_banner.explore_statuses": "這些於此伺服器以及去中心化網路中其他伺服器發出的嘟文正在被此伺服器上的人們熱烈討論著。越多不同人轉嘟及最愛排名更高。", "dismissable_banner.explore_tags": "這些主題標籤正在被此伺服器以及去中心化網路上的人們熱烈討論著。越多不同人所嘟出的主題標籤排名更高。", "dismissable_banner.public_timeline": "這些是來自 {domain} 使用者們跟隨中帳號所發表之最新公開嘟文。", - "embed.instructions": "要在您的網站嵌入此嘟文,請複製以下程式碼。", + "embed.instructions": "若您欲於您的網站嵌入此嘟文,請複製以下程式碼。", "embed.preview": "它將顯示成這樣:", "emoji_button.activity": "活動", "emoji_button.clear": "清除", @@ -218,7 +218,7 @@ "emoji_button.objects": "物件", "emoji_button.people": "人物", "emoji_button.recent": "最常使用", - "emoji_button.search": "搜尋…", + "emoji_button.search": "搜尋...", "emoji_button.search_results": "搜尋結果", "emoji_button.symbols": "符號", "emoji_button.travel": "旅遊與地點", @@ -259,7 +259,7 @@ "filter_modal.added.context_mismatch_title": "不符合情境!", "filter_modal.added.expired_explanation": "此過濾器類別已失效,您需要更新過期日期以套用。", "filter_modal.added.expired_title": "過期的過濾器!", - "filter_modal.added.review_and_configure": "要檢視和進一步設定此過濾器類別,請至 {settings_link}。", + "filter_modal.added.review_and_configure": "要檢視與進一步設定此過濾器類別,請至 {settings_link}。", "filter_modal.added.review_and_configure_title": "過濾器設定", "filter_modal.added.settings_link": "設定頁面", "filter_modal.added.short_explanation": "此嘟文已被新增至以下過濾器類別:{title}。", @@ -362,7 +362,7 @@ "keyboard_shortcuts.search": "將游標移至搜尋框", "keyboard_shortcuts.spoilers": "顯示或隱藏內容警告之嘟文", "keyboard_shortcuts.start": "開啟「開始使用」欄位", - "keyboard_shortcuts.toggle_hidden": "顯示或隱藏在內容警告之後的嘟文", + "keyboard_shortcuts.toggle_hidden": "顯示或隱藏於內容警告之後的嘟文", "keyboard_shortcuts.toggle_sensitivity": "顯示或隱藏媒體", "keyboard_shortcuts.toot": "發個新嘟文", "keyboard_shortcuts.unfocus": "跳離文字撰寫區塊或搜尋框", @@ -376,7 +376,7 @@ "limited_account_hint.title": "此個人檔案已被 {domain} 的管理員隱藏。", "link_preview.author": "由 {name} 提供", "lists.account.add": "新增至列表", - "lists.account.remove": "從列表中移除", + "lists.account.remove": "自列表中移除", "lists.delete": "刪除列表", "lists.edit": "編輯列表", "lists.edit.submit": "變更標題", @@ -469,7 +469,7 @@ "notifications.permission_denied_alert": "由於之前瀏覽器權限被拒絕,無法啟用桌面通知", "notifications.permission_required": "由於尚未授予所需的權限,因此無法使用桌面通知。", "notifications_permission_banner.enable": "啟用桌面通知", - "notifications_permission_banner.how_to_control": "啟用桌面通知以在 Mastodon 沒有開啟的時候接收通知。在已經啟用桌面通知的時候,您可以透過上面的 {icon} 按鈕準確的控制哪些類型的互動會產生桌面通知。", + "notifications_permission_banner.how_to_control": "啟用桌面通知以於 Mastodon 沒有開啟的時候接收通知。啟用桌面通知後,您可以透過上面的 {icon} 按鈕準確的控制哪些類型的互動會產生桌面通知。", "notifications_permission_banner.title": "不要錯過任何東西!", "onboarding.action.back": "返回", "onboarding.actions.back": "返回", @@ -490,7 +490,7 @@ "onboarding.steps.follow_people.title": "客製化您的首頁時間軸", "onboarding.steps.publish_status.body": "向新世界打聲招呼吧。", "onboarding.steps.publish_status.title": "撰寫您第一則嘟文", - "onboarding.steps.setup_profile.body": "若您完整填寫個人檔案,其他人比較願意和您互動。", + "onboarding.steps.setup_profile.body": "若您完整填寫個人檔案,其他人比較願意與您互動。", "onboarding.steps.setup_profile.title": "客製化您的個人檔案", "onboarding.steps.share_profile.body": "讓您的朋友們知道如何於 Mastodon 找到您!", "onboarding.steps.share_profile.title": "分享您的 Mastodon 個人檔案", @@ -614,10 +614,10 @@ "sign_in_banner.create_account": "新增帳號", "sign_in_banner.sign_in": "登入", "sign_in_banner.sso_redirect": "登入或註冊", - "sign_in_banner.text": "登入以跟隨個人檔案和主題標籤,或收藏、分享和回覆嘟文。您也可以使用您的帳號在其他伺服器上進行互動。", + "sign_in_banner.text": "登入以跟隨個人檔案與主題標籤,或收藏、分享及回覆嘟文。您也可以使用您的帳號於其他伺服器進行互動。", "status.admin_account": "開啟 @{name} 的管理介面", "status.admin_domain": "開啟 {domain} 的管理介面", - "status.admin_status": "在管理介面開啟此嘟文", + "status.admin_status": "於管理介面開啟此嘟文", "status.block": "封鎖 @{name}", "status.bookmark": "書籤", "status.cancel_reblog_private": "取消轉嘟", @@ -672,8 +672,8 @@ "status.translated_from_with": "透過 {provider} 翻譯 {lang}", "status.uncached_media_warning": "無法預覽", "status.unmute_conversation": "解除此對話的靜音", - "status.unpin": "從個人檔案頁面取消釘選", - "subscribed_languages.lead": "僅選定語言的嘟文才會出現在您的首頁上,並在變更後列出時間軸。選取「無」以接收所有語言的嘟文。", + "status.unpin": "自個人檔案頁面取消釘選", + "subscribed_languages.lead": "僅選定語言的嘟文才會出現於您的首頁上,並於變更後列出時間軸。選取「無」以接收所有語言的嘟文。", "subscribed_languages.save": "儲存變更", "subscribed_languages.target": "變更 {target} 的訂閱語言", "tabs_bar.home": "首頁", @@ -696,7 +696,7 @@ "upload_area.title": "拖放來上傳", "upload_button.label": "上傳圖片、影片、或者音樂檔案", "upload_error.limit": "已達到檔案上傳限制。", - "upload_error.poll": "不允許在投票中上傳檔案。", + "upload_error.poll": "不允許於投票時上傳檔案。", "upload_form.audio_description": "為聽障人士增加文字說明", "upload_form.description": "為視障人士增加文字說明", "upload_form.description_missing": "沒有任何描述", @@ -706,7 +706,7 @@ "upload_form.video_description": "為聽障或視障人士增加文字說明", "upload_modal.analyzing_picture": "正在分析圖片…", "upload_modal.apply": "套用", - "upload_modal.applying": "正在套用⋯⋯", + "upload_modal.applying": "正在套用...", "upload_modal.choose_image": "選擇圖片", "upload_modal.description_placeholder": "我能吞下玻璃而不傷身體", "upload_modal.detect_text": "從圖片中偵測文字", diff --git a/config/locales/activerecord.lt.yml b/config/locales/activerecord.lt.yml index f54d00471b..cb6e21d8e8 100644 --- a/config/locales/activerecord.lt.yml +++ b/config/locales/activerecord.lt.yml @@ -1,6 +1,19 @@ --- lt: activerecord: + attributes: + poll: + expires_at: Galutinė data + options: Pasirinkimai + user: + agreement: Paslaugos sutartis + email: El. laiško adresas + locale: Lokali + password: Slaptažodis + user/account: + username: Naudotojo vardas + user/invite_request: + text: Priežastis errors: models: account: @@ -12,3 +25,35 @@ lt: attributes: url: invalid: nėra tinkamas URL adresas. + doorkeeper/application: + attributes: + website: + invalid: nėra tinkamas URL adresas. + import: + attributes: + data: + malformed: yra netaisyklinga. + status: + attributes: + reblog: + taken: įrašas jau egzistuoja. + user: + attributes: + email: + blocked: naudoja neleidžiamą el. laiško paslaugų teikėją. + unreachable: neatrodo, kad egzistuoja. + role_id: + elevated: negali būti didesnis nei tavo dabartinis vaidmuo. + user_role: + attributes: + permissions_as_keys: + dangerous: apima leidimus, kurie nėra saugūs pagrindiniam vaidmeniui. + elevated: negali apimti leidimų, kurių neturi tavo dabartinis vaidmuo. + own_role: negali būti pakeistas tavo dabartinis vaidmuo. + position: + elevated: negali būti didesnis nei tavo dabartinis vaidmuo. + own_role: negali būti pakeistas tavo dabartinis vaidmuo. + webhook: + attributes: + events: + invalid_permissions: negali įtraukti įvykių, į kuriuos neturi teisių. diff --git a/config/locales/be.yml b/config/locales/be.yml index 96a2720126..223b4d1dff 100644 --- a/config/locales/be.yml +++ b/config/locales/be.yml @@ -1052,7 +1052,7 @@ be: localization: body: Mastodon перакладаецца добраахвотнікамі. guide_link: https://be.crowdin.com/project/mastodon/be - guide_link_text: Кожны можа зрабіць унёсак. + guide_link_text: Кожны і кожная можа зрабіць унёсак. sensitive_content: Далікатны змест application_mailer: notification_preferences: Змяніць налады эл. пошты @@ -1575,7 +1575,7 @@ be: duration_too_short: гэта занадта хутка expired: Апытанне ўжо скончана invalid_choice: Абраны варыянт апытання не існуе - over_character_limit: не можа быць даўжэй за %{max} сімвалаў кожны + over_character_limit: кожны не можа быць даўжэй за %{max} сімвалаў self_vote: Вы не можаце галасаваць ва ўласных апытаннях too_few_options: павінна быць болей за адзін варыянт too_many_options: колькасць варыянтаў не можа перавышаць %{max} @@ -1764,7 +1764,7 @@ be: public: Публічны public_long: Усе могуць бачыць unlisted: Не ў спісе - unlisted_long: Кожны можа ўбачыць гэты допіс, але ён не паказваецца ў публічных стужках + unlisted_long: Усе могуць пабачыць гэты допіс, але ён не паказваецца ў публічных стужках statuses_cleanup: enabled: Аўтаматычна выдаляць старыя допісы enabled_hint: Аўтаматычна выдаляць вашыя допісы, калі яны дасягаюць вызначанага тэрміну, акрамя наступных выпадкаў diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 15ca094708..a04682a441 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -1074,6 +1074,10 @@ cs: hint_html: Ještě jedna věc! Musíme potvrdit, že jste člověk (to proto, abychom drželi stranou spam!). Vyřešte CAPTCHA níže a klikněte na "Pokračovat". title: Bezpečnostní kontrola confirmations: + awaiting_review_title: Vaše registrace se ověřuje + clicking_this_link: kliknutím na tento odkaz + registration_complete: Vaše registrace na %{domain} je hotová! + welcome_title: Vítám uživatele %{name}! wrong_email_hint: Pokud není e-mail správný, můžete si ho změnit v nastavení účtu. delete_account: Odstranit účet delete_account_html: Chcete-li odstranit svůj účet, pokračujte zde. Budete požádáni o potvrzení. @@ -1770,6 +1774,9 @@ cs: default: "%d. %b %Y, %H:%M" month: "%b %Y" time: "%H:%M" + translation: + errors: + too_many_requests: Na překladatelskou službu bylo zasláno v poslední době příliš mnoho požadavků. two_factor_authentication: add: Přidat disable: Vypnout 2FA diff --git a/config/locales/devise.zh-TW.yml b/config/locales/devise.zh-TW.yml index c01beb796b..de977426f2 100644 --- a/config/locales/devise.zh-TW.yml +++ b/config/locales/devise.zh-TW.yml @@ -9,7 +9,7 @@ zh-TW: already_authenticated: 您已登入。 inactive: 您的帳號尚未啟用。 invalid: 無效的 %{authentication_keys} 或密碼。 - last_attempt: 在帳號鎖定前,您還有最後一次嘗試機會。 + last_attempt: 帳號鎖定前,您還有最後一次嘗試機會。 locked: 已鎖定您的帳號。 not_found_in_database: 無效的 %{authentication_keys} 或密碼。 pending: 您的帳號仍在審核中。 @@ -20,8 +20,8 @@ zh-TW: confirmation_instructions: action: 驗證電子郵件地址 action_with_app: 確認並返回 %{app} - explanation: 您已經在 %{host} 上以此電子郵件地址建立了一支帳號。您距離啟用它只剩一點之遙了。若這不是您,請忽略此信件。 - explanation_when_pending: 您使用此電子郵件地址申請了 %{host} 的邀請。當您確認電子郵件地址後我們將審核您的申請。您可以在登入後變更詳細資訊或刪除您的帳號,但直到您的帳號被核准之前,您無法操作大部分的功能。若您的申請遭拒絕,您的資料將被移除而不必做後續動作。如果這不是您本人,請忽略此郵件。 + explanation: 您已於 %{host} 上以此電子郵件地址建立了一支帳號。您距離啟用它只剩一點之遙了。若這不是您,請忽略此信件。 + explanation_when_pending: 您使用此電子郵件地址申請了 %{host} 的邀請。當您確認電子郵件地址後我們將審核您的申請。您能於登入後變更詳細資訊或刪除您的帳號,但直到您的帳號被核准之前,您無法操作大部分的功能。若您的申請遭拒絕,您的資料將被移除而不必做後續動作。如果這不是您本人,請忽略此郵件。 extra_html: 同時也請看看伺服器規則服務條款。 subject: Mastodon:%{instance} 確認說明 title: 驗證電子郵件地址 @@ -37,7 +37,7 @@ zh-TW: title: 密碼已變更 reconfirmation_instructions: explanation: 請確認新的電子郵件地址以變更。 - extra: 若此次變更不是由您起始的,請忽略此信件。Mastodon 帳號的電子郵件地址在您存取上面的連結前不會變更。 + extra: 若此次變更不是由您起始的,請忽略此信件。Mastodon 帳號的電子郵件地址於您存取上面的連結前不會變更。 subject: Mastodon:確認 %{instance} 的電子郵件地址 title: 驗證電子郵件地址 reset_password_instructions: @@ -106,7 +106,7 @@ zh-TW: errors: messages: already_confirmed: 已經確認,請嘗試登入 - confirmation_period_expired: 需要在 %{period} 內完成驗證。請重新申請 + confirmation_period_expired: 您需要於 %{period} 內完成驗證。請重新申請 expired: 已經過期,請重新請求 not_found: 找不到 not_locked: 並未鎖定 diff --git a/config/locales/doorkeeper.zh-TW.yml b/config/locales/doorkeeper.zh-TW.yml index 6073096c31..c0d42ec7b3 100644 --- a/config/locales/doorkeeper.zh-TW.yml +++ b/config/locales/doorkeeper.zh-TW.yml @@ -72,7 +72,7 @@ zh-TW: revoke: 您確定嗎? index: authorized_at: 於 %{date} 授權 - description_html: 這些應用程式能透過 API 存取您的帳號。若有您不認得之應用程式,或應用程式行為異常,您可以於此註銷其存取權限。 + description_html: 這些應用程式能透過 API 存取您的帳號。若有您不認得之應用程式,或應用程式行為異常,您能於此註銷其存取權限。 last_used_at: 上次使用時間 %{date} never_used: 從未使用 scopes: 權限 diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 6085997248..69b1aa0a9d 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -419,8 +419,8 @@ pl: hint: Blokada domen nie zabroni tworzenia wpisów kont w bazie danych, ale pozwoli na automatyczną moderację kont do nich należących. severity: desc_html: "Wyciszenie uczyni wpisy użytkowników z tej domeny widoczne tylko dla osób, które go obserwują. Zawieszenie spowoduje usunięcie całej zawartości dodanej przez użytkownika. Użyj Żadne, jeżeli chcesz jedynie odrzucać zawartość multimedialną." - noop: Nic nie rób - silence: Limit + noop: Żadne + silence: Wycisz suspend: Zawieś title: Nowa blokada domen no_domain_block_selected: Nie zmieniono żadnych bloków domen, gdyż żadna nie została wybrana diff --git a/config/locales/simple_form.zh-TW.yml b/config/locales/simple_form.zh-TW.yml index be21f862f3..13b2ad30af 100644 --- a/config/locales/simple_form.zh-TW.yml +++ b/config/locales/simple_form.zh-TW.yml @@ -15,25 +15,25 @@ zh-TW: account_migration: acct: 指定要移動至的帳號的「使用者名稱@網域名稱」 account_warning_preset: - text: 您可使用嘟文語法,例如網址、「#」標籤和提及功能 - title: 可選。不會向收件者顯示 + text: 您可使用嘟文語法,例如網址、「#」標籤與提及功能 + title: 可選的。不會向收件者顯示 admin_account_action: include_statuses: 使用者可看到導致檢舉或警告的嘟文 send_email_notification: 使用者將收到帳號發生之事情的解釋 - text_html: 選用。您能使用嘟文語法。您可 新增警告預設 來節省時間 + text_html: 可選的。您能使用嘟文語法。您可 新增警告預設 來節省時間 type_html: 設定要使用 %{acct} 做的事 types: disable: 禁止該使用者使用他們的帳號,但是不刪除或隱藏他們的內容。 none: 使用這個寄送警告給該使用者,而不進行其他動作。 sensitive: 強制標記此使用者所有多媒體附加檔案為敏感內容。 - silence: 禁止該使用者發公開嘟文,從無跟隨他們的帳號中隱藏嘟文和通知。關閉所有對此帳號之檢舉報告。 + silence: 禁止該使用者發公開嘟文,從無跟隨他們的帳號中隱藏嘟文與通知。關閉所有對此帳號之檢舉報告。 suspend: 禁止所有對該帳號任何互動,並且刪除其內容。三十天內可以撤銷此動作。關閉所有對此帳號之檢舉報告。 - warning_preset_id: 選用。您仍可在預設的結尾新增自訂文字 + warning_preset_id: 可選的。您仍可於預設的結尾新增自訂文字 announcement: all_day: 當選取時,僅顯示出時間範圍中的日期部分 - ends_at: 可選的,公告會於該時間點自動取消發布 + ends_at: 可選的。公告會於該時間點自動取消發布 scheduled_at: 空白則立即發布公告 - starts_at: 可選的,讓公告在特定時間範圍內顯示 + starts_at: 可選的。讓公告於特定時間範圍內顯示 text: 您可以使用嘟文語法,但請小心別讓公告太鴨霸而佔據使用者的整個版面。 appeal: text: 您只能對警示提出一次申訴 @@ -44,12 +44,12 @@ zh-TW: context: 此過濾器應套用於以下一項或多項情境 current_password: 因安全因素,請輸入目前帳號的密碼 current_username: 請輸入目前帳號的使用者名稱以確認 - digest: 僅在您長時間未登入且在未登入期間收到私訊時傳送 + digest: 僅於您長時間未登入且於未登入期間收到私訊時傳送 email: 您將收到一封確認電子郵件 header: 支援 PNG、GIF 或 JPG 圖片格式,檔案最大為 %{size},會等比例縮減至 %{dimensions} 像素 inbox_url: 從您想要使用的中繼首頁複製網址 irreversible: 已過濾的嘟文將會不可逆地消失,即便之後移除過濾器也一樣 - locale: 使用者介面、電子郵件和推播通知的語言 + locale: 使用者介面、電子郵件與推播通知的語言 password: 使用至少 8 個字元 phrase: 無論是嘟文的本文或是內容警告都會被過濾 scopes: 允許讓應用程式存取的 API。 若您選擇最高階範圍,則無須選擇個別項目。 @@ -62,12 +62,12 @@ zh-TW: setting_use_blurhash: 彩色漸層圖樣是基於隱藏媒體內容顏色產生,所有細節將變得模糊 setting_use_pending_items: 關閉自動捲動更新,時間軸僅於點擊後更新 username: 您可以使用字幕、數字與底線 - whole_word: 如果關鍵字或詞組僅有字母與數字,則其將只在符合整個單字的時候才會套用 + whole_word: 如果關鍵字或詞組僅有字母與數字,則其將只於符合整個單字時才會套用 domain_allow: - domain: 此網域將能夠攫取本站資料,而自該網域發出的資料也會於本站處理和留存。 + domain: 此網域將能夠攫取本站資料,而自該網域發出的資料也會於本站處理及留存。 email_domain_block: - domain: 這可以是顯示在電子郵件中的網域名稱,或是其使用的 MX 紀錄。其將於註冊時檢查。 - with_dns_records: Mastodon 會嘗試解析所給網域的 DNS 記錄,解析結果一致者將一併封鎖 + domain: 這可以是顯示於電子郵件中的網域名稱,或是其使用的 MX 紀錄。其將於註冊時檢查。 + with_dns_records: Mastodon 會嘗試解析所提供之網域的 DNS 記錄,解析結果一致者將一併封鎖 featured_tag: name: 這些是您最近使用的一些主題標籤: filters: @@ -97,7 +97,7 @@ zh-TW: theme: 未登入之訪客或新使用者所見之佈景主題。 thumbnail: 大約 2:1 圖片會顯示於您伺服器資訊之旁。 timeline_preview: 未登入之訪客能夠瀏覽此伺服器上最新的公開嘟文。 - trendable_by_default: 跳過手動審核熱門內容。仍能在登上熱門趨勢後移除個別內容。 + trendable_by_default: 跳過手動審核熱門內容。仍能於登上熱門趨勢後移除個別內容。 trends: 熱門趨勢將顯示於您伺服器上正在吸引大量注意力的嘟文、主題標籤、或者新聞。 trends_as_landing_page: 顯示熱門趨勢內容給未登入使用者及訪客而不是關於此伺服器之描述。需要啟用熱門趨勢。 form_challenge: @@ -107,9 +107,9 @@ zh-TW: invite_request: text: 這會協助我們審核您的申請 ip_block: - comment: 可選的,但請記得您為何添加這項規則。 + comment: 可選的。但請記得您為何添加這項規則。 expires_in: IP 位址是經常共用或轉手的有限資源,不建議無限期地封鎖特定 IP 位址。 - ip: 請輸入 IPv4 或 IPv6 位址,亦可以用 CIDR 語法以封鎖整個 IP 區段。小心不要將自己給一併封鎖掉囉! + ip: 請輸入 IPv4 或 IPv6 位址,亦可以用 CIDR 語法以封鎖整個 IP 區段。小心不要將自己一併封鎖掉囉! severities: no_access: 封鎖對所有資源存取 sign_up_block: 無法註冊新帳號 @@ -129,11 +129,11 @@ zh-TW: chosen_languages: 當選取時,只有選取語言之嘟文會於公開時間軸中顯示 role: 角色控制使用者有哪些權限 user_role: - color: 在整個使用者介面中用於角色的顏色,十六進位格式的 RGB + color: 於整個使用者介面中用於角色的顏色,十六進位格式的 RGB highlighted: 這會讓角色公開可見 name: 角色的公開名稱,如果角色設定為顯示為徽章 - permissions_as_keys: 有此角色的使用者將有權存取…… - position: 在某些情況下,衝突的解決方式由更高階的角色決定。某些動作只能由優先程度較低的角色執行 + permissions_as_keys: 有此角色的使用者將有權存取... + position: 某些情況下,衝突的解決方式由更高階的角色決定。某些動作只能由優先程度較低的角色執行 webhook: events: 請選擇要傳送的事件 template: 使用變數代換組合您自己的 JSON payload。留白以使用預設 JSON 。 @@ -155,7 +155,7 @@ zh-TW: text: 預設文字 title: 標題 admin_account_action: - include_statuses: 在電子郵件中加入檢舉的嘟文 + include_statuses: 於電子郵件中加入檢舉之嘟文內容 send_email_notification: 透過電子郵件通知使用者 text: 自訂警告 type: 動作 @@ -230,7 +230,7 @@ zh-TW: username_or_email: 使用者名稱或電子郵件地址 whole_word: 整個詞彙 email_domain_block: - with_dns_records: 包括網域的 MX 記錄和 IP 位址 + with_dns_records: 包括網域的 MX 記錄與 IP 位址 featured_tag: name: "「#」主題標籤" filters: @@ -287,7 +287,7 @@ zh-TW: favourite: 當有使用者將您的嘟文加入最愛時,傳送電子郵件通知 follow: 當有使用者跟隨您時,傳送電子郵件通知 follow_request: 當有使用者請求跟隨您時,傳送電子郵件通知 - mention: 當有使用者在嘟文提及您時,傳送電子郵件通知 + mention: 當有使用者於嘟文提及您時,傳送電子郵件通知 pending_account: 有新的帳號需要審核 reblog: 當有使用者轉嘟您的嘟文時,傳送電子郵件通知 report: 新回報已遞交 @@ -304,16 +304,16 @@ zh-TW: indexable: 於搜尋引擎中包含個人檔案頁面 show_application: 顯示您發嘟文之應用程式 tag: - listable: 允許此主題標籤在搜尋及個人檔案目錄中顯示 + listable: 允許此主題標籤於搜尋及個人檔案目錄中顯示 name: 主題標籤 - trendable: 允許此主題標籤在熱門趨勢下顯示 + trendable: 允許此主題標籤於熱門趨勢下顯示 usable: 允許嘟文使用此主題標籤 user: role: 角色 time_zone: 時區 user_role: color: 識別顏色 - highlighted: 在使用者個人檔案上將角色顯示為徽章 + highlighted: 於使用者個人檔案中顯示角色徽章 name: 名稱 permissions_as_keys: 權限 position: 優先權 diff --git a/config/locales/vi.yml b/config/locales/vi.yml index ec8f6c1395..9d90d1d516 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -1343,6 +1343,7 @@ vi: '86400': 1 ngày expires_in_prompt: Không giới hạn generate: Tạo lời mời + invalid: Lời mời không hợp lệ invited_by: 'Bạn đã được mời bởi:' max_uses: other: "%{count} lần dùng" diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 3063b7afd1..7259afdbeb 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -1,10 +1,10 @@ --- zh-TW: about: - about_mastodon_html: Mastodon (長毛象)是一個自由、開放原始碼的社群網站。它是一個分散式的服務,避免您的通訊被單一商業機構壟斷操控。請您選擇一家您信任的 Mastodon 站點,在上面建立帳號,然後您就可以和任一 Mastodon 站點上的使用者互通,享受無縫的社群網路交流。 + about_mastodon_html: Mastodon (長毛象)是一個自由、開放原始碼的社群網站。它是一個分散式的服務,避免您的通訊被單一商業機構壟斷操控。請您選擇一家您信任的 Mastodon 站點,於其建立帳號,您就能與任一 Mastodon 站點上的使用者互通,享受無縫的社群網路交流。 contact_missing: 未設定 contact_unavailable: 未公開 - hosted_on: 在 %{domain} 運作的 Mastodon 站點 + hosted_on: 於 %{domain} 託管之 Mastodon 站點 title: 關於本站 accounts: follow: 跟隨 @@ -13,7 +13,7 @@ zh-TW: following: 正在跟隨 instance_actor_flash: 此帳號是用來代表此伺服器的虛擬執行者,而非個別使用者。它的用途為維繫聯邦宇宙,且不應被停權。 last_active: 上次活躍時間 - link_verified_on: 此連結的所有權已在 %{date} 檢查過 + link_verified_on: 此連結之所有權已於 %{date} 檢查過 nothing_here: 暫時沒有內容可供顯示! pin_errors: following: 您只能推薦您正在跟隨的使用者。 @@ -115,7 +115,7 @@ zh-TW: reject: 拒絕 rejected_msg: 已成功婉拒 %{username} 的新帳號申請 remote_suspension_irreversible: 此帳號之資料已被不可逆地刪除。 - remote_suspension_reversible_hint_html: 這個帳號已於此伺服器被停權,所有資料將會於 %{date} 被刪除。在此之前,遠端伺服器可以完全回復此的帳號。如果您想即時刪除這個帳號的資料,您可以在下面進行操作。 + remote_suspension_reversible_hint_html: 這個帳號已於此伺服器被停權,所有資料將會於 %{date} 被刪除。於此之前,遠端伺服器可以完全回復此的帳號。如果您想即時刪除這個帳號的資料,您能於下面進行操作。 remove_avatar: 取消大頭貼 remove_header: 移除開頭 removed_avatar_msg: 已成功刪除 %{username} 的大頭貼 @@ -149,7 +149,7 @@ zh-TW: suspend: 停權 suspended: 已停權 suspension_irreversible: 已永久刪除此帳號的資料。您可以取消這個帳號的停權狀態,但無法還原已刪除的資料。 - suspension_reversible_hint_html: 這個帳號已被暫停,所有數據將於 %{date} 被刪除。在此之前,您可以完全回復您的帳號。如果您想即時刪除這個帳號的數據,您可以在下面進行操作。 + suspension_reversible_hint_html: 這個帳號已被暫停,所有數據將於 %{date} 被刪除。於此之前,您可以完全回復您的帳號。如果您想即時刪除這個帳號的數據,您能於下面進行操作。 title: 帳號 unblock_email: 解除封鎖電子郵件地址 unblocked_email_msg: 成功解除封鎖 %{username} 的電子郵件地址 @@ -333,7 +333,7 @@ zh-TW: not_permitted: 您無權執行此操作 overwrite: 覆蓋 shortcode: 短代碼 - shortcode_hint: 至少 2 個字元,只能使用字母、數字和下劃線 + shortcode_hint: 至少 2 個字元,只能使用字母、數字與下劃線 title: 自訂表情符號 uncategorized: 未分類 unlist: 不公開 @@ -370,10 +370,10 @@ zh-TW: domain_allows: add_new: 將網域加入聯邦宇宙白名單 created_msg: 網域已成功加入聯邦宇宙白名單 - destroyed_msg: 網域已成功從聯邦宇宙白名單移除 + destroyed_msg: 網域已成功自聯邦宇宙白名單移除 export: 匯出 import: 匯入 - undo: 從聯邦宇宙白名單移除 + undo: 自聯邦宇宙白名單移除 domain_blocks: add_new: 新增網域黑名單 confirm_suspension: @@ -397,7 +397,7 @@ zh-TW: create: 新增封鎖 hint: 站點封鎖動作並不會阻止帳號紀錄被新增至資料庫,但會自動回溯性地對那些帳號套用特定管理設定。 severity: - desc_html: "「靜音」令該站點下使用者的嘟文,設定為只對跟隨者顯示,沒有跟隨的人會看不到。「停權」會刪除將該站點下使用者的嘟文、媒體檔案和個人檔案。「」則會拒絕接收來自該站點的媒體檔案。" + desc_html: "「靜音」令該站點下使用者的嘟文,設定為只對跟隨者顯示,沒有跟隨的人會看不到。「停權」會刪除將該站點下使用者的嘟文、媒體檔案與個人檔案。「」則會拒絕接收來自該站點的媒體檔案。" noop: 無 silence: 靜音 suspend: 停權 @@ -451,7 +451,7 @@ zh-TW: title: 匯入網域黑名單 no_file: 尚未選擇檔案 follow_recommendations: - description_html: "跟隨建議幫助新使用者們快速找到有趣的內容。當使用者沒有與其他帳號有足夠多的互動以建立個人化跟隨建議時,這些帳號將會被推薦。這些帳號將基於某選定語言之高互動和高本地跟隨者數量帳號而每日重新更新。" + description_html: "跟隨建議幫助新使用者們快速找到有趣的內容。當使用者沒有與其他帳號有足夠多的互動以建立個人化跟隨建議時,這些帳號將會被推薦。這些帳號將基於某選定語言之高互動與高本地跟隨者數量帳號而每日重新更新。" language: 對於語言 status: 狀態 suppress: 取消跟隨建議 @@ -461,7 +461,7 @@ zh-TW: instances: availability: description_html: - other: 若在%{count}天向某個網域遞送失敗,除非收到某個網域的遞送表單,否則不會繼續嘗試遞送。 + other: 若於 %{count} 天向某個網域遞送失敗,除非收到某個網域的遞送表單,否則不會繼續嘗試遞送。 failure_threshold_reached: 錯誤門檻於 %{date}。 failures_recorded: other: 錯誤嘗試於 %{count} 天。 @@ -590,7 +590,7 @@ zh-TW: by_target_domain: 檢舉帳號之網域 cancel: 取消 category: 分類 - category_description_html: 此帳號及/或被檢舉內容之原因會被引用在檢舉帳號通知中 + category_description_html: 此帳號及/或被檢舉內容之原因將被引用於檢舉帳號通知中 comment: none: 無 comment_description_html: 提供更多資訊,%{name} 寫道: @@ -611,7 +611,7 @@ zh-TW: delete: 刪除 placeholder: 記錄已執行的動作,或其他相關的更新... title: 備註 - notes_description_html: 檢視及留下些給其他管理員和未來的自己的備註 + notes_description_html: 檢視及留下些給其他管理員與未來的自己的備註 processed_msg: '檢舉報告 #%{id} 已被成功處理' quick_actions_description_html: 採取一個快速行動,或者下捲以檢視檢舉內容: remote_user_placeholder: 來自 %{instance} 之遠端使用者 @@ -624,7 +624,7 @@ zh-TW: skip_to_actions: 跳過行動 status: 嘟文 statuses: 被檢舉的內容 - statuses_description_html: 侵犯性違規內容會被引用在檢舉帳號通知中 + statuses_description_html: 侵犯性違規內容將被引用於檢舉帳號通知中 summary: action_preambles: delete_html: 您將要 移除 某些 @%{acct} 之嘟文。此將會: @@ -677,7 +677,7 @@ zh-TW: manage_announcements: 管理公告 manage_announcements_description: 允許使用者管理伺服器上的公告 manage_appeals: 管理解封申訴系統 - manage_appeals_description: 允許使用者審閱針對站務動作的申訴 + manage_appeals_description: 允許使用者審閱針對站務動作之申訴 manage_blocks: 管理封鎖 manage_blocks_description: 允許使用者封鎖電子郵件提供者與 IP 位置 manage_custom_emojis: 管理自訂表情符號 @@ -741,7 +741,7 @@ zh-TW: title: 預設將使用者排除於搜尋引擎索引 discovery: follow_recommendations: 跟隨建議 - preamble: 呈現有趣的內容有助於 Mastodon 上一人不識的新手上路。控制各種不同的分類在您伺服器上如何被探索到。 + preamble: 呈現有趣的內容有助於 Mastodon 上一人不識的新手上路。控制各種不同的分類於您伺服器上如何被探索到。 profile_directory: 個人檔案目錄 public_timelines: 公開時間軸 publish_discovered_servers: 公開已知伺服器列表 @@ -989,11 +989,11 @@ zh-TW: created_msg: 成功建立別名。您可以自舊帳號開始轉移。 deleted_msg: 成功移除別名。您將無法再由舊帳號轉移至目前的帳號。 empty: 您目前沒有任何別名。 - hint_html: 如果想由其他帳號轉移至此帳號,您可以於此處新增別名,稍後系統將容許您將跟隨者由舊帳號轉移至此。此項作業是無害且可復原的帳號的遷移程序需要在舊帳號啟動。 + hint_html: 如果想由其他帳號轉移至此帳號,您能於此處新增別名,稍後系統將容許您將跟隨者由舊帳號轉移至此。此項作業是無害且可復原的帳號的遷移程序需要於舊帳號啟動。 remove: 取消連結別名 appearance: advanced_web_interface: 進階網頁介面 - advanced_web_interface_hint: 進階網頁介面可讓您設定許多不同的欄位來善用螢幕空間,依需要同時查看許多不同的資訊如:首頁、通知、聯邦宇宙時間軸、任意數量的列表和主題標籤。 + advanced_web_interface_hint: 進階網頁介面可讓您設定許多不同的欄位來善用螢幕空間,依需要同時查看許多不同的資訊如:首頁、通知、聯邦宇宙時間軸、任意數量的列表與主題標籤。 animations_and_accessibility: 動畫與無障礙設定 confirmation_dialogs: 確認對話框 discovery: 探索 @@ -1033,13 +1033,13 @@ zh-TW: redirect_to_app_html: 您應被重新導向至 %{app_name} 應用程式。如尚未重新導向,請嘗試 %{clicking_this_link} 或手動回到應用程式。 registration_complete: 您於 %{domain} 之註冊申請已完成! welcome_title: 歡迎,%{name}! - wrong_email_hint: 若電子郵件地址不正確,您可以於帳號設定中更改。 + wrong_email_hint: 若電子郵件地址不正確,您能於帳號設定中更改。 delete_account: 刪除帳號 delete_account_html: 如果您欲刪除您的帳號,請點擊這裡繼續。您需要再三確認您的操作。 description: prefix_invited_by_user: "@%{name} 邀請您加入這個 Mastodon 伺服器!" prefix_sign_up: 馬上註冊 Mastodon 帳號吧! - suffix: 有了帳號,就可以從任何 Mastodon 伺服器跟隨任何人、發發廢嘟,並且與任何 Mastodon 伺服器的使用者交流,以及更多! + suffix: 有了帳號,就可以自任何 Mastodon 伺服器跟隨任何人、發發廢嘟,並且與任何 Mastodon 伺服器的使用者交流,以及更多! didnt_get_confirmation: 沒有收到確認連結嗎? dont_have_your_security_key: 找不到您的安全金鑰? forgot_password: 忘記密碼? @@ -1085,7 +1085,7 @@ zh-TW: preamble_html: 請使用您於 %{domain} 的帳號密碼登入。若您的帳號託管於其他伺服器,您將無法於此登入。 title: 登入 %{domain} sign_up: - manual_review: "%{domain} 上的註冊由我們的管理員進行人工審核。為協助我們處理您的註冊,請寫一些關於您自己的資訊以及您想要在 %{domain} 上註冊帳號的原因。" + manual_review: "%{domain} 上的註冊由我們的管理員進行人工審核。為協助我們處理您的註冊,請寫一些關於您自己的資訊以及您欲於 %{domain} 上註冊帳號之原因。" preamble: 於此 Mastodon 伺服器擁有帳號的話,您將能跟隨聯邦宇宙網路中任何一份子,無論他們的帳號託管於何處。 title: 讓我們一起設定 %{domain} 吧! status: @@ -1100,7 +1100,7 @@ zh-TW: use_security_key: 使用安全金鑰 challenge: confirm: 繼續 - hint_html: "温馨小提醒: 我們在接下來一小時內不會再要求您輸入密碼。" + hint_html: "温馨小提醒: 我們於接下來一小時內不會再要求您輸入密碼。" invalid_password: 密碼錯誤 prompt: 輸入密碼以繼續 crypto: @@ -1134,8 +1134,8 @@ zh-TW: warning: before: 在進行下一步驟之前,請詳細閱讀以下説明: caches: 已被其他節點快取的內容可能會殘留其中 - data_removal: 您的嘟文和其他資料將會被永久刪除 - email_change_html: 您可以在不刪除帳號的情況下變更您的電子郵件地址 + data_removal: 您的嘟文與其他資料將被永久刪除 + email_change_html: 您能於不刪除帳號的情況下變更您的電子郵件地址 email_contact_html: 如果您仍然沒有收到郵件,請寄信至 %{email} 以獲得協助 email_reconfirmation_html: 如果您沒有收到確認郵件,可以請求再次發送 irreversible: 您將無法復原或重新啟用您的帳號 @@ -1176,7 +1176,7 @@ zh-TW: invalid_domain: 並非一個有效網域 edit_profile: basic_information: 基本資訊 - hint_html: "自訂人們可以於您個人檔案及嘟文內容。當您完成填寫個人檔案以及設定大頭貼後,其他人們比較願意跟隨您並與您互動。" + hint_html: "自訂人們能於您個人檔案及嘟文旁所見之內容。當您完成填寫個人檔案以及設定大頭貼後,其他人們比較願意跟隨您並與您互動。" other: 其他 errors: '400': 您所送出的請求無效或格式不正確。 @@ -1194,13 +1194,13 @@ zh-TW: '503': 此頁面因伺服器暫時發生錯誤而無法提供。 noscript_html: 使用 Mastodon 網頁版應用需要啟用 JavaScript。您也可以選擇適用於您的平台的 Mastodon 應用。 existing_username_validator: - not_found: 無法在本站找到這個名稱的使用者 + not_found: 無法於本伺服器找到此使用者帳號 not_found_multiple: 揣嘸 %{usernames} exports: archive_takeout: date: 日期 download: 下載檔案 - hint_html: 您可以下載包含您的文章和媒體的檔案。資料以 ActivityPub 格式儲存,可用於相容的軟體。每次允許存檔的間隔至少 7 天。 + hint_html: 您可以下載包含您的嘟文與媒體的檔案。資料以 ActivityPub 格式儲存,可用於相容之軟體。每次允許存檔的間隔至少 7 天。 in_progress: 正在準備您的存檔... request: 下載存檔 size: 大小 @@ -1304,7 +1304,7 @@ zh-TW: following_html: 您將要 跟隨%{filename} 中之 %{total_items} 個帳號。 lists_html: 您將自 %{filename} 新增 %{total_items} 個帳號至您的列表。若不存在列表用以新增帳號,則會建立新列表。 muting_html: 您將要 靜音%{filename} 中之 %{total_items} 個帳號。 - preface: 您能於此匯入您在其他伺服器所匯出的資料檔,包括跟隨中的使用者、封鎖的使用者名單等。 + preface: 您能於此匯入您於其他伺服器所匯出的資料檔,包括跟隨中的使用者、封鎖的使用者名單等。 recent_imports: 最近匯入的 states: finished: 已完成 @@ -1414,12 +1414,12 @@ zh-TW: warning: backreference_required: 新的帳號必須先設定為反向參照到目前帳號 before: 在進行下一步驟之前,請詳細閱讀以下説明: - cooldown: 在轉移帳號後會有一段等待時間,在等待時間內您將無法再次轉移 + cooldown: 轉移帳號後會有一段等待時間,等待時間內您將無法再次轉移 disabled_account: 之後您的目前帳號將完全無法使用。但您可以存取資料匯出與重新啟用。 followers: 此動作將會將目前帳號的所有跟隨者轉移至新帳號 - only_redirect_html: 或者,您也可以僅在您的個人檔案中設定重新導向。 + only_redirect_html: 或者,您也可以僅於您的個人檔案中設定重新導向。 other_data: 其他資料並不會自動轉移 - redirect: 您目前的帳號將於個人檔案頁面新增重新導向公告,並會被排除在搜尋結果之外 + redirect: 您目前的帳號將於個人檔案頁面新增重新導向公告,並會被排除於搜尋結果之外 moderation: title: 站務 move_handler: @@ -1449,8 +1449,8 @@ zh-TW: title: 新的跟隨請求 mention: action: 回覆 - body: "%{name} 在嘟文中提及您:" - subject: "%{name} 在嘟文中提及您" + body: "%{name} 於嘟文中提及您:" + subject: "%{name} 於嘟文中提及您" title: 新的提及 poll: subject: 由 %{name} 發起的投票已結束 @@ -1510,7 +1510,7 @@ zh-TW: privacy: hint_html: "自訂您希望如何讓您的個人檔案及嘟文被發現。藉由啟用一系列 Mastodon 功能以幫助您觸及更廣的受眾。煩請花些時間確認您是否欲啟用這些設定。" privacy: 隱私權 - privacy_hint_html: 控制您希望向其他人揭露之內容。人們透過瀏覽其他人的跟隨者與其發嘟之應用程式發現有趣的個人檔案和酷炫的 Mastodon 應用程式,但您能選擇將其隱藏。 + privacy_hint_html: 控制您希望向其他人揭露之內容。人們透過瀏覽其他人的跟隨者與其發嘟之應用程式發現有趣的個人檔案與酷炫的 Mastodon 應用程式,但您能選擇將其隱藏。 reach: 觸及 reach_hint_html: 控制您希望被新使用者探索或跟隨之方式。想讓您的嘟文出現於探索頁面嗎?想讓其他人透過他們的跟隨建議找到您嗎?想自動接受所有新跟隨者嗎?或是想逐一控制跟隨請求嗎? search: 搜尋 @@ -1669,7 +1669,7 @@ zh-TW: private_long: 只有跟隨您的人能看到 public: 公開 public_long: 所有人都能看到 - unlisted: 不在公開時間軸顯示 + unlisted: 不於公開時間軸顯示 unlisted_long: 所有人都能看到,但不會出現在公開時間軸上 statuses_cleanup: enabled: 自動刪除舊嘟文 @@ -1679,7 +1679,7 @@ zh-TW: ignore_favs: 忽略最愛數 ignore_reblogs: 忽略轉嘟數 interaction_exceptions: 基於互動的例外規則 - interaction_exceptions_explanation: 請注意嘟文是無法保證被刪除的,如果在一次處理過後嘟文低於最愛或轉嘟的門檻。 + interaction_exceptions_explanation: 請注意嘟文是無法保證被刪除的,如果於一次處理過後嘟文低於最愛或轉嘟的門檻。 keep_direct: 保留私訊 keep_direct_hint: 不會刪除任何您的私訊 keep_media: 保留包含多媒體附加檔案之嘟文 @@ -1735,7 +1735,7 @@ zh-TW: enabled: 兩階段認證已啟用 enabled_success: 已成功啟用兩階段認證 generate_recovery_codes: 產生備用驗證碼 - lost_recovery_codes: 讓您可以在遺失手機時,使用備用驗證碼登入。若您已遺失備用驗證碼,可於此產生一批新的,舊有的備用驗證碼將會失效。 + lost_recovery_codes: 讓您能於遺失手機時,使用備用驗證碼登入。若您已遺失備用驗證碼,可於此產生一批新的,舊有的備用驗證碼將會失效。 methods: 兩步驟方式 otp: 驗證應用程式 recovery_codes: 備份備用驗證碼 @@ -1745,12 +1745,12 @@ zh-TW: user_mailer: appeal_approved: action: 前往您的帳號 - explanation: 您在 %{appeal_date} 遞交的針對您帳號的 %{strike_date} 警示的申訴已獲批准。您的帳號再次享有良好的信譽。 - subject: 您在 %{date} 提出的申訴已獲批准 + explanation: 您於 %{appeal_date} 遞交的針對您帳號的 %{strike_date} 警示之申訴已獲批准。您的帳號再次享有良好的信譽。 + subject: 您於 %{date} 提出之申訴已獲批准 title: 申訴已批准 appeal_rejected: - explanation: 您在 %{appeal_date} 遞交的針對您帳號的 %{strike_date} 警示的申訴已被駁回。 - subject: 您在 %{date} 提出的申訴已被駁回 + explanation: 您於 %{appeal_date} 遞交的針對您帳號的 %{strike_date} 警示之申訴已被駁回。 + subject: 您於 %{date} 提出之申訴已被駁回 title: 申訴被駁回 backup_ready: explanation: 您要求的 Mastodon 帳號完整備份檔案現已就緒,可供下載! @@ -1772,7 +1772,7 @@ zh-TW: explanation: delete_statuses: 您的某些嘟文被發現已違反一項或多項社群準則,隨後已被 %{instance} 的管理員刪除。 disable: 您無法繼續使用您的帳號,但您的個人頁面及其他資料內容保持不變。您可以要求一份您的資料備份,帳號異動設定,或是刪除帳號。 - mark_statuses_as_sensitive: 您的部份嘟文已被 %{instance} 的管理員標記為敏感內容。這代表了人們必須在顯示預覽前點擊嘟文中的媒體。您可以在將來嘟文時自己將媒體標記為敏感內容。 + mark_statuses_as_sensitive: 您的部份嘟文已被 %{instance} 的管理員標記為敏感內容。這代表了人們必須於顯示預覽前點擊嘟文中的媒體。您能於將來嘟文時自己將媒體標記為敏感內容。 sensitive: 由此刻起,您所有上傳的媒體檔案將被標記為敏感內容,並且隱藏於點擊警告之後。 silence: 您仍然能使用您的帳號,但僅有已跟隨您的人才能見到您於此伺服器之嘟文,您也可能會從各式探索功能中被排除。但其他人仍可手動跟隨您。 suspend: 您將不能使用您的帳號,您的個人檔案頁面及其他資料將不再能被存取。您仍可於約 30 日內資料被完全刪除前要求下載您的資料,但我們仍會保留一部份基本資料,以防止有人規避停權處罰。 @@ -1781,9 +1781,9 @@ zh-TW: subject: delete_statuses: 您於 %{acct} 之嘟文已被移除 disable: 您的帳號 %{acct} 已被凍結 - mark_statuses_as_sensitive: 您在 %{acct} 上的嘟文已被標記為敏感內容 + mark_statuses_as_sensitive: 您於 %{acct} 上的嘟文已被標記為敏感內容 none: 對 %{acct} 的警告 - sensitive: 從現在開始,您在 %{acct} 上的嘟文將會被標記為敏感內容 + sensitive: 從現在開始,您於 %{acct} 上之嘟文將會被標記為敏感內容 silence: 您的帳號 %{acct} 已被限制 suspend: 您的帳號 %{acct} 已被停權 title: @@ -1796,10 +1796,10 @@ zh-TW: suspend: 帳號己被停權 welcome: edit_profile_action: 設定個人檔案 - edit_profile_step: 您可以設定您的個人檔案,包括上傳大頭貼、變更顯示名稱等等。您也可以選擇在新的跟隨者跟隨前,先對他們進行審核。 + edit_profile_step: 您可以設定您的個人檔案,包括上傳大頭貼、變更顯示名稱等等。您也可以選擇於新的跟隨者跟隨前,先對他們進行審核。 explanation: 下面是幾個小幫助,希望它們能幫到您 final_action: 開始嘟嘟 - final_step: '開始嘟嘟吧!即使您現在沒有跟隨者,其他人仍然能在本站時間軸、主題標籤等地方,看到您的公開嘟文。試著用 #introductions 這個主題標籤介紹一下自己吧。' + final_step: '開始嘟嘟吧!即使您現在沒有跟隨者,其他人仍然能於本站時間軸、主題標籤等地方,看到您的公開嘟文。試著用 #introductions 這個主題標籤介紹一下自己吧。' full_handle: 您的完整帳號名稱 full_handle_hint: 您需要將這告訴您的朋友們,這樣他們就能從另一個伺服器向您發送訊息或跟隨您。 subject: 歡迎來到 Mastodon From d67bd44ca1542d665354e733b632c841b6b7d29b Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 15 Nov 2023 12:13:53 +0100 Subject: [PATCH 012/106] Add profile setup to onboarding in web UI (#27829) --- .../api/v1/accounts/credentials_controller.rb | 2 + app/javascript/mastodon/actions/accounts.js | 15 ++ app/javascript/mastodon/api_types/accounts.ts | 1 + .../mastodon/components/admin/Retention.jsx | 2 +- .../mastodon/components/loading_indicator.tsx | 26 ++- .../components/progress_indicator.jsx | 29 --- .../features/onboarding/components/step.jsx | 15 +- .../mastodon/features/onboarding/follows.jsx | 105 ++++------ .../mastodon/features/onboarding/index.jsx | 190 ++++++------------ .../mastodon/features/onboarding/profile.jsx | 162 +++++++++++++++ .../mastodon/features/onboarding/share.jsx | 100 ++++----- app/javascript/mastodon/features/ui/index.jsx | 2 +- app/javascript/mastodon/locales/en.json | 13 +- app/javascript/mastodon/models/account.ts | 1 + .../styles/mastodon/components.scss | 164 +++++---------- app/javascript/styles/mastodon/forms.scss | 105 ++++++++-- app/serializers/rest/account_serializer.rb | 6 +- config/routes.rb | 2 +- 18 files changed, 524 insertions(+), 416 deletions(-) delete mode 100644 app/javascript/mastodon/features/onboarding/components/progress_indicator.jsx create mode 100644 app/javascript/mastodon/features/onboarding/profile.jsx diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb index 76ba758245..8f31336b9f 100644 --- a/app/controllers/api/v1/accounts/credentials_controller.rb +++ b/app/controllers/api/v1/accounts/credentials_controller.rb @@ -16,6 +16,8 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController current_user.update(user_params) if user_params ActivityPub::UpdateDistributionWorker.perform_async(@account.id) render json: @account, serializer: REST::CredentialAccountSerializer + rescue ActiveRecord::RecordInvalid => e + render json: ValidationErrorFormatter.new(e).as_json, status: 422 end private diff --git a/app/javascript/mastodon/actions/accounts.js b/app/javascript/mastodon/actions/accounts.js index e0448f004c..9f3bbba033 100644 --- a/app/javascript/mastodon/actions/accounts.js +++ b/app/javascript/mastodon/actions/accounts.js @@ -661,3 +661,18 @@ export function unpinAccountFail(error) { error, }; } + +export const updateAccount = ({ displayName, note, avatar, header, discoverable, indexable }) => (dispatch, getState) => { + const data = new FormData(); + + data.append('display_name', displayName); + data.append('note', note); + if (avatar) data.append('avatar', avatar); + if (header) data.append('header', header); + data.append('discoverable', discoverable); + data.append('indexable', indexable); + + return api(getState).patch('/api/v1/accounts/update_credentials', data).then(response => { + dispatch(importFetchedAccount(response.data)); + }); +}; diff --git a/app/javascript/mastodon/api_types/accounts.ts b/app/javascript/mastodon/api_types/accounts.ts index 985abf9463..5bf3e64288 100644 --- a/app/javascript/mastodon/api_types/accounts.ts +++ b/app/javascript/mastodon/api_types/accounts.ts @@ -20,6 +20,7 @@ export interface ApiAccountJSON { bot: boolean; created_at: string; discoverable: boolean; + indexable: boolean; display_name: string; emojis: ApiCustomEmojiJSON[]; fields: ApiAccountFieldJSON[]; diff --git a/app/javascript/mastodon/components/admin/Retention.jsx b/app/javascript/mastodon/components/admin/Retention.jsx index 2f56710682..1e8ef48b7a 100644 --- a/app/javascript/mastodon/components/admin/Retention.jsx +++ b/app/javascript/mastodon/components/admin/Retention.jsx @@ -51,7 +51,7 @@ export default class Retention extends PureComponent { let content; if (loading) { - content = ; + content = ; } else { content = ( diff --git a/app/javascript/mastodon/components/loading_indicator.tsx b/app/javascript/mastodon/components/loading_indicator.tsx index 6bc24a0d61..fcdbe80d8a 100644 --- a/app/javascript/mastodon/components/loading_indicator.tsx +++ b/app/javascript/mastodon/components/loading_indicator.tsx @@ -1,7 +1,23 @@ +import { useIntl, defineMessages } from 'react-intl'; + import { CircularProgress } from './circular_progress'; -export const LoadingIndicator: React.FC = () => ( -
- -
-); +const messages = defineMessages({ + loading: { id: 'loading_indicator.label', defaultMessage: 'Loading…' }, +}); + +export const LoadingIndicator: React.FC = () => { + const intl = useIntl(); + + return ( +
+ +
+ ); +}; diff --git a/app/javascript/mastodon/features/onboarding/components/progress_indicator.jsx b/app/javascript/mastodon/features/onboarding/components/progress_indicator.jsx deleted file mode 100644 index 37288a286f..0000000000 --- a/app/javascript/mastodon/features/onboarding/components/progress_indicator.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import PropTypes from 'prop-types'; -import { Fragment } from 'react'; - -import classNames from 'classnames'; - -import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/done.svg'; - -import { Icon } from 'mastodon/components/icon'; - -const ProgressIndicator = ({ steps, completed }) => ( -
- {(new Array(steps)).fill().map((_, i) => ( - - {i > 0 &&
i })} />} - -
i })}> - {completed > i && } -
- - ))} -
-); - -ProgressIndicator.propTypes = { - steps: PropTypes.number.isRequired, - completed: PropTypes.number, -}; - -export default ProgressIndicator; diff --git a/app/javascript/mastodon/features/onboarding/components/step.jsx b/app/javascript/mastodon/features/onboarding/components/step.jsx index 1f42d9d499..1f83f20801 100644 --- a/app/javascript/mastodon/features/onboarding/components/step.jsx +++ b/app/javascript/mastodon/features/onboarding/components/step.jsx @@ -1,11 +1,13 @@ import PropTypes from 'prop-types'; +import { Link } from 'react-router-dom'; + import { ReactComponent as ArrowRightAltIcon } from '@material-symbols/svg-600/outlined/arrow_right_alt.svg'; import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/done.svg'; -import { Icon } from 'mastodon/components/icon'; +import { Icon } from 'mastodon/components/icon'; -const Step = ({ label, description, icon, iconComponent, completed, onClick, href }) => { +export const Step = ({ label, description, icon, iconComponent, completed, onClick, href, to }) => { const content = ( <>
@@ -29,6 +31,12 @@ const Step = ({ label, description, icon, iconComponent, completed, onClick, hre {content} ); + } else if (to) { + return ( + + {content} + + ); } return ( @@ -45,7 +53,6 @@ Step.propTypes = { iconComponent: PropTypes.func, completed: PropTypes.bool, href: PropTypes.string, + to: PropTypes.string, onClick: PropTypes.func, }; - -export default Step; diff --git a/app/javascript/mastodon/features/onboarding/follows.jsx b/app/javascript/mastodon/features/onboarding/follows.jsx index e21c7c75b6..e23a335c06 100644 --- a/app/javascript/mastodon/features/onboarding/follows.jsx +++ b/app/javascript/mastodon/features/onboarding/follows.jsx @@ -1,79 +1,62 @@ -import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; +import { useEffect } from 'react'; import { FormattedMessage } from 'react-intl'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { connect } from 'react-redux'; +import { Link } from 'react-router-dom'; + +import { useDispatch } from 'react-redux'; + import { fetchSuggestions } from 'mastodon/actions/suggestions'; import { markAsPartial } from 'mastodon/actions/timelines'; -import Column from 'mastodon/components/column'; import { ColumnBackButton } from 'mastodon/components/column_back_button'; import { EmptyAccount } from 'mastodon/components/empty_account'; import Account from 'mastodon/containers/account_container'; +import { useAppSelector } from 'mastodon/store'; -const mapStateToProps = state => ({ - suggestions: state.getIn(['suggestions', 'items']), - isLoading: state.getIn(['suggestions', 'isLoading']), -}); +export const Follows = () => { + const dispatch = useDispatch(); + const isLoading = useAppSelector(state => state.getIn(['suggestions', 'isLoading'])); + const suggestions = useAppSelector(state => state.getIn(['suggestions', 'items'])); -class Follows extends PureComponent { - - static propTypes = { - onBack: PropTypes.func, - dispatch: PropTypes.func.isRequired, - suggestions: ImmutablePropTypes.list, - isLoading: PropTypes.bool, - }; - - componentDidMount () { - const { dispatch } = this.props; + useEffect(() => { dispatch(fetchSuggestions(true)); + + return () => { + dispatch(markAsPartial('home')); + }; + }, [dispatch]); + + let loadedContent; + + if (isLoading) { + loadedContent = (new Array(8)).fill().map((_, i) => ); + } else if (suggestions.isEmpty()) { + loadedContent =
; + } else { + loadedContent = suggestions.map(suggestion => ); } - componentWillUnmount () { - const { dispatch } = this.props; - dispatch(markAsPartial('home')); - } + return ( + <> + - render () { - const { onBack, isLoading, suggestions } = this.props; - - let loadedContent; - - if (isLoading) { - loadedContent = (new Array(8)).fill().map((_, i) => ); - } else if (suggestions.isEmpty()) { - loadedContent =
; - } else { - loadedContent = suggestions.map(suggestion => ); - } - - return ( - - - -
-
-

-

-
- -
- {loadedContent} -
- -

{chunks} }} />

- -
- -
+
+
+

+

- - ); - } -} +
+ {loadedContent} +
-export default connect(mapStateToProps)(Follows); +

{chunks} }} />

+ +
+ +
+
+ + ); +}; diff --git a/app/javascript/mastodon/features/onboarding/index.jsx b/app/javascript/mastodon/features/onboarding/index.jsx index 51d4b71f24..51677fbc7a 100644 --- a/app/javascript/mastodon/features/onboarding/index.jsx +++ b/app/javascript/mastodon/features/onboarding/index.jsx @@ -1,152 +1,90 @@ -import PropTypes from 'prop-types'; +import { useCallback } from 'react'; -import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; +import { FormattedMessage, useIntl, defineMessages } from 'react-intl'; import { Helmet } from 'react-helmet'; -import { Link, withRouter } from 'react-router-dom'; +import { Link, Switch, Route, useHistory } from 'react-router-dom'; + +import { useDispatch } from 'react-redux'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { connect } from 'react-redux'; import { ReactComponent as AccountCircleIcon } from '@material-symbols/svg-600/outlined/account_circle.svg'; import { ReactComponent as ArrowRightAltIcon } from '@material-symbols/svg-600/outlined/arrow_right_alt.svg'; import { ReactComponent as ContentCopyIcon } from '@material-symbols/svg-600/outlined/content_copy.svg'; import { ReactComponent as EditNoteIcon } from '@material-symbols/svg-600/outlined/edit_note.svg'; import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add.svg'; -import { debounce } from 'lodash'; import illustration from 'mastodon/../images/elephant_ui_conversation.svg'; -import { fetchAccount } from 'mastodon/actions/accounts'; import { focusCompose } from 'mastodon/actions/compose'; -import { closeOnboarding } from 'mastodon/actions/onboarding'; import { Icon } from 'mastodon/components/icon'; import Column from 'mastodon/features/ui/components/column'; import { me } from 'mastodon/initial_state'; -import { makeGetAccount } from 'mastodon/selectors'; +import { useAppSelector } from 'mastodon/store'; import { assetHost } from 'mastodon/utils/config'; -import { WithRouterPropTypes } from 'mastodon/utils/react_router'; -import Step from './components/step'; -import Follows from './follows'; -import Share from './share'; +import { Step } from './components/step'; +import { Follows } from './follows'; +import { Profile } from './profile'; +import { Share } from './share'; const messages = defineMessages({ template: { id: 'onboarding.compose.template', defaultMessage: 'Hello #Mastodon!' }, }); -const mapStateToProps = () => { - const getAccount = makeGetAccount(); +const Onboarding = () => { + const account = useAppSelector(state => state.getIn(['accounts', me])); + const dispatch = useDispatch(); + const intl = useIntl(); + const history = useHistory(); - return state => ({ - account: getAccount(state, me), - }); + const handleComposeClick = useCallback(() => { + dispatch(focusCompose(history, intl.formatMessage(messages.template))); + }, [dispatch, intl, history]); + + return ( + + + +
+
+ +

+

+
+ +
+ 0 && account.get('note').length > 0)} icon='address-book-o' iconComponent={AccountCircleIcon} label={} description={} /> + = 1} icon='user-plus' iconComponent={PersonAddIcon} label={} description={} /> + = 1} icon='pencil-square-o' iconComponent={EditNoteIcon} label={} description={ }} />} /> + } description={} /> +
+ +

+ +
+ + + + + + + + + +
+
+
+ + + + +
+ + + + +
+ ); }; -class Onboarding extends ImmutablePureComponent { - static propTypes = { - dispatch: PropTypes.func.isRequired, - account: ImmutablePropTypes.record, - ...WithRouterPropTypes, - }; - - state = { - step: null, - profileClicked: false, - shareClicked: false, - }; - - handleClose = () => { - const { dispatch, history } = this.props; - - dispatch(closeOnboarding()); - history.push('/home'); - }; - - handleProfileClick = () => { - this.setState({ profileClicked: true }); - }; - - handleFollowClick = () => { - this.setState({ step: 'follows' }); - }; - - handleComposeClick = () => { - const { dispatch, intl, history } = this.props; - - dispatch(focusCompose(history, intl.formatMessage(messages.template))); - }; - - handleShareClick = () => { - this.setState({ step: 'share', shareClicked: true }); - }; - - handleBackClick = () => { - this.setState({ step: null }); - }; - - handleWindowFocus = debounce(() => { - const { dispatch, account } = this.props; - dispatch(fetchAccount(account.get('id'))); - }, 1000, { trailing: true }); - - componentDidMount () { - window.addEventListener('focus', this.handleWindowFocus, false); - } - - componentWillUnmount () { - window.removeEventListener('focus', this.handleWindowFocus); - } - - render () { - const { account } = this.props; - const { step, shareClicked } = this.state; - - switch(step) { - case 'follows': - return ; - case 'share': - return ; - } - - return ( - -
-
- -

-

-
- -
- 0 && account.get('note').length > 0)} icon='address-book-o' iconComponent={AccountCircleIcon} label={} description={} /> - = 7} icon='user-plus' iconComponent={PersonAddIcon} label={} description={} /> - = 1} icon='pencil-square-o' iconComponent={EditNoteIcon} label={} description={ }} />} /> - } description={} /> -
- -

- -
- - - - - - - - - -
-
- - - - -
- ); - } - -} - -export default withRouter(connect(mapStateToProps)(injectIntl(Onboarding))); +export default Onboarding; diff --git a/app/javascript/mastodon/features/onboarding/profile.jsx b/app/javascript/mastodon/features/onboarding/profile.jsx new file mode 100644 index 0000000000..19ba0bcb95 --- /dev/null +++ b/app/javascript/mastodon/features/onboarding/profile.jsx @@ -0,0 +1,162 @@ +import { useState, useMemo, useCallback, createRef } from 'react'; + +import { useIntl, defineMessages, FormattedMessage } from 'react-intl'; + +import classNames from 'classnames'; +import { useHistory } from 'react-router-dom'; + +import { useDispatch } from 'react-redux'; + + +import { ReactComponent as AddPhotoAlternateIcon } from '@material-symbols/svg-600/outlined/add_photo_alternate.svg'; +import { ReactComponent as EditIcon } from '@material-symbols/svg-600/outlined/edit.svg'; +import Toggle from 'react-toggle'; + +import { updateAccount } from 'mastodon/actions/accounts'; +import { Button } from 'mastodon/components/button'; +import { ColumnBackButton } from 'mastodon/components/column_back_button'; +import { Icon } from 'mastodon/components/icon'; +import { LoadingIndicator } from 'mastodon/components/loading_indicator'; +import { me } from 'mastodon/initial_state'; +import { useAppSelector } from 'mastodon/store'; +import { unescapeHTML } from 'mastodon/utils/html'; + +const messages = defineMessages({ + uploadHeader: { id: 'onboarding.profile.upload_header', defaultMessage: 'Upload profile header' }, + uploadAvatar: { id: 'onboarding.profile.upload_avatar', defaultMessage: 'Upload profile picture' }, +}); + +export const Profile = () => { + const account = useAppSelector(state => state.getIn(['accounts', me])); + const [displayName, setDisplayName] = useState(account.get('display_name')); + const [note, setNote] = useState(unescapeHTML(account.get('note'))); + const [avatar, setAvatar] = useState(null); + const [header, setHeader] = useState(null); + const [discoverable, setDiscoverable] = useState(account.get('discoverable')); + const [indexable, setIndexable] = useState(account.get('indexable')); + const [isSaving, setIsSaving] = useState(false); + const [errors, setErrors] = useState(); + const avatarFileRef = createRef(); + const headerFileRef = createRef(); + const dispatch = useDispatch(); + const intl = useIntl(); + const history = useHistory(); + + const handleDisplayNameChange = useCallback(e => { + setDisplayName(e.target.value); + }, [setDisplayName]); + + const handleNoteChange = useCallback(e => { + setNote(e.target.value); + }, [setNote]); + + const handleDiscoverableChange = useCallback(e => { + setDiscoverable(e.target.checked); + }, [setDiscoverable]); + + const handleIndexableChange = useCallback(e => { + setIndexable(e.target.checked); + }, [setIndexable]); + + const handleAvatarChange = useCallback(e => { + setAvatar(e.target?.files?.[0]); + }, [setAvatar]); + + const handleHeaderChange = useCallback(e => { + setHeader(e.target?.files?.[0]); + }, [setHeader]); + + const avatarPreview = useMemo(() => avatar ? URL.createObjectURL(avatar) : account.get('avatar'), [avatar, account]); + const headerPreview = useMemo(() => header ? URL.createObjectURL(header) : account.get('header'), [header, account]); + + const handleSubmit = useCallback(() => { + setIsSaving(true); + + dispatch(updateAccount({ + displayName, + note, + avatar, + header, + discoverable, + indexable, + })).then(() => history.push('/start/follows')).catch(err => { + setIsSaving(false); + setErrors(err.response.data.details); + }); + }, [dispatch, displayName, note, avatar, header, discoverable, indexable, history]); + + return ( + <> + + +
+
+

+

+
+ +
+
+ + + +
+ +
+ + +
+ +
+
+ +
+ + +
+