summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md10
-rw-r--r--config/config.exs1
-rw-r--r--config/description.exs11
-rw-r--r--docs/administration/CLI_tasks/instance.md3
-rw-r--r--docs/administration/updating.md4
-rw-r--r--docs/configuration/cheatsheet.md10
-rw-r--r--docs/development/API/admin_api.md114
-rw-r--r--docs/development/API/differences_in_mastoapi_responses.md2
-rw-r--r--docs/development/setting_up_a_gitlab_runner.md9
-rw-r--r--docs/installation/optional/media_graphics_packages.md15
-rw-r--r--installation/caddyfile-pleroma.example31
-rw-r--r--lib/mix/tasks/pleroma/instance.ex44
-rw-r--r--lib/pleroma/announcement.ex160
-rw-r--r--lib/pleroma/announcement_read_relationship.ex55
-rw-r--r--lib/pleroma/application_requirements.ex3
-rw-r--r--lib/pleroma/config/deprecation_warnings.ex40
-rw-r--r--lib/pleroma/constants.ex6
-rw-r--r--lib/pleroma/ecto_type/activity_pub/object_validators/mime.ex25
-rw-r--r--lib/pleroma/emails/user_email.ex461
-rw-r--r--lib/pleroma/http/adapter_helper/hackney.ex4
-rw-r--r--lib/pleroma/notification.ex8
-rw-r--r--lib/pleroma/reverse_proxy/client/hackney.ex1
-rw-r--r--lib/pleroma/upload.ex27
-rw-r--r--lib/pleroma/upload/filter/exiftool/read_description.ex49
-rw-r--r--lib/pleroma/upload/filter/exiftool/strip_location.ex (renamed from lib/pleroma/upload/filter/exiftool.ex)2
-rw-r--r--lib/pleroma/user.ex63
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub_controller.ex11
-rw-r--r--lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex21
-rw-r--r--lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex18
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex12
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex6
-rw-r--r--lib/pleroma/web/admin_api/controllers/announcement_controller.ex83
-rw-r--r--lib/pleroma/web/admin_api/views/announcement_view.ex15
-rw-r--r--lib/pleroma/web/api_spec/operations/account_operation.ex5
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/announcement_operation.ex165
-rw-r--r--lib/pleroma/web/api_spec/operations/announcement_operation.ex57
-rw-r--r--lib/pleroma/web/api_spec/schemas/announcement.ex45
-rw-r--r--lib/pleroma/web/feed/feed_view.ex1
-rw-r--r--lib/pleroma/web/gettext.ex192
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/account_controller.ex1
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/announcement_controller.ex60
-rw-r--r--lib/pleroma/web/mastodon_api/views/announcement_view.ex15
-rw-r--r--lib/pleroma/web/mastodon_api/views/instance_view.ex1
-rw-r--r--lib/pleroma/web/o_auth/mfa_view.ex1
-rw-r--r--lib/pleroma/web/o_auth/o_auth_view.ex2
-rw-r--r--lib/pleroma/web/plugs/cache.ex12
-rw-r--r--lib/pleroma/web/plugs/set_locale_plug.ex62
-rw-r--r--lib/pleroma/web/router.ex9
-rw-r--r--lib/pleroma/web/templates/email/digest.html.eex10
-rw-r--r--lib/pleroma/web/templates/feed/feed/tag.atom.eex4
-rw-r--r--lib/pleroma/web/templates/feed/feed/tag.rss.eex2
-rw-r--r--lib/pleroma/web/templates/layout/app.html.eex2
-rw-r--r--lib/pleroma/web/templates/layout/email.html.eex4
-rw-r--r--lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex2
-rw-r--r--lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex2
-rw-r--r--lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex8
-rw-r--r--lib/pleroma/web/templates/o_auth/mfa/totp.html.eex8
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex2
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex4
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex4
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex4
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/register.html.eex18
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/show.html.eex20
-rw-r--r--lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex2
-rw-r--r--lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex2
-rw-r--r--lib/pleroma/web/templates/twitter_api/password/reset.html.eex6
-rw-r--r--lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex8
-rw-r--r--lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex4
-rw-r--r--lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex6
-rw-r--r--lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex8
-rw-r--r--lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex6
-rw-r--r--lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex5
-rw-r--r--lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex8
-rw-r--r--lib/pleroma/web/twitter_api/twitter_api.ex6
-rw-r--r--lib/pleroma/web/twitter_api/views/password_view.ex1
-rw-r--r--lib/pleroma/web/twitter_api/views/remote_follow_view.ex7
-rw-r--r--lib/pleroma/web/twitter_api/views/util_view.ex1
-rw-r--r--lib/pleroma/web/views/email_view.ex1
-rw-r--r--lib/pleroma/web/views/mailer/subscription_view.ex1
-rw-r--r--mix.exs15
-rw-r--r--mix.lock6
-rw-r--r--priv/gettext/default.pot185
-rw-r--r--priv/gettext/en_test/LC_MESSAGES/default.po186
-rw-r--r--priv/gettext/en_test/LC_MESSAGES/errors.po557
-rw-r--r--priv/gettext/en_test/LC_MESSAGES/posix_errors.po153
-rw-r--r--priv/gettext/en_test/LC_MESSAGES/static_pages.po529
-rw-r--r--priv/gettext/errors.pot301
-rw-r--r--priv/gettext/posix_errors.pot88
-rw-r--r--priv/gettext/static_pages.pot513
-rw-r--r--priv/gettext/zh_Hans/LC_MESSAGES/default.po186
-rw-r--r--priv/gettext/zh_Hans/LC_MESSAGES/errors.po385
-rw-r--r--priv/gettext/zh_Hans/LC_MESSAGES/posix_errors.po153
-rw-r--r--priv/gettext/zh_Hans/LC_MESSAGES/static_pages.po547
-rw-r--r--priv/repo/migrations/20220220135625_upload_filter_exiftool_to_exiftool_strip_location.exs37
-rw-r--r--priv/repo/migrations/20220302013920_add_language_to_users.exs9
-rw-r--r--priv/repo/migrations/20220308012601_create_announcements.exs26
-rw-r--r--priv/repo/migrations/20220506175506_add_index_hotspots.exs17
-rw-r--r--priv/repo/migrations/20220602052233_change_report_notes_content_to_text.exs16
-rw-r--r--test/fixtures/image_with_caption-abstract.jpgbin0 -> 697 bytes
-rw-r--r--test/fixtures/image_with_imagedescription_and_caption-abstract.jpgbin0 -> 823 bytes
-rw-r--r--test/fixtures/image_with_imagedescription_and_caption-abstract_whitespaces.jpgbin0 -> 785 bytes
-rw-r--r--test/fixtures/image_with_no_description.jpgbin0 -> 631 bytes
-rw-r--r--test/mix/tasks/pleroma/digest_test.exs8
-rw-r--r--test/mix/tasks/pleroma/instance_test.exs9
-rw-r--r--test/pleroma/announcement_read_relationship_test.exs40
-rw-r--r--test/pleroma/announcement_test.exs98
-rw-r--r--test/pleroma/config/deprecation_warnings_test.exs56
-rw-r--r--test/pleroma/config_db_test.exs5
-rw-r--r--test/pleroma/docs/generator_test.exs4
-rw-r--r--test/pleroma/emails/user_email_test.exs12
-rw-r--r--test/pleroma/notification_test.exs19
-rw-r--r--test/pleroma/report_note_test.exs23
-rw-r--r--test/pleroma/upload/filter/exiftool/read_description_test.exs117
-rw-r--r--test/pleroma/upload/filter/exiftool/strip_location_test.exs (renamed from test/pleroma/upload/filter/exiftool_test.exs)6
-rw-r--r--test/pleroma/user_test.exs46
-rw-r--r--test/pleroma/web/activity_pub/activity_pub_controller_test.exs24
-rw-r--r--test/pleroma/web/activity_pub/mrf/anti_followbot_policy_test.exs57
-rw-r--r--test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs16
-rw-r--r--test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs40
-rw-r--r--test/pleroma/web/admin_api/controllers/announcement_controller_test.exs281
-rw-r--r--test/pleroma/web/admin_api/controllers/report_controller_test.exs4
-rw-r--r--test/pleroma/web/gettext_test.exs173
-rw-r--r--test/pleroma/web/mastodon_api/controllers/account_controller_test.exs70
-rw-r--r--test/pleroma/web/mastodon_api/controllers/announcement_controller_test.exs169
-rw-r--r--test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs1
-rw-r--r--test/pleroma/web/plugs/cache_test.exs18
-rw-r--r--test/pleroma/web/plugs/set_locale_plug_test.exs124
-rw-r--r--test/pleroma/web/twitter_api/remote_follow_controller_test.exs45
-rw-r--r--test/support/factory.ex12
129 files changed, 6750 insertions, 754 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9e6e0fdf2..f1beb0cd0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed
- Allow users to remove their emails if instance does not need email to register
+- Uploadfilter `Pleroma.Upload.Filter.Exiftool` has been renamed to `Pleroma.Upload.Filter.Exiftool.StripLocation`
### Added
- `activeMonth` and `activeHalfyear` fields in NodeInfo usage.users object
@@ -29,6 +30,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- MastoAPI: Support for `birthday` and `show_birthday` field in `/api/v1/accounts/update_credentials`.
- Configuration: Add `birthday_required` and `birthday_min_age` settings to provide a way to require users to enter their birth date.
- PleromaAPI: Add `GET /api/v1/pleroma/birthdays` API endpoint
+- Make backend-rendered pages translatable. This includes emails. Pages returned as a HTTP response are translated using the language specified in the `userLanguage` cookie, or the `Accept-Language` header. Emails are translated using the `language` field when registering. This language can be changed by `PATCH /api/v1/accounts/update_credentials` with the `language` field.
+- Uploadfilter `Pleroma.Upload.Filter.Exiftool.ReadDescription` returns description values to the FE so they can pre fill the image description field
### Fixed
- Subscription(Bell) Notifications: Don't create from Pipeline Ingested replies
@@ -47,6 +50,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Removed
+## 2.4.3 - 2022-05-06
+
+### Security
+- Private `/objects/` and `/activities/` leaking if cached by authenticated user
+- SweetXML library DTD bomb
+
## 2.4.2 - 2022-01-10
### Fixed
@@ -90,6 +99,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Improved Twittercard and OpenGraph meta tag generation including thumbnails and image dimension metadata when available.
- AdminAPI: sort users so the newest are at the top.
- ActivityPub Client-to-Server(C2S): Limitation on the type of Activity/Object are lifted as they are now passed through ObjectValidators
+- MRF (`AntiFollowbotPolicy`): Bot accounts are now also considered followbots. Users can still allow bots to follow them by first following the bot.
### Added
diff --git a/config/config.exs b/config/config.exs
index 6a5acda09..0fc959807 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -187,6 +187,7 @@ config :pleroma, :instance,
email: "example@example.com",
notify_email: "noreply@example.com",
description: "Pleroma: An efficient and flexible fediverse server",
+ short_description: "",
background_image: "/images/city.jpg",
instance_thumbnail: "/instance/thumbnail.jpeg",
limit: 5_000,
diff --git a/config/description.exs b/config/description.exs
index 704af8f68..b29348edf 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -537,6 +537,15 @@ config :pleroma, :config_description, [
]
},
%{
+ key: :short_description,
+ type: :string,
+ description:
+ "Shorter version of instance description. It can be seen on `/api/v1/instance`",
+ suggestions: [
+ "Cool instance"
+ ]
+ },
+ %{
key: :limit,
type: :integer,
description: "Posts character limit (CW/Subject included in the counter)",
@@ -2726,7 +2735,7 @@ config :pleroma, :config_description, [
key: :versions,
type: {:list, :atom},
description: "List of TLS version to use",
- suggestions: [:tlsv1, ":tlsv1.1", ":tlsv1.2"]
+ suggestions: [:tlsv1, ":tlsv1.1", ":tlsv1.2", ":tlsv1.3"]
}
]
}
diff --git a/docs/administration/CLI_tasks/instance.md b/docs/administration/CLI_tasks/instance.md
index 982b22bf3..88509cf5b 100644
--- a/docs/administration/CLI_tasks/instance.md
+++ b/docs/administration/CLI_tasks/instance.md
@@ -37,7 +37,8 @@ If any of the options are left unspecified, you will be prompted interactively.
- `--static-dir <path>` - the directory custom public files should be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)
- `--listen-ip <ip>` - the ip the app should listen to, defaults to 127.0.0.1
- `--listen-port <port>` - the port the app should listen to, defaults to 4000
-- `--strip-uploads <Y|N>` - use ExifTool to strip uploads of sensitive location data
+- `--strip-uploads-location <Y|N>` - use ExifTool to strip uploads of sensitive location data
+- `--read-uploads-description <Y|N>` - use ExifTool to read image descriptions from uploads
- `--anonymize-uploads <Y|N>` - randomize uploaded filenames
- `--dedupe-uploads <Y|N>` - store files based on their hash to reduce data storage requirements if duplicates are uploaded with different filenames
- `--skip-release-env` - skip generation the release environment file
diff --git a/docs/administration/updating.md b/docs/administration/updating.md
index ef2c9218c..01d3b9b0e 100644
--- a/docs/administration/updating.md
+++ b/docs/administration/updating.md
@@ -17,11 +17,11 @@ su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate"
## For from source installations (using git)
1. Go to the working directory of Pleroma (default is `/opt/pleroma`)
-2. Run `git pull`. This pulls the latest changes from upstream.
+2. Run `git pull` [^1]. This pulls the latest changes from upstream.
3. Run `mix deps.get` [^1]. This pulls in any new dependencies.
4. Stop the Pleroma service.
5. Run `mix ecto.migrate` [^1] [^2]. This task performs database migrations, if there were any.
6. Start the Pleroma service.
-[^1]: Depending on which install guide you followed (for example on Debian/Ubuntu), you want to run `mix` tasks as `pleroma` user by adding `sudo -Hu pleroma` before the command.
+[^1]: Depending on which install guide you followed (for example on Debian/Ubuntu), you want to run `git` and `mix` tasks as `pleroma` user by adding `sudo -Hu pleroma` before the command.
[^2]: Prefix with `MIX_ENV=prod` to run it using the production config file.
diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
index 4dacdc68c..6e13b9622 100644
--- a/docs/configuration/cheatsheet.md
+++ b/docs/configuration/cheatsheet.md
@@ -18,6 +18,7 @@ To add configuration to your config file, you can copy it from the base config.
* `email`: Email used to reach an Administrator/Moderator of the instance.
* `notify_email`: Email used for notifications.
* `description`: The instance’s description, can be seen in nodeinfo and ``/api/v1/instance``.
+* `short_description`: Shorter version of instance description, can be seen on ``/api/v1/instance``.
* `limit`: Posts character limit (CW/Subject included in the counter).
* `description_limit`: The character limit for image descriptions.
* `remote_limit`: Hard character limit beyond which remote posts will be dropped.
@@ -125,6 +126,7 @@ To add configuration to your config file, you can copy it from the base config.
* `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Sets a default expiration on all posts made by users of the local instance. Requires `Pleroma.Workers.PurgeExpiredActivity` to be enabled for processing the scheduled delections.
* `Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy`: Makes all bot posts to disappear from public timelines.
* `Pleroma.Web.ActivityPub.MRF.FollowBotPolicy`: Automatically follows newly discovered users from the specified bot account. Local accounts, locked accounts, and users with "#nobot" in their bio are respected and excluded from being followed.
+ * `Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy`: Drops follow requests from followbots. Users can still allow bots to follow them by first following the bot.
* `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)).
* `Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent`: Forces every mentioned user to be reflected in the post content.
* `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
@@ -626,12 +628,18 @@ This filter replaces the filename (not the path) of an upload. For complete obfu
No specific configuration.
-#### Pleroma.Upload.Filter.Exiftool
+#### Pleroma.Upload.Filter.Exiftool.StripLocation
This filter only strips the GPS and location metadata with Exiftool leaving color profiles and attributes intact.
No specific configuration.
+#### Pleroma.Upload.Filter.Exiftool.ReadDescription
+
+This filter reads the ImageDescription and iptc:Caption-Abstract fields with Exiftool so clients can prefill the media description field.
+
+No specific configuration.
+
#### Pleroma.Upload.Filter.Mogrify
* `args`: List of actions for the `mogrify` command like `"strip"` or `["strip", "auto-orient", {"implode", "1"}]`.
diff --git a/docs/development/API/admin_api.md b/docs/development/API/admin_api.md
index f14081893..c46f83839 100644
--- a/docs/development/API/admin_api.md
+++ b/docs/development/API/admin_api.md
@@ -1636,3 +1636,117 @@ Returns the content of the document
"error": "Could not install frontend"
}
```
+
+## `GET /api/v1/pleroma/admin/announcements`
+
+### List announcements
+
+- Params: `offset`, `limit`
+
+- Response: JSON, list of announcements
+
+```json
+[
+ {
+ "id": "AHDp0GBdRn1EPN5HN2",
+ "content": "some content",
+ "starts_at": null,
+ "ends_at": null,
+ "all_day": false,
+ "published_at": "2022-03-09T02:13:05",
+ "reactions": [],
+ "statuses": [],
+ "tags": [],
+ "emojis": [],
+ "updated_at": "2022-03-09T02:13:05"
+ }
+]
+```
+
+Note that this differs from the Mastodon API variant: Mastodon API only returns *active* announcements, while this returns all.
+
+## `GET /api/v1/pleroma/admin/announcements/:id`
+
+### Display one announcement
+
+- Response: JSON, one announcement
+
+```json
+{
+ "id": "AHDp0GBdRn1EPN5HN2",
+ "content": "some content",
+ "starts_at": null,
+ "ends_at": null,
+ "all_day": false,
+ "published_at": "2022-03-09T02:13:05",
+ "reactions": [],
+ "statuses": [],
+ "tags": [],
+ "emojis": [],
+ "updated_at": "2022-03-09T02:13:05"
+}
+```
+
+## `POST /api/v1/pleroma/admin/announcements`
+
+### Create an announcement
+
+- Params:
+ - `content`: string, required, announcement content
+ - `starts_at`: datetime, optional, default to null, the time when the announcement will become active (displayed to users); if it is null, the announcement will be active immediately
+ - `ends_at`: datetime, optional, default to null, the time when the announcement will become inactive (no longer displayed to users); if it is null, the announcement will be active until an admin deletes it
+ - `all_day`: boolean, optional, default to false, tells the client whether to only display dates for `starts_at` and `ends_at`
+
+- Response: JSON, created announcement
+
+```json
+{
+ "id": "AHDp0GBdRn1EPN5HN2",
+ "content": "some content",
+ "starts_at": null,
+ "ends_at": null,
+ "all_day": false,
+ "published_at": "2022-03-09T02:13:05",
+ "reactions": [],
+ "statuses": [],
+ "tags": [],
+ "emojis": [],
+ "updated_at": "2022-03-09T02:13:05"
+}
+```
+
+## `PATCH /api/v1/pleroma/admin/announcements/:id`
+
+### Change an announcement
+
+- Params: same as `POST /api/v1/pleroma/admin/announcements`, except no param is required.
+
+- Updates the announcement according to params. Missing params are kept as-is.
+
+- Response: JSON, updated announcement
+
+```json
+{
+ "id": "AHDp0GBdRn1EPN5HN2",
+ "content": "some content",
+ "starts_at": null,
+ "ends_at": null,
+ "all_day": false,
+ "published_at": "2022-03-09T02:13:05",
+ "reactions": [],
+ "statuses": [],
+ "tags": [],
+ "emojis": [],
+ "updated_at": "2022-03-09T02:13:05"
+}
+```
+
+## `DELETE /api/v1/pleroma/admin/announcements/:id`
+
+### Delete an announcement
+
+- Response: JSON, empty object
+
+```json
+{}
+```
diff --git a/docs/development/API/differences_in_mastoapi_responses.md b/docs/development/API/differences_in_mastoapi_responses.md
index 0e6bcb79b..73c46fff8 100644
--- a/docs/development/API/differences_in_mastoapi_responses.md
+++ b/docs/development/API/differences_in_mastoapi_responses.md
@@ -241,6 +241,7 @@ Additional parameters can be added to the JSON body/Form data:
- `discoverable` - if true, external services (search bots) etc. are allowed to index / list the account (regardless of this setting, user will still appear in regular search results).
- `actor_type` - the type of this account.
- `accepts_chat_messages` - if false, this account will reject all chat messages.
+- `language` - user's preferred language for receiving emails (digest, confirmation, etc.)
All images (avatar, banner and background) can be reset to the default by sending an empty string ("") instead of a file.
@@ -292,6 +293,7 @@ Has these additional parameters (which are the same as in Pleroma-API):
- `captcha_token`: optional, contains provider-specific captcha token
- `captcha_answer_data`: optional, contains provider-specific captcha data
- `token`: invite token required when the registrations aren't public.
+- `language`: optional, user's preferred language for receiving emails (digest, confirmation, etc.), default to the language set in the `userLanguage` cookies or `Accept-Language` header.
## Instance
diff --git a/docs/development/setting_up_a_gitlab_runner.md b/docs/development/setting_up_a_gitlab_runner.md
new file mode 100644
index 000000000..88beb82f2
--- /dev/null
+++ b/docs/development/setting_up_a_gitlab_runner.md
@@ -0,0 +1,9 @@
+# Setting up a Gitlab-runner
+
+When you push changes, a pipeline will start some automated jobs. These are done with so called [runners](https://docs.gitlab.com/runner/), services that run somewhere on a server and run these automated jobs. These jobs typically run tests and should pass. If not, you probably need to fix something.
+
+Generally, Pleroma provides a runner, so you don't need to set up your own. However, if for whatever reason you want to set up your own, here's some high level instructions.
+
+1. We use docker to run the jobs, so you should install that. For Debian, you need to allow non-free packages in the [source list](https://wiki.debian.org/SourcesList). Then you can install docker with `apt install docker-compose`.
+2. You can [install](https://docs.gitlab.com/runner/install/index.html) and [configure](https://docs.gitlab.com/runner/register/index.html) a Gitlab-runner. It's probably easiest to install from the packages, but there are other options as well.
+3. When registering the runner, you'll need some values. You can find them in the project under your own name. Choose "Settings", "CI/CD", and then expand "Runners". For executor you can choose "docker". For default image, you can use the image used in <https://git.pleroma.social/pleroma/pleroma/-/blob/develop/.gitlab-ci.yml#L1> (although it shouldn't matter much).
diff --git a/docs/installation/optional/media_graphics_packages.md b/docs/installation/optional/media_graphics_packages.md
index cb3d71188..de402d1c4 100644
--- a/docs/installation/optional/media_graphics_packages.md
+++ b/docs/installation/optional/media_graphics_packages.md
@@ -1,9 +1,9 @@
# Optional software packages needed for specific functionality
For specific Pleroma functionality (which is disabled by default) some or all of the below packages are required:
- * `ImageMagic`
- * `ffmpeg`
- * `exiftool`
+ * `ImageMagic`
+ * `ffmpeg`
+ * `exiftool`
Please refer to documentation in `docs/installation` on how to install them on specific OS.
@@ -14,19 +14,20 @@ Note: the packages are not required with the current default settings of Pleroma
`ImageMagick` is a set of tools to create, edit, compose, or convert bitmap images.
It is required for the following Pleroma features:
- * `Pleroma.Upload.Filters.Mogrify`, `Pleroma.Upload.Filters.Mogrifun` upload filters (related config: `Plaroma.Upload/filters` in `config/config.exs`)
- * Media preview proxy for still images (related config: `media_preview_proxy/enabled` in `config/config.exs`)
+ * `Pleroma.Upload.Filters.Mogrify`, `Pleroma.Upload.Filters.Mogrifun` upload filters (related config: `Plaroma.Upload/filters` in `config/config.exs`)
+ * Media preview proxy for still images (related config: `media_preview_proxy/enabled` in `config/config.exs`)
## `ffmpeg`
`ffmpeg` is software to record, convert and stream audio and video.
It is required for the following Pleroma features:
- * Media preview proxy for videos (related config: `media_preview_proxy/enabled` in `config/config.exs`)
+ * Media preview proxy for videos (related config: `media_preview_proxy/enabled` in `config/config.exs`)
## `exiftool`
`exiftool` is media files metadata reader/writer.
It is required for the following Pleroma features:
- * `Pleroma.Upload.Filters.Exiftool` upload filter (related config: `Plaroma.Upload/filters` in `config/config.exs`)
+ * `Pleroma.Upload.Filters.Exiftool.StripLocation` upload filter (related config: `Plaroma.Upload/filters` in `config/config.exs`)
+ * `Pleroma.Upload.Filters.Exiftool.ReadDescription` upload filter (related config: `Plaroma.Upload/filters` in `config/config.exs`)
diff --git a/installation/caddyfile-pleroma.example b/installation/caddyfile-pleroma.example
index 7985d9c67..cc7dda011 100644
--- a/installation/caddyfile-pleroma.example
+++ b/installation/caddyfile-pleroma.example
@@ -5,34 +5,13 @@
# 2. Copy this section into your Caddyfile and restart Caddy.
example.tld {
- log /var/log/caddy/pleroma_access.log
- errors /var/log/caddy/pleroma_error.log
+ log {
+ output file /var/log/caddy/pleroma.log
+ }
- gzip
+ encode gzip
# this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only
# and `localhost.` resolves to [::0] on some systems: see issue #930
- proxy / 127.0.0.1:4000 {
- websocket
- transparent
- }
-
- tls {
- # Remove the rest of the lines in here, if you want to support older devices
- key_type p256
- ciphers ECDHE-ECDSA-WITH-CHACHA20-POLY1305 ECDHE-RSA-WITH-CHACHA20-POLY1305 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-GCM-SHA256
- }
-
- # If you do not want to use the mediaproxy function, remove these lines.
- # To use this directive, you need the http.cache plugin for Caddy.
- cache {
- match_path /media
- default_max_age 720m
- }
-
- cache {
- match_path /proxy
- default_max_age 720m
- }
- # Stop removing lines here.
+ reverse_proxy 127.0.0.1:4000
}
diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex
index f292fc762..5c93f19ff 100644
--- a/lib/mix/tasks/pleroma/instance.ex
+++ b/lib/mix/tasks/pleroma/instance.ex
@@ -34,7 +34,8 @@ defmodule Mix.Tasks.Pleroma.Instance do
static_dir: :string,
listen_ip: :string,
listen_port: :string,
- strip_uploads: :string,
+ strip_uploads_location: :string,
+ read_uploads_description: :string,
anonymize_uploads: :string,
dedupe_uploads: :string
],
@@ -161,7 +162,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
)
|> Path.expand()
- {strip_uploads_message, strip_uploads_default} =
+ {strip_uploads_location_message, strip_uploads_location_default} =
if Pleroma.Utils.command_available?("exiftool") do
{"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as installed. (y/n)",
"y"}
@@ -170,12 +171,29 @@ defmodule Mix.Tasks.Pleroma.Instance do
"n"}
end
- strip_uploads =
+ strip_uploads_location =
get_option(
options,
- :strip_uploads,
- strip_uploads_message,
- strip_uploads_default
+ :strip_uploads_location,
+ strip_uploads_location_message,
+ strip_uploads_location_default
+ ) === "y"
+
+ {read_uploads_description_message, read_uploads_description_default} =
+ if Pleroma.Utils.command_available?("exiftool") do
+ {"Do you want to read data from uploaded files so clients can use it to prefill fields like image description? This requires exiftool, it was detected as installed. (y/n)",
+ "y"}
+ else
+ {"Do you want to read data from uploaded files so clients can use it to prefill fields like image description? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)",
+ "n"}
+ end
+
+ read_uploads_description =
+ get_option(
+ options,
+ :read_uploads_description,
+ read_uploads_description_message,
+ read_uploads_description_default
) === "y"
anonymize_uploads =
@@ -229,7 +247,8 @@ defmodule Mix.Tasks.Pleroma.Instance do
listen_port: listen_port,
upload_filters:
upload_filters(%{
- strip: strip_uploads,
+ strip_location: strip_uploads_location,
+ read_description: read_uploads_description,
anonymize: anonymize_uploads,
dedupe: dedupe_uploads
})
@@ -297,13 +316,20 @@ defmodule Mix.Tasks.Pleroma.Instance do
defp upload_filters(filters) when is_map(filters) do
enabled_filters =
- if filters.strip do
- [Pleroma.Upload.Filter.Exiftool]
+ if filters.strip_location do
+ [Pleroma.Upload.Filter.Exiftool.StripLocation]
else
[]
end
enabled_filters =
+ if filters.read_description do
+ enabled_filters ++ [Pleroma.Upload.Filter.Exiftool.ReadDescription]
+ else
+ enabled_filters
+ end
+
+ enabled_filters =
if filters.anonymize do
enabled_filters ++ [Pleroma.Upload.Filter.AnonymizeFilename]
else
diff --git a/lib/pleroma/announcement.ex b/lib/pleroma/announcement.ex
new file mode 100644
index 000000000..d97c5e728
--- /dev/null
+++ b/lib/pleroma/announcement.ex
@@ -0,0 +1,160 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Announcement do
+ use Ecto.Schema
+
+ import Ecto.Changeset, only: [cast: 3, validate_required: 2]
+ import Ecto.Query
+
+ alias Pleroma.AnnouncementReadRelationship
+ alias Pleroma.Repo
+
+ @type t :: %__MODULE__{}
+ @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
+
+ schema "announcements" do
+ field(:data, :map)
+ field(:starts_at, :utc_datetime)
+ field(:ends_at, :utc_datetime)
+ field(:rendered, :map)
+
+ timestamps(type: :utc_datetime)
+ end
+
+ def change(struct, params \\ %{}) do
+ struct
+ |> cast(validate_params(struct, params), [:data, :starts_at, :ends_at, :rendered])
+ |> validate_required([:data])
+ end
+
+ defp validate_params(struct, params) do
+ base_data =
+ %{
+ "content" => "",
+ "all_day" => false
+ }
+ |> Map.merge((struct && struct.data) || %{})
+
+ merged_data =
+ Map.merge(base_data, params.data)
+ |> Map.take(["content", "all_day"])
+
+ params
+ |> Map.merge(%{data: merged_data})
+ |> add_rendered_properties()
+ end
+
+ def add_rendered_properties(params) do
+ {content_html, _, _} =
+ Pleroma.Web.CommonAPI.Utils.format_input(params.data["content"], "text/plain",
+ mentions_format: :full
+ )
+
+ rendered = %{
+ "content" => content_html
+ }
+
+ params
+ |> Map.put(:rendered, rendered)
+ end
+
+ def add(params) do
+ changeset = change(%__MODULE__{}, params)
+
+ Repo.insert(changeset)
+ end
+
+ def update(announcement, params) do
+ changeset = change(announcement, params)
+
+ Repo.update(changeset)
+ end
+
+ def list_all do
+ __MODULE__
+ |> Repo.all()
+ end
+
+ def list_paginated(%{limit: limited_number, offset: offset_number}) do
+ __MODULE__
+ |> limit(^limited_number)
+ |> offset(^offset_number)
+ |> Repo.all()
+ end
+
+ def get_by_id(id) do
+ Repo.get_by(__MODULE__, id: id)
+ end
+
+ def delete_by_id(id) do
+ with announcement when not is_nil(announcement) <- get_by_id(id),
+ {:ok, _} <- Repo.delete(announcement) do
+ :ok
+ else
+ _ ->
+ :error
+ end
+ end
+
+ def read_by?(announcement, user) do
+ AnnouncementReadRelationship.exists?(user, announcement)
+ end
+
+ def mark_read_by(announcement, user) do
+ AnnouncementReadRelationship.mark_read(user, announcement)
+ end
+
+ def render_json(announcement, opts \\ []) do
+ extra_params =
+ case Keyword.fetch(opts, :for) do
+ {:ok, user} when not is_nil(user) ->
+ %{read: read_by?(announcement, user)}
+
+ _ ->
+ %{}
+ end
+
+ admin_extra_params =
+ case Keyword.fetch(opts, :admin) do
+ {:ok, true} ->
+ %{pleroma: %{raw_content: announcement.data["content"]}}
+
+ _ ->
+ %{}
+ end
+
+ base = %{
+ id: announcement.id,
+ content: announcement.rendered["content"],
+ starts_at: announcement.starts_at,
+ ends_at: announcement.ends_at,
+ all_day: announcement.data["all_day"],
+ published_at: announcement.inserted_at,
+ updated_at: announcement.updated_at,
+ mentions: [],
+ statuses: [],
+ tags: [],
+ emojis: [],
+ reactions: []
+ }
+
+ base
+ |> Map.merge(extra_params)
+ |> Map.merge(admin_extra_params)
+ end
+
+ # "visible" means:
+ # starts_at < time < ends_at
+ def list_all_visible_when(time) do
+ __MODULE__
+ |> where([a], is_nil(a.starts_at) or a.starts_at < ^time)
+ |> where([a], is_nil(a.ends_at) or a.ends_at > ^time)
+ |> Repo.all()
+ end
+
+ def list_all_visible do
+ list_all_visible_when(DateTime.now("Etc/UTC") |> elem(1))
+ end
+end
diff --git a/lib/pleroma/announcement_read_relationship.ex b/lib/pleroma/announcement_read_relationship.ex
new file mode 100644
index 000000000..9b64404ce
--- /dev/null
+++ b/lib/pleroma/announcement_read_relationship.ex
@@ -0,0 +1,55 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.AnnouncementReadRelationship do
+ use Ecto.Schema
+
+ import Ecto.Changeset
+
+ alias FlakeId.Ecto.CompatType
+ alias Pleroma.Announcement
+ alias Pleroma.Repo
+ alias Pleroma.User
+
+ @type t :: %__MODULE__{}
+
+ schema "announcement_read_relationships" do
+ belongs_to(:user, User, type: CompatType)
+ belongs_to(:announcement, Announcement, type: CompatType)
+
+ timestamps(updated_at: false)
+ end
+
+ def mark_read(user, announcement) do
+ %__MODULE__{}
+ |> cast(%{user_id: user.id, announcement_id: announcement.id}, [:user_id, :announcement_id])
+ |> validate_required([:user_id, :announcement_id])
+ |> foreign_key_constraint(:user_id)
+ |> foreign_key_constraint(:announcement_id)
+ |> unique_constraint([:user_id, :announcement_id])
+ |> Repo.insert()
+ end
+
+ def mark_unread(user, announcement) do
+ with relationship <- get(user, announcement),
+ {:exists, true} <- {:exists, not is_nil(relationship)},
+ {:ok, _} <- Repo.delete(relationship) do
+ :ok
+ else
+ {:exists, false} ->
+ :ok
+
+ _ ->
+ :error
+ end
+ end
+
+ def get(user, announcement) do
+ Repo.get_by(__MODULE__, user_id: user.id, announcement_id: announcement.id)
+ end
+
+ def exists?(user, announcement) do
+ not is_nil(get(user, announcement))
+ end
+end
diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex
index 06d388694..44b1c1705 100644
--- a/lib/pleroma/application_requirements.ex
+++ b/lib/pleroma/application_requirements.ex
@@ -164,7 +164,8 @@ defmodule Pleroma.ApplicationRequirements do
defp check_system_commands!(:ok) do
filter_commands_statuses = [
- check_filter(Pleroma.Upload.Filter.Exiftool, "exiftool"),
+ check_filter(Pleroma.Upload.Filter.Exiftool.StripLocation, "exiftool"),
+ check_filter(Pleroma.Upload.Filter.Exiftool.ReadDescription, "exiftool"),
check_filter(Pleroma.Upload.Filter.Mogrify, "mogrify"),
check_filter(Pleroma.Upload.Filter.Mogrifun, "mogrify"),
check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "mogrify"),
diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex
index 118dd3acc..599f1d3cf 100644
--- a/lib/pleroma/config/deprecation_warnings.ex
+++ b/lib/pleroma/config/deprecation_warnings.ex
@@ -20,6 +20,43 @@ defmodule Pleroma.Config.DeprecationWarnings do
"\n* `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions`"}
]
+ def check_exiftool_filter do
+ filters = Config.get([Pleroma.Upload]) |> Keyword.get(:filters, [])
+
+ if Pleroma.Upload.Filter.Exiftool in filters do
+ Logger.warn("""
+ !!!DEPRECATION WARNING!!!
+ Your config is using Exiftool as a filter instead of Exiftool.StripLocation. This should work for now, but you are advised to change to the new configuration to prevent possible issues later:
+
+ ```
+ config :pleroma, Pleroma.Upload,
+ filters: [Pleroma.Upload.Filter.Exiftool]
+ ```
+
+ Is now
+
+
+ ```
+ config :pleroma, Pleroma.Upload,
+ filters: [Pleroma.Upload.Filter.Exiftool.StripLocation]
+ ```
+ """)
+
+ new_config =
+ filters
+ |> Enum.map(fn
+ Pleroma.Upload.Filter.Exiftool -> Pleroma.Upload.Filter.Exiftool.StripLocation
+ filter -> filter
+ end)
+
+ Config.put([Pleroma.Upload, :filters], new_config)
+
+ :error
+ else
+ :ok
+ end
+ end
+
def check_simple_policy_tuples do
has_strings =
Config.get([:mrf_simple])
@@ -180,7 +217,8 @@ defmodule Pleroma.Config.DeprecationWarnings do
check_old_chat_shoutbox(),
check_quarantined_instances_tuples(),
check_transparency_exclusions_tuples(),
- check_simple_policy_tuples()
+ check_simple_policy_tuples(),
+ check_exiftool_filter()
]
|> Enum.reduce(:ok, fn
:ok, :ok -> :ok
diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex
index a42c71d23..7b63ab06e 100644
--- a/lib/pleroma/constants.ex
+++ b/lib/pleroma/constants.ex
@@ -27,4 +27,10 @@ defmodule Pleroma.Constants do
do:
~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc embed.js embed.css)
)
+
+ # basic regex, just there to weed out potential mistakes
+ # https://datatracker.ietf.org/doc/html/rfc2045#section-5.1
+ const(mime_regex,
+ do: ~r/^[^[:cntrl:] ()<>@,;:\\"\/\[\]?=]+\/[^[:cntrl:] ()<>@,;:\\"\/\[\]?=]+(; .*)?$/
+ )
end
diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/mime.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/mime.ex
new file mode 100644
index 000000000..31d51577d
--- /dev/null
+++ b/lib/pleroma/ecto_type/activity_pub/object_validators/mime.ex
@@ -0,0 +1,25 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.MIME do
+ use Ecto.Type
+
+ require Pleroma.Constants
+
+ def type, do: :string
+
+ def cast(mime) when is_binary(mime) do
+ if mime =~ Pleroma.Constants.mime_regex() do
+ {:ok, mime}
+ else
+ {:ok, "application/octet-stream"}
+ end
+ end
+
+ def cast(_), do: :error
+
+ def dump(data), do: {:ok, data}
+
+ def load(data), do: {:ok, data}
+end
diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex
index 8e21e9987..95b963764 100644
--- a/lib/pleroma/emails/user_email.ex
+++ b/lib/pleroma/emails/user_email.ex
@@ -5,9 +5,12 @@
defmodule Pleroma.Emails.UserEmail do
@moduledoc "User emails"
+ require Pleroma.Web.Gettext
+
alias Pleroma.Config
alias Pleroma.User
alias Pleroma.Web.Endpoint
+ alias Pleroma.Web.Gettext
alias Pleroma.Web.Router
import Swoosh.Email
@@ -27,29 +30,75 @@ defmodule Pleroma.Emails.UserEmail do
@spec welcome(User.t(), map()) :: Swoosh.Email.t()
def welcome(user, opts \\ %{}) do
- new()
- |> to(recipient(user))
- |> from(Map.get(opts, :sender, sender()))
- |> subject(Map.get(opts, :subject, "Welcome to #{instance_name()}!"))
- |> html_body(Map.get(opts, :html, "Welcome to #{instance_name()}!"))
- |> text_body(Map.get(opts, :text, "Welcome to #{instance_name()}!"))
+ Gettext.with_locale_or_default user.language do
+ new()
+ |> to(recipient(user))
+ |> from(Map.get(opts, :sender, sender()))
+ |> subject(
+ Map.get(
+ opts,
+ :subject,
+ Gettext.dpgettext(
+ "static_pages",
+ "welcome email subject",
+ "Welcome to %{instance_name}!",
+ instance_name: instance_name()
+ )
+ )
+ )
+ |> html_body(
+ Map.get(
+ opts,
+ :html,
+ Gettext.dpgettext(
+ "static_pages",
+ "welcome email html body",
+ "Welcome to %{instance_name}!",
+ instance_name: instance_name()
+ )
+ )
+ )
+ |> text_body(
+ Map.get(
+ opts,
+ :text,
+ Gettext.dpgettext(
+ "static_pages",
+ "welcome email text body",
+ "Welcome to %{instance_name}!",
+ instance_name: instance_name()
+ )
+ )
+ )
+ end
end
def password_reset_email(user, token) when is_binary(token) do
- password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token)
-
- html_body = """
- <h3>Reset your password at #{instance_name()}</h3>
- <p>Someone has requested password change for your account at #{instance_name()}.</p>
- <p>If it was you, visit the following link to proceed: <a href="#{password_reset_url}">reset password</a>.</p>
- <p>If it was someone else, nothing to worry about: your data is secure and your password has not been changed.</p>
- """
-
- new()
- |> to(recipient(user))
- |> from(sender())
- |> subject("Password reset")
- |> html_body(html_body)
+ Gettext.with_locale_or_default user.language do
+ password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token)
+
+ html_body =
+ Gettext.dpgettext(
+ "static_pages",
+ "password reset email body",
+ """
+ <h3>Reset your password at %{instance_name}</h3>
+ <p>Someone has requested password change for your account at %{instance_name}.</p>
+ <p>If it was you, visit the following link to proceed: <a href="%{password_reset_url}">reset password</a>.</p>
+ <p>If it was someone else, nothing to worry about: your data is secure and your password has not been changed.</p>
+ """,
+ instance_name: instance_name(),
+ password_reset_url: password_reset_url
+ )
+
+ new()
+ |> to(recipient(user))
+ |> from(sender())
+ |> subject(
+ Gettext.dpgettext("static_pages", "password reset email subject", "Password reset")
+ )
+ |> html_body(html_body)
+ end
end
def user_invitation_email(
@@ -58,73 +107,136 @@ defmodule Pleroma.Emails.UserEmail do
to_email,
to_name \\ nil
) do
- registration_url =
- Router.Helpers.redirect_url(
- Endpoint,
- :registration_page,
- user_invite_token.token
- )
+ Gettext.with_locale_or_default user.language do
+ registration_url =
+ Router.Helpers.redirect_url(
+ Endpoint,
+ :registration_page,
+ user_invite_token.token
+ )
+
+ html_body =
+ Gettext.dpgettext(
+ "static_pages",
+ "user invitation email body",
+ """
+ <h3>You are invited to %{instance_name}</h3>
+ <p>%{inviter_name} invites you to join %{instance_name}, an instance of Pleroma federated social networking platform.</p>
+ <p>Click the following link to register: <a href="%{registration_url}">accept invitation</a>.</p>
+ """,
+ instance_name: instance_name(),
+ inviter_name: user.name,
+ registration_url: registration_url
+ )
- html_body = """
- <h3>You are invited to #{instance_name()}</h3>
- <p>#{user.name} invites you to join #{instance_name()}, an instance of Pleroma federated social networking platform.</p>
- <p>Click the following link to register: <a href="#{registration_url}">accept invitation</a>.</p>
- """
-
- new()
- |> to(recipient(to_email, to_name))
- |> from(sender())
- |> subject("Invitation to #{instance_name()}")
- |> html_body(html_body)
+ new()
+ |> to(recipient(to_email, to_name))
+ |> from(sender())
+ |> subject(
+ Gettext.dpgettext(
+ "static_pages",
+ "user invitation email subject",
+ "Invitation to %{instance_name}",
+ instance_name: instance_name()
+ )
+ )
+ |> html_body(html_body)
+ end
end
def account_confirmation_email(user) do
- confirmation_url =
- Router.Helpers.confirm_email_url(
- Endpoint,
- :confirm_email,
- user.id,
- to_string(user.confirmation_token)
- )
+ Gettext.with_locale_or_default user.language do
+ confirmation_url =
+ Router.Helpers.confirm_email_url(
+ Endpoint,
+ :confirm_email,
+ user.id,
+ to_string(user.confirmation_token)
+ )
+
+ html_body =
+ Gettext.dpgettext(
+ "static_pages",
+ "confirmation email body",
+ """
+ <h3>Thank you for registering on %{instance_name}</h3>
+ <p>Email confirmation is required to activate the account.</p>
+ <p>Please click the following link to <a href="%{confirmation_url}">activate your account</a>.</p>
+ """,
+ instance_name: instance_name(),
+ confirmation_url: confirmation_url
+ )
- html_body = """
- <h3>Thank you for registering on #{instance_name()}</h3>
- <p>Email confirmation is required to activate the account.</p>
- <p>Please click the following link to <a href="#{confirmation_url}">activate your account</a>.</p>
- """
-
- new()
- |> to(recipient(user))
- |> from(sender())
- |> subject("#{instance_name()} account confirmation")
- |> html_body(html_body)
+ new()
+ |> to(recipient(user))
+ |> from(sender())
+ |> subject(
+ Gettext.dpgettext(
+ "static_pages",
+ "confirmation email subject",
+ "%{instance_name} account confirmation",
+ instance_name: instance_name()
+ )
+ )
+ |> html_body(html_body)
+ end
end
def approval_pending_email(user) do
- html_body = """
- <h3>Awaiting Approval</h3>
- <p>Your account at #{instance_name()} is being reviewed by staff. You will receive another email once your account is approved.</p>
- """
-
- new()
- |> to(recipient(user))
- |> from(sender())
- |> subject("Your account is awaiting approval")
- |> html_body(html_body)
+ Gettext.with_locale_or_default user.language do
+ html_body =
+ Gettext.dpgettext(
+ "static_pages",
+ "approval pending email body",
+ """
+ <h3>Awaiting Approval</h3>
+ <p>Your account at %{instance_name} is being reviewed by staff. You will receive another email once your account is approved.</p>
+ """,
+ instance_name: instance_name()
+ )
+
+ new()
+ |> to(recipient(user))
+ |> from(sender())
+ |> subject(
+ Gettext.dpgettext(
+ "static_pages",
+ "approval pending email subject",
+ "Your account is awaiting approval"
+ )
+ )
+ |> html_body(html_body)
+ end
end
def successful_registration_email(user) do
- html_body = """
- <h3>Hello @#{user.nickname},</h3>
- <p>Your account at #{instance_name()} has been registered successfully.</p>
- <p>No further action is required to activate your account.</p>
- """
-
- new()
- |> to(recipient(user))
- |> from(sender())
- |> subject("Account registered on #{instance_name()}")
- |> html_body(html_body)
+ Gettext.with_locale_or_default user.language do
+ html_body =
+ Gettext.dpgettext(
+ "static_pages",
+ "successful registration email body",
+ """
+ <h3>Hello @%{nickname},</h3>
+ <p>Your account at %{instance_name} has been registered successfully.</p>
+ <p>No further action is required to activate your account.</p>
+ """,
+ nickname: user.nickname,
+ instance_name: instance_name()
+ )
+
+ new()
+ |> to(recipient(user))
+ |> from(sender())
+ |> subject(
+ Gettext.dpgettext(
+ "static_pages",
+ "successful registration email subject",
+ "Account registered on %{instance_name}",
+ instance_name: instance_name()
+ )
+ )
+ |> html_body(html_body)
+ end
end
@doc """
@@ -134,69 +246,78 @@ defmodule Pleroma.Emails.UserEmail do
"""
@spec digest_email(User.t()) :: Swoosh.Email.t() | nil
def digest_email(user) do
- notifications = Pleroma.Notification.for_user_since(user, user.last_digest_emailed_at)
-
- mentions =
- notifications
- |> Enum.filter(&(&1.activity.data["type"] == "Create"))
- |> Enum.map(fn notification ->
- object = Pleroma.Object.normalize(notification.activity, fetch: false)
-
- if not is_nil(object) do
- object = update_in(object.data["content"], &format_links/1)
-
- %{
- data: notification,
- object: object,
- from: User.get_by_ap_id(notification.activity.actor)
- }
- end
- end)
- |> Enum.filter(& &1)
-
- followers =
- notifications
- |> Enum.filter(&(&1.activity.data["type"] == "Follow"))
- |> Enum.map(fn notification ->
- from = User.get_by_ap_id(notification.activity.actor)
-
- if not is_nil(from) do
- %{
- data: notification,
- object: Pleroma.Object.normalize(notification.activity, fetch: false),
- from: User.get_by_ap_id(notification.activity.actor)
- }
- end
- end)
- |> Enum.filter(& &1)
-
- unless Enum.empty?(mentions) do
- styling = Config.get([__MODULE__, :styling])
- logo = Config.get([__MODULE__, :logo])
-
- html_data = %{
- instance: instance_name(),
- user: user,
- mentions: mentions,
- followers: followers,
- unsubscribe_link: unsubscribe_url(user, "digest"),
- styling: styling
- }
-
- logo_path =
- if is_nil(logo) do
- Path.join(:code.priv_dir(:pleroma), "static/static/logo.svg")
- else
- Path.join(Config.get([:instance, :static_dir]), logo)
- end
-
- new()
- |> to(recipient(user))
- |> from(sender())
- |> subject("Your digest from #{instance_name()}")
- |> put_layout(false)
- |> render_body("digest.html", html_data)
- |> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.svg", type: :inline))
+ Gettext.with_locale_or_default user.language do
+ notifications = Pleroma.Notification.for_user_since(user, user.last_digest_emailed_at)
+
+ mentions =
+ notifications
+ |> Enum.filter(&(&1.activity.data["type"] == "Create"))
+ |> Enum.map(fn notification ->
+ object = Pleroma.Object.normalize(notification.activity, fetch: false)
+
+ if not is_nil(object) do
+ object = update_in(object.data["content"], &format_links/1)
+
+ %{
+ data: notification,
+ object: object,
+ from: User.get_by_ap_id(notification.activity.actor)
+ }
+ end
+ end)
+ |> Enum.filter(& &1)
+
+ followers =
+ notifications
+ |> Enum.filter(&(&1.activity.data["type"] == "Follow"))
+ |> Enum.map(fn notification ->
+ from = User.get_by_ap_id(notification.activity.actor)
+
+ if not is_nil(from) do
+ %{
+ data: notification,
+ object: Pleroma.Object.normalize(notification.activity, fetch: false),
+ from: User.get_by_ap_id(notification.activity.actor)
+ }
+ end
+ end)
+ |> Enum.filter(& &1)
+
+ unless Enum.empty?(mentions) do
+ styling = Config.get([__MODULE__, :styling])
+ logo = Config.get([__MODULE__, :logo])
+
+ html_data = %{
+ instance: instance_name(),
+ user: user,
+ mentions: mentions,
+ followers: followers,
+ unsubscribe_link: unsubscribe_url(user, "digest"),
+ styling: styling
+ }
+
+ logo_path =
+ if is_nil(logo) do
+ Path.join(:code.priv_dir(:pleroma), "static/static/logo.svg")
+ else
+ Path.join(Config.get([:instance, :static_dir]), logo)
+ end
+
+ new()
+ |> to(recipient(user))
+ |> from(sender())
+ |> subject(
+ Gettext.dpgettext(
+ "static_pages",
+ "digest email subject",
+ "Your digest from %{instance_name}",
+ instance_name: instance_name()
+ )
+ )
+ |> put_layout(false)
+ |> render_body("digest.html", html_data)
+ |> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.svg", type: :inline))
+ end
end
end
@@ -226,27 +347,47 @@ defmodule Pleroma.Emails.UserEmail do
def backup_is_ready_email(backup, admin_user_id \\ nil) do
%{user: user} = Pleroma.Repo.preload(backup, :user)
- download_url = Pleroma.Web.PleromaAPI.BackupView.download_url(backup)
-
- html_body =
- if is_nil(admin_user_id) do
- """
- <p>You requested a full backup of your Pleroma account. It's ready for download:</p>
- <p><a href="#{download_url}">#{download_url}</a></p>
- """
- else
- admin = Pleroma.Repo.get(User, admin_user_id)
-
- """
- <p>Admin @#{admin.nickname} requested a full backup of your Pleroma account. It's ready for download:</p>
- <p><a href="#{download_url}">#{download_url}</a></p>
- """
- end
- new()
- |> to(recipient(user))
- |> from(sender())
- |> subject("Your account archive is ready")
- |> html_body(html_body)
+ Gettext.with_locale_or_default user.language do
+ download_url = Pleroma.Web.PleromaAPI.BackupView.download_url(backup)
+
+ html_body =
+ if is_nil(admin_user_id) do
+ Gettext.dpgettext(
+ "static_pages",
+ "account archive email body - self-requested",
+ """
+ <p>You requested a full backup of your Pleroma account. It's ready for download:</p>
+ <p><a href="%{download_url}">%{download_url}</a></p>
+ """,
+ download_url: download_url
+ )
+ else
+ admin = Pleroma.Repo.get(User, admin_user_id)
+
+ Gettext.dpgettext(
+ "static_pages",
+ "account archive email body - admin requested",
+ """
+ <p>Admin @%{admin_nickname} requested a full backup of your Pleroma account. It's ready for download:</p>
+ <p><a href="%{download_url}">%{download_url}</a></p>
+ """,
+ admin_nickname: admin.nickname,
+ download_url: download_url
+ )
+ end
+
+ new()
+ |> to(recipient(user))
+ |> from(sender())
+ |> subject(
+ Gettext.dpgettext(
+ "static_pages",
+ "account archive email subject",
+ "Your account archive is ready"
+ )
+ )
+ |> html_body(html_body)
+ end
end
end
diff --git a/lib/pleroma/http/adapter_helper/hackney.ex b/lib/pleroma/http/adapter_helper/hackney.ex
index b4f2f0cc2..f3be1f3d0 100644
--- a/lib/pleroma/http/adapter_helper/hackney.ex
+++ b/lib/pleroma/http/adapter_helper/hackney.ex
@@ -24,10 +24,6 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do
|> Pleroma.HTTP.AdapterHelper.maybe_add_proxy(proxy)
end
- defp add_scheme_opts(opts, %URI{scheme: "https"}) do
- Keyword.put(opts, :ssl_options, versions: [:"tlsv1.2", :"tlsv1.1", :tlsv1])
- end
-
defp add_scheme_opts(opts, _), do: opts
defp maybe_add_with_body(opts) do
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index 41385884b..52fd2656b 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -341,6 +341,14 @@ defmodule Pleroma.Notification do
|> Repo.delete_all()
end
+ def destroy_multiple_from_types(%{id: user_id}, types) do
+ from(n in Notification,
+ where: n.user_id == ^user_id,
+ where: n.type in ^types
+ )
+ |> Repo.delete_all()
+ end
+
def dismiss(%Pleroma.Activity{} = activity) do
Notification
|> where([n], n.activity_id == ^activity.id)
diff --git a/lib/pleroma/reverse_proxy/client/hackney.ex b/lib/pleroma/reverse_proxy/client/hackney.ex
index 41eaf06cc..d3e986912 100644
--- a/lib/pleroma/reverse_proxy/client/hackney.ex
+++ b/lib/pleroma/reverse_proxy/client/hackney.ex
@@ -7,7 +7,6 @@ defmodule Pleroma.ReverseProxy.Client.Hackney do
@impl true
def request(method, url, headers, body, opts \\ []) do
- opts = Keyword.put(opts, :ssl_options, versions: [:"tlsv1.2", :"tlsv1.1", :tlsv1])
:hackney.request(method, url, headers, body, opts)
end
diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index 242813dcd..db2909276 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -60,12 +60,23 @@ defmodule Pleroma.Upload do
width: integer(),
height: integer(),
blurhash: String.t(),
+ description: String.t(),
path: String.t()
}
- defstruct [:id, :name, :tempfile, :content_type, :width, :height, :blurhash, :path]
-
- defp get_description(opts, upload) do
- case {opts[:description], Pleroma.Config.get([Pleroma.Upload, :default_description])} do
+ defstruct [
+ :id,
+ :name,
+ :tempfile,
+ :content_type,
+ :width,
+ :height,
+ :blurhash,
+ :description,
+ :path
+ ]
+
+ defp get_description(upload) do
+ case {upload.description, Pleroma.Config.get([Pleroma.Upload, :default_description])} do
{description, _} when is_binary(description) -> description
{_, :filename} -> upload.name
{_, str} when is_binary(str) -> str
@@ -81,7 +92,7 @@ defmodule Pleroma.Upload do
with {:ok, upload} <- prepare_upload(upload, opts),
upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"},
{:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload),
- description = get_description(opts, upload),
+ description = get_description(upload),
{_, true} <-
{:description_limit,
String.length(description) <= Pleroma.Config.get([:instance, :description_limit])},
@@ -152,7 +163,8 @@ defmodule Pleroma.Upload do
id: UUID.generate(),
name: file.filename,
tempfile: file.path,
- content_type: file.content_type
+ content_type: file.content_type,
+ description: opts.description
}}
end
end
@@ -172,7 +184,8 @@ defmodule Pleroma.Upload do
id: UUID.generate(),
name: hash <> "." <> ext,
tempfile: tmp_path,
- content_type: content_type
+ content_type: content_type,
+ description: opts.description
}}
end
end
diff --git a/lib/pleroma/upload/filter/exiftool/read_description.ex b/lib/pleroma/upload/filter/exiftool/read_description.ex
new file mode 100644
index 000000000..03d698a81
--- /dev/null
+++ b/lib/pleroma/upload/filter/exiftool/read_description.ex
@@ -0,0 +1,49 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Upload.Filter.Exiftool.ReadDescription do
+ @moduledoc """
+ Gets a valid description from the related EXIF tags and provides them in the response if no description is provided yet.
+ It will first check ImageDescription, when that doesn't probide a valid description, it will check iptc:Caption-Abstract.
+ A valid description means the fields are filled in and not too long (see `:instance, :description_limit`).
+ """
+ @behaviour Pleroma.Upload.Filter
+
+ @spec filter(Pleroma.Upload.t()) :: {:ok, any()} | {:error, String.t()}
+
+ def filter(%Pleroma.Upload{description: description})
+ when is_binary(description),
+ do: {:ok, :noop}
+
+ def filter(%Pleroma.Upload{tempfile: file} = upload),
+ do: {:ok, :filtered, upload |> Map.put(:description, read_description_from_exif_data(file))}
+
+ def filter(_, _), do: {:ok, :noop}
+
+ defp read_description_from_exif_data(file) do
+ nil
+ |> read_when_empty(file, "-ImageDescription")
+ |> read_when_empty(file, "-iptc:Caption-Abstract")
+ end
+
+ defp read_when_empty(current_description, _, _) when is_binary(current_description),
+ do: current_description
+
+ defp read_when_empty(_, file, tag) do
+ try do
+ {tag_content, 0} =
+ System.cmd("exiftool", ["-b", "-s3", tag, file], stderr_to_stdout: true, parallelism: true)
+
+ tag_content = String.trim(tag_content)
+
+ if tag_content != "" and
+ String.length(tag_content) <=
+ Pleroma.Config.get([:instance, :description_limit]),
+ do: tag_content,
+ else: nil
+ rescue
+ _ in ErlangError -> nil
+ end
+ end
+end
diff --git a/lib/pleroma/upload/filter/exiftool.ex b/lib/pleroma/upload/filter/exiftool/strip_location.ex
index 36cc045c2..6100527d3 100644
--- a/lib/pleroma/upload/filter/exiftool.ex
+++ b/lib/pleroma/upload/filter/exiftool/strip_location.ex
@@ -2,7 +2,7 @@
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
-defmodule Pleroma.Upload.Filter.Exiftool do
+defmodule Pleroma.Upload.Filter.Exiftool.StripLocation do
@moduledoc """
Strips GPS related EXIF tags and overwrites the file in place.
Also strips or replaces filesystem metadata e.g., timestamps.
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 562581be4..712d3b1d9 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -156,6 +156,7 @@ defmodule Pleroma.User do
field(:last_status_at, :naive_datetime)
field(:birthday, :date)
field(:show_birthday, :boolean, default: false)
+ field(:language, :string)
embeds_one(
:notification_settings,
@@ -705,7 +706,7 @@ defmodule Pleroma.User do
])
|> validate_required([:name, :nickname])
|> unique_constraint(:nickname)
- |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
+ |> validate_not_restricted_nickname(:nickname)
|> validate_format(:nickname, local_nickname_regex())
|> put_ap_id()
|> unique_constraint(:ap_id)
@@ -746,23 +747,16 @@ defmodule Pleroma.User do
:emoji,
:accepts_chat_messages,
:registration_reason,
- :birthday
+ :birthday,
+ :language
])
|> validate_required([:name, :nickname, :password, :password_confirmation])
|> validate_confirmation(:password)
|> unique_constraint(:email)
|> validate_format(:email, @email_regex)
- |> validate_change(:email, fn :email, email ->
- valid? =
- Config.get([User, :email_blacklist])
- |> Enum.all?(fn blacklisted_domain ->
- !String.ends_with?(email, ["@" <> blacklisted_domain, "." <> blacklisted_domain])
- end)
-
- if valid?, do: [], else: [email: "Invalid email"]
- end)
+ |> validate_email_not_in_blacklisted_domain(:email)
|> unique_constraint(:nickname)
- |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
+ |> validate_not_restricted_nickname(:nickname)
|> validate_format(:nickname, local_nickname_regex())
|> validate_length(:bio, max: bio_limit)
|> validate_length(:name, min: 1, max: name_limit)
@@ -776,6 +770,35 @@ defmodule Pleroma.User do
|> put_following_and_follower_and_featured_address()
end
+ def validate_not_restricted_nickname(changeset, field) do
+ validate_change(changeset, field, fn _, value ->
+ valid? =
+ Config.get([User, :restricted_nicknames])
+ |> Enum.all?(fn restricted_nickname ->
+ String.downcase(value) != String.downcase(restricted_nickname)
+ end)
+
+ if valid?, do: [], else: [nickname: "Invalid nickname"]
+ end)
+ end
+
+ def validate_email_not_in_blacklisted_domain(changeset, field) do
+ validate_change(changeset, field, fn _, value ->
+ valid? =
+ Config.get([User, :email_blacklist])
+ |> Enum.all?(fn blacklisted_domain ->
+ blacklisted_domain_downcase = String.downcase(blacklisted_domain)
+
+ !String.ends_with?(String.downcase(value), [
+ "@" <> blacklisted_domain_downcase,
+ "." <> blacklisted_domain_downcase
+ ])
+ end)
+
+ if valid?, do: [], else: [email: "Invalid email"]
+ end)
+ end
+
def maybe_validate_required_email(changeset, true), do: changeset
def maybe_validate_required_email(changeset, _) do
@@ -1127,10 +1150,24 @@ defmodule Pleroma.User do
|> update_and_set_cache()
end
- def update_and_set_cache(changeset) do
+ def update_and_set_cache(%{data: %Pleroma.User{} = user} = changeset) do
+ was_superuser_before_update = User.superuser?(user)
+
with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
set_cache(user)
end
+ |> maybe_remove_report_notifications(was_superuser_before_update)
+ end
+
+ defp maybe_remove_report_notifications({:ok, %Pleroma.User{} = user} = result, true) do
+ if not User.superuser?(user),
+ do: user |> Notification.destroy_multiple_from_types(["pleroma:report"])
+
+ result
+ end
+
+ defp maybe_remove_report_notifications(result, _) do
+ result
end
def get_user_friends_ap_ids(user) do
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 20f8bbc2d..b8f63d69d 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -84,6 +84,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
user <- Map.get(assigns, :user, nil),
{_, true} <- {:visible?, Visibility.visible_for_user?(object, user)} do
conn
+ |> maybe_skip_cache(user)
|> assign(:tracking_fun_data, object.id)
|> set_cache_ttl_for(object)
|> put_resp_content_type("application/activity+json")
@@ -112,6 +113,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
user <- Map.get(assigns, :user, nil),
{_, true} <- {:visible?, Visibility.visible_for_user?(activity, user)} do
conn
+ |> maybe_skip_cache(user)
|> maybe_set_tracking_data(activity)
|> set_cache_ttl_for(activity)
|> put_resp_content_type("application/activity+json")
@@ -151,6 +153,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
assign(conn, :cache_ttl, ttl)
end
+ def maybe_skip_cache(conn, user) do
+ if user do
+ conn
+ |> assign(:skip_cache, true)
+ else
+ conn
+ end
+ end
+
# GET /relay/following
def relay_following(conn, _params) do
with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
diff --git a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
index 605ca9971..97d75ecf2 100644
--- a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
@@ -24,7 +24,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do
defp score_displayname("fedibot"), do: 1.0
defp score_displayname(_), do: 0.0
- defp determine_if_followbot(%User{nickname: nickname, name: displayname}) do
+ defp determine_if_followbot(%User{nickname: nickname, name: displayname, actor_type: actor_type}) do
# nickname will be a binary string except when following a relay
nick_score =
if is_binary(nickname) do
@@ -45,19 +45,32 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do
0.0
end
- nick_score + name_score
+ # actor_type "Service" is a Bot account
+ actor_type_score =
+ if actor_type == "Service" do
+ 1.0
+ else
+ 0.0
+ end
+
+ nick_score + name_score + actor_type_score
end
defp determine_if_followbot(_), do: 0.0
+ defp bot_allowed?(%{"object" => target}, bot_actor) do
+ %User{} = user = normalize_by_ap_id(target)
+
+ User.following?(user, bot_actor)
+ end
+
@impl true
def filter(%{"type" => "Follow", "actor" => actor_id} = message) do
%User{} = actor = normalize_by_ap_id(actor_id)
score = determine_if_followbot(actor)
- # TODO: scan biography data for keywords and score it somehow.
- if score < 0.8 do
+ if score < 0.8 || bot_allowed?(message, actor) do
{:ok, message}
else
{:reject, "[AntiFollowbotPolicy] Scored #{actor_id} as #{score}"}
diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex
index 06305235e..f66c379b5 100644
--- a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex
@@ -12,6 +12,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
defp accept_host?(host), do: host in Config.get([:mrf_steal_emoji, :hosts], [])
+ defp shortcode_matches?(shortcode, pattern) when is_binary(pattern) do
+ shortcode == pattern
+ end
+
+ defp shortcode_matches?(shortcode, pattern) do
+ String.match?(shortcode, pattern)
+ end
+
defp steal_emoji({shortcode, url}, emoji_dir_path) do
url = Pleroma.Web.MediaProxy.url(url)
@@ -72,7 +80,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
reject_emoji? =
[:mrf_steal_emoji, :rejected_shortcodes]
|> Config.get([])
- |> Enum.find(false, fn regex -> String.match?(shortcode, regex) end)
+ |> Enum.find(false, fn pattern -> shortcode_matches?(shortcode, pattern) end)
!reject_emoji?
end)
@@ -122,8 +130,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
%{
key: :rejected_shortcodes,
type: {:list, :string},
- description: "Regex-list of shortcodes to reject",
- suggestions: [""]
+ description: """
+ A list of patterns or matches to reject shortcodes with.
+
+ Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
+ """,
+ suggestions: ["foo", ~r/foo/]
},
%{
key: :size_limit,
diff --git a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
index d1c61ac82..8b641d88d 100644
--- a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
@@ -12,14 +12,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
@primary_key false
embedded_schema do
field(:type, :string)
- field(:mediaType, :string, default: "application/octet-stream")
+ field(:mediaType, ObjectValidators.MIME, default: "application/octet-stream")
field(:name, :string)
field(:blurhash, :string)
embeds_many :url, UrlObjectValidator, primary_key: false do
field(:type, :string)
field(:href, ObjectValidators.Uri)
- field(:mediaType, :string, default: "application/octet-stream")
+ field(:mediaType, ObjectValidators.MIME, default: "application/octet-stream")
field(:width, :integer)
field(:height, :integer)
end
@@ -59,13 +59,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
end
def fix_media_type(data) do
- data = Map.put_new(data, "mediaType", data["mimeType"])
-
- if is_bitstring(data["mediaType"]) && MIME.extensions(data["mediaType"]) != [] do
- data
- else
- Map.put(data, "mediaType", "application/octet-stream")
- end
+ Map.put_new(data, "mediaType", data["mimeType"])
end
defp handle_href(href, mediaType, data) do
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index a70330f0e..d6622df86 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -203,13 +203,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
media_type =
cond do
- is_map(url) && MIME.extensions(url["mediaType"]) != [] ->
+ is_map(url) && url =~ Pleroma.Constants.mime_regex() ->
url["mediaType"]
- is_bitstring(data["mediaType"]) && MIME.extensions(data["mediaType"]) != [] ->
+ is_bitstring(data["mediaType"]) && data["mediaType"] =~ Pleroma.Constants.mime_regex() ->
data["mediaType"]
- is_bitstring(data["mimeType"]) && MIME.extensions(data["mimeType"]) != [] ->
+ is_bitstring(data["mimeType"]) && data["mimeType"] =~ Pleroma.Constants.mime_regex() ->
data["mimeType"]
true ->
diff --git a/lib/pleroma/web/admin_api/controllers/announcement_controller.ex b/lib/pleroma/web/admin_api/controllers/announcement_controller.ex
new file mode 100644
index 000000000..6ad5fc12c
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/announcement_controller.ex
@@ -0,0 +1,83 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.AnnouncementController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Announcement
+ alias Pleroma.Web.ControllerHelper
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+ plug(OAuthScopesPlug, %{scopes: ["admin:write"]} when action in [:create, :delete, :change])
+ plug(OAuthScopesPlug, %{scopes: ["admin:read"]} when action in [:index, :show])
+ action_fallback(Pleroma.Web.AdminAPI.FallbackController)
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.AnnouncementOperation
+
+ defp default_limit, do: 20
+
+ def index(conn, params) do
+ limit = Map.get(params, :limit, default_limit())
+ offset = Map.get(params, :offset, 0)
+
+ announcements = Announcement.list_paginated(%{limit: limit, offset: offset})
+
+ render(conn, "index.json", announcements: announcements)
+ end
+
+ def show(conn, %{id: id} = _params) do
+ announcement = Announcement.get_by_id(id)
+
+ if is_nil(announcement) do
+ {:error, :not_found}
+ else
+ render(conn, "show.json", announcement: announcement)
+ end
+ end
+
+ def create(%{body_params: params} = conn, _params) do
+ with {:ok, announcement} <- Announcement.add(change_params(params)) do
+ render(conn, "show.json", announcement: announcement)
+ else
+ _ ->
+ {:error, 400}
+ end
+ end
+
+ def change_params(orig_params) do
+ data =
+ %{}
+ |> Pleroma.Maps.put_if_present("content", orig_params, &Map.fetch(&1, :content))
+ |> Pleroma.Maps.put_if_present("all_day", orig_params, &Map.fetch(&1, :all_day))
+
+ orig_params
+ |> Map.merge(%{data: data})
+ end
+
+ def change(%{body_params: params} = conn, %{id: id} = _params) do
+ with announcement <- Announcement.get_by_id(id),
+ {:exists, true} <- {:exists, not is_nil(announcement)},
+ {:ok, announcement} <- Announcement.update(announcement, change_params(params)) do
+ render(conn, "show.json", announcement: announcement)
+ else
+ {:exists, false} ->
+ {:error, :not_found}
+
+ _ ->
+ {:error, 400}
+ end
+ end
+
+ def delete(conn, %{id: id} = _params) do
+ case Announcement.delete_by_id(id) do
+ :ok ->
+ conn
+ |> ControllerHelper.json_response(:ok, %{})
+
+ _ ->
+ {:error, :not_found}
+ end
+ end
+end
diff --git a/lib/pleroma/web/admin_api/views/announcement_view.ex b/lib/pleroma/web/admin_api/views/announcement_view.ex
new file mode 100644
index 000000000..a35bd60cf
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/announcement_view.ex
@@ -0,0 +1,15 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.AnnouncementView do
+ use Pleroma.Web, :view
+
+ def render("index.json", %{announcements: announcements}) do
+ render_many(announcements, __MODULE__, "show.json")
+ end
+
+ def render("show.json", %{announcement: announcement}) do
+ Pleroma.Announcement.render_json(announcement, admin: true)
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 026e92c5d..a64762285 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -549,6 +549,11 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
nullable: true,
description: "User's birthday",
format: :date
+ },
+ language: %Schema{
+ type: :string,
+ nullable: true,
+ description: "User's preferred language for emails"
}
},
example: %{
diff --git a/lib/pleroma/web/api_spec/operations/admin/announcement_operation.ex b/lib/pleroma/web/api_spec/operations/admin/announcement_operation.ex
new file mode 100644
index 000000000..58a039e72
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/admin/announcement_operation.ex
@@ -0,0 +1,165 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Admin.AnnouncementOperation do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.Announcement
+ alias Pleroma.Web.ApiSpec.Schemas.ApiError
+
+ import Pleroma.Web.ApiSpec.Helpers
+
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ def index_operation do
+ %Operation{
+ tags: ["Announcement managment"],
+ summary: "Retrieve a list of announcements",
+ operationId: "AdminAPI.AnnouncementController.index",
+ security: [%{"oAuth" => ["admin:read"]}],
+ parameters: [
+ Operation.parameter(
+ :limit,
+ :query,
+ %Schema{type: :integer, minimum: 1},
+ "the maximum number of announcements to return"
+ ),
+ Operation.parameter(
+ :offset,
+ :query,
+ %Schema{type: :integer, minimum: 0},
+ "the offset of the first announcement to return"
+ )
+ | admin_api_params()
+ ],
+ responses: %{
+ 200 => Operation.response("Response", "application/json", list_of_announcements()),
+ 400 => Operation.response("Forbidden", "application/json", ApiError),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
+ }
+ }
+ end
+
+ def show_operation do
+ %Operation{
+ tags: ["Announcement managment"],
+ summary: "Display one announcement",
+ operationId: "AdminAPI.AnnouncementController.show",
+ security: [%{"oAuth" => ["admin:read"]}],
+ parameters: [
+ Operation.parameter(
+ :id,
+ :path,
+ :string,
+ "announcement id"
+ )
+ | admin_api_params()
+ ],
+ responses: %{
+ 200 => Operation.response("Response", "application/json", Announcement),
+ 403 => Operation.response("Forbidden", "application/json", ApiError),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
+ def delete_operation do
+ %Operation{
+ tags: ["Announcement managment"],
+ summary: "Delete one announcement",
+ operationId: "AdminAPI.AnnouncementController.delete",
+ security: [%{"oAuth" => ["admin:write"]}],
+ parameters: [
+ Operation.parameter(
+ :id,
+ :path,
+ :string,
+ "announcement id"
+ )
+ | admin_api_params()
+ ],
+ responses: %{
+ 200 => Operation.response("Response", "application/json", %Schema{type: :object}),
+ 403 => Operation.response("Forbidden", "application/json", ApiError),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
+ def create_operation do
+ %Operation{
+ tags: ["Announcement managment"],
+ summary: "Create one announcement",
+ operationId: "AdminAPI.AnnouncementController.create",
+ security: [%{"oAuth" => ["admin:write"]}],
+ requestBody: request_body("Parameters", create_request(), required: true),
+ responses: %{
+ 200 => Operation.response("Response", "application/json", Announcement),
+ 400 => Operation.response("Bad Request", "application/json", ApiError),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
+ }
+ }
+ end
+
+ def change_operation do
+ %Operation{
+ tags: ["Announcement managment"],
+ summary: "Change one announcement",
+ operationId: "AdminAPI.AnnouncementController.change",
+ security: [%{"oAuth" => ["admin:write"]}],
+ parameters: [
+ Operation.parameter(
+ :id,
+ :path,
+ :string,
+ "announcement id"
+ )
+ | admin_api_params()
+ ],
+ requestBody: request_body("Parameters", change_request(), required: true),
+ responses: %{
+ 200 => Operation.response("Response", "application/json", Announcement),
+ 400 => Operation.response("Bad Request", "application/json", ApiError),
+ 403 => Operation.response("Forbidden", "application/json", ApiError),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
+ defp create_or_change_props do
+ %{
+ content: %Schema{type: :string},
+ starts_at: %Schema{type: :string, format: "date-time", nullable: true},
+ ends_at: %Schema{type: :string, format: "date-time", nullable: true},
+ all_day: %Schema{type: :boolean}
+ }
+ end
+
+ def create_request do
+ %Schema{
+ title: "AnnouncementCreateRequest",
+ type: :object,
+ required: [:content],
+ properties: create_or_change_props()
+ }
+ end
+
+ def change_request do
+ %Schema{
+ title: "AnnouncementChangeRequest",
+ type: :object,
+ properties: create_or_change_props()
+ }
+ end
+
+ def list_of_announcements do
+ %Schema{
+ type: :array,
+ items: Announcement
+ }
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/announcement_operation.ex b/lib/pleroma/web/api_spec/operations/announcement_operation.ex
new file mode 100644
index 000000000..71be0002a
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/announcement_operation.ex
@@ -0,0 +1,57 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.AnnouncementOperation do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.Announcement
+ alias Pleroma.Web.ApiSpec.Schemas.ApiError
+
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ def index_operation do
+ %Operation{
+ tags: ["Announcement"],
+ summary: "Retrieve a list of announcements",
+ operationId: "MastodonAPI.AnnouncementController.index",
+ security: [%{"oAuth" => []}],
+ responses: %{
+ 200 => Operation.response("Response", "application/json", list_of_announcements()),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
+ }
+ }
+ end
+
+ def mark_read_operation do
+ %Operation{
+ tags: ["Announcement"],
+ summary: "Mark one announcement as read",
+ operationId: "MastodonAPI.AnnouncementController.mark_read",
+ security: [%{"oAuth" => ["write:accounts"]}],
+ parameters: [
+ Operation.parameter(
+ :id,
+ :path,
+ :string,
+ "announcement id"
+ )
+ ],
+ responses: %{
+ 200 => Operation.response("Response", "application/json", %Schema{type: :object}),
+ 403 => Operation.response("Forbidden", "application/json", ApiError),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
+ def list_of_announcements do
+ %Schema{
+ type: :array,
+ items: Announcement
+ }
+ end
+end
diff --git a/lib/pleroma/web/api_spec/schemas/announcement.ex b/lib/pleroma/web/api_spec/schemas/announcement.ex
new file mode 100644
index 000000000..67d129ef6
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/announcement.ex
@@ -0,0 +1,45 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.Announcement do
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.FlakeID
+
+ require OpenApiSpex
+
+ OpenApiSpex.schema(%{
+ title: "Announcement",
+ description: "Response schema for an announcement",
+ type: :object,
+ properties: %{
+ id: FlakeID,
+ content: %Schema{type: :string},
+ starts_at: %Schema{
+ type: :string,
+ format: "date-time",
+ nullable: true
+ },
+ ends_at: %Schema{
+ type: :string,
+ format: "date-time",
+ nullable: true
+ },
+ all_day: %Schema{type: :boolean},
+ published_at: %Schema{type: :string, format: "date-time"},
+ updated_at: %Schema{type: :string, format: "date-time"},
+ read: %Schema{type: :boolean},
+ mentions: %Schema{type: :array},
+ statuses: %Schema{type: :array},
+ tags: %Schema{type: :array},
+ emojis: %Schema{type: :array},
+ reactions: %Schema{type: :array},
+ pleroma: %Schema{
+ type: :object,
+ properties: %{
+ raw_content: %Schema{type: :string}
+ }
+ }
+ }
+ })
+end
diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex
index eb80043f6..35a5f9482 100644
--- a/lib/pleroma/web/feed/feed_view.ex
+++ b/lib/pleroma/web/feed/feed_view.ex
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.Feed.FeedView do
alias Pleroma.Formatter
alias Pleroma.Object
alias Pleroma.User
+ alias Pleroma.Web.Gettext
alias Pleroma.Web.MediaProxy
require Pleroma.Constants
diff --git a/lib/pleroma/web/gettext.ex b/lib/pleroma/web/gettext.ex
index 3771c316d..5ef49d841 100644
--- a/lib/pleroma/web/gettext.ex
+++ b/lib/pleroma/web/gettext.ex
@@ -25,4 +25,196 @@ defmodule Pleroma.Web.Gettext do
See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
"""
use Gettext, otp_app: :pleroma
+
+ def language_tag do
+ # Naive implementation: HTML lang attribute uses BCP 47, which
+ # uses - as a separator.
+ # https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang
+
+ Gettext.get_locale()
+ |> String.replace("_", "-", global: true)
+ end
+
+ def normalize_locale(locale) do
+ if is_binary(locale) do
+ String.replace(locale, "-", "_", global: true)
+ else
+ nil
+ end
+ end
+
+ def supports_locale?(locale) do
+ Pleroma.Web.Gettext
+ |> Gettext.known_locales()
+ |> Enum.member?(locale)
+ end
+
+ def variant?(locale), do: String.contains?(locale, "_")
+
+ def language_for_variant(locale) do
+ Enum.at(String.split(locale, "_"), 0)
+ end
+
+ def ensure_fallbacks(locales) do
+ locales
+ |> Enum.flat_map(fn locale ->
+ others =
+ other_supported_variants_of_locale(locale)
+ |> Enum.filter(fn l -> not Enum.member?(locales, l) end)
+
+ [locale] ++ others
+ end)
+ end
+
+ def other_supported_variants_of_locale(locale) do
+ cond do
+ supports_locale?(locale) ->
+ []
+
+ variant?(locale) ->
+ lang = language_for_variant(locale)
+ if supports_locale?(lang), do: [lang], else: []
+
+ true ->
+ Gettext.known_locales(Pleroma.Web.Gettext)
+ |> Enum.filter(fn l -> String.starts_with?(l, locale <> "_") end)
+ end
+ end
+
+ def get_locales do
+ Process.get({Pleroma.Web.Gettext, :locales}, [])
+ end
+
+ def is_locale_list(locales) do
+ Enum.all?(locales, &is_binary/1)
+ end
+
+ def put_locales(locales) do
+ if is_locale_list(locales) do
+ Process.put({Pleroma.Web.Gettext, :locales}, Enum.uniq(locales))
+ Gettext.put_locale(Enum.at(locales, 0, Gettext.get_locale()))
+ :ok
+ else
+ {:error, :not_locale_list}
+ end
+ end
+
+ def locale_or_default(locale) do
+ if supports_locale?(locale) do
+ locale
+ else
+ Gettext.get_locale()
+ end
+ end
+
+ def with_locales_func(locales, fun) do
+ prev_locales = Process.get({Pleroma.Web.Gettext, :locales})
+ put_locales(locales)
+
+ try do
+ fun.()
+ after
+ if prev_locales do
+ put_locales(prev_locales)
+ else
+ Process.delete({Pleroma.Web.Gettext, :locales})
+ Process.delete(Gettext)
+ end
+ end
+ end
+
+ defmacro with_locales(locales, do: fun) do
+ quote do
+ Pleroma.Web.Gettext.with_locales_func(unquote(locales), fn ->
+ unquote(fun)
+ end)
+ end
+ end
+
+ def to_locale_list(locale) when is_binary(locale) do
+ locale
+ |> String.split(",")
+ |> Enum.filter(&supports_locale?/1)
+ end
+
+ def to_locale_list(_), do: []
+
+ defmacro with_locale_or_default(locale, do: fun) do
+ quote do
+ Pleroma.Web.Gettext.with_locales_func(
+ Pleroma.Web.Gettext.to_locale_list(unquote(locale))
+ |> Enum.concat(Pleroma.Web.Gettext.get_locales()),
+ fn ->
+ unquote(fun)
+ end
+ )
+ end
+ end
+
+ defp next_locale(locale, list) do
+ index = Enum.find_index(list, fn item -> item == locale end)
+
+ if not is_nil(index) do
+ Enum.at(list, index + 1)
+ else
+ nil
+ end
+ end
+
+ # We do not yet have a proper English translation. The "English"
+ # version is currently but the fallback msgid. However, this
+ # will not work if the user puts English as the first language,
+ # and at the same time specifies other languages, as gettext will
+ # think the English translation is missing, and call
+ # handle_missing_translation functions. This may result in
+ # text in other languages being shown even if English is preferred
+ # by the user.
+ #
+ # To prevent this, we do not allow fallbacking when the current
+ # locale missing a translation is English.
+ defp should_fallback?(locale) do
+ locale != "en"
+ end
+
+ def handle_missing_translation(locale, domain, msgctxt, msgid, bindings) do
+ next = next_locale(locale, get_locales())
+
+ if is_nil(next) or not should_fallback?(locale) do
+ super(locale, domain, msgctxt, msgid, bindings)
+ else
+ {:ok,
+ Gettext.with_locale(next, fn ->
+ Gettext.dpgettext(Pleroma.Web.Gettext, domain, msgctxt, msgid, bindings)
+ end)}
+ end
+ end
+
+ def handle_missing_plural_translation(
+ locale,
+ domain,
+ msgctxt,
+ msgid,
+ msgid_plural,
+ n,
+ bindings
+ ) do
+ next = next_locale(locale, get_locales())
+
+ if is_nil(next) or not should_fallback?(locale) do
+ super(locale, domain, msgctxt, msgid, msgid_plural, n, bindings)
+ else
+ {:ok,
+ Gettext.with_locale(next, fn ->
+ Gettext.dpngettext(
+ Pleroma.Web.Gettext,
+ domain,
+ msgctxt,
+ msgid,
+ msgid_plural,
+ n,
+ bindings
+ )
+ end)}
+ end
+ end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index f15305f9c..50c12a1b1 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -221,6 +221,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
# Note: param name is indeed :discoverable (not an error)
|> Maps.put_if_present(:is_discoverable, params[:discoverable])
|> Maps.put_if_present(:birthday, params[:birthday])
+ |> Maps.put_if_present(:language, Pleroma.Web.Gettext.normalize_locale(params[:language]))
# What happens here:
#
diff --git a/lib/pleroma/web/mastodon_api/controllers/announcement_controller.ex b/lib/pleroma/web/mastodon_api/controllers/announcement_controller.ex
new file mode 100644
index 000000000..080af96d5
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/announcement_controller.ex
@@ -0,0 +1,60 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.AnnouncementController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper,
+ only: [
+ json_response: 3
+ ]
+
+ alias Pleroma.Announcement
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
+ # Mastodon docs say this only requires a user token, no scopes needed
+ # As the op `|` requires at least one scope to be present, we use `&` here.
+ plug(
+ OAuthScopesPlug,
+ %{scopes: [], op: :&}
+ when action in [:index]
+ )
+
+ # Same as in MastodonAPI specs
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write:accounts"]}
+ when action in [:mark_read]
+ )
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AnnouncementOperation
+
+ @doc "GET /api/v1/announcements"
+ def index(%{assigns: %{user: user}} = conn, _params) do
+ render(conn, "index.json", announcements: all_visible(), user: user)
+ end
+
+ def index(conn, _params) do
+ render(conn, "index.json", announcements: all_visible(), user: nil)
+ end
+
+ defp all_visible do
+ Announcement.list_all_visible()
+ end
+
+ @doc "POST /api/v1/announcements/:id/dismiss"
+ def mark_read(%{assigns: %{user: user}} = conn, %{id: id} = _params) do
+ with announcement when not is_nil(announcement) <- Announcement.get_by_id(id),
+ {:ok, _} <- Announcement.mark_read_by(announcement, user) do
+ json_response(conn, :ok, %{})
+ else
+ _ ->
+ {:error, :not_found}
+ end
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/views/announcement_view.ex b/lib/pleroma/web/mastodon_api/views/announcement_view.ex
new file mode 100644
index 000000000..93fdfb1f1
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/views/announcement_view.ex
@@ -0,0 +1,15 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.AnnouncementView do
+ use Pleroma.Web, :view
+
+ def render("index.json", %{announcements: announcements, user: user}) do
+ render_many(announcements, __MODULE__, "show.json", user: user)
+ end
+
+ def render("show.json", %{announcement: announcement, user: user}) do
+ Pleroma.Announcement.render_json(announcement, for: user)
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex
index ee52475d5..62931bd41 100644
--- a/lib/pleroma/web/mastodon_api/views/instance_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex
@@ -17,6 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
uri: Pleroma.Web.Endpoint.url(),
title: Keyword.get(instance, :name),
description: Keyword.get(instance, :description),
+ short_description: Keyword.get(instance, :short_description),
version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})",
email: Keyword.get(instance, :email),
urls: %{
diff --git a/lib/pleroma/web/o_auth/mfa_view.ex b/lib/pleroma/web/o_auth/mfa_view.ex
index e6b142ac0..bcadafe57 100644
--- a/lib/pleroma/web/o_auth/mfa_view.ex
+++ b/lib/pleroma/web/o_auth/mfa_view.ex
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.OAuth.MFAView do
use Pleroma.Web, :view
import Phoenix.HTML.Form
alias Pleroma.MFA
+ alias Pleroma.Web.Gettext
def render("mfa_response.json", %{token: token, user: user}) do
%{
diff --git a/lib/pleroma/web/o_auth/o_auth_view.ex b/lib/pleroma/web/o_auth/o_auth_view.ex
index 6f006b72b..108102ce2 100644
--- a/lib/pleroma/web/o_auth/o_auth_view.ex
+++ b/lib/pleroma/web/o_auth/o_auth_view.ex
@@ -5,6 +5,8 @@
defmodule Pleroma.Web.OAuth.OAuthView do
use Pleroma.Web, :view
import Phoenix.HTML.Form
+ import Phoenix.HTML
+ alias Pleroma.Web.Gettext
alias Pleroma.Web.OAuth.Token.Utils
diff --git a/lib/pleroma/web/plugs/cache.ex b/lib/pleroma/web/plugs/cache.ex
index e2cf5759d..667477857 100644
--- a/lib/pleroma/web/plugs/cache.ex
+++ b/lib/pleroma/web/plugs/cache.ex
@@ -97,13 +97,21 @@ defmodule Pleroma.Web.Plugs.Cache do
key = cache_key(conn, opts)
content_type = content_type(conn)
+ should_cache = not Map.get(conn.assigns, :skip_cache, false)
+
conn =
unless opts[:tracking_fun] do
- @cachex.put(:web_resp_cache, key, {content_type, body}, ttl: ttl)
+ if should_cache do
+ @cachex.put(:web_resp_cache, key, {content_type, body}, ttl: ttl)
+ end
+
conn
else
tracking_fun_data = Map.get(conn.assigns, :tracking_fun_data, nil)
- @cachex.put(:web_resp_cache, key, {content_type, body, tracking_fun_data}, ttl: ttl)
+
+ if should_cache do
+ @cachex.put(:web_resp_cache, key, {content_type, body, tracking_fun_data}, ttl: ttl)
+ end
opts.tracking_fun.(conn, tracking_fun_data)
end
diff --git a/lib/pleroma/web/plugs/set_locale_plug.ex b/lib/pleroma/web/plugs/set_locale_plug.ex
index 850a9b3bc..271912ace 100644
--- a/lib/pleroma/web/plugs/set_locale_plug.ex
+++ b/lib/pleroma/web/plugs/set_locale_plug.ex
@@ -6,18 +6,56 @@
defmodule Pleroma.Web.Plugs.SetLocalePlug do
import Plug.Conn, only: [get_req_header: 2, assign: 3]
+ def frontend_language_cookie_name, do: "userLanguage"
+
def init(_), do: nil
def call(conn, _) do
- locale = get_locale_from_header(conn) || Gettext.get_locale()
- Gettext.put_locale(locale)
- assign(conn, :locale, locale)
+ locales = get_locales_from_header(conn)
+ first_locale = Enum.at(locales, 0, Gettext.get_locale())
+
+ Pleroma.Web.Gettext.put_locales(locales)
+
+ conn
+ |> assign(:locale, first_locale)
+ |> assign(:locales, locales)
end
- defp get_locale_from_header(conn) do
+ defp get_locales_from_header(conn) do
conn
- |> extract_accept_language()
- |> Enum.find(&supported_locale?/1)
+ |> extract_preferred_language()
+ |> normalize_language_codes()
+ |> all_supported()
+ |> Enum.uniq()
+ end
+
+ defp all_supported(locales) do
+ locales
+ |> Pleroma.Web.Gettext.ensure_fallbacks()
+ |> Enum.filter(&supported_locale?/1)
+ end
+
+ defp normalize_language_codes(codes) do
+ codes
+ |> Enum.map(fn code -> Pleroma.Web.Gettext.normalize_locale(code) end)
+ end
+
+ defp extract_preferred_language(conn) do
+ extract_frontend_language(conn) ++ extract_accept_language(conn)
+ end
+
+ defp extract_frontend_language(conn) do
+ %{req_cookies: cookies} =
+ conn
+ |> Plug.Conn.fetch_cookies()
+
+ case cookies[frontend_language_cookie_name()] do
+ nil ->
+ []
+
+ fe_lang ->
+ String.split(fe_lang, ",")
+ end
end
defp extract_accept_language(conn) do
@@ -29,7 +67,6 @@ defmodule Pleroma.Web.Plugs.SetLocalePlug do
|> Enum.sort(&(&1.quality > &2.quality))
|> Enum.map(& &1.tag)
|> Enum.reject(&is_nil/1)
- |> ensure_language_fallbacks()
_ ->
[]
@@ -37,9 +74,7 @@ defmodule Pleroma.Web.Plugs.SetLocalePlug do
end
defp supported_locale?(locale) do
- Pleroma.Web.Gettext
- |> Gettext.known_locales()
- |> Enum.member?(locale)
+ Pleroma.Web.Gettext.supports_locale?(locale)
end
defp parse_language_option(string) do
@@ -53,11 +88,4 @@ defmodule Pleroma.Web.Plugs.SetLocalePlug do
%{tag: captures["tag"], quality: quality}
end
-
- defp ensure_language_fallbacks(tags) do
- Enum.flat_map(tags, fn tag ->
- [language | _] = String.split(tag, "-")
- if Enum.member?(tags, language), do: [tag], else: [tag, language]
- end)
- end
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index ceb6c3cfd..7bbc20275 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -229,6 +229,12 @@ defmodule Pleroma.Web.Router do
post("/frontends/install", FrontendController, :install)
post("/backups", AdminAPIController, :create_backup)
+
+ get("/announcements", AnnouncementController, :index)
+ post("/announcements", AnnouncementController, :create)
+ get("/announcements/:id", AnnouncementController, :show)
+ patch("/announcements/:id", AnnouncementController, :change)
+ delete("/announcements/:id", AnnouncementController, :delete)
end
# AdminAPI: admins and mods (staff) can perform these actions (if enabled by config)
@@ -575,6 +581,9 @@ defmodule Pleroma.Web.Router do
get("/timelines/home", TimelineController, :home)
get("/timelines/direct", TimelineController, :direct)
get("/timelines/list/:list_id", TimelineController, :list)
+
+ get("/announcements", AnnouncementController, :index)
+ post("/announcements/:id/dismiss", AnnouncementController, :mark_read)
end
scope "/api/v1", Pleroma.Web.MastodonAPI do
diff --git a/lib/pleroma/web/templates/email/digest.html.eex b/lib/pleroma/web/templates/email/digest.html.eex
index 60eceff22..1efc76e1a 100644
--- a/lib/pleroma/web/templates/email/digest.html.eex
+++ b/lib/pleroma/web/templates/email/digest.html.eex
@@ -160,7 +160,7 @@
<div
style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height: 14px; color: <%= @styling.header_color %>;">
<p style="line-height: 36px; text-align: center; margin: 0;"><span
- style="font-size: 30px; color: <%= @styling.header_color %>;">Hey <%= @user.nickname %>, here is what you've missed!</span></p>
+ style="font-size: 30px; color: <%= @styling.header_color %>;"><%= Gettext.dpgettext("static_pages", "digest email header line", "Hey %{nickname}, here is what you've missed!", nickname: @user.nickname) %></span></p>
</div>
</div>
<!--[if mso]></td></tr></table><![endif]-->
@@ -382,7 +382,7 @@
<div
style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_color %>;">
<p style="font-size: 12px; line-height: 24px; text-align: center; margin: 0;"><span
- style="font-size: 20px;"><%= length(@followers) %> New Followers</span><span
+ style="font-size: 20px;"><%= Gettext.dpngettext("static_pages", "new followers count header", "%{count} New Follower", "%{count} New Followers", length(@followers), count: length(@followers)) %></span><span
style="font-size: 20px; line-height: 24px;"></span></p>
</div>
</div>
@@ -535,16 +535,16 @@
style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;">
<p
style="font-size: 12px; line-height: 16px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;">
- <span style="font-size: 14px;">You have received this email because you have signed up to receive digest emails from <b><%= @instance %></b> Pleroma instance.</span></p>
+ <span style="font-size: 14px;"><%= raw Gettext.dpgettext("static_pages", "digest email sending reason", "You have received this email because you have signed up to receive digest emails from <b>%{instance}</b> Pleroma instance.", instance: safe_to_string(html_escape(@instance))) %></span></p>
<p
style="font-size: 12px; line-height: 14px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;">
 </p>
<p
style="font-size: 12px; line-height: 16px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;">
- <span style="font-size: 14px;">The email address you are subscribed as is <a href="mailto:<%= @user.email %>" style="color: <%= @styling.link_color %>;text-decoration: none;"><%= @user.email %></a>. </span></p>
+ <span style="font-size: 14px;"><%= raw Gettext.dpgettext("static_pages", "digest email receiver address", "The email address you are subscribed as is <a href='mailto:%{@user.email}' style='color: %{color};text-decoration: none;'>%{email}</a>. ", color: safe_to_string(html_escape(@styling.link_color)), email: safe_to_string(html_escape(@user.email))) %></span></p>
<p
style="font-size: 12px; line-height: 16px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;">
- <span style="font-size: 14px;">To unsubscribe, please go <%= link "here", style: "color: #{@styling.link_color};text-decoration: none;", to: @unsubscribe_link %>.</span></p>
+ <span style="font-size: 14px;"><%= raw Gettext.dpgettext("static_pages", "digest email unsubscribe action", "To unsubscribe, please go %{here}.", here: safe_to_string link(Gettext.dpgettext("static_pages", "digest email unsubscribe action link text", "here"), style: "color: #{@styling.link_color};text-decoration: none;", to: @unsubscribe_link)) %></span></p>
</div>
<!--[if mso]></td></tr></table><![endif]-->
<!--[if (!mso)&(!IE)]><!-->
diff --git a/lib/pleroma/web/templates/feed/feed/tag.atom.eex b/lib/pleroma/web/templates/feed/feed/tag.atom.eex
index de0731085..6d497e84c 100644
--- a/lib/pleroma/web/templates/feed/feed/tag.atom.eex
+++ b/lib/pleroma/web/templates/feed/feed/tag.atom.eex
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
-<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom"
+<feed xml:lang="<%= Gettext.language_tag() %>" xmlns="http://www.w3.org/2005/Atom"
xmlns:thr="http://purl.org/syndication/thread/1.0"
xmlns:georss="http://www.georss.org/georss"
xmlns:activity="http://activitystrea.ms/spec/1.0/"
@@ -12,7 +12,7 @@
<id><%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.rss' %></id>
<title>#<%= @tag %></title>
- <subtitle>These are public toots tagged with #<%= @tag %>. You can interact with them if you have an account anywhere in the fediverse.</subtitle>
+ <subtitle><%= Gettext.dpgettext("static_pages", "tag feed description", "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse.", tag: @tag) %></subtitle>
<logo><%= feed_logo() %></logo>
<updated><%= most_recent_update(@activities) %></updated>
<link rel="self" href="<%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.atom' %>" type="application/atom+xml"/>
diff --git a/lib/pleroma/web/templates/feed/feed/tag.rss.eex b/lib/pleroma/web/templates/feed/feed/tag.rss.eex
index 9c3613feb..edcc3e436 100644
--- a/lib/pleroma/web/templates/feed/feed/tag.rss.eex
+++ b/lib/pleroma/web/templates/feed/feed/tag.rss.eex
@@ -4,7 +4,7 @@
<title>#<%= @tag %></title>
- <description>These are public toots tagged with #<%= @tag %>. You can interact with them if you have an account anywhere in the fediverse.</description>
+ <description><%= Gettext.dpgettext("static_pages", "tag feed description", "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse.", tag: @tag) %></description>
<link><%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.rss' %></link>
<webfeeds:logo><%= feed_logo() %></webfeeds:logo>
<webfeeds:accentColor>2b90d9</webfeeds:accentColor>
diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex
index 1ede59fd8..e33bada85 100644
--- a/lib/pleroma/web/templates/layout/app.html.eex
+++ b/lib/pleroma/web/templates/layout/app.html.eex
@@ -1,5 +1,5 @@
<!DOCTYPE html>
-<html lang="en">
+<html lang="<%= Pleroma.Web.Gettext.language_tag() %>">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui">
diff --git a/lib/pleroma/web/templates/layout/email.html.eex b/lib/pleroma/web/templates/layout/email.html.eex
index f6dcd7f0f..087aa4fc0 100644
--- a/lib/pleroma/web/templates/layout/email.html.eex
+++ b/lib/pleroma/web/templates/layout/email.html.eex
@@ -1,5 +1,5 @@
<!DOCTYPE html>
-<html lang="en">
+<html lang="<%= Pleroma.Web.Gettext.language_tag() %>">
<head>
<meta charset="utf-8">
<title><%= @email.subject %></title>
@@ -7,4 +7,4 @@
<body>
<%= render @view_module, @view_template, assigns %>
</body>
-</html> \ No newline at end of file
+</html>
diff --git a/lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex b/lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex
index 7b476f02d..df090ffcd 100644
--- a/lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex
+++ b/lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex
@@ -1 +1 @@
-<h1>UNSUBSCRIBE FAILURE</h1>
+<h1><%= Gettext.dpgettext("static_pages", "mailer unsubscribe failed message", "UNSUBSCRIBE FAILURE") %></h1>
diff --git a/lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex b/lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex
index 6dfa2c185..cbce495d4 100644
--- a/lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex
+++ b/lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex
@@ -1 +1 @@
-<h1>UNSUBSCRIBE SUCCESSFUL</h1>
+<h1><%= Gettext.dpgettext("static_pages", "mailer unsubscribe successful message", "UNSUBSCRIBE SUCCESSFUL") %></h1>
diff --git a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex
index b9daa8d8b..e45d13bdf 100644
--- a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex
+++ b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex
@@ -5,11 +5,11 @@
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
<% end %>
-<h2>Two-factor recovery</h2>
+<h2><%= Gettext.dpgettext("static_pages", "mfa recover page title", "Two-factor recovery") %></h2>
<%= form_for @conn, Routes.mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
<div class="input">
- <%= label f, :code, "Recovery code" %>
+ <%= label f, :code, Gettext.dpgettext("static_pages", "mfa recover recovery code prompt", "Recovery code") %>
<%= text_input f, :code, [autocomplete: false, autocorrect: "off", autocapitalize: "off", autofocus: true, spellcheck: false] %>
<%= hidden_input f, :mfa_token, value: @mfa_token %>
<%= hidden_input f, :state, value: @state %>
@@ -17,8 +17,8 @@
<%= hidden_input f, :challenge_type, value: "recovery" %>
</div>
-<%= submit "Verify" %>
+<%= submit Gettext.dpgettext("static_pages", "mfa recover verify recovery code button", "Verify") %>
<% end %>
<a href="<%= Routes.mfa_path(@conn, :show, %{challenge_type: "totp", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>">
- Enter a two-factor code
+ <%= Gettext.dpgettext("static_pages", "mfa recover use 2fa code link", "Enter a two-factor code") %>
</a>
diff --git a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex
index 27600253c..50e6c04b6 100644
--- a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex
+++ b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex
@@ -5,11 +5,11 @@
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
<% end %>
-<h2>Two-factor authentication</h2>
+<h2><%= Gettext.dpgettext("static_pages", "mfa auth page title", "Two-factor authentication") %></h2>
<%= form_for @conn, Routes.mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
<div class="input">
- <%= label f, :code, "Authentication code" %>
+ <%= label f, :code, Gettext.dpgettext("static_pages", "mfa auth code prompt", "Authentication code") %>
<%= text_input f, :code, [autocomplete: "one-time-code", autocorrect: "off", autocapitalize: "off", autofocus: true, pattern: "[0-9]*", spellcheck: false] %>
<%= hidden_input f, :mfa_token, value: @mfa_token %>
<%= hidden_input f, :state, value: @state %>
@@ -17,8 +17,8 @@
<%= hidden_input f, :challenge_type, value: "totp" %>
</div>
-<%= submit "Verify" %>
+<%= submit Gettext.dpgettext("static_pages", "mfa auth verify code button", "Verify") %>
<% end %>
<a href="<%= Routes.mfa_path(@conn, :show, %{challenge_type: "recovery", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>">
- Enter a two-factor recovery code
+ <%= Gettext.dpgettext("static_pages", "mfa auth page use recovery code link", "Enter a two-factor recovery code") %>
</a>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex
index c9ec1ecbf..73115e92a 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex
@@ -1,5 +1,5 @@
<div class="scopes-input">
- <%= label @form, :scope, "The following permissions will be granted" %>
+ <%= label @form, :scope, Gettext.dpgettext("static_pages", "oauth scopes message", "The following permissions will be granted") %>
<div class="scopes">
<%= for scope <- @available_scopes do %>
<%# Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted %>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
index dc4521a62..8b894cd58 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
@@ -1,4 +1,4 @@
-<h2>Sign in with external provider</h2>
+<h2><%= Gettext.dpgettext("static_pages", "oauth external provider page title", "Sign in with external provider") %></h2>
<%= form_for @conn, Routes.o_auth_path(@conn, :prepare_request), [as: "authorization", method: "get"], fn f -> %>
<div style="display: none">
@@ -10,6 +10,6 @@
<%= hidden_input f, :state, value: @state %>
<%= for strategy <- Pleroma.Config.oauth_consumer_strategies() do %>
- <%= submit "Sign in with #{String.capitalize(strategy)}", name: "provider", value: strategy %>
+ <%= submit Gettext.dpgettext("static_pages", "oauth external provider sign in button", "Sign in with %{strategy}", strategy: String.capitalize(strategy)), name: "provider", value: strategy %>
<% end %>
<% end %>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex
index ffabe29a6..76ed3fda5 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex
@@ -1,2 +1,2 @@
-<h1>Successfully authorized</h1>
-<h2>Token code is <br><%= @auth.token %></h2>
+<h1><%= Gettext.dpgettext("static_pages", "oauth authorized page title", "Successfully authorized") %></h1>
+<h2><%= raw Gettext.dpgettext("static_pages", "oauth token code message", "Token code is <br>%{token}", token: safe_to_string(html_escape(@auth.token))) %></h2>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex
index 82785c4b9..754bf2eb0 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex
@@ -1,2 +1,2 @@
-<h1>Authorization exists</h1>
-<h2>Access token is <br><%= @token.token %></h2>
+<h1><%= Gettext.dpgettext("static_pages", "oauth authorization exists page title", "Authorization exists") %></h1>
+<h2><%= raw Gettext.dpgettext("static_pages", "oauth token code message", "Token code is <br>%{token}", token: safe_to_string(html_escape(@token.token))) %></h2>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
index 3ac428b2f..1f661efb2 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
@@ -5,34 +5,34 @@
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
<% end %>
-<h2>Registration Details</h2>
+<h2><%= Gettext.dpgettext("static_pages", "oauth register page title", "Registration Details") %></h2>
-<p>If you'd like to register a new account, please provide the details below.</p>
+<p><%= Gettext.dpgettext("static_pages", "oauth register page fill form prompt", "If you'd like to register a new account, please provide the details below.") %></p>
<%= form_for @conn, Routes.o_auth_path(@conn, :register), [as: "authorization"], fn f -> %>
<div class="input">
- <%= label f, :nickname, "Nickname" %>
+ <%= label f, :nickname, Gettext.dpgettext("static_pages", "oauth register page nickname prompt", "Nickname") %>
<%= text_input f, :nickname, value: @nickname, autocomplete: "username" %>
</div>
<div class="input">
- <%= label f, :email, "Email" %>
+ <%= label f, :email, Gettext.dpgettext("static_pages", "oauth register page email prompt", "Email") %>
<%= text_input f, :email, value: @email, autocomplete: "email" %>
</div>
-<%= submit "Proceed as new user", name: "op", value: "register" %>
+<%= submit Gettext.dpgettext("static_pages", "oauth register page register button", "Proceed as new user"), name: "op", value: "register" %>
-<p>Alternatively, sign in to connect to existing account.</p>
+<p><%= Gettext.dpgettext("static_pages", "oauth register page login prompt", "Alternatively, sign in to connect to existing account.") %></p>
<div class="input">
- <%= label f, :name, "Name or email" %>
+ <%= label f, :name, Gettext.dpgettext("static_pages", "oauth register page login username prompt", "Name or email") %>
<%= text_input f, :name, autocomplete: "username" %>
</div>
<div class="input">
- <%= label f, :password, "Password" %>
+ <%= label f, :password, Gettext.dpgettext("static_pages", "oauth register page login password prompt", "Password") %>
<%= password_input f, :password, autocomplete: "password" %>
</div>
-<%= submit "Proceed as existing user", name: "op", value: "connect" %>
+<%= submit Gettext.dpgettext("static_pages", "oauth register page login button", "Proceed as existing user"), name: "op", value: "connect" %>
<%= hidden_input f, :client_id, value: @client_id %>
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
index d63da6c1d..a2f41618e 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
@@ -20,21 +20,23 @@
<div class="container__content">
<%= if @app do %>
- <p>Application <strong><%= @app.client_name %></strong> is requesting access to your account.</p>
+ <p><%= raw Gettext.dpgettext("static_pages", "oauth authorize message", "Application <strong>%{client_name}</strong> is requesting access to your account.", client_name: safe_to_string(html_escape(@app.client_name))) %></p>
<%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %>
<% end %>
<%= if @user do %>
<div class="actions">
- <a class="button button--cancel" href="/">Cancel</a>
- <%= submit "Approve", class: "button--approve" %>
+ <a class="button button--cancel" href="/">
+ <%= Gettext.dpgettext("static_pages", "oauth authorize cancel button", "Cancel") %>
+ </a>
+ <%= submit Gettext.dpgettext("static_pages", "oauth authorize approve button", "Approve"), class: "button--approve" %>
</div>
<% else %>
<%= if @params["registration"] in ["true", true] do %>
- <h3>This is the first time you visit! Please enter your Pleroma handle.</h3>
- <p>Choose carefully! You won't be able to change this later. You will be able to change your display name, though.</p>
+ <h3><%= Gettext.dpgettext("static_pages", "oauth register page title", "This is the first time you visit! Please enter your Pleroma handle.") %></h3>
+ <p><%= Gettext.dpgettext("static_pages", "oauth register nickname unchangeable warning", "Choose carefully! You won't be able to change this later. You will be able to change your display name, though.") %></p>
<div class="input">
- <%= label f, :nickname, "Pleroma Handle" %>
+ <%= label f, :nickname, Gettext.dpgettext("static_pages", "oauth register nickname prompt", "Pleroma Handle") %>
<%= text_input f, :nickname, placeholder: "lain", autocomplete: "username" %>
</div>
<%= hidden_input f, :name, value: @params["name"] %>
@@ -42,14 +44,14 @@
<br>
<% else %>
<div class="input">
- <%= label f, :name, "Username" %>
+ <%= label f, :name, Gettext.dpgettext("static_pages", "oauth login username prompt", "Username") %>
<%= text_input f, :name %>
</div>
<div class="input">
- <%= label f, :password, "Password" %>
+ <%= label f, :password, Gettext.dpgettext("static_pages", "oauth login password prompt", "Password") %>
<%= password_input f, :password %>
</div>
- <%= submit "Log In" %>
+ <%= submit Gettext.dpgettext("static_pages", "oauth login button", "Log In") %>
<% end %>
<% end %>
</div>
diff --git a/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex b/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex
index 3191bf450..a14ca305e 100644
--- a/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex
+++ b/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex
@@ -5,7 +5,7 @@
<form class="pull-right collapse" method="POST" action="<%= Helpers.util_path(@conn, :remote_subscribe) %>">
<input type="hidden" name="nickname" value="<%= @user.nickname %>">
<input type="hidden" name="profile" value="">
- <button type="submit" class="collapse">Remote follow</button>
+ <button type="submit" class="collapse"><%= Gettext.dpgettext("static_pages", "static fe profile page remote follow button", "Remote follow") %></button>
</form>
<%= raw Formatter.emojify(@user.name, @user.emoji) %> |
<%= link "@#{@user.nickname}@#{Endpoint.host()}", to: (@user.uri || @user.ap_id) %>
diff --git a/lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex b/lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex
index ee84750c7..5ac0aa4e0 100644
--- a/lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex
@@ -1 +1 @@
-<h2>Invalid Token</h2>
+<h2><%= Gettext.dpgettext("static_pages", "password reset invalid token message", "Invalid Token") %></h2>
diff --git a/lib/pleroma/web/templates/twitter_api/password/reset.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset.html.eex
index fbcacdc14..6a544af51 100644
--- a/lib/pleroma/web/templates/twitter_api/password/reset.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/password/reset.html.eex
@@ -1,13 +1,13 @@
<h2>Password Reset for <%= @user.nickname %></h2>
<%= form_for @conn, Routes.reset_password_path(@conn, :do_reset), [as: "data"], fn f -> %>
<div class="form-row">
- <%= label f, :password, "Password" %>
+ <%= label f, :password, Gettext.dpgettext("static_pages", "password reset form password prompt", "Password") %>
<%= password_input f, :password %>
</div>
<div class="form-row">
- <%= label f, :password_confirmation, "Confirmation" %>
+ <%= label f, :password_confirmation, Gettext.dpgettext("static_pages", "password reset form confirm password prompt", "Confirmation") %>
<%= password_input f, :password_confirmation %>
</div>
<%= hidden_input f, :token, value: @token.token %>
- <%= submit "Reset" %>
+ <%= submit Gettext.dpgettext("static_pages", "password reset button", "Reset") %>
<% end %>
diff --git a/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex
index 4ed4ac8bc..774e3462a 100644
--- a/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex
@@ -1,2 +1,6 @@
-<h2>Password reset failed</h2>
-<h3><a href="<%= Pleroma.Web.Endpoint.url() %>">Homepage</a></h3>
+<h2><%= Gettext.dpgettext("static_pages", "password reset failed message", "Password reset failed") %></h2>
+<h3>
+ <a href="<%= Pleroma.Web.Endpoint.url() %>">
+ <%= Gettext.dpgettext("static_pages", "password reset failed homepage link", "Homepage") %>
+ </a>
+</h3>
diff --git a/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex
index 086d4e08b..40f6bb3fc 100644
--- a/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex
@@ -1,2 +1,2 @@
-<h2>Password changed!</h2>
-<h3><a href="<%= Pleroma.Web.Endpoint.url() %>">Homepage</a></h3>
+<h2><%= Gettext.dpgettext("static_pages", "password reset successful message", "Password changed!") %></h2>
+<h3><a href="<%= Pleroma.Web.Endpoint.url() %>"><%= Gettext.dpgettext("static_pages", "password reset successful homepage link", "Homepage") %></a></h3>
diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex
index a7be53091..e2d251fac 100644
--- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex
@@ -1,11 +1,11 @@
<%= if @error == :error do %>
- <h2>Error fetching user</h2>
+ <h2><%= Gettext.dpgettext("static_pages", "remote follow error", "Error fetching user") %></h2>
<% else %>
- <h2>Remote follow</h2>
+ <h2><%= Gettext.dpgettext("static_pages", "remote follow header", "Remote follow") %></h2>
<img height="128" width="128" src="<%= avatar_url(@followee) %>">
<p><%= @followee.nickname %></p>
<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "user"], fn f -> %>
<%= hidden_input f, :id, value: @followee.id %>
- <%= submit "Authorize" %>
+ <%= submit Gettext.dpgettext("static_pages", "remote follow authorization button", "Authorize") %>
<% end %>
<% end %>
diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex
index bc5fb28e3..26340a906 100644
--- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex
@@ -1,14 +1,14 @@
<%= if @error do %>
<h2><%= @error %></h2>
<% end %>
-<h2>Log in to follow</h2>
+<h2><%= Gettext.dpgettext("static_pages", "remote follow header, need login", "Log in to follow") %></h2>
<p><%= @followee.nickname %></p>
<img height="128" width="128" src="<%= avatar_url(@followee) %>">
<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "authorization"], fn f -> %>
-<%= text_input f, :name, placeholder: "Username", required: true, autocomplete: "username" %>
+<%= text_input f, :name, placeholder: Gettext.dpgettext("static_pages", "placeholder text for username entry", "Username"), required: true, autocomplete: "username" %>
<br>
-<%= password_input f, :password, placeholder: "Password", required: true, autocomplete: "password" %>
+<%= password_input f, :password, placeholder: Gettext.dpgettext("static_pages", "placeholder text for password entry", "Password"), required: true, autocomplete: "password" %>
<br>
<%= hidden_input f, :id, value: @followee.id %>
-<%= submit "Authorize" %>
+<%= submit Gettext.dpgettext("static_pages", "remote follow authorization button for login", "Authorize") %>
<% end %>
diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex
index a54ed83b5..638212c1e 100644
--- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex
@@ -1,13 +1,13 @@
<%= if @error do %>
<h2><%= @error %></h2>
<% end %>
-<h2>Two-factor authentication</h2>
+<h2><%= Gettext.dpgettext("static_pages", "remote follow mfa header", "Two-factor authentication") %></h2>
<p><%= @followee.nickname %></p>
<img height="128" width="128" src="<%= avatar_url(@followee) %>">
<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "mfa"], fn f -> %>
-<%= text_input f, :code, placeholder: "Authentication code", required: true %>
+<%= text_input f, :code, placeholder: Gettext.dpgettext("static_pages", "placeholder text for auth code entry", "Authentication code"), required: true %>
<br>
<%= hidden_input f, :id, value: @followee.id %>
<%= hidden_input f, :token, value: @mfa_token %>
-<%= submit "Authorize" %>
+<%= submit Gettext.dpgettext("static_pages", "remote follow authorization button for mfa", "Authorize") %>
<% end %>
diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex
index da473d502..2fb4cc5d3 100644
--- a/lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex
@@ -1,6 +1,5 @@
<%= if @error do %>
-<p>Error following account</p>
+<p><%= Gettext.dpgettext("static_pages", "remote follow error", "Error following account") %></p>
<% else %>
-<h2>Account followed!</h2>
+<h2><%= Gettext.dpgettext("static_pages", "remote follow success", "Account followed!") %></h2>
<% end %>
-
diff --git a/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex b/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex
index a6b313d8a..848660f26 100644
--- a/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex
@@ -1,10 +1,10 @@
<%= if @error do %>
- <h2>Error: <%= @error %></h2>
+ <h2><%= Gettext.dpgettext("static_pages", "remote follow error", "Error: %{error}", error: @error) %></h2>
<% else %>
- <h2>Remotely follow <%= @nickname %></h2>
+ <h2><%= Gettext.dpgettext("static_pages", "remote follow header", "Remotely follow %{nickname}", nickname: @nickname) %></h2>
<%= form_for @conn, Routes.util_path(@conn, :remote_subscribe), [as: "user"], fn f -> %>
<%= hidden_input f, :nickname, value: @nickname %>
- <%= text_input f, :profile, placeholder: "Your account ID, e.g. lain@quitter.se" %>
- <%= submit "Follow" %>
+ <%= text_input f, :profile, placeholder: Gettext.dpgettext("static_pages", "placeholder text for account id", "Your account ID, e.g. lain@quitter.se") %>
+ <%= submit Gettext.dpgettext("static_pages", "remote follow authorization button for following with a remote account", "Follow") %>
<% end %>
<% end %>
diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex
index 02b76da32..ef2eb75f4 100644
--- a/lib/pleroma/web/twitter_api/twitter_api.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api.ex
@@ -12,6 +12,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
alias Pleroma.UserInviteToken
def register_user(params, opts \\ []) do
+ fallback_language = Gettext.get_locale()
+
params =
params
|> Map.take([:email, :token, :password])
@@ -21,6 +23,10 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|> Map.put(:password_confirmation, params[:password])
|> Map.put(:registration_reason, params[:reason])
|> Map.put(:birthday, params[:birthday])
+ |> Map.put(
+ :language,
+ Pleroma.Web.Gettext.normalize_locale(params[:language]) || fallback_language
+ )
if Pleroma.Config.get([:instance, :registrations_open]) do
create_user(params, opts)
diff --git a/lib/pleroma/web/twitter_api/views/password_view.ex b/lib/pleroma/web/twitter_api/views/password_view.ex
index e6bef19dc..55790941f 100644
--- a/lib/pleroma/web/twitter_api/views/password_view.ex
+++ b/lib/pleroma/web/twitter_api/views/password_view.ex
@@ -5,4 +5,5 @@
defmodule Pleroma.Web.TwitterAPI.PasswordView do
use Pleroma.Web, :view
import Phoenix.HTML.Form
+ alias Pleroma.Web.Gettext
end
diff --git a/lib/pleroma/web/twitter_api/views/remote_follow_view.ex b/lib/pleroma/web/twitter_api/views/remote_follow_view.ex
index 93993cf40..8902261b0 100644
--- a/lib/pleroma/web/twitter_api/views/remote_follow_view.ex
+++ b/lib/pleroma/web/twitter_api/views/remote_follow_view.ex
@@ -5,6 +5,11 @@
defmodule Pleroma.Web.TwitterAPI.RemoteFollowView do
use Pleroma.Web, :view
import Phoenix.HTML.Form
+ alias Pleroma.Web.Gettext
- defdelegate avatar_url(user), to: Pleroma.User
+ def avatar_url(user) do
+ user
+ |> Pleroma.User.avatar_url()
+ |> Pleroma.Web.MediaProxy.url()
+ end
end
diff --git a/lib/pleroma/web/twitter_api/views/util_view.ex b/lib/pleroma/web/twitter_api/views/util_view.ex
index 2b9005dae..69f243097 100644
--- a/lib/pleroma/web/twitter_api/views/util_view.ex
+++ b/lib/pleroma/web/twitter_api/views/util_view.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilView do
import Phoenix.HTML.Form
alias Pleroma.Config
alias Pleroma.Web.Endpoint
+ alias Pleroma.Web.Gettext
def status_net_config(instance) do
"""
diff --git a/lib/pleroma/web/views/email_view.ex b/lib/pleroma/web/views/email_view.ex
index c28ffaed2..9ab708212 100644
--- a/lib/pleroma/web/views/email_view.ex
+++ b/lib/pleroma/web/views/email_view.ex
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.EmailView do
use Pleroma.Web, :view
import Phoenix.HTML
import Phoenix.HTML.Link
+ alias Pleroma.Web.Gettext
def avatar_url(user) do
Pleroma.User.avatar_url(user)
diff --git a/lib/pleroma/web/views/mailer/subscription_view.ex b/lib/pleroma/web/views/mailer/subscription_view.ex
index dd112dbbb..037fb6578 100644
--- a/lib/pleroma/web/views/mailer/subscription_view.ex
+++ b/lib/pleroma/web/views/mailer/subscription_view.ex
@@ -4,4 +4,5 @@
defmodule Pleroma.Web.Mailer.SubscriptionView do
use Pleroma.Web, :view
+ alias Pleroma.Web.Gettext
end
diff --git a/mix.exs b/mix.exs
index 4387cb0aa..6e84fe482 100644
--- a/mix.exs
+++ b/mix.exs
@@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
def project do
[
app: :pleroma,
- version: version("2.4.51"),
+ version: version("2.4.52"),
elixir: "~> 1.9",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
@@ -124,7 +124,10 @@ defmodule Pleroma.Mixfile do
{:ecto_sql, "~> 3.6.2"},
{:postgrex, ">= 0.15.5"},
{:oban, "~> 2.3.4"},
- {:gettext, "~> 0.18"},
+ {:gettext,
+ git: "https://github.com/tusooa/gettext.git",
+ ref: "72fb2496b6c5280ed911bdc3756890e7f38a4808",
+ override: true},
{:bcrypt_elixir, "~> 2.2"},
{:trailing_format_plug, "~> 0.0.7"},
{:fast_sanitize, "~> 0.2.0"},
@@ -142,7 +145,7 @@ defmodule Pleroma.Mixfile do
{:mogrify, "~> 0.9.1"},
{:ex_aws, "~> 2.1.6"},
{:ex_aws_s3, "~> 2.0"},
- {:sweet_xml, "~> 0.6.6"},
+ {:sweet_xml, "~> 0.7.2"},
{:earmark, "~> 1.4.15"},
{:bbcode_pleroma, "~> 0.2.0"},
{:crypt,
@@ -191,7 +194,7 @@ defmodule Pleroma.Mixfile do
ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"},
{:restarter, path: "./restarter"},
{:majic, "~> 1.0"},
- {:eblurhash, "~> 1.1.0"},
+ {:eblurhash, "~> 1.2.2"},
{:open_api_spex, "~> 3.10"},
{:phoenix_live_dashboard, "~> 0.6.2"},
{:ecto_psql_extras, "~> 0.6"},
@@ -243,9 +246,10 @@ defmodule Pleroma.Mixfile do
identifier_filter = ~r/[^0-9a-z\-]+/i
git_available? = match?({_output, 0}, System.cmd("sh", ["-c", "command -v git"]))
+ dotgit_present? = File.exists?(".git")
git_pre_release =
- if git_available? do
+ if git_available? and dotgit_present? do
{tag, tag_err} =
System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true)
@@ -272,6 +276,7 @@ defmodule Pleroma.Mixfile do
# Branch name as pre-release version component, denoted with a dot
branch_name =
with true <- git_available?,
+ true <- dotgit_present?,
{branch_name, 0} <- System.cmd("git", ["rev-parse", "--abbrev-ref", "HEAD"]),
branch_name <- String.trim(branch_name),
branch_name <- System.get_env("PLEROMA_BUILD_BRANCH") || branch_name,
diff --git a/mix.lock b/mix.lock
index 817240538..14e43c703 100644
--- a/mix.lock
+++ b/mix.lock
@@ -29,7 +29,7 @@
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
"earmark": {:hex, :earmark, "1.4.18", "618c4ff1563450d1832b7fb41dc6755e470f91a6fd4c70f350a58b14f64a7db8", [:mix], [{:earmark_parser, ">= 1.4.17", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "57ac3b6da3958ed09c669a9b159e86377fcccda56bacde8a209fa4dcdef52560"},
"earmark_parser": {:hex, :earmark_parser, "1.4.17", "6f3c7e94170377ba45241d394389e800fb15adc5de51d0a3cd52ae766aafd63f", [:mix], [], "hexpm", "f93ac89c9feca61c165b264b5837bf82344d13bebc634cd575cb711e2e342023"},
- "eblurhash": {:hex, :eblurhash, "1.1.0", "e10ccae762598507ebfacf0b645ed49520f2afa3e7e9943e73a91117dffce415", [:rebar3], [], "hexpm", "2e6b889d09fddd374e3c5ac57c486138768763264e99ac1074ae5fa7fc9ab51d"},
+ "eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"},
"ecto": {:hex, :ecto, "3.6.2", "efdf52acfc4ce29249bab5417415bd50abd62db7b0603b8bab0d7b996548c2bc", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "efad6dfb04e6f986b8a3047822b0f826d9affe8e4ebdd2aeedbfcb14fd48884e"},
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
"ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.4", "5d43fd088d39a158c860b17e8d210669587f63ec89ea122a4654861c8c6e2db4", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.15.7", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "311db02f1b772e3d0dc7f56a05044b5e1499d78ed6abf38885e1ca70059449e5"},
@@ -55,7 +55,7 @@
"gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"},
"gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm"},
"gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"},
- "gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"},
+ "gettext": {:git, "https://github.com/tusooa/gettext.git", "72fb2496b6c5280ed911bdc3756890e7f38a4808", [ref: "72fb2496b6c5280ed911bdc3756890e7f38a4808"]},
"gun": {:hex, :gun, "2.0.0-rc.2", "7c489a32dedccb77b6e82d1f3c5a7dadfbfa004ec14e322cdb5e579c438632d2", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "6b9d1eae146410d727140dbf8b404b9631302ecc2066d1d12f22097ad7d254fc"},
"hackney": {:hex, :hackney, "1.18.0", "c4443d960bb9fba6d01161d01cd81173089686717d9490e5d3606644c48d121f", [:rebar3], [{:certifi, "~>2.8.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "9afcda620704d720db8c6a3123e9848d09c87586dc1c10479c42627b905b5c5e"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
@@ -120,7 +120,7 @@
"remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]},
"sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
- "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"},
+ "sweet_xml": {:hex, :sweet_xml, "0.7.2", "4729f997286811fabdd8288f8474e0840a76573051062f066c4b597e76f14f9f", [:mix], [], "hexpm", "6894e68a120f454534d99045ea3325f7740ea71260bc315f82e29731d570a6e8"},
"swoosh": {:hex, :swoosh, "1.3.11", "34f79c57f19892b43bd2168de9ff5de478a721a26328ef59567aad4243e7a77b", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "f1e2a048db454f9982b9cf840f75e7399dd48be31ecc2a7dc10012a803b913af"},
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
"table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"},
diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot
new file mode 100644
index 000000000..fed111ccb
--- /dev/null
+++ b/priv/gettext/default.pot
@@ -0,0 +1,185 @@
+## This file is a PO Template file.
+##
+## "msgid"s here are often extracted from source code.
+## Add new translations manually only if they're dynamic
+## translations that can't be statically extracted.
+##
+## Run "mix gettext.extract" to bring this file up to
+## date. Leave "msgstr"s empty as changing them here as no
+## effect: edit them in PO (.po) files instead.
+msgid ""
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:122
+msgid "%{name} - %{count} is not a multiple of %{multiple}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:131
+msgid "%{name} - %{value} is larger than exclusive maximum %{max}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:140
+msgid "%{name} - %{value} is larger than inclusive maximum %{max}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:149
+msgid "%{name} - %{value} is smaller than exclusive minimum %{min}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:158
+msgid "%{name} - %{value} is smaller than inclusive minimum %{min}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:102
+msgid "%{name} - Array items must be unique."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:114
+msgid "%{name} - Array length %{length} is larger than maxItems: %{}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:106
+msgid "%{name} - Array length %{length} is smaller than minItems: %{min}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:166
+msgid "%{name} - Invalid %{type}. Got: %{value}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:174
+msgid "%{name} - Invalid format. Expected %{format}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:51
+msgid "%{name} - Invalid schema.type. Got: %{type}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:178
+msgid "%{name} - Invalid value for enum."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:95
+msgid "%{name} - String length is larger than maxLength: %{length}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:88
+msgid "%{name} - String length is smaller than minLength: %{length}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:63
+msgid "%{name} - null value where %{type} expected."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:60
+msgid "%{name} - null value."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:182
+msgid "Failed to cast to any schema in %{polymorphic_type}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:71
+msgid "Failed to cast value as %{invalid_schema}. Value must be castable using `allOf` schemas listed."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:84
+msgid "Failed to cast value to one of: %{failed_schemas}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:78
+msgid "Failed to cast value using any of: %{failed_schemas}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:212
+msgid "Invalid value for header: %{name}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:204
+msgid "Missing field: %{name}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:208
+msgid "Missing header: %{name}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:196
+msgid "No value provided for required discriminator `%{field}`."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:216
+msgid "Object property count %{property_count} is greater than maxProperties: %{max_properties}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:224
+msgid "Object property count %{property_count} is less than minProperties: %{min_properties}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/static_fe/static_fe/error.html.eex:2
+msgid "Oops"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:188
+msgid "Unexpected field: %{name}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:200
+msgid "Unknown schema: %{name}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:192
+msgid "Value used as discriminator for `%{field}` matches no schemas."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/embed/show.html.eex:43
+#: lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex:37
+msgid "announces"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/embed/show.html.eex:44
+#: lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex:38
+msgid "likes"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/embed/show.html.eex:42
+#: lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex:36
+msgid "replies"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/embed/show.html.eex:27
+#: lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex:22
+msgid "sensitive media"
+msgstr ""
diff --git a/priv/gettext/en_test/LC_MESSAGES/default.po b/priv/gettext/en_test/LC_MESSAGES/default.po
new file mode 100644
index 000000000..63db74608
--- /dev/null
+++ b/priv/gettext/en_test/LC_MESSAGES/default.po
@@ -0,0 +1,186 @@
+## "msgid"s in this file come from POT (.pot) files.
+##
+## Do not add, change, or remove "msgid"s manually here as
+## they're tied to the ones in the corresponding POT file
+## (with the same domain).
+##
+## Use "mix gettext.extract --merge" or "mix gettext.merge"
+## to merge POT files into PO files.
+msgid ""
+msgstr ""
+"Language: en_test\n"
+"Plural-Forms: nplurals=2\n"
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:122
+msgid "%{name} - %{count} is not a multiple of %{multiple}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:131
+msgid "%{name} - %{value} is larger than exclusive maximum %{max}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:140
+msgid "%{name} - %{value} is larger than inclusive maximum %{max}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:149
+msgid "%{name} - %{value} is smaller than exclusive minimum %{min}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:158
+msgid "%{name} - %{value} is smaller than inclusive minimum %{min}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:102
+msgid "%{name} - Array items must be unique."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:114
+msgid "%{name} - Array length %{length} is larger than maxItems: %{}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:106
+msgid "%{name} - Array length %{length} is smaller than minItems: %{min}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:166
+msgid "%{name} - Invalid %{type}. Got: %{value}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:174
+msgid "%{name} - Invalid format. Expected %{format}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:51
+msgid "%{name} - Invalid schema.type. Got: %{type}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:178
+msgid "%{name} - Invalid value for enum."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:95
+msgid "%{name} - String length is larger than maxLength: %{length}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:88
+msgid "%{name} - String length is smaller than minLength: %{length}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:63
+msgid "%{name} - null value where %{type} expected."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:60
+msgid "%{name} - null value."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:182
+msgid "Failed to cast to any schema in %{polymorphic_type}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:71
+msgid "Failed to cast value as %{invalid_schema}. Value must be castable using `allOf` schemas listed."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:84
+msgid "Failed to cast value to one of: %{failed_schemas}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:78
+msgid "Failed to cast value using any of: %{failed_schemas}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:212
+msgid "Invalid value for header: %{name}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:204
+msgid "Missing field: %{name}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:208
+msgid "Missing header: %{name}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:196
+msgid "No value provided for required discriminator `%{field}`."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:216
+msgid "Object property count %{property_count} is greater than maxProperties: %{max_properties}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:224
+msgid "Object property count %{property_count} is less than minProperties: %{min_properties}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/static_fe/static_fe/error.html.eex:2
+msgid "Oops"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:188
+msgid "Unexpected field: %{name}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:200
+msgid "Unknown schema: %{name}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:192
+msgid "Value used as discriminator for `%{field}` matches no schemas."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/embed/show.html.eex:43
+#: lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex:37
+msgid "announces"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/embed/show.html.eex:44
+#: lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex:38
+msgid "likes"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/embed/show.html.eex:42
+#: lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex:36
+msgid "replies"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/embed/show.html.eex:27
+#: lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex:22
+msgid "sensitive media"
+msgstr ""
diff --git a/priv/gettext/en_test/LC_MESSAGES/errors.po b/priv/gettext/en_test/LC_MESSAGES/errors.po
new file mode 100644
index 000000000..a40de7f8b
--- /dev/null
+++ b/priv/gettext/en_test/LC_MESSAGES/errors.po
@@ -0,0 +1,557 @@
+## "msgid"s in this file come from POT (.pot) files.
+##
+## Do not add, change, or remove "msgid"s manually here as
+## they're tied to the ones in the corresponding POT file
+## (with the same domain).
+##
+## Use "mix gettext.extract --merge" or "mix gettext.merge"
+## to merge POT files into PO files.
+msgid ""
+msgstr ""
+"Language: en_test\n"
+"Plural-Forms: nplurals=2\n"
+
+msgid "can't be blank"
+msgstr ""
+
+msgid "has already been taken"
+msgstr ""
+
+msgid "is invalid"
+msgstr ""
+
+msgid "has invalid format"
+msgstr ""
+
+msgid "has an invalid entry"
+msgstr ""
+
+msgid "is reserved"
+msgstr ""
+
+msgid "does not match confirmation"
+msgstr ""
+
+msgid "is still associated with this entry"
+msgstr ""
+
+msgid "are still associated with this entry"
+msgstr ""
+
+msgid "should be %{count} character(s)"
+msgid_plural "should be %{count} character(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "should have %{count} item(s)"
+msgid_plural "should have %{count} item(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "should be at least %{count} character(s)"
+msgid_plural "should be at least %{count} character(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "should have at least %{count} item(s)"
+msgid_plural "should have at least %{count} item(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "should be at most %{count} character(s)"
+msgid_plural "should be at most %{count} character(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "should have at most %{count} item(s)"
+msgid_plural "should have at most %{count} item(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "must be less than %{number}"
+msgstr ""
+
+msgid "must be greater than %{number}"
+msgstr ""
+
+msgid "must be less than or equal to %{number}"
+msgstr ""
+
+msgid "must be greater than or equal to %{number}"
+msgstr ""
+
+msgid "must be equal to %{number}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api.ex:523
+msgid "Account not found"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api.ex:316
+msgid "Already voted"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:402
+msgid "Bad request"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/controller_helper.ex:97
+#: lib/pleroma/web/controller_helper.ex:103
+msgid "Can't display this activity"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:324
+msgid "Can't find user"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:80
+msgid "Can't get favorites"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:482
+msgid "Cannot post an empty status without attachments"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:441
+msgid "Comment must be up to %{max_size} characters"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/config_db.ex:200
+msgid "Config with params %{params} not found"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api.ex:167 lib/pleroma/web/common_api.ex:171
+msgid "Could not delete"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api.ex:217
+msgid "Could not favorite"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api.ex:254
+msgid "Could not unfavorite"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api.ex:202
+msgid "Could not unrepeat"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api.ex:530 lib/pleroma/web/common_api.ex:539
+msgid "Could not update state"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:205
+msgid "Error."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/twitter_api/twitter_api.ex:99
+msgid "Invalid CAPTCHA"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:144
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:631
+msgid "Invalid credentials"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/plugs/ensure_authenticated_plug.ex:42
+msgid "Invalid credentials."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api.ex:337
+msgid "Invalid indices"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:29
+msgid "Invalid parameters"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:349
+msgid "Invalid password."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:254
+msgid "Invalid request"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/twitter_api/twitter_api.ex:102
+msgid "Kocaptcha service unavailable"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:140
+msgid "Missing parameters"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:477
+msgid "No such conversation"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:171
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:197 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:239
+msgid "No such permission_group"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:504
+#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11 lib/pleroma/web/feed/tag_controller.ex:16
+#: lib/pleroma/web/feed/user_controller.ex:69 lib/pleroma/web/o_status/o_status_controller.ex:132
+#: lib/pleroma/web/plugs/uploaded_media.ex:84
+msgid "Not found"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api.ex:308
+msgid "Poll's author can't vote"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:39 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:51
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:52 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:326
+#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
+msgid "Record not found"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35
+#: lib/pleroma/web/feed/user_controller.ex:78 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:42
+#: lib/pleroma/web/o_status/o_status_controller.ex:138
+msgid "Something went wrong"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/activity_draft.ex:143
+msgid "The message visibility must be direct"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:492
+msgid "The status is over the character limit"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex:36
+msgid "This resource requires authentication."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/plugs/rate_limiter.ex:208
+msgid "Throttled"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api.ex:338
+msgid "Too many choices"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:268
+msgid "You can't revoke your own admin status."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:243
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:333
+msgid "Your account is currently disabled"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:205
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:356
+msgid "Your login is missing a confirmed e-mail address"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:392
+msgid "can't read inbox of %{nickname} as %{as_nickname}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:491
+msgid "can't update outbox of %{nickname} as %{as_nickname}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api.ex:475
+msgid "conversation is already muted"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:510
+msgid "error"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:34
+msgid "mascots can only be images"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:63
+msgid "not found"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:437
+msgid "Bad OAuth request."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/twitter_api/twitter_api.ex:108
+msgid "CAPTCHA already used"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/twitter_api/twitter_api.ex:105
+msgid "CAPTCHA expired"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/plugs/uploaded_media.ex:57
+msgid "Failed"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:453
+msgid "Failed to authenticate: %{message}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:484
+msgid "Failed to set up user account."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/plugs/o_auth_scopes_plug.ex:37
+msgid "Insufficient permissions: %{permissions}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/plugs/uploaded_media.ex:111
+msgid "Internal Error"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/o_auth/fallback_controller.ex:22
+#: lib/pleroma/web/o_auth/fallback_controller.ex:29
+msgid "Invalid Username/Password"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/twitter_api/twitter_api.ex:111
+msgid "Invalid answer data"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33
+msgid "Nodeinfo schema version not handled"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:194
+msgid "This action is outside the authorized scopes"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/o_auth/fallback_controller.ex:14
+msgid "Unknown error, please check the details and try again."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:136
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:180
+msgid "Unlisted redirect_uri."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:433
+msgid "Unsupported OAuth provider: %{provider}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/uploaders/uploader.ex:74
+msgid "Uploader callback timeout"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/uploader_controller.ex:23
+msgid "bad request"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/twitter_api/twitter_api.ex:96
+msgid "CAPTCHA Error"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api.ex:266
+msgid "Could not add reaction emoji"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api.ex:277
+msgid "Could not remove reaction emoji"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/twitter_api/twitter_api.ex:122
+msgid "Invalid CAPTCHA (Missing parameter: %{name})"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:96
+msgid "List not found"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:151
+msgid "Missing parameter: %{name}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:232
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:346
+msgid "Password reset is required"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/tests/auth_test_controller.ex:9
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/chat_controller.ex:6 lib/pleroma/web/admin_api/controllers/config_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:6 lib/pleroma/web/admin_api/controllers/frontend_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/instance_controller.ex:6 lib/pleroma/web/admin_api/controllers/instance_document_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/invite_controller.ex:6 lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex:6 lib/pleroma/web/admin_api/controllers/relay_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/report_controller.ex:6 lib/pleroma/web/admin_api/controllers/status_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/user_controller.ex:6 lib/pleroma/web/controller_helper.ex:6 lib/pleroma/web/embed_controller.ex:6
+#: lib/pleroma/web/fallback/redirect_controller.ex:6 lib/pleroma/web/feed/tag_controller.ex:6
+#: lib/pleroma/web/feed/user_controller.ex:6 lib/pleroma/web/mailer/subscription_controller.ex:6
+#: lib/pleroma/web/manifest_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/app_controller.ex:11 lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/directory_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14 lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/report_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7 lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6 lib/pleroma/web/media_proxy/media_proxy_controller.ex:6
+#: lib/pleroma/web/mongoose_im/mongoose_im_controller.ex:6 lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6
+#: lib/pleroma/web/o_auth/fallback_controller.ex:6 lib/pleroma/web/o_auth/mfa_controller.ex:10
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:6 lib/pleroma/web/o_status/o_status_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/app_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/backup_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/chat_controller.ex:5
+#: lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/instances_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/notification_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/report_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7 lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex:6
+#: lib/pleroma/web/static_fe/static_fe_controller.ex:6 lib/pleroma/web/twitter_api/controller.ex:6
+#: lib/pleroma/web/twitter_api/controllers/password_controller.ex:10 lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6
+#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:6 lib/pleroma/web/uploader_controller.ex:6
+#: lib/pleroma/web/web_finger/web_finger_controller.ex:6
+msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/plugs/ensure_authenticated_plug.ex:32
+msgid "Two-factor authentication enabled, you must use a access token."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
+msgid "Web push subscription is disabled on this Pleroma instance"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:234
+msgid "You can't revoke your own admin/moderator status."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:129
+msgid "authorization required for timeline view"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24
+msgid "Access denied"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:321
+msgid "This API requires an authenticated user"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex:26
+#: lib/pleroma/web/plugs/user_is_admin_plug.ex:21
+msgid "User is not an admin."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/user/backup.ex:75
+msgid "Last export was less than a day ago"
+msgid_plural "Last export was less than %{days} days ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#, elixir-format
+#: lib/pleroma/user/backup.ex:93
+msgid "Backups require enabled email"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:423
+msgid "Character limit (%{limit} characters) exceeded, contains %{length} characters"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/user/backup.ex:98
+msgid "Email is required"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:507
+msgid "Too many attachments"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex:33
+#: lib/pleroma/web/plugs/user_is_staff_plug.ex:20
+msgid "User is not a staff member."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:366
+msgid "Your account is awaiting approval."
+msgstr ""
diff --git a/priv/gettext/en_test/LC_MESSAGES/posix_errors.po b/priv/gettext/en_test/LC_MESSAGES/posix_errors.po
new file mode 100644
index 000000000..663fc5924
--- /dev/null
+++ b/priv/gettext/en_test/LC_MESSAGES/posix_errors.po
@@ -0,0 +1,153 @@
+## "msgid"s in this file come from POT (.pot) files.
+##
+## Do not add, change, or remove "msgid"s manually here as
+## they're tied to the ones in the corresponding POT file
+## (with the same domain).
+##
+## Use "mix gettext.extract --merge" or "mix gettext.merge"
+## to merge POT files into PO files.
+msgid ""
+msgstr ""
+"Language: en_test\n"
+"Plural-Forms: nplurals=2\n"
+
+msgid "eperm"
+msgstr ""
+
+msgid "eacces"
+msgstr ""
+
+msgid "eagain"
+msgstr ""
+
+msgid "ebadf"
+msgstr ""
+
+msgid "ebadmsg"
+msgstr ""
+
+msgid "ebusy"
+msgstr ""
+
+msgid "edeadlk"
+msgstr ""
+
+msgid "edeadlock"
+msgstr ""
+
+msgid "edquot"
+msgstr ""
+
+msgid "eexist"
+msgstr ""
+
+msgid "efault"
+msgstr ""
+
+msgid "efbig"
+msgstr ""
+
+msgid "eftype"
+msgstr ""
+
+msgid "eintr"
+msgstr ""
+
+msgid "einval"
+msgstr ""
+
+msgid "eio"
+msgstr ""
+
+msgid "eisdir"
+msgstr ""
+
+msgid "eloop"
+msgstr ""
+
+msgid "emfile"
+msgstr ""
+
+msgid "emlink"
+msgstr ""
+
+msgid "emultihop"
+msgstr ""
+
+msgid "enametoolong"
+msgstr ""
+
+msgid "enfile"
+msgstr ""
+
+msgid "enobufs"
+msgstr ""
+
+msgid "enodev"
+msgstr ""
+
+msgid "enolck"
+msgstr ""
+
+msgid "enolink"
+msgstr ""
+
+msgid "enoent"
+msgstr ""
+
+msgid "enomem"
+msgstr ""
+
+msgid "enospc"
+msgstr ""
+
+msgid "enosr"
+msgstr ""
+
+msgid "enostr"
+msgstr ""
+
+msgid "enosys"
+msgstr ""
+
+msgid "enotblk"
+msgstr ""
+
+msgid "enotdir"
+msgstr ""
+
+msgid "enotsup"
+msgstr ""
+
+msgid "enxio"
+msgstr ""
+
+msgid "eopnotsupp"
+msgstr ""
+
+msgid "eoverflow"
+msgstr ""
+
+msgid "epipe"
+msgstr ""
+
+msgid "erange"
+msgstr ""
+
+msgid "erofs"
+msgstr ""
+
+msgid "espipe"
+msgstr ""
+
+msgid "esrch"
+msgstr ""
+
+msgid "estale"
+msgstr ""
+
+msgid "etxtbsy"
+msgstr ""
+
+msgid "exdev"
+msgstr ""
diff --git a/priv/gettext/en_test/LC_MESSAGES/static_pages.po b/priv/gettext/en_test/LC_MESSAGES/static_pages.po
new file mode 100644
index 000000000..1a3b7b355
--- /dev/null
+++ b/priv/gettext/en_test/LC_MESSAGES/static_pages.po
@@ -0,0 +1,529 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR Free Software Foundation, Inc.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"PO-Revision-Date: 2022-03-06 11:27-0500\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#~ ## "msgid"s in this file come from POT (.pot) files.
+#~ ##
+#~ ## Do not add, change, or remove "msgid"s manually here as
+#~ ## they're tied to the ones in the corresponding POT file
+#~ ## (with the same domain).
+#~ ##
+#~ ## Use "mix gettext.extract --merge" or "mix gettext.merge"
+#~ ## to merge POT files into PO files.
+#~ msgid ""
+#~ msgstr ""
+#~ "Language: en_test\n"
+#~ "Plural-Forms: nplurals=2\n"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:9
+msgctxt "remote follow authorization button"
+msgid "Authorize"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:2
+msgctxt "remote follow error"
+msgid "Error fetching user"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:4
+msgctxt "remote follow header"
+msgid "Remote follow"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:8
+msgctxt "placeholder text for auth code entry"
+msgid "Authentication code"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:10
+msgctxt "placeholder text for password entry"
+msgid "Password"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:8
+msgctxt "placeholder text for username entry"
+msgid "Username"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:13
+msgctxt "remote follow authorization button for login"
+msgid "Authorize"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:12
+msgctxt "remote follow authorization button for mfa"
+msgid "Authorize"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:2
+msgctxt "remote follow error"
+msgid "Error following account"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:4
+msgctxt "remote follow header, need login"
+msgid "Log in to follow"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:4
+msgctxt "remote follow mfa header"
+msgid "Two-factor authentication"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:4
+msgctxt "remote follow success"
+msgid "Account followed!"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:7
+msgctxt "placeholder text for account id"
+msgid "Your account ID, e.g. lain@quitter.se"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:8
+msgctxt "remote follow authorization button for following with a remote account"
+msgid "Follow"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:2
+msgctxt "remote follow error"
+msgid "Error: %{error}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:4
+msgctxt "remote follow header"
+msgid "Remotely follow %{nickname}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:12
+msgctxt "password reset button"
+msgid "Reset"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex:4
+msgctxt "password reset failed homepage link"
+msgid "Homepage"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex:1
+msgctxt "password reset failed message"
+msgid "Password reset failed"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:8
+msgctxt "password reset form confirm password prompt"
+msgid "Confirmation"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:4
+msgctxt "password reset form password prompt"
+msgid "Password"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex:1
+msgctxt "password reset invalid token message"
+msgid "Invalid Token"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex:2
+msgctxt "password reset successful homepage link"
+msgid "Homepage"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex:1
+msgctxt "password reset successful message"
+msgid "Password changed!"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/feed/feed/tag.atom.eex:15
+#: lib/pleroma/web/templates/feed/feed/tag.rss.eex:7
+msgctxt "tag feed description"
+msgid "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:1
+msgctxt "oauth authorization exists page title"
+msgid "Authorization exists"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:32
+msgctxt "oauth authorize approve button"
+msgid "Approve"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:30
+msgctxt "oauth authorize cancel button"
+msgid "Cancel"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:23
+msgctxt "oauth authorize message"
+msgid "Application <strong>%{client_name}</strong> is requesting access to your account."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:1
+msgctxt "oauth authorized page title"
+msgid "Successfully authorized"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex:1
+msgctxt "oauth external provider page title"
+msgid "Sign in with external provider"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex:13
+msgctxt "oauth external provider sign in button"
+msgid "Sign in with %{strategy}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:54
+msgctxt "oauth login button"
+msgid "Log In"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:51
+msgctxt "oauth login password prompt"
+msgid "Password"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:47
+msgctxt "oauth login username prompt"
+msgid "Username"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:39
+msgctxt "oauth register nickname prompt"
+msgid "Pleroma Handle"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:37
+msgctxt "oauth register nickname unchangeable warning"
+msgid "Choose carefully! You won't be able to change this later. You will be able to change your display name, though."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:18
+msgctxt "oauth register page email prompt"
+msgid "Email"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:10
+msgctxt "oauth register page fill form prompt"
+msgid "If you'd like to register a new account, please provide the details below."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:35
+msgctxt "oauth register page login button"
+msgid "Proceed as existing user"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:31
+msgctxt "oauth register page login password prompt"
+msgid "Password"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:24
+msgctxt "oauth register page login prompt"
+msgid "Alternatively, sign in to connect to existing account."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:27
+msgctxt "oauth register page login username prompt"
+msgid "Name or email"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:14
+msgctxt "oauth register page nickname prompt"
+msgid "Nickname"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:22
+msgctxt "oauth register page register button"
+msgid "Proceed as new user"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:8
+msgctxt "oauth register page title"
+msgid "Registration Details"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:36
+msgctxt "oauth register page title"
+msgid "This is the first time you visit! Please enter your Pleroma handle."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex:2
+msgctxt "oauth scopes message"
+msgid "The following permissions will be granted"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:2
+#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:2
+msgctxt "oauth token code message"
+msgid "Token code is <br>%{token}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:12
+msgctxt "mfa auth code prompt"
+msgid "Authentication code"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:8
+msgctxt "mfa auth page title"
+msgid "Two-factor authentication"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:23
+msgctxt "mfa auth page use recovery code link"
+msgid "Enter a two-factor recovery code"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:20
+msgctxt "mfa auth verify code button"
+msgid "Verify"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:8
+msgctxt "mfa recover page title"
+msgid "Two-factor recovery"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:12
+msgctxt "mfa recover recovery code prompt"
+msgid "Recovery code"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:23
+msgctxt "mfa recover use 2fa code link"
+msgid "Enter a two-factor code"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:20
+msgctxt "mfa recover verify recovery code button"
+msgid "Verify"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex:8
+msgctxt "static fe profile page remote follow button"
+msgid "Remote follow"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/email/digest.html.eex:163
+msgctxt "digest email header line"
+msgid "Hey %{nickname}, here is what you've missed!"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/email/digest.html.eex:544
+msgctxt "digest email receiver address"
+msgid "The email address you are subscribed as is <a href='mailto:%{@user.email}' style='color: %{color};text-decoration: none;'>%{email}</a>. "
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/email/digest.html.eex:538
+msgctxt "digest email sending reason"
+msgid "You have received this email because you have signed up to receive digest emails from <b>%{instance}</b> Pleroma instance."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/email/digest.html.eex:547
+msgctxt "digest email unsubscribe action"
+msgid "To unsubscribe, please go %{here}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/email/digest.html.eex:547
+msgctxt "digest email unsubscribe action link text"
+msgid "here"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex:1
+msgctxt "mailer unsubscribe failed message"
+msgid "UNSUBSCRIBE FAILURE"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex:1
+msgctxt "mailer unsubscribe successful message"
+msgid "UNSUBSCRIBE SUCCESSFUL"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/email/digest.html.eex:385
+msgctxt "new followers count header"
+msgid "%{count} New Follower"
+msgid_plural "%{count} New Followers"
+msgstr[0] "xx%{count} New Followerxx"
+msgstr[1] "xx%{count} New Followersxx"
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:356
+msgctxt "account archive email body - self-requested"
+msgid "<p>You requested a full backup of your Pleroma account. It's ready for download:</p>\n<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:384
+msgctxt "account archive email subject"
+msgid "Your account archive is ready"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:188
+msgctxt "approval pending email body"
+msgid "<h3>Awaiting Approval</h3>\n<p>Your account at %{instance_name} is being reviewed by staff. You will receive another email once your account is approved.</p>\n"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:202
+msgctxt "approval pending email subject"
+msgid "Your account is awaiting approval"
+msgstr "xxYour account is awaiting approvalxx"
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:158
+msgctxt "confirmation email body"
+msgid "<h3>Thank you for registering on %{instance_name}</h3>\n<p>Email confirmation is required to activate the account.</p>\n<p>Please click the following link to <a href=\"%{confirmation_url}\">activate your account</a>.</p>\n"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:174
+msgctxt "confirmation email subject"
+msgid "%{instance_name} account confirmation"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:310
+msgctxt "digest email subject"
+msgid "Your digest from %{instance_name}"
+msgstr "xxYour digest from %{instance_name}xx"
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:81
+msgctxt "password reset email body"
+msgid "<h3>Reset your password at %{instance_name}</h3>\n<p>Someone has requested password change for your account at %{instance_name}.</p>\n<p>If it was you, visit the following link to proceed: <a href=\"%{password_reset_url}\">reset password</a>.</p>\n<p>If it was someone else, nothing to worry about: your data is secure and your password has not been changed.</p>\n"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:98
+msgctxt "password reset email subject"
+msgid "Password reset"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:215
+msgctxt "successful registration email body"
+msgid "<h3>Hello @%{nickname},</h3>\n<p>Your account at %{instance_name} has been registered successfully.</p>\n<p>No further action is required to activate your account.</p>\n"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:231
+msgctxt "successful registration email subject"
+msgid "Account registered on %{instance_name}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:119
+msgctxt "user invitation email body"
+msgid "<h3>You are invited to %{instance_name}</h3>\n<p>%{inviter_name} invites you to join %{instance_name}, an instance of Pleroma federated social networking platform.</p>\n<p>Click the following link to register: <a href=\"%{registration_url}\">accept invitation</a>.</p>\n"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:136
+msgctxt "user invitation email subject"
+msgid "Invitation to %{instance_name}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:53
+msgctxt "welcome email html body"
+msgid "Welcome to %{instance_name}!"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:41
+msgctxt "welcome email subject"
+msgid "Welcome to %{instance_name}!"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:65
+msgctxt "welcome email text body"
+msgid "Welcome to %{instance_name}!"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:368
+msgctxt "account archive email body - admin requested"
+msgid "<p>Admin @%{admin_nickname} requested a full backup of your Pleroma account. It's ready for download:</p>\n<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
+msgstr ""
diff --git a/priv/gettext/errors.pot b/priv/gettext/errors.pot
index e337226a7..7644fc230 100644
--- a/priv/gettext/errors.pot
+++ b/priv/gettext/errors.pot
@@ -90,121 +90,99 @@ msgid "must be equal to %{number}"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/common_api/common_api.ex:505
+#: lib/pleroma/web/common_api.ex:523
msgid "Account not found"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/common_api/common_api.ex:339
+#: lib/pleroma/web/common_api.ex:316
msgid "Already voted"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/oauth/oauth_controller.ex:359
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:402
msgid "Bad request"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:426
-msgid "Can't delete object"
-msgstr ""
-
-#, elixir-format
-#: lib/pleroma/web/controller_helper.ex:105
-#: lib/pleroma/web/controller_helper.ex:111
+#: lib/pleroma/web/controller_helper.ex:97
+#: lib/pleroma/web/controller_helper.ex:103
msgid "Can't display this activity"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:285
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:324
msgid "Can't find user"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:61
+#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:80
msgid "Can't get favorites"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:438
-msgid "Can't like object"
-msgstr ""
-
-#, elixir-format
-#: lib/pleroma/web/common_api/utils.ex:563
+#: lib/pleroma/web/common_api/utils.ex:482
msgid "Cannot post an empty status without attachments"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/common_api/utils.ex:511
+#: lib/pleroma/web/common_api/utils.ex:441
msgid "Comment must be up to %{max_size} characters"
msgstr ""
#, elixir-format
-#: lib/pleroma/config/config_db.ex:191
+#: lib/pleroma/config_db.ex:200
msgid "Config with params %{params} not found"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/common_api/common_api.ex:181
-#: lib/pleroma/web/common_api/common_api.ex:185
+#: lib/pleroma/web/common_api.ex:167 lib/pleroma/web/common_api.ex:171
msgid "Could not delete"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/common_api/common_api.ex:231
+#: lib/pleroma/web/common_api.ex:217
msgid "Could not favorite"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/common_api/common_api.ex:453
-msgid "Could not pin"
-msgstr ""
-
-#, elixir-format
-#: lib/pleroma/web/common_api/common_api.ex:278
+#: lib/pleroma/web/common_api.ex:254
msgid "Could not unfavorite"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/common_api/common_api.ex:463
-msgid "Could not unpin"
-msgstr ""
-
-#, elixir-format
-#: lib/pleroma/web/common_api/common_api.ex:216
+#: lib/pleroma/web/common_api.ex:202
msgid "Could not unrepeat"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/common_api/common_api.ex:512
-#: lib/pleroma/web/common_api/common_api.ex:521
+#: lib/pleroma/web/common_api.ex:530 lib/pleroma/web/common_api.ex:539
msgid "Could not update state"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:207
+#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:205
msgid "Error."
msgstr ""
#, elixir-format
-#: lib/pleroma/web/twitter_api/twitter_api.ex:106
+#: lib/pleroma/web/twitter_api/twitter_api.ex:99
msgid "Invalid CAPTCHA"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:116
-#: lib/pleroma/web/oauth/oauth_controller.ex:568
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:144
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:631
msgid "Invalid credentials"
msgstr ""
#, elixir-format
-#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38
+#: lib/pleroma/web/plugs/ensure_authenticated_plug.ex:42
msgid "Invalid credentials."
msgstr ""
#, elixir-format
-#: lib/pleroma/web/common_api/common_api.ex:355
+#: lib/pleroma/web/common_api.ex:337
msgid "Invalid indices"
msgstr ""
@@ -214,189 +192,184 @@ msgid "Invalid parameters"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/common_api/utils.ex:414
+#: lib/pleroma/web/common_api/utils.ex:349
msgid "Invalid password."
msgstr ""
#, elixir-format
-#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:220
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:254
msgid "Invalid request"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/twitter_api/twitter_api.ex:109
+#: lib/pleroma/web/twitter_api/twitter_api.ex:102
msgid "Kocaptcha service unavailable"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:112
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:140
msgid "Missing parameters"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/common_api/utils.ex:547
+#: lib/pleroma/web/common_api/utils.ex:477
msgid "No such conversation"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:388
-#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:414 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:456
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:171
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:197 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:239
msgid "No such permission_group"
msgstr ""
#, elixir-format
-#: lib/pleroma/plugs/uploaded_media.ex:84
-#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11
-#: lib/pleroma/web/feed/user_controller.ex:71 lib/pleroma/web/ostatus/ostatus_controller.ex:143
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:504
+#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11 lib/pleroma/web/feed/tag_controller.ex:16
+#: lib/pleroma/web/feed/user_controller.ex:69 lib/pleroma/web/o_status/o_status_controller.ex:132
+#: lib/pleroma/web/plugs/uploaded_media.ex:84
msgid "Not found"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/common_api/common_api.ex:331
+#: lib/pleroma/web/common_api.ex:308
msgid "Poll's author can't vote"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
-#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49
-#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:50 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:306
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:39 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:51
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:52 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:326
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
msgid "Record not found"
msgstr ""
#, elixir-format
#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35
-#: lib/pleroma/web/feed/user_controller.ex:77 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:36
-#: lib/pleroma/web/ostatus/ostatus_controller.ex:149
+#: lib/pleroma/web/feed/user_controller.ex:78 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:42
+#: lib/pleroma/web/o_status/o_status_controller.ex:138
msgid "Something went wrong"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/common_api/activity_draft.ex:107
+#: lib/pleroma/web/common_api/activity_draft.ex:143
msgid "The message visibility must be direct"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/common_api/utils.ex:573
+#: lib/pleroma/web/common_api/utils.ex:492
msgid "The status is over the character limit"
msgstr ""
#, elixir-format
-#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31
+#: lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex:36
msgid "This resource requires authentication."
msgstr ""
#, elixir-format
-#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
+#: lib/pleroma/web/plugs/rate_limiter.ex:208
msgid "Throttled"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/common_api/common_api.ex:356
+#: lib/pleroma/web/common_api.ex:338
msgid "Too many choices"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:443
-msgid "Unhandled activity type"
-msgstr ""
-
-#, elixir-format
-#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:485
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:268
msgid "You can't revoke your own admin status."
msgstr ""
#, elixir-format
-#: lib/pleroma/web/oauth/oauth_controller.ex:221
-#: lib/pleroma/web/oauth/oauth_controller.ex:308
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:243
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:333
msgid "Your account is currently disabled"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/oauth/oauth_controller.ex:183
-#: lib/pleroma/web/oauth/oauth_controller.ex:331
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:205
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:356
msgid "Your login is missing a confirmed e-mail address"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:390
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:392
msgid "can't read inbox of %{nickname} as %{as_nickname}"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:473
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:491
msgid "can't update outbox of %{nickname} as %{as_nickname}"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/common_api/common_api.ex:471
+#: lib/pleroma/web/common_api.ex:475
msgid "conversation is already muted"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:314
-#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:492
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:510
msgid "error"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:32
+#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:34
msgid "mascots can only be images"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:63
msgid "not found"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/oauth/oauth_controller.ex:394
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:437
msgid "Bad OAuth request."
msgstr ""
#, elixir-format
-#: lib/pleroma/web/twitter_api/twitter_api.ex:115
+#: lib/pleroma/web/twitter_api/twitter_api.ex:108
msgid "CAPTCHA already used"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/twitter_api/twitter_api.ex:112
+#: lib/pleroma/web/twitter_api/twitter_api.ex:105
msgid "CAPTCHA expired"
msgstr ""
#, elixir-format
-#: lib/pleroma/plugs/uploaded_media.ex:57
+#: lib/pleroma/web/plugs/uploaded_media.ex:57
msgid "Failed"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/oauth/oauth_controller.ex:410
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:453
msgid "Failed to authenticate: %{message}."
msgstr ""
#, elixir-format
-#: lib/pleroma/web/oauth/oauth_controller.ex:441
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:484
msgid "Failed to set up user account."
msgstr ""
#, elixir-format
-#: lib/pleroma/plugs/oauth_scopes_plug.ex:38
+#: lib/pleroma/web/plugs/o_auth_scopes_plug.ex:37
msgid "Insufficient permissions: %{permissions}."
msgstr ""
#, elixir-format
-#: lib/pleroma/plugs/uploaded_media.ex:104
+#: lib/pleroma/web/plugs/uploaded_media.ex:111
msgid "Internal Error"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/oauth/fallback_controller.ex:22
-#: lib/pleroma/web/oauth/fallback_controller.ex:29
+#: lib/pleroma/web/o_auth/fallback_controller.ex:22
+#: lib/pleroma/web/o_auth/fallback_controller.ex:29
msgid "Invalid Username/Password"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/twitter_api/twitter_api.ex:118
+#: lib/pleroma/web/twitter_api/twitter_api.ex:111
msgid "Invalid answer data"
msgstr ""
@@ -406,28 +379,28 @@ msgid "Nodeinfo schema version not handled"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/oauth/oauth_controller.ex:172
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:194
msgid "This action is outside the authorized scopes"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/oauth/fallback_controller.ex:14
+#: lib/pleroma/web/o_auth/fallback_controller.ex:14
msgid "Unknown error, please check the details and try again."
msgstr ""
#, elixir-format
-#: lib/pleroma/web/oauth/oauth_controller.ex:119
-#: lib/pleroma/web/oauth/oauth_controller.ex:158
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:136
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:180
msgid "Unlisted redirect_uri."
msgstr ""
#, elixir-format
-#: lib/pleroma/web/oauth/oauth_controller.ex:390
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:433
msgid "Unsupported OAuth provider: %{provider}."
msgstr ""
#, elixir-format
-#: lib/pleroma/uploaders/uploader.ex:72
+#: lib/pleroma/uploaders/uploader.ex:74
msgid "Uploader callback timeout"
msgstr ""
@@ -437,134 +410,154 @@ msgid "bad request"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/twitter_api/twitter_api.ex:103
+#: lib/pleroma/web/twitter_api/twitter_api.ex:96
msgid "CAPTCHA Error"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/common_api/common_api.ex:290
+#: lib/pleroma/web/common_api.ex:266
msgid "Could not add reaction emoji"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/common_api/common_api.ex:301
+#: lib/pleroma/web/common_api.ex:277
msgid "Could not remove reaction emoji"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/twitter_api/twitter_api.ex:129
+#: lib/pleroma/web/twitter_api/twitter_api.ex:122
msgid "Invalid CAPTCHA (Missing parameter: %{name})"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92
+#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:96
msgid "List not found"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:123
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:151
msgid "Missing parameter: %{name}"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/oauth/oauth_controller.ex:210
-#: lib/pleroma/web/oauth/oauth_controller.ex:321
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:232
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:346
msgid "Password reset is required"
msgstr ""
#, elixir-format
#: lib/pleroma/tests/auth_test_controller.ex:9
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6
-#: lib/pleroma/web/admin_api/controllers/config_controller.ex:6 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/chat_controller.ex:6 lib/pleroma/web/admin_api/controllers/config_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:6 lib/pleroma/web/admin_api/controllers/frontend_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/instance_controller.ex:6 lib/pleroma/web/admin_api/controllers/instance_document_controller.ex:6
#: lib/pleroma/web/admin_api/controllers/invite_controller.ex:6 lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex:6
-#: lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex:6 lib/pleroma/web/admin_api/controllers/relay_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex:6 lib/pleroma/web/admin_api/controllers/relay_controller.ex:6
#: lib/pleroma/web/admin_api/controllers/report_controller.ex:6 lib/pleroma/web/admin_api/controllers/status_controller.ex:6
-#: lib/pleroma/web/controller_helper.ex:6 lib/pleroma/web/embed_controller.ex:6
-#: lib/pleroma/web/fallback_redirect_controller.ex:6 lib/pleroma/web/feed/tag_controller.ex:6
-#: lib/pleroma/web/feed/user_controller.ex:6 lib/pleroma/web/mailer/subscription_controller.ex:2
-#: lib/pleroma/web/masto_fe_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6
-#: lib/pleroma/web/mastodon_api/controllers/app_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/user_controller.ex:6 lib/pleroma/web/controller_helper.ex:6 lib/pleroma/web/embed_controller.ex:6
+#: lib/pleroma/web/fallback/redirect_controller.ex:6 lib/pleroma/web/feed/tag_controller.ex:6
+#: lib/pleroma/web/feed/user_controller.ex:6 lib/pleroma/web/mailer/subscription_controller.ex:6
+#: lib/pleroma/web/manifest_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/app_controller.ex:11 lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6
-#: lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6
-#: lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6
-#: lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6
-#: lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14
-#: lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6
-#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/report_controller.ex:8
-#: lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6
-#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7
-#: lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6
-#: lib/pleroma/web/media_proxy/media_proxy_controller.ex:6 lib/pleroma/web/mongooseim/mongoose_im_controller.ex:6
-#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6 lib/pleroma/web/oauth/fallback_controller.ex:6
-#: lib/pleroma/web/oauth/mfa_controller.ex:10 lib/pleroma/web/oauth/oauth_controller.ex:6
-#: lib/pleroma/web/ostatus/ostatus_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6
-#: lib/pleroma/web/pleroma_api/controllers/chat_controller.ex:5 lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex:6
-#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:2 lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex:6
-#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/notification_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/directory_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14 lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/report_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7 lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6 lib/pleroma/web/media_proxy/media_proxy_controller.ex:6
+#: lib/pleroma/web/mongoose_im/mongoose_im_controller.ex:6 lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6
+#: lib/pleroma/web/o_auth/fallback_controller.ex:6 lib/pleroma/web/o_auth/mfa_controller.ex:10
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:6 lib/pleroma/web/o_status/o_status_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/app_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/backup_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/chat_controller.ex:5
+#: lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/instances_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/notification_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/report_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex:6
-#: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7 lib/pleroma/web/static_fe/static_fe_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7 lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex:6
+#: lib/pleroma/web/static_fe/static_fe_controller.ex:6 lib/pleroma/web/twitter_api/controller.ex:6
#: lib/pleroma/web/twitter_api/controllers/password_controller.ex:10 lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6
-#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:6 lib/pleroma/web/twitter_api/twitter_api_controller.ex:6
-#: lib/pleroma/web/uploader_controller.ex:6 lib/pleroma/web/web_finger/web_finger_controller.ex:6
+#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:6 lib/pleroma/web/uploader_controller.ex:6
+#: lib/pleroma/web/web_finger/web_finger_controller.ex:6
msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
msgstr ""
#, elixir-format
-#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28
+#: lib/pleroma/web/plugs/ensure_authenticated_plug.ex:32
msgid "Two-factor authentication enabled, you must use a access token."
msgstr ""
#, elixir-format
-#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210
-msgid "Unexpected error occurred while adding file to pack."
+#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
+msgid "Web push subscription is disabled on this Pleroma instance"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:234
+msgid "You can't revoke your own admin/moderator status."
msgstr ""
#, elixir-format
-#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:138
-msgid "Unexpected error occurred while creating pack."
+#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:129
+msgid "authorization required for timeline view"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:278
-msgid "Unexpected error occurred while removing file from pack."
+#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24
+msgid "Access denied"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:250
-msgid "Unexpected error occurred while updating file in pack."
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:321
+msgid "This API requires an authenticated user"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:179
-msgid "Unexpected error occurred while updating pack metadata."
+#: lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex:26
+#: lib/pleroma/web/plugs/user_is_admin_plug.ex:21
+msgid "User is not an admin."
msgstr ""
#, elixir-format
-#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
-msgid "Web push subscription is disabled on this Pleroma instance"
+#: lib/pleroma/user/backup.ex:75
+msgid "Last export was less than a day ago"
+msgid_plural "Last export was less than %{days} days ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#, elixir-format
+#: lib/pleroma/user/backup.ex:93
+msgid "Backups require enabled email"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451
-msgid "You can't revoke your own admin/moderator status."
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:423
+msgid "Character limit (%{limit} characters) exceeded, contains %{length} characters"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126
-msgid "authorization required for timeline view"
+#: lib/pleroma/user/backup.ex:98
+msgid "Email is required"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24
-msgid "Access denied"
+#: lib/pleroma/web/common_api/utils.ex:507
+msgid "Too many attachments"
msgstr ""
#, elixir-format
-#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282
-msgid "This API requires an authenticated user"
+#: lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex:33
+#: lib/pleroma/web/plugs/user_is_staff_plug.ex:20
+msgid "User is not a staff member."
msgstr ""
#, elixir-format
-#: lib/pleroma/plugs/user_is_admin_plug.ex:21
-msgid "User is not an admin."
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:366
+msgid "Your account is awaiting approval."
msgstr ""
diff --git a/priv/gettext/posix_errors.pot b/priv/gettext/posix_errors.pot
index c9f593944..3533639e0 100644
--- a/priv/gettext/posix_errors.pot
+++ b/priv/gettext/posix_errors.pot
@@ -15,135 +15,135 @@ msgstr ""
msgid "eagain"
msgstr ""
-
+
msgid "ebadf"
msgstr ""
-
+
msgid "ebadmsg"
msgstr ""
-
+
msgid "ebusy"
msgstr ""
-
+
msgid "edeadlk"
msgstr ""
-
+
msgid "edeadlock"
msgstr ""
-
+
msgid "edquot"
msgstr ""
-
+
msgid "eexist"
msgstr ""
-
+
msgid "efault"
msgstr ""
-
+
msgid "efbig"
msgstr ""
-
+
msgid "eftype"
msgstr ""
-
+
msgid "eintr"
msgstr ""
-
+
msgid "einval"
msgstr ""
-
+
msgid "eio"
msgstr ""
-
+
msgid "eisdir"
msgstr ""
-
+
msgid "eloop"
msgstr ""
-
+
msgid "emfile"
msgstr ""
-
+
msgid "emlink"
msgstr ""
-
+
msgid "emultihop"
msgstr ""
-
+
msgid "enametoolong"
msgstr ""
-
+
msgid "enfile"
msgstr ""
-
+
msgid "enobufs"
msgstr ""
-
+
msgid "enodev"
msgstr ""
-
+
msgid "enolck"
msgstr ""
-
+
msgid "enolink"
msgstr ""
-
+
msgid "enoent"
msgstr ""
-
+
msgid "enomem"
msgstr ""
-
+
msgid "enospc"
msgstr ""
-
+
msgid "enosr"
msgstr ""
-
+
msgid "enostr"
msgstr ""
-
+
msgid "enosys"
msgstr ""
-
+
msgid "enotblk"
msgstr ""
-
+
msgid "enotdir"
msgstr ""
-
+
msgid "enotsup"
msgstr ""
-
+
msgid "enxio"
msgstr ""
-
+
msgid "eopnotsupp"
msgstr ""
-
+
msgid "eoverflow"
msgstr ""
-
+
msgid "epipe"
msgstr ""
-
+
msgid "erange"
msgstr ""
-
+
msgid "erofs"
msgstr ""
-
+
msgid "espipe"
msgstr ""
-
+
msgid "esrch"
msgstr ""
-
+
msgid "estale"
msgstr ""
-
+
msgid "etxtbsy"
msgstr ""
-
+
msgid "exdev"
msgstr ""
diff --git a/priv/gettext/static_pages.pot b/priv/gettext/static_pages.pot
new file mode 100644
index 000000000..fbc3e61a3
--- /dev/null
+++ b/priv/gettext/static_pages.pot
@@ -0,0 +1,513 @@
+## This file is a PO Template file.
+##
+## "msgid"s here are often extracted from source code.
+## Add new translations manually only if they're dynamic
+## translations that can't be statically extracted.
+##
+## Run "mix gettext.extract" to bring this file up to
+## date. Leave "msgstr"s empty as changing them here as no
+## effect: edit them in PO (.po) files instead.
+msgid ""
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:9
+msgctxt "remote follow authorization button"
+msgid "Authorize"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:2
+msgctxt "remote follow error"
+msgid "Error fetching user"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:4
+msgctxt "remote follow header"
+msgid "Remote follow"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:8
+msgctxt "placeholder text for auth code entry"
+msgid "Authentication code"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:10
+msgctxt "placeholder text for password entry"
+msgid "Password"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:8
+msgctxt "placeholder text for username entry"
+msgid "Username"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:13
+msgctxt "remote follow authorization button for login"
+msgid "Authorize"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:12
+msgctxt "remote follow authorization button for mfa"
+msgid "Authorize"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:2
+msgctxt "remote follow error"
+msgid "Error following account"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:4
+msgctxt "remote follow header, need login"
+msgid "Log in to follow"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:4
+msgctxt "remote follow mfa header"
+msgid "Two-factor authentication"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:4
+msgctxt "remote follow success"
+msgid "Account followed!"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:7
+msgctxt "placeholder text for account id"
+msgid "Your account ID, e.g. lain@quitter.se"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:8
+msgctxt "remote follow authorization button for following with a remote account"
+msgid "Follow"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:2
+msgctxt "remote follow error"
+msgid "Error: %{error}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:4
+msgctxt "remote follow header"
+msgid "Remotely follow %{nickname}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:12
+msgctxt "password reset button"
+msgid "Reset"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex:4
+msgctxt "password reset failed homepage link"
+msgid "Homepage"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex:1
+msgctxt "password reset failed message"
+msgid "Password reset failed"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:8
+msgctxt "password reset form confirm password prompt"
+msgid "Confirmation"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:4
+msgctxt "password reset form password prompt"
+msgid "Password"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex:1
+msgctxt "password reset invalid token message"
+msgid "Invalid Token"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex:2
+msgctxt "password reset successful homepage link"
+msgid "Homepage"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex:1
+msgctxt "password reset successful message"
+msgid "Password changed!"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/feed/feed/tag.atom.eex:15
+#: lib/pleroma/web/templates/feed/feed/tag.rss.eex:7
+msgctxt "tag feed description"
+msgid "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:1
+msgctxt "oauth authorization exists page title"
+msgid "Authorization exists"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:32
+msgctxt "oauth authorize approve button"
+msgid "Approve"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:30
+msgctxt "oauth authorize cancel button"
+msgid "Cancel"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:23
+msgctxt "oauth authorize message"
+msgid "Application <strong>%{client_name}</strong> is requesting access to your account."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:1
+msgctxt "oauth authorized page title"
+msgid "Successfully authorized"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex:1
+msgctxt "oauth external provider page title"
+msgid "Sign in with external provider"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex:13
+msgctxt "oauth external provider sign in button"
+msgid "Sign in with %{strategy}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:54
+msgctxt "oauth login button"
+msgid "Log In"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:51
+msgctxt "oauth login password prompt"
+msgid "Password"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:47
+msgctxt "oauth login username prompt"
+msgid "Username"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:39
+msgctxt "oauth register nickname prompt"
+msgid "Pleroma Handle"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:37
+msgctxt "oauth register nickname unchangeable warning"
+msgid "Choose carefully! You won't be able to change this later. You will be able to change your display name, though."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:18
+msgctxt "oauth register page email prompt"
+msgid "Email"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:10
+msgctxt "oauth register page fill form prompt"
+msgid "If you'd like to register a new account, please provide the details below."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:35
+msgctxt "oauth register page login button"
+msgid "Proceed as existing user"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:31
+msgctxt "oauth register page login password prompt"
+msgid "Password"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:24
+msgctxt "oauth register page login prompt"
+msgid "Alternatively, sign in to connect to existing account."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:27
+msgctxt "oauth register page login username prompt"
+msgid "Name or email"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:14
+msgctxt "oauth register page nickname prompt"
+msgid "Nickname"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:22
+msgctxt "oauth register page register button"
+msgid "Proceed as new user"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:8
+msgctxt "oauth register page title"
+msgid "Registration Details"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:36
+msgctxt "oauth register page title"
+msgid "This is the first time you visit! Please enter your Pleroma handle."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex:2
+msgctxt "oauth scopes message"
+msgid "The following permissions will be granted"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:2
+#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:2
+msgctxt "oauth token code message"
+msgid "Token code is <br>%{token}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:12
+msgctxt "mfa auth code prompt"
+msgid "Authentication code"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:8
+msgctxt "mfa auth page title"
+msgid "Two-factor authentication"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:23
+msgctxt "mfa auth page use recovery code link"
+msgid "Enter a two-factor recovery code"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:20
+msgctxt "mfa auth verify code button"
+msgid "Verify"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:8
+msgctxt "mfa recover page title"
+msgid "Two-factor recovery"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:12
+msgctxt "mfa recover recovery code prompt"
+msgid "Recovery code"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:23
+msgctxt "mfa recover use 2fa code link"
+msgid "Enter a two-factor code"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:20
+msgctxt "mfa recover verify recovery code button"
+msgid "Verify"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex:8
+msgctxt "static fe profile page remote follow button"
+msgid "Remote follow"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/email/digest.html.eex:163
+msgctxt "digest email header line"
+msgid "Hey %{nickname}, here is what you've missed!"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/email/digest.html.eex:544
+msgctxt "digest email receiver address"
+msgid "The email address you are subscribed as is <a href='mailto:%{@user.email}' style='color: %{color};text-decoration: none;'>%{email}</a>. "
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/email/digest.html.eex:538
+msgctxt "digest email sending reason"
+msgid "You have received this email because you have signed up to receive digest emails from <b>%{instance}</b> Pleroma instance."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/email/digest.html.eex:547
+msgctxt "digest email unsubscribe action"
+msgid "To unsubscribe, please go %{here}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/email/digest.html.eex:547
+msgctxt "digest email unsubscribe action link text"
+msgid "here"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex:1
+msgctxt "mailer unsubscribe failed message"
+msgid "UNSUBSCRIBE FAILURE"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex:1
+msgctxt "mailer unsubscribe successful message"
+msgid "UNSUBSCRIBE SUCCESSFUL"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/email/digest.html.eex:385
+msgctxt "new followers count header"
+msgid "%{count} New Follower"
+msgid_plural "%{count} New Followers"
+msgstr[0] ""
+msgstr[1] ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:356
+msgctxt "account archive email body - self-requested"
+msgid "<p>You requested a full backup of your Pleroma account. It's ready for download:</p>\n<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:384
+msgctxt "account archive email subject"
+msgid "Your account archive is ready"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:188
+msgctxt "approval pending email body"
+msgid "<h3>Awaiting Approval</h3>\n<p>Your account at %{instance_name} is being reviewed by staff. You will receive another email once your account is approved.</p>\n"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:202
+msgctxt "approval pending email subject"
+msgid "Your account is awaiting approval"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:158
+msgctxt "confirmation email body"
+msgid "<h3>Thank you for registering on %{instance_name}</h3>\n<p>Email confirmation is required to activate the account.</p>\n<p>Please click the following link to <a href=\"%{confirmation_url}\">activate your account</a>.</p>\n"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:174
+msgctxt "confirmation email subject"
+msgid "%{instance_name} account confirmation"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:310
+msgctxt "digest email subject"
+msgid "Your digest from %{instance_name}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:81
+msgctxt "password reset email body"
+msgid "<h3>Reset your password at %{instance_name}</h3>\n<p>Someone has requested password change for your account at %{instance_name}.</p>\n<p>If it was you, visit the following link to proceed: <a href=\"%{password_reset_url}\">reset password</a>.</p>\n<p>If it was someone else, nothing to worry about: your data is secure and your password has not been changed.</p>\n"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:98
+msgctxt "password reset email subject"
+msgid "Password reset"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:215
+msgctxt "successful registration email body"
+msgid "<h3>Hello @%{nickname},</h3>\n<p>Your account at %{instance_name} has been registered successfully.</p>\n<p>No further action is required to activate your account.</p>\n"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:231
+msgctxt "successful registration email subject"
+msgid "Account registered on %{instance_name}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:119
+msgctxt "user invitation email body"
+msgid "<h3>You are invited to %{instance_name}</h3>\n<p>%{inviter_name} invites you to join %{instance_name}, an instance of Pleroma federated social networking platform.</p>\n<p>Click the following link to register: <a href=\"%{registration_url}\">accept invitation</a>.</p>\n"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:136
+msgctxt "user invitation email subject"
+msgid "Invitation to %{instance_name}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:53
+msgctxt "welcome email html body"
+msgid "Welcome to %{instance_name}!"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:41
+msgctxt "welcome email subject"
+msgid "Welcome to %{instance_name}!"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:65
+msgctxt "welcome email text body"
+msgid "Welcome to %{instance_name}!"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:368
+msgctxt "account archive email body - admin requested"
+msgid "<p>Admin @%{admin_nickname} requested a full backup of your Pleroma account. It's ready for download:</p>\n<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
+msgstr ""
diff --git a/priv/gettext/zh_Hans/LC_MESSAGES/default.po b/priv/gettext/zh_Hans/LC_MESSAGES/default.po
new file mode 100644
index 000000000..06995d283
--- /dev/null
+++ b/priv/gettext/zh_Hans/LC_MESSAGES/default.po
@@ -0,0 +1,186 @@
+## "msgid"s in this file come from POT (.pot) files.
+##
+## Do not add, change, or remove "msgid"s manually here as
+## they're tied to the ones in the corresponding POT file
+## (with the same domain).
+##
+## Use "mix gettext.extract --merge" or "mix gettext.merge"
+## to merge POT files into PO files.
+msgid ""
+msgstr ""
+"Language: zh_Hans\n"
+"Plural-Forms: nplurals=1\n"
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:122
+msgid "%{name} - %{count} is not a multiple of %{multiple}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:131
+msgid "%{name} - %{value} is larger than exclusive maximum %{max}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:140
+msgid "%{name} - %{value} is larger than inclusive maximum %{max}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:149
+msgid "%{name} - %{value} is smaller than exclusive minimum %{min}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:158
+msgid "%{name} - %{value} is smaller than inclusive minimum %{min}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:102
+msgid "%{name} - Array items must be unique."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:114
+msgid "%{name} - Array length %{length} is larger than maxItems: %{}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:106
+msgid "%{name} - Array length %{length} is smaller than minItems: %{min}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:166
+msgid "%{name} - Invalid %{type}. Got: %{value}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:174
+msgid "%{name} - Invalid format. Expected %{format}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:51
+msgid "%{name} - Invalid schema.type. Got: %{type}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:178
+msgid "%{name} - Invalid value for enum."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:95
+msgid "%{name} - String length is larger than maxLength: %{length}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:88
+msgid "%{name} - String length is smaller than minLength: %{length}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:63
+msgid "%{name} - null value where %{type} expected."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:60
+msgid "%{name} - null value."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:182
+msgid "Failed to cast to any schema in %{polymorphic_type}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:71
+msgid "Failed to cast value as %{invalid_schema}. Value must be castable using `allOf` schemas listed."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:84
+msgid "Failed to cast value to one of: %{failed_schemas}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:78
+msgid "Failed to cast value using any of: %{failed_schemas}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:212
+msgid "Invalid value for header: %{name}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:204
+msgid "Missing field: %{name}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:208
+msgid "Missing header: %{name}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:196
+msgid "No value provided for required discriminator `%{field}`."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:216
+msgid "Object property count %{property_count} is greater than maxProperties: %{max_properties}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:224
+msgid "Object property count %{property_count} is less than minProperties: %{min_properties}"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/static_fe/static_fe/error.html.eex:2
+msgid "Oops"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:188
+msgid "Unexpected field: %{name}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:200
+msgid "Unknown schema: %{name}."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/api_spec/render_error.ex:192
+msgid "Value used as discriminator for `%{field}` matches no schemas."
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/embed/show.html.eex:43
+#: lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex:37
+msgid "announces"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/embed/show.html.eex:44
+#: lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex:38
+msgid "likes"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/embed/show.html.eex:42
+#: lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex:36
+msgid "replies"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/templates/embed/show.html.eex:27
+#: lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex:22
+msgid "sensitive media"
+msgstr ""
diff --git a/priv/gettext/zh_Hans/LC_MESSAGES/errors.po b/priv/gettext/zh_Hans/LC_MESSAGES/errors.po
index ecf1dab6b..569b00a42 100644
--- a/priv/gettext/zh_Hans/LC_MESSAGES/errors.po
+++ b/priv/gettext/zh_Hans/LC_MESSAGES/errors.po
@@ -99,482 +99,519 @@ msgstr "必须大于等于 %{number}"
msgid "must be equal to %{number}"
msgstr "必须等于 %{number}"
-#: lib/pleroma/web/common_api/common_api.ex:505
#, elixir-format
+#: lib/pleroma/web/common_api.ex:523
msgid "Account not found"
msgstr "未找到账号"
-#: lib/pleroma/web/common_api/common_api.ex:339
#, elixir-format
+#: lib/pleroma/web/common_api.ex:316
msgid "Already voted"
msgstr "已经进行了投票"
-#: lib/pleroma/web/oauth/oauth_controller.ex:359
#, elixir-format
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:402
msgid "Bad request"
msgstr "不正确的请求"
-#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:426
-#, elixir-format
-msgid "Can't delete object"
-msgstr "不能删除对象"
-
-#: lib/pleroma/web/controller_helper.ex:105
-#: lib/pleroma/web/controller_helper.ex:111
#, elixir-format
+#: lib/pleroma/web/controller_helper.ex:97
+#: lib/pleroma/web/controller_helper.ex:103
msgid "Can't display this activity"
msgstr "不能显示该活动"
-#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:285
#, elixir-format
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:324
msgid "Can't find user"
msgstr "找不到用户"
-#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:61
#, elixir-format
+#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:80
msgid "Can't get favorites"
msgstr "不能获取收藏"
-#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:438
-#, elixir-format
-msgid "Can't like object"
-msgstr "不能喜欢对象"
-
-#: lib/pleroma/web/common_api/utils.ex:563
#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:482
msgid "Cannot post an empty status without attachments"
msgstr "无法发送空白且不包含附件的状态"
-#: lib/pleroma/web/common_api/utils.ex:511
#, elixir-format, fuzzy
+#: lib/pleroma/web/common_api/utils.ex:441
msgid "Comment must be up to %{max_size} characters"
msgstr "评论最多可使用 %{max_size} 字符"
-#: lib/pleroma/config/config_db.ex:191
#, elixir-format
+#: lib/pleroma/config_db.ex:200
msgid "Config with params %{params} not found"
msgstr "无法找到包含参数 %{params} 的配置"
-#: lib/pleroma/web/common_api/common_api.ex:181
-#: lib/pleroma/web/common_api/common_api.ex:185
#, elixir-format
+#: lib/pleroma/web/common_api.ex:167
+#: lib/pleroma/web/common_api.ex:171
msgid "Could not delete"
msgstr "无法删除"
-#: lib/pleroma/web/common_api/common_api.ex:231
#, elixir-format
+#: lib/pleroma/web/common_api.ex:217
msgid "Could not favorite"
msgstr "无法收藏"
-#: lib/pleroma/web/common_api/common_api.ex:453
-#, elixir-format
-msgid "Could not pin"
-msgstr "无法置顶"
-
-#: lib/pleroma/web/common_api/common_api.ex:278
#, elixir-format
+#: lib/pleroma/web/common_api.ex:254
msgid "Could not unfavorite"
msgstr "无法取消收藏"
-#: lib/pleroma/web/common_api/common_api.ex:463
-#, elixir-format
-msgid "Could not unpin"
-msgstr "无法取消置顶"
-
-#: lib/pleroma/web/common_api/common_api.ex:216
#, elixir-format
+#: lib/pleroma/web/common_api.ex:202
msgid "Could not unrepeat"
msgstr "无法取消转发"
-#: lib/pleroma/web/common_api/common_api.ex:512
-#: lib/pleroma/web/common_api/common_api.ex:521
#, elixir-format
+#: lib/pleroma/web/common_api.ex:530
+#: lib/pleroma/web/common_api.ex:539
msgid "Could not update state"
msgstr "无法更新状态"
-#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:207
#, elixir-format
+#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:205
msgid "Error."
msgstr "错误。"
-#: lib/pleroma/web/twitter_api/twitter_api.ex:106
#, elixir-format
+#: lib/pleroma/web/twitter_api/twitter_api.ex:99
msgid "Invalid CAPTCHA"
msgstr "无效的验证码"
-#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:116
-#: lib/pleroma/web/oauth/oauth_controller.ex:568
#, elixir-format
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:144
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:631
msgid "Invalid credentials"
msgstr "无效的凭据"
-#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38
#, elixir-format
+#: lib/pleroma/web/plugs/ensure_authenticated_plug.ex:42
msgid "Invalid credentials."
msgstr "无效的凭据。"
-#: lib/pleroma/web/common_api/common_api.ex:355
#, elixir-format
+#: lib/pleroma/web/common_api.ex:337
msgid "Invalid indices"
msgstr "无效的索引"
-#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:29
#, elixir-format
+#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:29
msgid "Invalid parameters"
msgstr "无效的参数"
-#: lib/pleroma/web/common_api/utils.ex:414
#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:349
msgid "Invalid password."
msgstr "无效的密码。"
-#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:220
#, elixir-format
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:254
msgid "Invalid request"
msgstr "无效的请求"
-#: lib/pleroma/web/twitter_api/twitter_api.ex:109
#, elixir-format
+#: lib/pleroma/web/twitter_api/twitter_api.ex:102
msgid "Kocaptcha service unavailable"
msgstr "Kocaptcha 服务不可用"
-#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:112
#, elixir-format
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:140
msgid "Missing parameters"
msgstr "缺少参数"
-#: lib/pleroma/web/common_api/utils.ex:547
#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:477
msgid "No such conversation"
msgstr "没有该对话"
-#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:388
-#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:414 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:456
#, elixir-format, fuzzy
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:171
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:197
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:239
msgid "No such permission_group"
msgstr "没有该权限组"
-#: lib/pleroma/plugs/uploaded_media.ex:84
-#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11
-#: lib/pleroma/web/feed/user_controller.ex:71 lib/pleroma/web/ostatus/ostatus_controller.ex:143
#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:504
+#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11
+#: lib/pleroma/web/feed/tag_controller.ex:16
+#: lib/pleroma/web/feed/user_controller.ex:69
+#: lib/pleroma/web/o_status/o_status_controller.ex:132
+#: lib/pleroma/web/plugs/uploaded_media.ex:84
msgid "Not found"
msgstr "未找到"
-#: lib/pleroma/web/common_api/common_api.ex:331
#, elixir-format
+#: lib/pleroma/web/common_api.ex:308
msgid "Poll's author can't vote"
msgstr "投票的发起者不能投票"
+#, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
-#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49
-#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:50 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:306
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:39
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:51
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:52
+#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:326
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
-#, elixir-format
msgid "Record not found"
msgstr "未找到该记录"
-#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35
-#: lib/pleroma/web/feed/user_controller.ex:77 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:36
-#: lib/pleroma/web/ostatus/ostatus_controller.ex:149
#, elixir-format
+#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35
+#: lib/pleroma/web/feed/user_controller.ex:78
+#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:42
+#: lib/pleroma/web/o_status/o_status_controller.ex:138
msgid "Something went wrong"
msgstr "发生了一些错误"
-#: lib/pleroma/web/common_api/activity_draft.ex:107
#, elixir-format
+#: lib/pleroma/web/common_api/activity_draft.ex:143
msgid "The message visibility must be direct"
msgstr "该消息必须为私信"
-#: lib/pleroma/web/common_api/utils.ex:573
#, elixir-format
+#: lib/pleroma/web/common_api/utils.ex:492
msgid "The status is over the character limit"
msgstr "状态超过了字符数限制"
-#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31
#, elixir-format
+#: lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex:36
msgid "This resource requires authentication."
msgstr "该资源需要认证。"
-#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
#, elixir-format, fuzzy
+#: lib/pleroma/web/plugs/rate_limiter.ex:208
msgid "Throttled"
msgstr "节流了"
-#: lib/pleroma/web/common_api/common_api.ex:356
#, elixir-format
+#: lib/pleroma/web/common_api.ex:338
msgid "Too many choices"
msgstr "太多选项"
-#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:443
-#, elixir-format
-msgid "Unhandled activity type"
-msgstr ""
-
-#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:485
#, elixir-format
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:268
msgid "You can't revoke your own admin status."
msgstr "您不能撤消自己的管理员权限。"
-#: lib/pleroma/web/oauth/oauth_controller.ex:221
-#: lib/pleroma/web/oauth/oauth_controller.ex:308
#, elixir-format
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:243
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:333
msgid "Your account is currently disabled"
msgstr "您的账户已被禁用"
-#: lib/pleroma/web/oauth/oauth_controller.ex:183
-#: lib/pleroma/web/oauth/oauth_controller.ex:331
#, elixir-format
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:205
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:356
msgid "Your login is missing a confirmed e-mail address"
msgstr "您的账户缺少已认证的 e-mail 地址"
-#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:390
#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:392
msgid "can't read inbox of %{nickname} as %{as_nickname}"
msgstr "无法以 %{as_nickname} 读取 %{nickname} 的收件箱"
-#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:473
#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:491
msgid "can't update outbox of %{nickname} as %{as_nickname}"
msgstr "无法以 %{as_nickname} 更新 %{nickname} 的出件箱"
-#: lib/pleroma/web/common_api/common_api.ex:471
#, elixir-format
+#: lib/pleroma/web/common_api.ex:475
msgid "conversation is already muted"
msgstr "对话已经被静音"
-#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:314
-#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:492
#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:510
msgid "error"
msgstr "错误"
-#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:32
#, elixir-format
+#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:34
msgid "mascots can only be images"
msgstr "吉祥物只能是图片"
-#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62
#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:63
msgid "not found"
msgstr "未找到"
-#: lib/pleroma/web/oauth/oauth_controller.ex:394
#, elixir-format
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:437
msgid "Bad OAuth request."
msgstr "错误的 OAuth 请求。"
-#: lib/pleroma/web/twitter_api/twitter_api.ex:115
#, elixir-format
+#: lib/pleroma/web/twitter_api/twitter_api.ex:108
msgid "CAPTCHA already used"
msgstr "验证码已被使用"
-#: lib/pleroma/web/twitter_api/twitter_api.ex:112
#, elixir-format
+#: lib/pleroma/web/twitter_api/twitter_api.ex:105
msgid "CAPTCHA expired"
msgstr "验证码已过期"
-#: lib/pleroma/plugs/uploaded_media.ex:57
#, elixir-format
+#: lib/pleroma/web/plugs/uploaded_media.ex:57
msgid "Failed"
msgstr "失败"
-#: lib/pleroma/web/oauth/oauth_controller.ex:410
#, elixir-format, fuzzy
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:453
msgid "Failed to authenticate: %{message}."
msgstr "认证失败:%{message}。"
-#: lib/pleroma/web/oauth/oauth_controller.ex:441
#, elixir-format
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:484
msgid "Failed to set up user account."
msgstr "建立用户帐号失败。"
-#: lib/pleroma/plugs/oauth_scopes_plug.ex:38
#, elixir-format
+#: lib/pleroma/web/plugs/o_auth_scopes_plug.ex:37
msgid "Insufficient permissions: %{permissions}."
msgstr "权限不足:%{permissions}。"
-#: lib/pleroma/plugs/uploaded_media.ex:104
#, elixir-format
+#: lib/pleroma/web/plugs/uploaded_media.ex:111
msgid "Internal Error"
msgstr "内部错误"
-#: lib/pleroma/web/oauth/fallback_controller.ex:22
-#: lib/pleroma/web/oauth/fallback_controller.ex:29
#, elixir-format
+#: lib/pleroma/web/o_auth/fallback_controller.ex:22
+#: lib/pleroma/web/o_auth/fallback_controller.ex:29
msgid "Invalid Username/Password"
msgstr "无效的用户名/密码"
-#: lib/pleroma/web/twitter_api/twitter_api.ex:118
#, elixir-format, fuzzy
+#: lib/pleroma/web/twitter_api/twitter_api.ex:111
msgid "Invalid answer data"
msgstr "无效的回答数据"
-#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33
#, elixir-format
+#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33
msgid "Nodeinfo schema version not handled"
msgstr ""
-#: lib/pleroma/web/oauth/oauth_controller.ex:172
#, elixir-format
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:194
msgid "This action is outside the authorized scopes"
msgstr "此操作在许可范围以外"
-#: lib/pleroma/web/oauth/fallback_controller.ex:14
#, elixir-format
+#: lib/pleroma/web/o_auth/fallback_controller.ex:14
msgid "Unknown error, please check the details and try again."
msgstr "未知错误,请检查并重试。"
-#: lib/pleroma/web/oauth/oauth_controller.ex:119
-#: lib/pleroma/web/oauth/oauth_controller.ex:158
#, elixir-format
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:136
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:180
msgid "Unlisted redirect_uri."
msgstr ""
-#: lib/pleroma/web/oauth/oauth_controller.ex:390
#, elixir-format
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:433
msgid "Unsupported OAuth provider: %{provider}."
msgstr "不支持的 OAuth 提供者:%{provider}。"
-#: lib/pleroma/uploaders/uploader.ex:72
#, elixir-format, fuzzy
+#: lib/pleroma/uploaders/uploader.ex:74
msgid "Uploader callback timeout"
msgstr "上传回复超时"
-#: lib/pleroma/web/uploader_controller.ex:23
#, elixir-format
+#: lib/pleroma/web/uploader_controller.ex:23
msgid "bad request"
msgstr "错误的请求"
-#: lib/pleroma/web/twitter_api/twitter_api.ex:103
#, elixir-format
+#: lib/pleroma/web/twitter_api/twitter_api.ex:96
msgid "CAPTCHA Error"
msgstr "验证码错误"
-#: lib/pleroma/web/common_api/common_api.ex:290
#, elixir-format, fuzzy
+#: lib/pleroma/web/common_api.ex:266
msgid "Could not add reaction emoji"
msgstr "无法添加表情反应"
-#: lib/pleroma/web/common_api/common_api.ex:301
#, elixir-format
+#: lib/pleroma/web/common_api.ex:277
msgid "Could not remove reaction emoji"
msgstr "无法移除表情反应"
-#: lib/pleroma/web/twitter_api/twitter_api.ex:129
#, elixir-format
+#: lib/pleroma/web/twitter_api/twitter_api.ex:122
msgid "Invalid CAPTCHA (Missing parameter: %{name})"
msgstr "无效的验证码(缺少参数:%{name})"
-#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92
#, elixir-format
+#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:96
msgid "List not found"
msgstr "未找到列表"
-#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:123
#, elixir-format
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:151
msgid "Missing parameter: %{name}"
msgstr "缺少参数:%{name}"
-#: lib/pleroma/web/oauth/oauth_controller.ex:210
-#: lib/pleroma/web/oauth/oauth_controller.ex:321
#, elixir-format
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:232
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:346
msgid "Password reset is required"
msgstr "需要重置密码"
+#, elixir-format
#: lib/pleroma/tests/auth_test_controller.ex:9
-#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6
-#: lib/pleroma/web/admin_api/controllers/config_controller.ex:6 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:6
-#: lib/pleroma/web/admin_api/controllers/invite_controller.ex:6 lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex:6
-#: lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex:6 lib/pleroma/web/admin_api/controllers/relay_controller.ex:6
-#: lib/pleroma/web/admin_api/controllers/report_controller.ex:6 lib/pleroma/web/admin_api/controllers/status_controller.ex:6
-#: lib/pleroma/web/controller_helper.ex:6 lib/pleroma/web/embed_controller.ex:6
-#: lib/pleroma/web/fallback_redirect_controller.ex:6 lib/pleroma/web/feed/tag_controller.ex:6
-#: lib/pleroma/web/feed/user_controller.ex:6 lib/pleroma/web/mailer/subscription_controller.ex:2
-#: lib/pleroma/web/masto_fe_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6
-#: lib/pleroma/web/mastodon_api/controllers/app_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6
-#: lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6
-#: lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6
-#: lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6
-#: lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6
-#: lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14
-#: lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6
-#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/report_controller.ex:8
-#: lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6
-#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7
-#: lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6
-#: lib/pleroma/web/media_proxy/media_proxy_controller.ex:6 lib/pleroma/web/mongooseim/mongoose_im_controller.ex:6
-#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6 lib/pleroma/web/oauth/fallback_controller.ex:6
-#: lib/pleroma/web/oauth/mfa_controller.ex:10 lib/pleroma/web/oauth/oauth_controller.ex:6
-#: lib/pleroma/web/ostatus/ostatus_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6
-#: lib/pleroma/web/pleroma_api/controllers/chat_controller.ex:5 lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex:6
-#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:2 lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex:6
-#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/notification_controller.ex:6
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/chat_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/config_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/frontend_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/instance_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/instance_document_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/invite_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/relay_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/report_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/status_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/user_controller.ex:6
+#: lib/pleroma/web/controller_helper.ex:6
+#: lib/pleroma/web/embed_controller.ex:6
+#: lib/pleroma/web/fallback/redirect_controller.ex:6
+#: lib/pleroma/web/feed/tag_controller.ex:6
+#: lib/pleroma/web/feed/user_controller.ex:6
+#: lib/pleroma/web/mailer/subscription_controller.ex:6
+#: lib/pleroma/web/manifest_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/app_controller.ex:11
+#: lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/directory_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14
+#: lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/report_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7
+#: lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6
+#: lib/pleroma/web/media_proxy/media_proxy_controller.ex:6
+#: lib/pleroma/web/mongoose_im/mongoose_im_controller.ex:6
+#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6
+#: lib/pleroma/web/o_auth/fallback_controller.ex:6
+#: lib/pleroma/web/o_auth/mfa_controller.ex:10
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:6
+#: lib/pleroma/web/o_status/o_status_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/app_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/backup_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/chat_controller.ex:5
+#: lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/instances_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/notification_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/report_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex:6
-#: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7 lib/pleroma/web/static_fe/static_fe_controller.ex:6
-#: lib/pleroma/web/twitter_api/controllers/password_controller.ex:10 lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6
-#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:6 lib/pleroma/web/twitter_api/twitter_api_controller.ex:6
-#: lib/pleroma/web/uploader_controller.ex:6 lib/pleroma/web/web_finger/web_finger_controller.ex:6
-#, elixir-format
+#: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7
+#: lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex:6
+#: lib/pleroma/web/static_fe/static_fe_controller.ex:6
+#: lib/pleroma/web/twitter_api/controller.ex:6
+#: lib/pleroma/web/twitter_api/controllers/password_controller.ex:10
+#: lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6
+#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:6
+#: lib/pleroma/web/uploader_controller.ex:6
+#: lib/pleroma/web/web_finger/web_finger_controller.ex:6
msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
msgstr ""
-#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28
#, elixir-format, fuzzy
+#: lib/pleroma/web/plugs/ensure_authenticated_plug.ex:32
msgid "Two-factor authentication enabled, you must use a access token."
msgstr "已启用两因素验证,您需要使用访问令牌。"
-#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210
-#, elixir-format
-msgid "Unexpected error occurred while adding file to pack."
-msgstr "向表情包添加文件时发生了没有预料到的错误。"
-
-#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:138
-#, elixir-format
-msgid "Unexpected error occurred while creating pack."
-msgstr "创建表情包时发生了没有预料到的错误。"
-
-#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:278
-#, elixir-format
-msgid "Unexpected error occurred while removing file from pack."
-msgstr "从表情包移除文件时发生了没有预料到的错误。"
-
-#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:250
-#, elixir-format
-msgid "Unexpected error occurred while updating file in pack."
-msgstr "更新表情包内的文件时发生了没有预料到的错误。"
-
-#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:179
-#, elixir-format
-msgid "Unexpected error occurred while updating pack metadata."
-msgstr "更新表情包元数据时发生了没有预料到的错误。"
-
-#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
#, elixir-format, fuzzy
+#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
msgid "Web push subscription is disabled on this Pleroma instance"
msgstr "此 Pleroma 实例禁用了网页推送订阅"
-#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451
#, elixir-format
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:234
msgid "You can't revoke your own admin/moderator status."
msgstr "您不能撤消自己的管理员权限。"
-#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126
#, elixir-format
+#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:129
msgid "authorization required for timeline view"
msgstr "浏览时间线需要认证"
-#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24
#, elixir-format
+#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24
msgid "Access denied"
msgstr "拒绝访问"
-#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282
#, elixir-format
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:321
msgid "This API requires an authenticated user"
msgstr "此 API 需要已认证的用户"
-#: lib/pleroma/plugs/user_is_admin_plug.ex:21
#, elixir-format
+#: lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex:26
+#: lib/pleroma/web/plugs/user_is_admin_plug.ex:21
msgid "User is not an admin."
msgstr "该用户不是管理员。"
+
+#, elixir-format
+#: lib/pleroma/user/backup.ex:75
+msgid "Last export was less than a day ago"
+msgid_plural "Last export was less than %{days} days ago"
+msgstr[0] ""
+
+#, elixir-format
+#: lib/pleroma/user/backup.ex:93
+msgid "Backups require enabled email"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:423
+msgid "Character limit (%{limit} characters) exceeded, contains %{length} characters"
+msgstr ""
+
+#, elixir-format
+#: lib/pleroma/user/backup.ex:98
+msgid "Email is required"
+msgstr ""
+
+#, elixir-format, fuzzy
+#: lib/pleroma/web/common_api/utils.ex:507
+msgid "Too many attachments"
+msgstr "太多选项"
+
+#, elixir-format, fuzzy
+#: lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex:33
+#: lib/pleroma/web/plugs/user_is_staff_plug.ex:20
+msgid "User is not a staff member."
+msgstr "该用户不是管理员。"
+
+#, elixir-format
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:366
+msgid "Your account is awaiting approval."
+msgstr ""
diff --git a/priv/gettext/zh_Hans/LC_MESSAGES/posix_errors.po b/priv/gettext/zh_Hans/LC_MESSAGES/posix_errors.po
new file mode 100644
index 000000000..d0d08cc9c
--- /dev/null
+++ b/priv/gettext/zh_Hans/LC_MESSAGES/posix_errors.po
@@ -0,0 +1,153 @@
+## "msgid"s in this file come from POT (.pot) files.
+##
+## Do not add, change, or remove "msgid"s manually here as
+## they're tied to the ones in the corresponding POT file
+## (with the same domain).
+##
+## Use "mix gettext.extract --merge" or "mix gettext.merge"
+## to merge POT files into PO files.
+msgid ""
+msgstr ""
+"Language: zh_Hans\n"
+"Plural-Forms: nplurals=1\n"
+
+msgid "eperm"
+msgstr ""
+
+msgid "eacces"
+msgstr ""
+
+msgid "eagain"
+msgstr ""
+
+msgid "ebadf"
+msgstr ""
+
+msgid "ebadmsg"
+msgstr ""
+
+msgid "ebusy"
+msgstr ""
+
+msgid "edeadlk"
+msgstr ""
+
+msgid "edeadlock"
+msgstr ""
+
+msgid "edquot"
+msgstr ""
+
+msgid "eexist"
+msgstr ""
+
+msgid "efault"
+msgstr ""
+
+msgid "efbig"
+msgstr ""
+
+msgid "eftype"
+msgstr ""
+
+msgid "eintr"
+msgstr ""
+
+msgid "einval"
+msgstr ""
+
+msgid "eio"
+msgstr ""
+
+msgid "eisdir"
+msgstr ""
+
+msgid "eloop"
+msgstr ""
+
+msgid "emfile"
+msgstr ""
+
+msgid "emlink"
+msgstr ""
+
+msgid "emultihop"
+msgstr ""
+
+msgid "enametoolong"
+msgstr ""
+
+msgid "enfile"
+msgstr ""
+
+msgid "enobufs"
+msgstr ""
+
+msgid "enodev"
+msgstr ""
+
+msgid "enolck"
+msgstr ""
+
+msgid "enolink"
+msgstr ""
+
+msgid "enoent"
+msgstr ""
+
+msgid "enomem"
+msgstr ""
+
+msgid "enospc"
+msgstr ""
+
+msgid "enosr"
+msgstr ""
+
+msgid "enostr"
+msgstr ""
+
+msgid "enosys"
+msgstr ""
+
+msgid "enotblk"
+msgstr ""
+
+msgid "enotdir"
+msgstr ""
+
+msgid "enotsup"
+msgstr ""
+
+msgid "enxio"
+msgstr ""
+
+msgid "eopnotsupp"
+msgstr ""
+
+msgid "eoverflow"
+msgstr ""
+
+msgid "epipe"
+msgstr ""
+
+msgid "erange"
+msgstr ""
+
+msgid "erofs"
+msgstr ""
+
+msgid "espipe"
+msgstr ""
+
+msgid "esrch"
+msgstr ""
+
+msgid "estale"
+msgstr ""
+
+msgid "etxtbsy"
+msgstr ""
+
+msgid "exdev"
+msgstr ""
diff --git a/priv/gettext/zh_Hans/LC_MESSAGES/static_pages.po b/priv/gettext/zh_Hans/LC_MESSAGES/static_pages.po
new file mode 100644
index 000000000..fa0412cec
--- /dev/null
+++ b/priv/gettext/zh_Hans/LC_MESSAGES/static_pages.po
@@ -0,0 +1,547 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR Free Software Foundation, Inc.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"PO-Revision-Date: 2022-04-07 17:40-0400\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#~ ## "msgid"s in this file come from POT (.pot) files.
+#~ ##
+#~ ## Do not add, change, or remove "msgid"s manually here as
+#~ ## they're tied to the ones in the corresponding POT file
+#~ ## (with the same domain).
+#~ ##
+#~ ## Use "mix gettext.extract --merge" or "mix gettext.merge"
+#~ ## to merge POT files into PO files.
+#~ msgid ""
+#~ msgstr ""
+#~ "Language: zh_Hans\n"
+#~ "Plural-Forms: nplurals=1\n"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:9
+msgctxt "remote follow authorization button"
+msgid "Authorize"
+msgstr "授权"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:2
+msgctxt "remote follow error"
+msgid "Error fetching user"
+msgstr "获取用户时出错"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:4
+msgctxt "remote follow header"
+msgid "Remote follow"
+msgstr "远程关注"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:8
+msgctxt "placeholder text for auth code entry"
+msgid "Authentication code"
+msgstr "授权代码"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:10
+msgctxt "placeholder text for password entry"
+msgid "Password"
+msgstr "密码"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:8
+msgctxt "placeholder text for username entry"
+msgid "Username"
+msgstr "用户名"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:13
+msgctxt "remote follow authorization button for login"
+msgid "Authorize"
+msgstr "授权"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:12
+msgctxt "remote follow authorization button for mfa"
+msgid "Authorize"
+msgstr "授权"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:2
+msgctxt "remote follow error"
+msgid "Error following account"
+msgstr "关注用户时出错"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:4
+msgctxt "remote follow header, need login"
+msgid "Log in to follow"
+msgstr "登录以关注"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:4
+msgctxt "remote follow mfa header"
+msgid "Two-factor authentication"
+msgstr "两步鉴权"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:4
+msgctxt "remote follow success"
+msgid "Account followed!"
+msgstr "已经关注了账号!"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:7
+msgctxt "placeholder text for account id"
+msgid "Your account ID, e.g. lain@quitter.se"
+msgstr "你的账户 ID,如 lain@quitter.se"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:8
+msgctxt "remote follow authorization button for following with a remote account"
+msgid "Follow"
+msgstr "关注"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:2
+msgctxt "remote follow error"
+msgid "Error: %{error}"
+msgstr "错误:%{error}"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:4
+msgctxt "remote follow header"
+msgid "Remotely follow %{nickname}"
+msgstr "远程关注 %{nickname}"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:12
+msgctxt "password reset button"
+msgid "Reset"
+msgstr "重置"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex:4
+msgctxt "password reset failed homepage link"
+msgid "Homepage"
+msgstr "回主页"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex:1
+msgctxt "password reset failed message"
+msgid "Password reset failed"
+msgstr "密码重置失败"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:8
+msgctxt "password reset form confirm password prompt"
+msgid "Confirmation"
+msgstr "确认密码"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:4
+msgctxt "password reset form password prompt"
+msgid "Password"
+msgstr "密码"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex:1
+msgctxt "password reset invalid token message"
+msgid "Invalid Token"
+msgstr "无效的令牌"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex:2
+msgctxt "password reset successful homepage link"
+msgid "Homepage"
+msgstr "回主页"
+
+#, elixir-format
+#: lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex:1
+msgctxt "password reset successful message"
+msgid "Password changed!"
+msgstr "密码已经修改了!"
+
+#, elixir-format
+#: lib/pleroma/web/templates/feed/feed/tag.atom.eex:15
+#: lib/pleroma/web/templates/feed/feed/tag.rss.eex:7
+msgctxt "tag feed description"
+msgid "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse."
+msgstr "这些是标了 #%{tag} 签的公开文章。你要是在联邦宇宙的任何地方有账号,就能和它们互动。"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:1
+msgctxt "oauth authorization exists page title"
+msgid "Authorization exists"
+msgstr "授权已经存在"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:32
+msgctxt "oauth authorize approve button"
+msgid "Approve"
+msgstr "批准"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:30
+msgctxt "oauth authorize cancel button"
+msgid "Cancel"
+msgstr "取消"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:23
+msgctxt "oauth authorize message"
+msgid "Application <strong>%{client_name}</strong> is requesting access to your account."
+msgstr "应用程序 <strong>%{client_name}</strong> 在请求访问你的账号。"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:1
+msgctxt "oauth authorized page title"
+msgid "Successfully authorized"
+msgstr "成功授权"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex:1
+msgctxt "oauth external provider page title"
+msgid "Sign in with external provider"
+msgstr "通过外部提供者登录"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex:13
+msgctxt "oauth external provider sign in button"
+msgid "Sign in with %{strategy}"
+msgstr "通过 %{strategy} 登录"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:54
+msgctxt "oauth login button"
+msgid "Log In"
+msgstr "登录"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:51
+msgctxt "oauth login password prompt"
+msgid "Password"
+msgstr "密码"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:47
+msgctxt "oauth login username prompt"
+msgid "Username"
+msgstr "用户名"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:39
+msgctxt "oauth register nickname prompt"
+msgid "Pleroma Handle"
+msgstr "Pleroma 用户名"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:37
+msgctxt "oauth register nickname unchangeable warning"
+msgid "Choose carefully! You won't be able to change this later. You will be able to change your display name, though."
+msgstr "选仔细了!你之后就不能改它了。但是你可以改显示名。"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:18
+msgctxt "oauth register page email prompt"
+msgid "Email"
+msgstr "邮箱"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:10
+msgctxt "oauth register page fill form prompt"
+msgid "If you'd like to register a new account, please provide the details below."
+msgstr "如果你想注册新账号,请提供如下信息。"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:35
+msgctxt "oauth register page login button"
+msgid "Proceed as existing user"
+msgstr "以已有用户继续"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:31
+msgctxt "oauth register page login password prompt"
+msgid "Password"
+msgstr "密码"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:24
+msgctxt "oauth register page login prompt"
+msgid "Alternatively, sign in to connect to existing account."
+msgstr "或者,登录到已有账号。"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:27
+msgctxt "oauth register page login username prompt"
+msgid "Name or email"
+msgstr "名字或邮箱"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:14
+msgctxt "oauth register page nickname prompt"
+msgid "Nickname"
+msgstr "昵称"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:22
+msgctxt "oauth register page register button"
+msgid "Proceed as new user"
+msgstr "以新用户继续"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:8
+msgctxt "oauth register page title"
+msgid "Registration Details"
+msgstr "注册详情"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:36
+msgctxt "oauth register page title"
+msgid "This is the first time you visit! Please enter your Pleroma handle."
+msgstr "这是你第一次访问。请输入 Pleroma 用户名。"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex:2
+msgctxt "oauth scopes message"
+msgid "The following permissions will be granted"
+msgstr "将要允许如下权限"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:2
+#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:2
+msgctxt "oauth token code message"
+msgid "Token code is <br>%{token}"
+msgstr "令牌代码是<br>%{token}"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:12
+msgctxt "mfa auth code prompt"
+msgid "Authentication code"
+msgstr "鉴权代码"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:8
+msgctxt "mfa auth page title"
+msgid "Two-factor authentication"
+msgstr "两步鉴权"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:23
+msgctxt "mfa auth page use recovery code link"
+msgid "Enter a two-factor recovery code"
+msgstr "输入两步恢复码"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:20
+msgctxt "mfa auth verify code button"
+msgid "Verify"
+msgstr "验证"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:8
+msgctxt "mfa recover page title"
+msgid "Two-factor recovery"
+msgstr "两步恢复"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:12
+msgctxt "mfa recover recovery code prompt"
+msgid "Recovery code"
+msgstr "恢复码"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:23
+msgctxt "mfa recover use 2fa code link"
+msgid "Enter a two-factor code"
+msgstr "输入鉴权码"
+
+#, elixir-format
+#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:20
+msgctxt "mfa recover verify recovery code button"
+msgid "Verify"
+msgstr "验证"
+
+#, elixir-format
+#: lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex:8
+msgctxt "static fe profile page remote follow button"
+msgid "Remote follow"
+msgstr "远程关注"
+
+#, elixir-format
+#: lib/pleroma/web/templates/email/digest.html.eex:163
+msgctxt "digest email header line"
+msgid "Hey %{nickname}, here is what you've missed!"
+msgstr "早 %{nickname},你刚错过这些!"
+
+#, elixir-format
+#: lib/pleroma/web/templates/email/digest.html.eex:544
+msgctxt "digest email receiver address"
+msgid "The email address you are subscribed as is <a href='mailto:%{@user.email}' style='color: %{color};text-decoration: none;'>%{email}</a>. "
+msgstr "你订阅的邮箱地址是 <a href='mailto:%{@user.email}' style='color: %{color};text-decoration: none;'>%{email}</a>。"
+
+#, elixir-format
+#: lib/pleroma/web/templates/email/digest.html.eex:538
+msgctxt "digest email sending reason"
+msgid "You have received this email because you have signed up to receive digest emails from <b>%{instance}</b> Pleroma instance."
+msgstr "因为你选择了收取来自 <b>%{instance}</b> 的摘要邮件,所以你会收到这封邮件。"
+
+#, elixir-format
+#: lib/pleroma/web/templates/email/digest.html.eex:547
+msgctxt "digest email unsubscribe action"
+msgid "To unsubscribe, please go %{here}."
+msgstr "要取消订阅,请去%{here}"
+
+#, elixir-format
+#: lib/pleroma/web/templates/email/digest.html.eex:547
+msgctxt "digest email unsubscribe action link text"
+msgid "here"
+msgstr "此处"
+
+#, elixir-format
+#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex:1
+msgctxt "mailer unsubscribe failed message"
+msgid "UNSUBSCRIBE FAILURE"
+msgstr "取消订阅失败"
+
+#, elixir-format
+#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex:1
+msgctxt "mailer unsubscribe successful message"
+msgid "UNSUBSCRIBE SUCCESSFUL"
+msgstr "取消订阅成功"
+
+#, elixir-format
+#: lib/pleroma/web/templates/email/digest.html.eex:385
+msgctxt "new followers count header"
+msgid "%{count} New Follower"
+msgid_plural "%{count} New Followers"
+msgstr[0] "%{count} 个新关注者"
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:356
+msgctxt "account archive email body - self-requested"
+msgid "<p>You requested a full backup of your Pleroma account. It's ready for download:</p>\n<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
+msgstr ""
+"<p>你之前要了一份你的 Pleroma 账号的完整备份。现在可以下载了:</p>\n"
+"<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:384
+msgctxt "account archive email subject"
+msgid "Your account archive is ready"
+msgstr "你的账号存档准备好了"
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:188
+msgctxt "approval pending email body"
+msgid "<h3>Awaiting Approval</h3>\n<p>Your account at %{instance_name} is being reviewed by staff. You will receive another email once your account is approved.</p>\n"
+msgstr ""
+"<h3>等待批准</h3>\n"
+"<p>管理人员正在审核你在 %{instance_name} 的账号。等账号批准之后你会收到另一封邮件。</p>\n"
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:202
+msgctxt "approval pending email subject"
+msgid "Your account is awaiting approval"
+msgstr "你的账号在等待批准"
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:158
+msgctxt "confirmation email body"
+msgid "<h3>Thank you for registering on %{instance_name}</h3>\n<p>Email confirmation is required to activate the account.</p>\n<p>Please click the following link to <a href=\"%{confirmation_url}\">activate your account</a>.</p>\n"
+msgstr ""
+"<h3>感谢注册 %{instance_name}</h3>\n"
+"<p>要激活账号,必须验证邮箱。</p>\n"
+"<p>请点如下链接来<a href=\"%{confirmation_url}\">激活账号</a>。</p>\n"
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:174
+msgctxt "confirmation email subject"
+msgid "%{instance_name} account confirmation"
+msgstr "%{instance_name} 账号激活"
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:310
+msgctxt "digest email subject"
+msgid "Your digest from %{instance_name}"
+msgstr "来自 %{instance_name} 的摘要"
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:81
+msgctxt "password reset email body"
+msgid "<h3>Reset your password at %{instance_name}</h3>\n<p>Someone has requested password change for your account at %{instance_name}.</p>\n<p>If it was you, visit the following link to proceed: <a href=\"%{password_reset_url}\">reset password</a>.</p>\n<p>If it was someone else, nothing to worry about: your data is secure and your password has not been changed.</p>\n"
+msgstr ""
+"<h3>重置你在 %{instance_name} 的密码</h3>\n"
+"<p>有人请求了重置你在 %{instance_name} 的账号的密码。</p>\n"
+"<p>如果那是你,访问如下链接以继续:<a href=\"%{password_reset_url}\">重置密码</a>。</p>\n"
+"<p>如果是别人,不必担心:你的数据很安全,密码也没变。</p>\n"
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:98
+msgctxt "password reset email subject"
+msgid "Password reset"
+msgstr "密码重置"
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:215
+msgctxt "successful registration email body"
+msgid "<h3>Hello @%{nickname},</h3>\n<p>Your account at %{instance_name} has been registered successfully.</p>\n<p>No further action is required to activate your account.</p>\n"
+msgstr ""
+"<h3>早 @%{nickname},</h3>\n"
+"<p>你在 %{instance_name} 上的账号已经成功注册了。</p>\n"
+"<p>你的账号已经激活,无需再做任何操作。</p>\n"
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:231
+msgctxt "successful registration email subject"
+msgid "Account registered on %{instance_name}"
+msgstr "在 %{instance_name} 上注册了账号"
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:119
+msgctxt "user invitation email body"
+msgid "<h3>You are invited to %{instance_name}</h3>\n<p>%{inviter_name} invites you to join %{instance_name}, an instance of Pleroma federated social networking platform.</p>\n<p>Click the following link to register: <a href=\"%{registration_url}\">accept invitation</a>.</p>\n"
+msgstr ""
+"<h3>有人邀请你去 %{instance_name}</h3>\n"
+"<p>%{inviter_name} 邀请你去 %{instance_name}。这是社交网络平台 Pleroma 的一个实例。</p>\n"
+"<p>点如下链接以注册:<a href=\"%{registration_url}\">接受邀请</a>。</p>\n"
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:136
+msgctxt "user invitation email subject"
+msgid "Invitation to %{instance_name}"
+msgstr "去 %{instance_name} 的邀请"
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:53
+msgctxt "welcome email html body"
+msgid "Welcome to %{instance_name}!"
+msgstr "欢迎来到 %{instance_name}!"
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:41
+msgctxt "welcome email subject"
+msgid "Welcome to %{instance_name}!"
+msgstr "欢迎来到 %{instance_name}!"
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:65
+msgctxt "welcome email text body"
+msgid "Welcome to %{instance_name}!"
+msgstr "欢迎来到 %{instance_name}!"
+
+#, elixir-format
+#: lib/pleroma/emails/user_email.ex:368
+msgctxt "account archive email body - admin requested"
+msgid "<p>Admin @%{admin_nickname} requested a full backup of your Pleroma account. It's ready for download:</p>\n<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
+msgstr ""
+"<p>管理员 @%{admin_nickname} 之前要了一份你的 Pleroma 账号的完整备份。现在可以下载了:</p>\n"
+"<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
diff --git a/priv/repo/migrations/20220220135625_upload_filter_exiftool_to_exiftool_strip_location.exs b/priv/repo/migrations/20220220135625_upload_filter_exiftool_to_exiftool_strip_location.exs
new file mode 100644
index 000000000..0878b9699
--- /dev/null
+++ b/priv/repo/migrations/20220220135625_upload_filter_exiftool_to_exiftool_strip_location.exs
@@ -0,0 +1,37 @@
+defmodule Pleroma.Repo.Migrations.UploadFilterExiftoolToExiftoolStripLocation do
+ use Ecto.Migration
+
+ alias Pleroma.ConfigDB
+
+ def up,
+ do:
+ ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Upload})
+ |> update_filtername(
+ Pleroma.Upload.Filter.Exiftool,
+ Pleroma.Upload.Filter.Exiftool.StripLocation
+ )
+
+ def down,
+ do:
+ ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Upload})
+ |> update_filtername(
+ Pleroma.Upload.Filter.Exiftool.StripLocation,
+ Pleroma.Upload.Filter.Exiftool
+ )
+
+ defp update_filtername(%{value: value}, from_filtername, to_filtername) do
+ new_value =
+ value
+ |> Keyword.update(:filters, [], fn filters ->
+ filters
+ |> Enum.map(fn
+ ^from_filtername -> to_filtername
+ filter -> filter
+ end)
+ end)
+
+ ConfigDB.update_or_create(%{group: :pleroma, key: Pleroma.Upload, value: new_value})
+ end
+
+ defp update_filtername(_, _, _), do: nil
+end
diff --git a/priv/repo/migrations/20220302013920_add_language_to_users.exs b/priv/repo/migrations/20220302013920_add_language_to_users.exs
new file mode 100644
index 000000000..7a63c36aa
--- /dev/null
+++ b/priv/repo/migrations/20220302013920_add_language_to_users.exs
@@ -0,0 +1,9 @@
+defmodule Pleroma.Repo.Migrations.AddLanguageToUsers do
+ use Ecto.Migration
+
+ def change do
+ alter table(:users) do
+ add_if_not_exists(:language, :string)
+ end
+ end
+end
diff --git a/priv/repo/migrations/20220308012601_create_announcements.exs b/priv/repo/migrations/20220308012601_create_announcements.exs
new file mode 100644
index 000000000..01c9ce041
--- /dev/null
+++ b/priv/repo/migrations/20220308012601_create_announcements.exs
@@ -0,0 +1,26 @@
+defmodule Pleroma.Repo.Migrations.CreateAnnouncements do
+ use Ecto.Migration
+
+ def change do
+ create_if_not_exists table(:announcements, primary_key: false) do
+ add(:id, :uuid, primary_key: true)
+ add(:data, :map)
+ add(:starts_at, :naive_datetime)
+ add(:ends_at, :naive_datetime)
+ add(:rendered, :map)
+
+ timestamps()
+ end
+
+ create_if_not_exists table(:announcement_read_relationships) do
+ add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
+ add(:announcement_id, references(:announcements, type: :uuid, on_delete: :delete_all))
+
+ timestamps(updated_at: false)
+ end
+
+ create_if_not_exists(
+ unique_index(:announcement_read_relationships, [:user_id, :announcement_id])
+ )
+ end
+end
diff --git a/priv/repo/migrations/20220506175506_add_index_hotspots.exs b/priv/repo/migrations/20220506175506_add_index_hotspots.exs
new file mode 100644
index 000000000..e1f59bbac
--- /dev/null
+++ b/priv/repo/migrations/20220506175506_add_index_hotspots.exs
@@ -0,0 +1,17 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Repo.Migrations.AddIndexHotspots do
+ use Ecto.Migration
+
+ def change do
+ # Stop inserts into activities from doing a full-table scan of users:
+ create_if_not_exists(index(:users, [:ap_id, "COALESCE(follower_address, '')"]))
+
+ # Change two indexes and a filter recheck into one index scan:
+ create_if_not_exists(index(:following_relationships, [:follower_id, :state]))
+
+ create_if_not_exists(index(:notifications, [:user_id, :seen]))
+ end
+end
diff --git a/priv/repo/migrations/20220602052233_change_report_notes_content_to_text.exs b/priv/repo/migrations/20220602052233_change_report_notes_content_to_text.exs
new file mode 100644
index 000000000..9343373fc
--- /dev/null
+++ b/priv/repo/migrations/20220602052233_change_report_notes_content_to_text.exs
@@ -0,0 +1,16 @@
+defmodule Pleroma.Repo.Migrations.ChangeReportNotesContentToText do
+ use Ecto.Migration
+
+ def up do
+ alter table(:report_notes) do
+ modify(:content, :text)
+ end
+ end
+
+ # 20191203043610_create_report_notes.exs
+ def down do
+ alter table(:report_notes) do
+ modify(:content, :string)
+ end
+ end
+end
diff --git a/test/fixtures/image_with_caption-abstract.jpg b/test/fixtures/image_with_caption-abstract.jpg
new file mode 100644
index 000000000..f982ffa81
--- /dev/null
+++ b/test/fixtures/image_with_caption-abstract.jpg
Binary files differ
diff --git a/test/fixtures/image_with_imagedescription_and_caption-abstract.jpg b/test/fixtures/image_with_imagedescription_and_caption-abstract.jpg
new file mode 100644
index 000000000..c82a269ef
--- /dev/null
+++ b/test/fixtures/image_with_imagedescription_and_caption-abstract.jpg
Binary files differ
diff --git a/test/fixtures/image_with_imagedescription_and_caption-abstract_whitespaces.jpg b/test/fixtures/image_with_imagedescription_and_caption-abstract_whitespaces.jpg
new file mode 100644
index 000000000..a232fd2a1
--- /dev/null
+++ b/test/fixtures/image_with_imagedescription_and_caption-abstract_whitespaces.jpg
Binary files differ
diff --git a/test/fixtures/image_with_no_description.jpg b/test/fixtures/image_with_no_description.jpg
new file mode 100644
index 000000000..ec6fc4be8
--- /dev/null
+++ b/test/fixtures/image_with_no_description.jpg
Binary files differ
diff --git a/test/mix/tasks/pleroma/digest_test.exs b/test/mix/tasks/pleroma/digest_test.exs
index 0a25b508f..d2a8606c7 100644
--- a/test/mix/tasks/pleroma/digest_test.exs
+++ b/test/mix/tasks/pleroma/digest_test.exs
@@ -53,7 +53,13 @@ defmodule Mix.Tasks.Pleroma.DigestTest do
assert_email_sent(
to: {user2.name, user2.email},
- html_body: ~r/here is what you've missed!/i
+ html_body:
+ Regex.compile!(
+ "here is what you've missed!"
+ |> Phoenix.HTML.html_escape()
+ |> Phoenix.HTML.safe_to_string(),
+ "i"
+ )
)
end
end
diff --git a/test/mix/tasks/pleroma/instance_test.exs b/test/mix/tasks/pleroma/instance_test.exs
index 249689ec6..b1c10e03c 100644
--- a/test/mix/tasks/pleroma/instance_test.exs
+++ b/test/mix/tasks/pleroma/instance_test.exs
@@ -67,7 +67,9 @@ defmodule Mix.Tasks.Pleroma.InstanceTest do
"test/uploads",
"--static-dir",
"./test/../test/instance/static/",
- "--strip-uploads",
+ "--strip-uploads-location",
+ "y",
+ "--read-uploads-description",
"y",
"--dedupe-uploads",
"n",
@@ -91,7 +93,10 @@ defmodule Mix.Tasks.Pleroma.InstanceTest do
assert generated_config =~ "password: \"dbpass\""
assert generated_config =~ "configurable_from_database: true"
assert generated_config =~ "http: [ip: {127, 0, 0, 1}, port: 4000]"
- assert generated_config =~ "filters: [Pleroma.Upload.Filter.Exiftool]"
+
+ assert generated_config =~
+ "filters: [Pleroma.Upload.Filter.Exiftool.StripLocation, Pleroma.Upload.Filter.Exiftool.ReadDescription]"
+
assert File.read!(tmp_path() <> "setup.psql") == generated_setup_psql()
assert File.exists?(Path.expand("./test/instance/static/robots.txt"))
end
diff --git a/test/pleroma/announcement_read_relationship_test.exs b/test/pleroma/announcement_read_relationship_test.exs
new file mode 100644
index 000000000..5fd4ffbef
--- /dev/null
+++ b/test/pleroma/announcement_read_relationship_test.exs
@@ -0,0 +1,40 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.AnnouncementReadRelationshipTest do
+ alias Pleroma.AnnouncementReadRelationship
+
+ use Pleroma.DataCase, async: true
+
+ import Pleroma.Factory
+
+ setup do
+ {:ok, user: insert(:user), announcement: insert(:announcement)}
+ end
+
+ describe "mark_read/2" do
+ test "should insert relationship", %{user: user, announcement: announcement} do
+ {:ok, _} = AnnouncementReadRelationship.mark_read(user, announcement)
+
+ assert AnnouncementReadRelationship.exists?(user, announcement)
+ end
+ end
+
+ describe "mark_unread/2" do
+ test "should delete relationship", %{user: user, announcement: announcement} do
+ {:ok, _} = AnnouncementReadRelationship.mark_read(user, announcement)
+
+ assert :ok = AnnouncementReadRelationship.mark_unread(user, announcement)
+ refute AnnouncementReadRelationship.exists?(user, announcement)
+ end
+
+ test "should not fail if relationship does not exist", %{
+ user: user,
+ announcement: announcement
+ } do
+ assert :ok = AnnouncementReadRelationship.mark_unread(user, announcement)
+ refute AnnouncementReadRelationship.exists?(user, announcement)
+ end
+ end
+end
diff --git a/test/pleroma/announcement_test.exs b/test/pleroma/announcement_test.exs
new file mode 100644
index 000000000..a007c3718
--- /dev/null
+++ b/test/pleroma/announcement_test.exs
@@ -0,0 +1,98 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.AnnouncementTest do
+ alias Pleroma.Announcement
+
+ use Pleroma.DataCase, async: true
+
+ import Pleroma.Factory
+
+ describe "list_all_visible_when/1" do
+ setup do: {:ok, time: NaiveDateTime.utc_now()}
+
+ test "with no start or end time", %{time: time} do
+ _announcement = insert(:announcement)
+
+ assert [_] = Announcement.list_all_visible_when(time)
+ end
+
+ test "with start time before current", %{time: time} do
+ before_now = NaiveDateTime.add(time, -10, :second)
+
+ _announcement = insert(:announcement, %{starts_at: before_now})
+
+ assert [_] = Announcement.list_all_visible_when(time)
+ end
+
+ test "with start time after current", %{time: time} do
+ after_now = NaiveDateTime.add(time, 10, :second)
+
+ _announcement = insert(:announcement, %{starts_at: after_now})
+
+ assert [] = Announcement.list_all_visible_when(time)
+ end
+
+ test "with end time after current", %{time: time} do
+ after_now = NaiveDateTime.add(time, 10, :second)
+
+ _announcement = insert(:announcement, %{ends_at: after_now})
+
+ assert [_] = Announcement.list_all_visible_when(time)
+ end
+
+ test "with end time before current", %{time: time} do
+ before_now = NaiveDateTime.add(time, -10, :second)
+
+ _announcement = insert(:announcement, %{ends_at: before_now})
+
+ assert [] = Announcement.list_all_visible_when(time)
+ end
+
+ test "with both start and end time", %{time: time} do
+ before_now = NaiveDateTime.add(time, -10, :second)
+ after_now = NaiveDateTime.add(time, 10, :second)
+
+ _announcement = insert(:announcement, %{starts_at: before_now, ends_at: after_now})
+
+ assert [_] = Announcement.list_all_visible_when(time)
+ end
+
+ test "with both start and end time, current not in the range", %{time: time} do
+ before_now = NaiveDateTime.add(time, -10, :second)
+ after_now = NaiveDateTime.add(time, 10, :second)
+
+ _announcement = insert(:announcement, %{starts_at: after_now, ends_at: before_now})
+
+ assert [] = Announcement.list_all_visible_when(time)
+ end
+ end
+
+ describe "announcements formatting" do
+ test "it formats links" do
+ raw = "something on https://pleroma.social ."
+ announcement = insert(:announcement, %{data: %{"content" => raw}})
+
+ assert announcement.rendered["content"] =~ ~r(<a.+?https://pleroma.social)
+ assert announcement.data["content"] == raw
+ end
+
+ test "it formats mentions" do
+ user = insert(:user)
+ raw = "something on @#{user.nickname} ."
+ announcement = insert(:announcement, %{data: %{"content" => raw}})
+
+ assert announcement.rendered["content"] =~ ~r(<a.+?#{user.nickname})
+ assert announcement.data["content"] == raw
+ end
+
+ test "it formats tags" do
+ raw = "something on #mew ."
+ announcement = insert(:announcement, %{data: %{"content" => raw}})
+
+ assert announcement.rendered["content"] =~ ~r(<a.+?#mew)
+ assert announcement.data["content"] == raw
+ end
+ end
+end
diff --git a/test/pleroma/config/deprecation_warnings_test.exs b/test/pleroma/config/deprecation_warnings_test.exs
index 202ec4b90..f3453ddb0 100644
--- a/test/pleroma/config/deprecation_warnings_test.exs
+++ b/test/pleroma/config/deprecation_warnings_test.exs
@@ -11,6 +11,62 @@ defmodule Pleroma.Config.DeprecationWarningsTest do
alias Pleroma.Config
alias Pleroma.Config.DeprecationWarnings
+ describe "filter exiftool" do
+ test "gives warning when still used" do
+ clear_config(
+ [Pleroma.Upload, :filters],
+ [Pleroma.Upload.Filter.Exiftool]
+ )
+
+ assert capture_log(fn -> DeprecationWarnings.check_exiftool_filter() end) =~
+ """
+ !!!DEPRECATION WARNING!!!
+ Your config is using Exiftool as a filter instead of Exiftool.StripLocation. This should work for now, but you are advised to change to the new configuration to prevent possible issues later:
+
+ ```
+ config :pleroma, Pleroma.Upload,
+ filters: [Pleroma.Upload.Filter.Exiftool]
+ ```
+
+ Is now
+
+
+ ```
+ config :pleroma, Pleroma.Upload,
+ filters: [Pleroma.Upload.Filter.Exiftool.StripLocation]
+ ```
+ """
+ end
+
+ test "changes setting to exiftool strip location" do
+ clear_config(
+ [Pleroma.Upload, :filters],
+ [Pleroma.Upload.Filter.Exiftool, Pleroma.Upload.Filter.Exiftool.ReadDescription]
+ )
+
+ expected_config = [
+ Pleroma.Upload.Filter.Exiftool.StripLocation,
+ Pleroma.Upload.Filter.Exiftool.ReadDescription
+ ]
+
+ capture_log(fn -> DeprecationWarnings.warn() end)
+
+ assert Config.get([Pleroma.Upload]) |> Keyword.get(:filters, []) == expected_config
+ end
+
+ test "doesn't give a warning with correct config" do
+ clear_config(
+ [Pleroma.Upload, :filters],
+ [
+ Pleroma.Upload.Filter.Exiftool.StripLocation,
+ Pleroma.Upload.Filter.Exiftool.ReadDescription
+ ]
+ )
+
+ assert capture_log(fn -> DeprecationWarnings.check_exiftool_filter() end) == ""
+ end
+ end
+
describe "simple policy tuples" do
test "gives warning when there are still strings" do
clear_config([:mrf_simple],
diff --git a/test/pleroma/config_db_test.exs b/test/pleroma/config_db_test.exs
index ea44b716f..ba7c615e2 100644
--- a/test/pleroma/config_db_test.exs
+++ b/test/pleroma/config_db_test.exs
@@ -238,10 +238,11 @@ defmodule Pleroma.ConfigDBTest do
end
test "ssl options" do
- assert ConfigDB.to_elixir_types([":tlsv1", ":tlsv1.1", ":tlsv1.2"]) == [
+ assert ConfigDB.to_elixir_types([":tlsv1", ":tlsv1.1", ":tlsv1.2", ":tlsv1.3"]) == [
:tlsv1,
:"tlsv1.1",
- :"tlsv1.2"
+ :"tlsv1.2",
+ :"tlsv1.3"
]
end
diff --git a/test/pleroma/docs/generator_test.exs b/test/pleroma/docs/generator_test.exs
index 92b4d13e4..5e41d6ccb 100644
--- a/test/pleroma/docs/generator_test.exs
+++ b/test/pleroma/docs/generator_test.exs
@@ -86,7 +86,7 @@ defmodule Pleroma.Docs.GeneratorTest do
key: :versions,
type: {:list, :atom},
description: "List of TLS version to use",
- suggestions: [:tlsv1, ":tlsv1.1", ":tlsv1.2"]
+ suggestions: [:tlsv1, ":tlsv1.1", ":tlsv1.2", ":tlsv1.3"]
}
]
},
@@ -213,7 +213,7 @@ defmodule Pleroma.Docs.GeneratorTest do
test "suggestion for tls versions" do
[%{children: children} | _] = Generator.convert_to_strings(@descriptions)
child = Enum.at(children, 8)
- assert child[:suggestions] == [":tlsv1", ":tlsv1.1", ":tlsv1.2"]
+ assert child[:suggestions] == [":tlsv1", ":tlsv1.1", ":tlsv1.2", ":tlsv1.3"]
end
test "subgroup with module name" do
diff --git a/test/pleroma/emails/user_email_test.exs b/test/pleroma/emails/user_email_test.exs
index e0f6fd83b..7061148b0 100644
--- a/test/pleroma/emails/user_email_test.exs
+++ b/test/pleroma/emails/user_email_test.exs
@@ -56,4 +56,16 @@ defmodule Pleroma.Emails.UserEmailTest do
assert email.subject == "Your account is awaiting approval"
assert email.html_body =~ "Awaiting Approval"
end
+
+ test "email i18n" do
+ user = insert(:user, language: "en_test")
+ email = UserEmail.approval_pending_email(user)
+ assert email.subject == "xxYour account is awaiting approvalxx"
+ end
+
+ test "email i18n should fallback to default locale if user language is unsupported" do
+ user = insert(:user, language: "unsupported")
+ email = UserEmail.approval_pending_email(user)
+ assert email.subject == "Your account is awaiting approval"
+ end
end
diff --git a/test/pleroma/notification_test.exs b/test/pleroma/notification_test.exs
index 340a5f841..805764ea4 100644
--- a/test/pleroma/notification_test.exs
+++ b/test/pleroma/notification_test.exs
@@ -520,6 +520,25 @@ defmodule Pleroma.NotificationTest do
end
end
+ describe "destroy_multiple_from_types/2" do
+ test "clears all notifications of a certain type for a given user" do
+ report_activity = insert(:report_activity)
+ user1 = insert(:user, is_moderator: true, is_admin: true)
+ user2 = insert(:user, is_moderator: true, is_admin: true)
+ {:ok, _} = Notification.create_notifications(report_activity)
+
+ {:ok, _} =
+ CommonAPI.post(user2, %{
+ status: "hey @#{user1.nickname} !"
+ })
+
+ Notification.destroy_multiple_from_types(user1, ["pleroma:report"])
+
+ assert [%Pleroma.Notification{type: "mention"}] = Notification.for_user(user1)
+ assert [%Pleroma.Notification{type: "pleroma:report"}] = Notification.for_user(user2)
+ end
+ end
+
describe "set_read_up_to()" do
test "it sets all notifications as read up to a specified notification ID" do
user = insert(:user)
diff --git a/test/pleroma/report_note_test.exs b/test/pleroma/report_note_test.exs
index 136ad9a40..e2028a3ba 100644
--- a/test/pleroma/report_note_test.exs
+++ b/test/pleroma/report_note_test.exs
@@ -13,4 +13,27 @@ defmodule Pleroma.ReportNoteTest do
assert {:ok, note} = ReportNote.create(user.id, report.id, "naughty boy")
assert note.content == "naughty boy"
end
+
+ test "create/3 with very long content" do
+ user = insert(:user)
+ report = insert(:report_activity)
+
+ very_long_content = """
+ ] pwgen 25 15
+ eJ9eeceiquoolei2queeLeimi aiN9ie2iokie8chush7aiph5N ulaNgaighoPiequaipuzoog8F
+ Ohphei0hee6hoo0wah4Aasah9 ziel3Yo3eew4neiy3ekiesh8u ue9ShahTh7oongoPheeneijah
+ ohGheeCh6aloque0Neviopou3 ush2oobohxeec4aequeich3Oo Ze3eighoowiojadohch8iCa1n
+ Yu4yieBie9eengoich8fae4th chohqu6exooSiibogh3iefeez peephahtaik9quie5mohD9nee
+ eeQuur3rie5mei8ieng6iesie wei1meinguv0Heidoov8Ibaed deemo2Poh6ohc3eiBeez1uox2
+ ] pwgen 25 15
+ eJ9eeceiquoolei2queeLeimi aiN9ie2iokie8chush7aiph5N ulaNgaighoPiequaipuzoog8F
+ Ohphei0hee6hoo0wah4Aasah9 ziel3Yo3eew4neiy3ekiesh8u ue9ShahTh7oongoPheeneijah
+ ohGheeCh6aloque0Neviopou3 ush2oobohxeec4aequeich3Oo Ze3eighoowiojadohch8iCa1n
+ Yu4yieBie9eengoich8fae4th chohqu6exooSiibogh3iefeez peephahtaik9quie5mohD9nee
+ eeQuur3rie5mei8ieng6iesie wei1meinguv0Heidoov8Ibaed deemo2Poh6ohc3eiBeez1uox2
+ """
+
+ assert {:ok, note} = ReportNote.create(user.id, report.id, very_long_content)
+ assert note.content == very_long_content
+ end
end
diff --git a/test/pleroma/upload/filter/exiftool/read_description_test.exs b/test/pleroma/upload/filter/exiftool/read_description_test.exs
new file mode 100644
index 000000000..7389fda47
--- /dev/null
+++ b/test/pleroma/upload/filter/exiftool/read_description_test.exs
@@ -0,0 +1,117 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Upload.Filter.Exiftool.ReadDescriptionTest do
+ use Pleroma.DataCase, async: true
+ alias Pleroma.Upload.Filter
+
+ @uploads %Pleroma.Upload{
+ name: "image_with_imagedescription_and_caption-abstract.jpg",
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
+ tempfile: Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
+ description: nil
+ }
+
+ test "keeps description when not empty" do
+ uploads = %Pleroma.Upload{
+ name: "image_with_imagedescription_and_caption-abstract.jpg",
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
+ tempfile:
+ Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
+ description: "Some description"
+ }
+
+ assert Filter.Exiftool.ReadDescription.filter(uploads) ==
+ {:ok, :noop}
+ end
+
+ test "otherwise returns ImageDescription when present" do
+ uploads_after = %Pleroma.Upload{
+ name: "image_with_imagedescription_and_caption-abstract.jpg",
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
+ tempfile:
+ Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
+ description: "a descriptive white pixel"
+ }
+
+ assert Filter.Exiftool.ReadDescription.filter(@uploads) ==
+ {:ok, :filtered, uploads_after}
+ end
+
+ test "otherwise returns iptc:Caption-Abstract when present" do
+ upload = %Pleroma.Upload{
+ name: "image_with_caption-abstract.jpg",
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/image_with_caption-abstract.jpg"),
+ tempfile: Path.absname("test/fixtures/image_with_caption-abstract.jpg"),
+ description: nil
+ }
+
+ upload_after = %Pleroma.Upload{
+ name: "image_with_caption-abstract.jpg",
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/image_with_caption-abstract.jpg"),
+ tempfile: Path.absname("test/fixtures/image_with_caption-abstract.jpg"),
+ description: "an abstract white pixel"
+ }
+
+ assert Filter.Exiftool.ReadDescription.filter(upload) ==
+ {:ok, :filtered, upload_after}
+ end
+
+ test "otherwise returns nil" do
+ uploads = %Pleroma.Upload{
+ name: "image_with_no_description.jpg",
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/image_with_no_description.jpg"),
+ tempfile: Path.absname("test/fixtures/image_with_no_description.jpg"),
+ description: nil
+ }
+
+ assert Filter.Exiftool.ReadDescription.filter(uploads) ==
+ {:ok, :filtered, uploads}
+ end
+
+ test "Return nil when image description from EXIF data exceeds the maximum length" do
+ clear_config([:instance, :description_limit], 5)
+
+ assert Filter.Exiftool.ReadDescription.filter(@uploads) ==
+ {:ok, :filtered, @uploads}
+ end
+
+ test "Ignores content with only whitespace" do
+ uploads = %Pleroma.Upload{
+ name: "non-existant.jpg",
+ content_type: "image/jpeg",
+ path:
+ Path.absname(
+ "test/fixtures/image_with_imagedescription_and_caption-abstract_whitespaces.jpg"
+ ),
+ tempfile:
+ Path.absname(
+ "test/fixtures/image_with_imagedescription_and_caption-abstract_whitespaces.jpg"
+ ),
+ description: nil
+ }
+
+ assert Filter.Exiftool.ReadDescription.filter(uploads) ==
+ {:ok, :filtered, uploads}
+ end
+
+ test "Return nil when image description from EXIF data can't be read" do
+ uploads = %Pleroma.Upload{
+ name: "non-existant.jpg",
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/non-existant.jpg"),
+ tempfile: Path.absname("test/fixtures/non-existant_tmp.jpg"),
+ description: nil
+ }
+
+ assert Filter.Exiftool.ReadDescription.filter(uploads) ==
+ {:ok, :filtered, uploads}
+ end
+end
diff --git a/test/pleroma/upload/filter/exiftool_test.exs b/test/pleroma/upload/filter/exiftool/strip_location_test.exs
index 0a0ef2bdf..7e1541f60 100644
--- a/test/pleroma/upload/filter/exiftool_test.exs
+++ b/test/pleroma/upload/filter/exiftool/strip_location_test.exs
@@ -2,7 +2,7 @@
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
-defmodule Pleroma.Upload.Filter.ExiftoolTest do
+defmodule Pleroma.Upload.Filter.Exiftool.StripLocationTest do
use Pleroma.DataCase, async: true
alias Pleroma.Upload.Filter
@@ -21,7 +21,7 @@ defmodule Pleroma.Upload.Filter.ExiftoolTest do
tempfile: Path.absname("test/fixtures/DSCN0010_tmp.jpg")
}
- assert Filter.Exiftool.filter(upload) == {:ok, :filtered}
+ assert Filter.Exiftool.StripLocation.filter(upload) == {:ok, :filtered}
{exif_original, 0} = System.cmd("exiftool", ["test/fixtures/DSCN0010.jpg"])
{exif_filtered, 0} = System.cmd("exiftool", ["test/fixtures/DSCN0010_tmp.jpg"])
@@ -37,6 +37,6 @@ defmodule Pleroma.Upload.Filter.ExiftoolTest do
content_type: "image/webp"
}
- assert Filter.Exiftool.filter(upload) == {:ok, :noop}
+ assert Filter.Exiftool.StripLocation.filter(upload) == {:ok, :noop}
end
end
diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
index bb28a3f81..408389c3a 100644
--- a/test/pleroma/user_test.exs
+++ b/test/pleroma/user_test.exs
@@ -5,6 +5,7 @@
defmodule Pleroma.UserTest do
alias Pleroma.Activity
alias Pleroma.Builders.UserBuilder
+ alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.Tests.ObanHelpers
@@ -617,9 +618,10 @@ defmodule Pleroma.UserTest do
end
test "it restricts certain nicknames" do
+ clear_config([User, :restricted_nicknames], ["about"])
[restricted_name | _] = Pleroma.Config.get([User, :restricted_nicknames])
- assert is_bitstring(restricted_name)
+ assert is_binary(restricted_name)
params =
@full_user_data
@@ -630,6 +632,23 @@ defmodule Pleroma.UserTest do
refute changeset.valid?
end
+ test "it is case-insensitive when restricting nicknames" do
+ clear_config([User, :restricted_nicknames], ["about"])
+ [restricted_name | _] = Pleroma.Config.get([User, :restricted_nicknames])
+
+ assert is_binary(restricted_name)
+
+ restricted_upcase_name = String.upcase(restricted_name)
+
+ params =
+ @full_user_data
+ |> Map.put(:nickname, restricted_upcase_name)
+
+ changeset = User.register_changeset(%User{}, params)
+
+ refute changeset.valid?
+ end
+
test "it blocks blacklisted email domains" do
clear_config([User, :email_blacklist], ["trolling.world"])
@@ -638,6 +657,11 @@ defmodule Pleroma.UserTest do
changeset = User.register_changeset(%User{}, params)
refute changeset.valid?
+ # Block with case-insensitive match
+ params = Map.put(@full_user_data, :email, "troll@TrOlLing.wOrld")
+ changeset = User.register_changeset(%User{}, params)
+ refute changeset.valid?
+
# Block with subdomain match
params = Map.put(@full_user_data, :email, "troll@gnomes.trolling.world")
changeset = User.register_changeset(%User{}, params)
@@ -2210,6 +2234,26 @@ defmodule Pleroma.UserTest do
assert {:ok, user} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}")
assert %User{bio: "test-bio"} = User.get_cached_by_ap_id(user.ap_id)
end
+
+ test "removes report notifs when user isn't superuser any more" do
+ report_activity = insert(:report_activity)
+ user = insert(:user, is_moderator: true, is_admin: true)
+ {:ok, _} = Notification.create_notifications(report_activity)
+
+ assert [%Pleroma.Notification{type: "pleroma:report"}] = Notification.for_user(user)
+
+ {:ok, user} = user |> User.admin_api_update(%{is_moderator: false})
+ # is still superuser because still admin
+ assert [%Pleroma.Notification{type: "pleroma:report"}] = Notification.for_user(user)
+
+ {:ok, user} = user |> User.admin_api_update(%{is_moderator: true, is_admin: false})
+ # is still superuser because still moderator
+ assert [%Pleroma.Notification{type: "pleroma:report"}] = Notification.for_user(user)
+
+ {:ok, user} = user |> User.admin_api_update(%{is_moderator: false})
+ # is not a superuser any more
+ assert [] = Notification.for_user(user)
+ end
end
describe "following/followers synchronization" do
diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
index 1923ec479..1c5c40e84 100644
--- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
@@ -291,6 +291,30 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
end
+ test "does not cache authenticated response", %{conn: conn} do
+ user = insert(:user)
+ reader = insert(:user)
+
+ {:ok, post} =
+ CommonAPI.post(user, %{status: "test @#{reader.nickname}", visibility: "local"})
+
+ object = Object.normalize(post, fetch: false)
+ uuid = String.split(object.data["id"], "/") |> List.last()
+
+ assert response =
+ conn
+ |> assign(:user, reader)
+ |> put_req_header("accept", "application/activity+json")
+ |> get("/objects/#{uuid}")
+
+ json_response(response, 200)
+
+ conn
+ |> put_req_header("accept", "application/activity+json")
+ |> get("/objects/#{uuid}")
+ |> json_response(404)
+ end
+
test "it returns 404 for non-public messages", %{conn: conn} do
note = insert(:direct_note)
uuid = String.split(note.data["id"], "/") |> List.last()
diff --git a/test/pleroma/web/activity_pub/mrf/anti_followbot_policy_test.exs b/test/pleroma/web/activity_pub/mrf/anti_followbot_policy_test.exs
index 8192629c6..732a5a7f3 100644
--- a/test/pleroma/web/activity_pub/mrf/anti_followbot_policy_test.exs
+++ b/test/pleroma/web/activity_pub/mrf/anti_followbot_policy_test.exs
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicyTest do
use Pleroma.DataCase, async: true
import Pleroma.Factory
+ alias Pleroma.User
alias Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy
describe "blocking based on attributes" do
@@ -38,21 +39,55 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicyTest do
assert {:reject, "[AntiFollowbotPolicy]" <> _} = AntiFollowbotPolicy.filter(message)
end
+
+ test "matches followbots by actor_type" do
+ actor = insert(:user, %{actor_type: "Service"})
+ target = insert(:user)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "type" => "Follow",
+ "actor" => actor.ap_id,
+ "object" => target.ap_id,
+ "id" => "https://example.com/activities/1234"
+ }
+
+ assert {:reject, "[AntiFollowbotPolicy]" <> _} = AntiFollowbotPolicy.filter(message)
+ end
end
- test "it allows non-followbots" do
- actor = insert(:user)
- target = insert(:user)
+ describe "it allows" do
+ test "non-followbots" do
+ actor = insert(:user)
+ target = insert(:user)
- message = %{
- "@context" => "https://www.w3.org/ns/activitystreams",
- "type" => "Follow",
- "actor" => actor.ap_id,
- "object" => target.ap_id,
- "id" => "https://example.com/activities/1234"
- }
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "type" => "Follow",
+ "actor" => actor.ap_id,
+ "object" => target.ap_id,
+ "id" => "https://example.com/activities/1234"
+ }
- {:ok, _} = AntiFollowbotPolicy.filter(message)
+ {:ok, _} = AntiFollowbotPolicy.filter(message)
+ end
+
+ test "bots if the target follows the bots" do
+ actor = insert(:user, %{actor_type: "Service"})
+ target = insert(:user)
+
+ User.follow(target, actor)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "type" => "Follow",
+ "actor" => actor.ap_id,
+ "object" => target.ap_id,
+ "id" => "https://example.com/activities/1234"
+ }
+
+ {:ok, _} = AntiFollowbotPolicy.filter(message)
+ end
end
test "it gracefully handles nil display names" do
diff --git a/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs b/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs
index c537670f2..89d32352f 100644
--- a/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs
+++ b/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs
@@ -60,7 +60,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicyTest do
|> File.exists?()
end
- test "reject shortcode", %{message: message} do
+ test "reject regex shortcode", %{message: message} do
refute "firedfox" in installed()
clear_config(:mrf_steal_emoji,
@@ -74,6 +74,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicyTest do
refute "firedfox" in installed()
end
+ test "reject string shortcode", %{message: message} do
+ refute "firedfox" in installed()
+
+ clear_config(:mrf_steal_emoji,
+ hosts: ["example.org"],
+ size_limit: 284_468,
+ rejected_shortcodes: ["firedfox"]
+ )
+
+ assert {:ok, _message} = StealEmojiPolicy.filter(message)
+
+ refute "firedfox" in installed()
+ end
+
test "reject if size is above the limit", %{message: message} do
refute "firedfox" in installed()
diff --git a/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs
index b07011b76..9a17e277e 100644
--- a/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs
+++ b/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs
@@ -27,6 +27,46 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidatorTest do
assert attachment.mediaType == "application/octet-stream"
end
+ test "works with an unknown but valid mime type" do
+ attachment = %{
+ "mediaType" => "x-custom/x-type",
+ "type" => "Document",
+ "url" => "https://example.org"
+ }
+
+ assert {:ok, attachment} =
+ AttachmentValidator.cast_and_validate(attachment)
+ |> Ecto.Changeset.apply_action(:insert)
+
+ assert attachment.mediaType == "x-custom/x-type"
+ end
+
+ test "works with invalid mime types" do
+ attachment = %{
+ "mediaType" => "x-customx-type",
+ "type" => "Document",
+ "url" => "https://example.org"
+ }
+
+ assert {:ok, attachment} =
+ AttachmentValidator.cast_and_validate(attachment)
+ |> Ecto.Changeset.apply_action(:insert)
+
+ assert attachment.mediaType == "application/octet-stream"
+
+ attachment = %{
+ "mediaType" => "https://example.org",
+ "type" => "Document",
+ "url" => "https://example.org"
+ }
+
+ assert {:ok, attachment} =
+ AttachmentValidator.cast_and_validate(attachment)
+ |> Ecto.Changeset.apply_action(:insert)
+
+ assert attachment.mediaType == "application/octet-stream"
+ end
+
test "it turns mastodon attachments into our attachments" do
attachment = %{
"url" =>
diff --git a/test/pleroma/web/admin_api/controllers/announcement_controller_test.exs b/test/pleroma/web/admin_api/controllers/announcement_controller_test.exs
new file mode 100644
index 000000000..5b8148c05
--- /dev/null
+++ b/test/pleroma/web/admin_api/controllers/announcement_controller_test.exs
@@ -0,0 +1,281 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.AnnouncementControllerTest do
+ use Pleroma.Web.ConnCase
+
+ import Pleroma.Factory
+
+ setup do
+ admin = insert(:user, is_admin: true)
+ token = insert(:oauth_admin_token, user: admin)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, token)
+
+ {:ok, %{admin: admin, token: token, conn: conn}}
+ end
+
+ describe "GET /api/v1/pleroma/admin/announcements" do
+ test "it lists all announcements", %{conn: conn} do
+ %{id: id} = insert(:announcement)
+
+ response =
+ conn
+ |> get("/api/v1/pleroma/admin/announcements")
+ |> json_response_and_validate_schema(:ok)
+
+ assert [%{"id" => ^id}] = response
+ end
+
+ test "it paginates announcements", %{conn: conn} do
+ _announcements = Enum.map(0..20, fn _ -> insert(:announcement) end)
+
+ response =
+ conn
+ |> get("/api/v1/pleroma/admin/announcements")
+ |> json_response_and_validate_schema(:ok)
+
+ assert length(response) == 20
+ end
+
+ test "it paginates announcements with custom params", %{conn: conn} do
+ announcements = Enum.map(0..20, fn _ -> insert(:announcement) end)
+
+ response =
+ conn
+ |> get("/api/v1/pleroma/admin/announcements", limit: 5, offset: 7)
+ |> json_response_and_validate_schema(:ok)
+
+ assert length(response) == 5
+ assert Enum.at(response, 0)["id"] == Enum.at(announcements, 7).id
+ end
+
+ test "it returns empty list with out-of-bounds offset", %{conn: conn} do
+ _announcements = Enum.map(0..20, fn _ -> insert(:announcement) end)
+
+ response =
+ conn
+ |> get("/api/v1/pleroma/admin/announcements", offset: 21)
+ |> json_response_and_validate_schema(:ok)
+
+ assert [] = response
+ end
+
+ test "it rejects invalid pagination params", %{conn: conn} do
+ conn
+ |> get("/api/v1/pleroma/admin/announcements", limit: 0)
+ |> json_response_and_validate_schema(400)
+
+ conn
+ |> get("/api/v1/pleroma/admin/announcements", limit: -1)
+ |> json_response_and_validate_schema(400)
+
+ conn
+ |> get("/api/v1/pleroma/admin/announcements", offset: -1)
+ |> json_response_and_validate_schema(400)
+ end
+ end
+
+ describe "GET /api/v1/pleroma/admin/announcements/:id" do
+ test "it displays one announcement", %{conn: conn} do
+ %{id: id} = insert(:announcement)
+
+ response =
+ conn
+ |> get("/api/v1/pleroma/admin/announcements/#{id}")
+ |> json_response_and_validate_schema(:ok)
+
+ assert %{"id" => ^id} = response
+ end
+
+ test "it returns not found for non-existent id", %{conn: conn} do
+ %{id: id} = insert(:announcement)
+
+ _response =
+ conn
+ |> get("/api/v1/pleroma/admin/announcements/#{id}xxx")
+ |> json_response_and_validate_schema(:not_found)
+ end
+ end
+
+ describe "DELETE /api/v1/pleroma/admin/announcements/:id" do
+ test "it deletes specified announcement", %{conn: conn} do
+ %{id: id} = insert(:announcement)
+
+ _response =
+ conn
+ |> delete("/api/v1/pleroma/admin/announcements/#{id}")
+ |> json_response_and_validate_schema(:ok)
+ end
+
+ test "it returns not found for non-existent id", %{conn: conn} do
+ %{id: id} = insert(:announcement)
+
+ _response =
+ conn
+ |> delete("/api/v1/pleroma/admin/announcements/#{id}xxx")
+ |> json_response_and_validate_schema(:not_found)
+
+ assert %{id: ^id} = Pleroma.Announcement.get_by_id(id)
+ end
+ end
+
+ describe "PATCH /api/v1/pleroma/admin/announcements/:id" do
+ test "it returns not found for non-existent id", %{conn: conn} do
+ %{id: id} = insert(:announcement)
+
+ _response =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> patch("/api/v1/pleroma/admin/announcements/#{id}xxx", %{})
+ |> json_response_and_validate_schema(:not_found)
+
+ assert %{id: ^id} = Pleroma.Announcement.get_by_id(id)
+ end
+
+ test "it updates a field", %{conn: conn} do
+ %{id: id} = insert(:announcement)
+
+ now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
+ starts_at = NaiveDateTime.add(now, -10, :second)
+
+ _response =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> patch("/api/v1/pleroma/admin/announcements/#{id}", %{
+ starts_at: NaiveDateTime.to_iso8601(starts_at)
+ })
+ |> json_response_and_validate_schema(:ok)
+
+ new = Pleroma.Announcement.get_by_id(id)
+
+ assert NaiveDateTime.compare(new.starts_at, starts_at) == :eq
+ end
+
+ test "it updates with time with utc timezone", %{conn: conn} do
+ %{id: id} = insert(:announcement)
+
+ now = DateTime.now("Etc/UTC") |> elem(1) |> DateTime.truncate(:second)
+ starts_at = DateTime.add(now, -10, :second)
+
+ _response =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> patch("/api/v1/pleroma/admin/announcements/#{id}", %{
+ starts_at: DateTime.to_iso8601(starts_at)
+ })
+ |> json_response_and_validate_schema(:ok)
+
+ new = Pleroma.Announcement.get_by_id(id)
+
+ assert DateTime.compare(new.starts_at, starts_at) == :eq
+ end
+
+ test "it updates a data field", %{conn: conn} do
+ %{id: id} = announcement = insert(:announcement, data: %{"all_day" => true})
+
+ assert announcement.data["all_day"] == true
+
+ new_content = "new content"
+
+ response =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> patch("/api/v1/pleroma/admin/announcements/#{id}", %{
+ content: new_content
+ })
+ |> json_response_and_validate_schema(:ok)
+
+ assert response["content"] == new_content
+ assert response["all_day"] == true
+
+ new = Pleroma.Announcement.get_by_id(id)
+
+ assert new.data["content"] == new_content
+ assert new.data["all_day"] == true
+ end
+
+ test "it nullifies a nullable field", %{conn: conn} do
+ now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
+ starts_at = NaiveDateTime.add(now, -10, :second)
+
+ %{id: id} = insert(:announcement, starts_at: starts_at)
+
+ response =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> patch("/api/v1/pleroma/admin/announcements/#{id}", %{
+ starts_at: nil
+ })
+ |> json_response_and_validate_schema(:ok)
+
+ assert response["starts_at"] == nil
+
+ new = Pleroma.Announcement.get_by_id(id)
+
+ assert new.starts_at == nil
+ end
+ end
+
+ describe "POST /api/v1/pleroma/admin/announcements" do
+ test "it creates an announcement", %{conn: conn} do
+ content = "test post announcement api"
+
+ now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
+ starts_at = NaiveDateTime.add(now, -10, :second)
+ ends_at = NaiveDateTime.add(now, 10, :second)
+
+ response =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/pleroma/admin/announcements", %{
+ "content" => content,
+ "starts_at" => NaiveDateTime.to_iso8601(starts_at),
+ "ends_at" => NaiveDateTime.to_iso8601(ends_at),
+ "all_day" => true
+ })
+ |> json_response_and_validate_schema(:ok)
+
+ assert %{"content" => ^content, "all_day" => true} = response
+
+ announcement = Pleroma.Announcement.get_by_id(response["id"])
+
+ assert not is_nil(announcement)
+
+ assert NaiveDateTime.compare(announcement.starts_at, starts_at) == :eq
+ assert NaiveDateTime.compare(announcement.ends_at, ends_at) == :eq
+ end
+
+ test "creating with time with utc timezones", %{conn: conn} do
+ content = "test post announcement api"
+
+ now = DateTime.now("Etc/UTC") |> elem(1) |> DateTime.truncate(:second)
+ starts_at = DateTime.add(now, -10, :second)
+ ends_at = DateTime.add(now, 10, :second)
+
+ response =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/pleroma/admin/announcements", %{
+ "content" => content,
+ "starts_at" => DateTime.to_iso8601(starts_at),
+ "ends_at" => DateTime.to_iso8601(ends_at),
+ "all_day" => true
+ })
+ |> json_response_and_validate_schema(:ok)
+
+ assert %{"content" => ^content, "all_day" => true} = response
+
+ announcement = Pleroma.Announcement.get_by_id(response["id"])
+
+ assert not is_nil(announcement)
+
+ assert DateTime.compare(announcement.starts_at, starts_at) == :eq
+ assert DateTime.compare(announcement.ends_at, ends_at) == :eq
+ end
+ end
+end
diff --git a/test/pleroma/web/admin_api/controllers/report_controller_test.exs b/test/pleroma/web/admin_api/controllers/report_controller_test.exs
index 6211956c6..30dcb87e2 100644
--- a/test/pleroma/web/admin_api/controllers/report_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/report_controller_test.exs
@@ -355,7 +355,6 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
} = note
end
- @tag :erratic
test "it returns reports with notes", %{conn: conn, admin: admin} do
conn = get(conn, "/api/pleroma/admin/reports")
@@ -364,7 +363,8 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
[note, _] = notes
assert note["user"]["nickname"] == admin.nickname
- assert note["content"] == "this is disgusting!"
+ # We use '=~' because the order of the notes isn't guaranteed
+ assert note["content"] =~ "this is disgusting"
assert note["created_at"]
assert response["total"] == 1
end
diff --git a/test/pleroma/web/gettext_test.exs b/test/pleroma/web/gettext_test.exs
new file mode 100644
index 000000000..e186f1ab3
--- /dev/null
+++ b/test/pleroma/web/gettext_test.exs
@@ -0,0 +1,173 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.GettextTest do
+ use ExUnit.Case
+
+ require Pleroma.Web.Gettext
+
+ test "put_locales/1: set the first in the list to Gettext's locale" do
+ Pleroma.Web.Gettext.put_locales(["zh_Hans", "en_test"])
+
+ assert "zh_Hans" == Gettext.get_locale(Pleroma.Web.Gettext)
+ end
+
+ test "with_locales/2: reset locale on exit" do
+ old_first_locale = Gettext.get_locale(Pleroma.Web.Gettext)
+ old_locales = Pleroma.Web.Gettext.get_locales()
+
+ Pleroma.Web.Gettext.with_locales ["zh_Hans", "en_test"] do
+ assert "zh_Hans" == Gettext.get_locale(Pleroma.Web.Gettext)
+ assert ["zh_Hans", "en_test"] == Pleroma.Web.Gettext.get_locales()
+ end
+
+ assert old_first_locale == Gettext.get_locale(Pleroma.Web.Gettext)
+ assert old_locales == Pleroma.Web.Gettext.get_locales()
+ end
+
+ describe "handle_missing_translation/5" do
+ test "fallback to next locale if some translation is not available" do
+ Pleroma.Web.Gettext.with_locales ["x_unsupported", "en_test"] do
+ assert "xxYour account is awaiting approvalxx" ==
+ Pleroma.Web.Gettext.dpgettext(
+ "static_pages",
+ "approval pending email subject",
+ "Your account is awaiting approval"
+ )
+ end
+ end
+
+ test "putting en locale at the front should not make gettext fallback unexpectedly" do
+ Pleroma.Web.Gettext.with_locales ["en", "en_test"] do
+ assert "Your account is awaiting approval" ==
+ Pleroma.Web.Gettext.dpgettext(
+ "static_pages",
+ "approval pending email subject",
+ "Your account is awaiting approval"
+ )
+ end
+ end
+
+ test "duplicated locale in list should not result in infinite loops" do
+ Pleroma.Web.Gettext.with_locales ["x_unsupported", "x_unsupported", "en_test"] do
+ assert "xxYour account is awaiting approvalxx" ==
+ Pleroma.Web.Gettext.dpgettext(
+ "static_pages",
+ "approval pending email subject",
+ "Your account is awaiting approval"
+ )
+ end
+ end
+
+ test "direct interpolation" do
+ Pleroma.Web.Gettext.with_locales ["en_test"] do
+ assert "xxYour digest from some instancexx" ==
+ Pleroma.Web.Gettext.dpgettext(
+ "static_pages",
+ "digest email subject",
+ "Your digest from %{instance_name}",
+ instance_name: "some instance"
+ )
+ end
+ end
+
+ test "fallback with interpolation" do
+ Pleroma.Web.Gettext.with_locales ["x_unsupported", "en_test"] do
+ assert "xxYour digest from some instancexx" ==
+ Pleroma.Web.Gettext.dpgettext(
+ "static_pages",
+ "digest email subject",
+ "Your digest from %{instance_name}",
+ instance_name: "some instance"
+ )
+ end
+ end
+
+ test "fallback to msgid" do
+ Pleroma.Web.Gettext.with_locales ["x_unsupported"] do
+ assert "Your digest from some instance" ==
+ Pleroma.Web.Gettext.dpgettext(
+ "static_pages",
+ "digest email subject",
+ "Your digest from %{instance_name}",
+ instance_name: "some instance"
+ )
+ end
+ end
+ end
+
+ describe "handle_missing_plural_translation/7" do
+ test "direct interpolation" do
+ Pleroma.Web.Gettext.with_locales ["en_test"] do
+ assert "xx1 New Followerxx" ==
+ Pleroma.Web.Gettext.dpngettext(
+ "static_pages",
+ "new followers count header",
+ "%{count} New Follower",
+ "%{count} New Followers",
+ 1,
+ count: 1
+ )
+
+ assert "xx5 New Followersxx" ==
+ Pleroma.Web.Gettext.dpngettext(
+ "static_pages",
+ "new followers count header",
+ "%{count} New Follower",
+ "%{count} New Followers",
+ 5,
+ count: 5
+ )
+ end
+ end
+
+ test "fallback with interpolation" do
+ Pleroma.Web.Gettext.with_locales ["x_unsupported", "en_test"] do
+ assert "xx1 New Followerxx" ==
+ Pleroma.Web.Gettext.dpngettext(
+ "static_pages",
+ "new followers count header",
+ "%{count} New Follower",
+ "%{count} New Followers",
+ 1,
+ count: 1
+ )
+
+ assert "xx5 New Followersxx" ==
+ Pleroma.Web.Gettext.dpngettext(
+ "static_pages",
+ "new followers count header",
+ "%{count} New Follower",
+ "%{count} New Followers",
+ 5,
+ count: 5
+ )
+ end
+ end
+
+ test "fallback to msgid" do
+ Pleroma.Web.Gettext.with_locales ["x_unsupported"] do
+ assert "1 New Follower" ==
+ Pleroma.Web.Gettext.dpngettext(
+ "static_pages",
+ "new followers count header",
+ "%{count} New Follower",
+ "%{count} New Followers",
+ 1,
+ count: 1
+ )
+
+ assert "5 New Followers" ==
+ Pleroma.Web.Gettext.dpngettext(
+ "static_pages",
+ "new followers count header",
+ "%{count} New Follower",
+ "%{count} New Followers",
+ 5,
+ count: 5
+ )
+ end
+ end
+ end
+end
diff --git a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs
index 853d2c111..effa2144f 100644
--- a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs
@@ -13,6 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
alias Pleroma.Web.ActivityPub.InternalFetchActor
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.OAuth.Token
+ alias Pleroma.Web.Plugs.SetLocalePlug
import Pleroma.Factory
@@ -1662,6 +1663,75 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
end
end
+ describe "create account with language" do
+ setup %{conn: conn} do
+ app_token = insert(:oauth_token, user: nil)
+
+ conn =
+ conn
+ |> put_req_header("authorization", "Bearer " <> app_token.token)
+ |> put_req_header("content-type", "multipart/form-data")
+ |> put_req_cookie(SetLocalePlug.frontend_language_cookie_name(), "zh-Hans")
+ |> SetLocalePlug.call([])
+
+ [conn: conn]
+ end
+
+ test "creates an account with language parameter", %{conn: conn} do
+ params = %{
+ username: "foo",
+ email: "foo@example.org",
+ password: "dupa.8",
+ agreement: true,
+ language: "ru"
+ }
+
+ res =
+ conn
+ |> post("/api/v1/accounts", params)
+
+ assert json_response_and_validate_schema(res, 200)
+
+ assert %{language: "ru"} = Pleroma.User.get_by_nickname("foo")
+ end
+
+ test "language parameter should be normalized", %{conn: conn} do
+ params = %{
+ username: "foo",
+ email: "foo@example.org",
+ password: "dupa.8",
+ agreement: true,
+ language: "ru-RU"
+ }
+
+ res =
+ conn
+ |> post("/api/v1/accounts", params)
+
+ assert json_response_and_validate_schema(res, 200)
+
+ assert %{language: "ru_RU"} = Pleroma.User.get_by_nickname("foo")
+ end
+
+ test "createing an account without language parameter should fallback to cookie/header language",
+ %{conn: conn} do
+ params = %{
+ username: "foo2",
+ email: "foo2@example.org",
+ password: "dupa.8",
+ agreement: true
+ }
+
+ res =
+ conn
+ |> post("/api/v1/accounts", params)
+
+ assert json_response_and_validate_schema(res, 200)
+
+ assert %{language: "zh_Hans"} = Pleroma.User.get_by_nickname("foo2")
+ end
+ end
+
describe "GET /api/v1/accounts/:id/lists - account_lists" do
test "returns lists to which the account belongs" do
%{user: user, conn: conn} = oauth_access(["read:lists"])
diff --git a/test/pleroma/web/mastodon_api/controllers/announcement_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/announcement_controller_test.exs
new file mode 100644
index 000000000..60c9978c2
--- /dev/null
+++ b/test/pleroma/web/mastodon_api/controllers/announcement_controller_test.exs
@@ -0,0 +1,169 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.AnnouncementControllerTest do
+ use Pleroma.Web.ConnCase
+
+ import Pleroma.Factory
+
+ alias Pleroma.Announcement
+ alias Pleroma.AnnouncementReadRelationship
+
+ describe "GET /api/v1/announcements" do
+ setup do
+ %{conn: conn} = oauth_access([])
+ {:ok, conn: conn}
+ end
+
+ test "it does not allow guests", %{conn: conn} do
+ _response =
+ conn
+ |> assign(:token, nil)
+ |> get("/api/v1/announcements")
+ |> json_response_and_validate_schema(:forbidden)
+ end
+
+ test "it allows users with scopes" do
+ %{conn: conn} = oauth_access(["read:accounts"])
+
+ _response =
+ conn
+ |> get("/api/v1/announcements")
+ |> json_response_and_validate_schema(:ok)
+ end
+
+ test "it lists all announcements", %{conn: conn} do
+ %{id: id} = insert(:announcement)
+
+ response =
+ conn
+ |> get("/api/v1/announcements")
+ |> json_response_and_validate_schema(:ok)
+
+ assert [%{"id" => ^id}] = response
+ end
+
+ test "it returns time with utc timezone", %{conn: conn} do
+ start_time =
+ NaiveDateTime.utc_now()
+ |> NaiveDateTime.add(-999_999, :second)
+ |> NaiveDateTime.truncate(:second)
+
+ end_time =
+ NaiveDateTime.utc_now()
+ |> NaiveDateTime.add(999_999, :second)
+ |> NaiveDateTime.truncate(:second)
+
+ %{id: id} = insert(:announcement, %{starts_at: start_time, ends_at: end_time})
+
+ response =
+ conn
+ |> get("/api/v1/announcements")
+ |> json_response_and_validate_schema(:ok)
+
+ assert [%{"id" => ^id}] = [announcement] = response
+
+ assert String.ends_with?(announcement["starts_at"], "Z")
+ assert String.ends_with?(announcement["ends_at"], "Z")
+ end
+
+ test "it does not list announcements starting after current time", %{conn: conn} do
+ time = NaiveDateTime.utc_now() |> NaiveDateTime.add(999_999, :second)
+ insert(:announcement, starts_at: time)
+
+ response =
+ conn
+ |> get("/api/v1/announcements")
+ |> json_response_and_validate_schema(:ok)
+
+ assert [] = response
+ end
+
+ test "it does not list announcements ending before current time", %{conn: conn} do
+ time = NaiveDateTime.utc_now() |> NaiveDateTime.add(-999_999, :second)
+ insert(:announcement, ends_at: time)
+
+ response =
+ conn
+ |> get("/api/v1/announcements")
+ |> json_response_and_validate_schema(:ok)
+
+ assert [] = response
+ end
+
+ test "when authenticated, also expose read property", %{conn: conn} do
+ %{id: id} = insert(:announcement)
+
+ response =
+ conn
+ |> get("/api/v1/announcements")
+ |> json_response_and_validate_schema(:ok)
+
+ assert [%{"id" => ^id, "read" => false}] = response
+ end
+
+ test "when authenticated and announcement is read by user" do
+ %{id: id} = announcement = insert(:announcement)
+ user = insert(:user)
+
+ AnnouncementReadRelationship.mark_read(user, announcement)
+
+ %{conn: conn} = oauth_access(["read:accounts"], user: user)
+
+ response =
+ conn
+ |> get("/api/v1/announcements")
+ |> json_response_and_validate_schema(:ok)
+
+ assert [%{"id" => ^id, "read" => true}] = response
+ end
+ end
+
+ describe "POST /api/v1/announcements/:id/dismiss" do
+ setup do: oauth_access(["write:accounts"])
+
+ test "it requires auth", %{conn: conn} do
+ %{id: id} = insert(:announcement)
+
+ _response =
+ conn
+ |> assign(:token, nil)
+ |> post("/api/v1/announcements/#{id}/dismiss")
+ |> json_response_and_validate_schema(:forbidden)
+ end
+
+ test "it requires write:accounts oauth scope" do
+ %{id: id} = insert(:announcement)
+
+ %{conn: conn} = oauth_access(["read:accounts"])
+
+ _response =
+ conn
+ |> post("/api/v1/announcements/#{id}/dismiss")
+ |> json_response_and_validate_schema(:forbidden)
+ end
+
+ test "it gives 404 for non-existent announcements", %{conn: conn} do
+ %{id: id} = insert(:announcement)
+
+ _response =
+ conn
+ |> post("/api/v1/announcements/#{id}xxx/dismiss")
+ |> json_response_and_validate_schema(:not_found)
+ end
+
+ test "it marks announcement as read", %{user: user, conn: conn} do
+ %{id: id} = announcement = insert(:announcement)
+
+ refute Announcement.read_by?(announcement, user)
+
+ _response =
+ conn
+ |> post("/api/v1/announcements/#{id}/dismiss")
+ |> json_response_and_validate_schema(:ok)
+
+ assert Announcement.read_by?(announcement, user)
+ end
+ end
+end
diff --git a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs
index 9845408d6..13e3ffc0a 100644
--- a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs
@@ -22,6 +22,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do
"uri" => _,
"title" => _,
"description" => _,
+ "short_description" => _,
"version" => _,
"email" => from_config_email,
"urls" => %{
diff --git a/test/pleroma/web/plugs/cache_test.exs b/test/pleroma/web/plugs/cache_test.exs
index d84e2d268..0c119528d 100644
--- a/test/pleroma/web/plugs/cache_test.exs
+++ b/test/pleroma/web/plugs/cache_test.exs
@@ -179,4 +179,22 @@ defmodule Pleroma.Web.Plugs.CacheTest do
|> send_resp(:im_a_teapot, "🥤")
|> sent_resp()
end
+
+ test "ignores if skip_cache is assigned" do
+ assert @miss_resp ==
+ conn(:get, "/")
+ |> assign(:skip_cache, true)
+ |> Cache.call(%{query_params: false, ttl: nil})
+ |> put_resp_content_type("cofe/hot")
+ |> send_resp(:ok, "cofe")
+ |> sent_resp()
+
+ assert @miss_resp ==
+ conn(:get, "/")
+ |> assign(:skip_cache, true)
+ |> Cache.call(%{query_params: false, ttl: nil})
+ |> put_resp_content_type("cofe/hot")
+ |> send_resp(:ok, "cofe")
+ |> sent_resp()
+ end
end
diff --git a/test/pleroma/web/plugs/set_locale_plug_test.exs b/test/pleroma/web/plugs/set_locale_plug_test.exs
index 4328b9d42..4f664f84e 100644
--- a/test/pleroma/web/plugs/set_locale_plug_test.exs
+++ b/test/pleroma/web/plugs/set_locale_plug_test.exs
@@ -16,7 +16,7 @@ defmodule Pleroma.Web.Plugs.SetLocalePlugTest do
|> SetLocalePlug.call([])
assert "en" == Gettext.get_locale()
- assert %{locale: "en"} == conn.assigns
+ assert %{locale: "en"} = conn.assigns
end
test "use supported locale from `accept-language`" do
@@ -30,7 +30,125 @@ defmodule Pleroma.Web.Plugs.SetLocalePlugTest do
|> SetLocalePlug.call([])
assert "ru" == Gettext.get_locale()
- assert %{locale: "ru"} == conn.assigns
+ assert %{locale: "ru"} = conn.assigns
+ end
+
+ test "fallback to the general language if a variant is not supported" do
+ conn =
+ :get
+ |> conn("/cofe")
+ |> Conn.put_req_header(
+ "accept-language",
+ "ru-CA;q=0.9, en;q=0.8, *;q=0.5"
+ )
+ |> SetLocalePlug.call([])
+
+ assert "ru" == Gettext.get_locale()
+ assert %{locale: "ru"} = conn.assigns
+ end
+
+ test "use supported locale with specifiers from `accept-language`" do
+ conn =
+ :get
+ |> conn("/cofe")
+ |> Conn.put_req_header(
+ "accept-language",
+ "zh-Hans;q=0.9, en;q=0.8, *;q=0.5"
+ )
+ |> SetLocalePlug.call([])
+
+ assert "zh_Hans" == Gettext.get_locale()
+ assert %{locale: "zh_Hans"} = conn.assigns
+ end
+
+ test "it assigns all supported locales" do
+ conn =
+ :get
+ |> conn("/cofe")
+ |> Conn.put_req_header(
+ "accept-language",
+ "ru, fr-CH, fr;q=0.9, en;q=0.8, x-unsupported;q=0.8, *;q=0.5"
+ )
+ |> SetLocalePlug.call([])
+
+ assert "ru" == Gettext.get_locale()
+ assert %{locale: "ru", locales: ["ru", "fr", "en"]} = conn.assigns
+ end
+
+ test "it assigns all supported locales in cookie" do
+ conn =
+ :get
+ |> conn("/cofe")
+ |> put_req_cookie(SetLocalePlug.frontend_language_cookie_name(), "zh-Hans,uk,zh-Hant")
+ |> Conn.put_req_header(
+ "accept-language",
+ "ru, fr-CH, fr;q=0.9, en;q=0.8, x-unsupported;q=0.8, *;q=0.5"
+ )
+ |> SetLocalePlug.call([])
+
+ assert "zh_Hans" == Gettext.get_locale()
+
+ assert %{locale: "zh_Hans", locales: ["zh_Hans", "uk", "zh_Hant", "ru", "fr", "en"]} =
+ conn.assigns
+ end
+
+ test "fallback to some variant of the language if the unqualified language is not supported" do
+ conn =
+ :get
+ |> conn("/cofe")
+ |> Conn.put_req_header(
+ "accept-language",
+ "zh;q=0.9, en;q=0.8, *;q=0.5"
+ )
+ |> SetLocalePlug.call([])
+
+ assert "zh_" <> _ = Gettext.get_locale()
+ assert %{locale: "zh_" <> _} = conn.assigns
+ end
+
+ test "use supported locale from cookie" do
+ conn =
+ :get
+ |> conn("/cofe")
+ |> put_req_cookie(SetLocalePlug.frontend_language_cookie_name(), "zh-Hans")
+ |> Conn.put_req_header(
+ "accept-language",
+ "ru, fr-CH, fr;q=0.9, en;q=0.8, *;q=0.5"
+ )
+ |> SetLocalePlug.call([])
+
+ assert "zh_Hans" == Gettext.get_locale()
+ assert %{locale: "zh_Hans"} = conn.assigns
+ end
+
+ test "fallback to supported locale from `accept-language` if locale in cookie not supported" do
+ conn =
+ :get
+ |> conn("/cofe")
+ |> put_req_cookie(SetLocalePlug.frontend_language_cookie_name(), "x-nonexist")
+ |> Conn.put_req_header(
+ "accept-language",
+ "ru, fr-CH, fr;q=0.9, en;q=0.8, *;q=0.5"
+ )
+ |> SetLocalePlug.call([])
+
+ assert "ru" == Gettext.get_locale()
+ assert %{locale: "ru"} = conn.assigns
+ end
+
+ test "fallback to default if nothing is supported" do
+ conn =
+ :get
+ |> conn("/cofe")
+ |> put_req_cookie(SetLocalePlug.frontend_language_cookie_name(), "x-nonexist")
+ |> Conn.put_req_header(
+ "accept-language",
+ "x-nonexist"
+ )
+ |> SetLocalePlug.call([])
+
+ assert "en" == Gettext.get_locale()
+ assert %{locale: "en"} = conn.assigns
end
test "use default locale if locale from `accept-language` is not supported" do
@@ -41,6 +159,6 @@ defmodule Pleroma.Web.Plugs.SetLocalePlugTest do
|> SetLocalePlug.call([])
assert "en" == Gettext.get_locale()
- assert %{locale: "en"} == conn.assigns
+ assert %{locale: "en"} = conn.assigns
end
end
diff --git a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs
index a493dc9f6..2b57a42a4 100644
--- a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs
+++ b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs
@@ -410,4 +410,49 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do
assert response =~ "Error following account"
end
end
+
+ describe "avatar url" do
+ test "without media proxy" do
+ clear_config([:media_proxy, :enabled], false)
+
+ user =
+ insert(:user, %{
+ local: false,
+ avatar: %{"url" => [%{"href" => "https://remote.org/avatar.png"}]}
+ })
+
+ avatar_url = Pleroma.Web.TwitterAPI.RemoteFollowView.avatar_url(user)
+
+ assert avatar_url == "https://remote.org/avatar.png"
+ end
+
+ test "with media proxy" do
+ clear_config([:media_proxy, :enabled], true)
+
+ user =
+ insert(:user, %{
+ local: false,
+ avatar: %{"url" => [%{"href" => "https://remote.org/avatar.png"}]}
+ })
+
+ avatar_url = Pleroma.Web.TwitterAPI.RemoteFollowView.avatar_url(user)
+ url = Pleroma.Web.Endpoint.url()
+
+ assert String.starts_with?(avatar_url, url)
+ end
+
+ test "local avatar is not proxied" do
+ clear_config([:media_proxy, :enabled], true)
+
+ user =
+ insert(:user, %{
+ local: true,
+ avatar: %{"url" => [%{"href" => "#{Pleroma.Web.Endpoint.url()}/localuser/avatar.png"}]}
+ })
+
+ avatar_url = Pleroma.Web.TwitterAPI.RemoteFollowView.avatar_url(user)
+
+ assert avatar_url == "#{Pleroma.Web.Endpoint.url()}/localuser/avatar.png"
+ end
+ end
end
diff --git a/test/support/factory.ex b/test/support/factory.ex
index 09456debf..efbf3df2e 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -627,4 +627,16 @@ defmodule Pleroma.Factory do
context: ["home"]
}
end
+
+ def announcement_factory(params \\ %{}) do
+ data = Map.get(params, :data, %{})
+
+ {_, params} = Map.pop(params, :data)
+
+ %Pleroma.Announcement{
+ data: Map.merge(%{"content" => "test announcement", "all_day" => false}, data)
+ }
+ |> Map.merge(params)
+ |> Pleroma.Announcement.add_rendered_properties()
+ end
end