forked from AkkomaGang/akkoma
Merge pull request 'Merge search behaviour change, and refactor elasticsearch' (#28) from elasticsearch-refactor into develop
Reviewed-on: AkkomaGang/akkoma#28
This commit is contained in:
commit
40bec73db6
110 changed files with 4954 additions and 1342 deletions
|
@ -54,6 +54,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Readded mastoFE
|
- Readded mastoFE
|
||||||
- Added support for custom emoji reactions
|
- Added support for custom emoji reactions
|
||||||
- Added `emoji_url` in notifications to allow for custom emoji rendering
|
- Added `emoji_url` in notifications to allow for custom emoji rendering
|
||||||
|
- 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.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Subscription(Bell) Notifications: Don't create from Pipeline Ingested replies
|
- Subscription(Bell) Notifications: Don't create from Pipeline Ingested replies
|
||||||
|
@ -116,6 +117,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.
|
- 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.
|
- 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
|
- 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
|
### Added
|
||||||
|
|
||||||
|
|
|
@ -568,7 +568,8 @@
|
||||||
remote_fetcher: 2,
|
remote_fetcher: 2,
|
||||||
attachments_cleanup: 1,
|
attachments_cleanup: 1,
|
||||||
new_users_digest: 1,
|
new_users_digest: 1,
|
||||||
mute_expire: 5
|
mute_expire: 5,
|
||||||
|
search_indexing: 10
|
||||||
],
|
],
|
||||||
plugins: [Oban.Plugins.Pruner],
|
plugins: [Oban.Plugins.Pruner],
|
||||||
crontab: [
|
crontab: [
|
||||||
|
@ -579,7 +580,8 @@
|
||||||
config :pleroma, :workers,
|
config :pleroma, :workers,
|
||||||
retries: [
|
retries: [
|
||||||
federator_incoming: 5,
|
federator_incoming: 5,
|
||||||
federator_outgoing: 5
|
federator_outgoing: 5,
|
||||||
|
search_indexing: 2
|
||||||
]
|
]
|
||||||
|
|
||||||
config :pleroma, Pleroma.Formatter,
|
config :pleroma, Pleroma.Formatter,
|
||||||
|
@ -850,17 +852,32 @@
|
||||||
|
|
||||||
config :pleroma, ConcurrentLimiter, [
|
config :pleroma, ConcurrentLimiter, [
|
||||||
{Pleroma.Web.RichMedia.Helpers, [max_running: 5, max_waiting: 5]},
|
{Pleroma.Web.RichMedia.Helpers, [max_running: 5, max_waiting: 5]},
|
||||||
{Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy, [max_running: 5, max_waiting: 5]}
|
{Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy, [max_running: 5, max_waiting: 5]},
|
||||||
|
{Pleroma.Search, [max_running: 30, max_waiting: 50]}
|
||||||
]
|
]
|
||||||
|
|
||||||
config :pleroma, :search, provider: Pleroma.Search.Builtin
|
config :pleroma, Pleroma.Search, module: Pleroma.Search.DatabaseSearch
|
||||||
|
|
||||||
config :pleroma, :telemetry,
|
config :pleroma, Pleroma.Search.Meilisearch,
|
||||||
slow_queries_logging: [
|
url: "http://127.0.0.1:7700/",
|
||||||
enabled: false,
|
private_key: nil,
|
||||||
min_duration: 500_000,
|
initial_indexing_chunk_size: 100_000
|
||||||
exclude_sources: [nil, "oban_jobs"]
|
|
||||||
]
|
config :pleroma, Pleroma.Search.Elasticsearch.Cluster,
|
||||||
|
url: "http://localhost:9200",
|
||||||
|
username: "elastic",
|
||||||
|
password: "changeme",
|
||||||
|
api: Elasticsearch.API.HTTP,
|
||||||
|
json_library: Jason,
|
||||||
|
indexes: %{
|
||||||
|
activities: %{
|
||||||
|
settings: "priv/es-mappings/activity.json",
|
||||||
|
store: Pleroma.Search.Elasticsearch.Store,
|
||||||
|
sources: [Pleroma.Activity],
|
||||||
|
bulk_page_size: 5000,
|
||||||
|
bulk_wait_interval: 15_000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Import environment specific config. This must remain at the bottom
|
# Import environment specific config. This must remain at the bottom
|
||||||
# of this file so it overrides the configuration defined above.
|
# of this file so it overrides the configuration defined above.
|
||||||
|
|
|
@ -3429,5 +3429,133 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
group: :pleroma,
|
||||||
|
key: Pleroma.Search,
|
||||||
|
type: :group,
|
||||||
|
description: "General search settings.",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :module,
|
||||||
|
type: :keyword,
|
||||||
|
description: "Selected search module.",
|
||||||
|
suggestion: [Pleroma.Search.DatabaseSearch, Pleroma.Search.Meilisearch]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
group: :pleroma,
|
||||||
|
key: Pleroma.Search.Meilisearch,
|
||||||
|
type: :group,
|
||||||
|
description: "Meilisearch settings.",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :url,
|
||||||
|
type: :string,
|
||||||
|
description: "Meilisearch URL.",
|
||||||
|
suggestion: ["http://127.0.0.1:7700/"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :private_key,
|
||||||
|
type: :string,
|
||||||
|
description:
|
||||||
|
"Private key for meilisearch authentication, or `nil` to disable private key authentication.",
|
||||||
|
suggestion: [nil]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :initial_indexing_chunk_size,
|
||||||
|
type: :int,
|
||||||
|
description:
|
||||||
|
"Amount of posts in a batch when running the initial indexing operation. Should probably not be more than 100000" <>
|
||||||
|
" since there's a limit on maximum insert size",
|
||||||
|
suggestion: [100_000]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
group: :pleroma,
|
||||||
|
key: Pleroma.Search.Elasticsearch.Cluster,
|
||||||
|
type: :group,
|
||||||
|
description: "Elasticsearch settings.",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :url,
|
||||||
|
type: :string,
|
||||||
|
description: "Elasticsearch URL.",
|
||||||
|
suggestion: ["http://127.0.0.1:9200/"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :username,
|
||||||
|
type: :string,
|
||||||
|
description: "Username to connect to ES. Set to nil if your cluster is unauthenticated.",
|
||||||
|
suggestion: ["elastic"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :password,
|
||||||
|
type: :string,
|
||||||
|
description: "Password to connect to ES. Set to nil if your cluster is unauthenticated.",
|
||||||
|
suggestion: ["changeme"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :api,
|
||||||
|
type: :module,
|
||||||
|
description:
|
||||||
|
"The API module used by Elasticsearch. Should always be Elasticsearch.API.HTTP",
|
||||||
|
suggestion: [Elasticsearch.API.HTTP]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :json_library,
|
||||||
|
type: :module,
|
||||||
|
description:
|
||||||
|
"The JSON module used to encode/decode when communicating with Elasticsearch",
|
||||||
|
suggestion: [Jason]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :indexes,
|
||||||
|
type: :map,
|
||||||
|
description: "The indices to set up in Elasticsearch",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :activities,
|
||||||
|
type: :map,
|
||||||
|
description: "Config for the index to use for activities",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :settings,
|
||||||
|
type: :string,
|
||||||
|
description:
|
||||||
|
"Path to the file containing index settings for the activities index. Should contain a mapping.",
|
||||||
|
suggestion: ["priv/es-mappings/activity.json"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :store,
|
||||||
|
type: :module,
|
||||||
|
description: "The internal store module",
|
||||||
|
suggestion: [Pleroma.Search.Elasticsearch.Store]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :sources,
|
||||||
|
type: {:list, :module},
|
||||||
|
description: "The internal types to use for this index",
|
||||||
|
suggestion: [[Pleroma.Activity]]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :bulk_page_size,
|
||||||
|
type: :int,
|
||||||
|
description: "Size for bulk put requests, mostly used on building the index",
|
||||||
|
suggestion: [5000]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :bulk_wait_interval,
|
||||||
|
type: :int,
|
||||||
|
description: "Time to wait between bulk put requests (in ms)",
|
||||||
|
suggestion: [15_000]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -134,6 +134,10 @@
|
||||||
ap_streamer: Pleroma.Web.ActivityPub.ActivityPubMock,
|
ap_streamer: Pleroma.Web.ActivityPub.ActivityPubMock,
|
||||||
logger: Pleroma.LoggerMock
|
logger: Pleroma.LoggerMock
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Search, module: Pleroma.Search.DatabaseSearch
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Search.Meilisearch, url: "http://127.0.0.1:7700/", private_key: nil
|
||||||
|
|
||||||
# Reduce recompilation time
|
# Reduce recompilation time
|
||||||
# https://dashbit.co/blog/speeding-up-re-compilation-of-elixir-projects
|
# https://dashbit.co/blog/speeding-up-re-compilation-of-elixir-projects
|
||||||
config :phoenix, :plug_init_mode, :runtime
|
config :phoenix, :plug_init_mode, :runtime
|
||||||
|
|
|
@ -17,11 +17,11 @@ su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate"
|
||||||
## For from source installations (using git)
|
## For from source installations (using git)
|
||||||
|
|
||||||
1. Go to the working directory of Pleroma (default is `/opt/pleroma`)
|
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.
|
3. Run `mix deps.get` [^1]. This pulls in any new dependencies.
|
||||||
4. Stop the Pleroma service.
|
4. Stop the Pleroma service.
|
||||||
5. Run `mix ecto.migrate` [^1] [^2]. This task performs database migrations, if there were any.
|
5. Run `mix ecto.migrate` [^1] [^2]. This task performs database migrations, if there were any.
|
||||||
6. Start the Pleroma service.
|
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.
|
[^2]: Prefix with `MIX_ENV=prod` to run it using the production config file.
|
||||||
|
|
|
@ -125,6 +125,8 @@ 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.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.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.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)).
|
||||||
* `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
|
* `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
|
||||||
* `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
|
* `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
|
||||||
|
|
||||||
|
|
163
docs/configuration/search.md
Normal file
163
docs/configuration/search.md
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
# Configuring search
|
||||||
|
|
||||||
|
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
||||||
|
|
||||||
|
## Built-in search
|
||||||
|
|
||||||
|
To use built-in search that has no external dependencies, set the search module to `Pleroma.Activity`:
|
||||||
|
|
||||||
|
> config :pleroma, Pleroma.Search, module: Pleroma.Search.DatabaseSearch
|
||||||
|
|
||||||
|
While it has no external dependencies, it has problems with performance and relevancy.
|
||||||
|
|
||||||
|
## Meilisearch
|
||||||
|
|
||||||
|
Note that it's quite a bit more memory hungry than PostgreSQL (around 4-5G for ~1.2 million
|
||||||
|
posts while idle and up to 7G while indexing initially). The disk usage for this additional index is also
|
||||||
|
around 4 gigabytes. Like [RUM](./cheatsheet.md#rum-indexing-for-full-text-search) indexes, it offers considerably
|
||||||
|
higher performance and ordering by timestamp in a reasonable amount of time.
|
||||||
|
Additionally, the search results seem to be more accurate.
|
||||||
|
|
||||||
|
Due to high memory usage, it may be best to set it up on a different machine, if running pleroma on a low-resource
|
||||||
|
computer, and use private key authentication to secure the remote search instance.
|
||||||
|
|
||||||
|
To use [meilisearch](https://www.meilisearch.com/), set the search module to `Pleroma.Search.Meilisearch`:
|
||||||
|
|
||||||
|
> config :pleroma, Pleroma.Search, module: Pleroma.Search.Meilisearch
|
||||||
|
|
||||||
|
You then need to set the address of the meilisearch instance, and optionally the private key for authentication. You might
|
||||||
|
also want to change the `initial_indexing_chunk_size` to be smaller if you're server is not very powerful, but not higher than `100_000`,
|
||||||
|
because meilisearch will refuse to process it if it's too big. However, in general you want this to be as big as possible, because meilisearch
|
||||||
|
indexes faster when it can process many posts in a single batch.
|
||||||
|
|
||||||
|
> config :pleroma, Pleroma.Search.Meilisearch,
|
||||||
|
> url: "http://127.0.0.1:7700/",
|
||||||
|
> private_key: "private key",
|
||||||
|
> initial_indexing_chunk_size: 100_000
|
||||||
|
|
||||||
|
Information about setting up meilisearch can be found in the
|
||||||
|
[official documentation](https://docs.meilisearch.com/learn/getting_started/installation.html).
|
||||||
|
You probably want to start it with `MEILI_NO_ANALYTICS=true` environment variable to disable analytics.
|
||||||
|
At least version 0.25.0 is required, but you are strongly adviced to use at least 0.26.0, as it introduces
|
||||||
|
the `--enable-auto-batching` option which drastically improves performance. Without this option, the search
|
||||||
|
is hardly usable on a somewhat big instance.
|
||||||
|
|
||||||
|
### Private key authentication (optional)
|
||||||
|
|
||||||
|
To set the private key, use the `MEILI_MASTER_KEY` environment variable when starting. After setting the _master key_,
|
||||||
|
you have to get the _private key_, which is actually used for authentication.
|
||||||
|
|
||||||
|
=== "OTP"
|
||||||
|
```sh
|
||||||
|
./bin/pleroma_ctl search.meilisearch show-keys <your master key here>
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "From Source"
|
||||||
|
```sh
|
||||||
|
mix pleroma.search.meilisearch show-keys <your master key here>
|
||||||
|
```
|
||||||
|
|
||||||
|
You will see a "Default Admin API Key", this is the key you actually put into your configuration file.
|
||||||
|
|
||||||
|
### Initial indexing
|
||||||
|
|
||||||
|
After setting up the configuration, you'll want to index all of your already existsing posts. Only public posts are indexed. You'll only
|
||||||
|
have to do it one time, but it might take a while, depending on the amount of posts your instance has seen. This is also a fairly RAM
|
||||||
|
consuming process for `meilisearch`, and it will take a lot of RAM when running if you have a lot of posts (seems to be around 5G for ~1.2
|
||||||
|
million posts while idle and up to 7G while indexing initially, but your experience may be different).
|
||||||
|
|
||||||
|
The sequence of actions is as follows:
|
||||||
|
|
||||||
|
1. First, change the configuration to use `Pleroma.Search.Meilisearch` as the search backend
|
||||||
|
2. Restart your instance, at this point it can be used while the search indexing is running, though search won't return anything
|
||||||
|
3. Start the initial indexing process (as described below with `index`),
|
||||||
|
and wait until the task says it sent everything from the database to index
|
||||||
|
4. Wait until everything is actually indexed (by checking with `stats` as described below),
|
||||||
|
at this point you don't have to do anything, just wait a while.
|
||||||
|
|
||||||
|
To start the initial indexing, run the `index` command:
|
||||||
|
|
||||||
|
=== "OTP"
|
||||||
|
```sh
|
||||||
|
./bin/pleroma_ctl search.meilisearch index
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "From Source"
|
||||||
|
```sh
|
||||||
|
mix pleroma.search.meilisearch index
|
||||||
|
```
|
||||||
|
|
||||||
|
This will show you the total amount of posts to index, and then show you the amount of posts indexed currently, until the numbers eventually
|
||||||
|
become the same. The posts are indexed in big batches and meilisearch will take some time to actually index them, even after you have
|
||||||
|
inserted all the posts into it. Depending on the amount of posts, this may be as long as several hours. To get information about the status
|
||||||
|
of indexing and how many posts have actually been indexed, use the `stats` command:
|
||||||
|
|
||||||
|
=== "OTP"
|
||||||
|
```sh
|
||||||
|
./bin/pleroma_ctl search.meilisearch stats
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "From Source"
|
||||||
|
```sh
|
||||||
|
mix pleroma.search.meilisearch stats
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clearing the index
|
||||||
|
|
||||||
|
In case you need to clear the index (for example, to re-index from scratch, if that needs to happen for some reason), you can
|
||||||
|
use the `clear` command:
|
||||||
|
|
||||||
|
=== "OTP"
|
||||||
|
```sh
|
||||||
|
./bin/pleroma_ctl search.meilisearch clear
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "From Source"
|
||||||
|
```sh
|
||||||
|
mix pleroma.search.meilisearch clear
|
||||||
|
```
|
||||||
|
|
||||||
|
This will clear **all** the posts from the search index. Note, that deleted posts are also removed from index by the instance itself, so
|
||||||
|
there is no need to actually clear the whole index, unless you want **all** of it gone. That said, the index does not hold any information
|
||||||
|
that cannot be re-created from the database, it should also generally be a lot smaller than the size of your database. Still, the size
|
||||||
|
depends on the amount of text in posts.
|
||||||
|
|
||||||
|
## Elasticsearch
|
||||||
|
|
||||||
|
As with meilisearch, this can be rather memory-hungry, but it is very good at what it does.
|
||||||
|
|
||||||
|
To use [elasticsearch](https://www.elastic.co/), set the search module to `Pleroma.Search.Elasticsearch`:
|
||||||
|
|
||||||
|
> config :pleroma, Pleroma.Search, module: Pleroma.Search.Elasticsearch
|
||||||
|
|
||||||
|
You then need to set the URL and authentication credentials if relevant.
|
||||||
|
|
||||||
|
> config :pleroma, Pleroma.Search.Elasticsearch.Cluster,
|
||||||
|
> url: "http://127.0.0.1:9200/",
|
||||||
|
> username: "elastic",
|
||||||
|
> password: "changeme",
|
||||||
|
|
||||||
|
### Initial indexing
|
||||||
|
|
||||||
|
After setting up the configuration, you'll want to index all of your already existsing posts. Only public posts are indexed. You'll only
|
||||||
|
have to do it one time, but it might take a while, depending on the amount of posts your instance has seen.
|
||||||
|
|
||||||
|
The sequence of actions is as follows:
|
||||||
|
|
||||||
|
1. First, change the configuration to use `Pleroma.Search.Elasticsearch` as the search backend
|
||||||
|
2. Restart your instance, at this point it can be used while the search indexing is running, though search won't return anything
|
||||||
|
3. Start the initial indexing process (as described below with `index`),
|
||||||
|
and wait until the task says it sent everything from the database to index
|
||||||
|
4. Wait until the index tasks exits
|
||||||
|
|
||||||
|
To start the initial indexing, run the `build` command:
|
||||||
|
|
||||||
|
=== "OTP"
|
||||||
|
```sh
|
||||||
|
./bin/pleroma_ctl search.elasticsearch index activities --cluster Pleroma.Search.Elasticsearch.Cluster
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "From Source"
|
||||||
|
```sh
|
||||||
|
mix elasticsearch.build activities --cluster Pleroma.Search.Elasticsearch.Cluster
|
||||||
|
```
|
|
@ -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).
|
- `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.
|
- `actor_type` - the type of this account.
|
||||||
- `accepts_chat_messages` - if false, this account will reject all chat messages.
|
- `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.
|
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_token`: optional, contains provider-specific captcha token
|
||||||
- `captcha_answer_data`: optional, contains provider-specific captcha data
|
- `captcha_answer_data`: optional, contains provider-specific captcha data
|
||||||
- `token`: invite token required when the registrations aren't public.
|
- `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
|
## Instance
|
||||||
|
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Mix.Tasks.Pleroma.Search do
|
|
||||||
use Mix.Task
|
|
||||||
import Mix.Pleroma
|
|
||||||
import Ecto.Query
|
|
||||||
alias Pleroma.Activity
|
|
||||||
alias Pleroma.Pagination
|
|
||||||
alias Pleroma.User
|
|
||||||
alias Pleroma.Hashtag
|
|
||||||
|
|
||||||
@shortdoc "Manages elasticsearch"
|
|
||||||
|
|
||||||
def run(["import", "activities" | _rest]) do
|
|
||||||
start_pleroma()
|
|
||||||
|
|
||||||
from(a in Activity, where: not ilike(a.actor, "%/relay"))
|
|
||||||
|> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data))
|
|
||||||
|> Activity.with_preloaded_object()
|
|
||||||
|> Activity.with_preloaded_user_actor()
|
|
||||||
|> get_all(:activities)
|
|
||||||
end
|
|
||||||
|
|
||||||
def run(["import", "users" | _rest]) do
|
|
||||||
start_pleroma()
|
|
||||||
|
|
||||||
from(u in User, where: u.nickname not in ["internal.fetch", "relay"])
|
|
||||||
|> get_all(:users)
|
|
||||||
end
|
|
||||||
|
|
||||||
def run(["import", "hashtags" | _rest]) do
|
|
||||||
start_pleroma()
|
|
||||||
|
|
||||||
from(h in Hashtag)
|
|
||||||
|> Pleroma.Repo.all()
|
|
||||||
|> Pleroma.Elasticsearch.bulk_post(:hashtags)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp get_all(query, index, max_id \\ nil) do
|
|
||||||
params = %{limit: 1000}
|
|
||||||
|
|
||||||
params =
|
|
||||||
if max_id == nil do
|
|
||||||
params
|
|
||||||
else
|
|
||||||
Map.put(params, :max_id, max_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
res =
|
|
||||||
query
|
|
||||||
|> Pagination.fetch_paginated(params)
|
|
||||||
|
|
||||||
if res == [] do
|
|
||||||
:ok
|
|
||||||
else
|
|
||||||
res
|
|
||||||
|> Pleroma.Elasticsearch.bulk_post(index)
|
|
||||||
|
|
||||||
get_all(query, index, List.last(res).id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
9
lib/mix/tasks/pleroma/search/elasticsearch.ex
Normal file
9
lib/mix/tasks/pleroma/search/elasticsearch.ex
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
defmodule Mix.Tasks.Pleroma.Search.Elasticsearch do
|
||||||
|
alias Mix.Tasks.Elasticsearch.Build
|
||||||
|
import Mix.Pleroma
|
||||||
|
|
||||||
|
def run(["index" | args]) do
|
||||||
|
start_pleroma()
|
||||||
|
Build.run(args)
|
||||||
|
end
|
||||||
|
end
|
144
lib/mix/tasks/pleroma/search/meilisearch.ex
Normal file
144
lib/mix/tasks/pleroma/search/meilisearch.ex
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Mix.Tasks.Pleroma.Search.Meilisearch do
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
import Mix.Pleroma
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
import Pleroma.Search.Meilisearch,
|
||||||
|
only: [meili_post: 2, meili_put: 2, meili_get: 1, meili_delete!: 1]
|
||||||
|
|
||||||
|
def run(["index"]) do
|
||||||
|
start_pleroma()
|
||||||
|
|
||||||
|
meili_version =
|
||||||
|
(
|
||||||
|
{:ok, result} = meili_get("/version")
|
||||||
|
|
||||||
|
result["pkgVersion"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# The ranking rule syntax was changed but nothing about that is mentioned in the changelog
|
||||||
|
if not Version.match?(meili_version, ">= 0.25.0") do
|
||||||
|
raise "Meilisearch <0.24.0 not supported"
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, _} =
|
||||||
|
meili_post(
|
||||||
|
"/indexes/objects/settings/ranking-rules",
|
||||||
|
[
|
||||||
|
"published:desc",
|
||||||
|
"words",
|
||||||
|
"exactness",
|
||||||
|
"proximity",
|
||||||
|
"typo",
|
||||||
|
"attribute",
|
||||||
|
"sort"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
{:ok, _} =
|
||||||
|
meili_post(
|
||||||
|
"/indexes/objects/settings/searchable-attributes",
|
||||||
|
[
|
||||||
|
"content"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
IO.puts("Created indices. Starting to insert posts.")
|
||||||
|
|
||||||
|
chunk_size = Pleroma.Config.get([Pleroma.Search.Meilisearch, :initial_indexing_chunk_size])
|
||||||
|
|
||||||
|
Pleroma.Repo.transaction(
|
||||||
|
fn ->
|
||||||
|
query =
|
||||||
|
from(Pleroma.Object,
|
||||||
|
# Only index public and unlisted posts which are notes and have some text
|
||||||
|
where:
|
||||||
|
fragment("data->>'type' = 'Note'") and
|
||||||
|
(fragment("data->'to' \\? ?", ^Pleroma.Constants.as_public()) or
|
||||||
|
fragment("data->'cc' \\? ?", ^Pleroma.Constants.as_public())),
|
||||||
|
order_by: [desc: fragment("data->'published'")]
|
||||||
|
)
|
||||||
|
|
||||||
|
count = query |> Pleroma.Repo.aggregate(:count, :data)
|
||||||
|
IO.puts("Entries to index: #{count}")
|
||||||
|
|
||||||
|
Pleroma.Repo.stream(
|
||||||
|
query,
|
||||||
|
timeout: :infinity
|
||||||
|
)
|
||||||
|
|> Stream.map(&Pleroma.Search.Meilisearch.object_to_search_data/1)
|
||||||
|
|> Stream.filter(fn o -> not is_nil(o) end)
|
||||||
|
|> Stream.chunk_every(chunk_size)
|
||||||
|
|> Stream.transform(0, fn objects, acc ->
|
||||||
|
new_acc = acc + Enum.count(objects)
|
||||||
|
|
||||||
|
# Reset to the beginning of the line and rewrite it
|
||||||
|
IO.write("\r")
|
||||||
|
IO.write("Indexed #{new_acc} entries")
|
||||||
|
|
||||||
|
{[objects], new_acc}
|
||||||
|
end)
|
||||||
|
|> Stream.each(fn objects ->
|
||||||
|
result =
|
||||||
|
meili_put(
|
||||||
|
"/indexes/objects/documents",
|
||||||
|
objects
|
||||||
|
)
|
||||||
|
|
||||||
|
with {:ok, res} <- result do
|
||||||
|
if not Map.has_key?(res, "uid") do
|
||||||
|
IO.puts("\nFailed to index: #{inspect(result)}")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
e -> IO.puts("\nFailed to index due to network error: #{inspect(e)}")
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> Stream.run()
|
||||||
|
end,
|
||||||
|
timeout: :infinity
|
||||||
|
)
|
||||||
|
|
||||||
|
IO.write("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(["clear"]) do
|
||||||
|
start_pleroma()
|
||||||
|
|
||||||
|
meili_delete!("/indexes/objects/documents")
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(["show-keys", master_key]) do
|
||||||
|
start_pleroma()
|
||||||
|
|
||||||
|
endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url])
|
||||||
|
|
||||||
|
{:ok, result} =
|
||||||
|
Pleroma.HTTP.get(
|
||||||
|
Path.join(endpoint, "/keys"),
|
||||||
|
[{"Authorization", "Bearer #{master_key}"}]
|
||||||
|
)
|
||||||
|
|
||||||
|
decoded = Jason.decode!(result.body)
|
||||||
|
|
||||||
|
if decoded["results"] do
|
||||||
|
Enum.each(decoded["results"], fn %{"description" => desc, "key" => key} ->
|
||||||
|
IO.puts("#{desc}: #{key}")
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
IO.puts("Error fetching the keys, check the master key is correct: #{inspect(decoded)}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(["stats"]) do
|
||||||
|
start_pleroma()
|
||||||
|
|
||||||
|
{:ok, result} = meili_get("/indexes/objects/stats")
|
||||||
|
IO.puts("Number of entries: #{result["numberOfDocuments"]}")
|
||||||
|
IO.puts("Indexing? #{result["isIndexing"]}")
|
||||||
|
end
|
||||||
|
end
|
|
@ -367,7 +367,7 @@ def restrict_deactivated_users(query) do
|
||||||
from(activity in query, where: activity.actor not in subquery(deactivated_users_query))
|
from(activity in query, where: activity.actor not in subquery(deactivated_users_query))
|
||||||
end
|
end
|
||||||
|
|
||||||
defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search
|
defdelegate search(user, query, options \\ []), to: Pleroma.Search.DatabaseSearch
|
||||||
|
|
||||||
def direct_conversation_id(activity, for_user) do
|
def direct_conversation_id(activity, for_user) do
|
||||||
alias Pleroma.Conversation.Participation
|
alias Pleroma.Conversation.Participation
|
||||||
|
|
|
@ -105,6 +105,7 @@ def start(_type, _args) do
|
||||||
{Oban, Config.get(Oban)},
|
{Oban, Config.get(Oban)},
|
||||||
Pleroma.Web.Endpoint
|
Pleroma.Web.Endpoint
|
||||||
] ++
|
] ++
|
||||||
|
elasticsearch_children() ++
|
||||||
task_children(@mix_env) ++
|
task_children(@mix_env) ++
|
||||||
dont_run_in_test(@mix_env) ++
|
dont_run_in_test(@mix_env) ++
|
||||||
shout_child(shout_enabled?())
|
shout_child(shout_enabled?())
|
||||||
|
@ -303,11 +304,25 @@ defp http_children(Tesla.Adapter.Gun, _) do
|
||||||
|
|
||||||
defp http_children(_, _), do: []
|
defp http_children(_, _), do: []
|
||||||
|
|
||||||
|
def elasticsearch_children do
|
||||||
|
config = Config.get([Pleroma.Search, :module])
|
||||||
|
|
||||||
|
if config == Pleroma.Search.Elasticsearch do
|
||||||
|
[Pleroma.Search.Elasticsearch.Cluster]
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@spec limiters_setup() :: :ok
|
@spec limiters_setup() :: :ok
|
||||||
def limiters_setup do
|
def limiters_setup do
|
||||||
config = Config.get(ConcurrentLimiter, [])
|
config = Config.get(ConcurrentLimiter, [])
|
||||||
|
|
||||||
[Pleroma.Web.RichMedia.Helpers, Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy]
|
[
|
||||||
|
Pleroma.Web.RichMedia.Helpers,
|
||||||
|
Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy,
|
||||||
|
Pleroma.Search
|
||||||
|
]
|
||||||
|> Enum.each(fn module ->
|
|> Enum.each(fn module ->
|
||||||
mod_config = Keyword.get(config, module, [])
|
mod_config = Keyword.get(config, module, [])
|
||||||
|
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
# Akkoma: A lightweight social networking server
|
|
||||||
# Copyright © 2022-2022 Akkoma Authors <https://git.ihatebeinga.live/IHBAGang/akkoma/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Elasticsearch.DocumentMappings.Activity do
|
|
||||||
alias Pleroma.Object
|
|
||||||
|
|
||||||
def id(obj), do: obj.id
|
|
||||||
|
|
||||||
def encode(%{object: %{data: %{"type" => "Note"}}} = activity) do
|
|
||||||
%{
|
|
||||||
_timestamp: activity.inserted_at,
|
|
||||||
user: activity.user_actor.nickname,
|
|
||||||
content: activity.object.data["content"],
|
|
||||||
instance: URI.parse(activity.user_actor.ap_id).host,
|
|
||||||
hashtags: Object.hashtags(activity.object)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,21 +0,0 @@
|
||||||
# Akkoma: A lightweight social networking server
|
|
||||||
# Copyright © 2022-2022 Akkoma Authors <https://git.ihatebeinga.live/IHBAGang/akkoma/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Elasticsearch.DocumentMappings.Hashtag do
|
|
||||||
def id(obj), do: obj.id
|
|
||||||
|
|
||||||
def encode(%{timestamp: _} = hashtag) do
|
|
||||||
%{
|
|
||||||
hashtag: hashtag.name,
|
|
||||||
timestamp: hashtag.timestamp
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def encode(hashtag) do
|
|
||||||
%{
|
|
||||||
hashtag: hashtag.name,
|
|
||||||
timestamp: hashtag.inserted_at
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,17 +0,0 @@
|
||||||
# Akkoma: A lightweight social networking server
|
|
||||||
# Copyright © 2022-2022 Akkoma Authors <https://git.ihatebeinga.live/IHBAGang/akkoma/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Elasticsearch.DocumentMappings.User do
|
|
||||||
def id(obj), do: obj.id
|
|
||||||
|
|
||||||
def encode(%{actor_type: "Person"} = user) do
|
|
||||||
%{
|
|
||||||
timestamp: user.inserted_at,
|
|
||||||
instance: URI.parse(user.ap_id).host,
|
|
||||||
nickname: user.nickname,
|
|
||||||
bio: user.bio,
|
|
||||||
display_name: user.name
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,256 +0,0 @@
|
||||||
# Akkoma: A lightweight social networking server
|
|
||||||
# Copyright © 2022-2022 Akkoma Authors <https://git.ihatebeinga.live/IHBAGang/akkoma/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Elasticsearch do
|
|
||||||
alias Pleroma.Activity
|
|
||||||
alias Pleroma.User
|
|
||||||
alias Pleroma.Object
|
|
||||||
alias Pleroma.Elasticsearch.DocumentMappings
|
|
||||||
alias Pleroma.Config
|
|
||||||
require Logger
|
|
||||||
|
|
||||||
defp url do
|
|
||||||
Config.get([:elasticsearch, :url])
|
|
||||||
end
|
|
||||||
|
|
||||||
defp enabled? do
|
|
||||||
Config.get([:search, :provider]) == Pleroma.Search.Elasticsearch
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_by_id(:activity, id) do
|
|
||||||
if enabled?() do
|
|
||||||
Elastix.Document.delete(url(), "activities", "activity", id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def put_by_id(:activity, id) do
|
|
||||||
id
|
|
||||||
|> Activity.get_by_id_with_object()
|
|
||||||
|> maybe_put_into_elasticsearch()
|
|
||||||
end
|
|
||||||
|
|
||||||
def maybe_put_into_elasticsearch({:ok, item}) do
|
|
||||||
maybe_put_into_elasticsearch(item)
|
|
||||||
end
|
|
||||||
|
|
||||||
def maybe_put_into_elasticsearch(
|
|
||||||
%{data: %{"type" => "Create"}, object: %{data: %{"type" => "Note"}}} = activity
|
|
||||||
) do
|
|
||||||
if enabled?() do
|
|
||||||
actor = Pleroma.Activity.user_actor(activity)
|
|
||||||
|
|
||||||
activity
|
|
||||||
|> Map.put(:user_actor, actor)
|
|
||||||
|> put()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def maybe_put_into_elasticsearch(%User{actor_type: "Person"} = user) do
|
|
||||||
if enabled?() do
|
|
||||||
put(user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def maybe_put_into_elasticsearch(_) do
|
|
||||||
{:ok, :skipped}
|
|
||||||
end
|
|
||||||
|
|
||||||
def maybe_bulk_post(data, type) do
|
|
||||||
if enabled?() do
|
|
||||||
bulk_post(data, type)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def put(%Activity{} = activity) do
|
|
||||||
with {:ok, _} <-
|
|
||||||
Elastix.Document.index(
|
|
||||||
url(),
|
|
||||||
"activities",
|
|
||||||
"activity",
|
|
||||||
DocumentMappings.Activity.id(activity),
|
|
||||||
DocumentMappings.Activity.encode(activity)
|
|
||||||
) do
|
|
||||||
activity
|
|
||||||
|> Map.get(:object)
|
|
||||||
|> Object.hashtags()
|
|
||||||
|> Enum.map(fn x ->
|
|
||||||
%{id: x, name: x, timestamp: DateTime.to_iso8601(DateTime.utc_now())}
|
|
||||||
end)
|
|
||||||
|> bulk_post(:hashtags)
|
|
||||||
else
|
|
||||||
{:error, %{reason: err}} ->
|
|
||||||
Logger.error("Could not put activity: #{err}")
|
|
||||||
:skipped
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def put(%User{} = user) do
|
|
||||||
with {:ok, _} <-
|
|
||||||
Elastix.Document.index(
|
|
||||||
url(),
|
|
||||||
"users",
|
|
||||||
"user",
|
|
||||||
DocumentMappings.User.id(user),
|
|
||||||
DocumentMappings.User.encode(user)
|
|
||||||
) do
|
|
||||||
:ok
|
|
||||||
else
|
|
||||||
{:error, %{reason: err}} ->
|
|
||||||
Logger.error("Could not put user: #{err}")
|
|
||||||
:skipped
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def bulk_post(data, :activities) do
|
|
||||||
d =
|
|
||||||
data
|
|
||||||
|> Enum.filter(fn x ->
|
|
||||||
t =
|
|
||||||
x.object
|
|
||||||
|> Map.get(:data, %{})
|
|
||||||
|> Map.get("type", "")
|
|
||||||
|
|
||||||
t == "Note"
|
|
||||||
end)
|
|
||||||
|> Enum.map(fn d ->
|
|
||||||
[
|
|
||||||
%{index: %{_id: DocumentMappings.Activity.id(d)}},
|
|
||||||
DocumentMappings.Activity.encode(d)
|
|
||||||
]
|
|
||||||
end)
|
|
||||||
|> List.flatten()
|
|
||||||
|
|
||||||
with {:ok, %{body: %{"errors" => false}}} <-
|
|
||||||
Elastix.Bulk.post(
|
|
||||||
url(),
|
|
||||||
d,
|
|
||||||
index: "activities",
|
|
||||||
type: "activity"
|
|
||||||
) do
|
|
||||||
:ok
|
|
||||||
else
|
|
||||||
{:error, %{reason: err}} ->
|
|
||||||
Logger.error("Could not bulk put activity: #{err}")
|
|
||||||
:skipped
|
|
||||||
|
|
||||||
{:ok, %{body: _}} ->
|
|
||||||
:skipped
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def bulk_post(data, :users) do
|
|
||||||
d =
|
|
||||||
data
|
|
||||||
|> Enum.filter(fn x -> x.actor_type == "Person" end)
|
|
||||||
|> Enum.map(fn d ->
|
|
||||||
[
|
|
||||||
%{index: %{_id: DocumentMappings.User.id(d)}},
|
|
||||||
DocumentMappings.User.encode(d)
|
|
||||||
]
|
|
||||||
end)
|
|
||||||
|> List.flatten()
|
|
||||||
|
|
||||||
with {:ok, %{body: %{"errors" => false}}} <-
|
|
||||||
Elastix.Bulk.post(
|
|
||||||
url(),
|
|
||||||
d,
|
|
||||||
index: "users",
|
|
||||||
type: "user"
|
|
||||||
) do
|
|
||||||
:ok
|
|
||||||
else
|
|
||||||
{:error, %{reason: err}} ->
|
|
||||||
Logger.error("Could not bulk put users: #{err}")
|
|
||||||
:skipped
|
|
||||||
|
|
||||||
{:ok, %{body: _}} ->
|
|
||||||
:skipped
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def bulk_post(data, :hashtags) when is_list(data) do
|
|
||||||
d =
|
|
||||||
data
|
|
||||||
|> Enum.map(fn d ->
|
|
||||||
[
|
|
||||||
%{index: %{_id: DocumentMappings.Hashtag.id(d)}},
|
|
||||||
DocumentMappings.Hashtag.encode(d)
|
|
||||||
]
|
|
||||||
end)
|
|
||||||
|> List.flatten()
|
|
||||||
|
|
||||||
with {:ok, %{body: %{"errors" => false}}} <-
|
|
||||||
Elastix.Bulk.post(
|
|
||||||
url(),
|
|
||||||
d,
|
|
||||||
index: "hashtags",
|
|
||||||
type: "hashtag"
|
|
||||||
) do
|
|
||||||
:ok
|
|
||||||
else
|
|
||||||
{:error, %{reason: err}} ->
|
|
||||||
Logger.error("Could not bulk put hashtags: #{err}")
|
|
||||||
:skipped
|
|
||||||
|
|
||||||
{:ok, %{body: _}} ->
|
|
||||||
:skipped
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def bulk_post(_, :hashtags), do: {:ok, nil}
|
|
||||||
|
|
||||||
def search(_, _, _, :skip), do: []
|
|
||||||
|
|
||||||
def search(:raw, index, type, q) do
|
|
||||||
with {:ok, raw_results} <- Elastix.Search.search(url(), index, [type], q) do
|
|
||||||
results =
|
|
||||||
raw_results
|
|
||||||
|> Map.get(:body, %{})
|
|
||||||
|> Map.get("hits", %{})
|
|
||||||
|> Map.get("hits", [])
|
|
||||||
|
|
||||||
{:ok, results}
|
|
||||||
else
|
|
||||||
{:error, e} ->
|
|
||||||
Logger.error(e)
|
|
||||||
{:error, e}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def search(:activities, q) do
|
|
||||||
with {:ok, results} <- search(:raw, "activities", "activity", q) do
|
|
||||||
results
|
|
||||||
|> Enum.map(fn result -> result["_id"] end)
|
|
||||||
|> Pleroma.Activity.all_by_ids_with_object()
|
|
||||||
|> Enum.sort(&(&1.inserted_at >= &2.inserted_at))
|
|
||||||
else
|
|
||||||
e ->
|
|
||||||
Logger.error(e)
|
|
||||||
[]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def search(:users, q) do
|
|
||||||
with {:ok, results} <- search(:raw, "users", "user", q) do
|
|
||||||
results
|
|
||||||
|> Enum.map(fn result -> result["_id"] end)
|
|
||||||
|> Pleroma.User.get_all_by_ids()
|
|
||||||
else
|
|
||||||
e ->
|
|
||||||
Logger.error(e)
|
|
||||||
[]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def search(:hashtags, q) do
|
|
||||||
with {:ok, results} <- search(:raw, "hashtags", "hashtag", q) do
|
|
||||||
results
|
|
||||||
|> Enum.map(fn result -> result["_source"]["hashtag"] end)
|
|
||||||
else
|
|
||||||
e ->
|
|
||||||
Logger.error(e)
|
|
||||||
[]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -5,9 +5,12 @@
|
||||||
defmodule Pleroma.Emails.UserEmail do
|
defmodule Pleroma.Emails.UserEmail do
|
||||||
@moduledoc "User emails"
|
@moduledoc "User emails"
|
||||||
|
|
||||||
|
require Pleroma.Web.Gettext
|
||||||
|
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.Endpoint
|
alias Pleroma.Web.Endpoint
|
||||||
|
alias Pleroma.Web.Gettext
|
||||||
alias Pleroma.Web.Router
|
alias Pleroma.Web.Router
|
||||||
|
|
||||||
import Swoosh.Email
|
import Swoosh.Email
|
||||||
|
@ -27,30 +30,76 @@ defp recipient(%User{} = user), do: recipient(user.email, user.name)
|
||||||
|
|
||||||
@spec welcome(User.t(), map()) :: Swoosh.Email.t()
|
@spec welcome(User.t(), map()) :: Swoosh.Email.t()
|
||||||
def welcome(user, opts \\ %{}) do
|
def welcome(user, opts \\ %{}) do
|
||||||
|
Gettext.with_locale_or_default user.language do
|
||||||
new()
|
new()
|
||||||
|> to(recipient(user))
|
|> to(recipient(user))
|
||||||
|> from(Map.get(opts, :sender, sender()))
|
|> from(Map.get(opts, :sender, sender()))
|
||||||
|> subject(Map.get(opts, :subject, "Welcome to #{instance_name()}!"))
|
|> subject(
|
||||||
|> html_body(Map.get(opts, :html, "Welcome to #{instance_name()}!"))
|
Map.get(
|
||||||
|> text_body(Map.get(opts, :text, "Welcome to #{instance_name()}!"))
|
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
|
end
|
||||||
|
|
||||||
def password_reset_email(user, token) when is_binary(token) do
|
def password_reset_email(user, token) when is_binary(token) do
|
||||||
|
Gettext.with_locale_or_default user.language do
|
||||||
password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token)
|
password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token)
|
||||||
|
|
||||||
html_body = """
|
html_body =
|
||||||
<h3>Reset your password at #{instance_name()}</h3>
|
Gettext.dpgettext(
|
||||||
<p>Someone has requested password change for your account at #{instance_name()}.</p>
|
"static_pages",
|
||||||
<p>If it was you, visit the following link to proceed: <a href="#{password_reset_url}">reset password</a>.</p>
|
"password reset email body",
|
||||||
<p>If it was someone else, nothing to worry about: your data is secure and your password has not been changed.</p>
|
|
||||||
"""
|
"""
|
||||||
|
<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()
|
new()
|
||||||
|> to(recipient(user))
|
|> to(recipient(user))
|
||||||
|> from(sender())
|
|> from(sender())
|
||||||
|> subject("Password reset")
|
|> subject(
|
||||||
|
Gettext.dpgettext("static_pages", "password reset email subject", "Password reset")
|
||||||
|
)
|
||||||
|> html_body(html_body)
|
|> html_body(html_body)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def user_invitation_email(
|
def user_invitation_email(
|
||||||
user,
|
user,
|
||||||
|
@ -58,6 +107,7 @@ def user_invitation_email(
|
||||||
to_email,
|
to_email,
|
||||||
to_name \\ nil
|
to_name \\ nil
|
||||||
) do
|
) do
|
||||||
|
Gettext.with_locale_or_default user.language do
|
||||||
registration_url =
|
registration_url =
|
||||||
Router.Helpers.redirect_url(
|
Router.Helpers.redirect_url(
|
||||||
Endpoint,
|
Endpoint,
|
||||||
|
@ -65,20 +115,37 @@ def user_invitation_email(
|
||||||
user_invite_token.token
|
user_invite_token.token
|
||||||
)
|
)
|
||||||
|
|
||||||
html_body = """
|
html_body =
|
||||||
<h3>You are invited to #{instance_name()}</h3>
|
Gettext.dpgettext(
|
||||||
<p>#{user.name} invites you to join #{instance_name()}, an instance of Pleroma federated social networking platform.</p>
|
"static_pages",
|
||||||
<p>Click the following link to register: <a href="#{registration_url}">accept invitation</a>.</p>
|
"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
|
||||||
|
)
|
||||||
|
|
||||||
new()
|
new()
|
||||||
|> to(recipient(to_email, to_name))
|
|> to(recipient(to_email, to_name))
|
||||||
|> from(sender())
|
|> from(sender())
|
||||||
|> subject("Invitation to #{instance_name()}")
|
|> subject(
|
||||||
|
Gettext.dpgettext(
|
||||||
|
"static_pages",
|
||||||
|
"user invitation email subject",
|
||||||
|
"Invitation to %{instance_name}",
|
||||||
|
instance_name: instance_name()
|
||||||
|
)
|
||||||
|
)
|
||||||
|> html_body(html_body)
|
|> html_body(html_body)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def account_confirmation_email(user) do
|
def account_confirmation_email(user) do
|
||||||
|
Gettext.with_locale_or_default user.language do
|
||||||
confirmation_url =
|
confirmation_url =
|
||||||
Router.Helpers.confirm_email_url(
|
Router.Helpers.confirm_email_url(
|
||||||
Endpoint,
|
Endpoint,
|
||||||
|
@ -87,45 +154,90 @@ def account_confirmation_email(user) do
|
||||||
to_string(user.confirmation_token)
|
to_string(user.confirmation_token)
|
||||||
)
|
)
|
||||||
|
|
||||||
html_body = """
|
html_body =
|
||||||
<h3>Thank you for registering on #{instance_name()}</h3>
|
Gettext.dpgettext(
|
||||||
<p>Email confirmation is required to activate the account.</p>
|
"static_pages",
|
||||||
<p>Please click the following link to <a href="#{confirmation_url}">activate your account</a>.</p>
|
"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
|
||||||
|
)
|
||||||
|
|
||||||
new()
|
new()
|
||||||
|> to(recipient(user))
|
|> to(recipient(user))
|
||||||
|> from(sender())
|
|> from(sender())
|
||||||
|> subject("#{instance_name()} account confirmation")
|
|> subject(
|
||||||
|
Gettext.dpgettext(
|
||||||
|
"static_pages",
|
||||||
|
"confirmation email subject",
|
||||||
|
"%{instance_name} account confirmation",
|
||||||
|
instance_name: instance_name()
|
||||||
|
)
|
||||||
|
)
|
||||||
|> html_body(html_body)
|
|> html_body(html_body)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def approval_pending_email(user) do
|
def approval_pending_email(user) do
|
||||||
html_body = """
|
Gettext.with_locale_or_default user.language do
|
||||||
<h3>Awaiting Approval</h3>
|
html_body =
|
||||||
<p>Your account at #{instance_name()} is being reviewed by staff. You will receive another email once your account is approved.</p>
|
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()
|
new()
|
||||||
|> to(recipient(user))
|
|> to(recipient(user))
|
||||||
|> from(sender())
|
|> from(sender())
|
||||||
|> subject("Your account is awaiting approval")
|
|> subject(
|
||||||
|
Gettext.dpgettext(
|
||||||
|
"static_pages",
|
||||||
|
"approval pending email subject",
|
||||||
|
"Your account is awaiting approval"
|
||||||
|
)
|
||||||
|
)
|
||||||
|> html_body(html_body)
|
|> html_body(html_body)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def successful_registration_email(user) do
|
def successful_registration_email(user) do
|
||||||
html_body = """
|
Gettext.with_locale_or_default user.language do
|
||||||
<h3>Hello @#{user.nickname},</h3>
|
html_body =
|
||||||
<p>Your account at #{instance_name()} has been registered successfully.</p>
|
Gettext.dpgettext(
|
||||||
<p>No further action is required to activate your account.</p>
|
"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()
|
new()
|
||||||
|> to(recipient(user))
|
|> to(recipient(user))
|
||||||
|> from(sender())
|
|> from(sender())
|
||||||
|> subject("Account registered on #{instance_name()}")
|
|> subject(
|
||||||
|
Gettext.dpgettext(
|
||||||
|
"static_pages",
|
||||||
|
"successful registration email subject",
|
||||||
|
"Account registered on %{instance_name}",
|
||||||
|
instance_name: instance_name()
|
||||||
|
)
|
||||||
|
)
|
||||||
|> html_body(html_body)
|
|> html_body(html_body)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Email used in digest email notifications
|
Email used in digest email notifications
|
||||||
|
@ -134,6 +246,7 @@ def successful_registration_email(user) do
|
||||||
"""
|
"""
|
||||||
@spec digest_email(User.t()) :: Swoosh.Email.t() | nil
|
@spec digest_email(User.t()) :: Swoosh.Email.t() | nil
|
||||||
def digest_email(user) do
|
def digest_email(user) do
|
||||||
|
Gettext.with_locale_or_default user.language do
|
||||||
notifications = Pleroma.Notification.for_user_since(user, user.last_digest_emailed_at)
|
notifications = Pleroma.Notification.for_user_since(user, user.last_digest_emailed_at)
|
||||||
|
|
||||||
mentions =
|
mentions =
|
||||||
|
@ -193,12 +306,20 @@ def digest_email(user) do
|
||||||
new()
|
new()
|
||||||
|> to(recipient(user))
|
|> to(recipient(user))
|
||||||
|> from(sender())
|
|> from(sender())
|
||||||
|> subject("Your digest from #{instance_name()}")
|
|> subject(
|
||||||
|
Gettext.dpgettext(
|
||||||
|
"static_pages",
|
||||||
|
"digest email subject",
|
||||||
|
"Your digest from %{instance_name}",
|
||||||
|
instance_name: instance_name()
|
||||||
|
)
|
||||||
|
)
|
||||||
|> put_layout(false)
|
|> put_layout(false)
|
||||||
|> render_body("digest.html", html_data)
|
|> render_body("digest.html", html_data)
|
||||||
|> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.svg", type: :inline))
|
|> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.svg", type: :inline))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp format_links(str) do
|
defp format_links(str) do
|
||||||
re = ~r/<a.+href=['"].*>/iU
|
re = ~r/<a.+href=['"].*>/iU
|
||||||
|
@ -226,27 +347,47 @@ def unsubscribe_url(user, notifications_type) do
|
||||||
|
|
||||||
def backup_is_ready_email(backup, admin_user_id \\ nil) do
|
def backup_is_ready_email(backup, admin_user_id \\ nil) do
|
||||||
%{user: user} = Pleroma.Repo.preload(backup, :user)
|
%{user: user} = Pleroma.Repo.preload(backup, :user)
|
||||||
|
|
||||||
|
Gettext.with_locale_or_default user.language do
|
||||||
download_url = Pleroma.Web.PleromaAPI.BackupView.download_url(backup)
|
download_url = Pleroma.Web.PleromaAPI.BackupView.download_url(backup)
|
||||||
|
|
||||||
html_body =
|
html_body =
|
||||||
if is_nil(admin_user_id) do
|
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>You requested a full backup of your Pleroma account. It's ready for download:</p>
|
||||||
<p><a href="#{download_url}">#{download_url}</a></p>
|
<p><a href="%{download_url}">%{download_url}</a></p>
|
||||||
"""
|
""",
|
||||||
|
download_url: download_url
|
||||||
|
)
|
||||||
else
|
else
|
||||||
admin = Pleroma.Repo.get(User, admin_user_id)
|
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>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>
|
<p><a href="%{download_url}">%{download_url}</a></p>
|
||||||
"""
|
""",
|
||||||
|
admin_nickname: admin.nickname,
|
||||||
|
download_url: download_url
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
new()
|
new()
|
||||||
|> to(recipient(user))
|
|> to(recipient(user))
|
||||||
|> from(sender())
|
|> from(sender())
|
||||||
|> subject("Your account archive is ready")
|
|> subject(
|
||||||
|
Gettext.dpgettext(
|
||||||
|
"static_pages",
|
||||||
|
"account archive email subject",
|
||||||
|
"Your account archive is ready"
|
||||||
|
)
|
||||||
|
)
|
||||||
|> html_body(html_body)
|
|> html_body(html_body)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
# emoji-test.txt
|
# emoji-test.txt
|
||||||
# Date: 2020-09-12, 22:19:50 GMT
|
# Date: 2021-08-26, 17:22:23 GMT
|
||||||
# © 2020 Unicode®, Inc.
|
# © 2021 Unicode®, Inc.
|
||||||
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
|
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
|
||||||
# For terms of use, see http://www.unicode.org/terms_of_use.html
|
# For terms of use, see http://www.unicode.org/terms_of_use.html
|
||||||
#
|
#
|
||||||
# Emoji Keyboard/Display Test Data for UTS #51
|
# Emoji Keyboard/Display Test Data for UTS #51
|
||||||
# Version: 13.1
|
# Version: 14.0
|
||||||
#
|
#
|
||||||
# For documentation and usage, see http://www.unicode.org/reports/tr51
|
# For documentation and usage, see http://www.unicode.org/reports/tr51
|
||||||
#
|
#
|
||||||
|
@ -43,6 +43,7 @@
|
||||||
1F602 ; fully-qualified # 😂 E0.6 face with tears of joy
|
1F602 ; fully-qualified # 😂 E0.6 face with tears of joy
|
||||||
1F642 ; fully-qualified # 🙂 E1.0 slightly smiling face
|
1F642 ; fully-qualified # 🙂 E1.0 slightly smiling face
|
||||||
1F643 ; fully-qualified # 🙃 E1.0 upside-down face
|
1F643 ; fully-qualified # 🙃 E1.0 upside-down face
|
||||||
|
1FAE0 ; fully-qualified # 🫠 E14.0 melting face
|
||||||
1F609 ; fully-qualified # 😉 E0.6 winking face
|
1F609 ; fully-qualified # 😉 E0.6 winking face
|
||||||
1F60A ; fully-qualified # 😊 E0.6 smiling face with smiling eyes
|
1F60A ; fully-qualified # 😊 E0.6 smiling face with smiling eyes
|
||||||
1F607 ; fully-qualified # 😇 E1.0 smiling face with halo
|
1F607 ; fully-qualified # 😇 E1.0 smiling face with halo
|
||||||
|
@ -68,10 +69,13 @@
|
||||||
1F911 ; fully-qualified # 🤑 E1.0 money-mouth face
|
1F911 ; fully-qualified # 🤑 E1.0 money-mouth face
|
||||||
|
|
||||||
# subgroup: face-hand
|
# subgroup: face-hand
|
||||||
1F917 ; fully-qualified # 🤗 E1.0 hugging face
|
1F917 ; fully-qualified # 🤗 E1.0 smiling face with open hands
|
||||||
1F92D ; fully-qualified # 🤭 E5.0 face with hand over mouth
|
1F92D ; fully-qualified # 🤭 E5.0 face with hand over mouth
|
||||||
|
1FAE2 ; fully-qualified # 🫢 E14.0 face with open eyes and hand over mouth
|
||||||
|
1FAE3 ; fully-qualified # 🫣 E14.0 face with peeking eye
|
||||||
1F92B ; fully-qualified # 🤫 E5.0 shushing face
|
1F92B ; fully-qualified # 🤫 E5.0 shushing face
|
||||||
1F914 ; fully-qualified # 🤔 E1.0 thinking face
|
1F914 ; fully-qualified # 🤔 E1.0 thinking face
|
||||||
|
1FAE1 ; fully-qualified # 🫡 E14.0 saluting face
|
||||||
|
|
||||||
# subgroup: face-neutral-skeptical
|
# subgroup: face-neutral-skeptical
|
||||||
1F910 ; fully-qualified # 🤐 E1.0 zipper-mouth face
|
1F910 ; fully-qualified # 🤐 E1.0 zipper-mouth face
|
||||||
|
@ -79,6 +83,7 @@
|
||||||
1F610 ; fully-qualified # 😐 E0.7 neutral face
|
1F610 ; fully-qualified # 😐 E0.7 neutral face
|
||||||
1F611 ; fully-qualified # 😑 E1.0 expressionless face
|
1F611 ; fully-qualified # 😑 E1.0 expressionless face
|
||||||
1F636 ; fully-qualified # 😶 E1.0 face without mouth
|
1F636 ; fully-qualified # 😶 E1.0 face without mouth
|
||||||
|
1FAE5 ; fully-qualified # 🫥 E14.0 dotted line face
|
||||||
1F636 200D 1F32B FE0F ; fully-qualified # 😶🌫️ E13.1 face in clouds
|
1F636 200D 1F32B FE0F ; fully-qualified # 😶🌫️ E13.1 face in clouds
|
||||||
1F636 200D 1F32B ; minimally-qualified # 😶🌫 E13.1 face in clouds
|
1F636 200D 1F32B ; minimally-qualified # 😶🌫 E13.1 face in clouds
|
||||||
1F60F ; fully-qualified # 😏 E0.6 smirking face
|
1F60F ; fully-qualified # 😏 E0.6 smirking face
|
||||||
|
@ -105,7 +110,7 @@
|
||||||
1F975 ; fully-qualified # 🥵 E11.0 hot face
|
1F975 ; fully-qualified # 🥵 E11.0 hot face
|
||||||
1F976 ; fully-qualified # 🥶 E11.0 cold face
|
1F976 ; fully-qualified # 🥶 E11.0 cold face
|
||||||
1F974 ; fully-qualified # 🥴 E11.0 woozy face
|
1F974 ; fully-qualified # 🥴 E11.0 woozy face
|
||||||
1F635 ; fully-qualified # 😵 E0.6 knocked-out face
|
1F635 ; fully-qualified # 😵 E0.6 face with crossed-out eyes
|
||||||
1F635 200D 1F4AB ; fully-qualified # 😵💫 E13.1 face with spiral eyes
|
1F635 200D 1F4AB ; fully-qualified # 😵💫 E13.1 face with spiral eyes
|
||||||
1F92F ; fully-qualified # 🤯 E5.0 exploding head
|
1F92F ; fully-qualified # 🤯 E5.0 exploding head
|
||||||
|
|
||||||
|
@ -121,6 +126,7 @@
|
||||||
|
|
||||||
# subgroup: face-concerned
|
# subgroup: face-concerned
|
||||||
1F615 ; fully-qualified # 😕 E1.0 confused face
|
1F615 ; fully-qualified # 😕 E1.0 confused face
|
||||||
|
1FAE4 ; fully-qualified # 🫤 E14.0 face with diagonal mouth
|
||||||
1F61F ; fully-qualified # 😟 E1.0 worried face
|
1F61F ; fully-qualified # 😟 E1.0 worried face
|
||||||
1F641 ; fully-qualified # 🙁 E1.0 slightly frowning face
|
1F641 ; fully-qualified # 🙁 E1.0 slightly frowning face
|
||||||
2639 FE0F ; fully-qualified # ☹️ E0.7 frowning face
|
2639 FE0F ; fully-qualified # ☹️ E0.7 frowning face
|
||||||
|
@ -130,6 +136,7 @@
|
||||||
1F632 ; fully-qualified # 😲 E0.6 astonished face
|
1F632 ; fully-qualified # 😲 E0.6 astonished face
|
||||||
1F633 ; fully-qualified # 😳 E0.6 flushed face
|
1F633 ; fully-qualified # 😳 E0.6 flushed face
|
||||||
1F97A ; fully-qualified # 🥺 E11.0 pleading face
|
1F97A ; fully-qualified # 🥺 E11.0 pleading face
|
||||||
|
1F979 ; fully-qualified # 🥹 E14.0 face holding back tears
|
||||||
1F626 ; fully-qualified # 😦 E1.0 frowning face with open mouth
|
1F626 ; fully-qualified # 😦 E1.0 frowning face with open mouth
|
||||||
1F627 ; fully-qualified # 😧 E1.0 anguished face
|
1F627 ; fully-qualified # 😧 E1.0 anguished face
|
||||||
1F628 ; fully-qualified # 😨 E0.6 fearful face
|
1F628 ; fully-qualified # 😨 E0.6 fearful face
|
||||||
|
@ -232,8 +239,8 @@
|
||||||
1F4AD ; fully-qualified # 💭 E1.0 thought balloon
|
1F4AD ; fully-qualified # 💭 E1.0 thought balloon
|
||||||
1F4A4 ; fully-qualified # 💤 E0.6 zzz
|
1F4A4 ; fully-qualified # 💤 E0.6 zzz
|
||||||
|
|
||||||
# Smileys & Emotion subtotal: 170
|
# Smileys & Emotion subtotal: 177
|
||||||
# Smileys & Emotion subtotal: 170 w/o modifiers
|
# Smileys & Emotion subtotal: 177 w/o modifiers
|
||||||
|
|
||||||
# group: People & Body
|
# group: People & Body
|
||||||
|
|
||||||
|
@ -269,6 +276,30 @@
|
||||||
1F596 1F3FD ; fully-qualified # 🖖🏽 E1.0 vulcan salute: medium skin tone
|
1F596 1F3FD ; fully-qualified # 🖖🏽 E1.0 vulcan salute: medium skin tone
|
||||||
1F596 1F3FE ; fully-qualified # 🖖🏾 E1.0 vulcan salute: medium-dark skin tone
|
1F596 1F3FE ; fully-qualified # 🖖🏾 E1.0 vulcan salute: medium-dark skin tone
|
||||||
1F596 1F3FF ; fully-qualified # 🖖🏿 E1.0 vulcan salute: dark skin tone
|
1F596 1F3FF ; fully-qualified # 🖖🏿 E1.0 vulcan salute: dark skin tone
|
||||||
|
1FAF1 ; fully-qualified # 🫱 E14.0 rightwards hand
|
||||||
|
1FAF1 1F3FB ; fully-qualified # 🫱🏻 E14.0 rightwards hand: light skin tone
|
||||||
|
1FAF1 1F3FC ; fully-qualified # 🫱🏼 E14.0 rightwards hand: medium-light skin tone
|
||||||
|
1FAF1 1F3FD ; fully-qualified # 🫱🏽 E14.0 rightwards hand: medium skin tone
|
||||||
|
1FAF1 1F3FE ; fully-qualified # 🫱🏾 E14.0 rightwards hand: medium-dark skin tone
|
||||||
|
1FAF1 1F3FF ; fully-qualified # 🫱🏿 E14.0 rightwards hand: dark skin tone
|
||||||
|
1FAF2 ; fully-qualified # 🫲 E14.0 leftwards hand
|
||||||
|
1FAF2 1F3FB ; fully-qualified # 🫲🏻 E14.0 leftwards hand: light skin tone
|
||||||
|
1FAF2 1F3FC ; fully-qualified # 🫲🏼 E14.0 leftwards hand: medium-light skin tone
|
||||||
|
1FAF2 1F3FD ; fully-qualified # 🫲🏽 E14.0 leftwards hand: medium skin tone
|
||||||
|
1FAF2 1F3FE ; fully-qualified # 🫲🏾 E14.0 leftwards hand: medium-dark skin tone
|
||||||
|
1FAF2 1F3FF ; fully-qualified # 🫲🏿 E14.0 leftwards hand: dark skin tone
|
||||||
|
1FAF3 ; fully-qualified # 🫳 E14.0 palm down hand
|
||||||
|
1FAF3 1F3FB ; fully-qualified # 🫳🏻 E14.0 palm down hand: light skin tone
|
||||||
|
1FAF3 1F3FC ; fully-qualified # 🫳🏼 E14.0 palm down hand: medium-light skin tone
|
||||||
|
1FAF3 1F3FD ; fully-qualified # 🫳🏽 E14.0 palm down hand: medium skin tone
|
||||||
|
1FAF3 1F3FE ; fully-qualified # 🫳🏾 E14.0 palm down hand: medium-dark skin tone
|
||||||
|
1FAF3 1F3FF ; fully-qualified # 🫳🏿 E14.0 palm down hand: dark skin tone
|
||||||
|
1FAF4 ; fully-qualified # 🫴 E14.0 palm up hand
|
||||||
|
1FAF4 1F3FB ; fully-qualified # 🫴🏻 E14.0 palm up hand: light skin tone
|
||||||
|
1FAF4 1F3FC ; fully-qualified # 🫴🏼 E14.0 palm up hand: medium-light skin tone
|
||||||
|
1FAF4 1F3FD ; fully-qualified # 🫴🏽 E14.0 palm up hand: medium skin tone
|
||||||
|
1FAF4 1F3FE ; fully-qualified # 🫴🏾 E14.0 palm up hand: medium-dark skin tone
|
||||||
|
1FAF4 1F3FF ; fully-qualified # 🫴🏿 E14.0 palm up hand: dark skin tone
|
||||||
|
|
||||||
# subgroup: hand-fingers-partial
|
# subgroup: hand-fingers-partial
|
||||||
1F44C ; fully-qualified # 👌 E0.6 OK hand
|
1F44C ; fully-qualified # 👌 E0.6 OK hand
|
||||||
|
@ -302,6 +333,12 @@
|
||||||
1F91E 1F3FD ; fully-qualified # 🤞🏽 E3.0 crossed fingers: medium skin tone
|
1F91E 1F3FD ; fully-qualified # 🤞🏽 E3.0 crossed fingers: medium skin tone
|
||||||
1F91E 1F3FE ; fully-qualified # 🤞🏾 E3.0 crossed fingers: medium-dark skin tone
|
1F91E 1F3FE ; fully-qualified # 🤞🏾 E3.0 crossed fingers: medium-dark skin tone
|
||||||
1F91E 1F3FF ; fully-qualified # 🤞🏿 E3.0 crossed fingers: dark skin tone
|
1F91E 1F3FF ; fully-qualified # 🤞🏿 E3.0 crossed fingers: dark skin tone
|
||||||
|
1FAF0 ; fully-qualified # 🫰 E14.0 hand with index finger and thumb crossed
|
||||||
|
1FAF0 1F3FB ; fully-qualified # 🫰🏻 E14.0 hand with index finger and thumb crossed: light skin tone
|
||||||
|
1FAF0 1F3FC ; fully-qualified # 🫰🏼 E14.0 hand with index finger and thumb crossed: medium-light skin tone
|
||||||
|
1FAF0 1F3FD ; fully-qualified # 🫰🏽 E14.0 hand with index finger and thumb crossed: medium skin tone
|
||||||
|
1FAF0 1F3FE ; fully-qualified # 🫰🏾 E14.0 hand with index finger and thumb crossed: medium-dark skin tone
|
||||||
|
1FAF0 1F3FF ; fully-qualified # 🫰🏿 E14.0 hand with index finger and thumb crossed: dark skin tone
|
||||||
1F91F ; fully-qualified # 🤟 E5.0 love-you gesture
|
1F91F ; fully-qualified # 🤟 E5.0 love-you gesture
|
||||||
1F91F 1F3FB ; fully-qualified # 🤟🏻 E5.0 love-you gesture: light skin tone
|
1F91F 1F3FB ; fully-qualified # 🤟🏻 E5.0 love-you gesture: light skin tone
|
||||||
1F91F 1F3FC ; fully-qualified # 🤟🏼 E5.0 love-you gesture: medium-light skin tone
|
1F91F 1F3FC ; fully-qualified # 🤟🏼 E5.0 love-you gesture: medium-light skin tone
|
||||||
|
@ -359,6 +396,12 @@
|
||||||
261D 1F3FD ; fully-qualified # ☝🏽 E1.0 index pointing up: medium skin tone
|
261D 1F3FD ; fully-qualified # ☝🏽 E1.0 index pointing up: medium skin tone
|
||||||
261D 1F3FE ; fully-qualified # ☝🏾 E1.0 index pointing up: medium-dark skin tone
|
261D 1F3FE ; fully-qualified # ☝🏾 E1.0 index pointing up: medium-dark skin tone
|
||||||
261D 1F3FF ; fully-qualified # ☝🏿 E1.0 index pointing up: dark skin tone
|
261D 1F3FF ; fully-qualified # ☝🏿 E1.0 index pointing up: dark skin tone
|
||||||
|
1FAF5 ; fully-qualified # 🫵 E14.0 index pointing at the viewer
|
||||||
|
1FAF5 1F3FB ; fully-qualified # 🫵🏻 E14.0 index pointing at the viewer: light skin tone
|
||||||
|
1FAF5 1F3FC ; fully-qualified # 🫵🏼 E14.0 index pointing at the viewer: medium-light skin tone
|
||||||
|
1FAF5 1F3FD ; fully-qualified # 🫵🏽 E14.0 index pointing at the viewer: medium skin tone
|
||||||
|
1FAF5 1F3FE ; fully-qualified # 🫵🏾 E14.0 index pointing at the viewer: medium-dark skin tone
|
||||||
|
1FAF5 1F3FF ; fully-qualified # 🫵🏿 E14.0 index pointing at the viewer: dark skin tone
|
||||||
|
|
||||||
# subgroup: hand-fingers-closed
|
# subgroup: hand-fingers-closed
|
||||||
1F44D ; fully-qualified # 👍 E0.6 thumbs up
|
1F44D ; fully-qualified # 👍 E0.6 thumbs up
|
||||||
|
@ -411,6 +454,12 @@
|
||||||
1F64C 1F3FD ; fully-qualified # 🙌🏽 E1.0 raising hands: medium skin tone
|
1F64C 1F3FD ; fully-qualified # 🙌🏽 E1.0 raising hands: medium skin tone
|
||||||
1F64C 1F3FE ; fully-qualified # 🙌🏾 E1.0 raising hands: medium-dark skin tone
|
1F64C 1F3FE ; fully-qualified # 🙌🏾 E1.0 raising hands: medium-dark skin tone
|
||||||
1F64C 1F3FF ; fully-qualified # 🙌🏿 E1.0 raising hands: dark skin tone
|
1F64C 1F3FF ; fully-qualified # 🙌🏿 E1.0 raising hands: dark skin tone
|
||||||
|
1FAF6 ; fully-qualified # 🫶 E14.0 heart hands
|
||||||
|
1FAF6 1F3FB ; fully-qualified # 🫶🏻 E14.0 heart hands: light skin tone
|
||||||
|
1FAF6 1F3FC ; fully-qualified # 🫶🏼 E14.0 heart hands: medium-light skin tone
|
||||||
|
1FAF6 1F3FD ; fully-qualified # 🫶🏽 E14.0 heart hands: medium skin tone
|
||||||
|
1FAF6 1F3FE ; fully-qualified # 🫶🏾 E14.0 heart hands: medium-dark skin tone
|
||||||
|
1FAF6 1F3FF ; fully-qualified # 🫶🏿 E14.0 heart hands: dark skin tone
|
||||||
1F450 ; fully-qualified # 👐 E0.6 open hands
|
1F450 ; fully-qualified # 👐 E0.6 open hands
|
||||||
1F450 1F3FB ; fully-qualified # 👐🏻 E1.0 open hands: light skin tone
|
1F450 1F3FB ; fully-qualified # 👐🏻 E1.0 open hands: light skin tone
|
||||||
1F450 1F3FC ; fully-qualified # 👐🏼 E1.0 open hands: medium-light skin tone
|
1F450 1F3FC ; fully-qualified # 👐🏼 E1.0 open hands: medium-light skin tone
|
||||||
|
@ -424,6 +473,31 @@
|
||||||
1F932 1F3FE ; fully-qualified # 🤲🏾 E5.0 palms up together: medium-dark skin tone
|
1F932 1F3FE ; fully-qualified # 🤲🏾 E5.0 palms up together: medium-dark skin tone
|
||||||
1F932 1F3FF ; fully-qualified # 🤲🏿 E5.0 palms up together: dark skin tone
|
1F932 1F3FF ; fully-qualified # 🤲🏿 E5.0 palms up together: dark skin tone
|
||||||
1F91D ; fully-qualified # 🤝 E3.0 handshake
|
1F91D ; fully-qualified # 🤝 E3.0 handshake
|
||||||
|
1F91D 1F3FB ; fully-qualified # 🤝🏻 E3.0 handshake: light skin tone
|
||||||
|
1F91D 1F3FC ; fully-qualified # 🤝🏼 E3.0 handshake: medium-light skin tone
|
||||||
|
1F91D 1F3FD ; fully-qualified # 🤝🏽 E3.0 handshake: medium skin tone
|
||||||
|
1F91D 1F3FE ; fully-qualified # 🤝🏾 E3.0 handshake: medium-dark skin tone
|
||||||
|
1F91D 1F3FF ; fully-qualified # 🤝🏿 E3.0 handshake: dark skin tone
|
||||||
|
1FAF1 1F3FB 200D 1FAF2 1F3FC ; fully-qualified # 🫱🏻🫲🏼 E14.0 handshake: light skin tone, medium-light skin tone
|
||||||
|
1FAF1 1F3FB 200D 1FAF2 1F3FD ; fully-qualified # 🫱🏻🫲🏽 E14.0 handshake: light skin tone, medium skin tone
|
||||||
|
1FAF1 1F3FB 200D 1FAF2 1F3FE ; fully-qualified # 🫱🏻🫲🏾 E14.0 handshake: light skin tone, medium-dark skin tone
|
||||||
|
1FAF1 1F3FB 200D 1FAF2 1F3FF ; fully-qualified # 🫱🏻🫲🏿 E14.0 handshake: light skin tone, dark skin tone
|
||||||
|
1FAF1 1F3FC 200D 1FAF2 1F3FB ; fully-qualified # 🫱🏼🫲🏻 E14.0 handshake: medium-light skin tone, light skin tone
|
||||||
|
1FAF1 1F3FC 200D 1FAF2 1F3FD ; fully-qualified # 🫱🏼🫲🏽 E14.0 handshake: medium-light skin tone, medium skin tone
|
||||||
|
1FAF1 1F3FC 200D 1FAF2 1F3FE ; fully-qualified # 🫱🏼🫲🏾 E14.0 handshake: medium-light skin tone, medium-dark skin tone
|
||||||
|
1FAF1 1F3FC 200D 1FAF2 1F3FF ; fully-qualified # 🫱🏼🫲🏿 E14.0 handshake: medium-light skin tone, dark skin tone
|
||||||
|
1FAF1 1F3FD 200D 1FAF2 1F3FB ; fully-qualified # 🫱🏽🫲🏻 E14.0 handshake: medium skin tone, light skin tone
|
||||||
|
1FAF1 1F3FD 200D 1FAF2 1F3FC ; fully-qualified # 🫱🏽🫲🏼 E14.0 handshake: medium skin tone, medium-light skin tone
|
||||||
|
1FAF1 1F3FD 200D 1FAF2 1F3FE ; fully-qualified # 🫱🏽🫲🏾 E14.0 handshake: medium skin tone, medium-dark skin tone
|
||||||
|
1FAF1 1F3FD 200D 1FAF2 1F3FF ; fully-qualified # 🫱🏽🫲🏿 E14.0 handshake: medium skin tone, dark skin tone
|
||||||
|
1FAF1 1F3FE 200D 1FAF2 1F3FB ; fully-qualified # 🫱🏾🫲🏻 E14.0 handshake: medium-dark skin tone, light skin tone
|
||||||
|
1FAF1 1F3FE 200D 1FAF2 1F3FC ; fully-qualified # 🫱🏾🫲🏼 E14.0 handshake: medium-dark skin tone, medium-light skin tone
|
||||||
|
1FAF1 1F3FE 200D 1FAF2 1F3FD ; fully-qualified # 🫱🏾🫲🏽 E14.0 handshake: medium-dark skin tone, medium skin tone
|
||||||
|
1FAF1 1F3FE 200D 1FAF2 1F3FF ; fully-qualified # 🫱🏾🫲🏿 E14.0 handshake: medium-dark skin tone, dark skin tone
|
||||||
|
1FAF1 1F3FF 200D 1FAF2 1F3FB ; fully-qualified # 🫱🏿🫲🏻 E14.0 handshake: dark skin tone, light skin tone
|
||||||
|
1FAF1 1F3FF 200D 1FAF2 1F3FC ; fully-qualified # 🫱🏿🫲🏼 E14.0 handshake: dark skin tone, medium-light skin tone
|
||||||
|
1FAF1 1F3FF 200D 1FAF2 1F3FD ; fully-qualified # 🫱🏿🫲🏽 E14.0 handshake: dark skin tone, medium skin tone
|
||||||
|
1FAF1 1F3FF 200D 1FAF2 1F3FE ; fully-qualified # 🫱🏿🫲🏾 E14.0 handshake: dark skin tone, medium-dark skin tone
|
||||||
1F64F ; fully-qualified # 🙏 E0.6 folded hands
|
1F64F ; fully-qualified # 🙏 E0.6 folded hands
|
||||||
1F64F 1F3FB ; fully-qualified # 🙏🏻 E1.0 folded hands: light skin tone
|
1F64F 1F3FB ; fully-qualified # 🙏🏻 E1.0 folded hands: light skin tone
|
||||||
1F64F 1F3FC ; fully-qualified # 🙏🏼 E1.0 folded hands: medium-light skin tone
|
1F64F 1F3FC ; fully-qualified # 🙏🏼 E1.0 folded hands: medium-light skin tone
|
||||||
|
@ -501,6 +575,7 @@
|
||||||
1F441 ; unqualified # 👁 E0.7 eye
|
1F441 ; unqualified # 👁 E0.7 eye
|
||||||
1F445 ; fully-qualified # 👅 E0.6 tongue
|
1F445 ; fully-qualified # 👅 E0.6 tongue
|
||||||
1F444 ; fully-qualified # 👄 E0.6 mouth
|
1F444 ; fully-qualified # 👄 E0.6 mouth
|
||||||
|
1FAE6 ; fully-qualified # 🫦 E14.0 biting lip
|
||||||
|
|
||||||
# subgroup: person
|
# subgroup: person
|
||||||
1F476 ; fully-qualified # 👶 E0.6 baby
|
1F476 ; fully-qualified # 👶 E0.6 baby
|
||||||
|
@ -1472,6 +1547,12 @@
|
||||||
1F477 1F3FE 200D 2640 ; minimally-qualified # 👷🏾♀ E4.0 woman construction worker: medium-dark skin tone
|
1F477 1F3FE 200D 2640 ; minimally-qualified # 👷🏾♀ E4.0 woman construction worker: medium-dark skin tone
|
||||||
1F477 1F3FF 200D 2640 FE0F ; fully-qualified # 👷🏿♀️ E4.0 woman construction worker: dark skin tone
|
1F477 1F3FF 200D 2640 FE0F ; fully-qualified # 👷🏿♀️ E4.0 woman construction worker: dark skin tone
|
||||||
1F477 1F3FF 200D 2640 ; minimally-qualified # 👷🏿♀ E4.0 woman construction worker: dark skin tone
|
1F477 1F3FF 200D 2640 ; minimally-qualified # 👷🏿♀ E4.0 woman construction worker: dark skin tone
|
||||||
|
1FAC5 ; fully-qualified # 🫅 E14.0 person with crown
|
||||||
|
1FAC5 1F3FB ; fully-qualified # 🫅🏻 E14.0 person with crown: light skin tone
|
||||||
|
1FAC5 1F3FC ; fully-qualified # 🫅🏼 E14.0 person with crown: medium-light skin tone
|
||||||
|
1FAC5 1F3FD ; fully-qualified # 🫅🏽 E14.0 person with crown: medium skin tone
|
||||||
|
1FAC5 1F3FE ; fully-qualified # 🫅🏾 E14.0 person with crown: medium-dark skin tone
|
||||||
|
1FAC5 1F3FF ; fully-qualified # 🫅🏿 E14.0 person with crown: dark skin tone
|
||||||
1F934 ; fully-qualified # 🤴 E3.0 prince
|
1F934 ; fully-qualified # 🤴 E3.0 prince
|
||||||
1F934 1F3FB ; fully-qualified # 🤴🏻 E3.0 prince: light skin tone
|
1F934 1F3FB ; fully-qualified # 🤴🏻 E3.0 prince: light skin tone
|
||||||
1F934 1F3FC ; fully-qualified # 🤴🏼 E3.0 prince: medium-light skin tone
|
1F934 1F3FC ; fully-qualified # 🤴🏼 E3.0 prince: medium-light skin tone
|
||||||
|
@ -1592,6 +1673,18 @@
|
||||||
1F930 1F3FD ; fully-qualified # 🤰🏽 E3.0 pregnant woman: medium skin tone
|
1F930 1F3FD ; fully-qualified # 🤰🏽 E3.0 pregnant woman: medium skin tone
|
||||||
1F930 1F3FE ; fully-qualified # 🤰🏾 E3.0 pregnant woman: medium-dark skin tone
|
1F930 1F3FE ; fully-qualified # 🤰🏾 E3.0 pregnant woman: medium-dark skin tone
|
||||||
1F930 1F3FF ; fully-qualified # 🤰🏿 E3.0 pregnant woman: dark skin tone
|
1F930 1F3FF ; fully-qualified # 🤰🏿 E3.0 pregnant woman: dark skin tone
|
||||||
|
1FAC3 ; fully-qualified # 🫃 E14.0 pregnant man
|
||||||
|
1FAC3 1F3FB ; fully-qualified # 🫃🏻 E14.0 pregnant man: light skin tone
|
||||||
|
1FAC3 1F3FC ; fully-qualified # 🫃🏼 E14.0 pregnant man: medium-light skin tone
|
||||||
|
1FAC3 1F3FD ; fully-qualified # 🫃🏽 E14.0 pregnant man: medium skin tone
|
||||||
|
1FAC3 1F3FE ; fully-qualified # 🫃🏾 E14.0 pregnant man: medium-dark skin tone
|
||||||
|
1FAC3 1F3FF ; fully-qualified # 🫃🏿 E14.0 pregnant man: dark skin tone
|
||||||
|
1FAC4 ; fully-qualified # 🫄 E14.0 pregnant person
|
||||||
|
1FAC4 1F3FB ; fully-qualified # 🫄🏻 E14.0 pregnant person: light skin tone
|
||||||
|
1FAC4 1F3FC ; fully-qualified # 🫄🏼 E14.0 pregnant person: medium-light skin tone
|
||||||
|
1FAC4 1F3FD ; fully-qualified # 🫄🏽 E14.0 pregnant person: medium skin tone
|
||||||
|
1FAC4 1F3FE ; fully-qualified # 🫄🏾 E14.0 pregnant person: medium-dark skin tone
|
||||||
|
1FAC4 1F3FF ; fully-qualified # 🫄🏿 E14.0 pregnant person: dark skin tone
|
||||||
1F931 ; fully-qualified # 🤱 E5.0 breast-feeding
|
1F931 ; fully-qualified # 🤱 E5.0 breast-feeding
|
||||||
1F931 1F3FB ; fully-qualified # 🤱🏻 E5.0 breast-feeding: light skin tone
|
1F931 1F3FB ; fully-qualified # 🤱🏻 E5.0 breast-feeding: light skin tone
|
||||||
1F931 1F3FC ; fully-qualified # 🤱🏼 E5.0 breast-feeding: medium-light skin tone
|
1F931 1F3FC ; fully-qualified # 🤱🏼 E5.0 breast-feeding: medium-light skin tone
|
||||||
|
@ -1862,6 +1955,7 @@
|
||||||
1F9DF 200D 2642 ; minimally-qualified # 🧟♂ E5.0 man zombie
|
1F9DF 200D 2642 ; minimally-qualified # 🧟♂ E5.0 man zombie
|
||||||
1F9DF 200D 2640 FE0F ; fully-qualified # 🧟♀️ E5.0 woman zombie
|
1F9DF 200D 2640 FE0F ; fully-qualified # 🧟♀️ E5.0 woman zombie
|
||||||
1F9DF 200D 2640 ; minimally-qualified # 🧟♀ E5.0 woman zombie
|
1F9DF 200D 2640 ; minimally-qualified # 🧟♀ E5.0 woman zombie
|
||||||
|
1F9CC ; fully-qualified # 🧌 E14.0 troll
|
||||||
|
|
||||||
# subgroup: person-activity
|
# subgroup: person-activity
|
||||||
1F486 ; fully-qualified # 💆 E0.6 person getting massage
|
1F486 ; fully-qualified # 💆 E0.6 person getting massage
|
||||||
|
@ -3168,8 +3262,8 @@
|
||||||
1FAC2 ; fully-qualified # 🫂 E13.0 people hugging
|
1FAC2 ; fully-qualified # 🫂 E13.0 people hugging
|
||||||
1F463 ; fully-qualified # 👣 E0.6 footprints
|
1F463 ; fully-qualified # 👣 E0.6 footprints
|
||||||
|
|
||||||
# People & Body subtotal: 2899
|
# People & Body subtotal: 2986
|
||||||
# People & Body subtotal: 494 w/o modifiers
|
# People & Body subtotal: 506 w/o modifiers
|
||||||
|
|
||||||
# group: Component
|
# group: Component
|
||||||
|
|
||||||
|
@ -3304,6 +3398,7 @@
|
||||||
1F988 ; fully-qualified # 🦈 E3.0 shark
|
1F988 ; fully-qualified # 🦈 E3.0 shark
|
||||||
1F419 ; fully-qualified # 🐙 E0.6 octopus
|
1F419 ; fully-qualified # 🐙 E0.6 octopus
|
||||||
1F41A ; fully-qualified # 🐚 E0.6 spiral shell
|
1F41A ; fully-qualified # 🐚 E0.6 spiral shell
|
||||||
|
1FAB8 ; fully-qualified # 🪸 E14.0 coral
|
||||||
|
|
||||||
# subgroup: animal-bug
|
# subgroup: animal-bug
|
||||||
1F40C ; fully-qualified # 🐌 E0.6 snail
|
1F40C ; fully-qualified # 🐌 E0.6 snail
|
||||||
|
@ -3329,6 +3424,7 @@
|
||||||
1F490 ; fully-qualified # 💐 E0.6 bouquet
|
1F490 ; fully-qualified # 💐 E0.6 bouquet
|
||||||
1F338 ; fully-qualified # 🌸 E0.6 cherry blossom
|
1F338 ; fully-qualified # 🌸 E0.6 cherry blossom
|
||||||
1F4AE ; fully-qualified # 💮 E0.6 white flower
|
1F4AE ; fully-qualified # 💮 E0.6 white flower
|
||||||
|
1FAB7 ; fully-qualified # 🪷 E14.0 lotus
|
||||||
1F3F5 FE0F ; fully-qualified # 🏵️ E0.7 rosette
|
1F3F5 FE0F ; fully-qualified # 🏵️ E0.7 rosette
|
||||||
1F3F5 ; unqualified # 🏵 E0.7 rosette
|
1F3F5 ; unqualified # 🏵 E0.7 rosette
|
||||||
1F339 ; fully-qualified # 🌹 E0.6 rose
|
1F339 ; fully-qualified # 🌹 E0.6 rose
|
||||||
|
@ -3353,9 +3449,11 @@
|
||||||
1F341 ; fully-qualified # 🍁 E0.6 maple leaf
|
1F341 ; fully-qualified # 🍁 E0.6 maple leaf
|
||||||
1F342 ; fully-qualified # 🍂 E0.6 fallen leaf
|
1F342 ; fully-qualified # 🍂 E0.6 fallen leaf
|
||||||
1F343 ; fully-qualified # 🍃 E0.6 leaf fluttering in wind
|
1F343 ; fully-qualified # 🍃 E0.6 leaf fluttering in wind
|
||||||
|
1FAB9 ; fully-qualified # 🪹 E14.0 empty nest
|
||||||
|
1FABA ; fully-qualified # 🪺 E14.0 nest with eggs
|
||||||
|
|
||||||
# Animals & Nature subtotal: 147
|
# Animals & Nature subtotal: 151
|
||||||
# Animals & Nature subtotal: 147 w/o modifiers
|
# Animals & Nature subtotal: 151 w/o modifiers
|
||||||
|
|
||||||
# group: Food & Drink
|
# group: Food & Drink
|
||||||
|
|
||||||
|
@ -3396,6 +3494,7 @@
|
||||||
1F9C5 ; fully-qualified # 🧅 E12.0 onion
|
1F9C5 ; fully-qualified # 🧅 E12.0 onion
|
||||||
1F344 ; fully-qualified # 🍄 E0.6 mushroom
|
1F344 ; fully-qualified # 🍄 E0.6 mushroom
|
||||||
1F95C ; fully-qualified # 🥜 E3.0 peanuts
|
1F95C ; fully-qualified # 🥜 E3.0 peanuts
|
||||||
|
1FAD8 ; fully-qualified # 🫘 E14.0 beans
|
||||||
1F330 ; fully-qualified # 🌰 E0.6 chestnut
|
1F330 ; fully-qualified # 🌰 E0.6 chestnut
|
||||||
|
|
||||||
# subgroup: food-prepared
|
# subgroup: food-prepared
|
||||||
|
@ -3491,6 +3590,7 @@
|
||||||
1F37B ; fully-qualified # 🍻 E0.6 clinking beer mugs
|
1F37B ; fully-qualified # 🍻 E0.6 clinking beer mugs
|
||||||
1F942 ; fully-qualified # 🥂 E3.0 clinking glasses
|
1F942 ; fully-qualified # 🥂 E3.0 clinking glasses
|
||||||
1F943 ; fully-qualified # 🥃 E3.0 tumbler glass
|
1F943 ; fully-qualified # 🥃 E3.0 tumbler glass
|
||||||
|
1FAD7 ; fully-qualified # 🫗 E14.0 pouring liquid
|
||||||
1F964 ; fully-qualified # 🥤 E5.0 cup with straw
|
1F964 ; fully-qualified # 🥤 E5.0 cup with straw
|
||||||
1F9CB ; fully-qualified # 🧋 E13.0 bubble tea
|
1F9CB ; fully-qualified # 🧋 E13.0 bubble tea
|
||||||
1F9C3 ; fully-qualified # 🧃 E12.0 beverage box
|
1F9C3 ; fully-qualified # 🧃 E12.0 beverage box
|
||||||
|
@ -3504,10 +3604,11 @@
|
||||||
1F374 ; fully-qualified # 🍴 E0.6 fork and knife
|
1F374 ; fully-qualified # 🍴 E0.6 fork and knife
|
||||||
1F944 ; fully-qualified # 🥄 E3.0 spoon
|
1F944 ; fully-qualified # 🥄 E3.0 spoon
|
||||||
1F52A ; fully-qualified # 🔪 E0.6 kitchen knife
|
1F52A ; fully-qualified # 🔪 E0.6 kitchen knife
|
||||||
|
1FAD9 ; fully-qualified # 🫙 E14.0 jar
|
||||||
1F3FA ; fully-qualified # 🏺 E1.0 amphora
|
1F3FA ; fully-qualified # 🏺 E1.0 amphora
|
||||||
|
|
||||||
# Food & Drink subtotal: 131
|
# Food & Drink subtotal: 134
|
||||||
# Food & Drink subtotal: 131 w/o modifiers
|
# Food & Drink subtotal: 134 w/o modifiers
|
||||||
|
|
||||||
# group: Travel & Places
|
# group: Travel & Places
|
||||||
|
|
||||||
|
@ -3597,6 +3698,7 @@
|
||||||
2668 FE0F ; fully-qualified # ♨️ E0.6 hot springs
|
2668 FE0F ; fully-qualified # ♨️ E0.6 hot springs
|
||||||
2668 ; unqualified # ♨ E0.6 hot springs
|
2668 ; unqualified # ♨ E0.6 hot springs
|
||||||
1F3A0 ; fully-qualified # 🎠 E0.6 carousel horse
|
1F3A0 ; fully-qualified # 🎠 E0.6 carousel horse
|
||||||
|
1F6DD ; fully-qualified # 🛝 E14.0 playground slide
|
||||||
1F3A1 ; fully-qualified # 🎡 E0.6 ferris wheel
|
1F3A1 ; fully-qualified # 🎡 E0.6 ferris wheel
|
||||||
1F3A2 ; fully-qualified # 🎢 E0.6 roller coaster
|
1F3A2 ; fully-qualified # 🎢 E0.6 roller coaster
|
||||||
1F488 ; fully-qualified # 💈 E0.6 barber pole
|
1F488 ; fully-qualified # 💈 E0.6 barber pole
|
||||||
|
@ -3652,6 +3754,7 @@
|
||||||
1F6E2 FE0F ; fully-qualified # 🛢️ E0.7 oil drum
|
1F6E2 FE0F ; fully-qualified # 🛢️ E0.7 oil drum
|
||||||
1F6E2 ; unqualified # 🛢 E0.7 oil drum
|
1F6E2 ; unqualified # 🛢 E0.7 oil drum
|
||||||
26FD ; fully-qualified # ⛽ E0.6 fuel pump
|
26FD ; fully-qualified # ⛽ E0.6 fuel pump
|
||||||
|
1F6DE ; fully-qualified # 🛞 E14.0 wheel
|
||||||
1F6A8 ; fully-qualified # 🚨 E0.6 police car light
|
1F6A8 ; fully-qualified # 🚨 E0.6 police car light
|
||||||
1F6A5 ; fully-qualified # 🚥 E0.6 horizontal traffic light
|
1F6A5 ; fully-qualified # 🚥 E0.6 horizontal traffic light
|
||||||
1F6A6 ; fully-qualified # 🚦 E1.0 vertical traffic light
|
1F6A6 ; fully-qualified # 🚦 E1.0 vertical traffic light
|
||||||
|
@ -3660,6 +3763,7 @@
|
||||||
|
|
||||||
# subgroup: transport-water
|
# subgroup: transport-water
|
||||||
2693 ; fully-qualified # ⚓ E0.6 anchor
|
2693 ; fully-qualified # ⚓ E0.6 anchor
|
||||||
|
1F6DF ; fully-qualified # 🛟 E14.0 ring buoy
|
||||||
26F5 ; fully-qualified # ⛵ E0.6 sailboat
|
26F5 ; fully-qualified # ⛵ E0.6 sailboat
|
||||||
1F6F6 ; fully-qualified # 🛶 E3.0 canoe
|
1F6F6 ; fully-qualified # 🛶 E3.0 canoe
|
||||||
1F6A4 ; fully-qualified # 🚤 E0.6 speedboat
|
1F6A4 ; fully-qualified # 🚤 E0.6 speedboat
|
||||||
|
@ -3797,8 +3901,8 @@
|
||||||
1F4A7 ; fully-qualified # 💧 E0.6 droplet
|
1F4A7 ; fully-qualified # 💧 E0.6 droplet
|
||||||
1F30A ; fully-qualified # 🌊 E0.6 water wave
|
1F30A ; fully-qualified # 🌊 E0.6 water wave
|
||||||
|
|
||||||
# Travel & Places subtotal: 264
|
# Travel & Places subtotal: 267
|
||||||
# Travel & Places subtotal: 264 w/o modifiers
|
# Travel & Places subtotal: 267 w/o modifiers
|
||||||
|
|
||||||
# group: Activities
|
# group: Activities
|
||||||
|
|
||||||
|
@ -3874,6 +3978,7 @@
|
||||||
1F52E ; fully-qualified # 🔮 E0.6 crystal ball
|
1F52E ; fully-qualified # 🔮 E0.6 crystal ball
|
||||||
1FA84 ; fully-qualified # 🪄 E13.0 magic wand
|
1FA84 ; fully-qualified # 🪄 E13.0 magic wand
|
||||||
1F9FF ; fully-qualified # 🧿 E11.0 nazar amulet
|
1F9FF ; fully-qualified # 🧿 E11.0 nazar amulet
|
||||||
|
1FAAC ; fully-qualified # 🪬 E14.0 hamsa
|
||||||
1F3AE ; fully-qualified # 🎮 E0.6 video game
|
1F3AE ; fully-qualified # 🎮 E0.6 video game
|
||||||
1F579 FE0F ; fully-qualified # 🕹️ E0.7 joystick
|
1F579 FE0F ; fully-qualified # 🕹️ E0.7 joystick
|
||||||
1F579 ; unqualified # 🕹 E0.7 joystick
|
1F579 ; unqualified # 🕹 E0.7 joystick
|
||||||
|
@ -3882,6 +3987,7 @@
|
||||||
1F9E9 ; fully-qualified # 🧩 E11.0 puzzle piece
|
1F9E9 ; fully-qualified # 🧩 E11.0 puzzle piece
|
||||||
1F9F8 ; fully-qualified # 🧸 E11.0 teddy bear
|
1F9F8 ; fully-qualified # 🧸 E11.0 teddy bear
|
||||||
1FA85 ; fully-qualified # 🪅 E13.0 piñata
|
1FA85 ; fully-qualified # 🪅 E13.0 piñata
|
||||||
|
1FAA9 ; fully-qualified # 🪩 E14.0 mirror ball
|
||||||
1FA86 ; fully-qualified # 🪆 E13.0 nesting dolls
|
1FA86 ; fully-qualified # 🪆 E13.0 nesting dolls
|
||||||
2660 FE0F ; fully-qualified # ♠️ E0.6 spade suit
|
2660 FE0F ; fully-qualified # ♠️ E0.6 spade suit
|
||||||
2660 ; unqualified # ♠ E0.6 spade suit
|
2660 ; unqualified # ♠ E0.6 spade suit
|
||||||
|
@ -3907,8 +4013,8 @@
|
||||||
1F9F6 ; fully-qualified # 🧶 E11.0 yarn
|
1F9F6 ; fully-qualified # 🧶 E11.0 yarn
|
||||||
1FAA2 ; fully-qualified # 🪢 E13.0 knot
|
1FAA2 ; fully-qualified # 🪢 E13.0 knot
|
||||||
|
|
||||||
# Activities subtotal: 95
|
# Activities subtotal: 97
|
||||||
# Activities subtotal: 95 w/o modifiers
|
# Activities subtotal: 97 w/o modifiers
|
||||||
|
|
||||||
# group: Objects
|
# group: Objects
|
||||||
|
|
||||||
|
@ -4009,6 +4115,7 @@
|
||||||
|
|
||||||
# subgroup: computer
|
# subgroup: computer
|
||||||
1F50B ; fully-qualified # 🔋 E0.6 battery
|
1F50B ; fully-qualified # 🔋 E0.6 battery
|
||||||
|
1FAAB ; fully-qualified # 🪫 E14.0 low battery
|
||||||
1F50C ; fully-qualified # 🔌 E0.6 electric plug
|
1F50C ; fully-qualified # 🔌 E0.6 electric plug
|
||||||
1F4BB ; fully-qualified # 💻 E0.6 laptop
|
1F4BB ; fully-qualified # 💻 E0.6 laptop
|
||||||
1F5A5 FE0F ; fully-qualified # 🖥️ E0.7 desktop computer
|
1F5A5 FE0F ; fully-qualified # 🖥️ E0.7 desktop computer
|
||||||
|
@ -4207,7 +4314,9 @@
|
||||||
1FA78 ; fully-qualified # 🩸 E12.0 drop of blood
|
1FA78 ; fully-qualified # 🩸 E12.0 drop of blood
|
||||||
1F48A ; fully-qualified # 💊 E0.6 pill
|
1F48A ; fully-qualified # 💊 E0.6 pill
|
||||||
1FA79 ; fully-qualified # 🩹 E12.0 adhesive bandage
|
1FA79 ; fully-qualified # 🩹 E12.0 adhesive bandage
|
||||||
|
1FA7C ; fully-qualified # 🩼 E14.0 crutch
|
||||||
1FA7A ; fully-qualified # 🩺 E12.0 stethoscope
|
1FA7A ; fully-qualified # 🩺 E12.0 stethoscope
|
||||||
|
1FA7B ; fully-qualified # 🩻 E14.0 x-ray
|
||||||
|
|
||||||
# subgroup: household
|
# subgroup: household
|
||||||
1F6AA ; fully-qualified # 🚪 E0.6 door
|
1F6AA ; fully-qualified # 🚪 E0.6 door
|
||||||
|
@ -4232,6 +4341,7 @@
|
||||||
1F9FB ; fully-qualified # 🧻 E11.0 roll of paper
|
1F9FB ; fully-qualified # 🧻 E11.0 roll of paper
|
||||||
1FAA3 ; fully-qualified # 🪣 E13.0 bucket
|
1FAA3 ; fully-qualified # 🪣 E13.0 bucket
|
||||||
1F9FC ; fully-qualified # 🧼 E11.0 soap
|
1F9FC ; fully-qualified # 🧼 E11.0 soap
|
||||||
|
1FAE7 ; fully-qualified # 🫧 E14.0 bubbles
|
||||||
1FAA5 ; fully-qualified # 🪥 E13.0 toothbrush
|
1FAA5 ; fully-qualified # 🪥 E13.0 toothbrush
|
||||||
1F9FD ; fully-qualified # 🧽 E11.0 sponge
|
1F9FD ; fully-qualified # 🧽 E11.0 sponge
|
||||||
1F9EF ; fully-qualified # 🧯 E11.0 fire extinguisher
|
1F9EF ; fully-qualified # 🧯 E11.0 fire extinguisher
|
||||||
|
@ -4246,9 +4356,10 @@
|
||||||
26B1 ; unqualified # ⚱ E1.0 funeral urn
|
26B1 ; unqualified # ⚱ E1.0 funeral urn
|
||||||
1F5FF ; fully-qualified # 🗿 E0.6 moai
|
1F5FF ; fully-qualified # 🗿 E0.6 moai
|
||||||
1FAA7 ; fully-qualified # 🪧 E13.0 placard
|
1FAA7 ; fully-qualified # 🪧 E13.0 placard
|
||||||
|
1FAAA ; fully-qualified # 🪪 E14.0 identification card
|
||||||
|
|
||||||
# Objects subtotal: 299
|
# Objects subtotal: 304
|
||||||
# Objects subtotal: 299 w/o modifiers
|
# Objects subtotal: 304 w/o modifiers
|
||||||
|
|
||||||
# group: Symbols
|
# group: Symbols
|
||||||
|
|
||||||
|
@ -4409,6 +4520,7 @@
|
||||||
2795 ; fully-qualified # ➕ E0.6 plus
|
2795 ; fully-qualified # ➕ E0.6 plus
|
||||||
2796 ; fully-qualified # ➖ E0.6 minus
|
2796 ; fully-qualified # ➖ E0.6 minus
|
||||||
2797 ; fully-qualified # ➗ E0.6 divide
|
2797 ; fully-qualified # ➗ E0.6 divide
|
||||||
|
1F7F0 ; fully-qualified # 🟰 E14.0 heavy equals sign
|
||||||
267E FE0F ; fully-qualified # ♾️ E11.0 infinity
|
267E FE0F ; fully-qualified # ♾️ E11.0 infinity
|
||||||
267E ; unqualified # ♾ E11.0 infinity
|
267E ; unqualified # ♾ E11.0 infinity
|
||||||
|
|
||||||
|
@ -4581,8 +4693,8 @@
|
||||||
1F533 ; fully-qualified # 🔳 E0.6 white square button
|
1F533 ; fully-qualified # 🔳 E0.6 white square button
|
||||||
1F532 ; fully-qualified # 🔲 E0.6 black square button
|
1F532 ; fully-qualified # 🔲 E0.6 black square button
|
||||||
|
|
||||||
# Symbols subtotal: 301
|
# Symbols subtotal: 302
|
||||||
# Symbols subtotal: 301 w/o modifiers
|
# Symbols subtotal: 302 w/o modifiers
|
||||||
|
|
||||||
# group: Flags
|
# group: Flags
|
||||||
|
|
||||||
|
@ -4871,7 +4983,7 @@
|
||||||
# Flags subtotal: 275 w/o modifiers
|
# Flags subtotal: 275 w/o modifiers
|
||||||
|
|
||||||
# Status Counts
|
# Status Counts
|
||||||
# fully-qualified : 3512
|
# fully-qualified : 3624
|
||||||
# minimally-qualified : 817
|
# minimally-qualified : 817
|
||||||
# unqualified : 252
|
# unqualified : 252
|
||||||
# component : 9
|
# component : 9
|
||||||
|
|
|
@ -61,7 +61,6 @@ def get_or_create_by_names(names) when is_list(names) do
|
||||||
{:ok, Repo.all(from(ht in Hashtag, where: ht.name in ^names))}
|
{:ok, Repo.all(from(ht in Hashtag, where: ht.name in ^names))}
|
||||||
end)
|
end)
|
||||||
|> Repo.transaction() do
|
|> Repo.transaction() do
|
||||||
Pleroma.Elasticsearch.maybe_bulk_post(hashtags, :hashtags)
|
|
||||||
{:ok, hashtags}
|
{:ok, hashtags}
|
||||||
else
|
else
|
||||||
{:error, _name, value, _changes_so_far} -> {:error, value}
|
{:error, _name, value, _changes_so_far} -> {:error, value}
|
||||||
|
|
|
@ -341,6 +341,14 @@ def destroy_multiple(%{id: user_id} = _user, ids) do
|
||||||
|> Repo.delete_all()
|
|> Repo.delete_all()
|
||||||
end
|
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
|
def dismiss(%Pleroma.Activity{} = activity) do
|
||||||
Notification
|
Notification
|
||||||
|> where([n], n.activity_id == ^activity.id)
|
|> where([n], n.activity_id == ^activity.id)
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
defmodule Pleroma.Search do
|
defmodule Pleroma.Search do
|
||||||
@type search_map :: %{
|
alias Pleroma.Workers.SearchIndexingWorker
|
||||||
statuses: [map],
|
|
||||||
accounts: [map],
|
|
||||||
hashtags: [map]
|
|
||||||
}
|
|
||||||
|
|
||||||
@doc """
|
def add_to_index(%Pleroma.Activity{id: activity_id}) do
|
||||||
Searches for stuff
|
SearchIndexingWorker.enqueue("add_to_index", %{"activity" => activity_id})
|
||||||
"""
|
end
|
||||||
@callback search(map, map, keyword) :: search_map
|
|
||||||
|
def remove_from_index(%Pleroma.Object{id: object_id}) do
|
||||||
|
SearchIndexingWorker.enqueue("remove_from_index", %{"object" => object_id})
|
||||||
|
end
|
||||||
|
|
||||||
|
def search(query, options) do
|
||||||
|
search_module = Pleroma.Config.get([Pleroma.Search, :module], Pleroma.Activity)
|
||||||
|
|
||||||
|
search_module.search(options[:for_user], query, options)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,138 +0,0 @@
|
||||||
defmodule Pleroma.Search.Builtin do
|
|
||||||
@behaviour Pleroma.Search
|
|
||||||
|
|
||||||
alias Pleroma.Repo
|
|
||||||
alias Pleroma.User
|
|
||||||
alias Pleroma.Activity
|
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
|
||||||
alias Pleroma.Web.Endpoint
|
|
||||||
|
|
||||||
require Logger
|
|
||||||
|
|
||||||
@impl Pleroma.Search
|
|
||||||
def search(_conn, %{q: query} = params, options) do
|
|
||||||
version = Keyword.get(options, :version)
|
|
||||||
timeout = Keyword.get(Repo.config(), :timeout, 15_000)
|
|
||||||
query = String.trim(query)
|
|
||||||
default_values = %{"statuses" => [], "accounts" => [], "hashtags" => []}
|
|
||||||
|
|
||||||
default_values
|
|
||||||
|> Enum.map(fn {resource, default_value} ->
|
|
||||||
if params[:type] in [nil, resource] do
|
|
||||||
{resource, fn -> resource_search(version, resource, query, options) end}
|
|
||||||
else
|
|
||||||
{resource, fn -> default_value end}
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|> Task.async_stream(fn {resource, f} -> {resource, with_fallback(f)} end,
|
|
||||||
timeout: timeout,
|
|
||||||
on_timeout: :kill_task
|
|
||||||
)
|
|
||||||
|> Enum.reduce(default_values, fn
|
|
||||||
{:ok, {resource, result}}, acc ->
|
|
||||||
Map.put(acc, resource, result)
|
|
||||||
|
|
||||||
_error, acc ->
|
|
||||||
acc
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp resource_search(_, "accounts", query, options) do
|
|
||||||
accounts = with_fallback(fn -> User.search(query, options) end)
|
|
||||||
|
|
||||||
AccountView.render("index.json",
|
|
||||||
users: accounts,
|
|
||||||
for: options[:for_user],
|
|
||||||
embed_relationships: options[:embed_relationships]
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp resource_search(_, "statuses", query, options) do
|
|
||||||
statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end)
|
|
||||||
|
|
||||||
StatusView.render("index.json",
|
|
||||||
activities: statuses,
|
|
||||||
for: options[:for_user],
|
|
||||||
as: :activity
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp resource_search(:v2, "hashtags", query, options) do
|
|
||||||
tags_path = Endpoint.url() <> "/tag/"
|
|
||||||
|
|
||||||
query
|
|
||||||
|> prepare_tags(options)
|
|
||||||
|> Enum.map(fn tag ->
|
|
||||||
%{name: tag, url: tags_path <> tag}
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp resource_search(:v1, "hashtags", query, options) do
|
|
||||||
prepare_tags(query, options)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp prepare_tags(query, options) do
|
|
||||||
tags =
|
|
||||||
query
|
|
||||||
|> preprocess_uri_query()
|
|
||||||
|> String.split(~r/[^#\w]+/u, trim: true)
|
|
||||||
|> Enum.uniq_by(&String.downcase/1)
|
|
||||||
|
|
||||||
explicit_tags = Enum.filter(tags, fn tag -> String.starts_with?(tag, "#") end)
|
|
||||||
|
|
||||||
tags =
|
|
||||||
if Enum.any?(explicit_tags) do
|
|
||||||
explicit_tags
|
|
||||||
else
|
|
||||||
tags
|
|
||||||
end
|
|
||||||
|
|
||||||
tags = Enum.map(tags, fn tag -> String.trim_leading(tag, "#") end)
|
|
||||||
|
|
||||||
tags =
|
|
||||||
if Enum.empty?(explicit_tags) && !options[:skip_joined_tag] do
|
|
||||||
add_joined_tag(tags)
|
|
||||||
else
|
|
||||||
tags
|
|
||||||
end
|
|
||||||
|
|
||||||
Pleroma.Pagination.paginate(tags, options)
|
|
||||||
end
|
|
||||||
|
|
||||||
# If `query` is a URI, returns last component of its path, otherwise returns `query`
|
|
||||||
defp preprocess_uri_query(query) do
|
|
||||||
if query =~ ~r/https?:\/\// do
|
|
||||||
query
|
|
||||||
|> String.trim_trailing("/")
|
|
||||||
|> URI.parse()
|
|
||||||
|> Map.get(:path)
|
|
||||||
|> String.split("/")
|
|
||||||
|> Enum.at(-1)
|
|
||||||
else
|
|
||||||
query
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp add_joined_tag(tags) do
|
|
||||||
tags
|
|
||||||
|> Kernel.++([joined_tag(tags)])
|
|
||||||
|> Enum.uniq_by(&String.downcase/1)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp joined_tag(tags) do
|
|
||||||
tags
|
|
||||||
|> Enum.map(fn tag -> String.capitalize(tag) end)
|
|
||||||
|> Enum.join()
|
|
||||||
end
|
|
||||||
|
|
||||||
defp with_fallback(f, fallback \\ []) do
|
|
||||||
try do
|
|
||||||
f.()
|
|
||||||
rescue
|
|
||||||
error ->
|
|
||||||
Logger.error("#{__MODULE__} search error: #{inspect(error)}")
|
|
||||||
fallback
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -2,7 +2,7 @@
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Activity.Search do
|
defmodule Pleroma.Search.DatabaseSearch do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Object.Fetcher
|
alias Pleroma.Object.Fetcher
|
||||||
alias Pleroma.Pagination
|
alias Pleroma.Pagination
|
||||||
|
@ -13,6 +13,8 @@ defmodule Pleroma.Activity.Search do
|
||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
|
@behaviour Pleroma.Search.SearchBackend
|
||||||
|
|
||||||
def search(user, search_query, options \\ []) do
|
def search(user, search_query, options \\ []) do
|
||||||
index_type = if Pleroma.Config.get([:database, :rum_enabled]), do: :rum, else: :gin
|
index_type = if Pleroma.Config.get([:database, :rum_enabled]), do: :rum, else: :gin
|
||||||
limit = Enum.min([Keyword.get(options, :limit), 40])
|
limit = Enum.min([Keyword.get(options, :limit), 40])
|
||||||
|
@ -45,6 +47,12 @@ def search(user, search_query, options \\ []) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def add_to_index(_activity), do: nil
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def remove_from_index(_object), do: nil
|
||||||
|
|
||||||
def maybe_restrict_author(query, %User{} = author) do
|
def maybe_restrict_author(query, %User{} = author) do
|
||||||
Activity.Queries.by_author(query, author)
|
Activity.Queries.by_author(query, author)
|
||||||
end
|
end
|
||||||
|
@ -57,7 +65,7 @@ def maybe_restrict_blocked(query, %User{} = user) do
|
||||||
|
|
||||||
def maybe_restrict_blocked(query, _), do: query
|
def maybe_restrict_blocked(query, _), do: query
|
||||||
|
|
||||||
defp restrict_public(q) do
|
def restrict_public(q) do
|
||||||
from([a, o] in q,
|
from([a, o] in q,
|
||||||
where: fragment("?->>'type' = 'Create'", a.data),
|
where: fragment("?->>'type' = 'Create'", a.data),
|
||||||
where: ^Pleroma.Constants.as_public() in a.recipients
|
where: ^Pleroma.Constants.as_public() in a.recipients
|
||||||
|
@ -124,7 +132,7 @@ defp query_with(q, :rum, search_query, :websearch) do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_restrict_local(q, user) do
|
def maybe_restrict_local(q, user) do
|
||||||
limit = Pleroma.Config.get([:instance, :limit_to_local_content], :unauthenticated)
|
limit = Pleroma.Config.get([:instance, :limit_to_local_content], :unauthenticated)
|
||||||
|
|
||||||
case {limit, user} do
|
case {limit, user} do
|
||||||
|
@ -137,7 +145,7 @@ defp maybe_restrict_local(q, user) do
|
||||||
|
|
||||||
defp restrict_local(q), do: where(q, local: true)
|
defp restrict_local(q), do: where(q, local: true)
|
||||||
|
|
||||||
defp maybe_fetch(activities, user, search_query) do
|
def maybe_fetch(activities, user, search_query) do
|
||||||
with true <- Regex.match?(~r/https?:/, search_query),
|
with true <- Regex.match?(~r/https?:/, search_query),
|
||||||
{:ok, object} <- Fetcher.fetch_object_from_id(search_query),
|
{:ok, object} <- Fetcher.fetch_object_from_id(search_query),
|
||||||
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
|
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
|
|
@ -3,24 +3,22 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Search.Elasticsearch do
|
defmodule Pleroma.Search.Elasticsearch do
|
||||||
@behaviour Pleroma.Search
|
@behaviour Pleroma.Search.SearchBackend
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Object.Fetcher
|
alias Pleroma.Object.Fetcher
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
alias Pleroma.Search.Elasticsearch.Parsers
|
alias Pleroma.Search.Elasticsearch.Parsers
|
||||||
alias Pleroma.Web.Endpoint
|
|
||||||
|
|
||||||
def es_query(:activity, query) do
|
def es_query(:activity, query, offset, limit) do
|
||||||
must = Parsers.Activity.parse(query)
|
must = Parsers.Activity.parse(query)
|
||||||
|
|
||||||
if must == [] do
|
if must == [] do
|
||||||
:skip
|
:skip
|
||||||
else
|
else
|
||||||
%{
|
%{
|
||||||
size: 50,
|
size: limit,
|
||||||
|
from: offset,
|
||||||
terminate_after: 50,
|
terminate_after: 50,
|
||||||
timeout: "5s",
|
timeout: "5s",
|
||||||
sort: [
|
sort: [
|
||||||
|
@ -36,50 +34,6 @@ def es_query(:activity, query) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def es_query(:user, query) do
|
|
||||||
must = Parsers.User.parse(query)
|
|
||||||
|
|
||||||
if must == [] do
|
|
||||||
:skip
|
|
||||||
else
|
|
||||||
%{
|
|
||||||
size: 50,
|
|
||||||
terminate_after: 50,
|
|
||||||
timeout: "5s",
|
|
||||||
sort: [
|
|
||||||
"_score"
|
|
||||||
],
|
|
||||||
query: %{
|
|
||||||
bool: %{
|
|
||||||
must: must
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def es_query(:hashtag, query) do
|
|
||||||
must = Parsers.Hashtag.parse(query)
|
|
||||||
|
|
||||||
if must == [] do
|
|
||||||
:skip
|
|
||||||
else
|
|
||||||
%{
|
|
||||||
size: 50,
|
|
||||||
terminate_after: 50,
|
|
||||||
timeout: "5s",
|
|
||||||
sort: [
|
|
||||||
"_score"
|
|
||||||
],
|
|
||||||
query: %{
|
|
||||||
bool: %{
|
|
||||||
must: Parsers.Hashtag.parse(query)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp maybe_fetch(:activity, search_query) do
|
defp maybe_fetch(:activity, search_query) do
|
||||||
with true <- Regex.match?(~r/https?:/, search_query),
|
with true <- Regex.match?(~r/https?:/, search_query),
|
||||||
{:ok, object} <- Fetcher.fetch_object_from_id(search_query),
|
{:ok, object} <- Fetcher.fetch_object_from_id(search_query),
|
||||||
|
@ -90,8 +44,10 @@ defp maybe_fetch(:activity, search_query) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl Pleroma.Search
|
def search(user, query, options) do
|
||||||
def search(%{assigns: %{user: user}} = _conn, %{q: query} = _params, _options) do
|
limit = Enum.min([Keyword.get(options, :limit), 40])
|
||||||
|
offset = Keyword.get(options, :offset, 0)
|
||||||
|
|
||||||
parsed_query =
|
parsed_query =
|
||||||
query
|
query
|
||||||
|> String.trim()
|
|> String.trim()
|
||||||
|
@ -104,30 +60,13 @@ def search(%{assigns: %{user: user}} = _conn, %{q: query} = _params, _options) d
|
||||||
|
|
||||||
activity_task =
|
activity_task =
|
||||||
Task.async(fn ->
|
Task.async(fn ->
|
||||||
q = es_query(:activity, parsed_query)
|
q = es_query(:activity, parsed_query, offset, limit)
|
||||||
|
|
||||||
Pleroma.Elasticsearch.search(:activities, q)
|
Pleroma.Search.Elasticsearch.Store.search(:activities, q)
|
||||||
|> Enum.filter(fn x -> Visibility.visible_for_user?(x, user) end)
|
|> Enum.filter(fn x -> Visibility.visible_for_user?(x, user) end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
user_task =
|
|
||||||
Task.async(fn ->
|
|
||||||
q = es_query(:user, parsed_query)
|
|
||||||
|
|
||||||
Pleroma.Elasticsearch.search(:users, q)
|
|
||||||
|> Enum.filter(fn x -> Pleroma.User.visible_for(x, user) == :visible end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
hashtag_task =
|
|
||||||
Task.async(fn ->
|
|
||||||
q = es_query(:hashtag, parsed_query)
|
|
||||||
|
|
||||||
Pleroma.Elasticsearch.search(:hashtags, q)
|
|
||||||
end)
|
|
||||||
|
|
||||||
activity_results = Task.await(activity_task)
|
activity_results = Task.await(activity_task)
|
||||||
user_results = Task.await(user_task)
|
|
||||||
hashtag_results = Task.await(hashtag_task)
|
|
||||||
direct_activity = Task.await(activity_fetch_task)
|
direct_activity = Task.await(activity_fetch_task)
|
||||||
|
|
||||||
activity_results =
|
activity_results =
|
||||||
|
@ -137,25 +76,16 @@ def search(%{assigns: %{user: user}} = _conn, %{q: query} = _params, _options) d
|
||||||
[direct_activity | activity_results]
|
[direct_activity | activity_results]
|
||||||
end
|
end
|
||||||
|
|
||||||
%{
|
activity_results
|
||||||
"accounts" =>
|
end
|
||||||
AccountView.render("index.json",
|
|
||||||
users: user_results,
|
@impl true
|
||||||
for: user
|
def add_to_index(activity) do
|
||||||
),
|
Elasticsearch.put_document(Pleroma.Search.Elasticsearch.Cluster, activity, "activities")
|
||||||
"hashtags" =>
|
end
|
||||||
Enum.map(hashtag_results, fn x ->
|
|
||||||
%{
|
@impl true
|
||||||
url: Endpoint.url() <> "/tag/" <> x,
|
def remove_from_index(object) do
|
||||||
name: x
|
Elasticsearch.delete_document(Pleroma.Search.Elasticsearch.Cluster, object, "activities")
|
||||||
}
|
|
||||||
end),
|
|
||||||
"statuses" =>
|
|
||||||
StatusView.render("index.json",
|
|
||||||
activities: activity_results,
|
|
||||||
for: user,
|
|
||||||
as: :activity
|
|
||||||
)
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
4
lib/pleroma/search/elasticsearch/cluster.ex
Normal file
4
lib/pleroma/search/elasticsearch/cluster.ex
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
defmodule Pleroma.Search.Elasticsearch.Cluster do
|
||||||
|
@moduledoc false
|
||||||
|
use Elasticsearch.Cluster, otp_app: :pleroma
|
||||||
|
end
|
|
@ -0,0 +1,61 @@
|
||||||
|
# Akkoma: A lightweight social networking server
|
||||||
|
# Copyright © 2022-2022 Akkoma Authors <https://git.ihatebeinga.live/IHBAGang/akkoma/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defimpl Elasticsearch.Document, for: Pleroma.Activity do
|
||||||
|
alias Pleroma.Object
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
def id(obj), do: obj.id
|
||||||
|
def routing(_), do: false
|
||||||
|
|
||||||
|
def object_to_search_data(object) do
|
||||||
|
# Only index public or unlisted Notes
|
||||||
|
if not is_nil(object) and object.data["type"] == "Note" and
|
||||||
|
not is_nil(object.data["content"]) and
|
||||||
|
(Pleroma.Constants.as_public() in object.data["to"] or
|
||||||
|
Pleroma.Constants.as_public() in object.data["cc"]) and
|
||||||
|
String.length(object.data["content"]) > 1 do
|
||||||
|
data = object.data
|
||||||
|
|
||||||
|
content_str =
|
||||||
|
case data["content"] do
|
||||||
|
[nil | rest] -> to_string(rest)
|
||||||
|
str -> str
|
||||||
|
end
|
||||||
|
|
||||||
|
content =
|
||||||
|
with {:ok, scrubbed} <- FastSanitize.strip_tags(content_str),
|
||||||
|
trimmed <- String.trim(scrubbed) do
|
||||||
|
trimmed
|
||||||
|
end
|
||||||
|
|
||||||
|
if String.length(content) > 1 do
|
||||||
|
{:ok, published, _} = DateTime.from_iso8601(data["published"])
|
||||||
|
|
||||||
|
%{
|
||||||
|
_timestamp: published,
|
||||||
|
content: content,
|
||||||
|
instance: URI.parse(object.data["actor"]).host,
|
||||||
|
hashtags: Object.hashtags(object),
|
||||||
|
user: Pleroma.User.get_cached_by_ap_id(object.data["actor"]).nickname
|
||||||
|
}
|
||||||
|
else
|
||||||
|
%{}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
%{}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def encode(activity) do
|
||||||
|
object = Pleroma.Object.normalize(activity)
|
||||||
|
object_to_search_data(object)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defimpl Elasticsearch.Document, for: Pleroma.Object do
|
||||||
|
def id(obj), do: obj.id
|
||||||
|
def routing(_), do: false
|
||||||
|
def encode(_), do: nil
|
||||||
|
end
|
|
@ -1,34 +0,0 @@
|
||||||
# Akkoma: A lightweight social networking server
|
|
||||||
# Copyright © 2022-2022 Akkoma Authors <https://git.ihatebeinga.live/IHBAGang/akkoma/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Search.Elasticsearch.Parsers.Hashtag do
|
|
||||||
defp to_es(term) when is_binary(term) do
|
|
||||||
%{
|
|
||||||
term: %{
|
|
||||||
hashtag: %{
|
|
||||||
value: String.downcase(term)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp to_es({:quoted, term}), do: to_es(term)
|
|
||||||
|
|
||||||
defp to_es({:filter, ["hashtag", query]}) do
|
|
||||||
%{
|
|
||||||
term: %{
|
|
||||||
hashtag: %{
|
|
||||||
value: String.downcase(query)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp to_es({:filter, _}), do: nil
|
|
||||||
|
|
||||||
def parse(q) do
|
|
||||||
Enum.map(q, &to_es/1)
|
|
||||||
|> Enum.filter(fn x -> x != nil end)
|
|
||||||
end
|
|
||||||
end
|
|
52
lib/pleroma/search/elasticsearch/store.ex
Normal file
52
lib/pleroma/search/elasticsearch/store.ex
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
# Akkoma: A lightweight social networking server
|
||||||
|
# Copyright © 2022-2022 Akkoma Authors <https://git.ihatebeinga.live/IHBAGang/akkoma/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Search.Elasticsearch.Store do
|
||||||
|
@behaviour Elasticsearch.Store
|
||||||
|
alias Pleroma.Search.Elasticsearch.Cluster
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
alias Pleroma.Repo
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def stream(schema) do
|
||||||
|
Repo.stream(schema)
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def transaction(fun) do
|
||||||
|
{:ok, result} = Repo.transaction(fun, timeout: :infinity)
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
def search(_, _, _, :skip), do: []
|
||||||
|
|
||||||
|
def search(:raw, index, q) do
|
||||||
|
with {:ok, raw_results} <- Elasticsearch.post(Cluster, "/#{index}/_search", q) do
|
||||||
|
results =
|
||||||
|
raw_results
|
||||||
|
|> Map.get("hits", %{})
|
||||||
|
|> Map.get("hits", [])
|
||||||
|
|
||||||
|
{:ok, results}
|
||||||
|
else
|
||||||
|
{:error, e} ->
|
||||||
|
Logger.error(e)
|
||||||
|
{:error, e}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def search(:activities, q) do
|
||||||
|
with {:ok, results} <- search(:raw, "activities", q) do
|
||||||
|
results
|
||||||
|
|> Enum.map(fn result -> result["_id"] end)
|
||||||
|
|> Pleroma.Activity.all_by_ids_with_object()
|
||||||
|
|> Enum.sort(&(&1.inserted_at >= &2.inserted_at))
|
||||||
|
else
|
||||||
|
e ->
|
||||||
|
Logger.error(e)
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,57 +0,0 @@
|
||||||
# Akkoma: A lightweight social networking server
|
|
||||||
# Copyright © 2022-2022 Akkoma Authors <https://git.ihatebeinga.live/IHBAGang/akkoma/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Search.Elasticsearch.Parsers.User do
|
|
||||||
defp to_es(term) when is_binary(term) do
|
|
||||||
%{
|
|
||||||
bool: %{
|
|
||||||
minimum_should_match: 1,
|
|
||||||
should: [
|
|
||||||
%{
|
|
||||||
match: %{
|
|
||||||
bio: %{
|
|
||||||
query: term,
|
|
||||||
operator: "AND"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
term: %{
|
|
||||||
nickname: %{
|
|
||||||
value: term
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
match: %{
|
|
||||||
display_name: %{
|
|
||||||
query: term,
|
|
||||||
operator: "AND"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp to_es({:quoted, term}), do: to_es(term)
|
|
||||||
|
|
||||||
defp to_es({:filter, ["user", query]}) do
|
|
||||||
%{
|
|
||||||
term: %{
|
|
||||||
nickname: %{
|
|
||||||
value: query
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp to_es({:filter, _}), do: nil
|
|
||||||
|
|
||||||
def parse(q) do
|
|
||||||
Enum.map(q, &to_es/1)
|
|
||||||
|> Enum.filter(fn x -> x != nil end)
|
|
||||||
end
|
|
||||||
end
|
|
169
lib/pleroma/search/meilisearch.ex
Normal file
169
lib/pleroma/search/meilisearch.ex
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
defmodule Pleroma.Search.Meilisearch do
|
||||||
|
require Logger
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
|
||||||
|
import Pleroma.Search.DatabaseSearch
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
@behaviour Pleroma.Search.SearchBackend
|
||||||
|
|
||||||
|
defp meili_headers do
|
||||||
|
private_key = Pleroma.Config.get([Pleroma.Search.Meilisearch, :private_key])
|
||||||
|
|
||||||
|
[{"Content-Type", "application/json"}] ++
|
||||||
|
if is_nil(private_key), do: [], else: [{"Authorization", "Bearer #{private_key}"}]
|
||||||
|
end
|
||||||
|
|
||||||
|
def meili_get(path) do
|
||||||
|
endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url])
|
||||||
|
|
||||||
|
result =
|
||||||
|
Pleroma.HTTP.get(
|
||||||
|
Path.join(endpoint, path),
|
||||||
|
meili_headers()
|
||||||
|
)
|
||||||
|
|
||||||
|
with {:ok, res} <- result do
|
||||||
|
{:ok, Jason.decode!(res.body)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def meili_post(path, params) do
|
||||||
|
endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url])
|
||||||
|
|
||||||
|
result =
|
||||||
|
Pleroma.HTTP.post(
|
||||||
|
Path.join(endpoint, path),
|
||||||
|
Jason.encode!(params),
|
||||||
|
meili_headers()
|
||||||
|
)
|
||||||
|
|
||||||
|
with {:ok, res} <- result do
|
||||||
|
{:ok, Jason.decode!(res.body)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def meili_put(path, params) do
|
||||||
|
endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url])
|
||||||
|
|
||||||
|
result =
|
||||||
|
Pleroma.HTTP.request(
|
||||||
|
:put,
|
||||||
|
Path.join(endpoint, path),
|
||||||
|
Jason.encode!(params),
|
||||||
|
meili_headers(),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
with {:ok, res} <- result do
|
||||||
|
{:ok, Jason.decode!(res.body)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def meili_delete!(path) do
|
||||||
|
endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url])
|
||||||
|
|
||||||
|
{:ok, _} =
|
||||||
|
Pleroma.HTTP.request(
|
||||||
|
:delete,
|
||||||
|
Path.join(endpoint, path),
|
||||||
|
"",
|
||||||
|
meili_headers(),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def search(user, query, options \\ []) do
|
||||||
|
limit = Enum.min([Keyword.get(options, :limit), 40])
|
||||||
|
offset = Keyword.get(options, :offset, 0)
|
||||||
|
author = Keyword.get(options, :author)
|
||||||
|
|
||||||
|
res =
|
||||||
|
meili_post(
|
||||||
|
"/indexes/objects/search",
|
||||||
|
%{q: query, offset: offset, limit: limit}
|
||||||
|
)
|
||||||
|
|
||||||
|
with {:ok, result} <- res do
|
||||||
|
hits = result["hits"] |> Enum.map(& &1["ap"])
|
||||||
|
|
||||||
|
try do
|
||||||
|
hits
|
||||||
|
|> Activity.create_by_object_ap_id()
|
||||||
|
|> Activity.with_preloaded_object()
|
||||||
|
|> Activity.with_preloaded_object()
|
||||||
|
|> Activity.restrict_deactivated_users()
|
||||||
|
|> maybe_restrict_local(user)
|
||||||
|
|> maybe_restrict_author(author)
|
||||||
|
|> maybe_restrict_blocked(user)
|
||||||
|
|> maybe_fetch(user, query)
|
||||||
|
|> order_by([object: obj], desc: obj.data["published"])
|
||||||
|
|> Pleroma.Repo.all()
|
||||||
|
rescue
|
||||||
|
_ -> maybe_fetch([], user, query)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def object_to_search_data(object) do
|
||||||
|
# Only index public or unlisted Notes
|
||||||
|
if not is_nil(object) and object.data["type"] == "Note" and
|
||||||
|
not is_nil(object.data["content"]) and
|
||||||
|
(Pleroma.Constants.as_public() in object.data["to"] or
|
||||||
|
Pleroma.Constants.as_public() in object.data["cc"]) and
|
||||||
|
String.length(object.data["content"]) > 1 do
|
||||||
|
data = object.data
|
||||||
|
|
||||||
|
content_str =
|
||||||
|
case data["content"] do
|
||||||
|
[nil | rest] -> to_string(rest)
|
||||||
|
str -> str
|
||||||
|
end
|
||||||
|
|
||||||
|
content =
|
||||||
|
with {:ok, scrubbed} <- FastSanitize.strip_tags(content_str),
|
||||||
|
trimmed <- String.trim(scrubbed) do
|
||||||
|
trimmed
|
||||||
|
end
|
||||||
|
|
||||||
|
if String.length(content) > 1 do
|
||||||
|
{:ok, published, _} = DateTime.from_iso8601(data["published"])
|
||||||
|
|
||||||
|
%{
|
||||||
|
id: object.id,
|
||||||
|
content: content,
|
||||||
|
ap: data["id"],
|
||||||
|
published: published |> DateTime.to_unix()
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def add_to_index(activity) do
|
||||||
|
maybe_search_data = object_to_search_data(activity.object)
|
||||||
|
|
||||||
|
if activity.data["type"] == "Create" and maybe_search_data do
|
||||||
|
result =
|
||||||
|
meili_put(
|
||||||
|
"/indexes/objects/documents",
|
||||||
|
[maybe_search_data]
|
||||||
|
)
|
||||||
|
|
||||||
|
with {:ok, res} <- result,
|
||||||
|
true <- Map.has_key?(res, "uid") do
|
||||||
|
# Do nothing
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
Logger.error("Failed to add activity #{activity.id} to index: #{inspect(result)}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def remove_from_index(object) do
|
||||||
|
meili_delete!("/indexes/objects/documents/#{object.id}")
|
||||||
|
end
|
||||||
|
end
|
17
lib/pleroma/search/search_backend.ex
Normal file
17
lib/pleroma/search/search_backend.ex
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
defmodule Pleroma.Search.SearchBackend do
|
||||||
|
@doc """
|
||||||
|
Add the object associated with the activity to the search index.
|
||||||
|
|
||||||
|
The whole activity is passed, to allow filtering on things such as scope.
|
||||||
|
"""
|
||||||
|
@callback add_to_index(activity :: Pleroma.Activity.t()) :: nil
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Remove the object from the index.
|
||||||
|
|
||||||
|
Just the object, as opposed to the whole activity, is passed, since the object
|
||||||
|
is what contains the actual content and there is no need for fitlering when removing
|
||||||
|
from index.
|
||||||
|
"""
|
||||||
|
@callback remove_from_index(object :: Pleroma.Object.t()) :: nil
|
||||||
|
end
|
|
@ -12,8 +12,7 @@ defmodule Pleroma.Telemetry.Logger do
|
||||||
[:pleroma, :connection_pool, :reclaim, :stop],
|
[:pleroma, :connection_pool, :reclaim, :stop],
|
||||||
[:pleroma, :connection_pool, :provision_failure],
|
[:pleroma, :connection_pool, :provision_failure],
|
||||||
[:pleroma, :connection_pool, :client, :dead],
|
[:pleroma, :connection_pool, :client, :dead],
|
||||||
[:pleroma, :connection_pool, :client, :add],
|
[:pleroma, :connection_pool, :client, :add]
|
||||||
[:pleroma, :repo, :query]
|
|
||||||
]
|
]
|
||||||
def attach do
|
def attach do
|
||||||
:telemetry.attach_many(
|
:telemetry.attach_many(
|
||||||
|
@ -93,64 +92,4 @@ def handle_event(
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event([:pleroma, :connection_pool, :client, :add], _, _, _), do: :ok
|
def handle_event([:pleroma, :connection_pool, :client, :add], _, _, _), do: :ok
|
||||||
|
|
||||||
def handle_event(
|
|
||||||
[:pleroma, :repo, :query] = _name,
|
|
||||||
%{query_time: query_time} = measurements,
|
|
||||||
%{source: source} = metadata,
|
|
||||||
config
|
|
||||||
) do
|
|
||||||
logging_config = Pleroma.Config.get([:telemetry, :slow_queries_logging], [])
|
|
||||||
|
|
||||||
if logging_config[:enabled] &&
|
|
||||||
logging_config[:min_duration] &&
|
|
||||||
query_time > logging_config[:min_duration] and
|
|
||||||
(is_nil(logging_config[:exclude_sources]) or
|
|
||||||
source not in logging_config[:exclude_sources]) do
|
|
||||||
log_slow_query(measurements, metadata, config)
|
|
||||||
else
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp log_slow_query(
|
|
||||||
%{query_time: query_time} = _measurements,
|
|
||||||
%{source: _source, query: query, params: query_params, repo: repo} = _metadata,
|
|
||||||
_config
|
|
||||||
) do
|
|
||||||
sql_explain =
|
|
||||||
with {:ok, %{rows: explain_result_rows}} <-
|
|
||||||
repo.query("EXPLAIN " <> query, query_params, log: false) do
|
|
||||||
Enum.map_join(explain_result_rows, "\n", & &1)
|
|
||||||
end
|
|
||||||
|
|
||||||
{:current_stacktrace, stacktrace} = Process.info(self(), :current_stacktrace)
|
|
||||||
|
|
||||||
pleroma_stacktrace =
|
|
||||||
Enum.filter(stacktrace, fn
|
|
||||||
{__MODULE__, _, _, _} ->
|
|
||||||
false
|
|
||||||
|
|
||||||
{mod, _, _, _} ->
|
|
||||||
mod
|
|
||||||
|> to_string()
|
|
||||||
|> String.starts_with?("Elixir.Pleroma.")
|
|
||||||
end)
|
|
||||||
|
|
||||||
Logger.warn(fn ->
|
|
||||||
"""
|
|
||||||
Slow query!
|
|
||||||
|
|
||||||
Total time: #{round(query_time / 1_000)} ms
|
|
||||||
|
|
||||||
#{query}
|
|
||||||
|
|
||||||
#{inspect(query_params, limit: :infinity)}
|
|
||||||
|
|
||||||
#{sql_explain}
|
|
||||||
|
|
||||||
#{Exception.format_stacktrace(pleroma_stacktrace)}
|
|
||||||
"""
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -151,6 +151,7 @@ defmodule Pleroma.User do
|
||||||
field(:pinned_objects, :map, default: %{})
|
field(:pinned_objects, :map, default: %{})
|
||||||
field(:is_suggested, :boolean, default: false)
|
field(:is_suggested, :boolean, default: false)
|
||||||
field(:last_status_at, :naive_datetime)
|
field(:last_status_at, :naive_datetime)
|
||||||
|
field(:language, :string)
|
||||||
|
|
||||||
embeds_one(
|
embeds_one(
|
||||||
:notification_settings,
|
:notification_settings,
|
||||||
|
@ -734,7 +735,8 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||||
:password_confirmation,
|
:password_confirmation,
|
||||||
:emoji,
|
:emoji,
|
||||||
:accepts_chat_messages,
|
:accepts_chat_messages,
|
||||||
:registration_reason
|
:registration_reason,
|
||||||
|
:language
|
||||||
])
|
])
|
||||||
|> validate_required([:name, :nickname, :password, :password_confirmation])
|
|> validate_required([:name, :nickname, :password, :password_confirmation])
|
||||||
|> validate_confirmation(:password)
|
|> validate_confirmation(:password)
|
||||||
|
@ -1089,11 +1091,24 @@ def update_and_set_cache(struct, params) do
|
||||||
|> update_and_set_cache()
|
|> update_and_set_cache()
|
||||||
end
|
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
|
with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
|
||||||
Pleroma.Elasticsearch.maybe_put_into_elasticsearch(user)
|
|
||||||
set_cache(user)
|
set_cache(user)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
def get_user_friends_ap_ids(user) do
|
def get_user_friends_ap_ids(user) do
|
||||||
|
|
|
@ -140,6 +140,9 @@ def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when
|
||||||
Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end)
|
Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
# Add local posts to search index
|
||||||
|
if local, do: Pleroma.Search.add_to_index(activity)
|
||||||
|
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
%Activity{} = activity ->
|
%Activity{} = activity ->
|
||||||
|
|
|
@ -24,7 +24,7 @@ defp score_displayname("federationbot"), do: 1.0
|
||||||
defp score_displayname("fedibot"), do: 1.0
|
defp score_displayname("fedibot"), do: 1.0
|
||||||
defp score_displayname(_), do: 0.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
|
# nickname will be a binary string except when following a relay
|
||||||
nick_score =
|
nick_score =
|
||||||
if is_binary(nickname) do
|
if is_binary(nickname) do
|
||||||
|
@ -45,19 +45,32 @@ defp determine_if_followbot(%User{nickname: nickname, name: displayname}) do
|
||||||
0.0
|
0.0
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
defp determine_if_followbot(_), do: 0.0
|
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
|
@impl true
|
||||||
def filter(%{"type" => "Follow", "actor" => actor_id} = message) do
|
def filter(%{"type" => "Follow", "actor" => actor_id} = message) do
|
||||||
%User{} = actor = normalize_by_ap_id(actor_id)
|
%User{} = actor = normalize_by_ap_id(actor_id)
|
||||||
|
|
||||||
score = determine_if_followbot(actor)
|
score = determine_if_followbot(actor)
|
||||||
|
|
||||||
# TODO: scan biography data for keywords and score it somehow.
|
if score < 0.8 || bot_allowed?(message, actor) do
|
||||||
if score < 0.8 do
|
|
||||||
{:ok, message}
|
{:ok, message}
|
||||||
else
|
else
|
||||||
{:reject, "[AntiFollowbotPolicy] Scored #{actor_id} as #{score}"}
|
{:reject, "[AntiFollowbotPolicy] Scored #{actor_id} as #{score}"}
|
||||||
|
|
|
@ -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 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
|
defp steal_emoji({shortcode, url}, emoji_dir_path) do
|
||||||
url = Pleroma.Web.MediaProxy.url(url)
|
url = Pleroma.Web.MediaProxy.url(url)
|
||||||
|
|
||||||
|
@ -72,7 +80,7 @@ def filter(%{"object" => %{"emoji" => foreign_emojis, "actor" => actor}} = messa
|
||||||
reject_emoji? =
|
reject_emoji? =
|
||||||
[:mrf_steal_emoji, :rejected_shortcodes]
|
[:mrf_steal_emoji, :rejected_shortcodes]
|
||||||
|> Config.get([])
|
|> Config.get([])
|
||||||
|> Enum.find(false, fn regex -> String.match?(shortcode, regex) end)
|
|> Enum.find(false, fn pattern -> shortcode_matches?(shortcode, pattern) end)
|
||||||
|
|
||||||
!reject_emoji?
|
!reject_emoji?
|
||||||
end)
|
end)
|
||||||
|
@ -122,8 +130,12 @@ def config_description do
|
||||||
%{
|
%{
|
||||||
key: :rejected_shortcodes,
|
key: :rejected_shortcodes,
|
||||||
type: {:list, :string},
|
type: {:list, :string},
|
||||||
description: "Regex-list of shortcodes to reject",
|
description: """
|
||||||
suggestions: [""]
|
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,
|
key: :size_limit,
|
||||||
|
|
|
@ -28,7 +28,6 @@ def common_pipeline(object, meta) do
|
||||||
case Repo.transaction(fn -> do_common_pipeline(object, meta) end, Utils.query_timeout()) do
|
case Repo.transaction(fn -> do_common_pipeline(object, meta) end, Utils.query_timeout()) do
|
||||||
{:ok, {:ok, activity, meta}} ->
|
{:ok, {:ok, activity, meta}} ->
|
||||||
side_effects().handle_after_transaction(meta)
|
side_effects().handle_after_transaction(meta)
|
||||||
side_effects().handle_after_transaction(activity)
|
|
||||||
{:ok, activity, meta}
|
{:ok, activity, meta}
|
||||||
|
|
||||||
{:ok, value} ->
|
{:ok, value} ->
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# Pleroma: A lightweight social networking server
|
# Pleroma: A lightweight social networking server
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.SideEffects do
|
defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||||
|
@ -193,6 +193,7 @@ def handle(%{data: %{"type" => "Like"}} = object, meta) do
|
||||||
# - Increase replies count
|
# - Increase replies count
|
||||||
# - Set up ActivityExpiration
|
# - Set up ActivityExpiration
|
||||||
# - Set up notifications
|
# - Set up notifications
|
||||||
|
# - Index incoming posts for search (if needed)
|
||||||
@impl true
|
@impl true
|
||||||
def handle(%{data: %{"type" => "Create"}} = activity, meta) do
|
def handle(%{data: %{"type" => "Create"}} = activity, meta) do
|
||||||
with {:ok, object, meta} <- handle_object_creation(meta[:object_data], activity, meta),
|
with {:ok, object, meta} <- handle_object_creation(meta[:object_data], activity, meta),
|
||||||
|
@ -222,6 +223,8 @@ def handle(%{data: %{"type" => "Create"}} = activity, meta) do
|
||||||
Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end)
|
Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
Pleroma.Search.add_to_index(Map.put(activity, :object, object))
|
||||||
|
|
||||||
meta =
|
meta =
|
||||||
meta
|
meta
|
||||||
|> add_notifications(notifications)
|
|> add_notifications(notifications)
|
||||||
|
@ -269,6 +272,7 @@ def handle(%{data: %{"type" => "Undo", "object" => undone_object}} = object, met
|
||||||
def handle(%{data: %{"type" => "EmojiReact"}} = object, meta) do
|
def handle(%{data: %{"type" => "EmojiReact"}} = object, meta) do
|
||||||
reacted_object = Object.get_by_ap_id(object.data["object"])
|
reacted_object = Object.get_by_ap_id(object.data["object"])
|
||||||
Utils.add_emoji_reaction_to_object(object, reacted_object)
|
Utils.add_emoji_reaction_to_object(object, reacted_object)
|
||||||
|
|
||||||
Notification.create_notifications(object)
|
Notification.create_notifications(object)
|
||||||
|
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
|
@ -281,6 +285,7 @@ def handle(%{data: %{"type" => "EmojiReact"}} = object, meta) do
|
||||||
# - Reduce the user note count
|
# - Reduce the user note count
|
||||||
# - Reduce the reply count
|
# - Reduce the reply count
|
||||||
# - Stream out the activity
|
# - Stream out the activity
|
||||||
|
# - Removes posts from search index (if needed)
|
||||||
@impl true
|
@impl true
|
||||||
def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, meta) do
|
def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, meta) do
|
||||||
deleted_object =
|
deleted_object =
|
||||||
|
@ -320,6 +325,12 @@ def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object,
|
||||||
|
|
||||||
if result == :ok do
|
if result == :ok do
|
||||||
Notification.create_notifications(object)
|
Notification.create_notifications(object)
|
||||||
|
|
||||||
|
# Only remove from index when deleting actual objects, not users or anything else
|
||||||
|
with %Pleroma.Object{} <- deleted_object do
|
||||||
|
Pleroma.Search.remove_from_index(deleted_object)
|
||||||
|
end
|
||||||
|
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
else
|
else
|
||||||
{:error, result}
|
{:error, result}
|
||||||
|
@ -537,24 +548,6 @@ defp add_notifications(meta, notifications) do
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_after_transaction(%Pleroma.Activity{data: %{"type" => "Create"}} = activity) do
|
|
||||||
Pleroma.Elasticsearch.put_by_id(:activity, activity.id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_after_transaction(%Pleroma.Activity{
|
|
||||||
data: %{"type" => "Delete", "deleted_activity_id" => id}
|
|
||||||
}) do
|
|
||||||
Pleroma.Elasticsearch.delete_by_id(:activity, id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_after_transaction(%Pleroma.Activity{}) do
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_after_transaction(%Pleroma.Object{}) do
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_after_transaction(meta) do
|
def handle_after_transaction(meta) do
|
||||||
meta
|
meta
|
||||||
|> send_notifications()
|
|> send_notifications()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# Pleroma: A lightweight social networking server
|
# Pleroma: A lightweight social networking server
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.SideEffects.Handling do
|
defmodule Pleroma.Web.ActivityPub.SideEffects.Handling do
|
||||||
|
|
|
@ -507,6 +507,11 @@ defp create_request do
|
||||||
type: :string,
|
type: :string,
|
||||||
nullable: true,
|
nullable: true,
|
||||||
description: "Invite token required when the registrations aren't public"
|
description: "Invite token required when the registrations aren't public"
|
||||||
|
},
|
||||||
|
language: %Schema{
|
||||||
|
type: :string,
|
||||||
|
nullable: true,
|
||||||
|
description: "User's preferred language for emails"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
example: %{
|
example: %{
|
||||||
|
|
|
@ -396,13 +396,7 @@ def listen(user, data) do
|
||||||
|
|
||||||
def post(user, %{status: _} = data) do
|
def post(user, %{status: _} = data) do
|
||||||
with {:ok, draft} <- ActivityDraft.create(user, data) do
|
with {:ok, draft} <- ActivityDraft.create(user, data) do
|
||||||
activity = ActivityPub.create(draft.changes, draft.preview?)
|
ActivityPub.create(draft.changes, draft.preview?)
|
||||||
|
|
||||||
unless draft.preview? do
|
|
||||||
Pleroma.Elasticsearch.maybe_put_into_elasticsearch(activity)
|
|
||||||
end
|
|
||||||
|
|
||||||
activity
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Web.Feed.FeedView do
|
||||||
alias Pleroma.Formatter
|
alias Pleroma.Formatter
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.Gettext
|
||||||
alias Pleroma.Web.MediaProxy
|
alias Pleroma.Web.MediaProxy
|
||||||
|
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
|
|
@ -25,4 +25,196 @@ defmodule Pleroma.Web.Gettext do
|
||||||
See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
|
See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
|
||||||
"""
|
"""
|
||||||
use Gettext, otp_app: :pleroma
|
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
|
end
|
||||||
|
|
|
@ -217,6 +217,7 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
|
||||||
|> Maps.put_if_present(:is_locked, params[:locked])
|
|> Maps.put_if_present(:is_locked, params[:locked])
|
||||||
# Note: param name is indeed :discoverable (not an error)
|
# Note: param name is indeed :discoverable (not an error)
|
||||||
|> Maps.put_if_present(:is_discoverable, params[:discoverable])
|
|> Maps.put_if_present(:is_discoverable, params[:discoverable])
|
||||||
|
|> Maps.put_if_present(:language, Pleroma.Web.Gettext.normalize_locale(params[:language]))
|
||||||
|
|
||||||
# What happens here:
|
# What happens here:
|
||||||
#
|
#
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
# Pleroma: A lightweight social networking server
|
# Pleroma: A lightweight social networking server
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.MastodonAPI.SearchController do
|
defmodule Pleroma.Web.MastodonAPI.SearchController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ControllerHelper
|
alias Pleroma.Web.ControllerHelper
|
||||||
|
alias Pleroma.Web.Endpoint
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||||
alias Pleroma.Web.Plugs.RateLimiter
|
alias Pleroma.Web.Plugs.RateLimiter
|
||||||
|
|
||||||
|
@ -41,13 +44,34 @@ def account_search(%{assigns: %{user: user}} = conn, %{q: query} = params) do
|
||||||
def search2(conn, params), do: do_search(:v2, conn, params)
|
def search2(conn, params), do: do_search(:v2, conn, params)
|
||||||
def search(conn, params), do: do_search(:v1, conn, params)
|
def search(conn, params), do: do_search(:v1, conn, params)
|
||||||
|
|
||||||
defp do_search(version, %{assigns: %{user: user}} = conn, params) do
|
defp do_search(version, %{assigns: %{user: user}} = conn, %{q: query} = params) do
|
||||||
options =
|
query = String.trim(query)
|
||||||
search_options(params, user)
|
options = search_options(params, user)
|
||||||
|> Keyword.put(:version, version)
|
timeout = Keyword.get(Repo.config(), :timeout, 15_000)
|
||||||
|
default_values = %{"statuses" => [], "accounts" => [], "hashtags" => []}
|
||||||
|
|
||||||
search_provider = Pleroma.Config.get([:search, :provider])
|
result =
|
||||||
json(conn, search_provider.search(conn, params, options))
|
default_values
|
||||||
|
|> Enum.map(fn {resource, default_value} ->
|
||||||
|
if params[:type] in [nil, resource] do
|
||||||
|
{resource, fn -> resource_search(version, resource, query, options) end}
|
||||||
|
else
|
||||||
|
{resource, fn -> default_value end}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> Task.async_stream(fn {resource, f} -> {resource, with_fallback(f)} end,
|
||||||
|
timeout: timeout,
|
||||||
|
on_timeout: :kill_task
|
||||||
|
)
|
||||||
|
|> Enum.reduce(default_values, fn
|
||||||
|
{:ok, {resource, result}}, acc ->
|
||||||
|
Map.put(acc, resource, result)
|
||||||
|
|
||||||
|
_error, acc ->
|
||||||
|
acc
|
||||||
|
end)
|
||||||
|
|
||||||
|
json(conn, result)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp search_options(params, user) do
|
defp search_options(params, user) do
|
||||||
|
@ -64,6 +88,104 @@ defp search_options(params, user) do
|
||||||
|> Enum.filter(&elem(&1, 1))
|
|> Enum.filter(&elem(&1, 1))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp resource_search(_, "accounts", query, options) do
|
||||||
|
accounts = with_fallback(fn -> User.search(query, options) end)
|
||||||
|
|
||||||
|
AccountView.render("index.json",
|
||||||
|
users: accounts,
|
||||||
|
for: options[:for_user],
|
||||||
|
embed_relationships: options[:embed_relationships]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp resource_search(_, "statuses", query, options) do
|
||||||
|
statuses = with_fallback(fn -> Pleroma.Search.search(query, options) end)
|
||||||
|
|
||||||
|
StatusView.render("index.json",
|
||||||
|
activities: statuses,
|
||||||
|
for: options[:for_user],
|
||||||
|
as: :activity
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp resource_search(:v2, "hashtags", query, options) do
|
||||||
|
tags_path = Endpoint.url() <> "/tag/"
|
||||||
|
|
||||||
|
query
|
||||||
|
|> prepare_tags(options)
|
||||||
|
|> Enum.map(fn tag ->
|
||||||
|
%{name: tag, url: tags_path <> tag}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp resource_search(:v1, "hashtags", query, options) do
|
||||||
|
prepare_tags(query, options)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp prepare_tags(query, options) do
|
||||||
|
tags =
|
||||||
|
query
|
||||||
|
|> preprocess_uri_query()
|
||||||
|
|> String.split(~r/[^#\w]+/u, trim: true)
|
||||||
|
|> Enum.uniq_by(&String.downcase/1)
|
||||||
|
|
||||||
|
explicit_tags = Enum.filter(tags, fn tag -> String.starts_with?(tag, "#") end)
|
||||||
|
|
||||||
|
tags =
|
||||||
|
if Enum.any?(explicit_tags) do
|
||||||
|
explicit_tags
|
||||||
|
else
|
||||||
|
tags
|
||||||
|
end
|
||||||
|
|
||||||
|
tags = Enum.map(tags, fn tag -> String.trim_leading(tag, "#") end)
|
||||||
|
|
||||||
|
tags =
|
||||||
|
if Enum.empty?(explicit_tags) && !options[:skip_joined_tag] do
|
||||||
|
add_joined_tag(tags)
|
||||||
|
else
|
||||||
|
tags
|
||||||
|
end
|
||||||
|
|
||||||
|
Pleroma.Pagination.paginate(tags, options)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp add_joined_tag(tags) do
|
||||||
|
tags
|
||||||
|
|> Kernel.++([joined_tag(tags)])
|
||||||
|
|> Enum.uniq_by(&String.downcase/1)
|
||||||
|
end
|
||||||
|
|
||||||
|
# If `query` is a URI, returns last component of its path, otherwise returns `query`
|
||||||
|
defp preprocess_uri_query(query) do
|
||||||
|
if query =~ ~r/https?:\/\// do
|
||||||
|
query
|
||||||
|
|> String.trim_trailing("/")
|
||||||
|
|> URI.parse()
|
||||||
|
|> Map.get(:path)
|
||||||
|
|> String.split("/")
|
||||||
|
|> Enum.at(-1)
|
||||||
|
else
|
||||||
|
query
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp joined_tag(tags) do
|
||||||
|
tags
|
||||||
|
|> Enum.map(fn tag -> String.capitalize(tag) end)
|
||||||
|
|> Enum.join()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp with_fallback(f, fallback \\ []) do
|
||||||
|
try do
|
||||||
|
f.()
|
||||||
|
rescue
|
||||||
|
error ->
|
||||||
|
Logger.error("#{__MODULE__} search error: #{inspect(error)}")
|
||||||
|
fallback
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp get_author(%{account_id: account_id}) when is_binary(account_id),
|
defp get_author(%{account_id: account_id}) when is_binary(account_id),
|
||||||
do: User.get_cached_by_id(account_id)
|
do: User.get_cached_by_id(account_id)
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.OAuth.MFAView do
|
||||||
use Pleroma.Web, :view
|
use Pleroma.Web, :view
|
||||||
import Phoenix.HTML.Form
|
import Phoenix.HTML.Form
|
||||||
alias Pleroma.MFA
|
alias Pleroma.MFA
|
||||||
|
alias Pleroma.Web.Gettext
|
||||||
|
|
||||||
def render("mfa_response.json", %{token: token, user: user}) do
|
def render("mfa_response.json", %{token: token, user: user}) do
|
||||||
%{
|
%{
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
defmodule Pleroma.Web.OAuth.OAuthView do
|
defmodule Pleroma.Web.OAuth.OAuthView do
|
||||||
use Pleroma.Web, :view
|
use Pleroma.Web, :view
|
||||||
import Phoenix.HTML.Form
|
import Phoenix.HTML.Form
|
||||||
|
import Phoenix.HTML
|
||||||
|
alias Pleroma.Web.Gettext
|
||||||
|
|
||||||
alias Pleroma.Web.OAuth.Token.Utils
|
alias Pleroma.Web.OAuth.Token.Utils
|
||||||
|
|
||||||
|
|
|
@ -6,18 +6,56 @@
|
||||||
defmodule Pleroma.Web.Plugs.SetLocalePlug do
|
defmodule Pleroma.Web.Plugs.SetLocalePlug do
|
||||||
import Plug.Conn, only: [get_req_header: 2, assign: 3]
|
import Plug.Conn, only: [get_req_header: 2, assign: 3]
|
||||||
|
|
||||||
|
def frontend_language_cookie_name, do: "userLanguage"
|
||||||
|
|
||||||
def init(_), do: nil
|
def init(_), do: nil
|
||||||
|
|
||||||
def call(conn, _) do
|
def call(conn, _) do
|
||||||
locale = get_locale_from_header(conn) || Gettext.get_locale()
|
locales = get_locales_from_header(conn)
|
||||||
Gettext.put_locale(locale)
|
first_locale = Enum.at(locales, 0, Gettext.get_locale())
|
||||||
assign(conn, :locale, locale)
|
|
||||||
|
Pleroma.Web.Gettext.put_locales(locales)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> assign(:locale, first_locale)
|
||||||
|
|> assign(:locales, locales)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_locale_from_header(conn) do
|
defp get_locales_from_header(conn) do
|
||||||
conn
|
conn
|
||||||
|> extract_accept_language()
|
|> extract_preferred_language()
|
||||||
|> Enum.find(&supported_locale?/1)
|
|> 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
|
end
|
||||||
|
|
||||||
defp extract_accept_language(conn) do
|
defp extract_accept_language(conn) do
|
||||||
|
@ -29,7 +67,6 @@ defp extract_accept_language(conn) do
|
||||||
|> Enum.sort(&(&1.quality > &2.quality))
|
|> Enum.sort(&(&1.quality > &2.quality))
|
||||||
|> Enum.map(& &1.tag)
|
|> Enum.map(& &1.tag)
|
||||||
|> Enum.reject(&is_nil/1)
|
|> Enum.reject(&is_nil/1)
|
||||||
|> ensure_language_fallbacks()
|
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
[]
|
[]
|
||||||
|
@ -37,9 +74,7 @@ defp extract_accept_language(conn) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp supported_locale?(locale) do
|
defp supported_locale?(locale) do
|
||||||
Pleroma.Web.Gettext
|
Pleroma.Web.Gettext.supports_locale?(locale)
|
||||||
|> Gettext.known_locales()
|
|
||||||
|> Enum.member?(locale)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp parse_language_option(string) do
|
defp parse_language_option(string) do
|
||||||
|
@ -53,11 +88,4 @@ defp parse_language_option(string) do
|
||||||
|
|
||||||
%{tag: captures["tag"], quality: quality}
|
%{tag: captures["tag"], quality: quality}
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -160,7 +160,7 @@
|
||||||
<div
|
<div
|
||||||
style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height: 14px; color: <%= @styling.header_color %>;">
|
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
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<!--[if mso]></td></tr></table><![endif]-->
|
<!--[if mso]></td></tr></table><![endif]-->
|
||||||
|
@ -382,7 +382,7 @@
|
||||||
<div
|
<div
|
||||||
style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_color %>;">
|
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
|
<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>
|
style="font-size: 20px; line-height: 24px;"></span></p>
|
||||||
</div>
|
</div>
|
||||||
</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;">
|
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
|
<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;">
|
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
|
<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;">
|
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>
|
||||||
<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;">
|
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
|
<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;">
|
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>
|
</div>
|
||||||
<!--[if mso]></td></tr></table><![endif]-->
|
<!--[if mso]></td></tr></table><![endif]-->
|
||||||
<!--[if (!mso)&(!IE)]><!-->
|
<!--[if (!mso)&(!IE)]><!-->
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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:thr="http://purl.org/syndication/thread/1.0"
|
||||||
xmlns:georss="http://www.georss.org/georss"
|
xmlns:georss="http://www.georss.org/georss"
|
||||||
xmlns:activity="http://activitystrea.ms/spec/1.0/"
|
xmlns:activity="http://activitystrea.ms/spec/1.0/"
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
<id><%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.rss' %></id>
|
<id><%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.rss' %></id>
|
||||||
<title>#<%= @tag %></title>
|
<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>
|
<logo><%= feed_logo() %></logo>
|
||||||
<updated><%= most_recent_update(@activities) %></updated>
|
<updated><%= most_recent_update(@activities) %></updated>
|
||||||
<link rel="self" href="<%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.atom' %>" type="application/atom+xml"/>
|
<link rel="self" href="<%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.atom' %>" type="application/atom+xml"/>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
|
|
||||||
<title>#<%= @tag %></title>
|
<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>
|
<link><%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.rss' %></link>
|
||||||
<webfeeds:logo><%= feed_logo() %></webfeeds:logo>
|
<webfeeds:logo><%= feed_logo() %></webfeeds:logo>
|
||||||
<webfeeds:accentColor>2b90d9</webfeeds:accentColor>
|
<webfeeds:accentColor>2b90d9</webfeeds:accentColor>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="<%= Pleroma.Web.Gettext.language_tag() %>">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui">
|
<meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui">
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="<%= Pleroma.Web.Gettext.language_tag() %>">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title><%= @email.subject %></title>
|
<title><%= @email.subject %></title>
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
<h1>UNSUBSCRIBE FAILURE</h1>
|
<h1><%= Gettext.dpgettext("static_pages", "mailer unsubscribe failed message", "UNSUBSCRIBE FAILURE") %></h1>
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
<h1>UNSUBSCRIBE SUCCESSFUL</h1>
|
<h1><%= Gettext.dpgettext("static_pages", "mailer unsubscribe successful message", "UNSUBSCRIBE SUCCESSFUL") %></h1>
|
||||||
|
|
|
@ -5,11 +5,11 @@
|
||||||
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
|
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
|
||||||
<% end %>
|
<% 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 -> %>
|
<%= form_for @conn, Routes.mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
|
||||||
<div class="input">
|
<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] %>
|
<%= 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, :mfa_token, value: @mfa_token %>
|
||||||
<%= hidden_input f, :state, value: @state %>
|
<%= hidden_input f, :state, value: @state %>
|
||||||
|
@ -17,8 +17,8 @@
|
||||||
<%= hidden_input f, :challenge_type, value: "recovery" %>
|
<%= hidden_input f, :challenge_type, value: "recovery" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= submit "Verify" %>
|
<%= submit Gettext.dpgettext("static_pages", "mfa recover verify recovery code button", "Verify") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<a href="<%= Routes.mfa_path(@conn, :show, %{challenge_type: "totp", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>">
|
<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>
|
</a>
|
||||||
|
|
|
@ -5,20 +5,20 @@
|
||||||
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
|
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
|
||||||
<% end %>
|
<% 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 -> %>
|
<%= form_for @conn, Routes.mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
|
||||||
<div class="input">
|
<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: false, autocorrect: "off", autocapitalize: "off", autofocus: true, pattern: "[0-9]*", spellcheck: false] %>
|
<%= 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, :mfa_token, value: @mfa_token %>
|
||||||
<%= hidden_input f, :state, value: @state %>
|
<%= hidden_input f, :state, value: @state %>
|
||||||
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
|
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
|
||||||
<%= hidden_input f, :challenge_type, value: "totp" %>
|
<%= hidden_input f, :challenge_type, value: "totp" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= submit "Verify" %>
|
<%= submit Gettext.dpgettext("static_pages", "mfa auth verify code button", "Verify") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<a href="<%= Routes.mfa_path(@conn, :show, %{challenge_type: "recovery", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>">
|
<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>
|
</a>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="scopes-input">
|
<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">
|
<div class="scopes">
|
||||||
<%= for scope <- @available_scopes do %>
|
<%= 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 %>
|
<%# Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted %>
|
||||||
|
|
|
@ -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 -> %>
|
<%= form_for @conn, Routes.o_auth_path(@conn, :prepare_request), [as: "authorization", method: "get"], fn f -> %>
|
||||||
<div style="display: none">
|
<div style="display: none">
|
||||||
|
@ -10,6 +10,6 @@
|
||||||
<%= hidden_input f, :state, value: @state %>
|
<%= hidden_input f, :state, value: @state %>
|
||||||
|
|
||||||
<%= for strategy <- Pleroma.Config.oauth_consumer_strategies() do %>
|
<%= 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 %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
<h1>Successfully authorized</h1>
|
<h1><%= Gettext.dpgettext("static_pages", "oauth authorized page title", "Successfully authorized") %></h1>
|
||||||
<h2>Token code is <br><%= @auth.token %></h2>
|
<h2><%= raw Gettext.dpgettext("static_pages", "oauth token code message", "Token code is <br>%{token}", token: safe_to_string(html_escape(@auth.token))) %></h2>
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
<h1>Authorization exists</h1>
|
<h1><%= Gettext.dpgettext("static_pages", "oauth authorization exists page title", "Authorization exists") %></h1>
|
||||||
<h2>Access token is <br><%= @token.token %></h2>
|
<h2><%= raw Gettext.dpgettext("static_pages", "oauth token code message", "Token code is <br>%{token}", token: safe_to_string(html_escape(@token.token))) %></h2>
|
||||||
|
|
|
@ -5,34 +5,34 @@
|
||||||
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
|
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
|
||||||
<% end %>
|
<% 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 -> %>
|
<%= form_for @conn, Routes.o_auth_path(@conn, :register), [as: "authorization"], fn f -> %>
|
||||||
|
|
||||||
<div class="input">
|
<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 %>
|
<%= text_input f, :nickname, value: @nickname, autocomplete: "username" %>
|
||||||
</div>
|
</div>
|
||||||
<div class="input">
|
<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 %>
|
<%= text_input f, :email, value: @email, autocomplete: "email" %>
|
||||||
</div>
|
</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">
|
<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 %>
|
<%= text_input f, :name, autocomplete: "username" %>
|
||||||
</div>
|
</div>
|
||||||
<div class="input">
|
<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 %>
|
<%= password_input f, :password, autocomplete: "password" %>
|
||||||
</div>
|
</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, :client_id, value: @client_id %>
|
||||||
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
|
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
|
||||||
|
|
|
@ -20,36 +20,38 @@
|
||||||
|
|
||||||
<div class="container__content">
|
<div class="container__content">
|
||||||
<%= if @app do %>
|
<%= 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}) %>
|
<%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if @user do %>
|
<%= if @user do %>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<a class="button button--cancel" href="/">Cancel</a>
|
<a class="button button--cancel" href="/">
|
||||||
<%= submit "Approve", class: "button--approve" %>
|
<%= Gettext.dpgettext("static_pages", "oauth authorize cancel button", "Cancel") %>
|
||||||
|
</a>
|
||||||
|
<%= submit Gettext.dpgettext("static_pages", "oauth authorize approve button", "Approve"), class: "button--approve" %>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= if @params["registration"] in ["true", true] do %>
|
<%= if @params["registration"] in ["true", true] do %>
|
||||||
<h3>This is the first time you visit! Please enter your Pleroma handle.</h3>
|
<h3><%= Gettext.dpgettext("static_pages", "oauth register page title", "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>
|
<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">
|
<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" %>
|
<%= text_input f, :nickname, placeholder: "lain", autocomplete: "username" %>
|
||||||
</div>
|
</div>
|
||||||
<%= hidden_input f, :name, value: @params["name"] %>
|
<%= hidden_input f, :name, value: @params["name"] %>
|
||||||
<%= hidden_input f, :password, value: @params["password"] %>
|
<%= hidden_input f, :password, value: @params["password"] %>
|
||||||
<br>
|
<br>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="input">
|
<div class="input">
|
||||||
<%= label f, :name, "Username" %>
|
<%= label f, :name, Gettext.dpgettext("static_pages", "oauth login username prompt", "Username") %>
|
||||||
<%= text_input f, :name %>
|
<%= text_input f, :name %>
|
||||||
</div>
|
</div>
|
||||||
<div class="input">
|
<div class="input">
|
||||||
<%= label f, :password, "Password" %>
|
<%= label f, :password, Gettext.dpgettext("static_pages", "oauth login password prompt", "Password") %>
|
||||||
<%= password_input f, :password %>
|
<%= password_input f, :password %>
|
||||||
</div>
|
</div>
|
||||||
<%= submit "Log In" %>
|
<%= submit Gettext.dpgettext("static_pages", "oauth login button", "Log In") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<form class="pull-right collapse" method="POST" action="<%= Helpers.util_path(@conn, :remote_subscribe) %>">
|
<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="nickname" value="<%= @user.nickname %>">
|
||||||
<input type="hidden" name="profile" value="">
|
<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>
|
</form>
|
||||||
<%= raw Formatter.emojify(@user.name, @user.emoji) %> |
|
<%= raw Formatter.emojify(@user.name, @user.emoji) %> |
|
||||||
<%= link "@#{@user.nickname}@#{Endpoint.host()}", to: (@user.uri || @user.ap_id) %>
|
<%= link "@#{@user.nickname}@#{Endpoint.host()}", to: (@user.uri || @user.ap_id) %>
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
<h2>Invalid Token</h2>
|
<h2><%= Gettext.dpgettext("static_pages", "password reset invalid token message", "Invalid Token") %></h2>
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<h2>Password Reset for <%= @user.nickname %></h2>
|
<h2>Password Reset for <%= @user.nickname %></h2>
|
||||||
<%= form_for @conn, Routes.reset_password_path(@conn, :do_reset), [as: "data"], fn f -> %>
|
<%= form_for @conn, Routes.reset_password_path(@conn, :do_reset), [as: "data"], fn f -> %>
|
||||||
<div class="form-row">
|
<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 %>
|
<%= password_input f, :password %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<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 %>
|
<%= password_input f, :password_confirmation %>
|
||||||
</div>
|
</div>
|
||||||
<%= hidden_input f, :token, value: @token.token %>
|
<%= hidden_input f, :token, value: @token.token %>
|
||||||
<%= submit "Reset" %>
|
<%= submit Gettext.dpgettext("static_pages", "password reset button", "Reset") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
<h2>Password reset failed</h2>
|
<h2><%= Gettext.dpgettext("static_pages", "password reset failed message", "Password reset failed") %></h2>
|
||||||
<h3><a href="<%= Pleroma.Web.Endpoint.url() %>">Homepage</a></h3>
|
<h3>
|
||||||
|
<a href="<%= Pleroma.Web.Endpoint.url() %>">
|
||||||
|
<%= Gettext.dpgettext("static_pages", "password reset failed homepage link", "Homepage") %>
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
<h2>Password changed!</h2>
|
<h2><%= Gettext.dpgettext("static_pages", "password reset successful message", "Password changed!") %></h2>
|
||||||
<h3><a href="<%= Pleroma.Web.Endpoint.url() %>">Homepage</a></h3>
|
<h3><a href="<%= Pleroma.Web.Endpoint.url() %>"><%= Gettext.dpgettext("static_pages", "password reset successful homepage link", "Homepage") %></a></h3>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<%= if @error == :error do %>
|
<%= if @error == :error do %>
|
||||||
<h2>Error fetching user</h2>
|
<h2><%= Gettext.dpgettext("static_pages", "remote follow error", "Error fetching user") %></h2>
|
||||||
<% else %>
|
<% 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) %>">
|
<img height="128" width="128" src="<%= avatar_url(@followee) %>">
|
||||||
<p><%= @followee.nickname %></p>
|
<p><%= @followee.nickname %></p>
|
||||||
<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "user"], fn f -> %>
|
<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "user"], fn f -> %>
|
||||||
<%= hidden_input f, :id, value: @followee.id %>
|
<%= hidden_input f, :id, value: @followee.id %>
|
||||||
<%= submit "Authorize" %>
|
<%= submit Gettext.dpgettext("static_pages", "remote follow authorization button", "Authorize") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<%= if @error do %>
|
<%= if @error do %>
|
||||||
<h2><%= @error %></h2>
|
<h2><%= @error %></h2>
|
||||||
<% end %>
|
<% 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>
|
<p><%= @followee.nickname %></p>
|
||||||
<img height="128" width="128" src="<%= avatar_url(@followee) %>">
|
<img height="128" width="128" src="<%= avatar_url(@followee) %>">
|
||||||
<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "authorization"], fn f -> %>
|
<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "authorization"], fn f -> %>
|
||||||
<%= text_input f, :name, placeholder: "Username", required: true %>
|
<%= text_input f, :name, placeholder: Gettext.dpgettext("static_pages", "placeholder text for username entry", "Username"), required: true, autocomplete: "username" %>
|
||||||
<br>
|
<br>
|
||||||
<%= password_input f, :password, placeholder: "Password", required: true %>
|
<%= password_input f, :password, placeholder: Gettext.dpgettext("static_pages", "placeholder text for password entry", "Password"), required: true, autocomplete: "password" %>
|
||||||
<br>
|
<br>
|
||||||
<%= hidden_input f, :id, value: @followee.id %>
|
<%= hidden_input f, :id, value: @followee.id %>
|
||||||
<%= submit "Authorize" %>
|
<%= submit Gettext.dpgettext("static_pages", "remote follow authorization button for login", "Authorize") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<%= if @error do %>
|
<%= if @error do %>
|
||||||
<h2><%= @error %></h2>
|
<h2><%= @error %></h2>
|
||||||
<% end %>
|
<% end %>
|
||||||
<h2>Two-factor authentication</h2>
|
<h2><%= Gettext.dpgettext("static_pages", "remote follow mfa header", "Two-factor authentication") %></h2>
|
||||||
<p><%= @followee.nickname %></p>
|
<p><%= @followee.nickname %></p>
|
||||||
<img height="128" width="128" src="<%= avatar_url(@followee) %>">
|
<img height="128" width="128" src="<%= avatar_url(@followee) %>">
|
||||||
<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "mfa"], fn f -> %>
|
<%= 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>
|
<br>
|
||||||
<%= hidden_input f, :id, value: @followee.id %>
|
<%= hidden_input f, :id, value: @followee.id %>
|
||||||
<%= hidden_input f, :token, value: @mfa_token %>
|
<%= hidden_input f, :token, value: @mfa_token %>
|
||||||
<%= submit "Authorize" %>
|
<%= submit Gettext.dpgettext("static_pages", "remote follow authorization button for mfa", "Authorize") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<%= if @error do %>
|
<%= if @error do %>
|
||||||
<p>Error following account</p>
|
<p><%= Gettext.dpgettext("static_pages", "remote follow error", "Error following account") %></p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<h2>Account followed!</h2>
|
<h2><%= Gettext.dpgettext("static_pages", "remote follow success", "Account followed!") %></h2>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<%= if @error do %>
|
<%= if @error do %>
|
||||||
<h2>Error: <%= @error %></h2>
|
<h2><%= Gettext.dpgettext("static_pages", "remote follow error", "Error: %{error}", error: @error) %></h2>
|
||||||
<% else %>
|
<% 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 -> %>
|
<%= form_for @conn, Routes.util_path(@conn, :remote_subscribe), [as: "user"], fn f -> %>
|
||||||
<%= hidden_input f, :nickname, value: @nickname %>
|
<%= hidden_input f, :nickname, value: @nickname %>
|
||||||
<%= text_input f, :profile, placeholder: "Your account ID, e.g. lain@quitter.se" %>
|
<%= text_input f, :profile, placeholder: Gettext.dpgettext("static_pages", "placeholder text for account id", "Your account ID, e.g. lain@quitter.se") %>
|
||||||
<%= submit "Follow" %>
|
<%= submit Gettext.dpgettext("static_pages", "remote follow authorization button for following with a remote account", "Follow") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -12,6 +12,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
||||||
alias Pleroma.UserInviteToken
|
alias Pleroma.UserInviteToken
|
||||||
|
|
||||||
def register_user(params, opts \\ []) do
|
def register_user(params, opts \\ []) do
|
||||||
|
fallback_language = Gettext.get_locale()
|
||||||
|
|
||||||
params =
|
params =
|
||||||
params
|
params
|
||||||
|> Map.take([:email, :token, :password])
|
|> Map.take([:email, :token, :password])
|
||||||
|
@ -20,6 +22,10 @@ def register_user(params, opts \\ []) do
|
||||||
|> Map.put(:name, Map.get(params, :fullname, params[:username]))
|
|> Map.put(:name, Map.get(params, :fullname, params[:username]))
|
||||||
|> Map.put(:password_confirmation, params[:password])
|
|> Map.put(:password_confirmation, params[:password])
|
||||||
|> Map.put(:registration_reason, params[:reason])
|
|> Map.put(:registration_reason, params[:reason])
|
||||||
|
|> Map.put(
|
||||||
|
:language,
|
||||||
|
Pleroma.Web.Gettext.normalize_locale(params[:language]) || fallback_language
|
||||||
|
)
|
||||||
|
|
||||||
if Pleroma.Config.get([:instance, :registrations_open]) do
|
if Pleroma.Config.get([:instance, :registrations_open]) do
|
||||||
create_user(params, opts)
|
create_user(params, opts)
|
||||||
|
|
|
@ -5,4 +5,5 @@
|
||||||
defmodule Pleroma.Web.TwitterAPI.PasswordView do
|
defmodule Pleroma.Web.TwitterAPI.PasswordView do
|
||||||
use Pleroma.Web, :view
|
use Pleroma.Web, :view
|
||||||
import Phoenix.HTML.Form
|
import Phoenix.HTML.Form
|
||||||
|
alias Pleroma.Web.Gettext
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
defmodule Pleroma.Web.TwitterAPI.RemoteFollowView do
|
defmodule Pleroma.Web.TwitterAPI.RemoteFollowView do
|
||||||
use Pleroma.Web, :view
|
use Pleroma.Web, :view
|
||||||
import Phoenix.HTML.Form
|
import Phoenix.HTML.Form
|
||||||
|
alias Pleroma.Web.Gettext
|
||||||
|
|
||||||
defdelegate avatar_url(user), to: Pleroma.User
|
defdelegate avatar_url(user), to: Pleroma.User
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilView do
|
||||||
import Phoenix.HTML.Form
|
import Phoenix.HTML.Form
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
alias Pleroma.Web.Endpoint
|
alias Pleroma.Web.Endpoint
|
||||||
|
alias Pleroma.Web.Gettext
|
||||||
|
|
||||||
def status_net_config(instance) do
|
def status_net_config(instance) do
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.EmailView do
|
||||||
use Pleroma.Web, :view
|
use Pleroma.Web, :view
|
||||||
import Phoenix.HTML
|
import Phoenix.HTML
|
||||||
import Phoenix.HTML.Link
|
import Phoenix.HTML.Link
|
||||||
|
alias Pleroma.Web.Gettext
|
||||||
|
|
||||||
def avatar_url(user) do
|
def avatar_url(user) do
|
||||||
Pleroma.User.avatar_url(user)
|
Pleroma.User.avatar_url(user)
|
||||||
|
|
|
@ -4,4 +4,5 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.Mailer.SubscriptionView do
|
defmodule Pleroma.Web.Mailer.SubscriptionView do
|
||||||
use Pleroma.Web, :view
|
use Pleroma.Web, :view
|
||||||
|
alias Pleroma.Web.Gettext
|
||||||
end
|
end
|
||||||
|
|
25
lib/pleroma/workers/search_indexing_worker.ex
Normal file
25
lib/pleroma/workers/search_indexing_worker.ex
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
defmodule Pleroma.Workers.SearchIndexingWorker do
|
||||||
|
use Pleroma.Workers.WorkerHelper, queue: "search_indexing"
|
||||||
|
|
||||||
|
@impl Oban.Worker
|
||||||
|
|
||||||
|
def perform(%Job{args: %{"op" => "add_to_index", "activity" => activity_id}}) do
|
||||||
|
activity = Pleroma.Activity.get_by_id_with_object(activity_id)
|
||||||
|
|
||||||
|
search_module = Pleroma.Config.get([Pleroma.Search, :module])
|
||||||
|
|
||||||
|
search_module.add_to_index(activity)
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform(%Job{args: %{"op" => "remove_from_index", "object" => object_id}}) do
|
||||||
|
object = Pleroma.Object.get_by_id(object_id)
|
||||||
|
|
||||||
|
search_module = Pleroma.Config.get([Pleroma.Search, :module])
|
||||||
|
|
||||||
|
search_module.remove_from_index(object)
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
10
mix.exs
10
mix.exs
|
@ -123,7 +123,10 @@ defp deps do
|
||||||
{:ecto_sql, "~> 3.6.2"},
|
{:ecto_sql, "~> 3.6.2"},
|
||||||
{:postgrex, ">= 0.15.5"},
|
{:postgrex, ">= 0.15.5"},
|
||||||
{:oban, "~> 2.3.4"},
|
{:oban, "~> 2.3.4"},
|
||||||
{:gettext, "~> 0.18"},
|
{:gettext,
|
||||||
|
git: "https://github.com/tusooa/gettext.git",
|
||||||
|
ref: "72fb2496b6c5280ed911bdc3756890e7f38a4808",
|
||||||
|
override: true},
|
||||||
{:bcrypt_elixir, "~> 2.2"},
|
{:bcrypt_elixir, "~> 2.2"},
|
||||||
{:trailing_format_plug, "~> 0.0.7"},
|
{:trailing_format_plug, "~> 0.0.7"},
|
||||||
{:fast_sanitize, "~> 0.2.0"},
|
{:fast_sanitize, "~> 0.2.0"},
|
||||||
|
@ -200,6 +203,7 @@ defp deps do
|
||||||
{:nimble_parsec, "~> 1.0", override: true},
|
{:nimble_parsec, "~> 1.0", override: true},
|
||||||
{:phoenix_live_dashboard, "~> 0.6.2"},
|
{:phoenix_live_dashboard, "~> 0.6.2"},
|
||||||
{:ecto_psql_extras, "~> 0.6"},
|
{:ecto_psql_extras, "~> 0.6"},
|
||||||
|
{:elasticsearch, "~> 1.0.0"},
|
||||||
|
|
||||||
# indirect dependency version override
|
# indirect dependency version override
|
||||||
{:plug, "~> 1.10.4", override: true},
|
{:plug, "~> 1.10.4", override: true},
|
||||||
|
@ -248,9 +252,10 @@ defp version(version) do
|
||||||
identifier_filter = ~r/[^0-9a-z\-]+/i
|
identifier_filter = ~r/[^0-9a-z\-]+/i
|
||||||
|
|
||||||
git_available? = match?({_output, 0}, System.cmd("sh", ["-c", "command -v git"]))
|
git_available? = match?({_output, 0}, System.cmd("sh", ["-c", "command -v git"]))
|
||||||
|
dotgit_present? = File.exists?(".git")
|
||||||
|
|
||||||
git_pre_release =
|
git_pre_release =
|
||||||
if git_available? do
|
if git_available? and dotgit_present? do
|
||||||
{tag, tag_err} =
|
{tag, tag_err} =
|
||||||
System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true)
|
System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true)
|
||||||
|
|
||||||
|
@ -277,6 +282,7 @@ defp version(version) do
|
||||||
# Branch name as pre-release version component, denoted with a dot
|
# Branch name as pre-release version component, denoted with a dot
|
||||||
branch_name =
|
branch_name =
|
||||||
with true <- git_available?,
|
with true <- git_available?,
|
||||||
|
true <- dotgit_present?,
|
||||||
{branch_name, 0} <- System.cmd("git", ["rev-parse", "--abbrev-ref", "HEAD"]),
|
{branch_name, 0} <- System.cmd("git", ["rev-parse", "--abbrev-ref", "HEAD"]),
|
||||||
branch_name <- String.trim(branch_name),
|
branch_name <- String.trim(branch_name),
|
||||||
branch_name <- System.get_env("PLEROMA_BUILD_BRANCH") || branch_name,
|
branch_name <- System.get_env("PLEROMA_BUILD_BRANCH") || branch_name,
|
||||||
|
|
2
mix.lock
2
mix.lock
|
@ -57,7 +57,7 @@
|
||||||
"gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"},
|
"gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"},
|
||||||
"gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm"},
|
"gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm"},
|
||||||
"gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [: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"},
|
"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.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.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", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
|
"hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.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", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
|
||||||
"hpax": {:hex, :hpax, "0.1.1", "2396c313683ada39e98c20a75a82911592b47e5c24391363343bde74f82396ca", [:mix], [], "hexpm", "0ae7d5a0b04a8a60caf7a39fcf3ec476f35cc2cc16c05abea730d3ce6ac6c826"},
|
"hpax": {:hex, :hpax, "0.1.1", "2396c313683ada39e98c20a75a82911592b47e5c24391363343bde74f82396ca", [:mix], [], "hexpm", "0ae7d5a0b04a8a60caf7a39fcf3ec476f35cc2cc16c05abea730d3ce6ac6c826"},
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"mappings": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"_timestamp": {
|
"_timestamp": {
|
||||||
"type": "date",
|
"type": "date",
|
||||||
|
@ -18,4 +19,5 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
185
priv/gettext/default.pot
Normal file
185
priv/gettext/default.pot
Normal file
|
@ -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 ""
|
186
priv/gettext/en_test/LC_MESSAGES/default.po
Normal file
186
priv/gettext/en_test/LC_MESSAGES/default.po
Normal file
|
@ -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 ""
|
557
priv/gettext/en_test/LC_MESSAGES/errors.po
Normal file
557
priv/gettext/en_test/LC_MESSAGES/errors.po
Normal file
|
@ -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 ""
|
153
priv/gettext/en_test/LC_MESSAGES/posix_errors.po
Normal file
153
priv/gettext/en_test/LC_MESSAGES/posix_errors.po
Normal file
|
@ -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 ""
|
529
priv/gettext/en_test/LC_MESSAGES/static_pages.po
Normal file
529
priv/gettext/en_test/LC_MESSAGES/static_pages.po
Normal file
|
@ -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 ""
|
|
@ -90,121 +90,99 @@ msgid "must be equal to %{number}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:505
|
#: lib/pleroma/web/common_api.ex:523
|
||||||
msgid "Account not found"
|
msgid "Account not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:339
|
#: lib/pleroma/web/common_api.ex:316
|
||||||
msgid "Already voted"
|
msgid "Already voted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:359
|
#: lib/pleroma/web/o_auth/o_auth_controller.ex:402
|
||||||
msgid "Bad request"
|
msgid "Bad request"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:426
|
#: lib/pleroma/web/controller_helper.ex:97
|
||||||
msgid "Can't delete object"
|
#: lib/pleroma/web/controller_helper.ex:103
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#, elixir-format
|
|
||||||
#: lib/pleroma/web/controller_helper.ex:105
|
|
||||||
#: lib/pleroma/web/controller_helper.ex:111
|
|
||||||
msgid "Can't display this activity"
|
msgid "Can't display this activity"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, 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"
|
msgid "Can't find user"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, 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"
|
msgid "Can't get favorites"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:438
|
#: lib/pleroma/web/common_api/utils.ex:482
|
||||||
msgid "Can't like object"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#, elixir-format
|
|
||||||
#: lib/pleroma/web/common_api/utils.ex:563
|
|
||||||
msgid "Cannot post an empty status without attachments"
|
msgid "Cannot post an empty status without attachments"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, 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"
|
msgid "Comment must be up to %{max_size} characters"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/config/config_db.ex:191
|
#: lib/pleroma/config_db.ex:200
|
||||||
msgid "Config with params %{params} not found"
|
msgid "Config with params %{params} not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:181
|
#: lib/pleroma/web/common_api.ex:167 lib/pleroma/web/common_api.ex:171
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:185
|
|
||||||
msgid "Could not delete"
|
msgid "Could not delete"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:231
|
#: lib/pleroma/web/common_api.ex:217
|
||||||
msgid "Could not favorite"
|
msgid "Could not favorite"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:453
|
#: lib/pleroma/web/common_api.ex:254
|
||||||
msgid "Could not pin"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#, elixir-format
|
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:278
|
|
||||||
msgid "Could not unfavorite"
|
msgid "Could not unfavorite"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:463
|
#: lib/pleroma/web/common_api.ex:202
|
||||||
msgid "Could not unpin"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#, elixir-format
|
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:216
|
|
||||||
msgid "Could not unrepeat"
|
msgid "Could not unrepeat"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:512
|
#: lib/pleroma/web/common_api.ex:530 lib/pleroma/web/common_api.ex:539
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:521
|
|
||||||
msgid "Could not update state"
|
msgid "Could not update state"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:207
|
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:205
|
||||||
msgid "Error."
|
msgid "Error."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:106
|
#: lib/pleroma/web/twitter_api/twitter_api.ex:99
|
||||||
msgid "Invalid CAPTCHA"
|
msgid "Invalid CAPTCHA"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:116
|
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:144
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:568
|
#: lib/pleroma/web/o_auth/o_auth_controller.ex:631
|
||||||
msgid "Invalid credentials"
|
msgid "Invalid credentials"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38
|
#: lib/pleroma/web/plugs/ensure_authenticated_plug.ex:42
|
||||||
msgid "Invalid credentials."
|
msgid "Invalid credentials."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:355
|
#: lib/pleroma/web/common_api.ex:337
|
||||||
msgid "Invalid indices"
|
msgid "Invalid indices"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -214,189 +192,184 @@ msgid "Invalid parameters"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/common_api/utils.ex:414
|
#: lib/pleroma/web/common_api/utils.ex:349
|
||||||
msgid "Invalid password."
|
msgid "Invalid password."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, 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"
|
msgid "Invalid request"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:109
|
#: lib/pleroma/web/twitter_api/twitter_api.ex:102
|
||||||
msgid "Kocaptcha service unavailable"
|
msgid "Kocaptcha service unavailable"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, 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"
|
msgid "Missing parameters"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/common_api/utils.ex:547
|
#: lib/pleroma/web/common_api/utils.ex:477
|
||||||
msgid "No such conversation"
|
msgid "No such conversation"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:388
|
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:171
|
||||||
#: 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:197 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:239
|
||||||
msgid "No such permission_group"
|
msgid "No such permission_group"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/plugs/uploaded_media.ex:84
|
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:504
|
||||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11
|
#: 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:71 lib/pleroma/web/ostatus/ostatus_controller.ex:143
|
#: 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"
|
msgid "Not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, 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"
|
msgid "Poll's author can't vote"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
|
#: 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:39 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:51
|
||||||
#: 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:52 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:326
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
|
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
|
||||||
msgid "Record not found"
|
msgid "Record not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35
|
#: 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/feed/user_controller.ex:78 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:42
|
||||||
#: lib/pleroma/web/ostatus/ostatus_controller.ex:149
|
#: lib/pleroma/web/o_status/o_status_controller.ex:138
|
||||||
msgid "Something went wrong"
|
msgid "Something went wrong"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, 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"
|
msgid "The message visibility must be direct"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, 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"
|
msgid "The status is over the character limit"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, 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."
|
msgid "This resource requires authentication."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
|
#: lib/pleroma/web/plugs/rate_limiter.ex:208
|
||||||
msgid "Throttled"
|
msgid "Throttled"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:356
|
#: lib/pleroma/web/common_api.ex:338
|
||||||
msgid "Too many choices"
|
msgid "Too many choices"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:443
|
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:268
|
||||||
msgid "Unhandled activity type"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#, elixir-format
|
|
||||||
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:485
|
|
||||||
msgid "You can't revoke your own admin status."
|
msgid "You can't revoke your own admin status."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:221
|
#: lib/pleroma/web/o_auth/o_auth_controller.ex:243
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:308
|
#: lib/pleroma/web/o_auth/o_auth_controller.ex:333
|
||||||
msgid "Your account is currently disabled"
|
msgid "Your account is currently disabled"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:183
|
#: lib/pleroma/web/o_auth/o_auth_controller.ex:205
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:331
|
#: lib/pleroma/web/o_auth/o_auth_controller.ex:356
|
||||||
msgid "Your login is missing a confirmed e-mail address"
|
msgid "Your login is missing a confirmed e-mail address"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, 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}"
|
msgid "can't read inbox of %{nickname} as %{as_nickname}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, 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}"
|
msgid "can't update outbox of %{nickname} as %{as_nickname}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:471
|
#: lib/pleroma/web/common_api.ex:475
|
||||||
msgid "conversation is already muted"
|
msgid "conversation is already muted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:314
|
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:510
|
||||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:492
|
|
||||||
msgid "error"
|
msgid "error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, 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"
|
msgid "mascots can only be images"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, 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"
|
msgid "not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:394
|
#: lib/pleroma/web/o_auth/o_auth_controller.ex:437
|
||||||
msgid "Bad OAuth request."
|
msgid "Bad OAuth request."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:115
|
#: lib/pleroma/web/twitter_api/twitter_api.ex:108
|
||||||
msgid "CAPTCHA already used"
|
msgid "CAPTCHA already used"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:112
|
#: lib/pleroma/web/twitter_api/twitter_api.ex:105
|
||||||
msgid "CAPTCHA expired"
|
msgid "CAPTCHA expired"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/plugs/uploaded_media.ex:57
|
#: lib/pleroma/web/plugs/uploaded_media.ex:57
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, 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}."
|
msgid "Failed to authenticate: %{message}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, 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."
|
msgid "Failed to set up user account."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/plugs/oauth_scopes_plug.ex:38
|
#: lib/pleroma/web/plugs/o_auth_scopes_plug.ex:37
|
||||||
msgid "Insufficient permissions: %{permissions}."
|
msgid "Insufficient permissions: %{permissions}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/plugs/uploaded_media.ex:104
|
#: lib/pleroma/web/plugs/uploaded_media.ex:111
|
||||||
msgid "Internal Error"
|
msgid "Internal Error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/oauth/fallback_controller.ex:22
|
#: lib/pleroma/web/o_auth/fallback_controller.ex:22
|
||||||
#: lib/pleroma/web/oauth/fallback_controller.ex:29
|
#: lib/pleroma/web/o_auth/fallback_controller.ex:29
|
||||||
msgid "Invalid Username/Password"
|
msgid "Invalid Username/Password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:118
|
#: lib/pleroma/web/twitter_api/twitter_api.ex:111
|
||||||
msgid "Invalid answer data"
|
msgid "Invalid answer data"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -406,28 +379,28 @@ msgid "Nodeinfo schema version not handled"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, 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"
|
msgid "This action is outside the authorized scopes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, 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."
|
msgid "Unknown error, please check the details and try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:119
|
#: lib/pleroma/web/o_auth/o_auth_controller.ex:136
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:158
|
#: lib/pleroma/web/o_auth/o_auth_controller.ex:180
|
||||||
msgid "Unlisted redirect_uri."
|
msgid "Unlisted redirect_uri."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, 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}."
|
msgid "Unsupported OAuth provider: %{provider}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/uploaders/uploader.ex:72
|
#: lib/pleroma/uploaders/uploader.ex:74
|
||||||
msgid "Uploader callback timeout"
|
msgid "Uploader callback timeout"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -437,120 +410,101 @@ msgid "bad request"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:103
|
#: lib/pleroma/web/twitter_api/twitter_api.ex:96
|
||||||
msgid "CAPTCHA Error"
|
msgid "CAPTCHA Error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:290
|
#: lib/pleroma/web/common_api.ex:266
|
||||||
msgid "Could not add reaction emoji"
|
msgid "Could not add reaction emoji"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:301
|
#: lib/pleroma/web/common_api.ex:277
|
||||||
msgid "Could not remove reaction emoji"
|
msgid "Could not remove reaction emoji"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, 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})"
|
msgid "Invalid CAPTCHA (Missing parameter: %{name})"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, 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"
|
msgid "List not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, 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}"
|
msgid "Missing parameter: %{name}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:210
|
#: lib/pleroma/web/o_auth/o_auth_controller.ex:232
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:321
|
#: lib/pleroma/web/o_auth/o_auth_controller.ex:346
|
||||||
msgid "Password reset is required"
|
msgid "Password reset is required"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/tests/auth_test_controller.ex:9
|
#: 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/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/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/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/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/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/feed/user_controller.ex:6 lib/pleroma/web/mailer/subscription_controller.ex:6
|
||||||
#: lib/pleroma/web/masto_fe_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/account_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:6 lib/pleroma/web/mastodon_api/controllers/auth_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/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/directory_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/domain_block_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/fallback_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/filter_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/follow_request_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/instance_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/list_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6
|
||||||
#: 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/mastodon_api_controller.ex:14 lib/pleroma/web/mastodon_api/controllers/media_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/notification_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/poll_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/report_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/scheduled_activity_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/search_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/timeline_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/media_proxy/media_proxy_controller.ex:6 lib/pleroma/web/mongooseim/mongoose_im_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/nodeinfo/nodeinfo_controller.ex:6 lib/pleroma/web/oauth/fallback_controller.ex:6
|
#: lib/pleroma/web/mongoose_im/mongoose_im_controller.ex:6 lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6
|
||||||
#: lib/pleroma/web/oauth/mfa_controller.ex:10 lib/pleroma/web/oauth/oauth_controller.ex:6
|
#: lib/pleroma/web/o_auth/fallback_controller.ex:6 lib/pleroma/web/o_auth/mfa_controller.ex:10
|
||||||
#: lib/pleroma/web/ostatus/ostatus_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6
|
#: 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/chat_controller.ex:5 lib/pleroma/web/pleroma_api/controllers/conversation_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/emoji_pack_controller.ex:2 lib/pleroma/web/pleroma_api/controllers/emoji_reaction_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/mascot_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/notification_controller.ex:6
|
#: 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/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/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/twitter_api/controllers/util_controller.ex:6 lib/pleroma/web/uploader_controller.ex:6
|
||||||
#: lib/pleroma/web/uploader_controller.ex:6 lib/pleroma/web/web_finger/web_finger_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."
|
msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, 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."
|
msgid "Two-factor authentication enabled, you must use a access token."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
|
||||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210
|
|
||||||
msgid "Unexpected error occurred while adding file to pack."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#, elixir-format
|
|
||||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:138
|
|
||||||
msgid "Unexpected error occurred while creating pack."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#, elixir-format
|
|
||||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:278
|
|
||||||
msgid "Unexpected error occurred while removing file from pack."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#, elixir-format
|
|
||||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:250
|
|
||||||
msgid "Unexpected error occurred while updating file in pack."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#, elixir-format
|
|
||||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:179
|
|
||||||
msgid "Unexpected error occurred while updating pack metadata."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
|
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
|
||||||
msgid "Web push subscription is disabled on this Pleroma instance"
|
msgid "Web push subscription is disabled on this Pleroma instance"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451
|
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:234
|
||||||
msgid "You can't revoke your own admin/moderator status."
|
msgid "You can't revoke your own admin/moderator status."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126
|
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:129
|
||||||
msgid "authorization required for timeline view"
|
msgid "authorization required for timeline view"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -560,11 +514,50 @@ msgid "Access denied"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282
|
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:321
|
||||||
msgid "This API requires an authenticated user"
|
msgid "This API requires an authenticated user"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
#: lib/pleroma/plugs/user_is_admin_plug.ex:21
|
#: 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."
|
msgid "User is not an admin."
|
||||||
msgstr ""
|
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 ""
|
||||||
|
|
513
priv/gettext/static_pages.pot
Normal file
513
priv/gettext/static_pages.pot
Normal file
|
@ -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 ""
|
|
@ -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
|
|
@ -53,7 +53,13 @@ test "Sends digest to the given user" do
|
||||||
|
|
||||||
assert_email_sent(
|
assert_email_sent(
|
||||||
to: {user2.name, user2.email},
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -56,4 +56,16 @@ test "build approval pending email" do
|
||||||
assert email.subject == "Your account is awaiting approval"
|
assert email.subject == "Your account is awaiting approval"
|
||||||
assert email.html_body =~ "Awaiting Approval"
|
assert email.html_body =~ "Awaiting Approval"
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -20,6 +20,7 @@ test "tells if a string is an unicode emoji" do
|
||||||
assert Emoji.is_unicode_emoji?("🤰")
|
assert Emoji.is_unicode_emoji?("🤰")
|
||||||
assert Emoji.is_unicode_emoji?("❤️")
|
assert Emoji.is_unicode_emoji?("❤️")
|
||||||
assert Emoji.is_unicode_emoji?("🏳️⚧️")
|
assert Emoji.is_unicode_emoji?("🏳️⚧️")
|
||||||
|
assert Emoji.is_unicode_emoji?("🫵")
|
||||||
|
|
||||||
# Additionally, we accept regional indicators.
|
# Additionally, we accept regional indicators.
|
||||||
assert Emoji.is_unicode_emoji?("🇵")
|
assert Emoji.is_unicode_emoji?("🇵")
|
||||||
|
|
|
@ -520,6 +520,25 @@ test "it clears all notifications belonging to the user" do
|
||||||
end
|
end
|
||||||
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
|
describe "set_read_up_to()" do
|
||||||
test "it sets all notifications as read up to a specified notification ID" do
|
test "it sets all notifications as read up to a specified notification ID" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Activity.SearchTest do
|
defmodule Pleroma.Search.DatabaseSearchTest do
|
||||||
alias Pleroma.Activity.Search
|
alias Pleroma.Search.DatabaseSearch
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ test "it finds something" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
{:ok, post} = CommonAPI.post(user, %{status: "it's wednesday my dudes"})
|
{:ok, post} = CommonAPI.post(user, %{status: "it's wednesday my dudes"})
|
||||||
|
|
||||||
[result] = Search.search(nil, "wednesday")
|
[result] = DatabaseSearch.search(nil, "wednesday")
|
||||||
|
|
||||||
assert result.id == post.id
|
assert result.id == post.id
|
||||||
end
|
end
|
||||||
|
@ -28,7 +28,7 @@ test "using plainto_tsquery on postgres < 11" do
|
||||||
{:ok, _post2} = CommonAPI.post(user, %{status: "it's wednesday my bros"})
|
{:ok, _post2} = CommonAPI.post(user, %{status: "it's wednesday my bros"})
|
||||||
|
|
||||||
# plainto doesn't understand complex queries
|
# plainto doesn't understand complex queries
|
||||||
assert [result] = Search.search(nil, "wednesday -dudes")
|
assert [result] = DatabaseSearch.search(nil, "wednesday -dudes")
|
||||||
|
|
||||||
assert result.id == post.id
|
assert result.id == post.id
|
||||||
end
|
end
|
||||||
|
@ -38,7 +38,7 @@ test "using websearch_to_tsquery" do
|
||||||
{:ok, _post} = CommonAPI.post(user, %{status: "it's wednesday my dudes"})
|
{:ok, _post} = CommonAPI.post(user, %{status: "it's wednesday my dudes"})
|
||||||
{:ok, other_post} = CommonAPI.post(user, %{status: "it's wednesday my bros"})
|
{:ok, other_post} = CommonAPI.post(user, %{status: "it's wednesday my bros"})
|
||||||
|
|
||||||
assert [result] = Search.search(nil, "wednesday -dudes")
|
assert [result] = DatabaseSearch.search(nil, "wednesday -dudes")
|
||||||
|
|
||||||
assert result.id == other_post.id
|
assert result.id == other_post.id
|
||||||
end
|
end
|
120
test/pleroma/search/elasticsearch_test.exs
Normal file
120
test/pleroma/search/elasticsearch_test.exs
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Search.ElasticsearchTest do
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
use Pleroma.DataCase
|
||||||
|
use Oban.Testing, repo: Pleroma.Repo
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
import Tesla.Mock
|
||||||
|
import Mock
|
||||||
|
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Workers.SearchIndexingWorker
|
||||||
|
|
||||||
|
describe "elasticsearch" do
|
||||||
|
setup do
|
||||||
|
clear_config([Pleroma.Search, :module], Pleroma.Search.Elasticsearch)
|
||||||
|
clear_config([Pleroma.Search.Elasticsearch.Cluster, :api], Pleroma.ElasticsearchMock)
|
||||||
|
end
|
||||||
|
|
||||||
|
setup_with_mocks(
|
||||||
|
[
|
||||||
|
{Pleroma.Search.Elasticsearch, [:passthrough],
|
||||||
|
[
|
||||||
|
add_to_index: fn a -> passthrough([a]) end,
|
||||||
|
remove_from_index: fn a -> passthrough([a]) end
|
||||||
|
]},
|
||||||
|
{Elasticsearch, [:passthrough],
|
||||||
|
[
|
||||||
|
put_document: fn _, _, _ -> :ok end,
|
||||||
|
delete_document: fn _, _, _ -> :ok end
|
||||||
|
]}
|
||||||
|
],
|
||||||
|
context,
|
||||||
|
do: {:ok, context}
|
||||||
|
)
|
||||||
|
|
||||||
|
test "indexes a local post on creation" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
status: "guys i just don't wanna leave the swamp",
|
||||||
|
visibility: "public"
|
||||||
|
})
|
||||||
|
|
||||||
|
args = %{"op" => "add_to_index", "activity" => activity.id}
|
||||||
|
|
||||||
|
assert_enqueued(
|
||||||
|
worker: SearchIndexingWorker,
|
||||||
|
args: args
|
||||||
|
)
|
||||||
|
|
||||||
|
assert :ok = perform_job(SearchIndexingWorker, args)
|
||||||
|
|
||||||
|
assert_called(Pleroma.Search.Elasticsearch.add_to_index(activity))
|
||||||
|
end
|
||||||
|
|
||||||
|
test "doesn't index posts that are not public" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
Enum.each(["private", "direct"], fn visibility ->
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
status: "guys i just don't wanna leave the swamp",
|
||||||
|
visibility: visibility
|
||||||
|
})
|
||||||
|
|
||||||
|
args = %{"op" => "add_to_index", "activity" => activity.id}
|
||||||
|
|
||||||
|
assert_enqueued(worker: SearchIndexingWorker, args: args)
|
||||||
|
assert :ok = perform_job(SearchIndexingWorker, args)
|
||||||
|
|
||||||
|
assert_not_called(Elasticsearch.put_document(:_))
|
||||||
|
end)
|
||||||
|
|
||||||
|
history = call_history(Pleroma.Search.Elasticsearch)
|
||||||
|
assert Enum.count(history) == 2
|
||||||
|
end
|
||||||
|
|
||||||
|
test "deletes posts from index when deleted locally" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
mock_global(fn
|
||||||
|
%{method: :put, url: "http://127.0.0.1:7700/indexes/objects/documents", body: body} ->
|
||||||
|
assert match?(
|
||||||
|
[%{"content" => "guys i just don't wanna leave the swamp"}],
|
||||||
|
Jason.decode!(body)
|
||||||
|
)
|
||||||
|
|
||||||
|
json(%{updateId: 1})
|
||||||
|
|
||||||
|
%{method: :delete, url: "http://127.0.0.1:7700/indexes/objects/documents/" <> id} ->
|
||||||
|
assert String.length(id) > 1
|
||||||
|
json(%{updateId: 2})
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
status: "guys i just don't wanna leave the swamp",
|
||||||
|
visibility: "public"
|
||||||
|
})
|
||||||
|
|
||||||
|
args = %{"op" => "add_to_index", "activity" => activity.id}
|
||||||
|
assert_enqueued(worker: SearchIndexingWorker, args: args)
|
||||||
|
assert :ok = perform_job(SearchIndexingWorker, args)
|
||||||
|
|
||||||
|
{:ok, _} = CommonAPI.delete(activity.id, user)
|
||||||
|
|
||||||
|
delete_args = %{"op" => "remove_from_index", "object" => activity.object.id}
|
||||||
|
assert_enqueued(worker: SearchIndexingWorker, args: delete_args)
|
||||||
|
assert :ok = perform_job(SearchIndexingWorker, delete_args)
|
||||||
|
|
||||||
|
assert_called(Pleroma.Search.Elasticsearch.remove_from_index(:_))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
129
test/pleroma/search/meilisearch_test.exs
Normal file
129
test/pleroma/search/meilisearch_test.exs
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Search.MeilisearchTest do
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
use Pleroma.DataCase
|
||||||
|
use Oban.Testing, repo: Pleroma.Repo
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
import Tesla.Mock
|
||||||
|
import Mock
|
||||||
|
|
||||||
|
alias Pleroma.Search.Meilisearch
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Workers.SearchIndexingWorker
|
||||||
|
|
||||||
|
setup_all do
|
||||||
|
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "meilisearch" do
|
||||||
|
setup do: clear_config([Pleroma.Search, :module], Meilisearch)
|
||||||
|
|
||||||
|
setup_with_mocks(
|
||||||
|
[
|
||||||
|
{Meilisearch, [:passthrough],
|
||||||
|
[
|
||||||
|
add_to_index: fn a -> passthrough([a]) end,
|
||||||
|
remove_from_index: fn a -> passthrough([a]) end,
|
||||||
|
meili_put: fn u, a -> passthrough([u, a]) end
|
||||||
|
]}
|
||||||
|
],
|
||||||
|
context,
|
||||||
|
do: {:ok, context}
|
||||||
|
)
|
||||||
|
|
||||||
|
test "indexes a local post on creation" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
mock_global(fn
|
||||||
|
%{method: :put, url: "http://127.0.0.1:7700/indexes/objects/documents", body: body} ->
|
||||||
|
assert match?(
|
||||||
|
[%{"content" => "guys i just don't wanna leave the swamp"}],
|
||||||
|
Jason.decode!(body)
|
||||||
|
)
|
||||||
|
|
||||||
|
json(%{updateId: 1})
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
status: "guys i just don't wanna leave the swamp",
|
||||||
|
visibility: "public"
|
||||||
|
})
|
||||||
|
|
||||||
|
args = %{"op" => "add_to_index", "activity" => activity.id}
|
||||||
|
|
||||||
|
assert_enqueued(
|
||||||
|
worker: SearchIndexingWorker,
|
||||||
|
args: args
|
||||||
|
)
|
||||||
|
|
||||||
|
assert :ok = perform_job(SearchIndexingWorker, args)
|
||||||
|
|
||||||
|
assert_called(Meilisearch.add_to_index(activity))
|
||||||
|
end
|
||||||
|
|
||||||
|
test "doesn't index posts that are not public" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
Enum.each(["private", "direct"], fn visibility ->
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
status: "guys i just don't wanna leave the swamp",
|
||||||
|
visibility: visibility
|
||||||
|
})
|
||||||
|
|
||||||
|
args = %{"op" => "add_to_index", "activity" => activity.id}
|
||||||
|
|
||||||
|
assert_enqueued(worker: SearchIndexingWorker, args: args)
|
||||||
|
assert :ok = perform_job(SearchIndexingWorker, args)
|
||||||
|
|
||||||
|
assert_not_called(Meilisearch.meili_put(:_))
|
||||||
|
end)
|
||||||
|
|
||||||
|
history = call_history(Meilisearch)
|
||||||
|
assert Enum.count(history) == 2
|
||||||
|
end
|
||||||
|
|
||||||
|
test "deletes posts from index when deleted locally" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
mock_global(fn
|
||||||
|
%{method: :put, url: "http://127.0.0.1:7700/indexes/objects/documents", body: body} ->
|
||||||
|
assert match?(
|
||||||
|
[%{"content" => "guys i just don't wanna leave the swamp"}],
|
||||||
|
Jason.decode!(body)
|
||||||
|
)
|
||||||
|
|
||||||
|
json(%{updateId: 1})
|
||||||
|
|
||||||
|
%{method: :delete, url: "http://127.0.0.1:7700/indexes/objects/documents/" <> id} ->
|
||||||
|
assert String.length(id) > 1
|
||||||
|
json(%{updateId: 2})
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
status: "guys i just don't wanna leave the swamp",
|
||||||
|
visibility: "public"
|
||||||
|
})
|
||||||
|
|
||||||
|
args = %{"op" => "add_to_index", "activity" => activity.id}
|
||||||
|
assert_enqueued(worker: SearchIndexingWorker, args: args)
|
||||||
|
assert :ok = perform_job(SearchIndexingWorker, args)
|
||||||
|
|
||||||
|
{:ok, _} = CommonAPI.delete(activity.id, user)
|
||||||
|
|
||||||
|
delete_args = %{"op" => "remove_from_index", "object" => activity.object.id}
|
||||||
|
assert_enqueued(worker: SearchIndexingWorker, args: delete_args)
|
||||||
|
assert :ok = perform_job(SearchIndexingWorker, delete_args)
|
||||||
|
|
||||||
|
assert_called(Meilisearch.remove_from_index(:_))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,6 +5,7 @@
|
||||||
defmodule Pleroma.UserTest do
|
defmodule Pleroma.UserTest do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Builders.UserBuilder
|
alias Pleroma.Builders.UserBuilder
|
||||||
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.Tests.ObanHelpers
|
alias Pleroma.Tests.ObanHelpers
|
||||||
|
@ -2153,6 +2154,26 @@ test "performs update cache if user updated" do
|
||||||
assert {:ok, user} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}")
|
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)
|
assert %User{bio: "test-bio"} = User.get_cached_by_ap_id(user.ap_id)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe "following/followers synchronization" do
|
describe "following/followers synchronization" do
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue