diff --git a/.env.production.sample b/.env.production.sample
index 9011dc2175..16ffd0803c 100644
--- a/.env.production.sample
+++ b/.env.production.sample
@@ -35,11 +35,14 @@ OTP_SECRET=
# E-mail configuration
# Note: Mailgun and SparkPost (https://sparkpo.st/smtp) each have good free tiers
+# If you want to use an SMTP server without authentication (e.g local Postfix relay)
+# then set SMTP_AUTH_METHOD to 'none' and leave SMTP_LOGIN and SMTP_PASSWORD blank
SMTP_SERVER=smtp.mailgun.org
SMTP_PORT=587
SMTP_LOGIN=
SMTP_PASSWORD=
SMTP_FROM_ADDRESS=notifications@example.com
+#SMTP_DOMAIN= # defaults to LOCAL_DOMAIN
#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
#SMTP_AUTH_METHOD=plain
#SMTP_OPENSSL_VERIFY_MODE=peer
@@ -71,6 +74,7 @@ SMTP_FROM_ADDRESS=notifications@example.com
# S3_PROTOCOL=https
# S3_HOSTNAME=
# S3_ENDPOINT=
+# S3_SIGNATURE_VERSION=
# Optional alias for S3 if you want to use Cloudfront or Cloudflare in front
# S3_CLOUDFRONT_HOST=
@@ -81,3 +85,7 @@ SMTP_FROM_ADDRESS=notifications@example.com
# Advanced settings
# If you need to use pgBouncer, you need to disable prepared statements:
# PREPARED_STATEMENTS=false
+
+# Cluster number setting for streaming API server.
+# If you comment out following line, cluster number will be `numOfCpuCores - 1`.
+STREAMING_CLUSTER_NUM=1
diff --git a/.gitignore b/.gitignore
index cda6b87b37..c6c468cc79 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,8 +4,9 @@
# or operating system, you probably want to add a global ignore instead:
# git config --global core.excludesfile '~/.gitignore_global'
-# Ignore bundler config.
+# Ignore bundler config and downloaded libraries.
/.bundle
+/vendor/bundle
# Ignore the default SQLite database.
/db/*.sqlite3
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9ca01a56f5..299306299a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -7,7 +7,7 @@ There are three ways in which you can contribute to this repository:
2. By working on the back-end application
3. By working on the front-end application
-Choosing what to work on in a large open source project is not easy. The list of GitHub issues may provide some ideas, but not every feature request has been greenlit. Likewise, not every change or feature that resolves a personal itch will be merged into the main repository. Some communication ahead of time may be wise. If your addition creates a new feature or setting, or otherwise changes how things work in some substantial way, please remember to submit a correlating pull request to document your changes in the [documentation](http://github.com/tootsuite/documentation).
+Choosing what to work on in a large open source project is not easy. The list of [GitHub issues](https://github.com/tootsuite/mastodon/issues) may provide some ideas, but not every feature request has been greenlit. Likewise, not every change or feature that resolves a personal itch will be merged into the main repository. Some communication ahead of time may be wise. If your addition creates a new feature or setting, or otherwise changes how things work in some substantial way, please remember to submit a correlating pull request to document your changes in the [documentation](http://github.com/tootsuite/documentation).
Below are the guidelines for working on pull requests:
@@ -21,9 +21,17 @@ Below are the guidelines for working on pull requests:
- No orthographic mistakes
- No Markdown syntax errors
+## Requirements
+
+- Ruby
+- Node.js
+- PostgreSQL
+- Redis
+- Nginx (optional)
+
## Back-end application
-It is expected that you have a working development environment set up. The development environment includes rubocop, which checks your Ruby code for compliance with our style guide and best practices. Sublime Text, likely like other editors, has a Rubocop plugin that runs checks on files as you edit them. The codebase also has a test suite.
+It is expected that you have a working development environment set up. The development environment includes [rubocop](https://github.com/bbatsov/rubocop), which checks your Ruby code for compliance with our style guide and best practices. Sublime Text, likely like other editors, has a [Rubocop plugin](https://github.com/pderichs/sublime_rubocop) that runs checks on files as you edit them. The codebase also has a test suite.
* The codebase is not perfect, at the time of writing, but it is expected that you do not introduce new code style violations
* The rspec test suite must pass
@@ -41,4 +49,3 @@ It is expected that you have a working development environment set up (see back-
* If you are introducing new strings, they must be using localization methods
If the JavaScript or CSS assets won't compile due to a syntax error, it's a good sign that the pull request isn't ready for submission yet.
-
diff --git a/README.md b/README.md
index ea7b20a5c8..804ad394b5 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ Mastodon
Mastodon is a free, open-source social network server. A decentralized solution to commercial platforms, it avoids the risks of a single company monopolizing your communication. Anyone can run Mastodon and participate in the social network seamlessly.
-An alternative implementation of the GNU social project. Based on ActivityStreams, Webfinger, PubsubHubbub and Salmon.
+An alternative implementation of the GNU social project. Based on [ActivityStreams](https://en.wikipedia.org/wiki/Activity_Streams_(format)), [Webfinger](https://en.wikipedia.org/wiki/WebFinger), [PubsubHubbub](https://en.wikipedia.org/wiki/PubSubHubbub) and [Salmon](https://en.wikipedia.org/wiki/Salmon_(protocol)).
Click on the screenshot to watch a demo of the UI:
@@ -48,126 +48,9 @@ If you would like, you can [support the development of this project on Patreon][
- **Deployable via Docker**
You don't need to mess with dependencies and configuration if you want to try Mastodon, if you have Docker and Docker Compose the deployment is extremely easy
-## Checking out
+## Deployment
-If you want a stable release for production use, you should use tagged releases. To checkout the latest available tagged version:
-
- git clone https://github.com/tootsuite/mastodon.git
- cd mastodon
- git checkout $(git describe --tags `git rev-list --tags --max-count=1`)
-
-## Configuration
-
-- `LOCAL_DOMAIN` should be the domain/hostname of your instance. This is **absolutely required** as it is used for generating unique IDs for everything federation-related
-- `LOCAL_HTTPS` set it to `true` if HTTPS works on your website. This is used to generate canonical URLs, which is also important when generating and parsing federation-related IDs
-
-Consult the example configuration file, `.env.production.sample` for the full list. Among other things you need to set details for the SMTP server you are going to use.
-
-## Requirements
-
-- Ruby
-- Node.js
-- PostgreSQL
-- Redis
-- Nginx
-
-## Running with Docker and Docker-Compose
-
-[](https://microbadger.com/images/gargron/mastodon "Get your own version badge on microbadger.com") [](https://microbadger.com/images/gargron/mastodon "Get your own image badge on microbadger.com")
-
-The project now includes a `Dockerfile` and a `docker-compose.yml` file (which requires at least docker-compose version `1.10.0`).
-
-Review the settings in `docker-compose.yml`. Note that it is not default to store the postgresql database and redis databases in a persistent storage location,
-so you may need or want to adjust the settings there.
-
-Then, you need to fill in the `.env.production` file:
-
- cp .env.production.sample .env.production
- nano .env.production
-
-Do NOT change the `REDIS_*` or `DB_*` settings when running with the default docker configurations.
-
-You will need to fill in, at least: `LOCAL_DOMAIN`, `LOCAL_HTTPS`, `PAPERCLIP_SECRET`, `SECRET_KEY_BASE`, `OTP_SECRET`, and the `SMTP_*` settings. To generate the `PAPERCLIP_SECRET`, `SECRET_KEY_BASE`, and `OTP_SECRET`, you may use:
-
-Before running the first time, you need to build the images:
-
- docker-compose build
-
-
- docker-compose run --rm web rake secret
-
-Do this once for each of those keys, and copy the result into the `.env.production` file in the appropriate field.
-
-Then you should run the `db:migrate` command to create the database, or migrate it from an older release:
-
- docker-compose run --rm web rails db:migrate
-
-Then, you will also need to precompile the assets:
-
- docker-compose run --rm web rails assets:precompile
-
-before you can launch the docker image with:
-
- docker-compose up
-
-If you wish to run this as a daemon process instead of monitoring it on console, use instead:
-
- docker-compose up -d
-
-Then you may login to your new Mastodon instance by browsing to http://localhost:3000/
-
-Following that, make sure that you read the [production guide](docs/Running-Mastodon/Production-guide.md). You are probably going to want to understand how
-to configure Nginx to make your Mastodon instance available to the rest of the world.
-
-The container has two volumes, for the assets and for user uploads, and optionally two more, for the postgresql and redis databases.
-
-The default docker-compose.yml maps them to the repository's `public/assets` and `public/system` directories, you may wish to put them somewhere else. Likewise, the PostgreSQL and Redis images have data containers that you may wish to map somewhere where you know how to find them and back them up.
-
-**Note**: The `--rm` option for docker-compose will remove the container that is created to run a one-off command after it completes. As data is stored in volumes it is not affected by that container clean-up.
-
-### Tasks
-
-- `rake mastodon:media:clear` removes uploads that have not been attached to any status after a while, you would want to run this from a periodic cronjob
-- `rake mastodon:push:clear` unsubscribes from PuSH notifications for remote users that have no local followers. You may not want to actually do that, to keep a fuller footprint of the fediverse or in case your users will soon re-follow
-- `rake mastodon:push:refresh` re-subscribes PuSH for expiring remote users, this should be run periodically from a cronjob and quite often as the expiration time depends on the particular hub of the remote user
-- `rake mastodon:feeds:clear_all` removes all timelines, which forces them to be re-built on the fly next time a user tries to fetch their home/mentions timeline. Only for troubleshooting
-- `rake mastodon:feeds:clear` removes timelines of users who haven't signed in lately, which allows to save RAM and improve message distribution. This is required to be run periodically so that when they login again the regeneration process will trigger
-
-Running any of these tasks via docker-compose would look like this:
-
- docker-compose run --rm web rake mastodon:media:clear
-
-### Updating
-
-This approach makes updating to the latest version a real breeze.
-
-1. `git pull` to download updates from the repository
-2. `docker-compose build` to compile the Docker image out of the changed source files
-3. (optional) `docker-compose run --rm web rails db:migrate` to perform database migrations. Does nothing if your database is up to date
-4. (optional) `docker-compose run --rm web rails assets:precompile` to compile new JS and CSS assets
-5. `docker-compose up -d` to re-create (restart) containers and pick up the changes
-
-## Deployment without Docker
-
-Docker is great for quickly trying out software, but it has its drawbacks too. If you prefer to run Mastodon without using Docker, refer to the [production guide](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Production-guide.md) for examples, configuration and instructions.
-
-## Deployment on Scalingo
-
-[](https://my.scalingo.com/deploy?source=https://github.com/tootsuite/mastodon#master)
-
-[You can view a guide for deployment on Scalingo here.](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Scalingo-guide.md)
-
-## Deployment on Heroku (experimental)
-
-[](https://heroku.com/deploy)
-
-Mastodon can run on [Heroku](https://heroku.com), but it gets expensive and impractical due to how Heroku prices resource usage. [You can view a guide for deployment on Heroku here](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Heroku-guide.md), but you have been warned.
-
-## Development with Vagrant
-
-A quick way to get a development environment up and running is with Vagrant. You will need recent versions of [Vagrant](https://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/) installed.
-
-[You can find the guide for setting up a Vagrant development environment here.](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Vagrant-guide.md)
+There are guides in the documentation repository for [deploying on various platforms](https://github.com/tootsuite/documentation#running-mastodon).
## Contributing
diff --git a/Vagrantfile b/Vagrantfile
index 66892e443c..9047037bc9 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -107,7 +107,11 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.hostsupdater.remove_on_suspend = false
end
- config.vm.synced_folder ".", "/vagrant", type: "nfs", mount_options: ['rw', 'vers=3', 'tcp']
+ if config.vm.networks.any? { |type, options| type == :private_network }
+ config.vm.synced_folder ".", "/vagrant", type: "nfs", mount_options: ['rw', 'vers=3', 'tcp']
+ else
+ config.vm.synced_folder ".", "/vagrant"
+ end
# Otherwise, you can access the site at http://localhost:3000
config.vm.network :forwarded_port, guest: 80, host: 3000
diff --git a/app/assets/fonts/roboto-mono/robotomono-bold-webfont.eot b/app/assets/fonts/roboto-mono/robotomono-bold-webfont.eot
deleted file mode 100755
index f320adde6b..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-bold-webfont.eot and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-bold-webfont.svg b/app/assets/fonts/roboto-mono/robotomono-bold-webfont.svg
deleted file mode 100755
index 80745576ba..0000000000
--- a/app/assets/fonts/roboto-mono/robotomono-bold-webfont.svg
+++ /dev/null
@@ -1,1051 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/app/assets/fonts/roboto-mono/robotomono-bold-webfont.ttf b/app/assets/fonts/roboto-mono/robotomono-bold-webfont.ttf
deleted file mode 100755
index d9a21ba8b6..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-bold-webfont.ttf and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-bold-webfont.woff b/app/assets/fonts/roboto-mono/robotomono-bold-webfont.woff
deleted file mode 100755
index 37147c2ffb..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-bold-webfont.woff and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-bold-webfont.woff2 b/app/assets/fonts/roboto-mono/robotomono-bold-webfont.woff2
deleted file mode 100755
index c029349a40..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-bold-webfont.woff2 and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-bolditalic-webfont.eot b/app/assets/fonts/roboto-mono/robotomono-bolditalic-webfont.eot
deleted file mode 100755
index 3fffa965b5..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-bolditalic-webfont.eot and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-bolditalic-webfont.svg b/app/assets/fonts/roboto-mono/robotomono-bolditalic-webfont.svg
deleted file mode 100755
index 2e3ed50458..0000000000
--- a/app/assets/fonts/roboto-mono/robotomono-bolditalic-webfont.svg
+++ /dev/null
@@ -1,1051 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/app/assets/fonts/roboto-mono/robotomono-bolditalic-webfont.ttf b/app/assets/fonts/roboto-mono/robotomono-bolditalic-webfont.ttf
deleted file mode 100755
index e14fe1a482..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-bolditalic-webfont.ttf and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-bolditalic-webfont.woff b/app/assets/fonts/roboto-mono/robotomono-bolditalic-webfont.woff
deleted file mode 100755
index 6bf6b8b732..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-bolditalic-webfont.woff and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-bolditalic-webfont.woff2 b/app/assets/fonts/roboto-mono/robotomono-bolditalic-webfont.woff2
deleted file mode 100755
index dddab814bd..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-bolditalic-webfont.woff2 and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-italic-webfont.eot b/app/assets/fonts/roboto-mono/robotomono-italic-webfont.eot
deleted file mode 100755
index 2b6b01d06b..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-italic-webfont.eot and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-italic-webfont.svg b/app/assets/fonts/roboto-mono/robotomono-italic-webfont.svg
deleted file mode 100755
index 25ef38f007..0000000000
--- a/app/assets/fonts/roboto-mono/robotomono-italic-webfont.svg
+++ /dev/null
@@ -1,1051 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/app/assets/fonts/roboto-mono/robotomono-italic-webfont.ttf b/app/assets/fonts/roboto-mono/robotomono-italic-webfont.ttf
deleted file mode 100755
index bb7c757f0c..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-italic-webfont.ttf and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-italic-webfont.woff b/app/assets/fonts/roboto-mono/robotomono-italic-webfont.woff
deleted file mode 100755
index 2d7e44bd3b..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-italic-webfont.woff and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-italic-webfont.woff2 b/app/assets/fonts/roboto-mono/robotomono-italic-webfont.woff2
deleted file mode 100755
index 4db3b7b260..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-italic-webfont.woff2 and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-light-webfont.eot b/app/assets/fonts/roboto-mono/robotomono-light-webfont.eot
deleted file mode 100755
index 3bded4244f..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-light-webfont.eot and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-light-webfont.svg b/app/assets/fonts/roboto-mono/robotomono-light-webfont.svg
deleted file mode 100755
index 57da2b35c1..0000000000
--- a/app/assets/fonts/roboto-mono/robotomono-light-webfont.svg
+++ /dev/null
@@ -1,1051 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/app/assets/fonts/roboto-mono/robotomono-light-webfont.ttf b/app/assets/fonts/roboto-mono/robotomono-light-webfont.ttf
deleted file mode 100755
index 8cc72558e2..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-light-webfont.ttf and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-light-webfont.woff b/app/assets/fonts/roboto-mono/robotomono-light-webfont.woff
deleted file mode 100755
index 49898c912f..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-light-webfont.woff and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-light-webfont.woff2 b/app/assets/fonts/roboto-mono/robotomono-light-webfont.woff2
deleted file mode 100755
index d86dd4b64f..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-light-webfont.woff2 and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-lightitalic-webfont.eot b/app/assets/fonts/roboto-mono/robotomono-lightitalic-webfont.eot
deleted file mode 100755
index 538f107609..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-lightitalic-webfont.eot and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-lightitalic-webfont.svg b/app/assets/fonts/roboto-mono/robotomono-lightitalic-webfont.svg
deleted file mode 100755
index d382f105f2..0000000000
--- a/app/assets/fonts/roboto-mono/robotomono-lightitalic-webfont.svg
+++ /dev/null
@@ -1,1051 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/app/assets/fonts/roboto-mono/robotomono-lightitalic-webfont.ttf b/app/assets/fonts/roboto-mono/robotomono-lightitalic-webfont.ttf
deleted file mode 100755
index 3e54c9f472..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-lightitalic-webfont.ttf and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-lightitalic-webfont.woff b/app/assets/fonts/roboto-mono/robotomono-lightitalic-webfont.woff
deleted file mode 100755
index 14e805b0d0..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-lightitalic-webfont.woff and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-lightitalic-webfont.woff2 b/app/assets/fonts/roboto-mono/robotomono-lightitalic-webfont.woff2
deleted file mode 100755
index 33afd9f32f..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-lightitalic-webfont.woff2 and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-medium-webfont.eot b/app/assets/fonts/roboto-mono/robotomono-medium-webfont.eot
deleted file mode 100755
index e9e87e1b6b..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-medium-webfont.eot and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-medium-webfont.svg b/app/assets/fonts/roboto-mono/robotomono-medium-webfont.svg
deleted file mode 100755
index 3d1613cbb0..0000000000
--- a/app/assets/fonts/roboto-mono/robotomono-medium-webfont.svg
+++ /dev/null
@@ -1,1051 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/app/assets/fonts/roboto-mono/robotomono-medium-webfont.ttf b/app/assets/fonts/roboto-mono/robotomono-medium-webfont.ttf
deleted file mode 100755
index 5d663e39f0..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-medium-webfont.ttf and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-medium-webfont.woff b/app/assets/fonts/roboto-mono/robotomono-medium-webfont.woff
deleted file mode 100755
index f1991290ff..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-medium-webfont.woff and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-medium-webfont.woff2 b/app/assets/fonts/roboto-mono/robotomono-medium-webfont.woff2
deleted file mode 100755
index ff3e381a07..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-medium-webfont.woff2 and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-mediumitalic-webfont.eot b/app/assets/fonts/roboto-mono/robotomono-mediumitalic-webfont.eot
deleted file mode 100755
index 57df4ce3ad..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-mediumitalic-webfont.eot and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-mediumitalic-webfont.svg b/app/assets/fonts/roboto-mono/robotomono-mediumitalic-webfont.svg
deleted file mode 100755
index 2fc51d666e..0000000000
--- a/app/assets/fonts/roboto-mono/robotomono-mediumitalic-webfont.svg
+++ /dev/null
@@ -1,1051 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/app/assets/fonts/roboto-mono/robotomono-mediumitalic-webfont.ttf b/app/assets/fonts/roboto-mono/robotomono-mediumitalic-webfont.ttf
deleted file mode 100755
index 9e2ed7e203..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-mediumitalic-webfont.ttf and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-mediumitalic-webfont.woff b/app/assets/fonts/roboto-mono/robotomono-mediumitalic-webfont.woff
deleted file mode 100755
index 1e1d5a943b..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-mediumitalic-webfont.woff and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-mediumitalic-webfont.woff2 b/app/assets/fonts/roboto-mono/robotomono-mediumitalic-webfont.woff2
deleted file mode 100755
index 4720e9f06f..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-mediumitalic-webfont.woff2 and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-thin-webfont.eot b/app/assets/fonts/roboto-mono/robotomono-thin-webfont.eot
deleted file mode 100755
index 0b01313f9d..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-thin-webfont.eot and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-thin-webfont.svg b/app/assets/fonts/roboto-mono/robotomono-thin-webfont.svg
deleted file mode 100755
index bcc4ef257f..0000000000
--- a/app/assets/fonts/roboto-mono/robotomono-thin-webfont.svg
+++ /dev/null
@@ -1,1051 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/app/assets/fonts/roboto-mono/robotomono-thin-webfont.ttf b/app/assets/fonts/roboto-mono/robotomono-thin-webfont.ttf
deleted file mode 100755
index 4a460ac235..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-thin-webfont.ttf and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-thin-webfont.woff b/app/assets/fonts/roboto-mono/robotomono-thin-webfont.woff
deleted file mode 100755
index a141a84e35..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-thin-webfont.woff and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-thin-webfont.woff2 b/app/assets/fonts/roboto-mono/robotomono-thin-webfont.woff2
deleted file mode 100755
index 929109f266..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-thin-webfont.woff2 and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-thinitalic-webfont.eot b/app/assets/fonts/roboto-mono/robotomono-thinitalic-webfont.eot
deleted file mode 100755
index 787dfbe63b..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-thinitalic-webfont.eot and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-thinitalic-webfont.svg b/app/assets/fonts/roboto-mono/robotomono-thinitalic-webfont.svg
deleted file mode 100755
index 87cfa29f6f..0000000000
--- a/app/assets/fonts/roboto-mono/robotomono-thinitalic-webfont.svg
+++ /dev/null
@@ -1,1051 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/app/assets/fonts/roboto-mono/robotomono-thinitalic-webfont.ttf b/app/assets/fonts/roboto-mono/robotomono-thinitalic-webfont.ttf
deleted file mode 100755
index 03194dfb4d..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-thinitalic-webfont.ttf and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-thinitalic-webfont.woff b/app/assets/fonts/roboto-mono/robotomono-thinitalic-webfont.woff
deleted file mode 100755
index 58962c8bd0..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-thinitalic-webfont.woff and /dev/null differ
diff --git a/app/assets/fonts/roboto-mono/robotomono-thinitalic-webfont.woff2 b/app/assets/fonts/roboto-mono/robotomono-thinitalic-webfont.woff2
deleted file mode 100755
index 7aa9cf254d..0000000000
Binary files a/app/assets/fonts/roboto-mono/robotomono-thinitalic-webfont.woff2 and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-black-webfont.eot b/app/assets/fonts/roboto/roboto-black-webfont.eot
deleted file mode 100755
index fc9b95fc58..0000000000
Binary files a/app/assets/fonts/roboto/roboto-black-webfont.eot and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-black-webfont.svg b/app/assets/fonts/roboto/roboto-black-webfont.svg
deleted file mode 100755
index a280ca097c..0000000000
--- a/app/assets/fonts/roboto/roboto-black-webfont.svg
+++ /dev/null
@@ -1,16273 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/app/assets/fonts/roboto/roboto-black-webfont.ttf b/app/assets/fonts/roboto/roboto-black-webfont.ttf
deleted file mode 100755
index 8f799bab10..0000000000
Binary files a/app/assets/fonts/roboto/roboto-black-webfont.ttf and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-black-webfont.woff b/app/assets/fonts/roboto/roboto-black-webfont.woff
deleted file mode 100755
index 67839aefc8..0000000000
Binary files a/app/assets/fonts/roboto/roboto-black-webfont.woff and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-black-webfont.woff2 b/app/assets/fonts/roboto/roboto-black-webfont.woff2
deleted file mode 100755
index 1590b1617f..0000000000
Binary files a/app/assets/fonts/roboto/roboto-black-webfont.woff2 and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-blackitalic-webfont.eot b/app/assets/fonts/roboto/roboto-blackitalic-webfont.eot
deleted file mode 100755
index dced1b7ab5..0000000000
Binary files a/app/assets/fonts/roboto/roboto-blackitalic-webfont.eot and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-blackitalic-webfont.svg b/app/assets/fonts/roboto/roboto-blackitalic-webfont.svg
deleted file mode 100755
index 7cbd06813f..0000000000
--- a/app/assets/fonts/roboto/roboto-blackitalic-webfont.svg
+++ /dev/null
@@ -1,16273 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/app/assets/fonts/roboto/roboto-blackitalic-webfont.ttf b/app/assets/fonts/roboto/roboto-blackitalic-webfont.ttf
deleted file mode 100755
index 4d64c6d64e..0000000000
Binary files a/app/assets/fonts/roboto/roboto-blackitalic-webfont.ttf and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-blackitalic-webfont.woff b/app/assets/fonts/roboto/roboto-blackitalic-webfont.woff
deleted file mode 100755
index 89487ff7cd..0000000000
Binary files a/app/assets/fonts/roboto/roboto-blackitalic-webfont.woff and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-blackitalic-webfont.woff2 b/app/assets/fonts/roboto/roboto-blackitalic-webfont.woff2
deleted file mode 100755
index 2b8f578bcf..0000000000
Binary files a/app/assets/fonts/roboto/roboto-blackitalic-webfont.woff2 and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-bolditalic-webfont.eot b/app/assets/fonts/roboto/roboto-bolditalic-webfont.eot
deleted file mode 100755
index bc06bef657..0000000000
Binary files a/app/assets/fonts/roboto/roboto-bolditalic-webfont.eot and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-bolditalic-webfont.svg b/app/assets/fonts/roboto/roboto-bolditalic-webfont.svg
deleted file mode 100755
index 9aec401f7e..0000000000
--- a/app/assets/fonts/roboto/roboto-bolditalic-webfont.svg
+++ /dev/null
@@ -1,16273 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/app/assets/fonts/roboto/roboto-bolditalic-webfont.ttf b/app/assets/fonts/roboto/roboto-bolditalic-webfont.ttf
deleted file mode 100755
index 2f95d20a76..0000000000
Binary files a/app/assets/fonts/roboto/roboto-bolditalic-webfont.ttf and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-bolditalic-webfont.woff b/app/assets/fonts/roboto/roboto-bolditalic-webfont.woff
deleted file mode 100755
index b6a56b254e..0000000000
Binary files a/app/assets/fonts/roboto/roboto-bolditalic-webfont.woff and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-bolditalic-webfont.woff2 b/app/assets/fonts/roboto/roboto-bolditalic-webfont.woff2
deleted file mode 100755
index af3d88958e..0000000000
Binary files a/app/assets/fonts/roboto/roboto-bolditalic-webfont.woff2 and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-light-webfont.eot b/app/assets/fonts/roboto/roboto-light-webfont.eot
deleted file mode 100755
index 63f849e7ee..0000000000
Binary files a/app/assets/fonts/roboto/roboto-light-webfont.eot and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-light-webfont.svg b/app/assets/fonts/roboto/roboto-light-webfont.svg
deleted file mode 100755
index 5d7213467d..0000000000
--- a/app/assets/fonts/roboto/roboto-light-webfont.svg
+++ /dev/null
@@ -1,15513 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/app/assets/fonts/roboto/roboto-light-webfont.ttf b/app/assets/fonts/roboto/roboto-light-webfont.ttf
deleted file mode 100755
index 9f5f6ae4ac..0000000000
Binary files a/app/assets/fonts/roboto/roboto-light-webfont.ttf and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-light-webfont.woff b/app/assets/fonts/roboto/roboto-light-webfont.woff
deleted file mode 100755
index e51a7fc7cc..0000000000
Binary files a/app/assets/fonts/roboto/roboto-light-webfont.woff and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-light-webfont.woff2 b/app/assets/fonts/roboto/roboto-light-webfont.woff2
deleted file mode 100755
index db6eb8c60b..0000000000
Binary files a/app/assets/fonts/roboto/roboto-light-webfont.woff2 and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-lightitalic-webfont.eot b/app/assets/fonts/roboto/roboto-lightitalic-webfont.eot
deleted file mode 100755
index e28f87527a..0000000000
Binary files a/app/assets/fonts/roboto/roboto-lightitalic-webfont.eot and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-lightitalic-webfont.svg b/app/assets/fonts/roboto/roboto-lightitalic-webfont.svg
deleted file mode 100755
index 3cf8a7b780..0000000000
--- a/app/assets/fonts/roboto/roboto-lightitalic-webfont.svg
+++ /dev/null
@@ -1,15513 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/app/assets/fonts/roboto/roboto-lightitalic-webfont.ttf b/app/assets/fonts/roboto/roboto-lightitalic-webfont.ttf
deleted file mode 100755
index a0eeebb5a4..0000000000
Binary files a/app/assets/fonts/roboto/roboto-lightitalic-webfont.ttf and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-lightitalic-webfont.woff b/app/assets/fonts/roboto/roboto-lightitalic-webfont.woff
deleted file mode 100755
index ad0e9cd68b..0000000000
Binary files a/app/assets/fonts/roboto/roboto-lightitalic-webfont.woff and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-lightitalic-webfont.woff2 b/app/assets/fonts/roboto/roboto-lightitalic-webfont.woff2
deleted file mode 100755
index 980ca9b986..0000000000
Binary files a/app/assets/fonts/roboto/roboto-lightitalic-webfont.woff2 and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-mediumitalic-webfont.eot b/app/assets/fonts/roboto/roboto-mediumitalic-webfont.eot
deleted file mode 100755
index ee4a1e9d04..0000000000
Binary files a/app/assets/fonts/roboto/roboto-mediumitalic-webfont.eot and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-mediumitalic-webfont.svg b/app/assets/fonts/roboto/roboto-mediumitalic-webfont.svg
deleted file mode 100755
index 6077cbf82c..0000000000
--- a/app/assets/fonts/roboto/roboto-mediumitalic-webfont.svg
+++ /dev/null
@@ -1,16273 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/app/assets/fonts/roboto/roboto-mediumitalic-webfont.ttf b/app/assets/fonts/roboto/roboto-mediumitalic-webfont.ttf
deleted file mode 100755
index 80f922c093..0000000000
Binary files a/app/assets/fonts/roboto/roboto-mediumitalic-webfont.ttf and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-mediumitalic-webfont.woff b/app/assets/fonts/roboto/roboto-mediumitalic-webfont.woff
deleted file mode 100755
index e2c3d1b819..0000000000
Binary files a/app/assets/fonts/roboto/roboto-mediumitalic-webfont.woff and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-mediumitalic-webfont.woff2 b/app/assets/fonts/roboto/roboto-mediumitalic-webfont.woff2
deleted file mode 100755
index 8f0a856953..0000000000
Binary files a/app/assets/fonts/roboto/roboto-mediumitalic-webfont.woff2 and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-thin-webfont.eot b/app/assets/fonts/roboto/roboto-thin-webfont.eot
deleted file mode 100755
index 1b100c7fe3..0000000000
Binary files a/app/assets/fonts/roboto/roboto-thin-webfont.eot and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-thin-webfont.svg b/app/assets/fonts/roboto/roboto-thin-webfont.svg
deleted file mode 100755
index 4bf4bd2624..0000000000
--- a/app/assets/fonts/roboto/roboto-thin-webfont.svg
+++ /dev/null
@@ -1,15513 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/app/assets/fonts/roboto/roboto-thin-webfont.ttf b/app/assets/fonts/roboto/roboto-thin-webfont.ttf
deleted file mode 100755
index ea923dc671..0000000000
Binary files a/app/assets/fonts/roboto/roboto-thin-webfont.ttf and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-thin-webfont.woff b/app/assets/fonts/roboto/roboto-thin-webfont.woff
deleted file mode 100755
index 57faf4f542..0000000000
Binary files a/app/assets/fonts/roboto/roboto-thin-webfont.woff and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-thin-webfont.woff2 b/app/assets/fonts/roboto/roboto-thin-webfont.woff2
deleted file mode 100755
index 7fcf1caa8e..0000000000
Binary files a/app/assets/fonts/roboto/roboto-thin-webfont.woff2 and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-thinitalic-webfont.eot b/app/assets/fonts/roboto/roboto-thinitalic-webfont.eot
deleted file mode 100755
index 13ecf851f6..0000000000
Binary files a/app/assets/fonts/roboto/roboto-thinitalic-webfont.eot and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-thinitalic-webfont.svg b/app/assets/fonts/roboto/roboto-thinitalic-webfont.svg
deleted file mode 100755
index 9e51522e31..0000000000
--- a/app/assets/fonts/roboto/roboto-thinitalic-webfont.svg
+++ /dev/null
@@ -1,15513 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/app/assets/fonts/roboto/roboto-thinitalic-webfont.ttf b/app/assets/fonts/roboto/roboto-thinitalic-webfont.ttf
deleted file mode 100755
index 7fa276a00e..0000000000
Binary files a/app/assets/fonts/roboto/roboto-thinitalic-webfont.ttf and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-thinitalic-webfont.woff b/app/assets/fonts/roboto/roboto-thinitalic-webfont.woff
deleted file mode 100755
index cb0699c575..0000000000
Binary files a/app/assets/fonts/roboto/roboto-thinitalic-webfont.woff and /dev/null differ
diff --git a/app/assets/fonts/roboto/roboto-thinitalic-webfont.woff2 b/app/assets/fonts/roboto/roboto-thinitalic-webfont.woff2
deleted file mode 100755
index d249d14193..0000000000
Binary files a/app/assets/fonts/roboto/roboto-thinitalic-webfont.woff2 and /dev/null differ
diff --git a/app/assets/images/logo.png b/app/assets/images/logo.png
index 3ed93f1206..f0c1c46c39 100644
Binary files a/app/assets/images/logo.png and b/app/assets/images/logo.png differ
diff --git a/app/assets/images/logo.svg b/app/assets/images/logo.svg
index 52bf86b0ee..c233db8428 100644
--- a/app/assets/images/logo.svg
+++ b/app/assets/images/logo.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/assets/javascripts/components/actions/accounts.jsx b/app/assets/javascripts/components/actions/accounts.jsx
index 37ebb99690..eac5c78bb4 100644
--- a/app/assets/javascripts/components/actions/accounts.jsx
+++ b/app/assets/javascripts/components/actions/accounts.jsx
@@ -126,7 +126,8 @@ export function expandAccountTimeline(id) {
max_id: lastId
}
}).then(response => {
- dispatch(expandAccountTimelineSuccess(id, response.data));
+ const next = getLinks(response).refs.find(link => link.rel === 'next');
+ dispatch(expandAccountTimelineSuccess(id, response.data, next));
}).catch(error => {
dispatch(expandAccountTimelineFail(id, error));
});
@@ -257,11 +258,12 @@ export function expandAccountTimelineRequest(id) {
};
};
-export function expandAccountTimelineSuccess(id, statuses) {
+export function expandAccountTimelineSuccess(id, statuses, next) {
return {
type: ACCOUNT_TIMELINE_EXPAND_SUCCESS,
id,
- statuses
+ statuses,
+ next
};
};
diff --git a/app/assets/javascripts/components/actions/compose.jsx b/app/assets/javascripts/components/actions/compose.jsx
index 88e91c356f..de75ddabe8 100644
--- a/app/assets/javascripts/components/actions/compose.jsx
+++ b/app/assets/javascripts/components/actions/compose.jsx
@@ -73,9 +73,13 @@ export function mentionCompose(account, router) {
export function submitCompose() {
return function (dispatch, getState) {
+ const status = emojione.shortnameToUnicode(getState().getIn(['compose', 'text'], ''));
+ if (!status || !status.length) {
+ return;
+ }
dispatch(submitComposeRequest());
api(getState).post('/api/v1/statuses', {
- status: emojione.shortnameToUnicode(getState().getIn(['compose', 'text'], '')),
+ status,
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')),
sensitive: getState().getIn(['compose', 'sensitive']),
diff --git a/app/assets/javascripts/components/components/attachment_list.jsx b/app/assets/javascripts/components/components/attachment_list.jsx
new file mode 100644
index 0000000000..56238fe195
--- /dev/null
+++ b/app/assets/javascripts/components/components/attachment_list.jsx
@@ -0,0 +1,34 @@
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PureRenderMixin from 'react-addons-pure-render-mixin';
+
+const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
+
+const AttachmentList = React.createClass({
+ propTypes: {
+ media: ImmutablePropTypes.list.isRequired
+ },
+
+ mixins: [PureRenderMixin],
+
+ render () {
+ const { media } = this.props;
+
+ return (
+
diff --git a/app/views/user_mailer/reset_password_instructions.ja.text.erb b/app/views/user_mailer/reset_password_instructions.ja.text.erb
index 5fb0eba045..9ed607b581 100644
--- a/app/views/user_mailer/reset_password_instructions.ja.text.erb
+++ b/app/views/user_mailer/reset_password_instructions.ja.text.erb
@@ -4,5 +4,5 @@ Mastodonアカウントのパスワードの変更がリクエストされまし
<%= edit_password_url(@resource, reset_password_token: @token) %>
-このメールに見に覚えのない場合は無視してください。
+このメールに身に覚えのない場合は無視してください。
上記のリンクにアクセスし、変更をしない限りパスワードは変更されません。
diff --git a/bin/setup b/bin/setup
index e620b4dadb..72b62a0283 100755
--- a/bin/setup
+++ b/bin/setup
@@ -17,6 +17,7 @@ chdir APP_ROOT do
puts '== Installing dependencies =='
system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install')
+ system!('yarn install')
# puts "\n== Copying sample files =="
# unless File.exist?('config/database.yml')
diff --git a/config/application.rb b/config/application.rb
index fa7098b39e..5b86cddb46 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -32,12 +32,15 @@ module Mastodon
:es,
:fi,
:fr,
- :it,
+ :hr,
:hu,
+ :it,
:ja,
:nl,
:no,
+ :oc,
:pt,
+ :'pt-BR',
:ru,
:uk,
'zh-CN',
diff --git a/config/deploy.rb b/config/deploy.rb
index 8ff933e9bf..39c97aeeb6 100644
--- a/config/deploy.rb
+++ b/config/deploy.rb
@@ -6,6 +6,7 @@ set :branch, 'skylight'
set :rbenv_type, :user
set :rbenv_ruby, File.read('.ruby-version').strip
set :migration_role, :app
+set :conditional_migrate, true
-append :linked_files, '.env.production'
+append :linked_files, '.env.production', 'public/robots.txt'
append :linked_dirs, 'vendor/bundle', 'public/system'
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 80021287a5..a4cdb27323 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -100,8 +100,10 @@ Rails.application.configure do
:address => ENV['SMTP_SERVER'],
:user_name => ENV['SMTP_LOGIN'],
:password => ENV['SMTP_PASSWORD'],
+
:domain => ENV['SMTP_DOMAIN'] || ENV['LOCAL_DOMAIN'],
- :authentication => ENV['SMTP_AUTH_METHOD'] || :plain,
+ :authentication => ENV['SMTP_AUTH_METHOD'] == 'none' ? nil : ENV['SMTP_AUTH_METHOD'] || :plain,
+
:openssl_verify_mode => ENV['SMTP_OPENSSL_VERIFY_MODE'],
:enable_starttls_auto => ENV['SMTP_ENABLE_STARTTLS_AUTO'] || true,
}
diff --git a/config/initializers/paperclip.rb b/config/initializers/paperclip.rb
index 77bc13b3aa..9e2996faa0 100644
--- a/config/initializers/paperclip.rb
+++ b/config/initializers/paperclip.rb
@@ -4,7 +4,7 @@ Paperclip.options[:read_timeout] = 60
Paperclip.interpolates :filename do |attachment, style|
return attachment.original_filename if style == :original
- [basename(attachment, style), content_type_extension(attachment, style)].delete_if(&:empty?).join('.')
+ [basename(attachment, style), extension(attachment, style)].delete_if(&:empty?).join('.')
end
if ENV['S3_ENABLED'] == 'true'
@@ -16,7 +16,7 @@ if ENV['S3_ENABLED'] == 'true'
Paperclip::Attachment.default_options[:s3_host_name] = ENV.fetch('S3_HOSTNAME') { "s3-#{ENV.fetch('S3_REGION')}.amazonaws.com" }
Paperclip::Attachment.default_options[:path] = '/:class/:attachment/:id_partition/:style/:filename'
Paperclip::Attachment.default_options[:s3_headers] = { 'Cache-Control' => 'max-age=315576000' }
- Paperclip::Attachment.default_options[:s3_permissions] = 'public-read'
+ Paperclip::Attachment.default_options[:s3_permissions] = ENV.fetch('S3_PERMISSION') { 'public-read' }
Paperclip::Attachment.default_options[:s3_region] = ENV.fetch('S3_REGION') { 'us-east-1' }
Paperclip::Attachment.default_options[:s3_credentials] = {
@@ -28,6 +28,7 @@ if ENV['S3_ENABLED'] == 'true'
unless ENV['S3_ENDPOINT'].blank?
Paperclip::Attachment.default_options[:s3_options] = {
endpoint: ENV['S3_ENDPOINT'],
+ signature_version: ENV['S3_SIGNATURE_VERSION'] || 'v4',
force_path_style: true,
}
@@ -39,6 +40,6 @@ if ENV['S3_ENABLED'] == 'true'
Paperclip::Attachment.default_options[:s3_host_alias] = ENV['S3_CLOUDFRONT_HOST']
end
else
- Paperclip::Attachment.default_options[:path] = (ENV['PAPERCLIP_ROOT_PATH'] || ':rails_root/public/system') + '/:class/:attachment/:id_partition/:style/:filename'
- Paperclip::Attachment.default_options[:url] = (ENV['PAPERCLIP_ROOT_URL'] || '/system') + '/:class/:attachment/:id_partition/:style/:filename'
+ Paperclip::Attachment.default_options[:path] = (ENV['PAPERCLIP_ROOT_PATH'] || ':rails_root/public/system') + '/:class/:attachment/:id_partition/:style/:filename'
+ Paperclip::Attachment.default_options[:url] = (ENV['PAPERCLIP_ROOT_URL'] || '/system') + '/:class/:attachment/:id_partition/:style/:filename'
end
diff --git a/config/initializers/rack-attack.rb b/config/initializers/rack_attack.rb
similarity index 50%
rename from config/initializers/rack-attack.rb
rename to config/initializers/rack_attack.rb
index 70f7846d19..67ec7c919a 100644
--- a/config/initializers/rack-attack.rb
+++ b/config/initializers/rack_attack.rb
@@ -1,7 +1,24 @@
+# frozen_string_literal: true
+
class Rack::Attack
# Rate limits for the API
throttle('api', limit: 300, period: 5.minutes) do |req|
- req.ip if req.path.match(/\A\/api\/v/)
+ req.ip if req.path =~ /\A\/api\/v/
+ end
+
+ # Rate limit logins
+ throttle('login', limit: 5, period: 5.minutes) do |req|
+ req.ip if req.path == '/auth/sign_in' && req.post?
+ end
+
+ # Rate limit sign-ups
+ throttle('register', limit: 5, period: 5.minutes) do |req|
+ req.ip if req.path == '/auth' && req.post?
+ end
+
+ # Rate limit forgotten passwords
+ throttle('reminder', limit: 5, period: 5.minutes) do |req|
+ req.ip if req.path == '/auth/password' && req.post?
end
self.throttled_response = lambda do |env|
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 75ac4e1bba..dcbeea745d 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -1,7 +1,7 @@
---
de:
about:
- about_mastodon: Mastodon ist ein freier, quelloffener soziales Netzwerkserver. Als dezentralisierte Alternative zu kommerziellen Plattformen verhindert es die Risiken, die entstehen, wenn eine einzelne Firma deine Kommunikation monopolisiert. Jeder kann Mastodon verwenden und ganz einfach am sozialen Netzwerk teilnehmen.
+ about_mastodon: Mastodon ist ein freier, quelloffener sozialer Netzwerkserver. Als dezentralisierte Alternative zu kommerziellen Plattformen verhindert es die Risiken, die entstehen, wenn eine einzelne Firma deine Kommunikation monopolisiert. Jeder kann Mastodon verwenden und ganz einfach am sozialen Netzwerk teilnehmen.
get_started: Erste Schritte
source_code: Quellcode
terms: AGB
diff --git a/config/locales/devise.ja.yml b/config/locales/devise.ja.yml
index 4aeb09cd40..c09c15bb46 100644
--- a/config/locales/devise.ja.yml
+++ b/config/locales/devise.ja.yml
@@ -7,7 +7,7 @@ ja:
send_paranoid_instructions: もしあなたのメールアドレスが登録されていれば、まもなくメールアドレスの確認の方法が記載されたメールが送信されます。
failure:
already_authenticated: 既にログイン済みです。
- inactive: あなたのアカウントはまだアクティベートされていません。
+ inactive: あなたのアカウントはまだ有効化されていません。
invalid: '%{authentication_keys}かパスワードが誤っています'
last_attempt: あと1回失敗するとアカウントがロックされます。
locked: アカウントはロックされました。
diff --git a/config/locales/devise.nl.yml b/config/locales/devise.nl.yml
index 9057a67759..28e012dfbe 100644
--- a/config/locales/devise.nl.yml
+++ b/config/locales/devise.nl.yml
@@ -3,57 +3,59 @@ nl:
devise:
confirmations:
confirmed: Je account is bevestigd.
- send_instructions: Je ontvangt via e-mail instructies hoe je je account kan bevestigen.
- send_paranoid_instructions: Als je e-mailadres bestaat in de database, ontvang je via e-mail instructies hoe je je account kan bevestigen.
+ send_instructions: Je ontvangt via e-mail instructies hoe je jouw account kan bevestigen.
+ send_paranoid_instructions: Als jouw e-mailadres in de database staat, ontvang je via e-mail instructies hoe je jouw account kan bevestigen.
failure:
already_authenticated: Je bent al ingelogd.
- inactive: Je account is nog niet geactiveerd.
- invalid: Ongeldig e-mail of wachtwoord.
- invalid_token: Invalide authenticiteit token.
- last_attempt: Je hebt nog een poging over voordat je account wordt geblokkeerd.
- locked: Je account is gelocked.
- not_found_in_database: Ongeldig e-mail of wachtwoord.
- timeout: Je sessie is verlopen, log a.u.b. opnieuw in.
- unauthenticated: Je dient in te loggen of je in te schrijven.
- unconfirmed: Je dient eerst je account te bevestigen.
+ inactive: Jouw account is nog niet geactiveerd.
+ invalid: Ongeldig e-mailadres of wachtwoord.
+ invalid_token: Ongeldige bevestigingscode.
+ last_attempt: Je hebt nog één poging over voordat jouw account geblokkeerd wordt.
+ locked: Jouw account is geblokkeerd.
+ not_found_in_database: Ongeldig e-mailadres of wachtwoord.
+ timeout: Jouw sessie is verlopen, log opnieuw in.
+ unauthenticated: Je dient in te loggen of te registreren.
+ unconfirmed: Je dient eerst jouw account te bevestigen.
mailer:
confirmation_instructions:
- subject: Bevestiging mailadres
+ subject: 'Mastodon: E-mail bevestigen voor %{instance}'
+ password_change:
+ subject: 'Mastodon: Wachtwoord veranderd'
reset_password_instructions:
- subject: Wachtwoord resetten
+ subject: 'Mastodon: Wachtwoord opnieuw instellen'
unlock_instructions:
- subject: Unlock instructies
+ subject: 'Mastodon: Instructies om account te deblokkeren'
omniauth_callbacks:
- failure: Kon je niet aanmelden met je %{kind} account, omdat "%{reason}".
- success: Successvol aangemeld met je %{kind} account.
+ failure: Kon je niet aanmelden met jouw %{kind} account, omdat "%{reason}".
+ success: Successvol aangemeld met jouw %{kind} account.
passwords:
- no_token: Je kan deze pagina niet benaderen zonder een "wachtwoord reset e-mail"
- send_instructions: Je ontvangt via e-mail instructies hoe je je wachtwoord moet resetten.
- send_paranoid_instructions: Als je e-mailadres bestaat in de database, ontvang je via e-mail instructies hoe je je wachtwoord moet resetten.
- updated: Je wachtwoord is gewijzigd. Je bent nu ingelogd.
- updated_not_active: Je wachtwoord is gewijzigd.
+ no_token: Je kan deze pagina niet benaderen zonder dat je een e-mail om je wachtwoord opnieuw in te stellen hebt ontvangen.
+ send_instructions: Je ontvangt via e-mail instructies hoe je jouw wachtwoord opnieuw moet instellen.
+ send_paranoid_instructions: Als jouw e-mailadres in de database staat, ontvang je via e-mail instructies hoe je jouw wachtwoord opnieuw moet instellen.
+ updated: Jouw wachtwoord is gewijzigd. Je bent nu ingelogd.
+ updated_not_active: Jouw wachtwoord is gewijzigd.
registrations:
- destroyed: Je account is verwijderd, wellicht tot ziens!
- signed_up: Je bent ingeschreven.
- signed_up_but_inactive: Je bent ingeschreven. Je kon alleen niet automatisch ingelogd worden omdat je account nog niet geactiveerd is.
- signed_up_but_locked: Je bent ingeschreven. Je kon alleen niet automatisch ingelogd worden omdat je account geblokkeerd is.
- signed_up_but_unconfirmed: Je ontvangt via e-mail instructies hoe je je account kunt activeren.
- update_needs_confirmation: Je hebt je e-mailadres succesvol gewijzigd, maar we moeten je nieuwe mailadres nog verifiëren. Controleer je e-mail en klik op de link in de mail om je mailadres te verifiëren.
- updated: Je account gegevens zijn opgeslagen.
+ destroyed: Jouw account is verwijderd. Wellicht tot ziens!
+ signed_up: Je bent geregistreerd.
+ signed_up_but_inactive: Je bent geregistreerd. Je kon alleen niet automatisch ingelogd worden omdat jouw account nog niet geactiveerd is.
+ signed_up_but_locked: Je bent ingeschreven. Je kon alleen niet automatisch ingelogd worden omdat jouw account geblokkeerd is.
+ signed_up_but_unconfirmed: Je ontvangt via e-mail instructies hoe je jouw account kunt activeren.
+ update_needs_confirmation: Je hebt je e-mailadres succesvol gewijzigd, maar we moeten je nieuwe mailadres nog bevestigen. Controleer jouw e-mail en klik op de link in de mail om jouw e-mailadres te bevestigen.
+ updated: Jouw accountgegevens zijn opgeslagen.
sessions:
signed_in: Je bent succesvol ingelogd.
signed_out: Je bent succesvol uitgelogd.
unlocks:
- send_instructions: Je ontvangt via e-mail instructies hoe je je account kan unlocken.
- send_paranoid_instructions: Als je e-mailadres bestaat in de database, ontvang je via e-mail instructies hoe je je account kan unlocken.
- unlocked: Je account is ge-unlocked. Je kan nu weer inloggen.
+ send_instructions: Je ontvangt via e-mail instructies hoe je jouw account kan deblokkeren.
+ send_paranoid_instructions: Als jouw e-mailadres in de database staat, ontvang je via e-mail instructies hoe je jouw account kan deblokkeren.
+ unlocked: Jouw account is gedeblokkeerd. Je kan nu weer inloggen.
errors:
messages:
already_confirmed: is reeds bevestigd
- confirmation_period_expired: moet worden bevestigd binnen %{period}, probeer het a.u.b. nog een keer
+ confirmation_period_expired: moet worden bevestigd binnen %{period}, probeer het nog een keer
expired: is verlopen, vraag een nieuwe aan
not_found: niet gevonden
- not_locked: is niet gesloten
+ not_locked: is niet geblokkeerd
not_saved:
- one: '1 fout blokkeerde het opslaan van deze %{resource}:'
- other: "%{count} fouten blokkeerden het opslaan van deze %{resource}:"
+ one: '1 fout verhinderde het opslaan van deze %{resource}:'
+ other: "%{count} fouten verhinderden het opslaan van deze %{resource}:"
diff --git a/config/locales/devise.pt-BR.yml b/config/locales/devise.pt-BR.yml
new file mode 100644
index 0000000000..c647fabbd8
--- /dev/null
+++ b/config/locales/devise.pt-BR.yml
@@ -0,0 +1,61 @@
+---
+pt-BR:
+ devise:
+ confirmations:
+ confirmed: O seu endereço de email foi confirmado.
+ send_instructions: Você irá receber um email com instruções em como confirmar o seu endereço de email dentro de alguns minutos.
+ send_paranoid_instructions: Se o seu endereço de email já existir na nossa base de dados, irá receber um email com instruções em como confirmá-lo dentro de alguns minutos.
+ failure:
+ already_authenticated: A sua sessão já está aberta.
+ inactive: A sua contra ainda não está ativada.
+ invalid: "%{authentication_keys} ou password inválidos."
+ last_attempt: Tem mais uma tentativa antes de a sua conta ser protegida.
+ locked: A sua conta está protegida
+ not_found_in_database: "%{authentication_keys} ou password inválidos."
+ timeout: A sua sessão expirou. Por favore entre de novo para continuar.
+ unauthenticated: Você precsa de entrar ou registar-se antes de continuar.
+ unconfirmed: Você tem de confirmar o seu endereço de email antes de continuar.
+ mailer:
+ confirmation_instructions:
+ subject: 'Mastodon: Instruções de confirmação'
+ password_change:
+ subject: 'Mastodon: Password nova'
+ reset_password_instructions:
+ subject: 'Mastodon: Instruções para editar a password'
+ unlock_instructions:
+ subject: 'Mastodon: Instruções para desproteger a sua conta'
+ omniauth_callbacks:
+ failure: Could not authenticate you from %{kind} because "%{reason}".
+ success: Successfully authenticated from %{kind} account.
+ passwords:
+ no_token: Você não pode aceder a esta página sem ter vindo de um email para mudar a password. Se este for o case, por favor faça questão de verificar que usou o URL no email.
+ send_instructions: Irá receber um email com instruções em como mudar a sua password dentro de algns minutos.
+ send_paranoid_instructions: Se seu endereço de email existe na nossa base de dados, irá receber um link para recuperar a sua password dentro de alguns minutos.
+ updated: A sua password foi alterada. A sua sessão está aberta.
+ updated_not_active: A sua password foi alterada.
+ registrations:
+ destroyed: Adeus! A sua conta foi cancelada. Esperamos vê-lo em breve.
+ signed_up: Bem vindo! A sua conta foi registada com sucesso.
+ signed_up_but_inactive: A sua conta foi registada. No entanto, não abrimos a sua sessão porque a sua conta ainda não foi ativada.
+ signed_up_but_locked: A sua conta foi registada. No entanto, não abrimos a sua sessão porque a sua conta está protegida.
+ signed_up_but_unconfirmed: Uma mensagem com um link de confirmação foi enviada para o seu email. Por favor siga o link para ativar a sua conta.
+ update_needs_confirmation: Você mudou o seu endereço de email ou password, mas é necessário confirmar a mudança. Por favor siga o link que foi enviado para o seu novo endereço de email.
+ updated: A sua conta foi alterada com sucesso.
+ sessions:
+ already_signed_out: Sessão fechada.
+ signed_in: Sessão iniciada.
+ signed_out: Sessão fechada.
+ unlocks:
+ send_instructions: Irá receber um email com instruções para desproteger a sua conta dentro de alguns minutos.
+ send_paranoid_instructions: Se a sua conta existe, irá receber um email com instruções a detalhar como a desproteger dentro de alguns minutos.
+ unlocked: A sua conta foi desprotegida. Por favor inicie sessão para continuar.
+ errors:
+ messages:
+ already_confirmed: já foi confirmado, por favor tente iniciar sessão
+ confirmation_period_expired: tem de ser confirmado dentro de %{period}, por favor tente outra vez
+ expired: expirou, por favor tente outra vez
+ not_found: não encontrado
+ not_locked: não está protegido
+ not_saved:
+ one: '1 erro impediu este %{resource} de ser guardado:'
+ other: "%{count} erros impediram este %{resource} de ser guardado:"
diff --git a/config/locales/devise.pt.yml b/config/locales/devise.pt.yml
index 8c049ce8b8..dc87cefdd6 100644
--- a/config/locales/devise.pt.yml
+++ b/config/locales/devise.pt.yml
@@ -2,60 +2,60 @@
pt:
devise:
confirmations:
- confirmed: O seu endereço de email foi confirmado.
- send_instructions: Você irá receber um email com instruções em como confirmar o seu endereço de email dentro de alguns minutos.
- send_paranoid_instructions: Se o seu endereço de email já existir na nossa base de dados, irá receber um email com instruções em como confirmá-lo dentro de alguns minutos.
+ confirmed: O teu endereço de email foi confirmado.
+ send_instructions: Vais receber um email com as instruções para confirmar o teu endereço de email dentro de alguns minutos.
+ send_paranoid_instructions: Se o teu endereço de email já existir na nossa base de dados, vais receber um email com as instruções de confirmação dentro de alguns minutos.
failure:
- already_authenticated: A sua sessão já está aberta.
- inactive: A sua contra ainda não está ativada.
- invalid: "%{authentication_keys} ou password inválidos."
- last_attempt: Tem mais uma tentativa antes de a sua conta ser protegida.
- locked: A sua conta está protegida
- not_found_in_database: "%{authentication_keys} ou password inválidos."
- timeout: A sua sessão expirou. Por favore entre de novo para continuar.
- unauthenticated: Você precsa de entrar ou registar-se antes de continuar.
- unconfirmed: Você tem de confirmar o seu endereço de email antes de continuar.
+ already_authenticated: A tua sessão já está aberta.
+ inactive: A tua conta ainda não está ativada.
+ invalid: "%{authentication_keys} ou palavra-passe não válida."
+ last_attempt: Tens mais uma tentativa antes de a tua conta ficar bloqueada.
+ locked: A tua conta está bloqueada
+ not_found_in_database: "%{authentication_keys} ou palavra-passe não válida."
+ timeout: A tua sessão expirou. Por favor, entra de novo para continuares.
+ unauthenticated: Precisas de entrar na tua conta ou registares-te antes de continuar.
+ unconfirmed: Tens de confirmar o teu endereço de email antes de continuar.
mailer:
confirmation_instructions:
- subject: 'Mastodon: Instruções de confirmação'
+ subject: 'Mastodon: Instruções de confirmação %{instance}'
password_change:
- subject: 'Mastodon: Password nova'
+ subject: 'Mastodon: Nova palavra-passe'
reset_password_instructions:
- subject: 'Mastodon: Instruções para editar a password'
+ subject: 'Mastodon: Instruções para editar a palavra-passe'
unlock_instructions:
- subject: 'Mastodon: Instruções para desproteger a sua conta'
+ subject: 'Mastodon: Instruções para desbloquear a tua conta'
omniauth_callbacks:
- failure: Could not authenticate you from %{kind} because "%{reason}".
- success: Successfully authenticated from %{kind} account.
+ failure: Não foi possível autenticar %{kind} porque "%{reason}".
+ success: Autenticado com sucesso na conta %{kind}.
passwords:
- no_token: Você não pode aceder a esta página sem ter vindo de um email para mudar a password. Se este for o case, por favor faça questão de verificar que usou o URL no email.
- send_instructions: Irá receber um email com instruções em como mudar a sua password dentro de algns minutos.
- send_paranoid_instructions: Se seu endereço de email existe na nossa base de dados, irá receber um link para recuperar a sua password dentro de alguns minutos.
- updated: A sua password foi alterada. A sua sessão está aberta.
- updated_not_active: A sua password foi alterada.
+ no_token: Não pode aceder a esta página se não vier através do link enviado por email para alteração da sua palavra-passe. Se usaste esse link para chegar aqui, por favor verifica que o endereço URL actual é o mesmo do que foi enviado no email.
+ send_instructions: Vais receber um email com instruções para alterar a palavra-passe dentro de algns minutos.
+ send_paranoid_instructions: Se o teu endereço de email existe na nossa base de dados, vais receber um link para recuperar a palavra-passe dentro de alguns minutos.
+ updated: A tua palavra-passe foi alterada. Estás agora autenticado na tua conta.
+ updated_not_active: A tua palavra-passe foi alterada.
registrations:
- destroyed: Adeus! A sua conta foi cancelada. Esperamos vê-lo em breve.
- signed_up: Bem vindo! A sua conta foi registada com sucesso.
- signed_up_but_inactive: A sua conta foi registada. No entanto, não abrimos a sua sessão porque a sua conta ainda não foi ativada.
- signed_up_but_locked: A sua conta foi registada. No entanto, não abrimos a sua sessão porque a sua conta está protegida.
- signed_up_but_unconfirmed: Uma mensagem com um link de confirmação foi enviada para o seu email. Por favor siga o link para ativar a sua conta.
- update_needs_confirmation: Você mudou o seu endereço de email ou password, mas é necessário confirmar a mudança. Por favor siga o link que foi enviado para o seu novo endereço de email.
- updated: A sua conta foi alterada com sucesso.
+ destroyed: Adeus! A tua conta foi cancelada. Esperamos ver-te em breve.
+ signed_up: Bem-vindo! A tua conta foi registada com sucesso.
+ signed_up_but_inactive: A tua conta foi registada. No entanto ainda não está activa.
+ signed_up_but_locked: A tua conta foi registada. No entanto está bloqueada.
+ signed_up_but_unconfirmed: Uma mensagem com um link de confirmação foi enviada para o teu email. Por favor segue esse link para activar a tua conta.
+ update_needs_confirmation: Alteraste o teu endereço de email ou palavra-passe, mas é necessário confirmar essa alteração. Por favor vai ao teu email e segue link que te enviámos.
+ updated: A tua conta foi actualizada com sucesso.
sessions:
- already_signed_out: Sessão fechada.
+ already_signed_out: Sessão encerrada.
signed_in: Sessão iniciada.
- signed_out: Sessão fechada.
+ signed_out: Sessão encerrada.
unlocks:
- send_instructions: Irá receber um email com instruções para desproteger a sua conta dentro de alguns minutos.
- send_paranoid_instructions: Se a sua conta existe, irá receber um email com instruções a detalhar como a desproteger dentro de alguns minutos.
- unlocked: A sua conta foi desprotegida. Por favor inicie sessão para continuar.
+ send_instructions: Vais receber um email com instruções para desbloquear a tua conta dentro de alguns minutos.
+ send_paranoid_instructions: Se a tua conta existe, vais receber um email com instruções a detalhar como a desbloquear dentro de alguns minutos.
+ unlocked: A sua conta foi desbloqueada. Por favor inica uma nova sessão para continuar.
errors:
messages:
- already_confirmed: já foi confirmado, por favor tente iniciar sessão
- confirmation_period_expired: tem de ser confirmado dentro de %{period}, por favor tente outra vez
+ already_confirmed: já confirmado, por favor tente iniciar sessão
+ confirmation_period_expired: tem de ser confirmado durante %{period}, por favor tenta outra vez
expired: expirou, por favor tente outra vez
not_found: não encontrado
- not_locked: não está protegido
+ not_locked: não estava bloqueada
not_saved:
one: '1 erro impediu este %{resource} de ser guardado:'
other: "%{count} erros impediram este %{resource} de ser guardado:"
diff --git a/config/locales/doorkeeper.nl.yml b/config/locales/doorkeeper.nl.yml
index 91e62dc0c1..9edbb8c9f1 100644
--- a/config/locales/doorkeeper.nl.yml
+++ b/config/locales/doorkeeper.nl.yml
@@ -4,7 +4,7 @@ nl:
attributes:
doorkeeper/application:
name: Naam
- redirect_uri: Redirect URI
+ redirect_uri: Redirect-URI
scopes: Scopes
errors:
models:
@@ -26,15 +26,15 @@ nl:
confirmations:
destroy: Weet je het zeker?
edit:
- title: Bewerk applicatie
+ title: Applicatie bewerken
form:
error: Oops! Controleer het formulier op fouten
help:
native_redirect_uri: Gebruik %{native_redirect_uri} voor lokale tests
redirect_uri: 'Gebruik één regel per URI. '
- scopes: Scheid scopes met spaties. Laat leeg om de standaard scopes te gebruiken.
+ scopes: Scopes met spaties van elkaar scheiden. Laat leeg om de standaardscopes te gebruiken.
index:
- callback_url: Callback URL
+ callback_url: Callback-URL
name: Naam
new: Nieuwe applicatie
title: Jouw applicaties
@@ -42,8 +42,8 @@ nl:
title: Nieuwe applicatie
show:
actions: Acties
- application_id: Applicatie Id
- callback_urls: Callback urls
+ application_id: Applicatie-ID
+ callback_urls: Callback-URL's
scopes: Scopes
secret: Secret
title: 'Applicatie: %{name}'
@@ -58,7 +58,7 @@ nl:
prompt: "%{client_name} autoriseren om uw account te gebruiken?"
title: Autorisatie vereist
show:
- title: Autorisatie code
+ title: Autorisatie-code
authorized_applications:
buttons:
revoke: Intrekken
@@ -71,24 +71,24 @@ nl:
title: Jouw geautoriseerde applicaties
errors:
messages:
- access_denied: De resource eigenaar of autorisatie-server weigerde het verzoek.
- credential_flow_not_configured: Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured.
- invalid_client: Client verificatie is mislukt door onbekende klant, geen client authenticatie opgegeven, of een niet-ondersteunde authenticatie methode.
- invalid_grant: De verstrekte autorisatie is ongeldig, verlopen, ingetrokken, komt niet overeen met de redirect uri die is opgegeven, of werd uitgegeven aan een andere klant.
- invalid_redirect_uri: De opgegeven redirect uri is niet geldig.
- invalid_request: Het verzoek mist een vereiste parameter, bevat een niet-ondersteunde parameter waarde of is anderszins onjuist.
- invalid_resource_owner: De verstrekte resource eigenaar gegevens zijn niet geldig of de resource eigenaar kan niet worden gevonden
- invalid_scope: De opgevraagde scope is niet geldig, onbekend of onjuist.
+ access_denied: De resource-eigenaar of autorisatie-server weigerde het verzoek.
+ credential_flow_not_configured: De wachtwoordgegevens-flow van de resource-eigenaar is mislukt omdat Doorkeeper.configure.resource_owner_from_credentials niet is ingesteld.
+ invalid_client: Clientverificatie is mislukt door een onbekende client, ontbrekende client-authenticatie of een niet ondersteunde authenticatie-methode.
+ invalid_grant: De verstrekte autorisatie is ongeldig, verlopen, ingetrokken, komt niet overeen met de redirect-URI die is opgegeven of werd uitgegeven aan een andere client.
+ invalid_redirect_uri: De opgegeven redirect-URI is ongeldig.
+ invalid_request: Het verzoek mist een vereiste parameter, bevat een niet ondersteunde parameterwaarde of is anderszins onjuist.
+ invalid_resource_owner: De verstrekte resource-eigenaargegevens zijn ogeldig of de resource-eigenaar kan niet worden gevonden.
+ invalid_scope: De opgevraagde scope is ongeldig, onbekend of onjuist.
invalid_token:
- expired: Het toegangstoken is verlopen
- revoked: Het toegangstoken is geweigerd
- unknown: Het toegangstoken is ongeldig
- resource_owner_authenticator_not_configured: Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfiged.
- server_error: De autorisatieserver is een onverwachte voorwaarde tegengekomen die het verzoek verhinderd.
+ expired: Het toegangssleutel is verlopen
+ revoked: Het toegangssleutel is geweigerd
+ unknown: Het toegangssleutel is ongeldig
+ resource_owner_authenticator_not_configured: Het opzoeken van de resource-eigenaar is mislukt omdat Doorkeeper.configure.resource_owner_authenticator niet is ingesteld.
+ server_error: De autorisatieserver is is een onverwachte situatie tegengekomen die het verzoek verhinderde.
temporarily_unavailable: De autorisatieserver is momenteel niet in staat het verzoek te behandelen als gevolg van een tijdelijke overbelasting of onderhoud aan de server.
- unauthorized_client: De client is niet bevoegd om dit verzoek met deze methode uit te voeren.
- unsupported_grant_type: Het type autorisatie is niet ondersteund door de autorisatieserver
- unsupported_response_type: De autorisatieserver ondersteund dit response type niet
+ unauthorized_client: De client is niet bevoegd om dit verzoek op deze manier uit te voeren.
+ unsupported_grant_type: Het type autorisatie wordt niet door de autorisatieserver ondersteund
+ unsupported_response_type: De autorisatieserver ondersteund dit antwoordtype niet
flash:
applications:
create:
@@ -105,10 +105,10 @@ nl:
nav:
applications: Applicaties
home: Home
- oauth2_provider: OAuth2 Provider
+ oauth2_provider: OAuth2-provider
application:
- title: OAuth autorisatie vereist
+ title: OAuth-autorisatie vereist
scopes:
- follow: volg, blokkeer, deblokkeer en stop volgen accounts
- read: lees je accountgegevens
- write: plaatsen namens jou
+ follow: volg, blokkeer, deblokkeer en stop het volgen van accounts
+ read: lees jouw accountgegevens
+ write: namens jou plaatsen
diff --git a/config/locales/doorkeeper.oc.yml b/config/locales/doorkeeper.oc.yml
new file mode 100644
index 0000000000..b60c284040
--- /dev/null
+++ b/config/locales/doorkeeper.oc.yml
@@ -0,0 +1,113 @@
+---
+oc:
+ activerecord:
+ attributes:
+ doorkeeper/application:
+ name: Nom
+ redirect_uri: URL de redireccion
+ errors:
+ models:
+ doorkeeper/application:
+ attributes:
+ redirect_uri:
+ fragment_present: pòt pas conténer un tròç.
+ invalid_uri: deu èstre un URI valid.
+ relative_uri: deu èstre un URI absolut.
+ secured_uri: deu èstre un HTTPS/SSL URI.
+ doorkeeper:
+ applications:
+ buttons:
+ authorize: Autorizar
+ cancel: Anullar
+ destroy: Suprimir
+ edit: Modificar
+ submit: Mandar
+ confirmations:
+ destroy: Sètz segur ?
+ edit:
+ title: Modificar l'aplicacion
+ form:
+ error: Ops ! Verificatz vòstre formulari
+ help:
+ native_redirect_uri: Emplegatz %{native_redirect_uri} per d'ensages locales
+ redirect_uri: Utilizatz una linha per URI
+ scopes: Separatz los encastres amb d’espacis. Daissatz void per utilizar l’encastre per defaut.
+ index:
+ callback_url: URL de rapèl
+ name: Nom
+ new: Nòva aplicacion
+ title: Vòstra aplicacions
+ new:
+ title: Nòva aplicacion
+ show:
+ actions: Accions
+ application_id: Id de l’aplicacion
+ callback_urls: urls de rapèls
+ scopes: Encastres
+ secret: Secret
+ title: 'Aplicacion: %{name}'
+ authorizations:
+ buttons:
+ authorize: Autorizar
+ deny: Refusar
+ error:
+ title: I a agut un error
+ new:
+ able_to: Aquesta aplicacion poirà
+ prompt: L’aplicacion %{client_name} demanda l’accès al vòstre compte.
+ title: Cal l’autorizacion
+ show:
+ title: Còdi d’autorizacion
+ authorized_applications:
+ buttons:
+ revoke: Revocar
+ confirmations:
+ revoke: Ne sètz segur?
+ index:
+ application: Aplicacion
+ created_at: Creada lo
+ date_format: "%d-%m-%Y %Ho%M %S"
+ scopes: Encastres
+ title: Las vòstras aplicacions autorizadas
+ errors:
+ messages:
+ access_denied: Lo proprietari de la ressorça o lo servider d’autorizacion refusèt la demanda.
+ credential_flow_not_configured: Lo flux de qualificacion del senhal del proprietari de la ressorça capitèt pas pr’amor que Doorkeeper.configure.resource_owner_from_credentials es pas configurat.
+ invalid_client: L’autorizacion del client capitèt pas pr’amor que lo client es desconegut, l’autorizacion del client es pas enclús, o lo metòde d’autorizacion es pas suportat.
+ invalid_grant: L’acòrdi d’autorizacion donadat es pas valid, expirat, revocat, una redireccion URI utilizat en la demanda d’autorizacion no correspond, o a estat desliurat a un altre client.
+ invalid_redirect_uri: L'URL de redireccion es pas valida.
+ invalid_request: La demanda a un paramètre que li manca, a una valor qu’es pas suportada, o quicòm mal format.
+ invalid_resource_owner: La qualificacion del proprietari de la ressorça donada es pas valid, o lo proprietari de la ressorça se pòt pas trobar.
+ invalid_scope: L’encastre demandat es pas valid, o mal format.
+ invalid_token:
+ expired: Lo geton d’accès a expirat
+ revoked: Lo geton d’accès a estat revocat
+ unknown: Lo geton d’accès es pas valid
+ resource_owner_authenticator_not_configured: La recèrca del proprietari de la ressorça a pas capitat pr’amor que Doorkeeper.configure.resource_owner_authenticator es pas configurat.
+ server_error: Lo servider d’autorizacion trobèt una condicion que l’empachèt d’acomplir la demanda.
+ temporarily_unavailable: Lo servider d’autorizacion pòt actualament pas menar la demanda pr’amor que es temporalament subrecargat o es en mantenença.
+ unauthorized_client: Lo client es pas autorizat a far aquesta demanda en utlizant aqueste metòde.
+ unsupported_grant_type: Lo tipe de qualificacion de l’autorizacion es pas suportat pel servider d’autorizacion.
+ unsupported_response_type: Lo servider d’autorizacion supòrta pas aqueste tipe de responsa.
+ flash:
+ applications:
+ create:
+ notice: Aplicacion creada.
+ destroy:
+ notice: Aplicacion escafada.
+ update:
+ notice: Aplicacion mesa a jorn.
+ authorized_applications:
+ destroy:
+ notice: Aplicacion revocada.
+ layouts:
+ admin:
+ nav:
+ applications: Aplicacions
+ oauth2_provider: Provesidor OAuth
+ application:
+ title: Cal una autorizacion OAuth
+ scopes:
+ follow: sègre, blocar, quitar de blocar e quitar de sègre de comptes
+ read: legissètz las donadas de vòstre compte
+ write: publicatz per vos
diff --git a/config/locales/doorkeeper.pt-BR.yml b/config/locales/doorkeeper.pt-BR.yml
new file mode 100644
index 0000000000..85ea3bfccf
--- /dev/null
+++ b/config/locales/doorkeeper.pt-BR.yml
@@ -0,0 +1,112 @@
+---
+pt-BR:
+ activerecord:
+ attributes:
+ doorkeeper/application:
+ name: Nome
+ redirect_uri: Redirect URI
+ errors:
+ models:
+ doorkeeper/application:
+ attributes:
+ redirect_uri:
+ fragment_present: não pode conter um fragmento.
+ invalid_uri: tem de ser um URI válido.
+ relative_uri: tem de ser um URI absoluto.
+ secured_uri: tem de ser um HTTPS/SSL URI.
+ doorkeeper:
+ applications:
+ buttons:
+ authorize: Autorizar
+ cancel: Cancelar
+ destroy: Destruir
+ edit: Editar
+ submit: Submeter
+ confirmations:
+ destroy: Tem a certeza?
+ edit:
+ title: Editar aplicação
+ form:
+ error: Oops! Verifique que o formulário não tem erros
+ help:
+ native_redirect_uri: Use %{native_redirect_uri} para testes locais
+ redirect_uri: Utilize uma linha por URI
+ scopes: Separate scopes with spaces. Leave blank to use the default scopes.
+ index:
+ callback_url: Callback URL
+ name: Nome
+ new: Nova Aplicação
+ title: As suas aplicações
+ new:
+ title: Nova aplicação
+ show:
+ actions: Ações
+ application_id: Id de Aplicação
+ callback_urls: Callback urls
+ scopes: Scopes
+ secret: Segredo
+ title: 'Aplicação: %{name}'
+ authorizations:
+ buttons:
+ authorize: Autorize
+ deny: Não autorize
+ error:
+ title: Ocorreu um erro
+ new:
+ able_to: Vai poder
+ prompt: Aplicação %{client_name} requisita acesso à sua conta
+ title: Autorização é necessária
+ show:
+ title: Código de autorização
+ authorized_applications:
+ buttons:
+ revoke: Revogar
+ confirmations:
+ revoke: Tem a certeza?
+ index:
+ application: Aplicação
+ created_at: Criada em
+ date_format: "%Y-%m-%d %H:%M:%S"
+ title: As suas aplicações autorizadas
+ errors:
+ messages:
+ access_denied: The resource owner or authorization server denied the request.
+ credential_flow_not_configured: Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured.
+ invalid_client: Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method.
+ invalid_grant: The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.
+ invalid_redirect_uri: The redirect uri included is not valid.
+ invalid_request: The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.
+ invalid_resource_owner: The provided resource owner credentials are not valid, or resource owner cannot be found
+ invalid_scope: The requested scope is invalid, unknown, or malformed.
+ invalid_token:
+ expired: O token de acesso expirou
+ revoked: O token de acesso foi revogado
+ unknown: O token de acesso é inválido
+ resource_owner_authenticator_not_configured: Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfiged.
+ server_error: The authorization server encountered an unexpected condition which prevented it from fulfilling the request.
+ temporarily_unavailable: The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.
+ unauthorized_client: The client is not authorized to perform this request using this method.
+ unsupported_grant_type: The authorization grant type is not supported by the authorization server.
+ unsupported_response_type: The authorization server does not support this response type.
+ flash:
+ applications:
+ create:
+ notice: Aplicação criada.
+ destroy:
+ notice: Aplicação eliminada.
+ update:
+ notice: Aplicação alterada.
+ authorized_applications:
+ destroy:
+ notice: Aplicação revogada.
+ layouts:
+ admin:
+ nav:
+ applications: Aplicações
+ oauth2_provider: OAuth2 Provider
+ application:
+ title: Autorização OAuth necessária
+ scopes:
+ follow: siga, bloqueie, desbloqueie, e deixe de seguir contas
+ read: tenha acesso aos dados da sua conta
+ write: públique por si
diff --git a/config/locales/doorkeeper.pt.yml b/config/locales/doorkeeper.pt.yml
index 2709856e85..87e01ba940 100644
--- a/config/locales/doorkeeper.pt.yml
+++ b/config/locales/doorkeeper.pt.yml
@@ -23,20 +23,20 @@ pt:
edit: Editar
submit: Submeter
confirmations:
- destroy: Tem a certeza?
+ destroy: Tens a certeza?
edit:
title: Editar aplicação
form:
- error: Oops! Verifique que o formulário não tem erros
+ error: Oops! Verifica que o formulário não tem erros
help:
- native_redirect_uri: Use %{native_redirect_uri} para testes locais
- redirect_uri: Utilize uma linha por URI
+ native_redirect_uri: Usa %{native_redirect_uri} para testes locais
+ redirect_uri: Utiliza uma linha por URI
scopes: Separate scopes with spaces. Leave blank to use the default scopes.
index:
callback_url: Callback URL
name: Nome
new: Nova Aplicação
- title: As suas aplicações
+ title: As tuas aplicações
new:
title: Nova aplicação
show:
@@ -54,7 +54,7 @@ pt:
title: Ocorreu um erro
new:
able_to: Vai poder
- prompt: Aplicação %{client_name} requisita acesso à sua conta
+ prompt: Aplicação %{client_name} pede acesso à tua conta
title: Autorização é necessária
show:
title: Código de autorização
@@ -62,12 +62,12 @@ pt:
buttons:
revoke: Revogar
confirmations:
- revoke: Tem a certeza?
+ revoke: Tens a certeza?
index:
application: Aplicação
created_at: Criada em
date_format: "%Y-%m-%d %H:%M:%S"
- title: As suas aplicações autorizadas
+ title: As tuas aplicações autorizadas
errors:
messages:
access_denied: The resource owner or authorization server denied the request.
@@ -107,6 +107,6 @@ pt:
application:
title: Autorização OAuth necessária
scopes:
- follow: siga, bloqueie, desbloqueie, e deixe de seguir contas
- read: tenha acesso aos dados da sua conta
- write: públique por si
\ No newline at end of file
+ follow: siga, bloqueie, desbloqueie, e deixa de seguir contas
+ read: tenha acesso aos dados da tua conta
+ write: públique por ti
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 039dabf871..c7b6a17d68 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -73,6 +73,10 @@ en:
push_subscription_expires: PuSH subscription expires
reset_password: Reset password
salmon_url: Salmon URL
+ show:
+ created_reports: Reports created by this account
+ report: report
+ targeted_reports: Reports made about this account
silence: Silence
statuses: Statuses
title: Accounts
@@ -110,6 +114,10 @@ en:
undo: Undo
title: Domain Blocks
undo: Undo
+ instances:
+ account_count: Known accounts
+ domain_name: Domain
+ title: Known Instances
pubsubhubbub:
callback_url: Callback URL
confirmed: Confirmed
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index ec38b411af..bd67b4409d 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -71,16 +71,19 @@ fr:
profile_url: URL du profil
public: Public
push_subscription_expires: Expiration de l'abonnement PuSH
+ reset_password: Réinitialiser le mot de passe
salmon_url: URL Salmon
silence: Rendre muet
statuses: Statuts
title: Comptes
- undo_silenced: Annuler la mu
+ undo_silenced: Annuler le silence
undo_suspension: Annuler la suspension
username: Nom d'utilisateur
web: Web
domain_blocks:
add_new: Ajouter
+ created_msg: Le blocage de domaine est désormais activé
+ destroyed_msg: Le blocage de domaine a été désactivé
domain: Domaine
new:
create: Créer le blocage
@@ -90,8 +93,23 @@ fr:
silence: Muet
suspend: Suspendre
title: Nouveau blocage de domaine
+ reject_media: Fichiers média rejetés
+ reject_media_hint: Supprime localement les fichiers média stockés et refuse d'en télécharger ultérieurement. Ne concerne pas les suspensions.
+ severities:
+ silence: Rendre muet
+ suspend: Suspendre
severity: Séverité
+ show:
+ affected_accounts:
+ one: Un compte affecté dans la base de données
+ other: "%{count} comptes affectés dans la base de données"
+ retroactive:
+ silence: Annuler le silence sur tous les comptes existants pour ce domaine
+ suspend: Annuler la suspension sur tous les comptes existants pour ce domaine
+ title: Annuler le blocage de domaine pour %{domain}
+ undo: Annuler
title: Blocage de domaines
+ undo: Annuler
pubsubhubbub:
callback_url: URL de rappel
confirmed: Confirmé
@@ -105,7 +123,7 @@ fr:
none: Aucun
delete: Supprimer
id: ID
- mark_as_resolved: Marqué comme résolu
+ mark_as_resolved: Marquer comme résolu
report: 'Signalement #%{id}'
reported_account: Compte signalé
reported_by: Signalé par
@@ -192,6 +210,7 @@ fr:
blocks: Vous bloquez
csv: CSV
follows: Vous suivez
+ mutes: Vous faites taire
storage: Médias stockés
generic:
changes_saved_msg: Les modifications ont été enregistrées avec succès !
@@ -199,15 +218,20 @@ fr:
save_changes: Enregistrer les modifications
validation_errors:
one: Quelque chose ne va pas ! Vérifiez l’erreur ci-dessous.
- other: Quelques choses ne vont pas ! Vérifiez les erreurs ci-dessous.
+ other: Certaines choses ne vont pas ! Vérifiez les erreurs ci-dessous.
imports:
- preface: Vous pouvez importer certaines données comme les personnes que vous suivez ou bloquez sur votre compte sur cette instance à partir de fichiers crées sur une autre instance.
- success: Vos données ont été importées avec succès et seront traités en temps et en heure
+ preface: Vous pouvez importer certaines données comme les personnes que vous suivez ou bloquez sur votre compte sur cette instance à partir de fichiers créés sur une autre instance.
+ success: Vos données ont été importées avec succès et seront traitées en temps et en heure
types:
blocking: Liste d'utilisateurs⋅trices bloqué⋅es
following: Liste d'utilisateurs⋅trices suivi⋅es
+ muting: Liste d'utilisateurs⋅trices que vous faites taire
upload: Importer
landing_strip_html: %{name} utilise %{domain}. Vous pouvez le/la suivre et interagir si vous possédez un compte quelque part dans le "fediverse". Si ce n'est pas le cas, vous pouvez en créer un ici.
+ media_attachments:
+ validations:
+ images_and_video: Impossible de joindre une vidéo à un statut contenant déjà des images
+ too_many: Impossible de joindre plus de 4 fichiers
notification_mailer:
digest:
body: 'Voici ce que vous avez raté sur ${instance} depuis votre dernière visite (%{}) :'
@@ -253,23 +277,32 @@ fr:
statuses:
open_in_web: Ouvrir sur le web
over_character_limit: limite de caractères dépassée de %{max} caractères
- show_more: Montrer plus
+ show_more: Afficher plus
visibilities:
private: Abonné⋅es uniquement
public: Public
unlisted: Public sans être affiché sur le fil public
stream_entries:
- click_to_show: Clic pour afficher
+ click_to_show: Cliquer pour afficher
reblogged: partagé
sensitive_content: Contenu sensible
time:
formats:
default: "%d %b %Y, %H:%M"
two_factor_auth:
+ code_hint: Entrez le code généré par votre application pour confirmer
description_html: Si vous activez l'identification à deux facteurs, vous devrez être en possession de votre téléphone afin de générer un code de connexion.
disable: Désactiver
enable: Activer
+ enabled_success: Identification à deux facteurs activée avec succès
+ generate_recovery_codes: Générer les codes de récupération
instructions_html: "Scannez ce QR code grâce à Google Authenticator, Authy ou une application similaire sur votre téléphone. Désormais, cette application générera des jetons que vous devrez saisir à chaque connexion."
+ lost_recovery_codes: Les codes de récupération vous permettent de retrouver les accès à votre comptre si vous perdez votre téléphone. Si vous perdez vos codes de récupération, vous pouvez les générer à nouveau ici. Vos anciens codes de récupération seront invalidés.
+ manual_instructions: "Si vous ne pouvez pas scanner ce QR code et devez l'entrer manuellement, voici le secret en clair :"
+ recovery_codes_regenerated: Codes de récupération régénérés avec succès
+ recovery_instructions: Si vous perdez l'accès à votre téléphone, vous pouvez utiliser un des codes de récupération ci-dessous pour récupérer l'accès à votre compte. Conservez les codes de récupération en toute sécurité, par exemple, en les imprimant et en les stockant avec vos autres documents importants.
+ setup: Installer
+ wrong_code: Les codes entrés sont incorrects ! L'heure du serveur et celle de votre appareil sont-elles correctes ?
users:
invalid_email: L'adresse courriel est invalide
invalid_otp_token: Le code d'authentification à deux facteurs est invalide
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index c9885b5f65..47b86b38ec 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -73,6 +73,10 @@ ja:
push_subscription_expires: PuSH購読期限切れ
reset_password: パスワード再設定
salmon_url: Salmon URL
+ show:
+ created_reports: このアカウントで作られたレポート
+ report: レポート
+ targeted_reports: このアカウントについてのレポート
silence: サイレンス
statuses: トゥート数
title: アカウント
@@ -94,13 +98,15 @@ ja:
suspend: 停止
title: 新規ドメインブロック
reject_media: メディアファイルを拒否
- reject_media_hint: ローカルに保村されたメディアファイルを削除し、今後のダウンロードを拒否します。停止とは無関係です。
+ reject_media_hint: ローカルに保存されたメディアファイルを削除し、今後のダウンロードを拒否します。停止とは無関係です。
severities:
silence: サイレンス
suspend: 停止
severity: 深刻度
show:
- affected_accounts: "データベース中の%{count}個のアカウントに影響します"
+ affected_accounts:
+ one: "データベース中の一つのアカウントに影響します"
+ other: "データベース中の%{count}個のアカウントに影響します"
retroactive:
silence: このドメインからの存在するすべてのアカウントのサイレンスを戻す
suspend: このドメインからの存在するすべてのアカウントの停止を戻す
@@ -108,6 +114,10 @@ ja:
undo: 元に戻す
title: ドメインブロック
undo: 元に戻す
+ instances:
+ account_count: 既知のアカウント数
+ domain_name: ドメイン名
+ title: 既知のインスタンス
pubsubhubbub:
callback_url: コールバックURL
confirmed: 確認済み
@@ -272,9 +282,9 @@ ja:
over_character_limit: 上限は %{max}文字までです
show_more: もっと見る
visibilities:
- private: Private - フォロワーだけに見せる
- public: Public - 全体に公開する
- unlisted: Unlisted - トゥートは公開するが、公開タイムラインには表示しない
+ private: 非公開 - フォロワーだけに公開
+ public: 公開 - 公開タイムラインに投稿する
+ unlisted: 未収載 - トゥートは公開するが、公開タイムラインには表示しない
stream_entries:
click_to_show: クリックして表示
reblogged: ブーストされました
@@ -288,12 +298,12 @@ ja:
disable: 無効
enable: 有効
enabled_success: 二段階認証が有効になりました
- generate_recovery_codes: 復元コードを生成
+ generate_recovery_codes: リカバリーコードを生成
instructions_html: "Google Authenticatorか、もしくはほかのTOTPアプリでこのQRコードをスキャンしてください。これ以降、ログインするときはそのアプリで生成されるコードが必要になります。"
- lost_recovery_codes: リカバリコードを使用すると携帯電話を紛失した場合でもアカウントにアクセスできるようになります。 リカバリーコードを紛失した場合もここで再生成することができますが、古いリカバリコードは無効になります。
+ lost_recovery_codes: リカバリーコードを使用すると携帯電話を紛失した場合でもアカウントにアクセスできるようになります。 リカバリーコードを紛失した場合もここで再生成することができますが、古いリカバリーコードは無効になります。
manual_instructions: 'QRコードがスキャンできず、手動での登録を希望の場合はこのシークレットコードを利用してください。:'
recovery_codes_regenerated: リカバリーコードが再生成されました。
- recovery_instructions: 携帯電話を紛失した場合、以下の内どれかのリカバリコードを使用してアカウントへアクセスすることができます。 リカバリコードは印刷して安全に保管してください。
+ recovery_instructions: 携帯電話を紛失した場合、以下の内どれかのリカバリーコードを使用してアカウントへアクセスすることができます。 リカバリーコードは印刷して安全に保管してください。
setup: 初期設定
wrong_code: コードが間違っています。サーバー上の時間とデバイス上の時間が一致していることを確認してください。
users:
diff --git a/config/locales/nl.yml b/config/locales/nl.yml
index 577b324549..8471743a50 100644
--- a/config/locales/nl.yml
+++ b/config/locales/nl.yml
@@ -1,34 +1,34 @@
---
nl:
about:
- about_mastodon: Mastodon is een vrij, gratis en open-source sociaal netwerk. Een gedecentraliseerd alternatief voor commerciële platforms, het voorkomt de risico's van een enkel bedrijf dat jouw communicatie monopoliseert. Kies een server die je vertrouwt — welke je ook kiest, je kunt met iedere ander communiceren. Iedereen kan een eigen Mastodon-server draaien en naadloos deelnemen in het sociale netwerk.
+ about_mastodon: Mastodon is een vrij, gratis en open-source sociaal netwerk. Een gedecentraliseerd alternatief voor commerciële platforms. Het voorkomt de risico's van een enkel bedrijf dat jouw communicatie monopoliseert. Kies een server die je vertrouwt — welke je ook kiest, je kunt met elke andere server communiceren. Iedereen kan een eigen Mastodon-server draaien en naadloos deelnemen in het sociale netwerk.
about_this: Over deze server
apps: Apps
- business_email: 'Zakelijk e-mailadres:'
+ business_email: 'E-mailadres:'
closed_registrations: Registreren op deze server is momenteel uitgeschakeld.
contact: Contact
description_headline: Wat is %{domain}?
domain_count_after: andere servers
domain_count_before: Verbonden met
features:
- api: Open API voor apps en services
- blocks: Rijke blokkeer- en dempingshulpmiddelen
+ api: Open API voor apps en diensten
+ blocks: Uitgebreide blokkeer- en negeerhulpmiddelen
characters: 500 tekens per bericht
chronology: Tijdlijnen zijn chronologisch
- ethics: 'Ethisch design: geen ads, geen spionage'
+ ethics: 'Ethisch design: geen advertenties, geen spionage'
gifv: GIFV-sets en korte video's
- privacy: Granulaire privacyinstellingen per bericht
+ privacy: Nauwkeurige privacyinstellingen per toot (bericht)
public: Openbare tijdlijnen
features_headline: Wat maakt Mastodon anders
get_started: Beginnen
links: Links
other_instances: Andere servers
- source_code: Source code
- status_count_after: statussen
- status_count_before: Wie schreef
+ source_code: Broncode
+ status_count_after: toots
+ status_count_before: Zij schreven
terms: Voorwaarden
user_count_after: gebruikers
- user_count_before: Thuis naar
+ user_count_before: Thuisbasis van
accounts:
follow: Volgen
followers: Volgers
@@ -37,7 +37,7 @@ nl:
people_followed_by: Mensen die %{name} volgt
people_who_follow: Mensen die %{name} volgen
posts: Berichten
- remote_follow: Externe volg
+ remote_follow: Extern volgen
unfollow: Ontvolgen
application_mailer:
settings: 'E-mailvoorkeuren wijzigen: %{link}'
@@ -58,7 +58,7 @@ nl:
authorize_follow:
error: Helaas, er is een fout opgetreden bij het opzoeken van de externe account
follow: Volgen
- prompt_html: 'Je (%{self}) hebt volgen aangevraagd:'
+ prompt_html: 'Je (%{self}) hebt toestemming gevraagd om iemand te mogen volgen:'
title: Volg %{acct}
datetime:
distance_in_words:
@@ -66,84 +66,86 @@ nl:
about_x_months: "%{count}ma"
about_x_years: "%{count}j"
almost_x_years: "%{count}j"
- half_a_minute: Net
+ half_a_minute: Zojuist
less_than_x_minutes: "%{count}m"
- less_than_x_seconds: Net
+ less_than_x_seconds: Zojuist
over_x_years: "%{count}j"
x_days: "%{count}d"
x_minutes: "%{count}m"
x_months: "%{count}ma"
x_seconds: "%{count}s"
exports:
- blocks: Je blokkeert
+ blocks: Jij blokkeert
csv: CSV
- follows: Je volgt
+ follows: Jij volgt
+ mutes: Jij negeert
storage: Mediaopslag
generic:
changes_saved_msg: Wijzigingen succesvol opgeslagen!
powered_by: mogelijk gemaakt door %{link}
- save_changes: Wijziginen opslaan
+ save_changes: Wijzigingen opslaan
validation_errors:
one: Er is iets niet helemaal goed! Bekijk onderstaande fout
other: Er is iets niet helemaal goed! Bekijk onderstaande %{count} fouten
imports:
- preface: Je kunt bepaalde gegevens, zoals de mensen die je volgt of blokkeert, importeren voor je account op deze server, als ze zijn geëxporteerd op een andere server.
- success: Je gegevens zijn succesvol geüpload en worden binnenkort verwerkt
+ preface: Je kunt bepaalde gegevens, zoals de mensen die jij volgt of hebt geblokkeerd, naar jouw account op deze server importeren. Je moet deze gegevens wel eerst op de oorspronkelijke server exporteren.
+ success: Jouw gegevens zijn succesvol geüpload en worden binnenkort verwerkt
types:
blocking: Blokkeerlijst
following: Volglijst
+ muting: Negeerlijst
upload: Uploaden
- landing_strip_html: %{name} is een gebruiker op %{domain}. Je kunt deze volgen of ermee interacteren als je ergens in deze fediverse een account hebt. Als je dat niet hebt, kun je je hier aanmelden.
+ landing_strip_html: %{name} is een gebruiker op %{domain}. Je kunt deze volgen en ermee communiceren als je ergens in deze fediverse een account hebt. Als je dat niet hebt, kun je je hier aanmelden.
notification_mailer:
digest:
- body: 'Hier is een korte samenvatting van wat je hebt gemist op %{instance} sinds je laatste bezoek op %{since}:'
- mention: "%{name} vermeldde je in:"
+ body: 'Hier is een korte samenvatting van wat je hebt gemist op %{instance} sinds jouw laatste bezoek op %{since}:'
+ mention: "%{name} vermeldde jou in:"
new_followers_summary:
- one: Je hebt een nieuwe volger! Hoera!
- other: Je hebt %{count} nieuwe volgers! Prachtig!
+ one: Jij hebt een nieuwe volger! Hoera!
+ other: Jij hebt %{count} nieuwe volgers! Prachtig!
subject:
- one: "1 nieuwe melding sinds je laatste bezoek \U0001F418"
- other: "%{count} nieuwe meldingen sinds je laatste bezoek \U0001F418"
+ one: "1 nieuwe melding sinds jouw laatste bezoek \U0001F418"
+ other: "%{count} nieuwe meldingen sinds jouw laatste bezoek \U0001F418"
favourite:
- body: 'Je status werd door %{name} als favoriet gemarkeerd:'
- subject: "%{name} markeerde je status als favoriet"
+ body: 'Jouw toot werd door %{name} als favoriet gemarkeerd:'
+ subject: "%{name} markeerde jouw toot als favoriet"
follow:
- body: "%{name} volgt je nu!"
- subject: "%{name} volgt je nu"
+ body: "%{name} volgt jou nu!"
+ subject: "%{name} volgt jou nu"
follow_request:
- body: "%{name} wil je graag volgen"
+ body: "%{name} wil jou graag volgen"
subject: 'Volgen in afwachting: %{name}'
mention:
- body: 'Je bent door %{name} vermeld in:'
- subject: Je bent vermeld door %{name}
+ body: 'Jij bent door %{name} vermeld in:'
+ subject: Jij bent vermeld door %{name}
reblog:
- body: 'Je status werd geboost door %{name}:'
- subject: "%{name} booste je status"
+ body: 'Jouw toot werd door %{name} geboost:'
+ subject: "%{name} booste jouw toot"
pagination:
next: Volgende
prev: Vorige
remote_follow:
- acct: Geef je gebruikersnaam@domein op waarvandaan je wilt volgen
- missing_resource: Kon vereiste doorverwijzings-URL voor je account niet vinden
+ acct: Geef jouw account@domein.tld op waarvandaan je wilt volgen
+ missing_resource: Kon vereiste doorverwijzings-URL voor jouw account niet vinden
proceed: Ga door om te volgen
- prompt: 'Je gaat volgen:'
+ prompt: 'Jij gaat volgen:'
settings:
authorized_apps: Geautoriseerde
back: Terug naar Mastodon
edit_profile: Profiel bewerken
- export: Gegevensexport
+ export: Export
import: Import
preferences: Voorkeuren
settings: Instellingen
- two_factor_auth: Twee-factorauthenticatie
+ two_factor_auth: Tweestapsverificatie
statuses:
open_in_web: Openen in web
- over_character_limit: Tekenlimiet van %{max} overschreden
+ over_character_limit: Limiet van %{max} tekens overschreden
show_more: Toon meer
visibilities:
private: Alleen aan volgers tonen
public: Openbaar
- unlisted: Openbaar, maar niet tonen op openbare tijdlijn
+ unlisted: Openbaar, maar niet op de openbare tijdlijn tonen
stream_entries:
click_to_show: Klik om te tonen
reblogged: boostte
@@ -152,10 +154,59 @@ nl:
formats:
default: "%b %d, %J, %U:%M"
two_factor_auth:
- description_html: Als je twee-factorauthenticatie instelt, kun je alleen aanmelden als je je mobiele telefoon bij je hebt, waarmee je de in te voeren tokens genereert.
+ description_html: Na het instellen van tweestapsverificatie, kun jij je alleen aanmelden als je jouw mobiele telefoon bij je hebt. Hiermee genereer je namelijk de in te voeren aanmeldcode.
disable: Uitschakelen
enable: Inschakelen
- instructions_html: "Scan deze QR-code in Google Authenticator of een soortgelijke app op je mobiele telefoon. Van nu af aan creëert deze app tokens die je bij aanmelden moet invoeren."
+ instructions_html: "Scan deze QR-code in Google Authenticator of een soortgelijke app op jouw mobiele telefoon. Van nu af aan genereert deze app aanmeldcodes die je bij het aanmelden moet invoeren."
users:
- invalid_email: Het e-mailadres is ongeldig
- invalid_otp_token: Ongeldige twee-factorcode
+ invalid_email: E-mailadres is ongeldig
+ invalid_otp_token: Ongeldige tweestaps-aanmeldcode
+ errors:
+ 404: De pagina waarnaar jij op zoek bent bestaat niet.
+ 410: De pagina waarnaar jij op zoek bent bestaat niet meer.
+ 422:
+ title: Veiligheidsverificatie mislukt
+ content: Veiligheidsverificatie mislukt. Blokkeer je toevallig cookies?
+ admin.reports:
+ title: Gerapporteerde toots
+ status: Toot
+ unresolved: Onopgelost
+ resolved: Opgelost
+ id: ID
+ target: Target
+ reported_by: Gerapporteerd door
+ comment:
+ label: Opmerking
+ none: Geen
+ view: Weergeven
+ report: 'Gerapporteerde toot #%{id}'
+ delete: Verwijderen
+ reported_account: Gerapporteerde account
+ reported_by: Gerapporteerd door
+ silence_account: Account stilzwijgen
+ suspend_account: Account blokkeren
+ mark_as_resolved: Markeer als opgelost
+ admin:
+ settings:
+ title: Server-instellingen
+ setting: Instelling
+ click_to_edit: Klik om te bewerken
+ contact_information:
+ label: Contactgegevens
+ username: Vul een gebruikersnaam in
+ email: Vul een openbaar gebruikt e-mailadres in
+ site_title: Naam Mastodon-server
+ site_description:
+ title: Omschrijving Mastodon-server
+ desc_html: "Dit wordt als een alinea op de voorpagina getoond en gebruikt als meta-tag in de paginabron. Je kan HTML gebruiken, zoals <a> en <em>."
+ site_description_extended:
+ title: Uitgebreide omschrijving Mastodon-server
+ desc_html: "Wordt op de uitgebreide informatiepagina weergegeven Je kan ook hier HTML gebruiken"
+ registrations:
+ open:
+ title: Open registratie
+ enabled: Ingeschakeld
+ disabled: Uitgeschakeld
+ closed_message:
+ title: Bericht wanneer registratie is uitgeschakeld
+ desc_html: "Wordt op de voorpagina weergegeven wanneer registratie van nieuwe accounts is uitgeschakeld En ook hier kan je HTML gebruiken"
diff --git a/config/locales/oc.yml b/config/locales/oc.yml
new file mode 100644
index 0000000000..46e32d8d32
--- /dev/null
+++ b/config/locales/oc.yml
@@ -0,0 +1,337 @@
+---
+oc:
+ about:
+ about_mastodon: Mastodon es un malhum social liure e open-source. Una
+ alternativa decentralizada a las platformas comercialas, aquò evita
+ qu’una sola companiá monopolize vòstra comunicacion. Causissètz une servidor
+ que vos fisatz, quina que siague vòstra causida, podètz interagir amb tot lo
+ mond. Qual que siague pòt aver son instància Mastodon e participar al malhum
+ social sens cap de problèmas.
+ about_this: A prepaus d’aquesta instància
+ apps: Aplicacions
+ business_email: 'Corrièl professional :'
+ closed_registrations: Las inscripcions son clavadas pel moment sus aquesta instància.
+ contact: Contacte
+ description_headline: Qué es %{domain} ?
+ domain_count_after: autras instàncias
+ domain_count_before: Connectat a
+ features:
+ api: API dobèrta per las aplicacions e servicis
+ blocks: Aisinas complètas per blocar e rescondre
+ characters: 500 caractèrs per publicacion
+ chronology: Flux d’actualitat cronologic
+ ethics: 'Ethical design: pas cap de reclama o traçadors'
+ gifv: Partatge de GIFs e vidèos cortas
+ privacy: Nivèl de confidencialitat configurable per cada publicacion
+ public: Fluxes d’actualitat publicsPublic timelines
+ features_headline: Çò que fa que Mastodon es diferent
+ get_started: Venètz al malhum
+ links: Ligams
+ other_instances: Autras instàncias
+ source_code: Còdi font
+ status_count_after: publicacions
+ status_count_before: a escrich
+ terms: Tèrmes
+ user_count_after: utilizaires
+ user_count_before: Ostal de
+ accounts:
+ follow: Sègre
+ followers: Abonats
+ following: Abonaments
+ nothing_here: I a pas res aquí !
+ people_followed_by: Lo mond que %{name} sèc
+ people_who_follow: Lo mond que ségon %{name}
+ posts: Estatuts
+ remote_follow: Sègre a distància
+ unfollow: Quitar de sègre
+ admin:
+ accounts:
+ are_you_sure: Are you sure?
+ display_name: Display name
+ domain: Domain
+ edit: Edit
+ email: E-mail
+ feed_url: Feed URL
+ followers: Followers
+ follows: Follows
+ location:
+ all: All
+ local: Local
+ remote: Remote
+ title: Location
+ media_attachments: Media attachments
+ moderation:
+ all: All
+ silenced: Silenced
+ suspended: Suspended
+ title: Moderation
+ most_recent_activity: Most recent activity
+ most_recent_ip: Most recent IP
+ not_subscribed: Not subscribed
+ order:
+ alphabetic: Alphabetic
+ most_recent: Most recent
+ title: Order
+ perform_full_suspension: Perform full suspension
+ profile_url: Profile URL
+ public: Public
+ push_subscription_expires: PuSH subscription expires
+ reset_password: Reset password
+ salmon_url: Salmon URL
+ silence: Silence
+ statuses: Statuses
+ title: Accounts
+ undo_silenced: Undo silence
+ undo_suspension: Undo suspension
+ username: Username
+ web: Web
+ domain_blocks:
+ add_new: Add new
+ created_msg: Domain block is now being processed
+ destroyed_msg: Domain block has been undone
+ domain: Domain
+ new:
+ create: Create block
+ hint: The domain block will not prevent creation of account entries in the
+ database, but will retroactively and automatically apply specific moderation
+ methods on those accounts.
+ severity:
+ desc_html: "Silence will make the account's posts invisible
+ to anyone who isn't following them. Suspend will remove
+ all of the account's content, media, and profile data."
+ silence: Silence
+ suspend: Suspend
+ title: New domain block
+ reject_media: Reject media files
+ reject_media_hint: Removes locally stored media files and refuses to download
+ any in the future. Irrelevant for suspensions
+ severities:
+ silence: Silence
+ suspend: Suspend
+ severity: Severity
+ show:
+ affected_accounts:
+ one: One account in the database affected
+ other: "%{count} accounts in the database affected"
+ retroactive:
+ silence: Unsilence all existing accounts from this domain
+ suspend: Unsuspend all existing accounts from this domain
+ title: Undo domain block for %{domain}
+ undo: Undo
+ title: Domain Blocks
+ undo: Undo
+ pubsubhubbub:
+ callback_url: Callback URL
+ confirmed: Confirmed
+ expires_in: Expires in
+ last_delivery: Last delivery
+ title: PubSubHubbub
+ topic: Topic
+ reports:
+ comment:
+ label: Comentari
+ none: Pas cap
+ delete: Suprimir
+ id: ID
+ mark_as_resolved: Marcat coma resolgut
+ report: 'enhalament #%{id}'
+ reported_account: Compte senhalat
+ reported_by: Senhalat per
+ resolved: Resolgut
+ silence_account: Metre lo compte en silenci
+ status: Estatut
+ suspend_account: Suspendre lo compte
+ target: Cibla
+ title: Senhalament
+ unresolved: Pas resolguts
+ view: Veire
+ settings:
+ click_to_edit: Clicatz per modificar
+ contact_information:
+ email: Picatz una adreça de corrièl
+ label: Informacions de contacte
+ username: Picatz un nom d'utilizaire
+ registrations:
+ closed_message:
+ desc_html: Affiché sur la page d'accueil lorsque les inscriptions sont fermées Vous
+ pouvez utiliser des balises HTML
+ title: Message de fermeture des inscriptions
+ open:
+ disabled: Desactivadas
+ enabled: Activadas
+ title: Inscripcions
+ setting: Paramètre
+ site_description:
+ desc_html: Affichée sous la forme d'un paragraphe sur la page d'accueil et
+ utilisée comme balise meta. Vous pouvez utiliser des balises HTML, en
+ particulier <a> et <em>.
+ title: Descripcion del site
+ site_description_extended:
+ desc_html: Affichée sur la page d'informations complémentaires du site Vous
+ pouvez utiliser des balises HTML
+ title: Description étendue du site
+ site_title: Títol del site
+ title: Paramètres del site
+ title: Administration
+ application_mailer:
+ settings: 'Cambiar las preferéncias de corrièl : %{link}'
+ signature: Notificacion de Mastodon de %{instance}
+ view: 'Veire :'
+ applications:
+ invalid_url: L’URL donada es invalida
+ auth:
+ change_password: Cambiar lo senhal
+ didnt_get_confirmation: Avètz pas recebut las instruccions de confirmacion ?
+ forgot_password: Senhal oblidat ?
+ login: Se connectar
+ logout: Se desconnectar
+ register: Se marcar
+ resend_confirmation: Tornar mandar las instruccions de confirmacion
+ reset_password: Reïnicializar lo senhal
+ set_new_password: Picar un nòu senhal
+ authorize_follow:
+ error: O planhèm, i a agut una error al moment de cercar lo compte
+ follow: Sègre
+ prompt_html: 'Avètz (%{self}) demandat de sègre :'
+ title: Sègre %{acct}
+ datetime:
+ distance_in_words:
+ about_x_hours: "%{count}oras"
+ about_x_months: "%{count}meses"
+ about_x_years:
+ one: un an
+ other: "%{count} ans"
+ almost_x_years:
+ one: un an
+ other: "%{count} ans"
+ half_a_minute: Ara
+ less_than_x_minutes: "%{count}minutas"
+ less_than_x_seconds: Ara
+ over_x_years:
+ one: un an
+ other: "%{count} ans"
+ x_days: "%{count}jorns"
+ x_minutes: "%{count}minutes"
+ x_months: "%{count}meses"
+ x_seconds: "%{count}segondas"
+ errors:
+ '404': La pagina que recercatz existís pas.
+ '410': La pagina que cercatz existís pas mai.
+ '422':
+ content: Verificacion de seguretat fracassada. Blocatz los cookies ?
+ title: Verificacion de seguretat fracassada
+ exports:
+ blocks: Blocatz
+ csv: CSV
+ follows: Seguètz
+ mutes: You mute
+ storage: Mèdias gardats
+ generic:
+ changes_saved_msg: Cambiaments ben realizats !
+ powered_by: propulsat per %{link}
+ save_changes: Salvagardar los cambiaments
+ validation_errors:
+ one: I a quicòm que truca ! Mercés de corregir l’error çai-jos
+ other: I a quicòm que truca ! Mercés de corregir las %{count} errors çai-jos
+ imports:
+ preface: Podètz importar qualques donadas coma lo mond que seguètz o blocatz a-n
+ aquesta instància d’un fichièr creat d’una autra instància.
+ success: Vòstras donadas son ben estadas mandadas e seràn tractadas tre que possible
+ types:
+ blocking: Lista de blocatge
+ following: Lista de mond que seguètz
+ muting: Muting list
+ upload: Importar
+ landing_strip_html: %{name} es un utilizaire de %{domain}.
+ Podètz lo sègre o interagir amb eles s’avètz un compte ont que siasgue sul fediverse.
+ Autrament podètz vos marcar aquí.
+ media_attachments:
+ validations:
+ images_and_video: Cannot attach a video to a status that already contains images
+ too_many: Cannot attach more than 4 files
+ notification_mailer:
+ digest:
+ body: 'Trobatz aquí un resumit de çò qu’avètz mancat dempuèi vòstra darrièra
+ visita lo %{since}:'
+ mention: "%{name} vos amencionat dins :"
+ new_followers_summary:
+ one: Avètz un nòu abonat ! Ouà !
+ other: Avètz %{count} nòus abonats ! Qué crane !
+ subject:
+ one: "Una nòva notificacion dempuèi vòstra darrièra visita \U0001F418"
+ other: "%{count} nòvas notificacions dempuèi vòstra darrièra visita \U0001F418"
+ favourite:
+ body: "%{name} a mes vòstre estatut en favorit :"
+ subject: "%{name} a mes vòstre estatut en favorit"
+ follow:
+ body: "%{name} vos sèc ara !"
+ subject: "%{name} es a vos sègre ara"
+ follow_request:
+ body: "%{name} a demandar a vos sègre"
+ subject: 'Demanda d’abonament : %{name}'
+ mention:
+ body: 'Sètz estat mencionat per %{name} dins :'
+ subject: Sètz estat mencionat per %{name}
+ reblog:
+ body: "%{name} a tornat partejar vòstre estatut :"
+ subject: "%{name} a tornat partejar vòstre estatut"
+ pagination:
+ next: Seguent
+ prev: Precedent
+ truncate: "…"
+ remote_follow:
+ acct: Picatz vòstre utilizaire@instància per utilizar per sègre aqueste utilizaire
+ missing_resource: URL de redireccion pas trobada
+ proceed: Contunhatz per sègre
+ prompt: 'Sètz per sègre :'
+ settings:
+ authorized_apps: Aplicacions autorizadas
+ back: Tornar a Mastodon
+ edit_profile: Modificar lo perfil
+ export: Export donadas
+ import: Import
+ preferences: Preferéncias
+ settings: Paramètres
+ two_factor_auth: Autentificacion en dos temps
+ statuses:
+ open_in_web: Dobrir sul web
+ over_character_limit: limit de %{max} caractèrs passat
+ show_more: Ne veire mai
+ visibilities:
+ private: Mostrar pas qu’als abonats
+ public: Public
+ unlisted: Public, mai pas afichat sul flux d’actualitat public
+ stream_entries:
+ click_to_show: Clic per afichar
+ reblogged: partejat
+ sensitive_content: Contengut sensible
+ time:
+ formats:
+ default: "%b %d %Y a %H o %M"
+ two_factor_auth:
+ code_hint: Enter the code generated by your authenticator app to confirm
+ description_html: S’activatz l’autentificacion two-factor, vos
+ caldrà vòstre mobil per vos connectar perque generarà un geton per vos daissar
+ dintrar.
+ disable: Desactivar
+ enable: Activar
+ enabled_success: Two-factor authentication successfully enabled
+ generate_recovery_codes: Generate Recovery Codes
+ instructions_html: "Escanatz aqueste còdi QR amb Google Authenticator
+ o una aplicacion similària sus vòstre mobil. A partir d’ara, aquesta
+ aplicacion generarà un geton que vos caldrà picar per vos connectar."
+ lost_recovery_codes: Recovery codes allow you to regain access to your account
+ if you lose your phone. If you've lost your recovery codes, you can regenerate
+ them here. Your old recovery codes will be invalidated.
+ manual_instructions: 'If you can''t scan the QR code and need to enter it manually,
+ here is the plain-text secret:'
+ recovery_codes_regenerated: Recovery codes successfully regenerated
+ recovery_instructions: If you ever lose access to your phone, you can use one
+ of the recovery codes below to regain access to your account. Keep the recovery
+ codes safe, for example by printing them and storing them with other important
+ documents.
+ setup: Set up
+ wrong_code: The entered code was invalid! Are server time and device time correct?
+ users:
+ invalid_email: L’adreça de corrièl es invalida
+ invalid_otp_token: Còdi d’autentificacion en dos temps invalid
diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml
new file mode 100644
index 0000000000..269a1a99be
--- /dev/null
+++ b/config/locales/pt-BR.yml
@@ -0,0 +1,198 @@
+---
+pt-BR:
+ about:
+ about_mastodon: Mastodon é um servidor de rede social grátis, e open-source. Uma alternativa descentralizada ás plataformas comerciais, que evita o risco de uma única empresa monopolizar a sua comunicação. Escolha um servidor que você confie — qualquer um que escolher, você poderá interagir com todo o resto. Qualquer um pode ter uma instância Mastodon e assim participar na rede social federada sem problemas.
+ about_this: Sobre essa instância
+ apps: Aplicações
+ business_email: 'Email comercial:'
+ closed_registrations: Registros estão fechadas para essa instância.
+ contact: Contato
+ description_headline: O que é %{domain}?
+ domain_count_after: outras instâncias
+ domain_count_before: Conectado a
+ features:
+ api: Aberto para API de aplicações e serviços
+ blocks: Bloqueos e ferramentas para mudar
+ characters: 500 caracteres por post
+ chronology: Timeline são cronologicas
+ ethics: 'Design ético: sem propaganda, sem tracking'
+ gifv: GIFV e vídeos curtos
+ privacy: Granular, privacidade setada por post
+ public: Timelines públicas
+ features_headline: O que torna Mastodon diferente
+ get_started: Comece aqui
+ links: Links
+ source_code: Source code
+ other_instances: Outras instâncias
+ terms: Termos
+ user_count_after: usuários
+ user_count_before: Lugar de
+ accounts:
+ follow: Seguir
+ followers: Seguidores
+ following: Seguindo
+ nothing_here: Não há nada aqui!
+ people_followed_by: Pessoas seguidas por %{name}
+ people_who_follow: Pessoas que seguem %{name}
+ posts: Posts
+ remote_follow: Acesso remoto
+ unfollow: Unfollow
+ admin:
+ accounts:
+ are_you_sure: Você tem certeza?
+ display_name: Nome mostrado
+ domain: Domain
+ edit: Editar
+ email: E-mail
+ feed_url: URL do Feed
+ followers: Seguidores
+ follows: Seguindo
+ location:
+ all: Todos
+ local: Local
+ remote: Remoto
+ title: Local
+ media_attachments: Mídia anexadas
+ moderation:
+ all: Todos
+ silenced: Silenciado
+ suspended: Supenso
+ title: Moderação
+ most_recent_activity: Atividade mais recente
+ most_recent_ip: IP mais recente
+ not_subscribed: Não inscrito
+ order:
+ alphabetic: Alfabética
+ most_recent: Mais recente
+ title: Ordem
+ perform_full_suspension: Fazer suspensão completa
+ profile_url: URL do perfil
+ public: Público
+ push_subscription_expires: PuSH subscription expires
+ salmon_url: Salmon URL
+ silence: Silêncio
+ statuses: Status
+ title: Contas
+ undo_silenced: Desfazer silenciar
+ undo_suspension: Desfazer supensão
+ username: Usuário
+ web: Web
+ domain_blocks:
+ add_new: Adicionar nova
+ created_msg: Bloqueio do domínio está sendo processado
+ destroyed_msg: Bloqueio de domínio está sendo desfeito
+ domain: Domínio
+ new:
+ create: Criar bloqueio
+ hint: O bloqueio de dominio não vai previnir a criação de entradas no banco de dados, mas irá, retroativamente e automaticamente aplicar métodos de moderação específica nessas contas.
+ severity:
+ desc_html: "Silenciar irá fazer com que os posts dessas contas sejam invisíveis para todos que não a seguem. Supender irá remover todos o conteúdo das contas, mídia e dados do perfil."
+ silence: Silenciar
+ suspend: Suspender
+ title: Novo bloqueio de domínio
+ reject_media: Rejeitar arquivos de mídia
+ reject_media_hint: Remove localmente arquivos armazenados e rejeita fazer o download de novos no futuro. Irrelevante em suspensões.
+ severities:
+ silence: Silenciar
+ suspend: Suspender
+ severity: Severidade
+ show:
+ affected_accounts:
+ one: Uma conta no banco de dados afetada
+ other: "%{count} contas no banco de dados afetada"
+ retroactive:
+ silence: Desilenciar todas as contas existentes nesse domínio
+ suspend: Desuspender todas as contas existentes nesse domínio
+ title: Desfazer bloqueio de domínio para %{domain}
+ title: Bloqueio de domínio
+ undo: Desfazer
+ pubsubhubbub:
+ callback_url: URL de Callback
+ confirmed: Confirmado
+ expires_in: Expira em
+ last_delivery: Última entrega
+ title: PubSubHubbub
+ topic: Tópico
+ reports:
+ comment:
+ label: Commentário
+ none: None
+ delete: Deletar
+ id: ID
+ mark_as_resolved: Marque como resolvido
+ report: 'Report #%{id}'
+ reported_account: Conta reportada
+ reported_by: Reportado por
+ resolved: Resolvido
+ silence_account: Conta silenciada
+ status: Status
+ suspend_account: Conta suspensa
+ target: Target
+ title: Reports
+ unresolved: Unresolved
+ view: View
+ settings:
+ click_to_edit: Clique para editar
+ contact_information:
+ email: Entre um endereço de email público
+ label: Informação de contato
+ username: Entre com usuário
+ registrations:
+ closed_message:
+ desc_html: Mostrar na página inicial quando registros estão fecados Você pode usar tags HTML
+ title: Mensagem de registro fechados
+ open:
+ disabled: Desabilitado
+ enabled: Habilitado
+ title: Aberto para registro
+ setting: Preferências
+ site_description:
+ desc_html: Mostrar como parágrafo e usado como meta tag. Vôce pode usar tags HTML, em particular <a> e <em>.
+ title: Descrição do site
+ site_description_extended:
+ desc_html: Mostrar na página de informação extendiada Você pode usar tags HTML
+ title: Descrição extendida do site
+ site_title: Título do site
+ title: Preferências do site
+ title: Administração
+ application_mailer:
+ settings: 'Mudar preferências de email: %{link}'
+ signature: notificações Mastodon de %{instance}
+ view: 'View:'
+ applications:
+ invalid_url: URL dada é inválida
+ auth:
+ change_password: Mudar senha
+ didnt_get_confirmation: Não recebeu instruções de confirmação?
+ forgot_password: Esqueceu a senha?
+ login: Entrar
+ register: Registar
+ resend_confirmation: Reenviar instruções de confirmação
+ reset_password: Resetar senha
+ set_new_password: Editar password
+ generic:
+ changes_saved_msg: Mudanças guardadas!
+ powered_by: powered by %{link}
+ save_changes: Guardar alterações
+ validation_errors:
+ one: Algo não está correto. Por favor reveja o erro abaixo
+ other: Algo não está correto. Por favor reveja os %{count} erros abaixo
+ notification_mailer:
+ favourite:
+ body: 'O seu post foi favoritado por %{name}:'
+ subject: "%{name} favouritou o seu post"
+ follow:
+ body: "%{name} seguiu você!"
+ subject: "%{name} segue você"
+ mention:
+ body: 'Você foi mencionado por %{name} em:'
+ subject: Foi mencionado por %{name}
+ reblog:
+ body: 'O seu post foi reblogado por %{name}:'
+ subject: "%{name} reblogou o seu post"
+ pagination:
+ next: Next
+ prev: Prev
+ settings:
+ edit_profile: Editar perfil
+ preferences: Preferências
diff --git a/config/locales/pt.yml b/config/locales/pt.yml
index dc9beae7bc..735bc14ba5 100644
--- a/config/locales/pt.yml
+++ b/config/locales/pt.yml
@@ -1,64 +1,66 @@
---
pt:
about:
- about_mastodon: Mastodon é um servidor de rede social grátis, e open-source. Uma alternativa descentralizada ás plataformas comerciais, que evita o risco de uma única empresa monopolizar a sua comunicação. Escolha um servidor que você confie — qualquer um que escolher, você poderá interagir com todo o resto. Qualquer um pode ter uma instância Mastodon e assim participar na rede social federada sem problemas.
- about_this: Sobre essa instância
+ about_mastodon: Mastodon é uma rede social grátis e em código aberto. Uma alternativa descentralizada às plataformas comerciais, que evita o risco de uma única empresa monopolizar a tua comunicação. Escolhe um servidor que confies, não importa qual, pois vais poder comunicar com todos os outros. Qualquer um pode criar uma instância Mastodon e participar nesta rede social.
+ about_this: Sobre esta instância
apps: Aplicações
business_email: 'Email comercial:'
- closed_registrations: Registros estão fechadas para essa instância.
- contact: Contato
- description_headline: O que é %{domain}?
+ closed_registrations: Novos registos estão fechados nesta instância.
+ contact: Contacto
+ description_headline: O que é o %{domain}?
domain_count_after: outras instâncias
- domain_count_before: Conectado a
+ domain_count_before: Ligado a
features:
- api: Aberto para API de aplicações e serviços
- blocks: Bloqueos e ferramentas para mudar
+ api: API aberta para aplicações e serviços
+ blocks: Ferramentas para silenciar e bloquear
characters: 500 caracteres por post
- chronology: Timeline são cronologicas
- ethics: 'Design ético: sem propaganda, sem tracking'
- gifv: GIFV e vídeos curtos
- privacy: Granular, privacidade setada por post
+ chronology: Timelines cronológicas
+ ethics: 'Design ético: sem públicidade ou tracking'
+ gifv: GIFV e pequenos vídeos
+ privacy: Privacidade granular por post
public: Timelines públicas
features_headline: O que torna Mastodon diferente
- get_started: Comece aqui
+ get_started: Começar
links: Links
- source_code: Source code
other_instances: Outras instâncias
+ source_code: Código fonte
+ status_count_after: publicações
+ status_count_before: Que fizeram
terms: Termos
- user_count_after: usuários
- user_count_before: Lugar de
+ user_count_after: utilizadores
+ user_count_before: Casa para
accounts:
follow: Seguir
followers: Seguidores
- following: Seguindo
+ following: A seguir
nothing_here: Não há nada aqui!
people_followed_by: Pessoas seguidas por %{name}
people_who_follow: Pessoas que seguem %{name}
posts: Posts
- remote_follow: Acesso remoto
- unfollow: Unfollow
+ remote_follow: Seguir remotamente
+ unfollow: Deixar de seguir
admin:
accounts:
- are_you_sure: Você tem certeza?
- display_name: Nome mostrado
- domain: Domain
+ are_you_sure: Tens a certeza?
+ display_name: Nome a mostrar
+ domain: Domínio
edit: Editar
email: E-mail
feed_url: URL do Feed
followers: Seguidores
- follows: Seguindo
+ follows: A seguir
location:
all: Todos
local: Local
remote: Remoto
title: Local
- media_attachments: Mídia anexadas
+ media_attachments: Media anexa
moderation:
all: Todos
- silenced: Silenciado
- suspended: Supenso
+ silenced: Silenciados
+ suspended: Supensos
title: Moderação
- most_recent_activity: Atividade mais recente
+ most_recent_activity: Actividade mais recente
most_recent_ip: IP mais recente
not_subscribed: Não inscrito
order:
@@ -69,6 +71,7 @@ pt:
profile_url: URL do perfil
public: Público
push_subscription_expires: PuSH subscription expires
+ reset_password: Reset palavra-passe
salmon_url: Salmon URL
silence: Silêncio
statuses: Status
@@ -78,34 +81,35 @@ pt:
username: Usuário
web: Web
domain_blocks:
- add_new: Adicionar nova
- created_msg: Bloqueio do domínio está sendo processado
- destroyed_msg: Bloqueio de domínio está sendo desfeito
+ add_new: Adicionar novo
+ created_msg: Bloqueio do domínio está a ser processado
+ destroyed_msg: Bloqueio de domínio está a ser removido
domain: Domínio
new:
create: Criar bloqueio
- hint: O bloqueio de dominio não vai previnir a criação de entradas no banco de dados, mas irá, retroativamente e automaticamente aplicar métodos de moderação específica nessas contas.
+ hint: O bloqueio de dominio não vai previnir a criação de entradas na base de dados, mas irá retroativamente e automaticamente aplicar métodos de moderação específica nessas contas.
severity:
- desc_html: "Silenciar irá fazer com que os posts dessas contas sejam invisíveis para todos que não a seguem. Supender irá remover todos o conteúdo das contas, mídia e dados do perfil."
+ desc_html: "Silenciar irá fazer com que os posts dessas contas sejam invisíveis para todos que não a seguem. Supender irá eliminar todo o conteúdo guardado dessa conta, mídia e informação de perfil."
silence: Silenciar
suspend: Suspender
title: Novo bloqueio de domínio
- reject_media: Rejeitar arquivos de mídia
- reject_media_hint: Remove localmente arquivos armazenados e rejeita fazer o download de novos no futuro. Irrelevante em suspensões.
+ reject_media: Rejeitar ficheiros de mídia
+ reject_media_hint: Remove localmente arquivos armazenados e rejeita fazer guardar novos no futuro. Irrelevante na suspensão.
severities:
silence: Silenciar
suspend: Suspender
severity: Severidade
show:
affected_accounts:
- one: Uma conta no banco de dados afetada
- other: "%{count} contas no banco de dados afetada"
+ one: Uma conta na base de dados afectada
+ other: "%{count} contas na base de dados afectadas"
retroactive:
- silence: Desilenciar todas as contas existentes nesse domínio
- suspend: Desuspender todas as contas existentes nesse domínio
- title: Desfazer bloqueio de domínio para %{domain}
+ silence: Não silenciar todas as contas existentes nesse domínio
+ suspend: Não suspender todas as contas existentes nesse domínio
+ title: Remover o bloqueio de domínio de %{domain}
+ undo: Anular
title: Bloqueio de domínio
- undo: Desfazer
+ undo: Anular
pubsubhubbub:
callback_url: URL de Callback
confirmed: Confirmado
@@ -115,84 +119,84 @@ pt:
topic: Tópico
reports:
comment:
- label: Commentário
- none: None
- delete: Deletar
+ label: Comentário
+ none: Nenhum
+ delete: Eliminar
id: ID
- mark_as_resolved: Marque como resolvido
- report: 'Report #%{id}'
- reported_account: Conta reportada
- reported_by: Reportado por
+ mark_as_resolved: Marcar como resolvido
+ report: 'Denúncia #%{id}'
+ reported_account: Conta denunciada
+ reported_by: Denúnciada por
resolved: Resolvido
silence_account: Conta silenciada
- status: Status
+ status: Estado
suspend_account: Conta suspensa
target: Target
- title: Reports
- unresolved: Unresolved
- view: View
+ title: Denúncias
+ unresolved: Por resolver
+ view: Ver
settings:
click_to_edit: Clique para editar
contact_information:
- email: Entre um endereço de email público
- label: Informação de contato
- username: Entre com usuário
+ email: Inserir um endereço de email para tornar público
+ label: Informação de contacto
+ username: Insira um nome de utilizador
registrations:
closed_message:
- desc_html: Mostrar na página inicial quando registros estão fecados Você pode usar tags HTML
- title: Mensagem de registro fechados
+ desc_html: Mostrar na página inicial quando registos estão encerrados Podes usar tags HTML
+ title: Mensagem de registos encerrados
open:
disabled: Desabilitado
enabled: Habilitado
- title: Aberto para registro
+ title: Aceitar novos registos
setting: Preferências
site_description:
- desc_html: Mostrar como parágrafo e usado como meta tag. Vôce pode usar tags HTML, em particular <a> e <em>.
+ desc_html: Mostrar como parágrafo na página inicial e usado como meta tag. Podes usar tags HTML, em particular <a> e <em>.
title: Descrição do site
site_description_extended:
- desc_html: Mostrar na página de informação extendiada Você pode usar tags HTML
- title: Descrição extendida do site
+ desc_html: Mostrar na página de mais informações Podes usar tags HTML
+ title: Página de mais informações
site_title: Título do site
title: Preferências do site
title: Administração
application_mailer:
- settings: 'Mudar preferências de email: %{link}'
- signature: notificações Mastodon de %{instance}
- view: 'View:'
+ settings: 'Alterar preferências de email: %{link}'
+ signature: notificações Mastodon do %{instance}
+ view: 'Ver:'
applications:
- invalid_url: URL dada é inválida
+ invalid_url: O URL é inválido
auth:
- change_password: Mudar senha
- didnt_get_confirmation: Não recebeu instruções de confirmação?
- forgot_password: Esqueceu a senha?
+ change_password: Alterar palavra-passe
+ didnt_get_confirmation: Não recebeu o email de confirmação?
+ forgot_password: Esqueceste a palavra-passe?
login: Entrar
register: Registar
resend_confirmation: Reenviar instruções de confirmação
- reset_password: Resetar senha
- set_new_password: Editar password
+ reset_password: Criar nova palavra-passe
+ set_new_password: Editar palavra-passe
generic:
- changes_saved_msg: Mudanças guardadas!
+ changes_saved_msg: Alteraçes guardadas!
powered_by: powered by %{link}
save_changes: Guardar alterações
validation_errors:
- one: Algo não está correto. Por favor reveja o erro abaixo
- other: Algo não está correto. Por favor reveja os %{count} erros abaixo
+ one: Algo não está correcto. Por favor vê o erro abaixo
+ other: Algo não está correto. Por favor vê os %{count} erros abaixo
notification_mailer:
favourite:
- body: 'O seu post foi favoritado por %{name}:'
- subject: "%{name} favouritou o seu post"
+ body: 'O teu post foi adicionado aos favoritos por %{name}:'
+ subject: "%{name} adicionou o teu post aos favoritos"
follow:
- body: "%{name} seguiu você!"
- subject: "%{name} segue você"
+ body: "%{name} é teu seguidor!"
+ subject: "%{name} começou a seguir-te"
mention:
- body: 'Você foi mencionado por %{name} em:'
- subject: Foi mencionado por %{name}
+ body: 'Foste mencionado por %{name}:'
+ subject: "%{name} mencionou-te"
reblog:
- body: 'O seu post foi reblogado por %{name}:'
- subject: "%{name} reblogou o seu post"
+ body: 'O teu post foi partilhado por %{name}:'
+ subject: "%{name} partilhou o teu post"
pagination:
- next: Next
- prev: Prev
+ next: Seguinte
+ prev: Anterior
settings:
edit_profile: Editar perfil
preferences: Preferências
diff --git a/config/locales/ru.yml b/config/locales/ru.yml
index 8e6a813bbe..3d1869ebdb 100644
--- a/config/locales/ru.yml
+++ b/config/locales/ru.yml
@@ -39,6 +39,126 @@ ru:
posts: Посты
remote_follow: Подписаться на удаленном узле
unfollow: Отписаться
+ admin:
+ accounts:
+ are_you_sure: Вы уверены?
+ display_name: Отображаемое имя
+ domain: Домен
+ edit: Изменить
+ email: E-mail
+ feed_url: URL фида
+ followers: Подписчики
+ follows: Подписки
+ location:
+ all: Все
+ local: Локальные
+ remote: Удаленные
+ title: Размещение
+ media_attachments: Мультимедийные вложения
+ moderation:
+ all: Все
+ silenced: Заглушенные
+ suspended: Заблокированные
+ title: Модерация
+ most_recent_activity: Последняя активность
+ most_recent_ip: Последний IP
+ not_subscribed: Не подписаны
+ order:
+ alphabetic: По алфавиту
+ most_recent: По дате
+ title: Порядок
+ perform_full_suspension: Полная блокировка
+ profile_url: URL профиля
+ public: Публичный
+ push_subscription_expires: Подписка PuSH истекает
+ reset_password: Сбросить пароль
+ salmon_url: Salmon URL
+ silence: Глушение
+ statuses: Статусы
+ title: Аккаунты
+ undo_silenced: Снять глушение
+ undo_suspension: Снять блокировку
+ username: Имя пользователя
+ web: WWW
+ domain_blocks:
+ add_new: Добавить новую
+ created_msg: Блокировка домена обрабатывается
+ destroyed_msg: Блокировка домена снята
+ domain: Домен
+ new:
+ create: Создать блокировку
+ hint: Блокировка домена не предотвратит создание новых аккаунтов в базе данных, но ретроактивно и автоматически применит указанные методы модерации для этих аккаунтов.
+ severity:
+ desc_html: "Глушение сделает статусы аккаунта невидимыми для всех, кроме их подписчиков. Блокировка удалит весь контент аккаунта, включая мультимедийные вложения и данные профиля."
+ silence: Глушение
+ suspend: Блокировка
+ title: Новая доменная блокировка
+ reject_media: Запретить медиаконтент
+ reject_media_hint: Удаляет локально хранимый медиаконтент и запрещает его загрузку в будущем. Не имеет значения в случае блокировки.
+ severities:
+ silence: Глушение
+ suspend: Блокировка
+ severity: Строгость
+ show:
+ affected_accounts:
+ one: Влияет на один аккаунт в базе данных
+ other: "Влияет на %{count} аккаунтов в базе данных"
+ retroactive:
+ silence: Снять глушение со всех существующих аккаунтов этого домена
+ suspend: Снять блокировку со всех существующих аккаунтов этого домена
+ title: Снять блокировку с домена %{domain}
+ undo: Отменить
+ title: Доменные блокировки
+ undo: Отемнить
+ pubsubhubbub:
+ callback_url: Callback URL
+ confirmed: Подтверждено
+ expires_in: Истекает через
+ last_delivery: Последняя доставка
+ title: PubSubHubbub
+ topic: Тема
+ reports:
+ comment:
+ label: Комментарий
+ none: Нет
+ delete: Удалить
+ id: ID
+ mark_as_resolved: Отметить как разрешенную
+ report: 'Жалоба #%{id}'
+ reported_account: Аккаунт нарушителя
+ reported_by: Отправитель жалобы
+ resolved: Разрешено
+ silence_account: Заглушить аккаунт
+ status: Статус
+ suspend_account: Блокировать аккаунт
+ target: Цель
+ title: Жалобы
+ unresolved: Неразрешенные
+ view: Просмотреть
+ settings:
+ click_to_edit: Нажмите для изменения
+ contact_information:
+ email: Введите публичный e-mail
+ label: Контактная информация
+ username: Введите имя пользователя
+ registrations:
+ closed_message:
+ desc_html: Отображается на титульной странице, когда закрыта регистрация Можно использовать HTML-теги
+ title: Сообщение о закрытой регистрации
+ open:
+ disabled: Закрыта
+ enabled: Открыта
+ title: Открыть регистрацию
+ setting: Настройка
+ site_description:
+ desc_html: Отображается в качестве параграфа на титульной странице и используется в качестве мета-тега. Можно использовать HTML-теги, в особенности <a> и <em>.
+ title: Описание сайта
+ site_description_extended:
+ desc_html: Отображается на странице дополнительной информации Можно использовать HTML-теги
+ title: Расширенное описание сайта
+ site_title: Название сайта
+ title: Настройки сайта
+ title: Администрирование
application_mailer:
settings: 'Изменить настройки e-mail: %{link}'
signature: Уведомления Mastodon от %{instance}
@@ -74,10 +194,17 @@ ru:
x_minutes: "%{count}мин"
x_months: "%{count}мес"
x_seconds: "%{count}сек"
+ errors:
+ '404': Страница, которую Вы искали, не существует.
+ '410': Страница, которую Вы искали, больше не существует.
+ '422':
+ content: Проверка безопасности не удалась. Возможно, Вы блокируете cookies?
+ title: Проверка безопасности не удалась.
exports:
- blocks: Вы заблокировали
+ blocks: Список блокировки
csv: CSV
follows: Подписки
+ mutes: Список глушения
storage: Ваш медиаконтент
generic:
changes_saved_msg: Изменения успешно сохранены!
@@ -90,10 +217,15 @@ ru:
preface: Вы можете загрузить некоторые данные, например, списки людей, на которых Вы подписаны или которых блокируете, в Ваш аккаунт на этом узле из файлов, экспортированных с другого узла.
success: Ваши данные были успешно загружены и будут обработаны с должной скоростью
types:
- blocking: Список блокируемых
- following: Список подписок
+ blocking: Список блокировки
+ following: Подписки
+ muting: Список глушения
upload: Загрузить
landing_strip_html: %{name} - пользователь на %{domain}. Вы можете подписаться на него/нее и общаться с ним/ней, если у Вас есть аккаунт на любом узле общей сети. Если у Вас его нет, вы можете зарегистрироваться здесь.
+ media_attachments:
+ validations:
+ images_and_video: Нельзя добавить видео к статусу с изображениями
+ too_many: Нельзя добавить более 4 файлов
notification_mailer:
digest:
body: 'Кратко о пропущенном Вами на %{instance} с Вашего последнего захода %{since}:'
@@ -156,9 +288,15 @@ ru:
description_html: При включении двухфакторной аутентификации, вход потребует от Вас использования Вашего телефона, который сгенерирует входные токены.
disable: Отключить
enable: Включить
+ enabled_success: Двухфакторная аутентификация успешно включена
+ generate_recovery_codes: Сгенерировать коды восстановления
instructions_html: "Отсканируйте этот QR-код с помощью Google Authenticator или другого подобного приложения на Вашем телефоне. С этого момента приложение будет генерировать токены, которые будет необходимо ввести для входа."
+ lost_recovery_codes: Коды восстановления позволяют вернуть доступ к аккаунту в случае утери телефона. Если Вы потеряли Ваши коды восстановления, вы можете заново сгенерировать их здесь. Ваши старые коды восстановления будут аннулированы.
manual_instructions: 'Если Вы не можете отсканировать QR-код и хотите ввести его вручную, секрет представлен здесь открытым текстом:'
+ recovery_codes_regenerated: Коды восстановления успешно сгенерированы
+ recovery_instructions: В случае утери доступа к Вашему телефону Вы можете использовать один из кодов восстановления, указанных ниже, чтобы вернуть доступ к аккаунту. Держите коды восстановления в безопасности, например, распечатав их и храня с другими важными документами.
setup: Настроить
+ wrong_code: Введенный код неверен! Правильно ли установлены серверное время и время устройства?
users:
invalid_email: Введенный e-mail неверен
invalid_otp_token: Введен неверный код
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index c25407f2b5..5335b0927c 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -28,6 +28,7 @@ en:
note: Bio
otp_attempt: Two-factor code
password: Password
+ setting_auto_play_gif: Auto-play animated GIFs
setting_boost_modal: Show confirmation dialog before boosting
setting_default_privacy: Post privacy
severity: Severity
diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml
index b9257ec35f..ee561955e3 100644
--- a/config/locales/simple_form.ja.yml
+++ b/config/locales/simple_form.ja.yml
@@ -28,6 +28,7 @@ ja:
note: プロフィール
otp_attempt: 二段階認証コード
password: パスワード
+ setting_auto_play_gif: アニメーションGIFを自動再生する
setting_boost_modal: ブーストする前に確認ダイアログを表示する
setting_default_privacy: 投稿の公開範囲
severity: 重大性
diff --git a/config/locales/simple_form.nl.yml b/config/locales/simple_form.nl.yml
index 5bc38a87b2..c0539fd636 100644
--- a/config/locales/simple_form.nl.yml
+++ b/config/locales/simple_form.nl.yml
@@ -6,39 +6,39 @@ nl:
avatar: PNG, GIF of JPG. Maximaal 2MB. Wordt teruggeschaald naar 120x120px
display_name: Maximaal 30 tekens
header: PNG, GIF of JPG. Maximaal 2MB. Wordt teruggeschaald naar 700x335px
- locked: Vereist dat je handmatig volgers accepteert en stelt standaard plaatsen berichten privacy in op alleen-volgers
- note: Maximaal 160 characters
+ locked: Vereist dat je handmatig volgers moet accepteren en stelt de privacy van toots standaard in op alleen volgers
+ note: Maximaal 160 tekens
imports:
- data: CSV file geëxporteerd van een andere Mastodon server
+ data: CSV-bestand dat op een andere Mastodon-server werd geëxporteerd
labels:
defaults:
avatar: Avatar
- confirm_new_password: Bevestig nieuw wachtwoord
- confirm_password: Bevestig wachtwoord
- current_password: Huidige wachtwoord
+ confirm_new_password: Nieuw wachtwoord bevestigen
+ confirm_password: Wachtwoord bevestigen
+ current_password: Huidig wachtwoord
data: Gegevens
display_name: Weergavenaam
email: E-mailadres
- header: Kop
+ header: Omslagfoto
locale: Taal
locked: Maak account besloten
new_password: Nieuwe wachtwoord
note: Bio
- otp_attempt: Twee-factor code
+ otp_attempt: Tweestaps-aanmeldcode
password: Wachtwoord
- setting_default_privacy: Berichten privacy
- type: Import type
+ setting_default_privacy: Tootprivacy
+ type: Importtype
username: gebruikersnaam
interactions:
- must_be_follower: Blokkeermeldingen van niet-volgers
- must_be_following: Blokkeer meldingen van mensen die je niet volgt
+ must_be_follower: Blokkeermeldingen van mensen die jou niet volgen
+ must_be_following: Blokkeermeldingen van mensen die jij niet volgt
notification_emails:
- digest: Verstuur samenvattingse-mails
- favourite: Verstuur een e-mail wanneer iemand je status als favoriet markeert
- follow: Verstuur een e-mail wanneer iemand je volgt
- follow_request: Verstuur een e-mail wanneer iemand je wil volgen
- mention: Verstuur een e-mail wanneer iemand je vermeld
- reblog: Verstuur een e-mail wanneer iemand je status boost
+ digest: Verstuur periodiek e-mails met een samenvatting
+ favourite: Verstuur een e-mail wanneer iemand jouw toot als favoriet markeert
+ follow: Verstuur een e-mail wanneer iemand jou volgt
+ follow_request: Verstuur een e-mail wanneer iemand jou wilt volgen
+ mention: Verstuur een e-mail wanneer iemand jou vermeld
+ reblog: Verstuur een e-mail wanneer iemand jouw toot heeft geboost
'no': 'Nee'
required:
mark: "*"
diff --git a/config/locales/simple_form.oc.yml b/config/locales/simple_form.oc.yml
new file mode 100644
index 0000000000..fb2bec1b48
--- /dev/null
+++ b/config/locales/simple_form.oc.yml
@@ -0,0 +1,46 @@
+---
+oc:
+ simple_form:
+ hints:
+ defaults:
+ avatar: PNG, GIF o JPG. Maximum 2 Mo. Serà retalhat en 120x120px
+ display_name: Maximum 30 caractèrs
+ header: PNG, GIF o JPG. Maximum 2 Mo. Serà retalhat en 700x335px
+ locked: Demanda qu’accepetatz manualament lo mond que vos sègon e botarà la visibilitat de vòstras publicacions coma accessiblas a vòstres abonats solament
+ note: Maximum 160 caractèrs
+ imports:
+ data: Fichièr CSV exportat d’una autra instància Mastodon
+ labels:
+ defaults:
+ avatar: Avatar
+ confirm_new_password: Confirmacion del nòu senhal
+ confirm_password: Confirmatz lo nòu senhal
+ current_password: Senhal actual
+ data: Data
+ display_name: Escais
+ email: Corrièl
+ header: Bandièra
+ locale: Lenga
+ locked: Far venir lo compte privat
+ new_password: Nòu senhal
+ note: Bio
+ otp_attempt: Còdi Two-factor
+ password: Senhal
+ setting_default_privacy: Confidencialitat de las publicacions
+ type: Tip d’impòrt
+ username: Nom d’utilizaire
+ interactions:
+ must_be_follower: Blocar las notificacions del mond que vos sègon pas
+ must_be_following: Blocar las notificacions del mond que seguètz pas
+ notification_emails:
+ digest: Enviar un corrièl recapitulatiu
+ favourite: Enviar un corrièl quand qualqu’un plaça vòstre estatut en favorit
+ follow: Enviar un corrièl quand qualqu’un vos sèc
+ follow_request: Enviar un corrièl quand qualqu’un demanda de vos sègre
+ mention: Enviar un corrièl quand qualqu’un vos menciona
+ reblog: Enviar un corrièl quand qualqu’un tòrna partejar vòstre estatut
+ 'no': 'Non'
+ required:
+ mark: "*"
+ text: requesit
+ 'yes': 'Òc'
diff --git a/config/locales/simple_form.pt-BR.yml b/config/locales/simple_form.pt-BR.yml
new file mode 100644
index 0000000000..28f7eeea89
--- /dev/null
+++ b/config/locales/simple_form.pt-BR.yml
@@ -0,0 +1,30 @@
+---
+pt-BR:
+ simple_form:
+ labels:
+ defaults:
+ avatar: Avatar
+ confirm_new_password: Confirme nova senha
+ confirm_password: Confirme a senha
+ current_password: Senha atual
+ display_name: Nome
+ email: Endereço de email
+ header: Header
+ locale: Linguagem
+ new_password: Nova senha
+ note: Biografia
+ password: Senha
+ username: Usuário
+ interactions:
+ must_be_follower: Bloquear notificações de não-seguidores
+ must_be_following: Bloquear notificações de pessoas que você
+ notification_emails:
+ favourite: Enviar email quando alguém favorita um post seu
+ follow: Enviar email quando alguém seguir você
+ mention: Enviar email quando alguém mencionar você
+ reblog: Enviar email quando alguém reblogar um post seu
+ 'no': 'Não'
+ required:
+ mark: "*"
+ text: necessário
+ 'yes': 'Sim'
diff --git a/config/locales/simple_form.pt.yml b/config/locales/simple_form.pt.yml
index e8b5e2d7f0..ba3326b230 100644
--- a/config/locales/simple_form.pt.yml
+++ b/config/locales/simple_form.pt.yml
@@ -1,28 +1,47 @@
---
pt:
simple_form:
+ hints:
+ defaults:
+ avatar: PNG, GIF ou JPG. No máximo 2MB. Vai ser reduzido para 120x120px
+ display_name: No máximo 30 caracteres
+ header: PNG, GIF or JPG. No máximo 2MB. Vai ser reduzido para 700x335px
+ locked: Requer que manualmente aproves seguidores e torna o default dos teus posts para privados (apenas seguidores)
+ note: No máximo 160 caracteres
+ imports:
+ data: Ficheiro CSV exportado de outra instância do Mastodon
+ sessions:
+ otp: Insere o código o código de autenticação de dois fatores do teu telefone ou utiliza um código de recuperação de acesso.
labels:
defaults:
- avatar: Avatar
- confirm_new_password: Confirme nova senha
- confirm_password: Confirme a senha
- current_password: Senha atual
+ avatar: Imagem de Perfil
+ confirm_new_password: Confirme nova palavra-passe
+ confirm_password: Confirme a palavra-passe
+ current_password: Palavra-passe actual
+ data: Data
display_name: Nome
email: Endereço de email
- header: Header
- locale: Linguagem
- new_password: Nova senha
+ header: Cabeçalho
+ locale: Língua
+ locked: Tornar conta privada
+ new_password: Nova palavra-passe
note: Biografia
- password: Senha
- username: Usuário
+ otp_attempt: Código de autenticação de dois fatores
+ password: Palavra-passe
+ setting_boost_modal: Pedir confirmação antes de partilhar um post
+ setting_default_privacy: Privacidade padrão de posts
+ severity: Severity
+ type: Import type
+ username: Utilizador
interactions:
must_be_follower: Bloquear notificações de não-seguidores
- must_be_following: Bloquear notificações de pessoas que você
+ must_be_following: Bloquear notificações de pessoas que não segues
notification_emails:
- favourite: Enviar email quando alguém favorita um post seu
- follow: Enviar email quando alguém seguir você
- mention: Enviar email quando alguém mencionar você
- reblog: Enviar email quando alguém reblogar um post seu
+ digest: Enviar um email da actividade nesta instância
+ favourite: Enviar email quando alguém adiciona um post teu aos favoritos
+ follow: Enviar email quando alguém te segue
+ mention: Enviar email quando alguém te menciona
+ reblog: Enviar email quando alguém partilhar um post teu
'no': 'Não'
required:
mark: "*"
diff --git a/config/locales/simple_form.ru.yml b/config/locales/simple_form.ru.yml
index b7d8e4e05f..8f6dfa5731 100644
--- a/config/locales/simple_form.ru.yml
+++ b/config/locales/simple_form.ru.yml
@@ -26,7 +26,9 @@ ru:
note: О Вас
otp_attempt: Двухфакторный код
password: Пароль
+ setting_boost_modal: Показывать диалог подтверждения перед продвижением
setting_default_privacy: Видимость постов
+ severity: Строгость
type: Тип импорта
username: Имя пользователя
interactions:
diff --git a/config/navigation.rb b/config/navigation.rb
index 6e2bcd001f..a3f752e477 100644
--- a/config/navigation.rb
+++ b/config/navigation.rb
@@ -17,10 +17,11 @@ SimpleNavigation::Configuration.run do |navigation|
primary.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_reports_url, if: proc { current_user.admin? } do |admin|
admin.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports}
admin.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts}
+ admin.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url, highlights_on: %r{/admin/instances}
admin.item :pubsubhubbubs, safe_join([fa_icon('paper-plane-o fw'), t('admin.pubsubhubbub.title')]), admin_pubsubhubbub_index_url
admin.item :domain_blocks, safe_join([fa_icon('lock fw'), t('admin.domain_blocks.title')]), admin_domain_blocks_url, highlights_on: %r{/admin/domain_blocks}
- admin.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url
- admin.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url
+ admin.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url, link_html: { target: 'sidekiq' }
+ admin.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url, link_html: { target: 'pghero' }
admin.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), admin_settings_url
end
diff --git a/config/routes.rb b/config/routes.rb
index fd186c3206..bc67016a48 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -16,7 +16,7 @@ Rails.application.routes.draw do
end
get '.well-known/host-meta', to: 'well_known/host_meta#show', as: :host_meta, defaults: { format: 'xml' }
- get '.well-known/webfinger', to: 'well_known/webfinger#show', as: :webfinger, defaults: { format: 'json' }
+ get '.well-known/webfinger', to: 'well_known/webfinger#show', as: :webfinger
devise_for :users, path: 'auth', controllers: {
sessions: 'auth/sessions',
@@ -37,13 +37,10 @@ Rails.application.routes.draw do
get :remote_follow, to: 'remote_follow#new'
post :remote_follow, to: 'remote_follow#create'
- member do
- get :followers
- get :following
-
- post :follow
- post :unfollow
- end
+ resources :followers, only: [:index], controller: :follower_accounts
+ resources :following, only: [:index], controller: :following_accounts
+ resource :follow, only: [:create], controller: :account_follow
+ resource :unfollow, only: [:create], controller: :account_unfollow
end
get '/@:username', to: 'accounts#show', as: :short_account
@@ -80,6 +77,7 @@ Rails.application.routes.draw do
resources :pubsubhubbub, only: [:index]
resources :domain_blocks, only: [:index, :new, :create, :show, :destroy]
resources :settings, only: [:index, :update]
+ resources :instances, only: [:index]
resources :reports, only: [:index, :show, :update] do
resources :reported_statuses, only: :destroy
diff --git a/config/settings.yml b/config/settings.yml
index 04213fd0bc..9813963b28 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -15,6 +15,7 @@ defaults: &defaults
open_registrations: true
closed_registrations_message: ''
boost_modal: false
+ auto_play_gif: true
notification_emails:
follow: false
reblog: false
diff --git a/db/migrate/20170418160728_add_indexes_to_reports_for_accounts.rb b/db/migrate/20170418160728_add_indexes_to_reports_for_accounts.rb
new file mode 100644
index 0000000000..cd69bb8b14
--- /dev/null
+++ b/db/migrate/20170418160728_add_indexes_to_reports_for_accounts.rb
@@ -0,0 +1,6 @@
+class AddIndexesToReportsForAccounts < ActiveRecord::Migration[5.0]
+ def change
+ add_index :reports, :account_id
+ add_index :reports, :target_account_id
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 62ff4207d3..78b419b827 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170414132105) do
+ActiveRecord::Schema.define(version: 20170418160728) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -215,6 +215,8 @@ ActiveRecord::Schema.define(version: 20170414132105) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "action_taken_by_account_id"
+ t.index ["account_id"], name: "index_reports_on_account_id", using: :btree
+ t.index ["target_account_id"], name: "index_reports_on_target_account_id", using: :btree
end
create_table "settings", force: :cascade do |t|
diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake
index a1947ea0e9..7dd7b5cd13 100644
--- a/lib/tasks/mastodon.rake
+++ b/lib/tasks/mastodon.rake
@@ -103,6 +103,12 @@ namespace :mastodon do
User.where(id: batch.map(&:id)).delete_all
end
end
+
+ desc 'List all admin users'
+ task admins: :environment do
+ puts 'Admin user emails:'
+ puts User.admins.map(&:email).join("\n")
+ end
end
namespace :settings do
@@ -145,8 +151,8 @@ namespace :mastodon do
Account.unscoped.where(avatar_content_type: 'image/gif').or(Account.unscoped.where(header_content_type: 'image/gif')).find_each do |account|
begin
- account.avatar.reprocess!
- account.header.reprocess!
+ account.avatar.reprocess! if account.avatar_content_type == 'image/gif' && !account.avatar.exists?(:static)
+ account.header.reprocess! if account.header_content_type == 'image/gif' && !account.header.exists?(:static)
rescue StandardError => e
Rails.logger.error "Error while generating static avatars/headers for account #{account.id}: #{e}"
next
diff --git a/public/mask-icon.svg b/public/mask-icon.svg
new file mode 100644
index 0000000000..c352301173
--- /dev/null
+++ b/public/mask-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/spec/controllers/account_follow_controller_spec.rb b/spec/controllers/account_follow_controller_spec.rb
new file mode 100644
index 0000000000..479101c67d
--- /dev/null
+++ b/spec/controllers/account_follow_controller_spec.rb
@@ -0,0 +1,24 @@
+require 'rails_helper'
+
+describe AccountFollowController do
+ render_views
+ let(:user) { Fabricate(:user) }
+ let(:alice) { Fabricate(:account, username: 'alice') }
+
+ describe 'POST #create' do
+ before do
+ sign_in(user)
+ end
+
+ it 'redirects to account path' do
+ service = double
+ allow(FollowService).to receive(:new).and_return(service)
+ allow(service).to receive(:call)
+
+ post :create, params: { account_username: alice.username }
+
+ expect(service).to have_received(:call).with(user.account, 'alice')
+ expect(response).to redirect_to(account_path(alice))
+ end
+ end
+end
diff --git a/spec/controllers/account_unfollow_controller_spec.rb b/spec/controllers/account_unfollow_controller_spec.rb
new file mode 100644
index 0000000000..1f28bf4ab8
--- /dev/null
+++ b/spec/controllers/account_unfollow_controller_spec.rb
@@ -0,0 +1,24 @@
+require 'rails_helper'
+
+describe AccountUnfollowController do
+ render_views
+ let(:user) { Fabricate(:user) }
+ let(:alice) { Fabricate(:account, username: 'alice') }
+
+ describe 'POST #create' do
+ before do
+ sign_in(user)
+ end
+
+ it 'redirects to account path' do
+ service = double
+ allow(UnfollowService).to receive(:new).and_return(service)
+ allow(service).to receive(:call)
+
+ post :create, params: { account_username: alice.username }
+
+ expect(service).to have_received(:call).with(user.account, alice)
+ expect(response).to redirect_to(account_path(alice))
+ end
+ end
+end
diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb
index d2c93c7079..94d10d78f1 100644
--- a/spec/controllers/accounts_controller_spec.rb
+++ b/spec/controllers/accounts_controller_spec.rb
@@ -44,18 +44,4 @@ RSpec.describe AccountsController, type: :controller do
end
end
end
-
- describe 'GET #followers' do
- it 'returns http success' do
- get :followers, params: { username: alice.username }
- expect(response).to have_http_status(:success)
- end
- end
-
- describe 'GET #following' do
- it 'returns http success' do
- get :following, params: { username: alice.username }
- expect(response).to have_http_status(:success)
- end
- end
end
diff --git a/spec/controllers/admin/instances_controller_spec.rb b/spec/controllers/admin/instances_controller_spec.rb
new file mode 100644
index 0000000000..c50ea352f7
--- /dev/null
+++ b/spec/controllers/admin/instances_controller_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+RSpec.describe Admin::InstancesController, type: :controller do
+ before do
+ sign_in Fabricate(:user, admin: true), scope: :user
+ end
+
+ describe 'GET #index' do
+ it 'returns http success' do
+ get :index
+
+ expect(response).to have_http_status(:success)
+ end
+ end
+end
diff --git a/spec/controllers/api/v1/statuses_controller_spec.rb b/spec/controllers/api/v1/statuses_controller_spec.rb
index 669956659a..74faed269e 100644
--- a/spec/controllers/api/v1/statuses_controller_spec.rb
+++ b/spec/controllers/api/v1/statuses_controller_spec.rb
@@ -7,179 +7,289 @@ RSpec.describe Api::V1::StatusesController, type: :controller do
let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
let(:token) { double acceptable?: true, resource_owner_id: user.id, application: app }
- before do
- allow(controller).to receive(:doorkeeper_token) { token }
- end
-
- describe 'GET #show' do
- let(:status) { Fabricate(:status, account: user.account) }
-
- it 'returns http success' do
- get :show, params: { id: status.id }
- expect(response).to have_http_status(:success)
- end
- end
-
- describe 'GET #context' do
- let(:status) { Fabricate(:status, account: user.account) }
-
+ context 'with an oauth token' do
before do
- Fabricate(:status, account: user.account, thread: status)
+ allow(controller).to receive(:doorkeeper_token) { token }
end
- it 'returns http success' do
- get :context, params: { id: status.id }
- expect(response).to have_http_status(:success)
+ describe 'GET #show' do
+ let(:status) { Fabricate(:status, account: user.account) }
+
+ it 'returns http success' do
+ get :show, params: { id: status.id }
+ expect(response).to have_http_status(:success)
+ end
+ end
+
+ describe 'GET #context' do
+ let(:status) { Fabricate(:status, account: user.account) }
+
+ before do
+ Fabricate(:status, account: user.account, thread: status)
+ end
+
+ it 'returns http success' do
+ get :context, params: { id: status.id }
+ expect(response).to have_http_status(:success)
+ end
+ end
+
+ describe 'GET #reblogged_by' do
+ let(:status) { Fabricate(:status, account: user.account) }
+
+ before do
+ post :reblog, params: { id: status.id }
+ end
+
+ it 'returns http success' do
+ get :reblogged_by, params: { id: status.id }
+ expect(response).to have_http_status(:success)
+ end
+ end
+
+ describe 'GET #favourited_by' do
+ let(:status) { Fabricate(:status, account: user.account) }
+
+ before do
+ post :favourite, params: { id: status.id }
+ end
+
+ it 'returns http success' do
+ get :favourited_by, params: { id: status.id }
+ expect(response).to have_http_status(:success)
+ end
+ end
+
+ describe 'POST #create' do
+ before do
+ post :create, params: { status: 'Hello world' }
+ end
+
+ it 'returns http success' do
+ expect(response).to have_http_status(:success)
+ end
+ end
+
+ describe 'DELETE #destroy' do
+ let(:status) { Fabricate(:status, account: user.account) }
+
+ before do
+ post :destroy, params: { id: status.id }
+ end
+
+ it 'returns http success' do
+ expect(response).to have_http_status(:success)
+ end
+
+ it 'removes the status' do
+ expect(Status.find_by(id: status.id)).to be nil
+ end
+ end
+
+ describe 'POST #reblog' do
+ let(:status) { Fabricate(:status, account: user.account) }
+
+ before do
+ post :reblog, params: { id: status.id }
+ end
+
+ it 'returns http success' do
+ expect(response).to have_http_status(:success)
+ end
+
+ it 'updates the reblogs count' do
+ expect(status.reblogs.count).to eq 1
+ end
+
+ it 'updates the reblogged attribute' do
+ expect(user.account.reblogged?(status)).to be true
+ end
+
+ it 'return json with updated attributes' do
+ hash_body = body_as_json
+
+ expect(hash_body[:reblog][:id]).to eq status.id
+ expect(hash_body[:reblog][:reblogs_count]).to eq 1
+ expect(hash_body[:reblog][:reblogged]).to be true
+ end
+ end
+
+ describe 'POST #unreblog' do
+ let(:status) { Fabricate(:status, account: user.account) }
+
+ before do
+ post :reblog, params: { id: status.id }
+ post :unreblog, params: { id: status.id }
+ end
+
+ it 'returns http success' do
+ expect(response).to have_http_status(:success)
+ end
+
+ it 'updates the reblogs count' do
+ expect(status.reblogs.count).to eq 0
+ end
+
+ it 'updates the reblogged attribute' do
+ expect(user.account.reblogged?(status)).to be false
+ end
+ end
+
+ describe 'POST #favourite' do
+ let(:status) { Fabricate(:status, account: user.account) }
+
+ before do
+ post :favourite, params: { id: status.id }
+ end
+
+ it 'returns http success' do
+ expect(response).to have_http_status(:success)
+ end
+
+ it 'updates the favourites count' do
+ expect(status.favourites.count).to eq 1
+ end
+
+ it 'updates the favourited attribute' do
+ expect(user.account.favourited?(status)).to be true
+ end
+
+ it 'return json with updated attributes' do
+ hash_body = body_as_json
+
+ expect(hash_body[:id]).to eq status.id
+ expect(hash_body[:favourites_count]).to eq 1
+ expect(hash_body[:favourited]).to be true
+ end
+ end
+
+ describe 'POST #unfavourite' do
+ let(:status) { Fabricate(:status, account: user.account) }
+
+ before do
+ post :favourite, params: { id: status.id }
+ post :unfavourite, params: { id: status.id }
+ end
+
+ it 'returns http success' do
+ expect(response).to have_http_status(:success)
+ end
+
+ it 'updates the favourites count' do
+ expect(status.favourites.count).to eq 0
+ end
+
+ it 'updates the favourited attribute' do
+ expect(user.account.favourited?(status)).to be false
+ end
end
end
- describe 'GET #reblogged_by' do
- let(:status) { Fabricate(:status, account: user.account) }
-
+ context 'without an oauth token' do
before do
- post :reblog, params: { id: status.id }
+ allow(controller).to receive(:doorkeeper_token) { nil }
end
- it 'returns http success' do
- get :reblogged_by, params: { id: status.id }
- expect(response).to have_http_status(:success)
- end
- end
+ context 'with a private status' do
+ let(:status) { Fabricate(:status, account: user.account, visibility: :private) }
- describe 'GET #favourited_by' do
- let(:status) { Fabricate(:status, account: user.account) }
+ describe 'GET #show' do
+ it 'returns http unautharized' do
+ get :show, params: { id: status.id }
+ expect(response).to have_http_status(:missing)
+ end
+ end
- before do
- post :favourite, params: { id: status.id }
+ describe 'GET #context' do
+ before do
+ Fabricate(:status, account: user.account, thread: status)
+ end
+
+ it 'returns http unautharized' do
+ get :context, params: { id: status.id }
+ expect(response).to have_http_status(:missing)
+ end
+ end
+
+ describe 'GET #card' do
+ it 'returns http unautharized' do
+ get :card, params: { id: status.id }
+ expect(response).to have_http_status(:missing)
+ end
+ end
+
+ describe 'GET #reblogged_by' do
+ before do
+ post :reblog, params: { id: status.id }
+ end
+
+ it 'returns http unautharized' do
+ get :reblogged_by, params: { id: status.id }
+ expect(response).to have_http_status(:missing)
+ end
+ end
+
+ describe 'GET #favourited_by' do
+ before do
+ post :favourite, params: { id: status.id }
+ end
+
+ it 'returns http unautharized' do
+ get :favourited_by, params: { id: status.id }
+ expect(response).to have_http_status(:missing)
+ end
+ end
end
- it 'returns http success' do
- get :favourited_by, params: { id: status.id }
- expect(response).to have_http_status(:success)
- end
- end
+ context 'with a public status' do
+ let(:status) { Fabricate(:status, account: user.account, visibility: :public) }
- describe 'POST #create' do
- before do
- post :create, params: { status: 'Hello world' }
- end
+ describe 'GET #show' do
+ it 'returns http success' do
+ get :show, params: { id: status.id }
+ expect(response).to have_http_status(:success)
+ end
+ end
- it 'returns http success' do
- expect(response).to have_http_status(:success)
- end
- end
+ describe 'GET #context' do
+ before do
+ Fabricate(:status, account: user.account, thread: status)
+ end
- describe 'DELETE #destroy' do
- let(:status) { Fabricate(:status, account: user.account) }
+ it 'returns http success' do
+ get :context, params: { id: status.id }
+ expect(response).to have_http_status(:success)
+ end
+ end
- before do
- post :destroy, params: { id: status.id }
- end
+ describe 'GET #card' do
+ it 'returns http success' do
+ get :card, params: { id: status.id }
+ expect(response).to have_http_status(:success)
+ end
+ end
- it 'returns http success' do
- expect(response).to have_http_status(:success)
- end
+ describe 'GET #reblogged_by' do
+ before do
+ post :reblog, params: { id: status.id }
+ end
- it 'removes the status' do
- expect(Status.find_by(id: status.id)).to be nil
- end
- end
+ it 'returns http success' do
+ get :reblogged_by, params: { id: status.id }
+ expect(response).to have_http_status(:success)
+ end
+ end
- describe 'POST #reblog' do
- let(:status) { Fabricate(:status, account: user.account) }
+ describe 'GET #favourited_by' do
+ before do
+ post :favourite, params: { id: status.id }
+ end
- before do
- post :reblog, params: { id: status.id }
- end
-
- it 'returns http success' do
- expect(response).to have_http_status(:success)
- end
-
- it 'updates the reblogs count' do
- expect(status.reblogs.count).to eq 1
- end
-
- it 'updates the reblogged attribute' do
- expect(user.account.reblogged?(status)).to be true
- end
-
- it 'return json with updated attributes' do
- hash_body = body_as_json
-
- expect(hash_body[:reblog][:id]).to eq status.id
- expect(hash_body[:reblog][:reblogs_count]).to eq 1
- expect(hash_body[:reblog][:reblogged]).to be true
- end
- end
-
- describe 'POST #unreblog' do
- let(:status) { Fabricate(:status, account: user.account) }
-
- before do
- post :reblog, params: { id: status.id }
- post :unreblog, params: { id: status.id }
- end
-
- it 'returns http success' do
- expect(response).to have_http_status(:success)
- end
-
- it 'updates the reblogs count' do
- expect(status.reblogs.count).to eq 0
- end
-
- it 'updates the reblogged attribute' do
- expect(user.account.reblogged?(status)).to be false
- end
- end
-
- describe 'POST #favourite' do
- let(:status) { Fabricate(:status, account: user.account) }
-
- before do
- post :favourite, params: { id: status.id }
- end
-
- it 'returns http success' do
- expect(response).to have_http_status(:success)
- end
-
- it 'updates the favourites count' do
- expect(status.favourites.count).to eq 1
- end
-
- it 'updates the favourited attribute' do
- expect(user.account.favourited?(status)).to be true
- end
-
- it 'return json with updated attributes' do
- hash_body = body_as_json
-
- expect(hash_body[:id]).to eq status.id
- expect(hash_body[:favourites_count]).to eq 1
- expect(hash_body[:favourited]).to be true
- end
- end
-
- describe 'POST #unfavourite' do
- let(:status) { Fabricate(:status, account: user.account) }
-
- before do
- post :favourite, params: { id: status.id }
- post :unfavourite, params: { id: status.id }
- end
-
- it 'returns http success' do
- expect(response).to have_http_status(:success)
- end
-
- it 'updates the favourites count' do
- expect(status.favourites.count).to eq 0
- end
-
- it 'updates the favourited attribute' do
- expect(user.account.favourited?(status)).to be false
+ it 'returns http success' do
+ get :favourited_by, params: { id: status.id }
+ expect(response).to have_http_status(:success)
+ end
+ end
end
end
end
diff --git a/spec/controllers/auth/registrations_controller_spec.rb b/spec/controllers/auth/registrations_controller_spec.rb
index 6b26e66931..c2141766eb 100644
--- a/spec/controllers/auth/registrations_controller_spec.rb
+++ b/spec/controllers/auth/registrations_controller_spec.rb
@@ -16,9 +16,12 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
end
describe 'POST #create' do
+ let(:accept_language) { Rails.application.config.i18n.available_locales.sample.to_s }
+
before do
Setting.open_registrations = true
request.env["devise.mapping"] = Devise.mappings[:user]
+ request.headers["Accept-Language"] = accept_language
post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678' } }
end
@@ -27,7 +30,9 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
end
it 'creates user' do
- expect(User.find_by(email: 'test@example.com')).to_not be_nil
+ user = User.find_by(email: 'test@example.com')
+ expect(user).to_not be_nil
+ expect(user.locale).to eq(accept_language)
end
end
end
diff --git a/spec/controllers/follower_accounts_controller_spec.rb b/spec/controllers/follower_accounts_controller_spec.rb
new file mode 100644
index 0000000000..82d2b2067c
--- /dev/null
+++ b/spec/controllers/follower_accounts_controller_spec.rb
@@ -0,0 +1,14 @@
+require 'rails_helper'
+
+describe FollowerAccountsController do
+ render_views
+ let(:alice) { Fabricate(:account, username: 'alice') }
+
+ describe 'GET #index' do
+ it 'returns http success' do
+ get :index, params: { account_username: alice.username }
+
+ expect(response).to have_http_status(:success)
+ end
+ end
+end
diff --git a/spec/controllers/following_accounts_controller_spec.rb b/spec/controllers/following_accounts_controller_spec.rb
new file mode 100644
index 0000000000..5b5a6fe5f2
--- /dev/null
+++ b/spec/controllers/following_accounts_controller_spec.rb
@@ -0,0 +1,14 @@
+require 'rails_helper'
+
+describe FollowingAccountsController do
+ render_views
+ let(:alice) { Fabricate(:account, username: 'alice') }
+
+ describe 'GET #index' do
+ it 'returns http success' do
+ get :index, params: { account_username: alice.username }
+
+ expect(response).to have_http_status(:success)
+ end
+ end
+end
diff --git a/spec/controllers/media_controller_spec.rb b/spec/controllers/media_controller_spec.rb
new file mode 100644
index 0000000000..ebf6aa006e
--- /dev/null
+++ b/spec/controllers/media_controller_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe MediaController do
+ describe '#show' do
+ it 'redirects to the file url when attached to a status' do
+ status = Fabricate(:status)
+ media_attachment = Fabricate(:media_attachment, status: status)
+ get :show, params: { id: media_attachment.to_param }
+
+ expect(response).to redirect_to(media_attachment.file.url(:original))
+ end
+
+ it 'responds with missing when there is not an attached status' do
+ media_attachment = Fabricate(:media_attachment, status: nil)
+ get :show, params: { id: media_attachment.to_param }
+
+ expect(response).to have_http_status(:missing)
+ end
+
+ it 'raises when shortcode cant be found' do
+ get :show, params: { id: 'missing' }
+
+ expect(response).to have_http_status(:missing)
+ end
+
+ it 'raises when not permitted to view' do
+ status = Fabricate(:status)
+ media_attachment = Fabricate(:media_attachment, status: status)
+ allow_any_instance_of(Status).to receive(:permitted?).and_return(false)
+ get :show, params: { id: media_attachment.to_param }
+
+ expect(response).to have_http_status(:missing)
+ end
+ end
+end
diff --git a/spec/helpers/instance_helper_spec.rb b/spec/helpers/instance_helper_spec.rb
new file mode 100644
index 0000000000..c42ed69388
--- /dev/null
+++ b/spec/helpers/instance_helper_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe InstanceHelper do
+ describe 'site_title' do
+ it 'Uses the Setting.site_title value when it exists' do
+ Setting.site_title = 'New site title'
+
+ expect(helper.site_title).to eq 'New site title'
+ end
+
+ it 'returns empty string when Setting.site_title is nil' do
+ Setting.site_title = nil
+
+ expect(helper.site_title).to eq ''
+ end
+ end
+
+ describe 'site_hostname' do
+ around(:each) do |example|
+ before = Rails.configuration.x.local_domain
+ example.run
+ Rails.configuration.x.local_domain = before
+ end
+
+ it 'returns the local domain value' do
+ Rails.configuration.x.local_domain = 'example.com'
+
+ expect(helper.site_hostname).to eq 'example.com'
+ end
+ end
+end
diff --git a/spec/helpers/site_title_helper_spec.rb b/spec/helpers/site_title_helper_spec.rb
deleted file mode 100644
index 8cfd9cba1c..0000000000
--- a/spec/helpers/site_title_helper_spec.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-require "rails_helper"
-
-describe "site_title" do
- it "Uses the Setting.site_title value when it exists" do
- Setting.site_title = "New site title"
-
- expect(helper.site_title).to eq "New site title"
- end
-
- it "returns empty string when Setting.site_title is nil" do
- Setting.site_title = nil
-
- expect(helper.site_title).to eq ""
- end
-end
diff --git a/spec/i18n_spec.rb b/spec/i18n_spec.rb
deleted file mode 100644
index 138d25569a..0000000000
--- a/spec/i18n_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-require 'i18n/tasks'
-
-RSpec.describe 'I18n' do
- let(:i18n) { I18n::Tasks::BaseTask.new }
- let(:missing_keys) { i18n.missing_keys }
- let(:unused_keys) { i18n.unused_keys }
-
- xit 'does not have missing keys' do
- expect(missing_keys).to be_empty, "Missing #{missing_keys.leaves.count} i18n keys, run `i18n-tasks missing' to show them"
- end
-
- xit 'does not have unused keys' do
- expect(unused_keys).to be_empty, "#{unused_keys.leaves.count} unused i18n keys, run `i18n-tasks unused' to show them"
- end
-end
diff --git a/spec/lib/atom_serializer_spec.rb b/spec/lib/atom_serializer_spec.rb
new file mode 100644
index 0000000000..cfca82a284
--- /dev/null
+++ b/spec/lib/atom_serializer_spec.rb
@@ -0,0 +1,206 @@
+require 'rails_helper'
+
+RSpec.describe AtomSerializer do
+ let(:author) { Fabricate(:account, username: 'Sombra', display_name: '1337 haxxor') }
+ let(:receiver) { Fabricate(:account, username: 'Symmetra') }
+
+ before do
+ stub_request(:get, "https://cb6e6126.ngrok.io/avatars/original/missing.png").to_return(status: 404)
+ stub_request(:get, "https://cb6e6126.ngrok.io/headers/original/missing.png").to_return(status: 404)
+ end
+
+ describe '#author' do
+ it 'returns dumpable XML with emojis' do
+ account = Fabricate(:account, display_name: '💩')
+ xml = AtomSerializer.render(AtomSerializer.new.author(account))
+
+ expect(xml).to be_a String
+ expect(xml).to match(/💩<\/poco:displayName>/)
+ end
+
+ it 'returns dumpable XML with invalid characters like \b and \v' do
+ account = Fabricate(:account, display_name: "im l33t\b haxo\b\vr")
+ xml = AtomSerializer.render(AtomSerializer.new.author(account))
+
+ expect(xml).to be_a String
+ expect(xml).to match(/im l33t haxor<\/poco:displayName>/)
+ end
+ end
+
+ describe '#entry' do
+ describe 'with deleted status' do
+ let(:entry) do
+ status = Fabricate(:status, account: author, text: 'boop')
+ entry = status.stream_entry
+ status.destroy
+ entry
+ end
+
+ it 'returns dumpable XML' do
+ xml = AtomSerializer.render(AtomSerializer.new.entry(entry, true))
+ expect(xml).to be_a String
+ expect(xml).to match(/#{TagManager.instance.unique_tag(entry.created_at, entry.activity_id, 'Status')}<\/id>/)
+ end
+
+ it 'triggers delete when processed' do
+ status = double(id: entry.activity_id)
+ service = double
+
+ allow(Status).to receive(:find_by).and_return(status)
+ allow(RemoveStatusService).to receive(:new).and_return(service)
+ allow(service).to receive(:call)
+
+ xml = AtomSerializer.render(AtomSerializer.new.entry(entry, true))
+ ProcessFeedService.new.call(xml, author)
+
+ expect(service).to have_received(:call).with(status)
+ end
+ end
+
+ describe 'with reblog of local user' do
+ it 'returns dumpable XML'
+ it 'creates a reblog'
+ end
+
+ describe 'with reblog of 3rd party user' do
+ it 'returns dumpable XML'
+ it 'creates a reblog with correct author'
+ end
+ end
+
+ describe '#follow_salmon' do
+ let(:xml) do
+ follow = Fabricate(:follow, account: author, target_account: receiver)
+ xml = AtomSerializer.render(AtomSerializer.new.follow_salmon(follow))
+ follow.destroy
+ xml
+ end
+
+ it 'returns dumpable XML' do
+ expect(xml).to be_a String
+ end
+
+ it 'triggers follow when processed' do
+ envelope = OStatus2::Salmon.new.pack(xml, author.keypair)
+ ProcessInteractionService.new.call(envelope, receiver)
+ expect(author.following?(receiver)).to be true
+ end
+ end
+
+ describe '#unfollow_salmon' do
+ let(:xml) do
+ follow = Fabricate(:follow, account: author, target_account: receiver)
+ follow.destroy
+ xml = AtomSerializer.render(AtomSerializer.new.unfollow_salmon(follow))
+ author.follow!(receiver)
+ xml
+ end
+
+ it 'returns dumpable XML' do
+ expect(xml).to be_a String
+ end
+
+ it 'triggers unfollow when processed' do
+ envelope = OStatus2::Salmon.new.pack(xml, author.keypair)
+ ProcessInteractionService.new.call(envelope, receiver)
+ expect(author.following?(receiver)).to be false
+ end
+ end
+
+ describe '#favourite_salmon' do
+ let(:status) { Fabricate(:status, account: receiver, text: 'Everything by design.') }
+
+ let(:xml) do
+ favourite = Fabricate(:favourite, account: author, status: status)
+ xml = AtomSerializer.render(AtomSerializer.new.favourite_salmon(favourite))
+ favourite.destroy
+ xml
+ end
+
+ it 'returns dumpable XML' do
+ expect(xml).to be_a String
+ end
+
+ it 'triggers favourite when processed' do
+ envelope = OStatus2::Salmon.new.pack(xml, author.keypair)
+ ProcessInteractionService.new.call(envelope, receiver)
+ expect(author.favourited?(status)).to be true
+ end
+ end
+
+ describe '#unfavourite_salmon' do
+ let(:status) { Fabricate(:status, account: receiver, text: 'Perfect harmony.') }
+
+ let(:xml) do
+ favourite = Fabricate(:favourite, account: author, status: status)
+ favourite.destroy
+ xml = AtomSerializer.render(AtomSerializer.new.unfavourite_salmon(favourite))
+ Fabricate(:favourite, account: author, status: status)
+ xml
+ end
+
+ it 'returns dumpable XML' do
+ expect(xml).to be_a String
+ end
+
+ it 'triggers unfavourite when processed' do
+ envelope = OStatus2::Salmon.new.pack(xml, author.keypair)
+ ProcessInteractionService.new.call(envelope, receiver)
+ expect(author.favourited?(status)).to be false
+ end
+ end
+
+ describe '#block_salmon' do
+ let(:xml) do
+ block = Fabricate(:block, account: author, target_account: receiver)
+ xml = AtomSerializer.render(AtomSerializer.new.block_salmon(block))
+ block.destroy
+ xml
+ end
+
+ it 'returns dumpable XML' do
+ expect(xml).to be_a String
+ end
+
+ it 'triggers block when processed' do
+ envelope = OStatus2::Salmon.new.pack(xml, author.keypair)
+ ProcessInteractionService.new.call(envelope, receiver)
+ expect(author.blocking?(receiver)).to be true
+ end
+ end
+
+ describe '#unblock_salmon' do
+ let(:xml) do
+ block = Fabricate(:block, account: author, target_account: receiver)
+ block.destroy
+ xml = AtomSerializer.render(AtomSerializer.new.unblock_salmon(block))
+ author.block!(receiver)
+ xml
+ end
+
+ it 'returns dumpable XML' do
+ expect(xml).to be_a String
+ end
+
+ it 'triggers unblock when processed' do
+ envelope = OStatus2::Salmon.new.pack(xml, author.keypair)
+ ProcessInteractionService.new.call(envelope, receiver)
+ expect(author.blocking?(receiver)).to be false
+ end
+ end
+
+ describe '#follow_request_salmon' do
+ it 'returns dumpable XML'
+ it 'triggers follow request when processed'
+ end
+
+ describe '#authorize_follow_request_salmon' do
+ it 'returns dumpable XML'
+ it 'creates follow from follow request when processed'
+ end
+
+ describe '#reject_follow_request_salmon' do
+ it 'returns dumpable XML'
+ it 'deletes follow request when processed'
+ end
+end
diff --git a/spec/lib/formatter_spec.rb b/spec/lib/formatter_spec.rb
index 4b003b8e50..b70231d265 100644
--- a/spec/lib/formatter_spec.rb
+++ b/spec/lib/formatter_spec.rb
@@ -2,7 +2,8 @@ require 'rails_helper'
RSpec.describe Formatter do
let(:account) { Fabricate(:account, username: 'alice') }
- let(:local_status) { Fabricate(:status, text: 'Hello world http://google.com', account: account) }
+ let(:local_text) { 'Hello world http://google.com' }
+ let(:local_status) { Fabricate(:status, text: local_text, account: account) }
let(:remote_status) { Fabricate(:status, text: ' Beep boop', uri: 'beepboop', account: account) }
describe '#format' do
@@ -20,35 +21,81 @@ RSpec.describe Formatter do
expect(subject).to match('http://google.com')
end
+ context 'matches a stand-alone medium URL' do
+ let(:local_text) { 'https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4' }
+ it 'has valid url' do
+ expect(subject).to include('href="https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4"')
+ end
+ end
+
+ context 'matches a stand-alone google URL' do
+ let(:local_text) { 'http://google.com' }
+ it 'has valid url' do
+ expect(subject).to include('href="http://google.com"')
+ end
+ end
+
+ context 'matches a URL without trailing period' do
+ let(:local_text) { 'http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona. ' }
+ it 'has valid url' do
+ expect(subject).to include('href="http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona"')
+ end
+ end
+
=begin
- it 'matches a stand-alone medium URL' do
- expect(subject.match('https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4')[0]).to eq 'https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4'
- end
-
- it 'matches a stand-alone google URL' do
- expect(subject.match('http://google.com')[0]).to eq 'http://google.com'
- end
-
- it 'matches a URL without trailing period' do
- expect(subject.match('http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona. ')[0]).to eq 'http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona'
- end
-
it 'matches a URL without closing paranthesis' do
expect(subject.match('(http://google.com/)')[0]).to eq 'http://google.com'
end
-
- it 'matches a URL without exclamation point' do
- expect(subject.match('http://www.google.com! ')[0]).to eq 'http://www.google.com'
- end
-
- it 'matches a URL with a query string' do
- expect(subject.match('https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink')[0]).to eq 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink'
- end
-
- it 'matches a URL with parenthesis in it' do
- expect(subject.match('https://en.wikipedia.org/wiki/Diaspora_(software)')[0]).to eq 'https://en.wikipedia.org/wiki/Diaspora_(software)'
- end
=end
+
+ context 'matches a URL without exclamation point' do
+ let(:local_text) { 'http://www.google.com!' }
+ it 'has valid url' do
+ expect(subject).to include('href="http://www.google.com"')
+ end
+ end
+
+ context 'matches a URL without single quote' do
+ let(:local_text) { "http://www.google.com'" }
+ it 'has valid url' do
+ expect(subject).to include('href="http://www.google.com"')
+ end
+ end
+
+ context 'matches a URL without angle brackets' do
+ let(:local_text) { 'http://www.google.com>' }
+ it 'has valid url' do
+ expect(subject).to include('href="http://www.google.com"')
+ end
+ end
+
+ context 'matches a URL with a query string' do
+ let(:local_text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink' }
+ it 'has valid url' do
+ expect(subject).to include('href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink"')
+ end
+ end
+
+ context 'matches a URL with parenthesis in it' do
+ let(:local_text) { 'https://en.wikipedia.org/wiki/Diaspora_(software)' }
+ it 'has valid url' do
+ expect(subject).to include('href="https://en.wikipedia.org/wiki/Diaspora_(software)"')
+ end
+ end
+
+ context 'contains html (script tag)' do
+ let(:local_text) { '' }
+ it 'has valid url' do
+ expect(subject).to match '
<script>alert("Hello")</script>
'
+ end
+ end
+
+ context 'contains html (xss attack)' do
+ let(:local_text) { %q{} }
+ it 'has valid url' do
+ expect(subject).to match '
<img src="javascript:alert('XSS');">
'
+ end
+ end
end
describe '#reformat' do
diff --git a/spec/lib/language_detector_spec.rb b/spec/lib/language_detector_spec.rb
new file mode 100644
index 0000000000..2e68ec63bc
--- /dev/null
+++ b/spec/lib/language_detector_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+require 'rails_helper'
+
+describe LanguageDetector do
+ describe 'to_iso_s' do
+ it 'detects english language' do
+ string = 'Hello and welcome to mastodon'
+ result = described_class.new(string).to_iso_s
+
+ expect(result).to eq :en
+ end
+
+ it 'detects spanish language' do
+ string = 'Obtener un Hola y bienvenidos a Mastodon'
+ result = described_class.new(string).to_iso_s
+
+ expect(result).to eq :es
+ end
+
+ describe 'when language can\'t be detected' do
+ it 'confirm language engine cant detect' do
+ result = WhatLanguage.new(:all).language_iso('')
+ expect(result).to be_nil
+ end
+
+ describe 'with an account' do
+ it 'uses the account locale when present' do
+ user = double(:user, locale: 'fr')
+ account = double(:account, user: user)
+ result = described_class.new('', account).to_iso_s
+
+ expect(result).to eq :fr
+ end
+
+ it 'uses default locale when account is present but has no locale' do
+ user = double(:user, locale: nil)
+ account = double(:accunt, user: user)
+ result = described_class.new('', account).to_iso_s
+
+ expect(result).to eq :en
+ end
+ end
+
+ describe 'with an `en` default locale' do
+ it 'uses the default locale' do
+ string = ''
+ result = described_class.new(string).to_iso_s
+
+ expect(result).to eq :en
+ end
+ end
+
+ describe 'with a non-`en` default locale' do
+ around(:each) do |example|
+ before = I18n.default_locale
+ I18n.default_locale = :ja
+ example.run
+ I18n.default_locale = before
+ end
+
+ it 'uses the default locale' do
+ string = ''
+ result = described_class.new(string).to_iso_s
+
+ expect(result).to eq :ja
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb
index 46c1ff63c0..3edbc32534 100644
--- a/spec/models/account_spec.rb
+++ b/spec/models/account_spec.rb
@@ -245,6 +245,23 @@ RSpec.describe Account, type: :model do
end
end
+ describe '.triadic_closures' do
+ it 'finds accounts you dont follow which are followed by accounts you do follow' do
+ me = Fabricate(:account)
+ friend = Fabricate(:account)
+ friends_friend = Fabricate(:account)
+ me.follow!(friend)
+ friend.follow!(friends_friend)
+
+ both_follow = Fabricate(:account)
+ me.follow!(both_follow)
+ friend.follow!(both_follow)
+
+ results = Account.triadic_closures(me)
+ expect(results).to eq [friends_friend]
+ end
+ end
+
describe '.find_local' do
before do
Fabricate(:account, username: 'Alice')
@@ -393,6 +410,20 @@ RSpec.describe Account, type: :model do
end
end
+ describe 'by_domain_accounts' do
+ it 'returns accounts grouped by domain sorted by accounts' do
+ 2.times { Fabricate(:account, domain: 'example.com') }
+ Fabricate(:account, domain: 'example2.com')
+
+ results = Account.by_domain_accounts
+ expect(results.length).to eq 2
+ expect(results.first.domain).to eq 'example.com'
+ expect(results.first.accounts_count).to eq 2
+ expect(results.last.domain).to eq 'example2.com'
+ expect(results.last.accounts_count).to eq 1
+ end
+ end
+
describe 'local' do
it 'returns an array of accounts who do not have a domain' do
account_1 = Fabricate(:account, domain: nil)
diff --git a/spec/models/report_filter_spec.rb b/spec/models/report_filter_spec.rb
new file mode 100644
index 0000000000..fc0b7d7b50
--- /dev/null
+++ b/spec/models/report_filter_spec.rb
@@ -0,0 +1,31 @@
+require 'rails_helper'
+
+describe ReportFilter do
+ describe 'with empty params' do
+ it 'defaults to unresolved reports list' do
+ filter = ReportFilter.new({})
+
+ expect(filter.results).to eq Report.unresolved
+ end
+ end
+
+ describe 'with invalid params' do
+ it 'raises with key error' do
+ filter = ReportFilter.new(wrong: true)
+
+ expect { filter.results }.to raise_error(/wrong/)
+ end
+ end
+
+ describe 'with valid params' do
+ it 'combines filters on Report' do
+ filter = ReportFilter.new(account_id: '123', resolved: true)
+
+ allow(Report).to receive(:where).and_return(Report.none)
+ allow(Report).to receive(:resolved).and_return(Report.none)
+ filter.results
+ expect(Report).to have_received(:where).with(account_id: '123')
+ expect(Report).to have_received(:resolved)
+ end
+ end
+end
diff --git a/spec/requests/link_headers_spec.rb b/spec/requests/link_headers_spec.rb
new file mode 100644
index 0000000000..3947806ccc
--- /dev/null
+++ b/spec/requests/link_headers_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'Link headers' do
+ describe 'on the account show page' do
+ let(:account) { Fabricate(:account, username: 'test') }
+
+ before do
+ get short_account_path(username: account)
+ end
+
+ it 'contains webfinger url in link header' do
+ link_header = link_header_with_type('application/xrd+xml')
+
+ expect(link_header.href).to match 'http://www.example.com/.well-known/webfinger?resource=acct%3Atest%40cb6e6126.ngrok.io'
+ expect(link_header.attr_pairs.first).to eq %w[rel lrdd]
+ end
+
+ it 'contains atom url in link header' do
+ link_header = link_header_with_type('application/atom+xml')
+
+ expect(link_header.href).to eq 'http://www.example.com/users/test.atom'
+ expect(link_header.attr_pairs.first).to eq %w[rel alternate]
+ end
+
+ def link_header_with_type(type)
+ response.headers['Link'].links.find do |link|
+ link.attr_pairs.any? { |pair| pair == ['type', type] }
+ end
+ end
+ end
+end
diff --git a/spec/requests/webfinger_request_spec.rb b/spec/requests/webfinger_request_spec.rb
index b5690d22f5..a17d6cc22e 100644
--- a/spec/requests/webfinger_request_spec.rb
+++ b/spec/requests/webfinger_request_spec.rb
@@ -1,33 +1,48 @@
-require "rails_helper"
+require 'rails_helper'
-describe "The webfinger route" do
+describe 'The webfinger route' do
let(:alice) { Fabricate(:account, username: 'alice') }
- describe "requested without accepts headers" do
- it "returns a json response" do
- get webfinger_url, params: { resource: alice.to_webfinger_s }
+ describe 'requested with standard accepts headers' do
+ it 'returns a json response' do
+ get webfinger_url(resource: alice.to_webfinger_s)
expect(response).to have_http_status(:success)
- expect(response.content_type).to eq "application/jrd+json"
+ expect(response.content_type).to eq 'application/jrd+json'
end
end
- describe "requested with html in accepts headers" do
- it "returns a json response" do
- headers = { 'HTTP_ACCEPT' => 'text/html' }
- get webfinger_url, params: { resource: alice.to_webfinger_s }, headers: headers
-
- expect(response).to have_http_status(:success)
- expect(response.content_type).to eq "application/jrd+json"
- end
- end
-
- describe "requested with xml format" do
- it "returns an xml response" do
+ describe 'asking for xml format' do
+ it 'returns an xml response for xml format' do
get webfinger_url(resource: alice.to_webfinger_s, format: :xml)
expect(response).to have_http_status(:success)
- expect(response.content_type).to eq "application/xrd+xml"
+ expect(response.content_type).to eq 'application/xrd+xml'
+ end
+
+ it 'returns an xml response for xml accept header' do
+ headers = { 'HTTP_ACCEPT' => 'application/xrd+xml' }
+ get webfinger_url(resource: alice.to_webfinger_s), headers: headers
+
+ expect(response).to have_http_status(:success)
+ expect(response.content_type).to eq 'application/xrd+xml'
+ end
+ end
+
+ describe 'asking for json format' do
+ it 'returns a json response for json format' do
+ get webfinger_url(resource: alice.to_webfinger_s, format: :json)
+
+ expect(response).to have_http_status(:success)
+ expect(response.content_type).to eq 'application/jrd+json'
+ end
+
+ it 'returns a json response for json accept header' do
+ headers = { 'HTTP_ACCEPT' => 'application/jrd+json' }
+ get webfinger_url(resource: alice.to_webfinger_s), headers: headers
+
+ expect(response).to have_http_status(:success)
+ expect(response.content_type).to eq 'application/jrd+json'
end
end
end
diff --git a/spec/routing/accounts_routing_spec.rb b/spec/routing/accounts_routing_spec.rb
new file mode 100644
index 0000000000..d04cb27f04
--- /dev/null
+++ b/spec/routing/accounts_routing_spec.rb
@@ -0,0 +1,31 @@
+require 'rails_helper'
+
+describe 'Routes under accounts/' do
+ describe 'the route for accounts who are followers of an account' do
+ it 'routes to the followers action with the right username' do
+ expect(get('/users/name/followers')).
+ to route_to('follower_accounts#index', account_username: 'name')
+ end
+ end
+
+ describe 'the route for accounts who are followed by an account' do
+ it 'routes to the following action with the right username' do
+ expect(get('/users/name/following')).
+ to route_to('following_accounts#index', account_username: 'name')
+ end
+ end
+
+ describe 'the route for following an account' do
+ it 'routes to the follow create action with the right username' do
+ expect(post('/users/name/follow')).
+ to route_to('account_follow#create', account_username: 'name')
+ end
+ end
+
+ describe 'the route for unfollowing an account' do
+ it 'routes to the unfollow create action with the right username' do
+ expect(post('/users/name/unfollow')).
+ to route_to('account_unfollow#create', account_username: 'name')
+ end
+ end
+end
diff --git a/spec/routing/well_known_routes_spec.rb b/spec/routing/well_known_routes_spec.rb
index 9540c3de34..2e25605c2e 100644
--- a/spec/routing/well_known_routes_spec.rb
+++ b/spec/routing/well_known_routes_spec.rb
@@ -10,6 +10,6 @@ end
describe 'the webfinger route' do
it 'routes to correct place with json format' do
expect(get('/.well-known/webfinger')).
- to route_to('well_known/webfinger#show', format: 'json')
+ to route_to('well_known/webfinger#show')
end
end
diff --git a/spec/services/account_search_service_spec.rb b/spec/services/account_search_service_spec.rb
index fa421c4436..7236238335 100644
--- a/spec/services/account_search_service_spec.rb
+++ b/spec/services/account_search_service_spec.rb
@@ -25,6 +25,18 @@ describe AccountSearchService do
end
describe 'searching local and remote users' do
+ describe "when only '@'" do
+ before do
+ allow(Account).to receive(:find_remote)
+ allow(Account).to receive(:search_for)
+ subject.call('@', 10)
+ end
+
+ it 'uses find_remote with empty query to look for local accounts' do
+ expect(Account).to have_received(:find_remote).with('', nil)
+ end
+ end
+
describe 'when no domain' do
before do
allow(Account).to receive(:find_remote)
diff --git a/spec/services/follow_remote_account_service_spec.rb b/spec/services/follow_remote_account_service_spec.rb
index 27df76457e..a8d4a7c6b2 100644
--- a/spec/services/follow_remote_account_service_spec.rb
+++ b/spec/services/follow_remote_account_service_spec.rb
@@ -3,10 +3,35 @@ require 'rails_helper'
RSpec.describe FollowRemoteAccountService do
subject { FollowRemoteAccountService.new }
- it 'returns nil if no such user can be resolved via webfinger'
- it 'returns nil if the domain does not have webfinger'
- it 'returns nil if remote user does not offer a hub URL'
- it 'returns an already existing remote account'
- it 'returns a new remote account'
- it 'fills the remote account with profile information'
+ before do
+ stub_request(:get, "https://quitter.no/.well-known/host-meta").to_return(request_fixture('.host-meta.txt'))
+ stub_request(:get, "https://example.com/.well-known/host-meta").to_return(status: 404)
+ stub_request(:get, "https://quitter.no/.well-known/webfinger?resource=acct:gargron@quitter.no").to_return(request_fixture('webfinger.txt'))
+ stub_request(:get, "https://quitter.no/.well-known/webfinger?resource=acct:catsrgr8@quitter.no").to_return(status: 404)
+ stub_request(:get, "https://quitter.no/api/statuses/user_timeline/7477.atom").to_return(request_fixture('feed.txt'))
+ stub_request(:get, "https://quitter.no/avatar/7477-300-20160211190340.png").to_return(request_fixture('avatar.txt'))
+ end
+
+ it 'raises error if no such user can be resolved via webfinger' do
+ expect { subject.call('catsrgr8@quitter.no') }.to raise_error Goldfinger::Error
+ end
+
+ it 'raises error if the domain does not have webfinger' do
+ expect { subject.call('catsrgr8@example.com') }.to raise_error Goldfinger::Error
+ end
+
+ it 'returns an already existing remote account' do
+ old_account = Fabricate(:account, username: 'gargron', domain: 'quitter.no')
+ returned_account = subject.call('gargron@quitter.no')
+
+ expect(old_account.id).to eq returned_account.id
+ end
+
+ it 'returns a new remote account' do
+ account = subject.call('gargron@quitter.no')
+
+ expect(account.username).to eq 'gargron'
+ expect(account.domain).to eq 'quitter.no'
+ expect(account.remote_url).to eq 'https://quitter.no/api/statuses/user_timeline/7477.atom'
+ end
end
diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb
index 0e39cd969a..c9d80257ff 100644
--- a/spec/services/post_status_service_spec.rb
+++ b/spec/services/post_status_service_spec.rb
@@ -64,6 +64,18 @@ RSpec.describe PostStatusService do
expect(status.application).to eq application
end
+ it 'creates a status with a language set' do
+ detector = double(to_iso_s: :en)
+ allow(LanguageDetector).to receive(:new).and_return(detector)
+
+ account = Fabricate(:account)
+ text = 'test status text'
+
+ subject.call(account, text)
+
+ expect(LanguageDetector).to have_received(:new).with(text, account)
+ end
+
it 'processes mentions' do
mention_service = double(:process_mentions_service)
allow(mention_service).to receive(:call)
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index aa180a387e..2bc462121b 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -21,10 +21,6 @@ RSpec.configure do |config|
end
end
- config.before :each do
- stub_request(:post, 'https://fcm.googleapis.com/fcm/send').to_return(status: 200, body: '')
- end
-
config.after :suite do
FileUtils.rm_rf(Dir["#{Rails.root}/spec/test_files/"])
end
diff --git a/storybook/config.js b/storybook/config.js
index 4a111a8b9c..976b83af08 100644
--- a/storybook/config.js
+++ b/storybook/config.js
@@ -14,10 +14,10 @@ window.storiesOf = storiesOf;
window.action = action;
window.React = React;
+let req = require.context('./stories/', true, /.story.jsx$/);
+
function loadStories () {
- require('./stories/loading_indicator.story.jsx');
- require('./stories/button.story.jsx');
- require('./stories/autosuggest_textarea.story.jsx');
+ req.keys().forEach((filename) => req(filename))
}
configure(loadStories, module);
diff --git a/storybook/stories/character_counter.story.jsx b/storybook/stories/character_counter.story.jsx
new file mode 100644
index 0000000000..931d8a0374
--- /dev/null
+++ b/storybook/stories/character_counter.story.jsx
@@ -0,0 +1,20 @@
+import { storiesOf } from '@kadira/storybook';
+import CharacterCounter from '../../app/assets/javascripts/components/features/compose/components/character_counter';
+
+storiesOf('CharacterCounter', module)
+ .add('no text', () => {
+ const text = '';
+ return ;
+ })
+ .add('a few strings text', () => {
+ const text = '0123456789';
+ return ;
+ })
+ .add('the same text', () => {
+ const text = '01234567890123456789';
+ return ;
+ })
+ .add('over text', () => {
+ const text = '01234567890123456789012345678901234567890123456789';
+ return ;
+ });
diff --git a/streaming/index.js b/streaming/index.js
index a1e7eaca77..5e25085c25 100644
--- a/streaming/index.js
+++ b/streaming/index.js
@@ -1,3 +1,5 @@
+import os from 'os';
+import cluster from 'cluster';
import dotenv from 'dotenv'
import express from 'express'
import http from 'http'
@@ -14,300 +16,318 @@ dotenv.config({
path: env === 'production' ? '.env.production' : '.env'
})
-const pgConfigs = {
- development: {
- database: 'mastodon_development',
- host: '/var/run/postgresql',
- max: 10
- },
+if (cluster.isMaster) {
+ // cluster master
- production: {
- user: process.env.DB_USER || 'mastodon',
- password: process.env.DB_PASS || '',
- database: process.env.DB_NAME || 'mastodon_production',
- host: process.env.DB_HOST || 'localhost',
- port: process.env.DB_PORT || 5432,
- max: 10
- }
-}
+ const core = +process.env.STREAMING_CLUSTER_NUM || (env === 'development' ? 1 : (os.cpus().length > 1 ? os.cpus().length - 1 : 1))
+ const fork = () => {
+ const worker = cluster.fork();
+ worker.on('exit', (code, signal) => {
+ log.error(`Worker died with exit code ${code}, signal ${signal} received.`);
+ setTimeout(() => fork(), 0);
+ });
+ };
+ for (let i = 0; i < core; i++) fork();
+ log.info(`Starting streaming API server master with ${core} workers`)
-const app = express()
-const pgPool = new pg.Pool(pgConfigs[env])
-const server = http.createServer(app)
-const wss = new WebSocket.Server({ server })
+} else {
+ // cluster worker
-const redisClient = redis.createClient({
- host: process.env.REDIS_HOST || '127.0.0.1',
- port: process.env.REDIS_PORT || 6379,
- password: process.env.REDIS_PASSWORD
-})
+ const pgConfigs = {
+ development: {
+ database: 'mastodon_development',
+ host: '/var/run/postgresql',
+ max: 10
+ },
-const subs = {}
-
-redisClient.on('pmessage', (_, channel, message) => {
- const callbacks = subs[channel]
-
- log.silly(`New message on channel ${channel}`)
-
- if (!callbacks) {
- return
+ production: {
+ user: process.env.DB_USER || 'mastodon',
+ password: process.env.DB_PASS || '',
+ database: process.env.DB_NAME || 'mastodon_production',
+ host: process.env.DB_HOST || 'localhost',
+ port: process.env.DB_PORT || 5432,
+ max: 10
+ }
}
- callbacks.forEach(callback => callback(message))
-})
+ const app = express()
+ const pgPool = new pg.Pool(pgConfigs[env])
+ const server = http.createServer(app)
+ const wss = new WebSocket.Server({ server })
-redisClient.psubscribe('timeline:*')
+ const redisClient = redis.createClient({
+ host: process.env.REDIS_HOST || '127.0.0.1',
+ port: process.env.REDIS_PORT || 6379,
+ password: process.env.REDIS_PASSWORD
+ })
-const subscribe = (channel, callback) => {
- log.silly(`Adding listener for ${channel}`)
- subs[channel] = subs[channel] || []
- subs[channel].push(callback)
-}
+ const subs = {}
-const unsubscribe = (channel, callback) => {
- log.silly(`Removing listener for ${channel}`)
- subs[channel] = subs[channel].filter(item => item !== callback)
-}
+ redisClient.on('pmessage', (_, channel, message) => {
+ const callbacks = subs[channel]
-const allowCrossDomain = (req, res, next) => {
- res.header('Access-Control-Allow-Origin', '*')
- res.header('Access-Control-Allow-Headers', 'Authorization, Accept, Cache-Control')
- res.header('Access-Control-Allow-Methods', 'GET, OPTIONS')
+ log.silly(`New message on channel ${channel}`)
- next()
-}
-
-const setRequestId = (req, res, next) => {
- req.requestId = uuid.v4()
- res.header('X-Request-Id', req.requestId)
-
- next()
-}
-
-const accountFromToken = (token, req, next) => {
- pgPool.connect((err, client, done) => {
- if (err) {
- next(err)
+ if (!callbacks) {
return
}
- client.query('SELECT oauth_access_tokens.resource_owner_id, users.account_id FROM oauth_access_tokens INNER JOIN users ON oauth_access_tokens.resource_owner_id = users.id WHERE oauth_access_tokens.token = $1 LIMIT 1', [token], (err, result) => {
- done()
+ callbacks.forEach(callback => callback(message))
+ })
+ redisClient.psubscribe('timeline:*')
+
+ const subscribe = (channel, callback) => {
+ log.silly(`Adding listener for ${channel}`)
+ subs[channel] = subs[channel] || []
+ subs[channel].push(callback)
+ }
+
+ const unsubscribe = (channel, callback) => {
+ log.silly(`Removing listener for ${channel}`)
+ subs[channel] = subs[channel].filter(item => item !== callback)
+ }
+
+ const allowCrossDomain = (req, res, next) => {
+ res.header('Access-Control-Allow-Origin', '*')
+ res.header('Access-Control-Allow-Headers', 'Authorization, Accept, Cache-Control')
+ res.header('Access-Control-Allow-Methods', 'GET, OPTIONS')
+
+ next()
+ }
+
+ const setRequestId = (req, res, next) => {
+ req.requestId = uuid.v4()
+ res.header('X-Request-Id', req.requestId)
+
+ next()
+ }
+
+ const accountFromToken = (token, req, next) => {
+ pgPool.connect((err, client, done) => {
if (err) {
next(err)
return
}
- if (result.rows.length === 0) {
- err = new Error('Invalid access token')
- err.statusCode = 401
+ client.query('SELECT oauth_access_tokens.resource_owner_id, users.account_id FROM oauth_access_tokens INNER JOIN users ON oauth_access_tokens.resource_owner_id = users.id WHERE oauth_access_tokens.token = $1 LIMIT 1', [token], (err, result) => {
+ done()
- next(err)
- return
- }
-
- req.accountId = result.rows[0].account_id
-
- next()
- })
- })
-}
-
-const authenticationMiddleware = (req, res, next) => {
- if (req.method === 'OPTIONS') {
- next()
- return
- }
-
- const authorization = req.get('Authorization')
-
- if (!authorization) {
- const err = new Error('Missing access token')
- err.statusCode = 401
-
- next(err)
- return
- }
-
- const token = authorization.replace(/^Bearer /, '')
-
- accountFromToken(token, req, next)
-}
-
-const errorMiddleware = (err, req, res, next) => {
- log.error(req.requestId, err)
- res.writeHead(err.statusCode || 500, { 'Content-Type': 'application/json' })
- res.end(JSON.stringify({ error: err.statusCode ? `${err}` : 'An unexpected error occurred' }))
-}
-
-const placeholders = (arr, shift = 0) => arr.map((_, i) => `$${i + 1 + shift}`).join(', ');
-
-const streamFrom = (id, req, output, attachCloseHandler, needsFiltering = false) => {
- log.verbose(req.requestId, `Starting stream from ${id} for ${req.accountId}`)
-
- const listener = message => {
- const { event, payload, queued_at } = JSON.parse(message)
-
- const transmit = () => {
- const now = new Date().getTime()
- const delta = now - queued_at;
-
- log.silly(req.requestId, `Transmitting for ${req.accountId}: ${event} ${payload} Delay: ${delta}ms`)
- output(event, payload)
- }
-
- // Only messages that may require filtering are statuses, since notifications
- // are already personalized and deletes do not matter
- if (needsFiltering && event === 'update') {
- pgPool.connect((err, client, done) => {
if (err) {
- log.error(err)
+ next(err)
return
}
- const unpackedPayload = JSON.parse(payload)
- const targetAccountIds = [unpackedPayload.account.id].concat(unpackedPayload.mentions.map(item => item.id)).concat(unpackedPayload.reblog ? [unpackedPayload.reblog.account.id] : [])
+ if (result.rows.length === 0) {
+ err = new Error('Invalid access token')
+ err.statusCode = 401
- client.query(`SELECT target_account_id FROM blocks WHERE account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 1)}) UNION SELECT target_account_id FROM mutes WHERE account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 1)})`, [req.accountId].concat(targetAccountIds), (err, result) => {
- done()
+ next(err)
+ return
+ }
+ req.accountId = result.rows[0].account_id
+
+ next()
+ })
+ })
+ }
+
+ const authenticationMiddleware = (req, res, next) => {
+ if (req.method === 'OPTIONS') {
+ next()
+ return
+ }
+
+ const authorization = req.get('Authorization')
+
+ if (!authorization) {
+ const err = new Error('Missing access token')
+ err.statusCode = 401
+
+ next(err)
+ return
+ }
+
+ const token = authorization.replace(/^Bearer /, '')
+
+ accountFromToken(token, req, next)
+ }
+
+ const errorMiddleware = (err, req, res, next) => {
+ log.error(req.requestId, err)
+ res.writeHead(err.statusCode || 500, { 'Content-Type': 'application/json' })
+ res.end(JSON.stringify({ error: err.statusCode ? `${err}` : 'An unexpected error occurred' }))
+ }
+
+ const placeholders = (arr, shift = 0) => arr.map((_, i) => `$${i + 1 + shift}`).join(', ');
+
+ const streamFrom = (id, req, output, attachCloseHandler, needsFiltering = false) => {
+ log.verbose(req.requestId, `Starting stream from ${id} for ${req.accountId}`)
+
+ const listener = message => {
+ const { event, payload, queued_at } = JSON.parse(message)
+
+ const transmit = () => {
+ const now = new Date().getTime()
+ const delta = now - queued_at;
+
+ log.silly(req.requestId, `Transmitting for ${req.accountId}: ${event} ${payload} Delay: ${delta}ms`)
+ output(event, payload)
+ }
+
+ // Only messages that may require filtering are statuses, since notifications
+ // are already personalized and deletes do not matter
+ if (needsFiltering && event === 'update') {
+ pgPool.connect((err, client, done) => {
if (err) {
log.error(err)
return
}
- if (result.rows.length > 0) {
- return
- }
+ const unpackedPayload = JSON.parse(payload)
+ const targetAccountIds = [unpackedPayload.account.id].concat(unpackedPayload.mentions.map(item => item.id)).concat(unpackedPayload.reblog ? [unpackedPayload.reblog.account.id] : [])
- transmit()
+ client.query(`SELECT target_account_id FROM blocks WHERE account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 1)}) UNION SELECT target_account_id FROM mutes WHERE account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 1)})`, [req.accountId].concat(targetAccountIds), (err, result) => {
+ done()
+
+ if (err) {
+ log.error(err)
+ return
+ }
+
+ if (result.rows.length > 0) {
+ return
+ }
+
+ transmit()
+ })
})
- })
- } else {
- transmit()
+ } else {
+ transmit()
+ }
+ }
+
+ subscribe(id, listener)
+ attachCloseHandler(id, listener)
+ }
+
+ // Setup stream output to HTTP
+ const streamToHttp = (req, res) => {
+ res.setHeader('Content-Type', 'text/event-stream')
+ res.setHeader('Transfer-Encoding', 'chunked')
+
+ const heartbeat = setInterval(() => res.write(':thump\n'), 15000)
+
+ req.on('close', () => {
+ log.verbose(req.requestId, `Ending stream for ${req.accountId}`)
+ clearInterval(heartbeat)
+ })
+
+ return (event, payload) => {
+ res.write(`event: ${event}\n`)
+ res.write(`data: ${payload}\n\n`)
}
}
- subscribe(id, listener)
- attachCloseHandler(id, listener)
-}
-
-// Setup stream output to HTTP
-const streamToHttp = (req, res) => {
- res.setHeader('Content-Type', 'text/event-stream')
- res.setHeader('Transfer-Encoding', 'chunked')
-
- const heartbeat = setInterval(() => res.write(':thump\n'), 15000)
-
- req.on('close', () => {
- log.verbose(req.requestId, `Ending stream for ${req.accountId}`)
- clearInterval(heartbeat)
- })
-
- return (event, payload) => {
- res.write(`event: ${event}\n`)
- res.write(`data: ${payload}\n\n`)
+ // Setup stream end for HTTP
+ const streamHttpEnd = req => (id, listener) => {
+ req.on('close', () => {
+ unsubscribe(id, listener)
+ })
}
-}
-// Setup stream end for HTTP
-const streamHttpEnd = req => (id, listener) => {
- req.on('close', () => {
- unsubscribe(id, listener)
- })
-}
+ // Setup stream output to WebSockets
+ const streamToWs = (req, ws) => {
+ const heartbeat = setInterval(() => ws.ping(), 15000)
-// Setup stream output to WebSockets
-const streamToWs = (req, ws) => {
- const heartbeat = setInterval(() => ws.ping(), 15000)
+ ws.on('close', () => {
+ log.verbose(req.requestId, `Ending stream for ${req.accountId}`)
+ clearInterval(heartbeat)
+ })
- ws.on('close', () => {
- log.verbose(req.requestId, `Ending stream for ${req.accountId}`)
- clearInterval(heartbeat)
- })
+ return (event, payload) => {
+ if (ws.readyState !== ws.OPEN) {
+ log.error(req.requestId, 'Tried writing to closed socket')
+ return
+ }
- return (event, payload) => {
- if (ws.readyState !== ws.OPEN) {
- log.error(req.requestId, 'Tried writing to closed socket')
- return
+ ws.send(JSON.stringify({ event, payload }))
}
-
- ws.send(JSON.stringify({ event, payload }))
}
-}
-// Setup stream end for WebSockets
-const streamWsEnd = ws => (id, listener) => {
- ws.on('close', () => {
- unsubscribe(id, listener)
+ // Setup stream end for WebSockets
+ const streamWsEnd = ws => (id, listener) => {
+ ws.on('close', () => {
+ unsubscribe(id, listener)
+ })
+
+ ws.on('error', e => {
+ unsubscribe(id, listener)
+ })
+ }
+
+ app.use(setRequestId)
+ app.use(allowCrossDomain)
+ app.use(authenticationMiddleware)
+ app.use(errorMiddleware)
+
+ app.get('/api/v1/streaming/user', (req, res) => {
+ streamFrom(`timeline:${req.accountId}`, req, streamToHttp(req, res), streamHttpEnd(req))
})
- ws.on('error', e => {
- unsubscribe(id, listener)
+ app.get('/api/v1/streaming/public', (req, res) => {
+ streamFrom('timeline:public', req, streamToHttp(req, res), streamHttpEnd(req), true)
+ })
+
+ app.get('/api/v1/streaming/public/local', (req, res) => {
+ streamFrom('timeline:public:local', req, streamToHttp(req, res), streamHttpEnd(req), true)
+ })
+
+ app.get('/api/v1/streaming/hashtag', (req, res) => {
+ streamFrom(`timeline:hashtag:${req.params.tag}`, req, streamToHttp(req, res), streamHttpEnd(req), true)
+ })
+
+ app.get('/api/v1/streaming/hashtag/local', (req, res) => {
+ streamFrom(`timeline:hashtag:${req.params.tag}:local`, req, streamToHttp(req, res), streamHttpEnd(req), true)
+ })
+
+ wss.on('connection', ws => {
+ const location = url.parse(ws.upgradeReq.url, true)
+ const token = location.query.access_token
+ const req = { requestId: uuid.v4() }
+
+ accountFromToken(token, req, err => {
+ if (err) {
+ log.error(req.requestId, err)
+ ws.close()
+ return
+ }
+
+ switch(location.query.stream) {
+ case 'user':
+ streamFrom(`timeline:${req.accountId}`, req, streamToWs(req, ws), streamWsEnd(ws))
+ break;
+ case 'public':
+ streamFrom('timeline:public', req, streamToWs(req, ws), streamWsEnd(ws), true)
+ break;
+ case 'public:local':
+ streamFrom('timeline:public:local', req, streamToWs(req, ws), streamWsEnd(ws), true)
+ break;
+ case 'hashtag':
+ streamFrom(`timeline:hashtag:${location.query.tag}`, req, streamToWs(req, ws), streamWsEnd(ws), true)
+ break;
+ case 'hashtag:local':
+ streamFrom(`timeline:hashtag:${location.query.tag}:local`, req, streamToWs(req, ws), streamWsEnd(ws), true)
+ break;
+ default:
+ ws.close()
+ }
+ })
+ })
+
+ server.listen(process.env.PORT || 4000, () => {
+ log.level = process.env.LOG_LEVEL || 'verbose'
+ log.info(`Starting streaming API server worker on port ${server.address().port}`)
})
}
-
-app.use(setRequestId)
-app.use(allowCrossDomain)
-app.use(authenticationMiddleware)
-app.use(errorMiddleware)
-
-app.get('/api/v1/streaming/user', (req, res) => {
- streamFrom(`timeline:${req.accountId}`, req, streamToHttp(req, res), streamHttpEnd(req))
-})
-
-app.get('/api/v1/streaming/public', (req, res) => {
- streamFrom('timeline:public', req, streamToHttp(req, res), streamHttpEnd(req), true)
-})
-
-app.get('/api/v1/streaming/public/local', (req, res) => {
- streamFrom('timeline:public:local', req, streamToHttp(req, res), streamHttpEnd(req), true)
-})
-
-app.get('/api/v1/streaming/hashtag', (req, res) => {
- streamFrom(`timeline:hashtag:${req.params.tag}`, req, streamToHttp(req, res), streamHttpEnd(req), true)
-})
-
-app.get('/api/v1/streaming/hashtag/local', (req, res) => {
- streamFrom(`timeline:hashtag:${req.params.tag}:local`, req, streamToHttp(req, res), streamHttpEnd(req), true)
-})
-
-wss.on('connection', ws => {
- const location = url.parse(ws.upgradeReq.url, true)
- const token = location.query.access_token
- const req = { requestId: uuid.v4() }
-
- accountFromToken(token, req, err => {
- if (err) {
- log.error(req.requestId, err)
- ws.close()
- return
- }
-
- switch(location.query.stream) {
- case 'user':
- streamFrom(`timeline:${req.accountId}`, req, streamToWs(req, ws), streamWsEnd(ws))
- break;
- case 'public':
- streamFrom('timeline:public', req, streamToWs(req, ws), streamWsEnd(ws), true)
- break;
- case 'public:local':
- streamFrom('timeline:public:local', req, streamToWs(req, ws), streamWsEnd(ws), true)
- break;
- case 'hashtag':
- streamFrom(`timeline:hashtag:${location.query.tag}`, req, streamToWs(req, ws), streamWsEnd(ws), true)
- break;
- case 'hashtag:local':
- streamFrom(`timeline:hashtag:${location.query.tag}:local`, req, streamToWs(req, ws), streamWsEnd(ws), true)
- break;
- default:
- ws.close()
- }
- })
-})
-
-server.listen(process.env.PORT || 4000, () => {
- log.level = process.env.LOG_LEVEL || 'verbose'
- log.info(`Starting streaming API server on port ${server.address().port}`)
-})