@rmdes/indiekit-endpoint-webmention-io 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +270 -0
- package/lib/controllers/blocklist.js +5 -1
- package/lib/controllers/dashboard.js +2 -2
- package/lib/storage/webmentions.js +5 -5
- package/lib/utils.js +15 -0
- package/locales/de.json +75 -0
- package/locales/es-419.json +75 -0
- package/locales/es.json +75 -0
- package/locales/fr.json +75 -0
- package/locales/hi.json +75 -0
- package/locales/id.json +75 -0
- package/locales/it.json +75 -0
- package/locales/nl.json +75 -0
- package/locales/pl.json +75 -0
- package/locales/pt-BR.json +75 -0
- package/locales/pt.json +75 -0
- package/locales/sr.json +75 -0
- package/locales/sv.json +75 -0
- package/locales/zh-Hans-CN.json +75 -0
- package/package.json +1 -1
- package/views/webmentions-blocklist.njk +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# @rmdes/indiekit-endpoint-webmention-io
|
|
2
|
+
|
|
3
|
+
Webmention moderation endpoint for [Indiekit](https://getindiekit.com). Syncs webmentions from webmention.io into MongoDB with delete, block, and privacy removal capabilities.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Background Sync**: Automatically fetches webmentions from webmention.io every 15 minutes (configurable)
|
|
8
|
+
- **Moderation Dashboard**: Admin UI for hiding/unhiding webmentions
|
|
9
|
+
- **Domain Blocking**: Block spam domains (hides all mentions, blocks future ones)
|
|
10
|
+
- **Privacy Removal**: GDPR-compliant permanent deletion with domain blocking
|
|
11
|
+
- **Public JSON API**: Drop-in replacement for webmention.io API with server-side caching
|
|
12
|
+
- **MongoDB Storage**: Persistent storage with indexes for fast queries
|
|
13
|
+
- **Incremental Sync**: Only fetches new webmentions since last sync (efficient)
|
|
14
|
+
- **Full Re-sync**: Option to clear and re-import all webmentions
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @rmdes/indiekit-endpoint-webmention-io
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Configuration
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
// indiekit.config.js
|
|
26
|
+
export default {
|
|
27
|
+
plugins: [
|
|
28
|
+
"@rmdes/indiekit-endpoint-webmention-io",
|
|
29
|
+
],
|
|
30
|
+
|
|
31
|
+
"@rmdes/indiekit-endpoint-webmention-io": {
|
|
32
|
+
mountPath: "/webmentions", // Optional, default "/webmentions"
|
|
33
|
+
token: process.env.WEBMENTION_IO_TOKEN, // REQUIRED: webmention.io API token
|
|
34
|
+
domain: "example.com", // REQUIRED: domain to fetch webmentions for
|
|
35
|
+
syncInterval: 900_000, // Optional, default 15 minutes (in ms)
|
|
36
|
+
cacheTtl: 60, // Optional, default 60 seconds (public API cache)
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Getting Your Webmention.io Token
|
|
42
|
+
|
|
43
|
+
1. Sign up at [webmention.io](https://webmention.io)
|
|
44
|
+
2. Add your domain
|
|
45
|
+
3. Find your token in the dashboard
|
|
46
|
+
4. Store it in your `.env` file:
|
|
47
|
+
```bash
|
|
48
|
+
WEBMENTION_IO_TOKEN=your_token_here
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Usage
|
|
52
|
+
|
|
53
|
+
### Admin Dashboard
|
|
54
|
+
|
|
55
|
+
Visit `/webmentions` in your Indiekit admin panel to:
|
|
56
|
+
|
|
57
|
+
- View all webmentions (paginated)
|
|
58
|
+
- Filter by visibility (all/visible/hidden)
|
|
59
|
+
- Filter by type (likes/replies/reposts/mentions)
|
|
60
|
+
- Hide/unhide individual webmentions
|
|
61
|
+
- Block spam domains
|
|
62
|
+
- Remove mentions for privacy requests (GDPR)
|
|
63
|
+
|
|
64
|
+
### Manual Sync
|
|
65
|
+
|
|
66
|
+
Trigger sync via admin dashboard buttons:
|
|
67
|
+
- **Sync Now**: Incremental sync (fetch only new mentions since last sync)
|
|
68
|
+
- **Full Re-sync**: Delete all and re-import everything from webmention.io
|
|
69
|
+
|
|
70
|
+
Or via POST requests:
|
|
71
|
+
```bash
|
|
72
|
+
# Incremental sync
|
|
73
|
+
curl -X POST https://your-site.com/webmentions/sync
|
|
74
|
+
|
|
75
|
+
# Full re-sync (destructive!)
|
|
76
|
+
curl -X POST https://your-site.com/webmentions/sync/full
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Public JSON API
|
|
80
|
+
|
|
81
|
+
The plugin exposes a public JSON API at `/webmentions/api/mentions` that can replace direct calls to webmention.io:
|
|
82
|
+
|
|
83
|
+
**Fetch all webmentions:**
|
|
84
|
+
```javascript
|
|
85
|
+
fetch('/webmentions/api/mentions?page=0&per-page=50')
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Filter by target URL:**
|
|
89
|
+
```javascript
|
|
90
|
+
fetch('/webmentions/api/mentions?target=https://example.com/post')
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Filter by type:**
|
|
94
|
+
```javascript
|
|
95
|
+
fetch('/webmentions/api/mentions?wm-property=like-of')
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Response format (JF2):**
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"type": "feed",
|
|
102
|
+
"name": "Webmentions",
|
|
103
|
+
"children": [
|
|
104
|
+
{
|
|
105
|
+
"type": "entry",
|
|
106
|
+
"wm-id": 12345,
|
|
107
|
+
"wm-received": "2025-02-13T10:00:00.000Z",
|
|
108
|
+
"wm-property": "in-reply-to",
|
|
109
|
+
"wm-target": "https://example.com/post",
|
|
110
|
+
"author": {
|
|
111
|
+
"type": "card",
|
|
112
|
+
"name": "Author Name",
|
|
113
|
+
"url": "https://author.site/",
|
|
114
|
+
"photo": "https://author.site/photo.jpg"
|
|
115
|
+
},
|
|
116
|
+
"url": "https://source.site/post",
|
|
117
|
+
"published": "2025-02-13T09:00:00.000Z",
|
|
118
|
+
"content": {
|
|
119
|
+
"html": "<p>Reply text...</p>",
|
|
120
|
+
"text": "Reply text..."
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Moderation Workflows
|
|
128
|
+
|
|
129
|
+
#### Hide a webmention
|
|
130
|
+
```bash
|
|
131
|
+
POST /webmentions/:wmId/hide
|
|
132
|
+
```
|
|
133
|
+
Marks the webmention as hidden (won't appear in public API).
|
|
134
|
+
|
|
135
|
+
#### Unhide a webmention
|
|
136
|
+
```bash
|
|
137
|
+
POST /webmentions/:wmId/unhide
|
|
138
|
+
```
|
|
139
|
+
Restores a hidden webmention.
|
|
140
|
+
|
|
141
|
+
#### Block a domain
|
|
142
|
+
```bash
|
|
143
|
+
POST /webmentions/block
|
|
144
|
+
Body: domain=spam.example.com
|
|
145
|
+
```
|
|
146
|
+
- Hides all existing mentions from the domain
|
|
147
|
+
- Adds domain to blocklist
|
|
148
|
+
- Future mentions from this domain are filtered during sync
|
|
149
|
+
|
|
150
|
+
#### Privacy removal (GDPR)
|
|
151
|
+
```bash
|
|
152
|
+
POST /webmentions/privacy-remove
|
|
153
|
+
Body: domain=user-request.example.com
|
|
154
|
+
```
|
|
155
|
+
- **Permanently deletes** all mentions from the domain
|
|
156
|
+
- Adds domain to blocklist with reason="privacy"
|
|
157
|
+
- Irreversible - use for GDPR/privacy requests only
|
|
158
|
+
|
|
159
|
+
#### Unblock a domain
|
|
160
|
+
```bash
|
|
161
|
+
POST /webmentions/blocklist/:domain/delete
|
|
162
|
+
```
|
|
163
|
+
- Removes domain from blocklist
|
|
164
|
+
- Unhides mentions that were hidden by the blocklist (not manual or privacy)
|
|
165
|
+
|
|
166
|
+
## API Reference
|
|
167
|
+
|
|
168
|
+
### Query Parameters
|
|
169
|
+
|
|
170
|
+
| Parameter | Type | Description |
|
|
171
|
+
|-----------|------|-------------|
|
|
172
|
+
| `page` | number | Page number (0-indexed, default: 0) |
|
|
173
|
+
| `per-page` | number | Items per page (max 10,000, default: 50) |
|
|
174
|
+
| `target` | string | Filter by target URL (with/without trailing slash) |
|
|
175
|
+
| `wm-property` | string | Filter by type: `in-reply-to`, `like-of`, `repost-of`, `mention-of`, `bookmark-of`, `rsvp` |
|
|
176
|
+
|
|
177
|
+
### Webmention Types
|
|
178
|
+
|
|
179
|
+
- `in-reply-to` - Replies
|
|
180
|
+
- `like-of` - Likes
|
|
181
|
+
- `repost-of` - Reposts/boosts
|
|
182
|
+
- `mention-of` - General mentions
|
|
183
|
+
- `bookmark-of` - Bookmarks
|
|
184
|
+
- `rsvp` - RSVP responses
|
|
185
|
+
|
|
186
|
+
## MongoDB Schema
|
|
187
|
+
|
|
188
|
+
The plugin creates two MongoDB collections:
|
|
189
|
+
|
|
190
|
+
### `webmentions`
|
|
191
|
+
|
|
192
|
+
```javascript
|
|
193
|
+
{
|
|
194
|
+
wmId: 12345, // Webmention ID (unique)
|
|
195
|
+
wmReceived: "2025-02-13T10:00:00.000Z",
|
|
196
|
+
wmProperty: "in-reply-to",
|
|
197
|
+
wmTarget: "https://example.com/post",
|
|
198
|
+
authorName: "Author Name",
|
|
199
|
+
authorUrl: "https://author.site/",
|
|
200
|
+
authorPhoto: "https://author.site/photo.jpg",
|
|
201
|
+
sourceUrl: "https://source.site/post",
|
|
202
|
+
sourceDomain: "source.site",
|
|
203
|
+
published: "2025-02-13T09:00:00.000Z",
|
|
204
|
+
contentHtml: "<p>Reply text...</p>",
|
|
205
|
+
contentText: "Reply text...",
|
|
206
|
+
name: "Post title",
|
|
207
|
+
hidden: false,
|
|
208
|
+
hiddenAt: null,
|
|
209
|
+
hiddenReason: null, // "manual", "blocklist", "privacy"
|
|
210
|
+
syncedAt: "2025-02-13T10:00:00.000Z",
|
|
211
|
+
raw: { ... } // Original JF2 entry
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### `webmentionBlocklist`
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
{
|
|
219
|
+
domain: "spam.example.com",
|
|
220
|
+
reason: "spam", // "spam", "privacy", "manual"
|
|
221
|
+
blockedAt: "2025-02-13T10:00:00.000Z",
|
|
222
|
+
mentionsHidden: 5
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## How It Works
|
|
227
|
+
|
|
228
|
+
1. **Background Sync**: Runs every 15 minutes (configurable)
|
|
229
|
+
2. **Incremental Fetching**: Uses `since_id` to only fetch new mentions
|
|
230
|
+
3. **Blocklist Filtering**: Mentions from blocked domains are never stored
|
|
231
|
+
4. **Pagination**: Fetches 100 mentions per page from webmention.io
|
|
232
|
+
5. **Rate Limiting**: 500ms delay between pages to avoid rate limits
|
|
233
|
+
6. **Caching**: Public API responses cached for 60 seconds (configurable)
|
|
234
|
+
|
|
235
|
+
## HTML Sanitization
|
|
236
|
+
|
|
237
|
+
All webmention HTML content is sanitized:
|
|
238
|
+
- Strips empty bridgy links
|
|
239
|
+
- Strips empty paragraphs
|
|
240
|
+
- Downgrades heading levels (h1→h3, h2→h4)
|
|
241
|
+
- Normalizes line breaks to paragraph breaks
|
|
242
|
+
|
|
243
|
+
## Comparison with Other Plugins
|
|
244
|
+
|
|
245
|
+
| Plugin | Purpose | Storage | API |
|
|
246
|
+
|--------|---------|---------|-----|
|
|
247
|
+
| **@rmdes/indiekit-endpoint-webmention-io** | Full moderation + public API | MongoDB | JF2 JSON |
|
|
248
|
+
| `@rmdes/indiekit-endpoint-webmentions-proxy` | Simple proxy (no moderation) | None | JF2 JSON |
|
|
249
|
+
| `@indiekit/endpoint-webmention` (upstream) | Admin dashboard only | None | HTML only |
|
|
250
|
+
|
|
251
|
+
Use this plugin if you need:
|
|
252
|
+
- Moderation capabilities
|
|
253
|
+
- Domain blocking
|
|
254
|
+
- Privacy removal (GDPR)
|
|
255
|
+
- Public API with caching
|
|
256
|
+
- Persistent storage
|
|
257
|
+
|
|
258
|
+
Use `@rmdes/indiekit-endpoint-webmentions-proxy` if you only need a simple public API without moderation.
|
|
259
|
+
|
|
260
|
+
## License
|
|
261
|
+
|
|
262
|
+
MIT
|
|
263
|
+
|
|
264
|
+
## Author
|
|
265
|
+
|
|
266
|
+
Ricardo Mendes - [rmendes.net](https://rmendes.net)
|
|
267
|
+
|
|
268
|
+
## Repository
|
|
269
|
+
|
|
270
|
+
[https://github.com/rmdes/indiekit-endpoint-webmention-io](https://github.com/rmdes/indiekit-endpoint-webmention-io)
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { getBlocklist, unblockDomain } from "../storage/blocklist.js";
|
|
6
6
|
import { unhideByDomain } from "../storage/webmentions.js";
|
|
7
|
+
import { ensureISOString } from "../utils.js";
|
|
7
8
|
|
|
8
9
|
export const blocklistController = {
|
|
9
10
|
/**
|
|
@@ -19,7 +20,10 @@ export const blocklistController = {
|
|
|
19
20
|
if (db) {
|
|
20
21
|
const collection = db.collection("webmentionBlocklist");
|
|
21
22
|
const raw = await getBlocklist(collection);
|
|
22
|
-
entries = raw
|
|
23
|
+
entries = raw.map((entry) => ({
|
|
24
|
+
...entry,
|
|
25
|
+
blockedAt: ensureISOString(entry.blockedAt),
|
|
26
|
+
}));
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
response.render("webmentions-blocklist", {
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "../storage/webmentions.js";
|
|
14
14
|
import { blockDomain } from "../storage/blocklist.js";
|
|
15
15
|
import { getSyncState } from "../sync.js";
|
|
16
|
-
import { getMentionType, getMentionTitle, getAuthorName } from "../utils.js";
|
|
16
|
+
import { getMentionType, getMentionTitle, getAuthorName, ensureISOString } from "../utils.js";
|
|
17
17
|
|
|
18
18
|
export const dashboardController = {
|
|
19
19
|
/**
|
|
@@ -82,7 +82,7 @@ export const dashboardController = {
|
|
|
82
82
|
locale: application.locale,
|
|
83
83
|
title: getMentionTitle({ name: item.name, "wm-property": item.wmProperty }),
|
|
84
84
|
description: html ? { html } : undefined,
|
|
85
|
-
published: item.published || item.wmReceived,
|
|
85
|
+
published: ensureISOString(item.published) || ensureISOString(item.wmReceived),
|
|
86
86
|
url: item.sourceUrl,
|
|
87
87
|
user: {
|
|
88
88
|
avatar: { src: item.authorPhoto },
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Webmentions MongoDB storage
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { extractDomain, sanitiseHtml } from "../utils.js";
|
|
5
|
+
import { extractDomain, ensureISOString, sanitiseHtml } from "../utils.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Ensure indexes exist
|
|
@@ -36,7 +36,7 @@ export function jf2ToDocument(item) {
|
|
|
36
36
|
|
|
37
37
|
return {
|
|
38
38
|
wmId: item["wm-id"],
|
|
39
|
-
wmReceived: item["wm-received"] || new Date().toISOString(),
|
|
39
|
+
wmReceived: ensureISOString(item["wm-received"]) || new Date().toISOString(),
|
|
40
40
|
wmProperty: item["wm-property"],
|
|
41
41
|
wmTarget: item["wm-target"],
|
|
42
42
|
authorName: item.author?.name || null,
|
|
@@ -44,7 +44,7 @@ export function jf2ToDocument(item) {
|
|
|
44
44
|
authorPhoto: item.author?.photo || null,
|
|
45
45
|
sourceUrl: item.url || null,
|
|
46
46
|
sourceDomain: extractDomain(item.author?.url || item.url || ""),
|
|
47
|
-
published: item.published
|
|
47
|
+
published: ensureISOString(item.published),
|
|
48
48
|
contentHtml,
|
|
49
49
|
contentText,
|
|
50
50
|
name: item.name || null,
|
|
@@ -65,7 +65,7 @@ export function documentToJf2(doc) {
|
|
|
65
65
|
const jf2 = {
|
|
66
66
|
type: "entry",
|
|
67
67
|
"wm-id": doc.wmId,
|
|
68
|
-
"wm-received": doc.wmReceived,
|
|
68
|
+
"wm-received": ensureISOString(doc.wmReceived),
|
|
69
69
|
"wm-property": doc.wmProperty,
|
|
70
70
|
"wm-target": doc.wmTarget,
|
|
71
71
|
author: {
|
|
@@ -75,7 +75,7 @@ export function documentToJf2(doc) {
|
|
|
75
75
|
photo: doc.authorPhoto || "",
|
|
76
76
|
},
|
|
77
77
|
url: doc.sourceUrl || "",
|
|
78
|
-
published: doc.published || doc.wmReceived,
|
|
78
|
+
published: ensureISOString(doc.published) || ensureISOString(doc.wmReceived),
|
|
79
79
|
};
|
|
80
80
|
|
|
81
81
|
if (doc.name) {
|
package/lib/utils.js
CHANGED
|
@@ -101,6 +101,21 @@ export const sanitiseHtml = (html) => {
|
|
|
101
101
|
return html;
|
|
102
102
|
};
|
|
103
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Ensure a value is an ISO 8601 date string.
|
|
106
|
+
* MongoDB may auto-convert ISO strings to Date objects (BSON Date).
|
|
107
|
+
* The Nunjucks `| date` filter calls `parseISO(string)` which crashes
|
|
108
|
+
* on Date objects with `dateString.split is not a function`.
|
|
109
|
+
* @param {*} value - Date object, ISO string, or null
|
|
110
|
+
* @returns {string|null} ISO string or null
|
|
111
|
+
*/
|
|
112
|
+
export const ensureISOString = (value) => {
|
|
113
|
+
if (!value) return null;
|
|
114
|
+
if (value instanceof Date) return value.toISOString();
|
|
115
|
+
if (typeof value === "string") return value;
|
|
116
|
+
return null;
|
|
117
|
+
};
|
|
118
|
+
|
|
104
119
|
/**
|
|
105
120
|
* Extract domain from a URL string
|
|
106
121
|
* @param {string} url - URL
|
package/locales/de.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"webmention-io": {
|
|
3
|
+
"title": "Webmentions",
|
|
4
|
+
"webmentions": {
|
|
5
|
+
"none": "Keine Webmentions gefunden. Versuchen Sie eine Synchronisierung."
|
|
6
|
+
},
|
|
7
|
+
"mention": {
|
|
8
|
+
"bookmark-of": "hat %s mit einem Lesezeichen versehen",
|
|
9
|
+
"in-reply-to": "hat auf %s geantwortet",
|
|
10
|
+
"like-of": "gefällt %s",
|
|
11
|
+
"mention-of": "hat %s erwähnt",
|
|
12
|
+
"repost-of": "hat %s geteilt",
|
|
13
|
+
"rsvp": "hat auf %s geantwortet"
|
|
14
|
+
},
|
|
15
|
+
"sync": {
|
|
16
|
+
"now": "Jetzt synchronisieren",
|
|
17
|
+
"full": "Vollständige Neusynchronisierung",
|
|
18
|
+
"lastSync": "Zuletzt synchronisiert",
|
|
19
|
+
"never": "Nie synchronisiert",
|
|
20
|
+
"synced": "Synchronisierung abgeschlossen",
|
|
21
|
+
"added": "neue Erwähnungen hinzugefügt",
|
|
22
|
+
"inProgress": "Synchronisierung läuft…"
|
|
23
|
+
},
|
|
24
|
+
"filter": {
|
|
25
|
+
"all": "Alle",
|
|
26
|
+
"visible": "Sichtbar",
|
|
27
|
+
"hidden": "Versteckt",
|
|
28
|
+
"show": "Anzeigen",
|
|
29
|
+
"type": "Typ",
|
|
30
|
+
"likes": "Gefällt mir",
|
|
31
|
+
"replies": "Antworten",
|
|
32
|
+
"reposts": "Geteilte Beiträge",
|
|
33
|
+
"mentions": "Erwähnungen"
|
|
34
|
+
},
|
|
35
|
+
"actions": {
|
|
36
|
+
"hide": "Verstecken",
|
|
37
|
+
"unhide": "Einblenden",
|
|
38
|
+
"block": "Blockieren",
|
|
39
|
+
"hidden": "Versteckt",
|
|
40
|
+
"reasonManual": "manuell",
|
|
41
|
+
"reasonBlocklist": "Sperrliste",
|
|
42
|
+
"reasonPrivacy": "Datenschutz"
|
|
43
|
+
},
|
|
44
|
+
"blocklist": {
|
|
45
|
+
"title": "Webmention-Sperrliste",
|
|
46
|
+
"description": "Blockierte Domains werden nicht in Webmentions angezeigt. Vorhandene Erwähnungen von blockierten Domains werden automatisch ausgeblendet.",
|
|
47
|
+
"add": "Domain blockieren",
|
|
48
|
+
"domainLabel": "Domain",
|
|
49
|
+
"domainPlaceholder": "spam.example.com",
|
|
50
|
+
"reasonLabel": "Grund",
|
|
51
|
+
"reasonSpam": "Spam",
|
|
52
|
+
"reasonManual": "Manuell",
|
|
53
|
+
"blockButton": "Domain blockieren",
|
|
54
|
+
"unblock": "Entsperren",
|
|
55
|
+
"empty": "Keine Domains blockiert.",
|
|
56
|
+
"privacy": {
|
|
57
|
+
"title": "Datenschutzentfernung",
|
|
58
|
+
"description": "Alle Webmentions von einer Domain dauerhaft löschen und zukünftige blockieren. Verwenden Sie dies für GDPR- oder persönliche Entfernungsanfragen.",
|
|
59
|
+
"domainLabel": "Zu entfernende Domain",
|
|
60
|
+
"removeButton": "Entfernen & Blockieren"
|
|
61
|
+
},
|
|
62
|
+
"blocked": "Domain blockiert",
|
|
63
|
+
"unblocked": "Domain entsperrt",
|
|
64
|
+
"removed": "Erwähnungen dauerhaft gelöscht",
|
|
65
|
+
"unhidden": "Erwähnungen wiederhergestellt",
|
|
66
|
+
"mentionsHidden": "Erwähnungen ausgeblendet",
|
|
67
|
+
"blockedAt": "Blockiert"
|
|
68
|
+
},
|
|
69
|
+
"counts": {
|
|
70
|
+
"total": "gesamt",
|
|
71
|
+
"hidden": "versteckt",
|
|
72
|
+
"visible": "sichtbar"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"webmention-io": {
|
|
3
|
+
"title": "Webmentions",
|
|
4
|
+
"webmentions": {
|
|
5
|
+
"none": "No se encontraron webmentions. Intentá ejecutar una sincronización."
|
|
6
|
+
},
|
|
7
|
+
"mention": {
|
|
8
|
+
"bookmark-of": "marcó %s",
|
|
9
|
+
"in-reply-to": "respondió a %s",
|
|
10
|
+
"like-of": "le gustó %s",
|
|
11
|
+
"mention-of": "mencionó %s",
|
|
12
|
+
"repost-of": "compartió %s",
|
|
13
|
+
"rsvp": "respondió a %s"
|
|
14
|
+
},
|
|
15
|
+
"sync": {
|
|
16
|
+
"now": "Sincronizar ahora",
|
|
17
|
+
"full": "Resincronización completa",
|
|
18
|
+
"lastSync": "Última sincronización",
|
|
19
|
+
"never": "Nunca sincronizado",
|
|
20
|
+
"synced": "Sincronización completada",
|
|
21
|
+
"added": "nuevas menciones agregadas",
|
|
22
|
+
"inProgress": "Sincronización en curso…"
|
|
23
|
+
},
|
|
24
|
+
"filter": {
|
|
25
|
+
"all": "Todos",
|
|
26
|
+
"visible": "Visible",
|
|
27
|
+
"hidden": "Oculto",
|
|
28
|
+
"show": "Mostrar",
|
|
29
|
+
"type": "Tipo",
|
|
30
|
+
"likes": "Me gusta",
|
|
31
|
+
"replies": "Respuestas",
|
|
32
|
+
"reposts": "Compartidos",
|
|
33
|
+
"mentions": "Menciones"
|
|
34
|
+
},
|
|
35
|
+
"actions": {
|
|
36
|
+
"hide": "Ocultar",
|
|
37
|
+
"unhide": "Mostrar",
|
|
38
|
+
"block": "Bloquear",
|
|
39
|
+
"hidden": "Oculto",
|
|
40
|
+
"reasonManual": "manual",
|
|
41
|
+
"reasonBlocklist": "lista de bloqueo",
|
|
42
|
+
"reasonPrivacy": "privacidad"
|
|
43
|
+
},
|
|
44
|
+
"blocklist": {
|
|
45
|
+
"title": "Lista de bloqueo de Webmentions",
|
|
46
|
+
"description": "Los dominios bloqueados no aparecerán en webmentions. Las menciones existentes de dominios bloqueados se ocultan automáticamente.",
|
|
47
|
+
"add": "Bloquear un dominio",
|
|
48
|
+
"domainLabel": "Dominio",
|
|
49
|
+
"domainPlaceholder": "spam.example.com",
|
|
50
|
+
"reasonLabel": "Motivo",
|
|
51
|
+
"reasonSpam": "Spam",
|
|
52
|
+
"reasonManual": "Manual",
|
|
53
|
+
"blockButton": "Bloquear dominio",
|
|
54
|
+
"unblock": "Desbloquear",
|
|
55
|
+
"empty": "No hay dominios bloqueados.",
|
|
56
|
+
"privacy": {
|
|
57
|
+
"title": "Eliminación por privacidad",
|
|
58
|
+
"description": "Eliminar permanentemente todos los webmentions de un dominio y bloquear futuros. Usar para solicitudes GDPR o de eliminación personal.",
|
|
59
|
+
"domainLabel": "Dominio a eliminar",
|
|
60
|
+
"removeButton": "Eliminar y bloquear"
|
|
61
|
+
},
|
|
62
|
+
"blocked": "Dominio bloqueado",
|
|
63
|
+
"unblocked": "Dominio desbloqueado",
|
|
64
|
+
"removed": "menciones eliminadas permanentemente",
|
|
65
|
+
"unhidden": "menciones restauradas",
|
|
66
|
+
"mentionsHidden": "menciones ocultas",
|
|
67
|
+
"blockedAt": "Bloqueado"
|
|
68
|
+
},
|
|
69
|
+
"counts": {
|
|
70
|
+
"total": "total",
|
|
71
|
+
"hidden": "oculto",
|
|
72
|
+
"visible": "visible"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
package/locales/es.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"webmention-io": {
|
|
3
|
+
"title": "Webmentions",
|
|
4
|
+
"webmentions": {
|
|
5
|
+
"none": "No se han encontrado webmentions. Intente ejecutar una sincronización."
|
|
6
|
+
},
|
|
7
|
+
"mention": {
|
|
8
|
+
"bookmark-of": "marcó %s",
|
|
9
|
+
"in-reply-to": "respondió a %s",
|
|
10
|
+
"like-of": "le gustó %s",
|
|
11
|
+
"mention-of": "mencionó %s",
|
|
12
|
+
"repost-of": "reenvió %s",
|
|
13
|
+
"rsvp": "respondió a %s"
|
|
14
|
+
},
|
|
15
|
+
"sync": {
|
|
16
|
+
"now": "Sincronizar ahora",
|
|
17
|
+
"full": "Resincronización completa",
|
|
18
|
+
"lastSync": "Última sincronización",
|
|
19
|
+
"never": "Nunca sincronizado",
|
|
20
|
+
"synced": "Sincronización completada",
|
|
21
|
+
"added": "nuevas menciones añadidas",
|
|
22
|
+
"inProgress": "Sincronización en curso…"
|
|
23
|
+
},
|
|
24
|
+
"filter": {
|
|
25
|
+
"all": "Todos",
|
|
26
|
+
"visible": "Visible",
|
|
27
|
+
"hidden": "Oculto",
|
|
28
|
+
"show": "Mostrar",
|
|
29
|
+
"type": "Tipo",
|
|
30
|
+
"likes": "Me gusta",
|
|
31
|
+
"replies": "Respuestas",
|
|
32
|
+
"reposts": "Reenvíos",
|
|
33
|
+
"mentions": "Menciones"
|
|
34
|
+
},
|
|
35
|
+
"actions": {
|
|
36
|
+
"hide": "Ocultar",
|
|
37
|
+
"unhide": "Mostrar",
|
|
38
|
+
"block": "Bloquear",
|
|
39
|
+
"hidden": "Oculto",
|
|
40
|
+
"reasonManual": "manual",
|
|
41
|
+
"reasonBlocklist": "lista de bloqueo",
|
|
42
|
+
"reasonPrivacy": "privacidad"
|
|
43
|
+
},
|
|
44
|
+
"blocklist": {
|
|
45
|
+
"title": "Lista de bloqueo de Webmentions",
|
|
46
|
+
"description": "Los dominios bloqueados no aparecerán en webmentions. Las menciones existentes de dominios bloqueados se ocultan automáticamente.",
|
|
47
|
+
"add": "Bloquear un dominio",
|
|
48
|
+
"domainLabel": "Dominio",
|
|
49
|
+
"domainPlaceholder": "spam.example.com",
|
|
50
|
+
"reasonLabel": "Motivo",
|
|
51
|
+
"reasonSpam": "Spam",
|
|
52
|
+
"reasonManual": "Manual",
|
|
53
|
+
"blockButton": "Bloquear dominio",
|
|
54
|
+
"unblock": "Desbloquear",
|
|
55
|
+
"empty": "No hay dominios bloqueados.",
|
|
56
|
+
"privacy": {
|
|
57
|
+
"title": "Eliminación por privacidad",
|
|
58
|
+
"description": "Eliminar permanentemente todos los webmentions de un dominio y bloquear futuros. Usar para solicitudes GDPR o de eliminación personal.",
|
|
59
|
+
"domainLabel": "Dominio a eliminar",
|
|
60
|
+
"removeButton": "Eliminar y bloquear"
|
|
61
|
+
},
|
|
62
|
+
"blocked": "Dominio bloqueado",
|
|
63
|
+
"unblocked": "Dominio desbloqueado",
|
|
64
|
+
"removed": "menciones eliminadas permanentemente",
|
|
65
|
+
"unhidden": "menciones restauradas",
|
|
66
|
+
"mentionsHidden": "menciones ocultas",
|
|
67
|
+
"blockedAt": "Bloqueado"
|
|
68
|
+
},
|
|
69
|
+
"counts": {
|
|
70
|
+
"total": "total",
|
|
71
|
+
"hidden": "oculto",
|
|
72
|
+
"visible": "visible"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
package/locales/fr.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"webmention-io": {
|
|
3
|
+
"title": "Webmentions",
|
|
4
|
+
"webmentions": {
|
|
5
|
+
"none": "Aucun webmention trouvé. Essayez d'exécuter une synchronisation."
|
|
6
|
+
},
|
|
7
|
+
"mention": {
|
|
8
|
+
"bookmark-of": "a mis %s en signet",
|
|
9
|
+
"in-reply-to": "a répondu à %s",
|
|
10
|
+
"like-of": "a aimé %s",
|
|
11
|
+
"mention-of": "a mentionné %s",
|
|
12
|
+
"repost-of": "a repartagé %s",
|
|
13
|
+
"rsvp": "a répondu à %s"
|
|
14
|
+
},
|
|
15
|
+
"sync": {
|
|
16
|
+
"now": "Synchroniser maintenant",
|
|
17
|
+
"full": "Resynchronisation complète",
|
|
18
|
+
"lastSync": "Dernière synchronisation",
|
|
19
|
+
"never": "Jamais synchronisé",
|
|
20
|
+
"synced": "Synchronisation terminée",
|
|
21
|
+
"added": "nouvelles mentions ajoutées",
|
|
22
|
+
"inProgress": "Synchronisation en cours…"
|
|
23
|
+
},
|
|
24
|
+
"filter": {
|
|
25
|
+
"all": "Tous",
|
|
26
|
+
"visible": "Visible",
|
|
27
|
+
"hidden": "Masqué",
|
|
28
|
+
"show": "Afficher",
|
|
29
|
+
"type": "Type",
|
|
30
|
+
"likes": "J'aime",
|
|
31
|
+
"replies": "Réponses",
|
|
32
|
+
"reposts": "Repartages",
|
|
33
|
+
"mentions": "Mentions"
|
|
34
|
+
},
|
|
35
|
+
"actions": {
|
|
36
|
+
"hide": "Masquer",
|
|
37
|
+
"unhide": "Afficher",
|
|
38
|
+
"block": "Bloquer",
|
|
39
|
+
"hidden": "Masqué",
|
|
40
|
+
"reasonManual": "manuel",
|
|
41
|
+
"reasonBlocklist": "liste de blocage",
|
|
42
|
+
"reasonPrivacy": "confidentialité"
|
|
43
|
+
},
|
|
44
|
+
"blocklist": {
|
|
45
|
+
"title": "Liste de blocage Webmention",
|
|
46
|
+
"description": "Les domaines bloqués n'apparaîtront pas dans les webmentions. Les mentions existantes de domaines bloqués sont automatiquement masquées.",
|
|
47
|
+
"add": "Bloquer un domaine",
|
|
48
|
+
"domainLabel": "Domaine",
|
|
49
|
+
"domainPlaceholder": "spam.example.com",
|
|
50
|
+
"reasonLabel": "Raison",
|
|
51
|
+
"reasonSpam": "Spam",
|
|
52
|
+
"reasonManual": "Manuel",
|
|
53
|
+
"blockButton": "Bloquer le domaine",
|
|
54
|
+
"unblock": "Débloquer",
|
|
55
|
+
"empty": "Aucun domaine bloqué.",
|
|
56
|
+
"privacy": {
|
|
57
|
+
"title": "Suppression pour confidentialité",
|
|
58
|
+
"description": "Supprimer définitivement tous les webmentions d'un domaine et bloquer les futurs. À utiliser pour les demandes GDPR ou de suppression personnelle.",
|
|
59
|
+
"domainLabel": "Domaine à supprimer",
|
|
60
|
+
"removeButton": "Supprimer et bloquer"
|
|
61
|
+
},
|
|
62
|
+
"blocked": "Domaine bloqué",
|
|
63
|
+
"unblocked": "Domaine débloqué",
|
|
64
|
+
"removed": "mentions supprimées définitivement",
|
|
65
|
+
"unhidden": "mentions restaurées",
|
|
66
|
+
"mentionsHidden": "mentions masquées",
|
|
67
|
+
"blockedAt": "Bloqué"
|
|
68
|
+
},
|
|
69
|
+
"counts": {
|
|
70
|
+
"total": "total",
|
|
71
|
+
"hidden": "masqué",
|
|
72
|
+
"visible": "visible"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|