Compare commits

...

89 commits

Author SHA1 Message Date
Weblate
de7561ddde Translated using Weblate (Portuguese)
Currently translated at 62.8% (655 of 1042 strings)

Co-authored-by: cel <8cbv6di5@duck.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/pt/
Translation: Pleroma fe/pleroma-fe
2023-05-09 19:11:12 +02:00
Weblate
4f26a90106 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1041 of 1041 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.9% (1040 of 1041 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.9% (1040 of 1041 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.9% (1039 of 1040 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Co-authored-by: SevicheCC <sevicheee@outlook.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/zh_Hans/
Translation: Pleroma fe/pleroma-fe
2023-05-09 19:11:12 +02:00
Weblate
3640526ab7 Translated using Weblate (Ukrainian)
Currently translated at 87.2% (908 of 1041 strings)

Co-authored-by: Denys Nykula <vegan@libre.net.ua>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/uk/
Translation: Pleroma fe/pleroma-fe
2023-05-09 19:11:12 +02:00
Weblate
191589283f Translated using Weblate (French)
Currently translated at 98.5% (1027 of 1042 strings)

Translated using Weblate (French)

Currently translated at 98.3% (1024 of 1041 strings)

Translated using Weblate (French)

Currently translated at 96.9% (1002 of 1033 strings)

Co-authored-by: Thomate <thomas@burdick.fr>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/fr/
Translation: Pleroma fe/pleroma-fe
2023-05-09 19:11:12 +02:00
Weblate
7c59aac3d5 Translated using Weblate (Japanese (ja_EASY))
Currently translated at 71.6% (747 of 1042 strings)

Translated using Weblate (Japanese (ja_EASY))

Currently translated at 71.6% (747 of 1042 strings)

Translated using Weblate (Japanese (ja_EASY))

Currently translated at 54.1% (564 of 1042 strings)

Co-authored-by: Hikaru Shinagawa <hikali.47041@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: kazari <6c577a54-aac9-482a-955e-745c858445e3@simplelogin.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ja_EASY/
Translation: Pleroma fe/pleroma-fe
2023-05-09 19:11:12 +02:00
Weblate
5eb4e8a58b Translated using Weblate (Dutch)
Currently translated at 99.7% (1038 of 1041 strings)

Co-authored-by: Fristi <fristi@subcon.town>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/nl/
Translation: Pleroma fe/pleroma-fe
2023-05-09 19:11:12 +02:00
Weblate
8ed3826ee4 Translated using Weblate (Indonesian)
Currently translated at 65.4% (676 of 1033 strings)

Translated using Weblate (Indonesian)

Currently translated at 65.4% (676 of 1033 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: t1 <taaa@fedora.email>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/id/
Translation: Pleroma fe/pleroma-fe
2023-05-09 19:11:12 +02:00
Weblate
b333542a77 Translated using Weblate (Spanish)
Currently translated at 90.8% (938 of 1033 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: taretka <info@tarteka.net>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/es/
Translation: Pleroma fe/pleroma-fe
2023-05-09 19:11:12 +02:00
Weblate
10163831ac Translated using Weblate (German)
Currently translated at 99.5% (1036 of 1041 strings)

Co-authored-by: Johann <johann@qwertqwefsday.eu>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/de/
Translation: Pleroma fe/pleroma-fe
2023-05-09 19:11:12 +02:00
25c205fe4c ensure we only fetch reports when we're an admin
Ref #288
2023-05-09 19:11:12 +02:00
solidsanek
9056321750 Drafts: Fix drafts erasing edits and redrafts 2023-05-09 19:11:12 +02:00
e5b226799d don't crash if class isn't a list 2023-05-09 19:11:12 +02:00
86d2553acf Fix floating point error for poll expiry
Previous code multiply with 0.001 before multiplication which leads to a
floating point error.  By changing it to division by 1000 after
multiplication this is avoided.
2023-05-09 19:11:12 +02:00
c3208db28d simplify timeline vibility checks 2023-05-09 19:11:12 +02:00
b552d8116a add timeline visibility setting parsing 2023-05-09 19:11:12 +02:00
0f4cdcda48 Add checks for currentUser on sidebar 2023-05-09 19:11:12 +02:00
6b4680bf37 Remove unused bits and bobs 2023-05-09 19:11:12 +02:00
3a8354abb4 Remove links from navs if we can't see the timeline 2023-05-09 19:11:12 +02:00
1e66217c16 add recently used emojis panel to emoji picker (#283)
~~(not intended for merging yet, just submitting this for preliminary review and discussion)~~

this patch adds a tab with recently used emojis to the emoji picker: https://akko.lain.gay/notice/ASoGCtyoiXbYPJjqpk

there's a couple of things i'm ~~still trying to work out~~ not totally happy with and i'd appreciate any feedback on them:

* the recentEmojis getter is called very frequently and has to do a possibly somewhat expensive lookup of emoji objects by their `displayName` each time, which i'm not sure is ideal
* ~~emoji reactions on posts added through the picker are picked up by the recentEmojis module, but clicks on existing emoji reactions are not, because `addReaction` in `react_button.js` only currently receives the replacement and not the full emoji object (if there even is one wherever that method is called from)~~ this works now and does the same stupid full search of all emojis by their name which i guess is less bad because this only happens when you hit a reaction emoji button that already existed

Reviewed-on: AkkomaGang/akkoma-fe#283
Co-authored-by: flisk <akkomadev.mvch71fq@flisk.xyz>
Co-committed-by: flisk <akkomadev.mvch71fq@flisk.xyz>
2023-05-09 19:11:12 +02:00
9d450953b6 oblong emoji in status 2023-05-09 19:10:45 +02:00
e8e4b4fc96 perhaps more graceful cqw 2023-05-09 19:10:45 +02:00
417f7a3ac1 support oblong reactions 2023-05-09 19:10:45 +02:00
1b038d3fdc fix realtime updates in 'following' replies filter
i'm not sure how this code was supposed to work, but the way it was
written would only add statuses to the timeline if they were in reply to
someone the user is following and erroneously filter out posts that
aren't replies.
2023-05-09 19:10:45 +02:00
solidsanek
93e1b750cf Post: remove debug logs 2023-05-09 19:10:45 +02:00
solidsanek
7463cbca53 Post: Add drafting feature 2023-05-09 19:10:45 +02:00
f7ea95cd0e components: emoji_reactions: force custom emoji reaction height
Prevents the usage of too long emoji reactions
2023-05-09 19:10:45 +02:00
3dbbc2519f Improve emoji picker performance (#275)
A simple virtual scroller is now used for the emoji grid. This avoids loading all emoji images at once, saving network bandwidth and reducing load on the server, while also putting less work on the browser's DOM and layout engine.

Co-authored-by: yan <yan@omg.lol>
Reviewed-on: AkkomaGang/akkoma-fe#275
Co-authored-by: yanchan09 <yan@omg.lol>
Co-committed-by: yanchan09 <yan@omg.lol>
2023-05-09 19:10:45 +02:00
5164f595bd paginate-follow-requests (#277)
Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/akkoma-fe#277
2023-05-09 19:10:45 +02:00
5f19c25fa5 Disable follow button if blocked by user 2023-05-09 19:10:45 +02:00
74026fc562 Add indicator if user blocks you 2023-05-09 19:10:45 +02:00
f7301dab5a add language input 2023-05-09 19:10:45 +02:00
Sol Fisher Romanoff
dca56dd40f Only show "keep open" emoji checkbox on post form 2023-05-09 19:10:45 +02:00
5bbac1fa61 Make everything work with a strict CSP 2023-05-09 19:10:45 +02:00
48700ea8d5 Remove console.log 2023-05-09 19:10:45 +02:00
e095e22f2c add follow/unfollow to followed tags list 2023-05-09 19:10:45 +02:00
79d506e331 Add list of followed hashtags to profile 2023-05-09 19:10:45 +02:00
0e71597e56 Fall back to nsfw image if no blurhash 2023-05-09 19:10:45 +02:00
e834656dc6 Add blurhash support 2023-05-09 19:10:45 +02:00
7ac8c9122c Allow follow(er) lists to be acessible by account owner even if follower counts are disabled (#246)
Currently, if a user has their follower/follow counts hidden, they cannot access their own list of followers/follows. This makes no real sense and means that they cannot modify those lists without disabling their privacy options.

This fix simply allows those tabs to be accessed no matter if the counts are hidden or not.

Reviewed-on: AkkomaGang/akkoma-fe#246
Co-authored-by: Beefox <bee@beefox.xyz>
Co-committed-by: Beefox <bee@beefox.xyz>
2023-05-09 19:10:45 +02:00
c9a76f6224 Make minimum width for 3-column layout 1280px (#255) (#256)
1280px is a pretty common screen width for several resolutions
(1280x720, 1280x800, 1280x1024, etc.). Since it is only 20px less than
the current 1300px minimum, this shouldn't be a big issue to lower the
minimum screen width for the 3-column layout to 1280px.

Closes: AkkomaGang/pleroma-fe#255

Co-authored-by: Francis Dinh <normandy@biribiri.dev>
Reviewed-on: AkkomaGang/akkoma-fe#256
Co-authored-by: Norm <normandy@biribiri.dev>
Co-committed-by: Norm <normandy@biribiri.dev>
2023-05-09 19:10:45 +02:00
Sol Fisher Romanoff
ca775554d3 Remove stray debug log 2023-05-09 19:10:45 +02:00
d16182547a remove IHBA assets 2023-05-09 19:10:45 +02:00
d9fa3d8251 add verification of links 2023-05-09 19:10:31 +02:00
47b7017256 force CI build 2023-05-09 19:09:52 +02:00
d2ac971f7f update readme 2023-05-09 19:09:52 +02:00
4b45a96c77 Revert "Merge pull request 'Don't show timeline links if disabled and logged out' (#250) from sfr/pleroma-fe:fix/hide-timelines into develop"
This reverts commit 0b5793c1e0, reversing
changes made to 72ef2e7454.
2023-05-09 19:09:38 +02:00
af3d4f8065 fix emoji picker in replies in notifications 2023-05-09 19:09:38 +02:00
ee2568d21a don't crash out if notification status is null 2023-05-09 19:09:38 +02:00
109bb1fa8a Revert "Revert "use v1 urls""
This reverts commit 8c6cf86de3.
2023-05-09 19:09:38 +02:00
Sol Fisher Romanoff
9dc8c6ea9f Fix user moderation dropdown clipping 2023-05-09 19:09:38 +02:00
Sol Fisher Romanoff
b7411c85f4 Don't show timeline links if disabled and logged out 2023-05-09 19:09:38 +02:00
fef
8c664a1396 fix 404 when reacting with Keycap Number Sign
The Unicode sequence for the Keycap Number Sign
emoji starts with an ASCII "#" character, which
the browser's URL parser will interpret as a URI
fragment and truncate it before sending the
request to the backend.
2023-05-09 19:09:38 +02:00
c53faaf284 Revert "use v1 urls"
This reverts commit 909271c764.
2023-05-09 19:09:38 +02:00
29865caf9a use v1 urls 2023-05-09 19:09:38 +02:00
ae75bb598b bump version 2023-05-09 19:09:38 +02:00
03e2912598 fix ES translation having weird o in a key 2023-05-09 19:09:38 +02:00
Weblate
d6ab4f85d3 Translated using Weblate (Japanese (ja_PEDANTIC))
Currently translated at 99.8% (1031 of 1033 strings)

Co-authored-by: Weblate Admin <hannah.ward9001@gmail.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ja_PEDANTIC/
Translation: Pleroma fe/pleroma-fe
2023-05-09 19:09:38 +02:00
Weblate
59a09bb60d Translated using Weblate (Indonesian)
Currently translated at 59.4% (614 of 1033 strings)

Translated using Weblate (Indonesian)

Currently translated at 53.0% (548 of 1033 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: t1 <taaa@fedora.email>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/id/
Translation: Pleroma fe/pleroma-fe
2023-05-09 19:09:38 +02:00
Weblate
0d81a1fcb3 Translated using Weblate (English)
Currently translated at 100.0% (1033 of 1033 strings)

Co-authored-by: Weblate Admin <hannah.ward9001@gmail.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/en/
Translation: Pleroma fe/pleroma-fe
2023-05-09 19:09:38 +02:00
Weblate
1bc98ce722 Translated using Weblate (Spanish)
Currently translated at 89.2% (918 of 1029 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: taretka <info@tarteka.net>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/es/
Translation: Pleroma fe/pleroma-fe
2023-05-09 19:08:31 +02:00
Weblate
7ba8c31adf Translated using Weblate (German)
Currently translated at 93.9% (967 of 1029 strings)

Co-authored-by: Johann <johann@qwertqwefsday.eu>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/de/
Translation: Pleroma fe/pleroma-fe
2023-05-09 19:08:31 +02:00
Sol Fisher Romanoff
24bc2c29ad Add YAML bug and feat templates 2023-05-09 19:08:31 +02:00
a339e03a8a Update 'ISSUE_TEMPLATE.md' 2023-05-09 19:08:31 +02:00
77e67f40ff Update 'ISSUE_TEMPLATE.md' 2023-05-09 19:08:31 +02:00
28a4c70516 Add issue template 2023-05-09 19:08:31 +02:00
3cdb990ea2 Ensure only content gets clipped 2023-05-09 19:08:31 +02:00
be4b1a8a5a remove whitespace between scope selector icons
when i originally wrote this, for reasons unclear to the present me, i used literal whitespaces to space out the icons on the scope selector 
this causes strange inconsistencies in spacing depending on the font being used 
akkoma also did not include the whitespace when adding the local-only scope, resulting in even weirder spacing 
this corrects all of that by removing the whitespaces and using css instead
2023-05-09 19:08:31 +02:00
19681d0c90 Move theme apply/reset to new row
Fixes #225
2023-05-09 19:08:31 +02:00
6b9c787bb8 Add ability to include custom CSS 2023-05-09 19:08:31 +02:00
11d374a3a1 Add hashtag following button (#244)
Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/pleroma-fe#244
2023-05-09 19:08:31 +02:00
1f875844aa use cutout of 512 logo 2023-05-09 19:08:31 +02:00
4510d3d130 fix SW path 2023-05-09 19:08:31 +02:00
ec0164452c Register serviceworker 2023-05-09 19:08:31 +02:00
3da3e09dc5 Add logo files 2023-05-09 19:08:31 +02:00
Karl Prieb
fdaef74109 add a left margin on search bar 2023-05-09 19:08:00 +02:00
2e9a36557d Add post expiry inputs 2023-05-09 19:08:00 +02:00
613e8584de Add link to RSS feed to the profile (#234)
Today I learned that akkoma and mastodon (and potentially other activitypub services) offer RSS/Atom feeds for user profiles at `[user profile url].rss`. This PR adds a direct link to the feed because I haven’t seen anything link to the feed on either mastodon-fe or pleroma-fe

Co-authored-by: Charlotte 🦝 Delenk <lotte@chir.rs>
Reviewed-on: AkkomaGang/pleroma-fe#234
Co-authored-by: darkkirb <lotte@chir.rs>
Co-committed-by: darkkirb <lotte@chir.rs>
2023-05-09 19:08:00 +02:00
Sean Meininger
359886928e added maintainer code 2023-05-09 19:08:00 +02:00
d2f704c6f5 Redirect users to "awaiting approval" message on register (#231)
Ref #81 - this implements a "your request has been sent" message

 ![image](/attachments/61dc3f5e-2706-46f9-a1ca-4efe3f526ff3)

Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/pleroma-fe#231
2023-05-09 19:08:00 +02:00
cba4a23379 Only reload user if it _is_ a user (#232)
Ref #181

Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/pleroma-fe#232
2023-05-09 19:08:00 +02:00
64cb7443a8 Don't error out if we can't fetch reports 2023-05-09 19:08:00 +02:00
David
c3f829eb68 Allow using mouse wheel to navigate through the emoji tabs 2023-05-09 19:08:00 +02:00
46520dadfd mobile-newline (#226)
Allows the handle of users to drop down onto the next line if there isn't enough room in order to improve useability on mobile

Reviewed-on: AkkomaGang/pleroma-fe#226
Co-authored-by: Beefox <bee@beefox.xyz>
Co-committed-by: Beefox <bee@beefox.xyz>
2023-05-09 19:08:00 +02:00
f75571edd0 move domain block to drop down menu 2023-05-09 19:08:00 +02:00
240389a83f Allow for searching unicode emoji via inputting emoji
(This is needed for the react menu)
2023-05-09 19:08:00 +02:00
d03969eb03 Constrain content to status content (#218) (#220)
MFM No longer overflows
![image](/attachments/7bbf519a-9fd2-492d-aba0-9a0e1ded6373)

Co-authored-by: David <dmgf2008@hotmail.com>
Reviewed-on: AkkomaGang/pleroma-fe#220
Co-authored-by: Mergan <mergan@noreply.akkoma>
Co-committed-by: Mergan <mergan@noreply.akkoma>
2023-05-09 19:08:00 +02:00
Weblate
7ea89a8b44 Translated using Weblate (Spanish)
Currently translated at 87.1% (893 of 1025 strings)

Co-authored-by: taretka <info@tarteka.net>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/es/
Translation: Pleroma fe/pleroma-fe
2023-05-09 19:08:00 +02:00
84950b9671 Fix build 2023-05-09 19:08:00 +02:00
4379c67180 Copy conversation display style from instance
Fixes #210
2023-05-09 19:08:00 +02:00
113 changed files with 3839 additions and 3252 deletions

View file

@ -0,0 +1,49 @@
name: "Bug report"
about: "Something isn't working as expected"
title: "[bug] "
body:
- type: markdown
attributes:
value: "Thanks for taking the time to file this bug report! Please try to be as specific and detailed as you can, so we can track down the issue and fix it as soon as possible."
- type: input
id: version
attributes:
label: "Version"
description: "Which version of pleroma-fe are you running? If running develop, specify the commit hash."
placeholder: "e.g. 2022.11, 40e86998e6"
- type: textarea
id: attempt
attributes:
label: "What were you trying to do?"
validations:
required: true
- type: textarea
id: expectation
attributes:
label: "What did you expect to happen?"
validations:
required: true
- type: textarea
id: reality
attributes:
label: "What actually happened?"
validations:
required: true
- type: dropdown
id: severity
attributes:
label: "Severity"
description: "Does this issue prevent you from using the software as normal?"
options:
- "I cannot use the software"
- "I cannot use it as easily as I'd like"
- "I can manage"
validations:
required: true
- type: checkboxes
id: searched
attributes:
label: "Have you searched for this issue?"
description: "Please double-check that your issue is not already being tracked on [the forums](https://meta.akkoma.dev) or [the issue tracker](https://akkoma.dev/AkkomaGang/pleroma-fe/issues)."
options:
- label: "I have double-checked and have not found this issue mentioned anywhere."

View file

@ -0,0 +1,29 @@
name: "Feature request"
about: "I'd like something to be added to pleroma-fe"
title: "[feat] "
body:
- type: markdown
attributes:
value: "Thanks for taking the time to request a new feature! Please be as concise and clear as you can in your proposal, so we could understand what you're going for."
- type: textarea
id: idea
attributes:
label: "The idea"
description: "What do you think you should be able to do in pleroma-fe?"
validations:
required: true
- type: textarea
id: reason
attributes:
label: "The reasoning"
description: "Why would this be a worthwhile feature? Does it solve any problems? Have people talked about wanting it?"
validations:
required: true
- type: checkboxes
id: searched
attributes:
label: "Have you searched for this feature request?"
description: "Please double-check that your issue is not already being tracked on [the forums](https://meta.akkoma.dev), [the issue tracker](https://akkoma.dev/AkkomaGang/pleroma-fe/issues), or the one for [the backend](https://akkoma.dev/AkkomaGang/akkoma/issues)."
options:
- label: "I have double-checked and have not found this feature request mentioned anywhere."
- label: "This feature is related to the pleroma-fe Akkoma frontend specifically, and not the backend."

1
.gitignore vendored
View file

@ -9,3 +9,4 @@ selenium-debug.log
config/local.json
config/local.*.json
docs/site/
.vscode/

View file

@ -1,19 +1,13 @@
{
"extends": [
"stylelint-rscss/config",
"stylelint-config-recommended-vue/scss",
"stylelint-config-recommended",
"stylelint-config-standard"
],
"customSyntax": "postcss-scss",
"rules": {
"declaration-no-important": true,
"rscss/no-descendant-combinator": false,
"rscss/class-format": [
true,
{
"component": "pascal-case",
"variant": "^-[a-z]\\w+",
"element": "^[a-z]\\w+"
}
]
"selector-class-pattern": null,
"custom-property-pattern": null
}
}

View file

@ -7,7 +7,7 @@ pipeline:
commands:
- yarn
- yarn lint
- yarn stylelint
#- yarn stylelint
test:
when:

View file

@ -2,7 +2,7 @@
![English OK](https://img.shields.io/badge/English-OK-blueviolet) ![日本語OK](https://img.shields.io/badge/%E6%97%A5%E6%9C%AC%E8%AA%9E-OK-blueviolet)
This is a fork of Pleroma-FE from the Pleroma project, with support for new Akkoma features such as:
This is a fork of Akkoma-FE from the Pleroma project, with support for new Akkoma features such as:
- MFM support via [marked-mfm](https://akkoma.dev/sfr/marked-mfm)
- Custom emoji reactions
@ -10,15 +10,15 @@ And specificly, this is a tweaked version with all the hyena.network themes and
# For Translators
The [Weblate UI](https://translate.akkoma.dev/projects/akkoma/pleroma-fe/) is recommended for adding or modifying translations for Pleroma-FE.
The [Weblate UI](https://translate.akkoma.dev/projects/akkoma/pleroma-fe/) is recommended for adding or modifying translations for Akkoma-FE.
Alternatively, edit/create `src/i18n/$LANGUAGE_CODE.json` (where `$LANGUAGE_CODE` is the [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) for your language), then add your language to [src/i18n/messages.js](https://akkoma.dev/AkkomaGang/pleroma-fe/src/branch/develop/src/i18n/messages.js) if it doesn't already exist there.
Pleroma-FE will set your language by your browser locale, but you can temporarily force it in the code by changing the locale in main.js.
Akkoma-FE will set your language by your browser locale, but you can temporarily force it in the code by changing the locale in main.js.
# FOR ADMINS
To use Pleroma-FE in Akkoma, use the [frontend](https://docs.akkoma.dev/stable/administration/CLI_tasks/frontend/) CLI task to install Pleroma-FE, then modify your configuration as described in the [Frontend Management](https://docs.akkoma.dev/stable/configuration/frontend_management/) doc.
To use Akkoma-FE in Akkoma, use the [frontend](https://docs.akkoma.dev/stable/administration/CLI_tasks/frontend/) CLI task to install Akkoma-FE, then modify your configuration as described in the [Frontend Management](https://docs.akkoma.dev/stable/configuration/frontend_management/) doc.
## Build Setup
@ -54,4 +54,4 @@ Edit config.json for configuration.
### Login methods
```loginMethod``` can be set to either ```password``` (the default) or ```token```, which will use the full oauth redirection flow, which is useful for SSO situations.
```loginMethod``` can be set to either ```password``` (the default) or ```token```, which will use the full oauth redirection flow, which is useful for SSO situations.

View file

@ -2,7 +2,6 @@ var path = require('path')
var config = require('../config')
var utils = require('./utils')
var projectRoot = path.resolve(__dirname, '../')
const WorkboxPlugin = require('workbox-webpack-plugin');
var { VueLoaderPlugin } = require('vue-loader')
var env = process.env.NODE_ENV
@ -119,11 +118,6 @@ module.exports = {
]
},
plugins: [
new WorkboxPlugin.InjectManifest({
swSrc: path.join(__dirname, '..', 'src/sw.js'),
swDest: 'sw-pleroma.js',
maximumFileSizeToCacheInBytes: 15 * 1024 * 1024,
}),
new VueLoaderPlugin()
]
}

View file

@ -2,6 +2,7 @@ var path = require('path')
var config = require('../config')
var utils = require('./utils')
var webpack = require('webpack')
const WorkboxPlugin = require('workbox-webpack-plugin');
var { merge } = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var MiniCssExtractPlugin = require('mini-css-extract-plugin')
@ -32,6 +33,11 @@ var webpackConfig = merge(baseWebpackConfig, {
chunkFilename: utils.assetsPath('js/[name].[chunkhash].js')
},
plugins: [
new WorkboxPlugin.InjectManifest({
swSrc: path.join(__dirname, '..', 'src/sw.js'),
swDest: 'sw-pleroma.js',
maximumFileSizeToCacheInBytes: 15 * 1024 * 1024,
}),
// http://vuejs.github.io/vue-loader/workflow/production.html
new webpack.DefinePlugin({
'process.env': env,

View file

@ -38,6 +38,11 @@ module.exports = {
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {
'/manifest.json': {
target,
changeOrigin: true,
cookieDomainRewrite: 'localhost'
},
'/api': {
target,
changeOrigin: true,

View file

@ -4,13 +4,14 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
<title>Akkoma</title>
<link rel="stylesheet" href="/static/font/css/fontello.css">
<link rel="stylesheet" href="/static/font/css/animation.css">
<link rel="stylesheet" href="/static/font/tiresias.css">
<link rel="stylesheet" href="/static/font/css/lato.css">
<link rel="stylesheet" href="/static/mfm.css">
<link rel="stylesheet" href="/static/custom.css">
<link rel="stylesheet" href="/static/theme-holder.css" id="theme-holder">
<!--server-generated-meta-->
<link rel="icon" type="image/png" href="/favicon.png">
<link rel="manifest" href="/manifest.json">
</head>
<body class="hidden">
<noscript>To use Akkoma, please enable JavaScript.</noscript>

View file

@ -1,6 +1,6 @@
{
"name": "pleroma_fe",
"version": "3.2.0",
"version": "3.5.0",
"description": "A frontend for Akkoma instances",
"author": "Roger Braun <roger@rogerbraun.net>",
"private": true,
@ -11,30 +11,30 @@
"unit:watch": "karma start test/unit/karma.conf.js --single-run=false",
"e2e": "node test/e2e/runner.js",
"test": "npm run unit && npm run e2e",
"stylelint": "npx stylelint src/components/status/status.scss",
"stylelint": "stylelint src/**/*.scss",
"lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs",
"lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs"
},
"dependencies": {
"@babel/runtime": "7.17.8",
"@chenfengyuan/vue-qrcode": "2.0.0",
"@floatingghost/pinch-zoom-element": "^1.3.1",
"@fortawesome/fontawesome-svg-core": "1.3.0",
"@fortawesome/free-regular-svg-icons": "^6.1.2",
"@fortawesome/free-solid-svg-icons": "^6.2.0",
"@fortawesome/vue-fontawesome": "3.0.1",
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
"@vuelidate/core": "2.0.0-alpha.42",
"@vuelidate/validators": "2.0.0-alpha.30",
"@vuelidate/core": "^2.0.0",
"@vuelidate/validators": "^2.0.0",
"blurhash": "^2.0.4",
"body-scroll-lock": "2.7.1",
"chromatism": "3.0.0",
"click-outside-vue3": "4.0.1",
"cropperjs": "1.5.12",
"diff": "3.5.0",
"escape-html": "1.0.3",
"iso-639-1": "^2.1.15",
"js-cookie": "^3.0.1",
"localforage": "1.10.0",
"marked": "^4.2.2",
"marked-mfm": "^0.5.0",
"parse-link-header": "^2.0.0",
"phoenix": "1.6.2",
"punycode.js": "2.1.0",
@ -84,7 +84,6 @@
"html-webpack-plugin": "^5.5.0",
"http-proxy-middleware": "0.21.0",
"inject-loader": "2.0.1",
"iso-639-1": "2.1.15",
"isparta-loader": "2.0.0",
"json-loader": "0.5.7",
"karma": "6.3.17",
@ -103,7 +102,9 @@
"nightwatch": "0.9.21",
"opn": "4.0.2",
"ora": "0.4.1",
"postcss-html": "^1.5.0",
"postcss-loader": "3.0.0",
"postcss-sass": "^0.5.0",
"raw-loader": "0.5.1",
"sass": "^1.56.0",
"sass-loader": "^13.2.0",
@ -112,9 +113,11 @@
"shelljs": "0.8.5",
"sinon": "2.4.1",
"sinon-chai": "2.14.0",
"stylelint": "13.6.1",
"stylelint-config-standard": "20.0.0",
"stylelint-rscss": "0.4.0",
"stylelint": "^14.15.0",
"stylelint-config-recommended-vue": "^1.4.0",
"stylelint-config-standard": "^29.0.0",
"stylelint-config-standard-scss": "^6.1.0",
"stylelint-rscss": "^0.4.0",
"url-loader": "^4.1.1",
"vue-loader": "^17.0.0",
"vue-style-loader": "^4.1.2",

View file

@ -1,6 +1,7 @@
// stylelint-disable rscss/class-format
@import './_variables.scss';
@import '@fortawesome/fontawesome-svg-core/styles.css';
@import '@floatingghost/pinch-zoom-element/dist/pinch-zoom.css';
:root {
--navbar-height: 3.5rem;
--post-line-height: 1.4;

View file

@ -4,6 +4,8 @@ import { createRouter, createWebHistory } from 'vue-router'
import vClickOutside from 'click-outside-vue3'
import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
import { config } from '@fortawesome/fontawesome-svg-core';
config.autoAddCss = false
import App from '../App.vue'
import routes from './routes'
@ -150,6 +152,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('showPanelNavShortcuts')
copyInstanceOption('stopGifs')
copyInstanceOption('logo')
copyInstanceOption('conversationDisplay')
store.dispatch('setInstanceOption', {
name: 'logoMask',
@ -319,6 +322,9 @@ const getNodeInfo = async ({ store }) => {
: federation.enabled
})
store.dispatch('setInstanceOption', { name: 'publicTimelineVisibility', value: metadata.publicTimelineVisibility })
store.dispatch('setInstanceOption', { name: 'federatedTimelineAvailable', value: metadata.federatedTimelineAvailable })
const accountActivationRequired = metadata.accountActivationRequired
store.dispatch('setInstanceOption', { name: 'accountActivationRequired', value: accountActivationRequired })
@ -393,9 +399,6 @@ const afterStoreSetup = async ({ store, i18n }) => {
])
// Start fetching things that don't need to block the UI
store.dispatch('fetchMutes')
store.dispatch('startFetchingAnnouncements')
store.dispatch('startFetchingReports')
getTOS({ store })
getStickers({ store })

View file

@ -22,6 +22,8 @@ import Lists from 'components/lists/lists.vue'
import ListTimeline from 'components/list_timeline/list_timeline.vue'
import ListEdit from 'components/list_edit/list_edit.vue'
import AnnouncementsPage from 'components/announcements_page/announcements_page.vue'
import RegistrationRequestSent from 'components/registration_request_sent/registration_request_sent.vue'
import AwaitingEmailConfirmation from 'components/awaiting_email_confirmation/awaiting_email_confirmation.vue'
export default (store) => {
const validateAuthenticatedRoute = (to, from, next) => {
@ -62,6 +64,8 @@ export default (store) => {
{ name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute },
{ name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
{ name: 'registration', path: '/registration', component: Registration },
{ name: 'registration-request-sent', path: '/registration-request-sent', component: RegistrationRequestSent },
{ name: 'awaiting-email-confirmation', path: '/awaiting-email-confirmation', component: AwaitingEmailConfirmation },
{ name: 'password-reset', path: '/password-reset', component: PasswordReset, props: true },
{ name: 'registration-token', path: '/registration/:token', component: Registration },
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },

View file

@ -26,6 +26,9 @@ const AccountActions = {
ConfirmModal
},
methods: {
refetchRelationship () {
return this.$store.dispatch('fetchUserRelationship', this.user.id)
},
showConfirmBlock () {
this.showingConfirmBlock = true
},
@ -57,6 +60,14 @@ const AccountActions = {
},
reportUser () {
this.$store.dispatch('openUserReportingModal', { userId: this.user.id })
},
muteDomain () {
this.$store.dispatch('muteDomain', this.user.screen_name.split('@')[1])
.then(() => this.refetchRelationship())
},
unmuteDomain () {
this.$store.dispatch('unmuteDomain', this.user.screen_name.split('@')[1])
.then(() => this.refetchRelationship())
}
},
computed: {

View file

@ -55,6 +55,20 @@
>
{{ $t('user_card.report') }}
</button>
<button
v-if="relationship.domain_blocking"
class="btn button-default btn-block dropdown-item"
@click="unmuteDomain"
>
{{ $t('user_card.domain_muted') }}
</button>
<button
v-else-if="!user.is_local"
class="btn button-default btn-block dropdown-item"
@click="muteDomain"
>
{{ $t('user_card.mute_domain') }}
</button>
</div>
</template>
<template v-slot:trigger>

View file

@ -18,6 +18,7 @@ import {
faPencilAlt,
faAlignRight
} from '@fortawesome/free-solid-svg-icons'
import Blurhash from '../blurhash/Blurhash.vue'
library.add(
faFile,
@ -63,7 +64,8 @@ const Attachment = {
components: {
Flash,
StillImage,
VideoAttachment
VideoAttachment,
Blurhash
},
computed: {
classNames () {
@ -84,6 +86,9 @@ const Attachment = {
useContainFit () {
return this.$store.getters.mergedConfig.useContainFit
},
useBlurhash () {
return this.$store.getters.mergedConfig.useBlurhash
},
placeholderName () {
if (this.attachment.description === '' || !this.attachment.description) {
return this.type.toUpperCase()

View file

@ -64,7 +64,15 @@
:title="attachment.description"
@click.prevent.stop="toggleHidden"
>
<Blurhash
v-if="useBlurhash && attachment.blurhash"
:height="512"
:width="1024"
:hash="attachment.blurhash"
:punch="1"
/>
<img
v-else
:key="nsfwImage"
class="nsfw"
:src="nsfwImage"

View file

@ -0,0 +1,4 @@
export default {
computed: {
}
}

View file

@ -0,0 +1,12 @@
<template>
<div class="panel panel-default">
<div class="panel-heading">
<h4>{{ $t('registration.awaiting_email_confirmation_title') }}</h4>
</div>
<div class="panel-body">
<p>{{ $t('registration.awaiting_email_confirmation') }}</p>
</div>
</div>
</template>
<script src="./awaiting_email_confirmation.js"></script>

View file

@ -0,0 +1,66 @@
<template>
<canvas
ref="canvas"
class="blurhash"
/>
</template>
<script>
import { decode } from "blurhash";
export default {
name: 'Blurhash',
props: {
hash: {
type: String,
required: true,
},
width: {
type: Number,
required: true,
},
height: {
type: Number,
required: true,
},
punch: {
type: Number,
default: null,
},
},
data() {
return {
canvas: null,
ctx: null,
};
},
mounted() {
this.canvas = this.$refs.canvas;
this.ctx = this.canvas.getContext('2d');
this.canvas.width = 1024;
this.canvas.height = 512;
this.draw();
},
methods: {
draw() {
const pixels = decode(this.hash, this.width, this.height, this.punch);
const imageData = this.ctx.createImageData(this.width, this.height);
imageData.data.set(pixels);
this.ctx.putImageData(imageData, 0, 0);
fetch("/static/blurhash-overlay.png")
.then((response) => response.blob())
.then((blob) => {
const img = new Image();
img.src = URL.createObjectURL(blob);
img.onload = () => {
this.ctx.drawImage(img, 0, 0, this.width, this.height);
};
});
},
}
}
</script>
<style scoped>
</style>

View file

@ -1,6 +1,11 @@
import SearchBar from 'components/search_bar/search_bar.vue'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
publicTimelineVisible,
federatedTimelineVisible,
bubbleTimelineVisible,
} from '../../lib/timeline_visibility'
import {
faSignInAlt,
faSignOutAlt,
@ -19,6 +24,7 @@ import {
faInfoCircle,
faUserTie
} from '@fortawesome/free-solid-svg-icons'
import { mapState } from 'vuex'
library.add(
faSignInAlt,
@ -103,7 +109,12 @@ export default {
},
showBubbleTimeline () {
return this.$store.state.instance.localBubbleInstances.length > 0
}
},
...mapState({
publicTimelineVisible,
federatedTimelineVisible,
bubbleTimelineVisible,
})
},
methods: {
scrollToTop () {

View file

@ -46,6 +46,7 @@
<router-link
:to="{ name: 'public-timeline' }"
class="nav-icon"
v-if="publicTimelineVisible"
>
<FAIcon
fixed-width
@ -55,7 +56,7 @@
/>
</router-link>
<router-link
v-if="currentUser && showBubbleTimeline"
v-if="bubbleTimelineVisible"
:to="{ name: 'bubble-timeline' }"
class="nav-icon"
>
@ -69,6 +70,7 @@
<router-link
:to="{ name: 'public-external-timeline' }"
class="nav-icon"
v-if="federatedTimelineVisible"
>
<FAIcon
fixed-width

View file

@ -0,0 +1,133 @@
const EMOJI_SIZE = 32 + 8
const GROUP_TITLE_HEIGHT = 24
const BUFFER_SIZE = 3 * EMOJI_SIZE
const EmojiGrid = {
props: {
groups: {
required: true,
type: Array
}
},
data () {
return {
containerWidth: 0,
containerHeight: 0,
scrollPos: 0,
resizeObserver: null
}
},
mounted () {
const rect = this.$refs.container.getBoundingClientRect()
this.containerWidth = rect.width
this.containerHeight = rect.height
this.resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
this.containerWidth = entry.contentRect.width
this.containerHeight = entry.contentRect.height
}
})
this.resizeObserver.observe(this.$refs.container)
},
beforeUnmount () {
this.resizeObserver.disconnect()
this.resizeObserver = null
},
watch: {
groups () {
// Scroll to top when grid content changes
if (this.$refs.container) {
this.$refs.container.scrollTo(0, 0)
}
},
activeGroup (group) {
this.$emit('activeGroup', group)
}
},
methods: {
onScroll () {
this.scrollPos = this.$refs.container.scrollTop
},
onEmoji (emoji) {
this.$emit('emoji', emoji)
},
scrollToItem (itemId) {
const container = this.$refs.container
if (!container) return
for (const item of this.itemList) {
if (item.id === itemId) {
container.scrollTo(0, item.position.y)
return
}
}
}
},
computed: {
// Total height of scroller content
gridHeight () {
if (this.itemList.length === 0) return 0
const lastItem = this.itemList[this.itemList.length - 1]
return (
lastItem.position.y +
('title' in lastItem ? GROUP_TITLE_HEIGHT : EMOJI_SIZE)
)
},
activeGroup () {
const items = this.itemList
for (let i = items.length - 1; i >= 0; i--) {
const item = items[i]
if ('title' in item && item.position.y <= this.scrollPos) {
return item.id
}
}
return null
},
itemList () {
const items = []
let x = 0
let y = 0
for (const group of this.groups) {
items.push({ position: { x, y }, id: group.id, title: group.text })
if (group.text.length) {
y += GROUP_TITLE_HEIGHT
}
for (const emoji of group.emojis) {
items.push({
position: { x, y },
id: `${group.id}-${emoji.displayText}`,
emoji
})
x += EMOJI_SIZE
if (x + EMOJI_SIZE > this.containerWidth) {
y += EMOJI_SIZE
x = 0
}
}
if (x > 0) {
y += EMOJI_SIZE
x = 0
}
}
return items
},
visibleItems () {
const startPos = this.scrollPos - BUFFER_SIZE
const endPos = this.scrollPos + this.containerHeight + BUFFER_SIZE
return this.itemList.filter((i) => {
return i.position.y >= startPos && i.position.y < endPos
})
},
scrolledClass () {
if (this.scrollPos <= 5) {
return 'scrolled-top'
} else if (this.scrollPos >= this.gridHeight - this.containerHeight - 5) {
return 'scrolled-bottom'
} else {
return 'scrolled-middle'
}
}
}
}
export default EmojiGrid

View file

@ -0,0 +1,60 @@
.emoji {
&-grid {
flex: 1 1 1px;
position: relative;
overflow: auto;
user-select: none;
mask: linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
linear-gradient(to bottom, white 0, transparent 100%) top no-repeat,
linear-gradient(to top, white, white);
transition: mask-size 150ms;
mask-size: 100% 20px, 100% 20px, auto;
// Autoprefixed seem to ignore this one, and also syntax is different
-webkit-mask-composite: xor;
mask-composite: exclude;
&.scrolled {
&-top {
mask-size: 100% 20px, 100% 0, auto;
}
&-bottom {
mask-size: 100% 0, 100% 20px, auto;
}
}
margin-left: 5px;
min-height: 200px;
}
&-group-title {
position: absolute;
font-size: 0.85em;
width: 100%;
margin: 0;
height: 24px;
display: flex;
align-items: end;
&.disabled {
display: none;
}
}
&-item {
position: absolute;
width: 32px;
height: 32px;
box-sizing: border-box;
display: flex;
font-size: 32px;
align-items: center;
justify-content: center;
margin: 4px;
cursor: pointer;
img {
object-fit: contain;
max-width: 100%;
max-height: 100%;
}
}
}

View file

@ -0,0 +1,48 @@
<template>
<div
ref="container"
class="emoji-grid"
:class="scrolledClass"
@scroll.passive="onScroll"
>
<div
:style="{
height: `${gridHeight}px`,
}"
>
<template v-for="item in visibleItems">
<h6
v-if="'title' in item && item.title.length"
:key="'title-' + item.id"
class="emoji-group-title"
:style="{
top: item.position.y + 'px',
left: item.position.x + 'px'
}"
>
{{ item.title }}
</h6>
<span
v-else-if="'emoji' in item"
:key="'emoji-' + item.id"
class="emoji-item"
:title="item.emoji.displayText"
:style="{
top: item.position.y + 'px',
left: item.position.x + 'px'
}"
@click.stop.prevent="onEmoji(item.emoji)"
>
<span v-if="!item.emoji.imageUrl">{{ item.emoji.replacement }}</span>
<img
v-else
:src="item.emoji.imageUrl"
>
</span>
</template>
</div>
</div>
</template>
<script src="./emoji_grid.js"></script>
<style lang="scss" src="./emoji_grid.scss"></style>

View file

@ -205,7 +205,6 @@ const EmojiInput = {
},
triggerShowPicker () {
this.showPicker = true
this.$refs.picker.startEmojiLoad()
this.$nextTick(() => {
this.scrollIntoView()
this.focusPickerInput()
@ -223,7 +222,6 @@ const EmojiInput = {
this.showPicker = !this.showPicker
if (this.showPicker) {
this.scrollIntoView()
this.$refs.picker.startEmojiLoad()
this.$nextTick(this.focusPickerInput)
}
},

View file

@ -18,6 +18,7 @@
<EmojiPicker
v-if="enableEmojiPicker"
ref="picker"
show-keep-open
:class="{ hide: !showPicker }"
:enable-sticker-picker="enableStickerPicker"
class="emoji-picker-panel"

View file

@ -1,5 +1,6 @@
import { defineAsyncComponent } from 'vue'
import Checkbox from '../checkbox/checkbox.vue'
import EmojiGrid from '../emoji_grid/emoji_grid.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faBoxOpen,
@ -14,19 +15,17 @@ library.add(
faSmileBeam
)
// At widest, approximately 20 emoji are visible in a row,
// loading 3 rows, could be overkill for narrow picker
const LOAD_EMOJI_BY = 60
// When to start loading new batch emoji, in pixels
const LOAD_EMOJI_MARGIN = 64
const EmojiPicker = {
props: {
enableStickerPicker: {
required: false,
type: Boolean,
default: false
},
showKeepOpen: {
required: false,
type: Boolean,
default: false
}
},
data () {
@ -34,16 +33,13 @@ const EmojiPicker = {
keyword: '',
activeGroup: 'standard',
showingStickers: false,
groupsScrolledClass: 'scrolled-top',
keepOpen: false,
customEmojiBufferSlice: LOAD_EMOJI_BY,
customEmojiTimeout: null,
customEmojiLoadAllConfirmed: false
keepOpen: false
}
},
components: {
StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')),
Checkbox
Checkbox,
EmojiGrid
},
methods: {
onStickerUploaded (e) {
@ -55,78 +51,21 @@ const EmojiPicker = {
onEmoji (emoji) {
const value = emoji.imageUrl ? `:${emoji.displayText}:` : emoji.replacement
this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen })
this.$store.commit('emojiUsed', emoji)
},
onScroll (e) {
const target = (e && e.target) || this.$refs['emoji-groups']
this.updateScrolledClass(target)
this.scrolledGroup(target)
this.triggerLoadMore(target)
onWheel (e) {
e.preventDefault()
this.$refs['emoji-tabs'].scrollBy(e.deltaY, 0)
},
highlight (key) {
this.setShowStickers(false)
this.activeGroup = key
},
updateScrolledClass (target) {
if (target.scrollTop <= 5) {
this.groupsScrolledClass = 'scrolled-top'
} else if (target.scrollTop >= target.scrollTopMax - 5) {
this.groupsScrolledClass = 'scrolled-bottom'
} else {
this.groupsScrolledClass = 'scrolled-middle'
if (this.keyword.length) {
this.$refs.emojiGrid.scrollToItem(key)
}
},
triggerLoadMore (target) {
const ref = this.$refs['group-end-custom']
if (!ref) return
const bottom = ref.offsetTop + ref.offsetHeight
const scrollerBottom = target.scrollTop + target.clientHeight
const scrollerTop = target.scrollTop
const scrollerMax = target.scrollHeight
// Loads more emoji when they come into view
const approachingBottom = bottom - scrollerBottom < LOAD_EMOJI_MARGIN
// Always load when at the very top in case there's no scroll space yet
const atTop = scrollerTop < 5
// Don't load when looking at unicode category or at the very bottom
const bottomAboveViewport = bottom < scrollerTop || scrollerBottom === scrollerMax
if (!bottomAboveViewport && (approachingBottom || atTop)) {
this.loadEmoji()
}
},
scrolledGroup (target) {
const top = target.scrollTop + 5
this.$nextTick(() => {
this.emojisView.forEach(group => {
const ref = this.$refs['group-' + group.id]
if (ref.offsetTop <= top) {
this.activeGroup = group.id
}
})
})
},
loadEmoji () {
const allLoaded = this.customEmojiBuffer.length === this.filteredEmoji.length
if (allLoaded) {
return
}
this.customEmojiBufferSlice += LOAD_EMOJI_BY
},
startEmojiLoad (forceUpdate = false) {
if (!forceUpdate) {
this.keyword = ''
}
this.$nextTick(() => {
this.$refs['emoji-groups'].scrollTop = 0
})
const bufferSize = this.customEmojiBuffer.length
const bufferPrefilledAll = bufferSize === this.filteredEmoji.length
if (bufferPrefilledAll && !forceUpdate) {
return
}
this.customEmojiBufferSlice = LOAD_EMOJI_BY
onActiveGroup (group) {
this.activeGroup = group
},
toggleStickers () {
this.showingStickers = !this.showingStickers
@ -138,17 +77,10 @@ const EmojiPicker = {
if (this.keyword === '') return list
const regex = new RegExp(escapeRegExp(trim(this.keyword)), 'i')
return list.filter(emoji => {
return regex.test(emoji.displayText)
return (regex.test(emoji.displayText) || (!emoji.imageUrl && emoji.replacement === this.keyword))
})
}
},
watch: {
keyword () {
this.customEmojiLoadAllConfirmed = false
this.onScroll()
this.startEmojiLoad(true)
}
},
computed: {
activeGroupView () {
return this.showingStickers ? '' : this.activeGroup
@ -164,10 +96,8 @@ const EmojiPicker = {
this.$store.state.instance.customEmoji || []
)
},
customEmojiBuffer () {
return this.filteredEmoji.slice(0, this.customEmojiBufferSlice)
},
emojis () {
const recentEmojis = this.$store.getters.recentEmojis
const standardEmojis = this.$store.state.instance.emoji || []
const customEmojis = this.sortedEmoji
const emojiPacks = []
@ -180,6 +110,15 @@ const EmojiPicker = {
})
})
return [
{
id: 'recent',
text: this.$t('emoji.recent'),
first: {
imageUrl: '',
replacement: '🕒',
},
emojis: this.filterByKeyword(recentEmojis)
},
{
id: 'standard',
text: this.$t('emoji.unicode'),

View file

@ -1,5 +1,16 @@
@import '../../_variables.scss';
// The worst query selector ever
// selects ONLY emojis pickers in replies in notifications
// who thought this was a good idea?
.notification > .Status > .status-container > .post-status-form > form > .form-group > .emoji-input > .emoji-picker {
max-width: 100%;
left: 0;
@media (min-width: 1300px) {
left: -30px;
}
}
.Notification {
.emoji-picker {
min-width: 160%;
@ -7,7 +18,7 @@
overflow: hidden;
left: -70%;
max-width: 100%;
@media (min-width: 800px) and (max-width: 1300px) {
@media (min-width: 800px) and (max-width: 1280px) {
left: -50%;
min-width: 50%;
max-width: 130%;
@ -18,6 +29,10 @@
min-width: 50%;
max-width: 130%;
}
.Status > .emoji-picker {
z-index: 1000;
}
}
}
.emoji-picker {
@ -70,10 +85,6 @@
flex-grow: 1;
}
.emoji-groups {
min-height: 200px;
}
.additional-tabs {
border-left: 1px solid;
border-left-color: $fallback--icon;
@ -152,76 +163,12 @@
}
}
.emoji {
&-search {
padding: 5px;
flex: 0 0 auto;
.emoji-search {
padding: 5px;
flex: 0 0 auto;
input {
width: 100%;
}
input {
width: 100%;
}
&-groups {
flex: 1 1 1px;
position: relative;
overflow: auto;
user-select: none;
mask: linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
linear-gradient(to bottom, white 0, transparent 100%) top no-repeat,
linear-gradient(to top, white, white);
transition: mask-size 150ms;
mask-size: 100% 20px, 100% 20px, auto;
// Autoprefixed seem to ignore this one, and also syntax is different
-webkit-mask-composite: xor;
mask-composite: exclude;
&.scrolled {
&-top {
mask-size: 100% 20px, 100% 0, auto;
}
&-bottom {
mask-size: 100% 0, 100% 20px, auto;
}
}
}
&-group {
display: flex;
align-items: center;
flex-wrap: wrap;
padding-left: 5px;
justify-content: left;
&-title {
font-size: 0.85em;
width: 100%;
margin: 0;
&.disabled {
display: none;
}
}
}
&-item {
width: 32px;
height: 32px;
box-sizing: border-box;
display: flex;
font-size: 32px;
align-items: center;
justify-content: center;
margin: 4px;
cursor: pointer;
img {
object-fit: contain;
max-width: 100%;
max-height: 100%;
}
}
}
}

View file

@ -1,7 +1,11 @@
<template>
<div class="emoji-picker panel panel-default panel-body">
<div class="heading">
<span class="emoji-tabs">
<span
ref="emoji-tabs"
class="emoji-tabs"
@wheel="onWheel"
>
<span
v-for="group in emojis"
:key="group.id"
@ -47,40 +51,16 @@
@input="$event.target.composing = false"
>
</div>
<EmojiGrid
ref="emojiGrid"
:groups="emojisView"
@emoji="onEmoji"
@active-group="onActiveGroup"
/>
<div
ref="emoji-groups"
class="emoji-groups"
:class="groupsScrolledClass"
@scroll="onScroll"
v-if="showKeepOpen"
class="keep-open"
>
<div
v-for="group in emojisView"
:key="group.id"
class="emoji-group"
>
<h6
:ref="'group-' + group.id"
class="emoji-group-title"
>
{{ group.text }}
</h6>
<span
v-for="emoji in group.emojis"
:key="group.id + emoji.displayText"
:title="emoji.displayText"
class="emoji-item"
@click.stop.prevent="onEmoji(emoji)"
>
<span v-if="!emoji.imageUrl">{{ emoji.replacement }}</span>
<img
v-else
:src="emoji.imageUrl"
>
</span>
<span :ref="'group-end-' + group.id" />
</div>
</div>
<div class="keep-open">
<Checkbox v-model="keepOpen">
{{ $t('emoji.keep_open') }}
</Checkbox>

View file

@ -3,6 +3,11 @@ import UserListPopover from '../user_list_popover/user_list_popover.vue'
const EMOJI_REACTION_COUNT_CUTOFF = 12
const findEmojiByReplacement = (state, replacement) => {
const allEmojis = state.instance.emoji.concat(state.instance.customEmoji)
return allEmojis.find(emoji => emoji.replacement === replacement)
}
const EmojiReactions = {
name: 'EmojiReactions',
components: {
@ -54,6 +59,8 @@ const EmojiReactions = {
},
reactWith (emoji) {
this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
const emojiObject = findEmojiByReplacement(this.$store.state, emoji)
this.$store.commit('emojiUsed', emojiObject)
},
unreact (emoji) {
this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })

View file

@ -18,7 +18,7 @@
:src="reaction.url"
:title="reaction.name"
class="reaction-emoji"
width="2.55em"
height="2.55em"
>
{{ reaction.count }}
</span>
@ -49,6 +49,7 @@
display: flex;
margin-top: 0.25em;
flex-wrap: wrap;
container-type: inline-size;
}
.unicode-emoji {
@ -64,7 +65,9 @@
justify-content: center;
box-sizing: border-box;
.reaction-emoji {
width: 2.55em !important;
width: auto;
max-width: 96cqw;
height: 2.55em !important;
margin-right: 0.25em;
}
&:focus {

View file

@ -15,6 +15,7 @@ import {
faBookmark as faBookmarkReg,
faFlag
} from '@fortawesome/free-regular-svg-icons'
import { mapState } from 'vuex'
library.add(
faEllipsisH,
@ -144,6 +145,7 @@ const ExtraButtons = {
statusPoll: this.status.poll,
statusFiles: [...this.status.attachments],
statusScope: this.status.visibility,
statusLanguage: this.status.language,
statusContentType: data.content_type
}))
this.doDeleteStatus()
@ -190,7 +192,7 @@ const ExtraButtons = {
isEdited () {
return this.status.edited_at !== null
},
editingAvailable () { return this.$store.state.instance.editingAvailable }
editingAvailable () { return this.$store.state.instance.editingAvailable },
}
}

View file

@ -43,6 +43,7 @@ const FollowRequestCard = {
doApprove () {
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
this.$store.dispatch('removeFollowRequest', this.user)
this.$store.dispatch('decrementFollowRequestsCount')
const notifId = this.findFollowRequestNotificationId()
this.$store.dispatch('markSingleNotificationAsSeen', { id: notifId })
@ -66,6 +67,7 @@ const FollowRequestCard = {
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
.then(() => {
this.$store.dispatch('dismissNotificationLocal', { id: notifId })
this.$store.dispatch('decrementFollowRequestsCount')
this.$store.dispatch('removeFollowRequest', this.user)
})
this.hideDenyConfirmDialog()
@ -80,6 +82,11 @@ const FollowRequestCard = {
},
shouldConfirmDeny () {
return this.mergedConfig.modalOnDenyFollow
},
show () {
const notifId = this.$store.state.api.followRequests.find(req => req.id === this.user.id)
return notifId !== undefined
}
}
}

View file

@ -1,5 +1,5 @@
<template>
<basic-user-card :user="user">
<basic-user-card :user="user" v-if="show">
<div class="follow-request-card-content-container">
<button
class="btn button-default"

View file

@ -1,10 +1,26 @@
import FollowRequestCard from '../follow_request_card/follow_request_card.vue'
import withLoadMore from '../../hocs/with_load_more/with_load_more'
import List from '../list/list.vue'
import get from 'lodash/get'
const FollowRequestList = withLoadMore({
fetch: (props, $store) => $store.dispatch('fetchFollowRequests'),
select: (props, $store) => get($store.state.api, 'followRequests', []).map(req => $store.getters.findUser(req.id)),
destroy: (props, $store) => $store.dispatch('clearFollowRequests'),
childPropName: 'items',
additionalPropNames: ['userId']
})(List);
const FollowRequests = {
components: {
FollowRequestCard
FollowRequestCard,
FollowRequestList
},
computed: {
userId () {
return this.$store.state.users.currentUser.id
},
requests () {
return this.$store.state.api.followRequests
}

View file

@ -6,12 +6,11 @@
</div>
</div>
<div class="panel-body">
<FollowRequestCard
v-for="request in requests"
:key="request.id"
:user="request"
class="list-item"
/>
<FollowRequestList :user-id="userId">
<template #item="{item}">
<FollowRequestCard :user="item" />
</template>
</FollowRequestList>
</div>
</div>
</template>

View file

@ -0,0 +1,77 @@
<template>
<div class="followed-tag-card">
<span>
<router-link :to="{ name: 'tag-timeline', params: {tag: tag.name}}">
<span class="tag-link">#{{ tag.name }}</span>
</router-link>
<span class="unfollow-tag">
<button
v-if="isFollowing"
class="button-default unfollow-tag-button"
:title="$t('user_card.unfollow_tag')"
@click="unfollowTag(tag.name)"
>
{{ $t('user_card.unfollow_tag') }}
</button>
<button
v-else
class="button-default follow-tag-button"
:title="$t('user_card.follow_tag')"
@click="followTag(tag.name)"
>
{{ $t('user_card.follow_tag') }}
</button>
</span>
</span>
</div>
</template>
<script>
export default {
name: 'FollowedTagCard',
props: {
tag: {
type: Object,
required: true
},
},
// this is a hack to update the state of the button
// for some reason, List does not update on changes to the tag object
data: () => ({
isFollowing: true
}),
mounted () {
this.isFollowing = this.tag.following
},
methods: {
unfollowTag (tag) {
this.$store.dispatch('unfollowTag', tag)
this.isFollowing = false
},
followTag (tag) {
this.$store.dispatch('followTag', tag)
this.isFollowing = true
}
}
}
</script>
<style scoped>
.followed-tag-card {
margin-left: 1rem;
margin-top: 1rem;
margin-bottom: 1rem;
}
.unfollow-tag {
position: absolute;
right: 1rem;
}
.tag-link {
font-size: large;
}
.unfollow-tag-button, .follow-tag-button {
font-size: medium;
}
</style>

View file

@ -33,11 +33,6 @@ library.add(
)
const NavPanel = {
created () {
if (this.currentUser && this.currentUser.locked) {
this.$store.dispatch('startFetchingFollowRequests')
}
},
components: {
TimelineMenuContent
},
@ -54,11 +49,13 @@ const NavPanel = {
computed: {
...mapState({
currentUser: state => state.users.currentUser,
followRequestCount: state => state.api.followRequests.length,
privateMode: state => state.instance.private,
federating: state => state.instance.federating
federating: state => state.instance.federating,
}),
...mapGetters(['unreadAnnouncementCount'])
...mapGetters(['unreadAnnouncementCount']),
followRequestCount () {
return this.$store.state.users.currentUser.follow_requests_count
}
}
}

View file

@ -1,4 +1,4 @@
import PinchZoom from '@kazvmoe-infra/pinch-zoom-element'
import PinchZoom from '@floatingghost/pinch-zoom-element'
export default {
methods: {

View file

@ -103,9 +103,9 @@ export default {
convertExpiryFromUnit (unit, amount) {
// Note: we want seconds and not milliseconds
switch (unit) {
case 'minutes': return 0.001 * amount * DateUtils.MINUTE
case 'hours': return 0.001 * amount * DateUtils.HOUR
case 'days': return 0.001 * amount * DateUtils.DAY
case 'minutes': return amount * DateUtils.MINUTE / 1000
case 'hours': return amount * DateUtils.HOUR / 1000
case 'days': return amount * DateUtils.DAY / 1000
}
},
expiryAmountChange () {

View file

@ -13,6 +13,7 @@ import suggestor from '../emoji_input/suggestor.js'
import { mapGetters, mapState } from 'vuex'
import Checkbox from '../checkbox/checkbox.vue'
import Select from '../select/select.vue'
import iso6391 from 'iso-639-1'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@ -53,6 +54,14 @@ const pxStringToNumber = (str) => {
return Number(str.substring(0, str.length - 2))
}
const deleteDraft = (draftKey) => {
const draftData = JSON.parse(localStorage.getItem('drafts') || '{}');
delete draftData[draftKey];
localStorage.setItem('drafts', JSON.stringify(draftData));
}
const PostStatusForm = {
props: [
'statusId',
@ -63,6 +72,7 @@ const PostStatusForm = {
'statusMediaDescriptions',
'statusScope',
'statusContentType',
'statusLanguage',
'replyTo',
'quoteId',
'repliedUser',
@ -128,7 +138,7 @@ const PostStatusForm = {
statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser)
}
const { postContentType: contentType, sensitiveByDefault, sensitiveIfSubject } = this.$store.getters.mergedConfig
const { postContentType: contentType, sensitiveByDefault, sensitiveIfSubject, interfaceLanguage } = this.$store.getters.mergedConfig
let statusParams = {
spoilerText: this.subject || '',
@ -139,6 +149,7 @@ const PostStatusForm = {
poll: {},
mediaDescriptions: {},
visibility: this.suggestedVisibility(),
language: interfaceLanguage,
contentType
}
@ -153,10 +164,41 @@ const PostStatusForm = {
poll: this.statusPoll || {},
mediaDescriptions: this.statusMediaDescriptions || {},
visibility: this.statusScope || this.suggestedVisibility(),
language: this.statusLanguage || interfaceLanguage,
contentType: statusContentType
}
}
if (!this.statusId) {
let draftKey = 'status';
if (this.replyTo) {
draftKey = 'reply:' + this.replyTo;
} else if (this.quoteId) {
draftKey = 'quote:' + this.quoteId;
}
const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[draftKey];
if (draft) {
statusParams = {
spoilerText: draft.data.spoilerText,
status: draft.data.status,
sensitiveIfSubject,
nsfw: draft.data.nsfw,
files: draft.data.files,
poll: draft.data.poll,
mediaDescriptions: draft.data.mediaDescriptions,
visibility: draft.data.visibility,
language: draft.data.language,
contentType: draft.data.contentType
}
if (draft.data.poll) {
this.togglePollForm();
}
}
}
return {
dropFiles: [],
uploadingFiles: false,
@ -259,7 +301,10 @@ const PostStatusForm = {
...mapGetters(['mergedConfig']),
...mapState({
mobileLayout: state => state.interface.mobileLayout
})
}),
isoLanguages () {
return iso6391.getAllCodes();
}
},
watch: {
'newStatus': {
@ -273,6 +318,7 @@ const PostStatusForm = {
statusChanged () {
this.autoPreview()
this.updateIdempotencyKey()
this.saveDraft()
},
clearStatus () {
const newStatus = this.newStatus
@ -282,6 +328,7 @@ const PostStatusForm = {
files: [],
visibility: newStatus.visibility,
contentType: newStatus.contentType,
language: newStatus.language,
poll: {},
mediaDescriptions: {}
}
@ -341,6 +388,7 @@ const PostStatusForm = {
inReplyToStatusId: this.replyTo,
quoteId: this.quoteId,
contentType: newStatus.contentType,
language: newStatus.language,
poll,
idempotencyKey: this.idempotencyKey
}
@ -375,6 +423,7 @@ const PostStatusForm = {
inReplyToStatusId: this.replyTo,
quoteId: this.quoteId,
contentType: newStatus.contentType,
language: newStatus.language,
poll: {},
preview: true
}).then((data) => {
@ -391,8 +440,38 @@ const PostStatusForm = {
}).finally(() => {
this.previewLoading = false
})
let draftKey = 'status';
if (this.replyTo) {
draftKey = 'reply:' + this.replyTo;
} else if (this.quoteId) {
draftKey = 'quote:' + this.quoteId;
}
deleteDraft(draftKey)
},
debouncePreviewStatus: debounce(function () { this.previewStatus() }, 500),
saveDraft() {
const draftData = JSON.parse(localStorage.getItem('drafts') || '{}');
let draftKey = 'status';
if (this.replyTo) {
draftKey = 'reply:' + this.replyTo;
} else if (this.quoteId) {
draftKey = 'quote:' + this.quoteId;
}
if (this.newStatus.status || this.newStatus.spoilerText || this.newStatus.files.length > 0 || this.newStatus.poll.length > 0) {
draftData[draftKey] = {
updatedAt: new Date(),
data: this.newStatus,
};
localStorage.setItem('drafts', JSON.stringify(draftData));
} else {
deleteDraft(draftKey);
}
},
autoPreview () {
if (!this.preview) return
this.previewLoading = true

View file

@ -194,6 +194,23 @@
:on-scope-change="changeVis"
/>
<div
class="language-selector"
>
<Select
id="post-language"
v-model="newStatus.language"
class="form-control"
>
<option
v-for="language in isoLanguages"
:key="language"
:value="language"
>
{{ language }}
</option>
</Select>
</div>
<div
v-if="postFormats.length > 1"
class="text-format"

View file

@ -79,8 +79,16 @@ const registration = {
if (!this.v$.$invalid) {
try {
await this.signUp(this.user)
this.$router.push({ name: 'friends' })
const data = await this.signUp(this.user)
if (data.me) {
this.$router.push({ name: 'friends' })
} else if (data.identifier === 'awaiting_approval') {
this.$router.push({ name: 'registration-request-sent' })
} else if (data.identifier === 'missing_confirmed_email') {
this.$router.push({ name: 'awaiting-email-confirmation' })
} else {
console.warn('Unknown response from sign up', data)
}
} catch (error) {
console.warn('Registration failed: ', error)
this.setCaptcha()

View file

@ -177,6 +177,7 @@
<div
v-if="accountApprovalRequired"
class="form-group"
:class="{ 'form-group--error': v$.user.reason.$error }"
>
<label
class="form--label"

View file

@ -0,0 +1,4 @@
export default {
computed: {
}
}

View file

@ -0,0 +1,12 @@
<template>
<div class="panel panel-default">
<div class="panel-heading">
<h4>{{ $t('registration.request_sent_title') }}</h4>
</div>
<div class="panel-body">
<p>{{ $t('registration.request_sent') }}</p>
</div>
</div>
</template>
<script src="./registration_request_sent.js"></script>

View file

@ -2,8 +2,6 @@ import { unescape, flattenDeep } from 'lodash'
import { getTagName, processTextForEmoji, getAttrs } from 'src/services/html_converter/utility.service.js'
import { convertHtmlToTree } from 'src/services/html_converter/html_tree_converter.service.js'
import { convertHtmlToLines } from 'src/services/html_converter/html_line_converter.service.js'
import { marked } from 'marked'
import markedMfm from 'marked-mfm'
import StillImage from 'src/components/still-image/still-image.vue'
import MentionsLine, { MENTIONS_LIMIT } from 'src/components/mentions_line/mentions_line.vue'
import HashtagLink from 'src/components/hashtag_link/hashtag_link.vue'
@ -190,7 +188,7 @@ export default {
break
}
case 'span':
if (this.handleLinks && attrs['class'] && attrs['class'].includes('h-card')) {
if (this.handleLinks && attrs?.['class']?.includes?.('h-card')) {
return ['', children.map(processItem), '']
}
}

View file

@ -50,7 +50,6 @@
.emoji {
display: inline-block;
width: var(--emoji-size, 32px);
height: var(--emoji-size, 32px);
}

View file

@ -16,7 +16,6 @@
class="fa-scale-110 fa-old-padding"
/>
</button>
{{ ' ' }}
<button
v-if="showPrivate"
class="button-unstyled scope"
@ -30,7 +29,6 @@
class="fa-scale-110 fa-old-padding"
/>
</button>
{{ ' ' }}
<button
v-if="showUnlisted"
class="button-unstyled scope"
@ -44,7 +42,6 @@
class="fa-scale-110 fa-old-padding"
/>
</button>
{{ ' ' }}
<button
v-if="showPublic"
class="button-unstyled scope"
@ -87,6 +84,7 @@
min-width: 1.3em;
min-height: 1.3em;
text-align: center;
margin-right: 0.4em;
&.selected svg {
color: $fallback--lightText;

View file

@ -73,6 +73,7 @@
.search-bar-input {
flex: 1 0 auto;
margin-left: 0.5em;
}
.cancel-search {

View file

@ -76,6 +76,10 @@
position: absolute;
right: 20px;
padding-right: 10px;
@media all and (max-width: 800px) {
display: none;
}
}
}
}

View file

@ -44,6 +44,10 @@
<div class="panel-body">
<SettingsModalContent v-if="modalOpenedOnce" />
</div>
<span
id="unscrolled-content"
class="extra-content"
/>
<div class="panel-footer settings-footer">
<Popover
class="export"
@ -53,7 +57,7 @@
:bound-to="{ x: 'container' }"
remove-padding
>
<template v-slot:trigger>
<template #trigger>
<button
class="btn button-default"
:title="$t('general.close')"
@ -65,7 +69,7 @@
/>
</button>
</template>
<template v-slot:content="{close}">
<template #content="{close}">
<div class="dropdown-menu">
<button
class="button-default dropdown-item dropdown-item-icon"
@ -103,14 +107,11 @@
<Checkbox
:model-value="!!expertLevel"
class="expertMode"
@update:modelValue="expertLevel = Number($event)"
>
{{ $t("settings.expert_mode") }}
</Checkbox>
<span
id="unscrolled-content"
class="extra-content"
/>
<button
v-if="currentUser"
class="button-default logout-button"

View file

@ -407,6 +407,15 @@
{{ $t('settings.preload_images') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="useBlurhash"
expert="1"
:disabled="!hideNsfw"
>
{{ $t('settings.use_blurhash') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="useOneClickNsfw"

View file

@ -43,7 +43,9 @@ const ProfileTab = {
bannerPreview: null,
background: null,
backgroundPreview: null,
emailLanguage: this.$store.state.users.currentUser.language || ''
emailLanguage: this.$store.state.users.currentUser.language || '',
newPostTTLDays: this.$store.state.users.currentUser.status_ttl_days,
expirePosts: this.$store.state.users.currentUser.status_ttl_days !== null,
}
},
components: {
@ -123,7 +125,8 @@ const ProfileTab = {
display_name: this.newName,
fields_attributes: this.newFields.filter(el => el != null),
bot: this.bot,
show_role: this.showRole
show_role: this.showRole,
status_ttl_days: this.expirePosts ? this.newPostTTLDays : -1
/* eslint-enable camelcase */
}

View file

@ -4,6 +4,10 @@
margin: 0;
}
.expire-posts-days {
margin-left: 1em;
}
.visibility-tray {
padding-top: 5px;
}

View file

@ -89,6 +89,20 @@
{{ $t('settings.bot') }}
</Checkbox>
</p>
<p>
<Checkbox v-model="expirePosts">
{{ $t('settings.expire_posts_enabled') }}
</Checkbox>
<input
v-model="newPostTTLDays"
:disabled="!expirePosts"
type="number"
min="1"
max="730"
class="expire-posts-days"
:placeholder="$t('settings.expire_posts_input_placeholder')"
/>
</p>
<p>
<interface-language-switcher
:prompt-text="$t('settings.email_language')"

View file

@ -284,7 +284,6 @@
box-shadow: none;
background: transparent;
color: var(--faint, $fallback--faint);
align-self: stretch;
}
.theme-color-cl,
@ -318,11 +317,11 @@
.extra-content {
.apply-container {
padding-left: 15vw;
display: flex;
flex-direction: row;
justify-content: space-around;
justify-content: space-evenly;
flex-grow: 1;
.btn {
flex-grow: 1;
min-height: 2em;

View file

@ -958,20 +958,22 @@
v-if="isActive"
to="#unscrolled-content"
>
<div class="apply-container">
<button
class="btn button-default submit"
:disabled="!themeValid"
@click="setCustomTheme"
>
{{ $t('general.apply') }}
</button>
<button
class="btn button-default"
@click="clearAll"
>
{{ $t('settings.style.switcher.reset') }}
</button>
<div class="panel-body settings-footer">
<div class="apply-container">
<button
class="btn button-default submit"
:disabled="!themeValid"
@click="setCustomTheme"
>
{{ $t('general.apply') }}
</button>
<button
class="btn button-default"
@click="clearAll"
>
{{ $t('settings.style.switcher.reset') }}
</button>
</div>
</div>
</teleport>
</div>

View file

@ -1,4 +1,4 @@
@import '../../_variables.scss';
@import "../../_variables.scss";
.Status {
min-width: 0;
@ -42,6 +42,10 @@
display: flex;
padding: var(--status-margin, $status-margin);
.content {
overflow: hidden;
}
> * {
min-width: 0;
}
@ -130,6 +134,15 @@
.heading-left {
display: flex;
min-width: 0;
flex-wrap: wrap;
img {
aspect-ratio: 1 / 1;
}
.nowrap {
white-space: nowrap;
}
}
.heading-right {
@ -139,6 +152,7 @@
.button-unstyled {
padding: 5px;
margin: -5px;
height: min-content;
&:hover svg {
color: $fallback--lightText;
@ -185,7 +199,7 @@
.reply-to-popover {
.reply-to:hover::before {
content: '';
content: "";
display: block;
position: absolute;
bottom: 0;
@ -195,13 +209,12 @@
}
.faint-link:hover {
// override default
text-decoration: none;
}
&.-strikethrough {
.reply-to::after {
content: '';
content: "";
display: block;
position: absolute;
top: 50%;
@ -293,10 +306,12 @@
position: relative;
width: 100%;
display: flex;
flex-wrap: wrap;
justify-content: left;
margin-top: var(--status-margin, $status-margin);
> * {
max-width: 4em;
min-width: fit-content;
flex: 1;
}
}
@ -340,7 +355,7 @@
margin-left: 0.2em;
&::before {
content: ' ';
content: " ";
}
}
@ -387,7 +402,7 @@
align-items: center;
&::before {
content: '';
content: "";
position: absolute;
height: 100%;
width: 1px;

View file

@ -166,19 +166,21 @@
>
{{ status.user.name }}
</h4>
<router-link
class="account-name"
:title="status.user.screen_name_ui"
:to="userProfileLink"
>
{{ status.user.screen_name_ui }}
</router-link>
<img
v-if="!!(status.user && status.user.favicon)"
class="status-favicon"
:src="status.user.favicon"
:title="faviconAlt(status)"
>
<span class="nowrap">
<router-link
class="account-name"
:title="status.user.screen_name_ui"
:to="userProfileLink"
>
@{{ status.user.screen_name_ui }}
</router-link>
<img
v-if="!!(status.user && status.user.favicon)"
class="status-favicon"
:src="status.user.favicon"
:title="faviconAlt(status)"
>
</span>
</div>
<span class="heading-right">
@ -350,22 +352,25 @@
</div>
</div>
<StatusContent
ref="content"
:status="status"
:no-heading="noHeading"
:highlight="highlight"
:focused="isFocused"
:controlled-showing-tall="controlledShowingTall"
:controlled-expanding-subject="controlledExpandingSubject"
:controlled-showing-long-subject="controlledShowingLongSubject"
:controlled-toggle-showing-tall="controlledToggleShowingTall"
:controlled-toggle-expanding-subject="controlledToggleExpandingSubject"
:controlled-toggle-showing-long-subject="controlledToggleShowingLongSubject"
@mediaplay="addMediaPlaying($event)"
@mediapause="removeMediaPlaying($event)"
@parseReady="setHeadTailLinks"
/>
<div class="content">
<StatusContent
ref="content"
class="status-content"
:status="status"
:no-heading="noHeading"
:highlight="highlight"
:focused="isFocused"
:controlled-showing-tall="controlledShowingTall"
:controlled-expanding-subject="controlledExpandingSubject"
:controlled-showing-long-subject="controlledShowingLongSubject"
:controlled-toggle-showing-tall="controlledToggleShowingTall"
:controlled-toggle-expanding-subject="controlledToggleExpandingSubject"
:controlled-toggle-showing-long-subject="controlledToggleShowingLongSubject"
@mediaplay="addMediaPlaying($event)"
@mediapause="removeMediaPlaying($event)"
@parseReady="setHeadTailLinks"
/>
</div>
<div
v-if="inConversation && !isPreview && replies && replies.length"
@ -532,6 +537,6 @@
</div>
</template>
<script src="./status.js" ></script>
<script src="./status.js"></script>
<style src="./status.scss" lang="scss"></style>

View file

@ -22,21 +22,18 @@
._mfm_x2_ {
.emoji {
width: 100px;
height: 100px;
}
}
._mfm_x3_ {
.emoji {
width: 150px;
height: 150px;
}
}
._mfm_x4_ {
.emoji {
width: 200px;
height: 200px;
}
}

View file

@ -71,7 +71,7 @@
img, video {
&.emoji {
width: 50px;
max-width: 100%;
height: 50px;
}
}
@ -89,7 +89,6 @@
animation: none !important;
}
.emoji {
width: 32px !important;
height: 32px !important;
}
}

View file

@ -6,11 +6,13 @@ import TimelineMenuTabs from '../timeline_menu_tabs/timeline_menu_tabs.vue'
import TimelineQuickSettings from './timeline_quick_settings.vue'
import { debounce, throttle, keyBy } from 'lodash'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faCircleNotch, faCog } from '@fortawesome/free-solid-svg-icons'
import { faCircleNotch, faCog, faPlus, faMinus } from '@fortawesome/free-solid-svg-icons'
library.add(
faCircleNotch,
faCog
faCog,
faPlus,
faMinus
)
const Timeline = {
@ -90,6 +92,15 @@ const Timeline = {
},
showPanelNavShortcuts () {
return this.$store.getters.mergedConfig.showPanelNavShortcuts
},
currentUser () {
return this.$store.state.users.currentUser
},
tagData () {
return this.$store.state.tags.tags[this.tag]
},
tagFollowed () {
return this.$store.state.tags.tags[this.tag]?.following
}
},
created () {
@ -118,6 +129,10 @@ const Timeline = {
}
window.addEventListener('keydown', this.handleShortKey)
setTimeout(this.determineVisibleStatuses, 250)
if (this.tag) {
this.$store.dispatch('getTag', this.tag)
}
},
unmounted () {
window.removeEventListener('scroll', this.handleScroll)
@ -232,6 +247,12 @@ const Timeline = {
}, 200),
handleVisibilityChange () {
this.unfocused = document.hidden
},
followTag (tag) {
return this.$store.dispatch('followTag', tag)
},
unfollowTag (tag) {
return this.$store.dispatch('unfollowTag', tag)
}
},
watch: {

View file

@ -21,6 +21,36 @@
{{ $t('timeline.up_to_date') }}
</div>
<TimelineQuickSettings v-if="!embedded" />
<div
v-if="currentUser && tag !== undefined && tagData && !tagFollowed"
class="followTag"
>
<button
class="button-default"
:title="$t('timeline.follow_tag')"
@click="followTag(tag)"
>
<FAIcon
size="sm"
icon="plus"
/>
</button>
</div>
<div
v-if="currentUser && tag !== undefined && tagData && tagFollowed"
class="followTag"
>
<button
class="button-default"
:title="$t('timeline.unfollow_tag')"
@click="unfollowTag(tag)"
>
<FAIcon
size="sm"
icon="minus"
/>
</button>
</div>
</div>
<div :class="classes.body">
<div

View file

@ -8,6 +8,7 @@ import {
faHome,
faCircle
} from '@fortawesome/free-solid-svg-icons'
import { federatedTimelineVisible, publicTimelineVisible, bubbleTimelineVisible } from '../../lib/timeline_visibility'
library.add(
faUsers,
@ -24,7 +25,9 @@ const TimelineMenuContent = {
currentUser: state => state.users.currentUser,
privateMode: state => state.instance.private,
federating: state => state.instance.federating,
showBubbleTimeline: state => (state.instance.localBubbleInstances.length > 0)
publicTimelineVisible,
federatedTimelineVisible,
bubbleTimelineVisible,
})
}
}

View file

@ -16,7 +16,7 @@
>{{ $t("nav.home_timeline") }}</span>
</router-link>
</li>
<li v-if="currentUser && showBubbleTimeline">
<li v-if="bubbleTimelineVisible">
<router-link
class="menu-item"
:to="{ name: 'bubble-timeline' }"
@ -32,7 +32,7 @@
>{{ $t("nav.bubble_timeline") }}</span>
</router-link>
</li>
<li v-if="currentUser || !privateMode">
<li v-if="publicTimelineVisible">
<router-link
class="menu-item"
:to="{ name: 'public-timeline' }"
@ -48,7 +48,7 @@
>{{ $t("nav.public_tl") }}</span>
</router-link>
</li>
<li v-if="federating && (currentUser || !privateMode)">
<li v-if="federatedTimelineVisible">
<router-link
class="menu-item"
:to="{ name: 'public-external-timeline' }"
@ -62,6 +62,7 @@
:title="$t('nav.twkn_timeline_description')"
:aria-label="$t('nav.twkn_timeline_description')"
>{{ $t("nav.twkn") }}</span>
</router-link>
</li>
<li v-if="currentUser">

View file

@ -8,6 +8,7 @@ import {
faHome
} from '@fortawesome/free-solid-svg-icons'
import { faCircle } from '@fortawesome/free-regular-svg-icons'
import { federatedTimelineVisible, publicTimelineVisible, bubbleTimelineVisible } from '../../lib/timeline_visibility'
library.add(
faUsers,
faGlobe,
@ -22,7 +23,10 @@ const TimelineMenuContent = {
...mapState({
currentUser: state => state.users.currentUser,
privateMode: state => state.instance.private,
federating: state => state.instance.federating
federating: state => state.instance.federating,
publicTimelineVisible,
federatedTimelineVisible,
bubbleTimelineVisible,
})
}
}

View file

@ -16,7 +16,7 @@
>{{ $t("nav.home_timeline") }}</span>
</router-link>
</li>
<li v-if="currentUser">
<li v-if="bubbleTimelineVisible">
<router-link
class="menu-item"
:to="{ name: 'bubble-timeline' }"
@ -32,7 +32,7 @@
>{{ $t("nav.bubble_timeline") }}</span>
</router-link>
</li>
<li v-if="currentUser || !privateMode">
<li v-if="publicTimelineVisible">
<router-link
class="menu-item"
:to="{ name: 'public-timeline' }"
@ -48,7 +48,7 @@
>{{ $t("nav.public_tl") }}</span>
</router-link>
</li>
<li v-if="federating && (currentUser || !privateMode)">
<li v-if="federatedTimelineVisible">
<router-link
class="menu-item"
:to="{ name: 'public-external-timeline' }"

View file

@ -4,6 +4,12 @@ import { library } from '@fortawesome/fontawesome-svg-core'
import {
faChevronDown
} from '@fortawesome/free-solid-svg-icons'
import { mapState } from 'vuex'
import {
publicTimelineVisible,
federatedTimelineVisible,
bubbleTimelineVisible,
} from '../../lib/timeline_visibility'
library.add(faChevronDown)
@ -36,12 +42,15 @@ const TimelineMenuTabs = {
}
},
computed: {
currentUser () {
return this.$store.state.users.currentUser
},
privateMode () {
return this.$store.state.instance.private
}
},
...mapState({
currentUser: state => state.users.currentUser,
publicTimelineVisible,
federatedTimelineVisible,
bubbleTimelineVisible,
})
},
methods: {
timelineName () {

View file

@ -18,6 +18,7 @@
<router-link
:to="{ name: 'public-timeline' }"
class="nav-icon"
v-if="publicTimelineVisible"
>
<FAIcon
fixed-width
@ -27,7 +28,7 @@
/>
</router-link>
<router-link
v-if="currentUser"
v-if="bubbleTimelineVisible"
:to="{ name: 'bubble-timeline' }"
class="nav-icon"
>
@ -41,6 +42,7 @@
<router-link
:to="{ name: 'public-external-timeline' }"
class="nav-icon"
v-if="federatedTimelineVisible"
>
<FAIcon
fixed-width

View file

@ -154,14 +154,6 @@ export default {
unmuteUser () {
this.$store.dispatch('unmuteUser', this.user.id)
},
muteDomain () {
this.$store.dispatch('muteDomain', this.user.screen_name.split('@')[1])
.then(() => this.refetchRelationship())
},
unmuteDomain () {
this.$store.dispatch('unmuteDomain', this.user.screen_name.split('@')[1])
.then(() => this.refetchRelationship())
},
subscribeUser () {
return this.$store.dispatch('subscribeUser', this.user.id)
},

View file

@ -2,7 +2,7 @@
.user-card {
position: relative;
z-index: 1;
z-index: 10;
&:hover {
--_still-image-img-visibility: visible;
@ -235,7 +235,7 @@
line-height: 22px;
flex-wrap: wrap;
.following, .requested_by {
.following, .requested_by, .blocking {
flex: 1 0 auto;
margin: 0;
margin-bottom: .25em;

View file

@ -67,6 +67,17 @@
icon="external-link-alt"
/>
</a>
<a
v-if="isOtherUser"
:href="user.statusnet_profile_url + '.rss'"
target="_blank"
class="button-unstyled external-link-button"
>
<FAIcon
class="icon"
icon="rss"
/>
</a>
<AccountActions
v-if="isOtherUser && loggedIn"
:user="user"
@ -116,6 +127,12 @@
</div>
</div>
<div class="user-meta">
<div
v-if="relationship.blocked_by && loggedIn && isOtherUser"
class="blocking"
>
{{ $t('user_card.blocks_you') }}
</div>
<div
v-if="relationship.followed_by && loggedIn && isOtherUser"
class="following"
@ -176,6 +193,7 @@
<FollowButton
:relationship="relationship"
:user="user"
:disabled="relationship.blocked_by"
/>
<template v-if="relationship.following">
<ProgressButton
@ -225,22 +243,6 @@
{{ $t('user_card.mute') }}
</button>
</div>
<div>
<button
v-if="relationship.domain_blocking"
class="btn button-default btn-block toggled"
@click="unmuteDomain"
>
{{ $t('user_card.domain_muted') }}
</button>
<button
v-else
class="btn button-default btn-block"
@click="muteDomain"
>
{{ $t('user_card.mute_domain') }}
</button>
</div>
<div>
<button
class="btn button-default btn-block"

View file

@ -10,11 +10,14 @@ import withLoadMore from '../../hocs/with_load_more/with_load_more'
import { debounce } from 'lodash'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faCircleNotch
faCircleNotch,
faCircleCheck
} from '@fortawesome/free-solid-svg-icons'
import FollowedTagCard from '../followed_tag_card/FollowedTagCard.vue'
library.add(
faCircleNotch
faCircleNotch,
faCircleCheck
)
const FollowerList = withLoadMore({
@ -33,12 +36,23 @@ const FriendList = withLoadMore({
additionalPropNames: ['userId']
})(List)
const FollowedTagList = withLoadMore({
fetch: (props, $store) => $store.dispatch('fetchFollowedTags', props.userId),
select: (props, $store) => get($store.getters.findUser(props.userId), 'followedTagIds', []).map(id => $store.getters.findTag(id)),
destroy: (props, $store) => $store.dispatch('clearFollowedTags', props.userId),
childPropName: 'items',
additionalPropNames: ['userId']
})(List)
const isUserPage = ({ name }) => name === 'user-profile' || name === 'external-user-profile'
const UserProfile = {
data () {
return {
error: false,
userId: null,
tab: 'statuses',
followsTab: 'users',
footerRef: null,
note: null,
noteLoading: false
@ -163,6 +177,9 @@ const UserProfile = {
this.tab = tab
this.$router.replace({ hash: `#${tab}` })
},
onFollowsTabSwitch (tab) {
this.followsTab = tab
},
linkClicked ({ target }) {
if (target.tagName === 'SPAN') {
target = target.parentNode
@ -182,12 +199,12 @@ const UserProfile = {
},
watch: {
'$route.params.id': function (newVal) {
if (newVal) {
if (isUserPage(this.$route) && newVal) {
this.switchUser(newVal)
}
},
'$route.params.name': function (newVal) {
if (newVal) {
if (isUserPage(this.$route) && newVal) {
this.switchUser(newVal)
}
},
@ -198,6 +215,7 @@ const UserProfile = {
}
},
components: {
FollowedTagCard,
UserCard,
Timeline,
FollowerList,
@ -205,7 +223,8 @@ const UserProfile = {
FollowCard,
TabSwitcher,
Conversation,
RichContent
RichContent,
FollowedTagList
}
}

View file

@ -37,6 +37,15 @@
:html="field.value"
:emoji="user.emoji"
/>
<span
v-if="field.verified_at"
class="user-profile-field-validated"
>
<FAIcon
icon="check-circle"
:title="$t('user_profile.field_validated')"
/>
</span>
</dd>
</dl>
</div>
@ -95,22 +104,48 @@
v-if="followsTabVisible"
key="followees"
:label="$t('user_card.followees')"
:disabled="!user.friends_count"
>
<FriendList :user-id="userId">
<template v-slot:item="{item}">
<FollowCard :user="item" />
</template>
</FriendList>
<tab-switcher
:active-tab="followsTab"
:render-only-focused="true"
:on-switch="onFollowsTabSwitch"
>
<div
key="users"
:label="$t('user_card.followed_users')"
>
<FriendList :user-id="userId">
<template #item="{item}">
<FollowCard :user="item" />
</template>
</FriendList>
</div>
<div
key="tags"
v-if="isUs"
:label="$t('user_card.followed_tags')"
>
<FollowedTagList
:user-id="userId"
:get-key="(item) => item.name"
>
<template #item="{item}">
<FollowedTagCard :tag="item" />
</template>
<template #empty>
{{ $t('user_card.not_following_any_hashtags')}}
</template>
</FollowedTagList>
</div>
</tab-switcher>
</div>
<div
v-if="followersTabVisible"
key="followers"
:label="$t('user_card.followers')"
:disabled="!user.followers_count"
>
<FollowerList :user-id="userId">
<template v-slot:item="{item}">
<template #item="{item}">
<FollowCard
:user="item"
:no-follows-you="isUs"
@ -225,6 +260,11 @@
padding: 0.5em 1.5em;
box-sizing: border-box;
}
.user-profile-field-validated {
margin-left: 1rem;
color: green;
}
}
}

View file

@ -59,7 +59,8 @@ const withLoadMore = ({
this.loading = false
this.bottomedOut = isEmpty(newEntries)
})
.catch(() => {
.catch((e) => {
console.error(e)
this.loading = false
this.error = true
})
@ -88,7 +89,7 @@ const withLoadMore = ({
const children = this.$slots
return (
<div class="with-load-more">
<WrappedComponent {...props}>
<WrappedComponent {...props} >
{children}
</WrappedComponent>
<div class="with-load-more-footer">

View file

@ -135,7 +135,7 @@
},
"scope_in_timeline": {
"direct": "Direkt",
"local": "Lokal - nur deine eigene Instanz kann diesen Beitrag sehen",
"local": "Lokal - nur deine eigene Instanz kann diese Nachricht sehen",
"private": "Nur an Folgende",
"public": "Öffentlich",
"unlisted": "Nicht gelistet"
@ -252,6 +252,10 @@
"hint": "Anmelden um an der Diskussion teilzunehmen",
"login": "Anmelden",
"logout": "Abmelden",
"logout_confirm": "Willst du dich wirklich abmelden?",
"logout_confirm_accept_button": "Abmelden",
"logout_confirm_cancel_button": "Abbrechen",
"logout_confirm_title": "Abmelden",
"password": "Passwort",
"placeholder": "meinbenutzername",
"recovery_code": "Wiederherstellungscode",
@ -264,6 +268,32 @@
"next": "Weiter",
"previous": "Zurück"
},
"moderation": {
"moderation": "Moderation",
"reports": {
"add_note": "Notiz hinzufügen",
"close": "Schließen",
"delete_note": "Löschen",
"delete_note_accept": "Ja, löschen",
"delete_note_cancel": "Nein, behalten",
"delete_note_confirm": "Soll diese Notiz wirklich gelöscht werden?",
"delete_note_title": "Löschen bestätigen",
"no_content": "Keine Beschreibung angegeben",
"no_reports": "Keine Meldungen verfügbar",
"note_placeholder": "Hinterlasse eine Notiz",
"notes": "{ count } Notiz | { count } Notizen",
"reopen": "Wieder öffnen",
"report": "Meldung über",
"reports": "Meldungen",
"resolve": "Lösen",
"show_closed": "Geschlossene anzeigen",
"statuses": "{ count } Post| { count } Posts",
"tag_policy_notice": "Schalte die TagPolicy-MRF ein, um Post-Beschränkungen einzustellen",
"tags": "Post-Einschränkungen einstellen"
},
"statuses": "Posts",
"users": "Benutzer"
},
"nav": {
"about": "Über",
"administration": "Administration",
@ -278,6 +308,7 @@
"interactions": "Interaktionen",
"lists": "Listen",
"mentions": "Erwähnungen",
"moderation": "Moderation",
"preferences": "Voreinstellungen",
"public_timeline_description": "Öffentliche Beiträge von dieser Instanz",
"public_tl": "Öffentliche Zeitleiste",
@ -345,10 +376,10 @@
},
"content_warning": "Inhaltswarnung (optional)",
"default": "Sitze gerade im Hofbräuhaus",
"direct_warning_to_all": "Dieser Beitrag wird für alle erwähnten Benutzer sichtbar sein.",
"direct_warning_to_first_only": "Dieser Beitrag wird für alle Benutzer, die am Anfang der Nachricht erwähnt wurden, sichtbar sein.",
"direct_warning_to_all": "Diese Nachricht wird für alle erwähnten Benutzer sichtbar sein.",
"direct_warning_to_first_only": "Diese Nachricht wird für alle Benutzer, die am Anfang der Nachricht erwähnt wurden, sichtbar sein.",
"edit_remote_warning": "Änderungen könnten auf manchen Instanzen nicht sichtbar sein!",
"edit_status": "Beitrag ändern",
"edit_status": "Nachricht bearbeiten",
"edit_unsupported_warning": "Umfragen und Erwähnungen werden durch die Bearbeitung nicht geändert.",
"empty_status_error": "Eine Nachricht ohne Text und ohne Anhänge kann nicht gesendet werden",
"media_description": "Medienbeschreibung",
@ -360,20 +391,22 @@
"preview": "Vorschau",
"preview_empty": "Leer",
"scope": {
"direct": "Direkt - Beitrag nur an erwähnte Profile",
"local": "Lokal - diesen Beitrag nicht föderieren",
"private": "Nur Follower - Beitrag nur für Follower sichtbar",
"public": "Öffentlich - Beitrag an öffentliche Zeitleisten",
"direct": "Direkt - Nachricht nur an erwähnte Profile",
"local": "Lokal - diese Nachricht nicht föderieren",
"private": "Nur Follower - Nachricht nur für Follower sichtbar",
"public": "Öffentlich - Nachricht an öffentliche Zeitleisten",
"unlisted": "Nicht gelistet - Nicht in öffentlichen Zeitleisten anzeigen"
},
"scope_notice": {
"local": "Dieser Bericht ist auf anderen Instanzen nicht sichbar",
"private": "Dieser Beitrag wird nur für deine Follower sichtbar sein",
"public": "Dieser Beitrag wird für alle sichtbar sein",
"unlisted": "Dieser Beitrag wird weder in der öffentlichen Zeitleiste noch im gesamten bekannten Netzwerk sichtbar sein"
"private": "Diese Nachricht wird nur für deine Follower sichtbar sein",
"public": "Diese Nachricht wird für alle sichtbar sein",
"unlisted": "Diese Nachricht wird weder in der öffentlichen Zeitleiste noch im gesamten bekannten Netzwerk sichtbar sein"
}
},
"registration": {
"awaiting_email_confirmation": "Dein Account wurde registriert und eine E-Mail wurde an deine Adresse gesendet. Bitte lies die E-Mail, um die Registrierung abzuschließen.",
"awaiting_email_confirmation_title": "Warte auf E-Mail-Bestätigung",
"bio": "Bio",
"bio_placeholder": "z. B.\nHallo! Willkommen auf meinem Profil.\nIch mag Anime und Spiele. Hoffentlich können wir Freunde sein!",
"captcha": "CAPTCHA",
@ -387,6 +420,8 @@
"reason_placeholder": "Diese Instanz bestätigt Registrierungen manuell. \nLass die Admins wissen warum du dich registrieren willst.",
"register": "Registrierung",
"registration": "Registrierung",
"request_sent": "Deine Bitte zur Registrierung wurde weitergeleitet. Du erhälst eine E-Mail, wenn deiner Registrierung zugestimmt wurde.",
"request_sent_title": "Bitte zur Registrierung gesendet",
"token": "Einladungsschlüssel",
"username_placeholder": "z. B. akko",
"validations": {
@ -465,10 +500,10 @@
"confirm_dialogs": "Bestätigung erforderlich für:",
"confirm_dialogs_approve_follow": "Annehmen einer Followanfrage",
"confirm_dialogs_block": "Jemanden blockieren",
"confirm_dialogs_delete": "Löschen eines Beitrages",
"confirm_dialogs_delete": "Löschen einer Nachricht",
"confirm_dialogs_deny_follow": "Ablehnen einer Followanfrage",
"confirm_dialogs_mute": "Jemanden stummschalten",
"confirm_dialogs_repeat": "Wiederholen eines Beitrages",
"confirm_dialogs_repeat": "Wiederholen einer Nachricht",
"confirm_dialogs_unfollow": "Folgen beenden",
"confirm_new_password": "Neues Passwort bestätigen",
"confirmation_dialogs": "Bestätigungs-Einstellungen",
@ -496,6 +531,8 @@
"enable_web_push_notifications": "Web-Pushbenachrichtigungen aktivieren",
"enter_current_password_to_confirm": "Gib dein aktuelles Passwort ein, um deine Identität zu bestätigen",
"expert_mode": "Erweiterte Einstellungen anzeigen",
"expire_posts_enabled": "Posts löschen, die älter als eine Anzahl an Tagen sind",
"expire_posts_input_placeholder": "Anzahl an Tagen",
"export_theme": "Farbschema speichern",
"file_export_import": {
"backup_restore": "Einstellungen backuppen",
@ -535,7 +572,7 @@
"hide_media_previews": "Verstecke Vorschau von Medien",
"hide_muted_posts": "Verberge Beiträge stummgeschalteter Nutzer",
"hide_muted_threads": "Stummgeschaltete Unterhaltungen ausblenden",
"hide_post_stats": "Beitragsstatistiken verbergen (z.B. die Anzahl der Favoriten)",
"hide_post_stats": "Nachrichtenstatistiken verbergen (z.B. die Anzahl der Favoriten)",
"hide_shoutbox": "Shoutbox der Instanz verbergen",
"hide_site_favicon": "Favicon der Instanz im Top-Panel nicht anzeigen",
"hide_site_name": "Instanznamen im Top-Panel nicht anzeigen",
@ -562,7 +599,7 @@
"loop_video_silent_only": "Nur Videos ohne Ton wiederholen (z.B. Mastodons \"gifs\")",
"mascot": "Mastodon-FE-Maskottchen",
"max_depth_in_thread": "Maximale Tiefe, bis zu der Unterhaltungen standardmäßig angezeigt werden",
"max_thumbnails": "Maximale Anzahl von Vorschaubildern pro Beitrag",
"max_thumbnails": "Maximale Anzahl von Vorschaubildern pro Nachricht (leer = keine Beschränkung)",
"mention_link_bolden_you": "eigene Erwähnungen hervorheben",
"mention_link_display": "Erwähungs-Links anzeigen",
"mention_link_display_full": "immer als vollständige Namen (z. B. {'@'}foo{'@'}example.org)",
@ -638,7 +675,7 @@
"panelRadius": "Panel",
"pause_on_unfocused": "Streaming pausieren, wenn das Tab nicht fokussiert ist",
"play_videos_in_modal": "Videos in größerem Medienfenster abspielen",
"post_status_content_type": "Standard-Beitragsart",
"post_status_content_type": "Standard-Format für Nachrichten",
"posts": "Beiträge",
"preload_images": "Bilder vorausladen",
"presets": "Voreinstellungen",
@ -656,7 +693,7 @@
"remove_alias": "Dieses Pseudonym entfernen",
"remove_backup": "Entfernen",
"render_mfm": "Misskey-Markdown darstellen",
"render_mfm_on_hover": "MFM-Animationen pausieren, solange sich der Mauszeiger nicht über dem Beitrag befindet",
"render_mfm_on_hover": "MFM-Animationen pausieren, solange sich der Mauszeiger nicht über der Nachricht befindet",
"replies_in_timeline": "Antworten in der Zeitleiste",
"reply_visibility_all": "Alle Antworten zeigen",
"reply_visibility_following": "Zeige nur Antworten an mich oder an Benutzer, denen ich folge",
@ -680,7 +717,7 @@
"security": "Sicherheit",
"security_tab": "Sicherheit",
"sensitive_by_default": "Alle Beiträge standardmäßig als heikel markieren",
"sensitive_if_subject": "Bilder automatisch als heikel markieren, wenn der Beitrag eine Inhaltswarnung hat",
"sensitive_if_subject": "Bilder automatisch als heikel markieren, wenn die Nachricht eine Inhaltswarnung hat",
"set_new_avatar": "Setze einen neuen Avatar",
"set_new_mascot": "Neues Maskottchen einstellen",
"set_new_profile_background": "Setze einen neuen Hintergrund für dein Profil",
@ -688,6 +725,19 @@
"setting_changed": "Einstellungen weichen von den Standardeinstellungen ab",
"setting_server_side": "Diese Einstellung hängt an deinem Profil und gilt für alle Sitzungen und Clients",
"settings": "Einstellungen",
"settings_profile": "Einstellungs-Profile",
"settings_profile_creation": "Neues Profil erstellen",
"settings_profile_creation_new_name_label": "Name",
"settings_profile_creation_submit": "Erstellen",
"settings_profile_currently": "Benutze Profil {name} (version: {version})",
"settings_profile_delete": "Löschen",
"settings_profile_delete_confirm": "Dieses Profil wirklich löschen?",
"settings_profile_force_sync": "Synchronisieren",
"settings_profile_in_use": "In Benutzung",
"settings_profile_use": "Verwenden",
"settings_profiles_refresh": "Einstellungs-Profile neu laden",
"settings_profiles_show": "Alle Einstellungs-Profile anzeigen",
"settings_profiles_unshow": "Alle Einstellungs-Profile verbergen",
"show_admin_badge": "Zeige Admin-Abzeichen auf meinem Profil",
"show_moderator_badge": "Zeige Moderator-Abzeichen auf meinem Profil",
"show_nav_shortcuts": "Zusätzliche Schnellnavigation im Top-Panel anzeigen",
@ -758,8 +808,8 @@
"components": {
"input": "Eingabefelder",
"interface": "Oberfläche",
"post": "Beitragstext",
"postCode": "Dicktengleicher Text in einem Beitrag (Rich-Text)"
"post": "Nachrichtentext",
"postCode": "nichtproportionaler Text in einer Nachricht (Rich-Text)"
},
"custom": "Benutzerdefiniert",
"family": "Schriftname",
@ -790,7 +840,7 @@
"component": "Komponente",
"components": {
"avatar": "Benutzer-Avatar (in der Profilansicht)",
"avatarStatus": "Benutzer-Avatar (in der Beitragsanzeige)",
"avatarStatus": "Benutzer-Avatar (in der Nachrichtenanzeige)",
"button": "Schaltfläche",
"buttonHover": "Schaltfläche (hover)",
"buttonPressed": "Schaltfläche (gedrückt)",
@ -861,12 +911,13 @@
"tooltipRadius": "Tooltips/Warnungen",
"translation_language": "Sprache für automatische Übersetzungen",
"tree_advanced": "Weitere Knöpfe zum Öffnen und Schließen von Antworten anzeigen",
"tree_fade_ancestors": "Vorgänger des aktuellen Beitrags schwach darstellen",
"tree_fade_ancestors": "Vorgänger der aktuellen Nachricht schwach darstellen",
"type_domains_to_mute": "Tippe die Domains ein, die du stummschalten willst",
"upload_a_photo": "Lade ein Foto hoch",
"useStreamingApi": "Empfange Posts und Benachrichtigungen in Echtzeit",
"useStreamingApiWarning": "(Nicht empfohlen, experimentell, bekannt dafür, Posts zu überspringen)",
"use_at_icon": "{'@'}-Symbol als Icon und nicht als Text anzeigen",
"use_blurhash": "Blurhash für NSFW-Vorschauen verwenden",
"use_contain_fit": "Vorschaubilder nicht zuschneiden",
"use_one_click_nsfw": "Heikle Anhänge mit nur einem Klick öffnen",
"user_mutes": "User",
@ -887,15 +938,21 @@
"word_filter": "Wortfilter",
"wordfilter": "Wortfilter"
},
"settings_profile": {
"creating": "Erstelle ein neues Einstellungs-Profil \"{profile}\"...",
"synchronization_error": "Konnte Einstellungen nicht synchronisieren: {err}",
"synchronized": "Einstellungen synchronisiert!",
"synchronizing": "Synchronisiere Einstellungs-Profil \"{profile}\"..."
},
"status": {
"ancestor_follow": "Zeige {numReplies} andere Antwort unter diesem Beitrag | Zeige {numReplies} andere Antworten unter diesem Beitrag",
"ancestor_follow": "Zeige {numReplies} andere Antwort unter dieser Nachricht | Zeige {numReplies} andere Antworten unter dieser Nachricht",
"ancestor_follow_with_icon": "{icon} {text}",
"attachment_stop_flash": "Flash-Player stoppen",
"bookmark": "Lesezeichen setzen",
"collapse_attachments": "Anhänge einklappen",
"copy_link": "Beitragslink kopieren",
"delete": "Lösche Beitrag",
"delete_confirm": "Möchtest du diese Beitrag wirklich löschen?",
"copy_link": "Link zur Nachricht kopieren",
"delete": "Lösche Nachricht",
"delete_confirm": "Möchtest du diese Nachricht wirklich löschen?",
"delete_confirm_accept_button": "Ja, löschen",
"delete_confirm_cancel_button": "Nein, behalten",
"delete_confirm_title": "Löschen bestätigen",
@ -909,7 +966,7 @@
"hide_attachment": "Anhänge verbergen",
"hide_content": "Inhalt verbergen",
"hide_full_subject": "Vollständige Inhaltswarnung verbergen",
"many_attachments": "Beitrag hat {number} Anhang | Beitrag hat {number} Anhänge",
"many_attachments": "Nachricht hat {number} Anhang | Nachricht hat {number} Anhänge",
"mentions": "Erwähnungen",
"move_down": "Anhang nach rechts verschieben",
"move_up": "Anhang nach links verschieben",
@ -920,8 +977,13 @@
"pin": "An Profil anheften",
"pinned": "Angeheftet",
"plus_more": "+{number} mehr",
"redraft": "Löschen & neu erstellen",
"redraft_confirm": "Diesen Post wirklich löschen und neu erstellen? Interaktionen des ursprünglichen Posts werden nicht erhalten.",
"redraft_confirm_accept_button": "Ja, löschen und neu erstellen",
"redraft_confirm_cancel_button": "Nein, das Original behalten",
"redraft_confirm_title": "Löschen und neu erstellen bestätigen",
"remove_attachment": "Anhang entfernen",
"repeat_confirm": "Beitrag wirklich wiederholen?",
"repeat_confirm": "Nachricht wirklich wiederholen?",
"repeat_confirm_accept_button": "Ja, wiederholen",
"repeat_confirm_cancel_button": "Nein, nicht wiederholen",
"repeat_confirm_title": "Wiederholen bestätigen",
@ -930,15 +992,15 @@
"replies_list_with_others": "Zeige noch {numReplies} Antwort | Zeige noch {numReplies} Antworten",
"reply_to": "Antworten auf",
"show_all_attachments": "Alle Anhänge anzeigen",
"show_all_conversation": "Ganzes Gespräch anzeigen (noch {numStatus} Beitrag) | Ganzes Gespräch anzeigen (noch {numStatus} Beiträge)",
"show_all_conversation": "Ganzes Gespräch anzeigen (noch {numStatus} Nachricht) | Ganzes Gespräch anzeigen (noch {numStatus} Nachrichten)",
"show_all_conversation_with_icon": "{icon} {text}",
"show_attachment_description": "Vorschau-Beschreibung (Anhang öffnen für vollständige Beschreibung)",
"show_attachment_in_modal": "Anhang in einem Fenster anzeigen",
"show_content": "Inhalt anzeigen",
"show_full_subject": "Vollständige Inhaltswarnung anzeigen",
"show_only_conversation_under_this": "Nur Antworten auf diesen Bericht anzeigen",
"status_deleted": "Dieser Beitrag wurde gelöscht",
"status_unavailable": "Beitrag nicht verfügbar",
"status_deleted": "Diese Nachricht wurde gelöscht",
"status_unavailable": "Nachricht nicht verfügbar",
"thread_follow": "Zeige noch {numStatus} Antwort | Zeige noch {numStatus} Antworten",
"thread_follow_with_icon": "{icon} {text}",
"thread_hide": "Diese Unterhaltung stummschalten",
@ -948,6 +1010,7 @@
"thread_show_full": "Zeige {numStatus} Antwort | Zeige {numStatus} Antworten",
"thread_show_full_with_icon": "{icon} {text}",
"translate": "Übersetzen",
"translated_from": "Übersetzt von {language}",
"unbookmark": "Lesezeichen entfernen",
"unmute_conversation": "Konversation nicht mehr stummstellen",
"unpin": "Nicht mehr an Profil anheften",
@ -979,17 +1042,22 @@
"collapse": "Einklappen",
"conversation": "Unterhaltung",
"error": "Fehler beim Lesen der Timeline: {0}",
"load_older": "Lade ältere Beiträge",
"no_more_statuses": "Keine weiteren Beiträge",
"no_retweet_hint": "Der Beitrag ist als nur-für-Follower oder Direktnachricht markiert und kann nicht wiederholt oder zitiert werden",
"no_statuses": "Keine Beiträge",
"follow_tag": "Hashtag folgen",
"load_older": "Lade ältere Nachrichten",
"no_more_statuses": "Keine weiteren Nachrichten",
"no_retweet_hint": "Die Nachricht ist als nur-für-Follower oder Direktnachricht markiert und kann nicht wiederholt oder zitiert werden",
"no_statuses": "Keine Nachrichten",
"reload": "Neu laden",
"repeated": "wiederholte",
"show_new": "Zeige Neuere",
"socket_broke": "Netzverbindung verloren: CloseEvent code {0}",
"socket_reconnected": "Netzverbindung hergestellt",
"unfollow_tag": "Hashtag entfolgen",
"up_to_date": "Aktuell"
},
"toast": {
"no_translation_target_set": "Keine Zielsprache für Übersetzungen eingestellt - das könnte schiefgehen. Bitte stell eine Zielsprache in deinen Einstellungen ein."
},
"tool_tip": {
"accept_follow_request": "Folgeanfrage annehmen",
"add_reaction": "Emoji-Reaktion hinzufügen",
@ -1049,6 +1117,7 @@
"block_confirm_title": "Benutzer blockieren",
"block_progress": "Blocken…",
"blocked": "Blockiert!",
"blocks_you": "Blockt dich!",
"bot": "Bot",
"deactivated": "Deaktiviert",
"deny": "Ablehnen",
@ -1063,7 +1132,10 @@
"follow_cancel": "Anfrage ablehnen",
"follow_progress": "Anfragen…",
"follow_sent": "Anfrage gesendet!",
"follow_tag": "Hashtag folgen",
"follow_unfollow": "Folgen beenden",
"followed_tags": "Gefolgte Hashtags",
"followed_users": "Gefolgte Benutzer",
"followees": "Folgt",
"followers": "Folgende",
"following": "Folgst du!",
@ -1088,13 +1160,16 @@
"mute_domain": "Domain blockieren",
"mute_progress": "Stummschalten erfolgt…",
"muted": "Stummgeschaltet",
"not_following_any_hashtags": "Du folgst keinen Hashtags",
"note": "Private Notiz",
"per_day": "pro Tag",
"remote_follow": "Folgen",
"remove_follower": "Nicht mehr folgen",
"replies": "Mit Antworten",
"report": "Melden",
"requested_by": "Möchte dir gern folgen",
"show_repeats": "Geteilte Beiträge anzeigen",
"statuses": "Beiträge",
"statuses": "Nachrichten",
"subscribe": "Folgen",
"unblock": "Entblocken",
"unblock_progress": "Entblocken…",
@ -1102,11 +1177,13 @@
"unfollow_confirm_accept_button": "Ja, nicht mehr folgen",
"unfollow_confirm_cancel_button": "Nein, weiter folgen",
"unfollow_confirm_title": "Benutzer nicht mehr folgen",
"unfollow_tag": "Hashtag nicht mehr folgen",
"unmute": "Stummschalten aufheben",
"unmute_progress": "Aufhebung erfolgt…",
"unsubscribe": "Entfolgen"
},
"user_profile": {
"field_validated": "Link verifiziert",
"profile_does_not_exist": "Profil nicht vorhanden.",
"profile_loading_error": "Beim Laden dieses Profils ist ein Fehler aufgetreten.",
"timeline_title": "Beiträge"

View file

@ -86,7 +86,8 @@
"load_all_hint": "Loaded first {saneAmount} emojo, loading all emojo may cause performance issues.",
"search_emoji": "Search for an emojo",
"stickers": "Stickers",
"unicode": "Unicode emojo"
"unicode": "Unicode emojo",
"recent": "Recently used"
},
"errors": {
"storage_unavailable": "Akkoma could not access browser storage. Your login or your local settings won't be saved and you might encounter unexpected issues. Try enabling cookies."
@ -165,72 +166,72 @@
"moves": "User migrates"
},
"languages": {
"bg": "Bulgarian",
"en": "English",
"ar": "Arabic",
"az": "Azerbaijani",
"zh": "Chinese",
"bg": "Bulgarian",
"cs": "Czech",
"da": "Danish",
"nl": "Dutch",
"eo": "Esperanto",
"fi": "Finnish",
"fr": "French",
"de": "German",
"el": "Greek",
"en": "English",
"eo": "Esperanto",
"es": "Spanish",
"fa": "Persian",
"fi": "Finnish",
"fr": "French",
"ga": "Irish",
"he": "Hebrew",
"hi": "Hindi",
"hu": "Hungarian",
"id": "Indonesian",
"ga": "Irish",
"it": "Italian",
"ja": "Japanese",
"ko": "Korean",
"fa": "Persian",
"lt": "Lithuanian",
"lv": "Latvian",
"nl": "Dutch",
"pl": "Polish",
"pt": "Portuguese",
"ru": "Russian",
"sk": "Slovak",
"es": "Spanish",
"sv": "Swedish",
"tr": "Turkish",
"uk": "Ukrainian",
"lt": "Lithuanian",
"lv": "Latvian",
"translated_from": {
"bg": "Translated from @:languages.bg",
"en": "Translated from @:languages.en",
"ar": "Translated from @:languages.ar",
"az": "Translated from @:languages.az",
"zh": "Translated from @:languages.zh",
"bg": "Translated from @:languages.bg",
"cs": "Translated from @:languages.cs",
"da": "Translated from @:languages.da",
"nl": "Translated from @:languages.nl",
"eo": "Translated from @:languages.eo",
"fi": "Translated from @:languages.fi",
"fr": "Translated from @:languages.fr",
"de": "Translated from @:languages.de",
"el": "Translated from @:languages.el",
"en": "Translated from @:languages.en",
"eo": "Translated from @:languages.eo",
"es": "Translated from @:languages.es",
"fa": "Translated from @:languages.fa",
"fi": "Translated from @:languages.fi",
"fr": "Translated from @:languages.fr",
"ga": "Translated from @:languages.ga",
"he": "Translated from @:languages.he",
"hi": "Translated from @:languages.hi",
"hu": "Translated from @:languages.hu",
"id": "Translated from @:languages.id",
"ga": "Translated from @:languages.ga",
"it": "Translated from @:languages.it",
"ja": "Translated from @:languages.ja",
"ko": "Translated from @:languages.ko",
"fa": "Translated from @:languages.fa",
"lt": "Translated from @:languages.lt",
"lv": "Translated from @:languages.lv",
"nl": "Translated from @:languages.nl",
"pl": "Translated from @:languages.pl",
"pt": "Translated from @:languages.pt",
"ru": "Translated from @:languages.ru",
"sk": "Translated from @:languages.sk",
"es": "Translated from @:languages.es",
"sv": "Translated from @:languages.sv",
"tr": "Translated from @:languages.tr",
"uk": "Translated from @:languages.uk",
"lt": "Translated from @:languages.lt",
"lv": "Translated from @:languages.lv"
}
"zh": "Translated from @:languages.zh"
},
"uk": "Ukrainian",
"zh": "Chinese"
},
"lists": {
"create": "Create",
@ -254,15 +255,15 @@
"hint": "Log in to join the discussion",
"login": "Log in",
"logout": "Log out",
"logout_confirm": "Are you sure you want to log out?",
"logout_confirm_accept_button": "Log out",
"logout_confirm_cancel_button": "Cancel",
"logout_confirm_title": "Log out",
"password": "Password",
"placeholder": "Your email or username",
"recovery_code": "Recovery code",
"register": "Want to register?",
"username": "Username",
"logout_confirm_cancel_button": "Cancel",
"logout_confirm_accept_button": "Log out",
"logout_confirm": "Are you sure you want to log out?",
"logout_confirm_title": "Log out"
"username": "Username"
},
"media_modal": {
"counter": "{current} / {total}",
@ -271,30 +272,30 @@
"previous": "Previous"
},
"moderation": {
"moderation": "Moderation",
"reports": {
"no_reports": "No reports to show",
"add_note": "Add note",
"close": "Close",
"delete_note": "Delete",
"delete_note_accept": "Yes, delete it",
"delete_note_cancel": "No, keep it",
"delete_note_confirm": "Are you sure you want to delete this note?",
"delete_note_title": "Confirm deletion",
"no_content": "No description given",
"note_placeholder": "Leave a note...",
"notes": "{ count } note | { count } notes",
"reopen": "Reopen",
"report": "Report on",
"reports": "Reports",
"resolve": "Resolve",
"show_closed": "Show closed",
"statuses": "{ count } status | { count } statuses",
"tag_policy_notice": "Enable the TagPolicy MRF to set post restrictions",
"tags": "Set post restrictions"
},
"statuses": "Statuses",
"users": "Users"
"moderation": "Moderation",
"reports": {
"add_note": "Add note",
"close": "Close",
"delete_note": "Delete",
"delete_note_accept": "Yes, delete it",
"delete_note_cancel": "No, keep it",
"delete_note_confirm": "Are you sure you want to delete this note?",
"delete_note_title": "Confirm deletion",
"no_content": "No description given",
"no_reports": "No reports to show",
"note_placeholder": "Leave a note",
"notes": "{ count } note | { count } notes",
"reopen": "Reopen",
"report": "Report on",
"reports": "Reports",
"resolve": "Resolve",
"show_closed": "Show closed",
"statuses": "{ count } post| { count } posts",
"tag_policy_notice": "Enable the TagPolicy MRF to set post restrictions",
"tags": "Set post restrictions"
},
"statuses": "Posts",
"users": "Users"
},
"nav": {
"about": "What is this place?",
@ -409,6 +410,8 @@
}
},
"registration": {
"awaiting_email_confirmation": "Your account has been registered and an email has been sent to your address. Please check the email to complete registration.",
"awaiting_email_confirmation_title": "Awaiting email confirmation",
"bio": "Bio",
"bio_placeholder": "e.g.\nAYO! Welcome to MTV Cribs\nAND WELCOME to my crib!",
"captcha": "CAPTCHA",
@ -422,6 +425,8 @@
"reason_placeholder": "You will first need to pass the vibe check!\nLet the nerds running this place why you want to join!",
"register": "Register",
"registration": "Registration",
"request_sent": "Your registration request has been sent for approval. You will receive an email when your account is approved.",
"request_sent_title": "Registration request sent",
"token": "Invite token",
"username_placeholder": "e.g. xXx_st1nky_BuTT_69_xXx",
"validations": {
@ -531,6 +536,8 @@
"enable_web_push_notifications": "Enable web push notifications",
"enter_current_password_to_confirm": "Enter your current password to confirm your identity",
"expert_mode": "Show advanced",
"expire_posts_enabled": "Delete posts after a set amount of days",
"expire_posts_input_placeholder": "Number of days",
"export_theme": "Save preset",
"file_export_import": {
"backup_restore": "Settings backup",
@ -725,18 +732,18 @@
"setting_server_side": "This setting is tied to your profile and affects all sessions and clients",
"settings": "Settings",
"settings_profile": "Settings Profiles",
"settings_profile_currently": "Currently using {name} (version: {version})",
"settings_profiles_show": "Show all settings profiles",
"settings_profiles_unshow": "Hide all settings profiles",
"settings_profile_in_use": "In use",
"settings_profile_creation": "Create new profile",
"settings_profile_creation_submit": "Create",
"settings_profile_creation_new_name_label": "Name",
"settings_profile_use": "Use",
"settings_profile_creation_submit": "Create",
"settings_profile_currently": "Currently using {name} (version: {version})",
"settings_profile_delete": "Delete",
"settings_profile_delete_confirm": "Do you really want to delete this profile?",
"settings_profile_force_sync": "Synchronize",
"settings_profile_in_use": "In use",
"settings_profile_use": "Use",
"settings_profiles_refresh": "Reload settings profiles",
"settings_profiles_show": "Show all settings profiles",
"settings_profiles_unshow": "Hide all settings profiles",
"show_admin_badge": "Show \"Admin\" badge in my profile",
"show_moderator_badge": "Show \"Moderator\" badge in my profile",
"show_nav_shortcuts": "Show extra navigation shortcuts in top panel",
@ -933,14 +940,15 @@
"title": "Version"
},
"virtual_scrolling": "Optimize timeline rendering",
"use_blurhash": "Use blurhashes for NSFW thumbnails",
"word_filter": "Word filter",
"wordfilter": "Wordfilter"
},
"settings_profile": {
"synchronizing": "Synchronizing setting profile \"{profile}\"...",
"synchronized": "Synchronized settings!",
"synchronization_error": "Could not synchronize settings: {err}",
"creating": "Creating new setting profile \"{profile}\"..."
"creating": "Creating new setting profile \"{profile}\"...",
"synchronization_error": "Could not synchronize settings: {err}",
"synchronized": "Synchronized settings!",
"synchronizing": "Synchronizing setting profile \"{profile}\"..."
},
"status": {
"ancestor_follow": "See {numReplies} other reply under this post | See {numReplies} other replies under this post",
@ -1040,6 +1048,7 @@
"collapse": "Collapse",
"conversation": "Conversation",
"error": "Error fetching timeline: {0}",
"follow_tag": "Follow hashtag",
"load_older": "Load older posts",
"no_more_statuses": "No more posts",
"no_retweet_hint": "Post is marked as followers-only or direct and cannot be retooted or quoted",
@ -1049,6 +1058,8 @@
"show_new": "Show new",
"socket_broke": "Realtime connection lost: CloseEvent code {0}",
"socket_reconnected": "Realtime connection established",
"follow_tag": "Follow hashtag",
"unfollow_tag": "Unfollow hashtag",
"up_to_date": "Up-to-date"
},
"toast": {
@ -1113,6 +1124,7 @@
"block_confirm_title": "Block user",
"block_progress": "Blocking…",
"blocked": "Blocked!",
"blocks_you": "Blocks you!",
"bot": "Bot",
"deactivated": "Deactivated",
"deny": "Deny",
@ -1130,9 +1142,10 @@
"follow_unfollow": "Unfollow this jerk",
"followees": "Following",
"followers": "Followers",
"followed_tags": "Followed hashtags",
"followed_users": "Followed users",
"following": "Following!",
"follows_you": "Follows you!",
"requested_by": "Has requested to follow you",
"hidden": "Hidden",
"hide_repeats": "Hide retoots",
"highlight": {
@ -1159,6 +1172,7 @@
"remove_follower": "Remove this nerd",
"replies": "With Replies",
"report": "Report this jerk",
"requested_by": "Has requested to follow you",
"show_repeats": "Show retoots",
"statuses": "Posts",
"subscribe": "Subscribe",
@ -1168,6 +1182,9 @@
"unfollow_confirm_accept_button": "Yes, unfollow",
"unfollow_confirm_cancel_button": "No, don't unfollow",
"unfollow_confirm_title": "Unfollow user",
"not_following_any_hashtags": "You are not following any hashtags",
"follow_tag": "Follow hashtag",
"unfollow_tag": "Unfollow hashtag",
"unmute": "Unmute",
"unmute_progress": "Unmuting…",
"unsubscribe": "Unsubscribe"
@ -1175,7 +1192,8 @@
"user_profile": {
"profile_does_not_exist": "Uhhh... The hell? This goes nowhere!",
"profile_loading_error": "This profile... Doesn't load? Alright???",
"timeline_title": "User timeline"
"timeline_title": "User timeline",
"field_validated": "Link-o Verified-o"
},
"user_reporting": {
"add_comment_description": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",

View file

@ -254,6 +254,10 @@
"hint": "Inicia sesión para unirte a la discusión",
"login": "Identificarse",
"logout": "Cerrar sesión",
"logout_confirm": "¿Estás seguro que quieres cerrar la sesión?",
"logout_confirm_accept_button": "Cerrar sesión",
"logout_confirm_cancel_button": "Cancelar",
"logout_confirm_title": "Cerrar sesión",
"password": "Contraseña",
"placeholder": "miusuario",
"recovery_code": "Código de recuperación",
@ -277,18 +281,19 @@
"delete_note_confirm": "¿Estás seguro que quieres eliminar esta nota?",
"delete_note_title": "Confirma la eliminación",
"no_content": "Sin descripción dada",
"note_placeholder": "Dejar una nota...",
"notes": "notas",
"no_reports": "No hay informes que mostrar",
"note_placeholder": "Dejar una nota",
"notes": "{ count } nota | { count } notas",
"reopen": "Reabrir",
"report": "Reportar",
"reports": "Reportes",
"resolve": "Resolver",
"show_closed": "Mostrar cerrados",
"statuses": "estados",
"statuses": "{ count } publicación | { count } publicaciones",
"tag_policy_notice": "Habilitar TagPolicy MRF para establecer restricciones de publicación",
"tags": "Establecer restricciones de publicación"
},
"statuses": "Estados",
"statuses": "Publicaciones",
"users": "Usuarios"
},
"nav": {
@ -404,6 +409,8 @@
}
},
"registration": {
"awaiting_email_confirmation": "Su cuenta ha sido registrada y se ha enviado un correo electrónico a su dirección. Por favor revise el correo electrónico para completar el registro.",
"awaiting_email_confirmation_title": "En espera de confirmación por correo electrónico",
"bio": "Biografía",
"bio_placeholder": "p. ej.\nHola, soy un ejemplo.\nAquí puedes poner algo representativo tuyo... o no.",
"captcha": "CAPTCHA",
@ -417,6 +424,8 @@
"reason_placeholder": "Los registros de esta instancia son aprobados manualmente.\nComéntanos por qué quieres registrarte aquí.",
"register": "Registrarse",
"registration": "Registro",
"request_sent": "Su solicitud de registro ha sido enviada para su aprobación. Recibirá un correo electrónico cuando se apruebe su cuenta.",
"request_sent_title": "Solicitud de registro enviada",
"token": "Token de invitación",
"username_placeholder": "p. ej. akko",
"validations": {
@ -445,6 +454,7 @@
},
"settings": {
"accent": "Acento",
"account_alias": "Alias de cuenta",
"account_alias_table_head": "Alias",
"account_backup": "Copia de seguridad de la cuenta",
"account_backup_description": "Esto le permite descargar un archivo con la información de su cuenta y sus publicaciones, pero aún no se pueden importar a una cuenta de Pleroma.",
@ -492,7 +502,16 @@
"columns": "Columnas",
"composing": "Redactando",
"confirm_dialogs": "Requiere confirmación de:",
"confirm_dialogs_approve_follow": "Aceptar una solicitud de seguimiento",
"confirm_dialogs_block": "Bloqueando a alguien",
"confirm_dialogs_delete": "Eliminar una publicación",
"confirm_dialogs_deny_follow": "Rechazar una solicitud de seguimiento",
"confirm_dialogs_mute": "Silenciar a alguien",
"confirm_dialogs_repeat": "Repitiendo una publicación",
"confirm_dialogs_unfollow": "Dejar de seguir a alguien",
"confirm_new_password": "Confirmar la nueva contraseña",
"confirmation_dialogs": "Opciones de confirmación",
"conversation_display": "Estilo de visualización de la conversación",
"conversation_display_linear": "Lineal",
"conversation_display_tree": "Ramificado",
"conversation_other_replies_button": "Mostrar el botón \"otras respuestas\"",
@ -516,6 +535,8 @@
"enable_web_push_notifications": "Habilitar las notificiaciones en el navegador",
"enter_current_password_to_confirm": "Introduce la contraseña actual para confirmar tu identidad",
"expert_mode": "Mostrar avanzados",
"expire_posts_enabled": "Eliminar publicaciones después de una cantidad determinada de días",
"expire_posts_input_placeholder": "Número de días",
"export_theme": "Exportar tema",
"file_export_import": {
"backup_restore": "Copia de seguridad de la configuración",
@ -543,6 +564,7 @@
"hide_all_muted_posts": "Ocultar las publicaciones silenciadas",
"hide_attachments_in_convo": "Ocultar adjuntos en las conversaciones",
"hide_attachments_in_tl": "Ocultar adjuntos en la línea temporal",
"hide_bot_indication": "Ocultar indicación de bot en publicaciones",
"hide_favorites_description": "No mostrar la lista de mis favoritos (las personas aún serán notificadas)",
"hide_filtered_statuses": "Ocultar estados filtrados",
"hide_followers_count_description": "No mostrar el número de cuentas que me siguen",
@ -587,8 +609,10 @@
"mention_link_display_full": "siempre como nombre completo (p. ej. {'@'}foo{'@'}example.org)",
"mention_link_display_full_for_remote": "como nombre completo solo para usuarios remotos (p. ej. {'@'}foo{'@'}example.org)",
"mention_link_display_short": "siempre como nombres cortos (p. ej. {'@'}foo)",
"mention_link_fade_domain": "Renombrar dominios (p. ej. {'@'}ejemplo.org en {'@'}algo{'@'}ejemplo.org)",
"mention_link_show_avatar": "Mostrar el avatar del usuario detrás del enlace",
"mention_link_show_tooltip": "Mostrar el nombre completo de los usuarios remotos como información emergente",
"mention_links": "Mencionar enlaces",
"mfa": {
"authentication_methods": "Métodos de autentificación",
"confirm_and_enable": "Confirmar y habilitar OTP",
@ -637,6 +661,7 @@
"notification_mutes": "Para dejar de recibir notificaciones de un usuario específico, siléncialo.",
"notification_setting_block_from_strangers": "Bloquea las notificaciones de los usuarios que no sigues",
"notification_setting_filters": "Filtros",
"notification_setting_hide_if_cw": "Ocultar las notificaciones \"push\", si la publicación está marcada como \"contenido sensible\"",
"notification_setting_hide_notification_contents": "Ocultar el remitente y el contenido de las notificaciones push",
"notification_setting_privacy": "Privacidad",
"notification_visibility": "Tipos de notificaciones a mostrar",
@ -645,6 +670,7 @@
"notification_visibility_likes": "Favoritos",
"notification_visibility_mentions": "Menciones",
"notification_visibility_moves": "Usuario Migrado",
"notification_visibility_polls": "Encuestas finalizadas en las que has participado",
"notification_visibility_repeats": "Repeticiones (Repeats)",
"notifications": "Notificaciones",
"nsfw_clickthrough": "Habilitar la ocultación de la imagen de vista previa del enlace y el adjunto para los estados NSFW por defecto",
@ -653,7 +679,9 @@
"panelRadius": "Paneles",
"pause_on_unfocused": "Parar la transmisión cuando no estés en foco",
"play_videos_in_modal": "Reproducir los vídeos en un marco emergente",
"post_status_content_type": "Formato de publicación",
"post_look_feel": "Aspecto de las publicaciones",
"post_status_content_type": "Formato predeterminado de publicación",
"posts": "Publicaciones",
"preload_images": "Precargar las imágenes",
"presets": "Por defecto",
"profile_background": "Imagen de fondo del perfil",
@ -667,6 +695,10 @@
"profile_tab": "Perfil",
"radii_help": "Establezca el redondeo de las esquinas de la interfaz (en píxeles)",
"refresh_token": "Actualizar el token",
"remove_alias": "Eliminar este alias",
"remove_backup": "Eliminar",
"render_mfm": "Renderizar Markdown de Misskey",
"render_mfm_on_hover": "Pausa las animaciones MFM hasta que se desplace el mensaje",
"replies_in_timeline": "Réplicas en la línea temporal",
"reply_visibility_all": "Mostrar todas las réplicas",
"reply_visibility_following": "Solo mostrar réplicas para mí o usuarios a los que sigo",
@ -690,13 +722,34 @@
"security": "Seguridad",
"security_tab": "Seguridad",
"sensitive_by_default": "Identificar las publicaciones como sensibles de forma predeterminada",
"sensitive_if_subject": "Marcar automáticamente las imágenes como confidenciales si se especifica una advertencia de contenido",
"set_new_avatar": "Cambiar avatar",
"set_new_mascot": "Fijar nueva mascota",
"set_new_profile_background": "Cambiar el fondo del perfil",
"set_new_profile_banner": "Cambiar la cabecera del perfil",
"setting_changed": "La configuración es diferente a la predeterminada",
"setting_server_side": "Esta configuración está vinculada a su perfil y afecta a todas las sesiones y clientes",
"settings": "Ajustes",
"settings_profile": "Ajustes de Perfiles",
"settings_profile_creation": "Crear nuevo perfil",
"settings_profile_creation_new_name_label": "Nombre",
"settings_profile_creation_submit": "Crear",
"settings_profile_currently": "Actualmente usando {nombre} (versión: {version})",
"settings_profile_delete": "Eliminar",
"settings_profile_delete_confirm": "¿Realmente quieres eliminar este perfil?",
"settings_profile_force_sync": "Sincronizar",
"settings_profile_in_use": "En uso",
"settings_profile_use": "Usar",
"settings_profiles_refresh": "Recargar perfiles de configuración",
"settings_profiles_show": "Mostrar todos los perfiles de configuración",
"settings_profiles_unshow": "Ocultar todos los perfiles de configuración",
"show_admin_badge": "Mostrar la insignia de \"Administrador/a\" en mi perfil",
"show_moderator_badge": "Mostrar la insignia de \"Moderador/a\" en mi perfil",
"show_nav_shortcuts": "Mostrar accesos directos de navegación adicionales en el panel superior",
"show_panel_nav_shortcuts": "Mostrar accesos directos de navegación de la línea de tiempo en la parte superior del panel",
"show_scrollbars": "Mostrar las barras de desplazamiento de la columna lateral",
"show_wider_shortcuts": "Mostrar una brecha más amplia entre los accesos directos del panel superior",
"show_yous": "Mostrar (Tú)s",
"stop_gifs": "Iniciar GIFs al pasar el ratón",
"streaming": "Habilitar la transmisión automática de nuevas publicaciones cuando se desplaza hacia la parte superior",
"style": {
@ -845,9 +898,9 @@
"use_source": "Nueva versión"
}
},
"subject_input_always_show": "Mostrar siempre el campo del tema",
"subject_line_behavior": "Copiar el tema en las respuestas",
"subject_line_email": "Como email: \"re: tema\"",
"subject_input_always_show": "Mostrar siempre el campo de advertencia de contenido",
"subject_line_behavior": "Copiar el campo de advertencia en las respuestas",
"subject_line_email": "Como email: \"re: advertencia\"",
"subject_line_mastodon": "Como mastodon: copiar como es",
"subject_line_noop": "No copiar",
"text": "Texto",
@ -855,8 +908,13 @@
"theme_help": "Use códigos de color hexadecimales (#rrggbb) para personalizar su tema de colores.",
"theme_help_v2_1": "También puede invalidar los colores y la opacidad de ciertos componentes si activa la casilla de verificación. Use el botón \"Borrar todo\" para deshacer los cambios.",
"theme_help_v2_2": "Los iconos debajo de algunas publicaciones son indicadores de contraste de fondo/texto, desplace el ratón por encima para obtener información más detallada. Tenga en cuenta que cuando se utilizan indicadores de contraste de transparencia se muestra el peor caso posible.",
"third_column_mode": "Cuando haya suficiente espacio, muestre la tercera columna",
"third_column_mode_none": "No mostrar la tercera columna en absoluto",
"third_column_mode_notifications": "Columna de notificaciones",
"third_column_mode_postform": "Formulario principal de publicación y navegación",
"token": "Token",
"tooltipRadius": "Información/alertas",
"translation_language": "Idioma de traducción automática",
"type_domains_to_mute": "Buscar dominios para silenciar",
"upload_a_photo": "Subir una foto",
"useStreamingApi": "Recibir publicaciones y notificaciones en tiempo real",

View file

@ -84,6 +84,7 @@
"keep_open": "Garder ouvert",
"load_all": "Charger tout les {emojiAmount} émojis",
"load_all_hint": "{saneAmount} émojis chargé, charger tout les émojis peuvent causer des problèmes de performances.",
"recent": "Utilisé récemment",
"search_emoji": "Rechercher un émoji",
"stickers": "Stickers",
"unicode": "émoji unicode"
@ -254,6 +255,10 @@
"hint": "Connectez-vous pour rejoindre la discussion",
"login": "Connexion",
"logout": "Déconnexion",
"logout_confirm": "Voulez-vous vraiment vous déconnecter ?",
"logout_confirm_accept_button": "Déconnexion",
"logout_confirm_cancel_button": "Annuler",
"logout_confirm_title": "Déconnexion",
"password": "Mot de passe",
"placeholder": "mon nom d'utilisateur",
"recovery_code": "Code de récupération",
@ -266,6 +271,32 @@
"next": "Suivant",
"previous": "Précédent"
},
"moderation": {
"moderation": "Modération",
"reports": {
"add_note": "Ajouter une note",
"close": "Fermer",
"delete_note": "Supprimer",
"delete_note_accept": "Oui, supprimer",
"delete_note_cancel": "Non, abandonner",
"delete_note_confirm": "Voulez-vous vraiment supprimer cette note ?",
"delete_note_title": "Confirmer la suppression",
"no_content": "Description vide",
"no_reports": "Aucun rapport",
"note_placeholder": "Laisser une note",
"notes": "{ count } note | { count } notes",
"reopen": "Rouvrir",
"report": "Signaler",
"reports": "Rapports",
"resolve": "Résoudre",
"show_closed": "Afficher les rapports classés",
"statuses": "{ count } statut| { count } statuts",
"tag_policy_notice": "Activer la politique MRF pour établir les restrictions de publication",
"tags": "Établir les restrictions de publication"
},
"statuses": "Statuts",
"users": "Utilisateurs"
},
"nav": {
"about": "À propos",
"administration": "Administration",
@ -282,6 +313,7 @@
"interactions": "Interactions",
"lists": "Listes",
"mentions": "Mentions",
"moderation": "Moderation",
"preferences": "Préférences",
"public_timeline_description": "Tous les statuts publics de cette instance",
"public_tl": "Flux publique",
@ -378,6 +410,8 @@
}
},
"registration": {
"awaiting_email_confirmation": "Votre compte a été enregistré et un courriel envoyé à votre adresse. Veuillez consulter votre boîte mail pour terminer la registration.",
"awaiting_email_confirmation_title": "En attente de confirmation par courriel",
"bio": "Biographie",
"bio_placeholder": "ex :\nSalut, je me présente ici !\nJadore les animés et les jeux vidéos. Jespère qu'on peut être amis⋅ies !",
"captcha": "CAPTCHA",

View file

@ -3,35 +3,66 @@
"mrf": {
"federation": "Federasi",
"keyword": {
"ftl_removal": "Penghapusan dari Linimasa \"Jaringan Yang Dikenal\"",
"is_replaced_by": "→",
"reject": "Tolak"
},
"mrf_policies": "Kebijakan MRF yang diaktifkan",
"mrf_policies_desc": "Kebijakan MRF memanipulasi federasi yang terjadi pada instansi ini. Kebijakan berikut adalah yang aktif:",
"simple": {
"accept": "Terima",
"accept_desc": "Instansi ini hanya menerima pesan dari instansi-instansi berikut:",
"ftl_removal": "Penghapusan dari Linimasa \"Jaringan Yang Dikenal\"",
"ftl_removal_desc": "Instansi ini menghapus instansi berikut dari linimasa \"Jaringan Yang Dikenal\":",
"instance": "Instansi",
"media_nsfw": "Media diatur sebagai sensitif secara paksa",
"media_nsfw_desc": "Instansi ini secara paksa menandai media pada postingan dari instansi berikut sebagai sensitif:",
"media_removal": "Penghapusan Media",
"media_removal_desc": "Instansi ini menghapus media dari postingan yang berasal dari instansi-instansi berikut:",
"not_applicable": "Tidak berlaku",
"quarantine": "Karantina",
"quarantine_desc": "Instansi ini hanya akan mengirim postingan publik ke instansi-instansi berikut:",
"quarantine_desc": "Instansi ini tidak akan mengirim postingan publik ke instansi berikut:",
"reason": "Alasan",
"reject": "Tolak",
"reject_desc": "Instansi ini tidak akan menerima pesan dari instansi-instansi berikut:"
"reject_desc": "Instansi ini tidak akan menerima pesan dari instansi-instansi berikut:",
"simple_policies": "Kebijakan khusus instansi"
}
},
"staff": "Staf"
},
"announcements": {
"all_day_prompt": "Ini adalah acara sepanjang hari",
"cancel_edit_action": "Batal",
"close_error": "Tutup",
"delete_action": "Hapus",
"edit_action": "Sunting",
"end_time_display": "Berakhir pada {time}",
"end_time_prompt": "Waktu berakhir: ",
"inactive_message": "Pengumuman ini nonaktif",
"mark_as_read_action": "Tandai sebagai dibaca",
"page_header": "Pengumuman",
"post_action": "Posting",
"post_error": "Kesalahan: {error}",
"post_form_header": "Posting pengumuman",
"post_placeholder": "Isi pengumuman",
"published_time_display": "Diterbitkan pada {time}",
"start_time_display": "Dimulai pada {time}",
"start_time_prompt": "Waktu mulai: ",
"submit_edit_action": "Kirim",
"title": "Pengumuman"
},
"chats": {
"chats": "Obrolan",
"delete": "Hapus",
"delete_confirm": "Apakah Anda benar-benar ingin menghapus pesan ini?",
"empty_chat_list_placeholder": "Anda belum memiliki obrolan. Buat sbeuah obrolan baru!",
"delete_confirm": "Apakah kamu benar-benar ingin menghapus pesan ini?",
"empty_chat_list_placeholder": "Kamu belum memiliki obrolan. Mulai obrolan baru!",
"empty_message_error": "Tidak dapat memposting pesan yang kosong",
"error_loading_chat": "Sesuatu yang salah terjadi ketika memuat obrolan.",
"error_sending_message": "Sesuatu yang salah terjadi ketika mengirim pesan.",
"message_user": "Kirim Pesan ke {nickname}",
"more": "Lebih banyak",
"new": "Obrolan Baru",
"you": "Anda:"
"you": "Kamu:"
},
"display_date": {
"today": "Hari Ini"
@ -40,7 +71,7 @@
"mute": "Bisukan",
"mute_progress": "Membisukan…",
"unmute": "Berhenti membisukan",
"unmute_progress": "Memberhentikan pembisuan…"
"unmute_progress": "Menghentikan pembisuan…"
},
"emoji": {
"add_emoji": "Sisipkan emoji",
@ -51,16 +82,17 @@
"load_all_hint": "Memuat {saneAmount} emoji pertama, memuat semua emoji dapat menyebabkan masalah performa.",
"search_emoji": "Cari emoji",
"stickers": "Stiker",
"unicode": "Emoji unicode"
"unicode": "Emoji Unicode"
},
"errors": {
"storage_unavailable": "Pleroma tidak dapat mengakses penyimpanan browser. Login Anda atau pengaturan lokal Anda tidak akan tersimpan dan masalah yang tidak terduga dapat terjadi. Coba mengaktifkan kuki."
"storage_unavailable": "Pleroma tidak dapat mengakses penyimpanan browser. Login kamu atau pengaturan lokal kamu tidak akan tersimpan dan masalah yang tidak terduga dapat terjadi. Coba aktifkan kuki."
},
"exporter": {
"export": "Ekspor",
"processing": "Memproses, Anda akan segera diminta untuk mengunduh berkas Anda"
"processing": "Memproses, kamu akan segera diminta untuk mengunduh berkas kamu"
},
"features_panel": {
"media_proxy": "Proxy media",
"text_limit": "Batas teks",
"title": "Fitur-fitur",
"upload_limit": "Batas unggahan"
@ -96,6 +128,12 @@
"admin": "Admin",
"moderator": "Moderator"
},
"scope_in_timeline": {
"direct": "Langsung",
"local": "Lokal - hanya instansi kamu yang dapat melihat postingan ini",
"private": "Hanya pengikut",
"public": "Publik"
},
"show_less": "Tampilkan lebih sedikit",
"show_more": "Tampilkan lebih banyak",
"submit": "Kirim",
@ -118,68 +156,187 @@
"load_older": "Muat interaksi yang lebih tua",
"moves": "Pengguna yang bermigrasi"
},
"languages": {
"ar": "Arab",
"az": "Azerbaijan",
"bg": "Bulgaria",
"cs": "Ceko",
"da": "Denmark",
"de": "Jerman",
"el": "Yunani",
"en": "Inggris",
"eo": "Esperanto",
"es": "Spanyol",
"fa": "Persia",
"fi": "Finlandia",
"fr": "Prancis",
"ga": "Irlandia",
"he": "Ibrani",
"hi": "Hindi",
"hu": "Hongaria",
"id": "Indonesia",
"it": "Italia",
"ja": "Jepang",
"ko": "Korea",
"lt": "Lithuania",
"lv": "Latvia",
"nl": "Belanda",
"pl": "Polandia",
"pt": "Portugis",
"ru": "Rusia",
"sk": "Slovakia",
"sv": "Swedia",
"tr": "Turki",
"translated_from": {
"ar": "Diterjemahkan dari bahasa @:languages.ar",
"az": "Diterjemahkan dari bahasa @:languages.az",
"bg": "Diterjemahkan dari bahasa @:languages.bg",
"cs": "Diterjemahkan dari bahasa @:languages.cs",
"da": "Diterjemahkan dari bahasa @:languages.da",
"de": "Diterjemahkan dari bahasa @:languages.de",
"el": "Diterjemahkan dari bahasa @:languages.el",
"en": "Diterjemahkan dari bahasa @:languages.en",
"eo": "Diterjemahkan dari bahasa @:languages.eo",
"es": "Diterjemahkan dari bahasa @:languages.es",
"fa": "Diterjemahkan dari bahasa @:languages.fa",
"fi": "Diterjemahkan dari bahasa @:languages.fi",
"fr": "Diterjemahkan dari bahasa @:languages.fr",
"ga": "Diterjemahkan dari bahasa @:languages.ga",
"he": "Diterjemahkan dari bahasa @:languages.he",
"hi": "Diterjemahkan dari bahasa @:languages.hi",
"hu": "Diterjemahkan dari bahasa @:languages.hu",
"id": "Diterjemahkan dari bahasa @:languages.id",
"it": "Diterjemahkan dari bahasa @:languages.it",
"ja": "Diterjemahkan dari bahasa @:languages.ja",
"ko": "Diterjemahkan dari bahasa @:languages.ko",
"lt": "Diterjemahkan dari bahasa @:languages.lt",
"lv": "Diterjemahkan dari bahasa @:languages.lv",
"nl": "Diterjemahkan dari bahasa @:languages.nl",
"pl": "Diterjemahkan dari bahasa @:languages.pl",
"pt": "Diterjemahkan dari bahasa @:languages.pt",
"ru": "Diterjemahkan dari bahasa @:languages.ru",
"sk": "Diterjemahkan dari bahasa @:languages.sk",
"sv": "Diterjemahkan dari bahasa @:languages.sv",
"tr": "Diterjemahkan dari bahasa @:languages.tr",
"uk": "Diterjemahkan dari bahasa @:languages.uk",
"zh": "Diterjemahkan dari bahasa @:languages.zh"
},
"uk": "Ukraina",
"zh": "Tionghoa"
},
"lists": {
"create": "Buat",
"delete": "Hapus daftar",
"lists": "Daftar",
"new": "Buat Daftar",
"save": "Simpan perubahan",
"search": "Telusuri pengguna",
"title": "Judul daftar"
},
"login": {
"authentication_code": "Kode otentikasi",
"description": "Masuk dengan OAuth",
"enter_recovery_code": "Masukkan kode pemulihan",
"enter_two_factor_code": "Masukkan kode dua-faktor",
"heading": {
"recovery": "Pemulihan dua-faktor",
"totp": "Otentikasi dua-faktor"
},
"hint": "Masuk untuk ikut berdiskusi",
"login": "Masuk",
"logout": "Keluar",
"logout_confirm": "Apa kamu yakin ingin keluar?",
"logout_confirm_accept_button": "Keluar",
"logout_confirm_cancel_button": "Batal",
"logout_confirm_title": "Keluar",
"password": "Kata sandi",
"placeholder": "contoh: lain",
"placeholder": "namapenggunaku",
"recovery_code": "Kode pemulihan",
"register": "Daftar",
"username": "Nama pengguna"
},
"media_modal": {
"next": "Selanjutnya",
"previous": "Sebelum"
"counter": "{current} / {total}",
"hide": "Tutup penampil media",
"next": "Berikutnya",
"previous": "Sebelumnya"
},
"moderation": {
"moderation": "Moderasi",
"reports": {
"add_note": "Tambahkan catatan",
"close": "Tutup",
"delete_note": "Hapus",
"delete_note_accept": "Ya, hapus",
"delete_note_cancel": "Tidak, kembalikan",
"delete_note_confirm": "Apa kamu yakin ingin menghapus catatan ini?",
"delete_note_title": "Konfirmasi penghapusan",
"no_content": "Tak diberikan keterangan",
"no_reports": "Tak ada laporan",
"note_placeholder": "Tinggalkan catatan",
"notes": "{ count } catatan",
"reopen": "Buka kembali",
"reports": "Laporan",
"resolve": "Selesaikan",
"show_closed": "Tampilkan yang telah ditutup",
"statuses": "{ count } postingan",
"tag_policy_notice": "Aktifkan MRF TagPolicy untuk mengatur pembatasan postingan",
"tags": "Atur pembatasan postingan"
},
"statuses": "Postingan",
"users": "Pengguna"
},
"nav": {
"about": "Tentang",
"administration": "Administrasi",
"announcements": "Pengumuman",
"back": "Kembali",
"bubble_timeline_description": "Postingan dari instansi yang dekat dengan instansimu, yang direkomendasikan oleh admin kamu",
"chats": "Obrolan",
"dms": "Pesan langsung",
"friend_requests": "Ingin mengikuti",
"home_timeline": "Linimasa beranda",
"home_timeline_description": "Postingan dari orang yang kamu ikuti",
"interactions": "Interaksi",
"lists": "Daftar",
"mentions": "Sebutan",
"moderation": "Moderasi",
"preferences": "Preferensi",
"public_timeline_description": "Postingan publik dari instansi ini",
"public_tl": "Linimasa publik",
"search": "Cari",
"search": "Penelusuran",
"timeline": "Linimasa",
"timelines": "Linimasa",
"user_search": "Pencarian Pengguna"
"twkn": "Jaringan Yang Dikenal",
"twkn_timeline_description": "Postingan dari seluruh jaringan",
"user_search": "Penelusuran Pengguna"
},
"notifications": {
"broken_favorite": "Status tak diketahui, mencarinya…",
"broken_favorite": "Postingan tak dikenal, mencarinya…",
"error": "Terjadi kesalahan ketika memuat notifikasi: {0}",
"favorited_you": "memfavoritkan status Anda",
"follow_request": "ingin mengikuti Anda",
"followed_you": "mengikuti Anda",
"favorited_you": "memfavoritkan postinganmu",
"follow_request": "ingin mengikuti kamu",
"followed_you": "mengikuti kamu",
"load_older": "Muat notifikasi yang lebih lama",
"migrated_to": "bermigrasi ke",
"no_more_notifications": "Tidak ada notifikasi lagi",
"notifications": "Notifikasi",
"poll_ended": "japat telah berakhir",
"reacted_with": "bereaksi dengan {0}",
"read": "Dibaca!",
"repeated_you": "mengulangi status Anda"
"repeated_you": "mengulangi postinganmu"
},
"password_reset": {
"check_email": "Periksa surelmu untuk mendapatkan tautan yang digunakan untuk mengatur ulang kata sandimu.",
"forgot_password": "Lupa kata sandi?",
"instruction": "Masukkan surel atau nama pengguna Anda. Kami akan mengirimkan Anda tautan untuk mengatur ulang kata sandi.",
"password_reset": "Pengatur-ulangan kata sandi",
"password_reset_disabled": "Pengatur-ulangan kata sandi dinonaktifkan. Hubungi administrator instansi Anda.",
"password_reset_required": "Anda harus mengatur ulang kata sandi Anda untuk masuk.",
"password_reset_required_but_mailer_is_disabled": "Anda harus mengatur ulang kata sandi, tetapi pengatur-ulangan kata sandi dinonaktifkan. Silakan hubungi administrator instansi Anda.",
"placeholder": "Surel atau nama pengguna Anda",
"instruction": "Masukkan surel atau nama pengguna kamu. Kami akan mengirimkan kamu tautan untuk mengatur ulang kata sandi.",
"password_reset": "Pengatur ulangan kata sandi",
"password_reset_disabled": "Pengatur-ulangan kata sandi dinonaktifkan. Silakan hubungi administrator instansi kamu.",
"password_reset_required": "Kamu harus mengatur ulang kata sandi kamu untuk masuk.",
"password_reset_required_but_mailer_is_disabled": "Kamu harus mengatur ulang kata sandi, tetapi pengatur-ulangan kata sandi dinonaktifkan. Silakan hubungi administrator instansimu.",
"placeholder": "Surel atau nama pengguna kamu",
"return_home": "Kembali ke halaman beranda",
"too_many_requests": "Anda telah mencapai batas percobaan, coba lagi nanti."
"too_many_requests": "Kamu telah mencapai batas percobaan, coba lagi nanti."
},
"polls": {
"add_option": "Tambahkan opsi",
@ -187,70 +344,87 @@
"expired": "Japat berakhir {0} yang lalu",
"expires_in": "Japat berakhir dalam {0}",
"expiry": "Usia japat",
"multiple_choices": "Lebih dari satu opsi dapat dipilih",
"not_enough_options": "Terlalu sedikit opsi yang unik pada japat",
"option": "Opsi",
"people_voted_count": "{count} orang memilih | {count} orang memilih",
"single_choice": "Hanya satu opsi dapat dipilih",
"type": "Jenis japat",
"vote": "Pilih",
"votes": "suara",
"votes_count": "{count} suara | {count} suara"
},
"post_status": {
"account_not_locked_warning": "Akun Anda tidak {0}. Siapapun dapat mengikuti Anda untuk melihat postingan hanya-pengikut Anda.",
"account_not_locked_warning": "Akun kamu tidak {0}. Siapapun dapat mengikuti kamu untuk melihat postingan hanya-pengikut kamu.",
"account_not_locked_warning_link": "terkunci",
"attachments_sensitive": "Tandai lampiran sebagai sensitif",
"content_type": {
"text/bbcode": "BBCode",
"text/html": "HTML",
"text/markdown": "Markdown",
"text/plain": "Teks biasa"
"text/plain": "Teks biasa",
"text/x.misskeymarkdown": "MFM"
},
"content_warning": "Subyek (opsional)",
"default": "Baru saja mendarat di L.A.",
"content_warning": "Peringatan Konten (opsional)",
"default": "Baru saja tiba di Luna Nova Academy",
"direct_warning_to_all": "Postingan ini akan terlihat oleh pengguna yang disebutkan.",
"direct_warning_to_first_only": "Postingan ini akan terlihat oleh pengguna yang disebutkan di awal pesan.",
"empty_status_error": "Tidak dapat memposting status kosong tanpa berkas",
"edit_remote_warning": "Perubahan yang dibuat pada postingan ini mungkin tidak terlihat pada beberapa instansi!",
"edit_status": "Sunting Status",
"edit_unsupported_warning": "Japat dan sebutan tidak bisa diubah dengan menyunting.",
"empty_status_error": "Tidak dapat memposting tanpa isi atau berkas",
"media_description": "Keterangan media",
"media_description_error": "Gagal memperbarui media, coba lagi",
"new_status": "Posting status baru",
"media_not_sensitive_warning": "Kamu memasang Peringatan Konten, namun lampirannya tidak ditandai sebagai sensitif!",
"new_status": "Posting",
"post": "Posting",
"posting": "Memposting",
"preview": "Pratinjau",
"preview_empty": "Kosong",
"scope": {
"direct": "Langsung - posting hanya kepada pengguna yang disebut",
"local": "Lokal - postingan tidak akan difederasi",
"private": "Hanya-pengikut - posting hanya kepada pengikut",
"public": "Publik - posting ke linimasa publik"
},
"scope_notice": {
"private": "Postingan ini akan terlihat hanya oleh pengikut Anda",
"public": "Postingan ini akan terlihat oleh siapa saja"
"local": "Postingan ini tidak akan terlihat di instansi lain",
"private": "Postingan ini akan terlihat hanya oleh pengikut kamu",
"public": "Postingan ini akan terlihat oleh siapa saja",
"unlisted": "Postingan ini tidak akan terlihat di Linimasa Publik dan Jaringan Yang Dikenal"
}
},
"registration": {
"awaiting_email_confirmation": "Akunmu telah terdaftar dan sebuah surel telah dikirimkan ke alamat kamu. Harap periksa surel untuk menyelesaikan pendaftaran.",
"awaiting_email_confirmation_title": "Menunggu konfirmasi surel",
"bio": "Bio",
"bio_placeholder": "contoh.\nHai, aku Lain.\nAku seorang putri anime yang tinggal di pinggiran kota Jepang. Kamu mungkin mengenal aku dari Wired.",
"bio_placeholder": "cth.\nHai! Selamat datang di bioku.\nAku suka menonton anime dan bermain game. Semoga kita bisa berteman!",
"captcha": "CAPTCHA",
"email": "Surel",
"fullname_placeholder": "contoh. Lain Iwakura",
"email_language": "Dalam bahasa apa kamu ingin menerima surel dari server ini?",
"fullname_placeholder": "cth. Atsuko Kagari",
"new_captcha": "Klik gambarnya untuk mendapatkan captcha baru",
"password_confirm": "Konfirmasi kata sandi",
"reason": "Alasan mendaftar",
"reason_placeholder": "Instansi ini menerima pendaftaran secara manual.\nBeritahu administrasinya mengapa Anda ingin mendaftar.",
"reason_placeholder": "Instansi ini menerima pendaftaran secara manual.\nBeritahu administrasinya mengapa kamu ingin mendaftar.",
"register": "Daftar",
"registration": "Pendaftaran",
"request_sent": "Permintaan pendaftaran kamu telah dikirim untuk diperiksa. Kamu akan menerima surel saat akunmu diterima.",
"request_sent_title": "Permintaan pendaftaran dikirim",
"token": "Token undangan",
"username_placeholder": "contoh. lain",
"username_placeholder": "cth. akko",
"validations": {
"email_required": "tidak boleh kosong",
"fullname_required": "tidak boleh kosong",
"password_confirmation_match": "harus sama dengan kata sandi",
"password_confirmation_required": "tidak boleh kosong",
"password_required": "tidak boleh kosong",
"username_required": "tidak boleh kosong"
}
},
"remote_user_resolver": {
"error": "Tidak ditemukan."
"error": "Tidak ditemukan.",
"searching_for": "Mencari"
},
"search": {
"hashtags": "Tagar",
@ -263,6 +437,13 @@
"select_all": "Pilih semua"
},
"settings": {
"account_backup": "Pencadangan akun",
"account_backup_description": "Ini memungkinkan kamu untuk mengunduh arsip yang berisi informasi tentang akun dan postingan kamu, namun belum bisa diimpor ke akun Pleroma.",
"account_privacy": "Privasi",
"add_backup": "Buat cadangan baru",
"add_backup_error": "Gagal menambahkan cadangan baru: {error}",
"added_backup": "Cadangan baru ditambahkan.",
"allow_following_move": "Ikuti otomatis apabila akun yang diikuti pindah",
"app_name": "Nama aplikasi",
"attachmentRadius": "Lampiran",
"attachments": "Lampiran",
@ -271,9 +452,10 @@
"avatarRadius": "Avatar",
"avatar_size_instruction": "Ukuran minimum gambar avatar yang disarankan adalah 150x150 piksel.",
"background": "Latar belakang",
"backup_not_ready": "Cadangan ini belum siap.",
"bio": "Bio",
"block_export": "Ekspor blokiran",
"block_export_button": "Ekspor blokiran Anda menjadi berkas csv",
"block_export_button": "Ekspor blokiranmu menjadi berkas csv",
"block_import": "Impor blokiran",
"block_import_error": "Terjadi kesalahan ketika mengimpor blokiran",
"blocks_imported": "Blokiran diimpor! Pemrosesannya mungkin memakan sedikit waktu.",
@ -285,65 +467,111 @@
"cOrange": "Jingga (Favorit)",
"cRed": "Merah (Batal)",
"change_email": "Ubah surel",
"change_email_error": "Ada masalah ketika mengubah surel Anda.",
"change_email_error": "Ada masalah ketika mengubah surel kamu.",
"change_password": "Ubah kata sandi",
"change_password_error": "Ada masalah ketika mengubah kata sandi Anda.",
"change_password_error": "Ada masalah ketika mengubah kata sandi kamu.",
"changed_email": "Surel berhasil diubah!",
"changed_password": "Kata sandi berhasil diubah!",
"chatMessageRadius": "Pesan obrolan",
"checkboxRadius": "Kotak centang",
"composing": "Menulis",
"confirm_dialogs": "Perlukan konfirmasi sebelum:",
"confirm_dialogs_approve_follow": "Menerima permintaan mengikuti",
"confirm_dialogs_block": "Memblokir seseorang",
"confirm_dialogs_delete": "Menghapus postingan",
"confirm_dialogs_deny_follow": "Menolak permintaan mengikuti",
"confirm_dialogs_mute": "Membisukan seseorang",
"confirm_dialogs_repeat": "Mengulangi postingan",
"confirm_dialogs_unfollow": "Berhenti mengikuti seseorang",
"confirm_new_password": "Konfirmasi kata sandi baru",
"conversation_display": "Gaya tampilan percakapan",
"conversation_display_tree": "Bercabang",
"conversation_other_replies_button_below": "Di bawah postingan",
"conversation_other_replies_button_inside": "Di postingan",
"current_avatar": "Avatarmu saat ini",
"current_password": "Kata sandi saat ini",
"data_import_export_tab": "Impor / ekspor data",
"delete_account": "Hapus akun",
"delete_account_description": "Hapus data Anda secara permanen dan menonaktifkan akun Anda.",
"delete_account_error": "Ada masalah ketika menghapus akun Anda. Jika ini terus terjadi harap hubungi adminstrator instansi Anda.",
"delete_account_instructions": "Ketik kata sandi Anda pada input di bawah untuk mengkonfirmasi penghapusan akun.",
"delete_account_description": "Hapus data kamu secara permanen dan nonaktifkan akunmu.",
"delete_account_error": "Ada masalah ketika menghapus akun kamu. Jika ini terus terjadi harap hubungi adminstrator instansi kamu.",
"delete_account_instructions": "Ketik kata sandi kamu pada input di bawah untuk mengonfirmasi penghapusan akun.",
"discoverable": "Izinkan penelusuran akun ini pada hasil pencarian dan layanan lainnya",
"domain_mutes": "Domain",
"download_backup": "Unduh",
"email_language": "Bahasa yang digunakan untuk menerima surel dari server ini",
"emoji_reactions_on_timeline": "Tampilkan reaksi emoji pada linimasa",
"enable_web_push_notifications": "Aktifkan notifikasi push web",
"enter_current_password_to_confirm": "Masukkan kata sandi Anda saat ini untuk mengkonfirmasi identitas Anda",
"enter_current_password_to_confirm": "Masukkan kata sandi kamu saat ini untuk mengonfirmasi identitas kamu",
"expert_mode": "Tampilkan pengaturan lanjutan",
"expire_posts_enabled": "Hapus postingan setelah jumlah hari yang ditentukan",
"expire_posts_input_placeholder": "Jumlah hari",
"file_export_import": {
"backup_restore": "Pencadangan pengaturan",
"backup_settings": "Cadangkan pengaturan ke berkas",
"backup_settings_theme": "Cadangkan pengaturan dan tema ke berkas",
"errors": {
"file_slightly_new": "Versi minor berkas berbeda, beberapa pengaturan mungkin tidak termuat",
"file_too_new": "Versi major tidak kompatibel: {fileMajor}, PleromaFE ini (versi {feMajor}) terlalu lama untuk menanganinya",
"file_too_old": "Versi major tidak kompatibel: {fileMajor}, versi berkas terlalu lama dan tidak lagi didukung (min. versi {feMajor})",
"invalid_file": "Berkas yang dipilih bukan cadangan pengaturan Pleroma yang didukung. Tidak dibuat perubahan."
},
"restore_settings": "Pulihkan pengaturan dari berkas"
},
"filtering": "Penyaringan",
"filtering_explanation": "Semua postingan yang mengandung kata-kata ini akan dibisukan, satu kata per baris",
"follow_import_error": "Terjadi kesalahan ketika mengimpor pengikut",
"fun": "Seru",
"general": "Umum",
"greentext": "Panah meme",
"hide_all_muted_posts": "Sembunyikan postingan yang dibisukan",
"hide_attachments_in_convo": "Sembunyikan lampiran pada percakapan",
"hide_attachments_in_tl": "Sembunyikan lampiran di linimasa",
"hide_bot_indication": "Sembunyikan tanda bot pada postingan",
"hide_filtered_statuses": "Sembunyikan semua postingan yang tersaring",
"hide_followers_count_description": "Jangan tampilkan jumlah pengikut",
"hide_followers_description": "Jangan tampilkan siapa yang mengikuti saya",
"hide_followers_description": "Jangan tampilkan siapa yang mengikutiku",
"hide_follows_count_description": "Jangan tampilkan jumlah mengikuti",
"hide_follows_description": "Jangan tampilkan siapa yang saya ikuti",
"hide_follows_description": "Jangan tampilkan siapa yang aku ikuti",
"hide_muted_posts": "Sembunyikan postingan-postingan dari pengguna yang dibisukan",
"hide_post_stats": "Sembunyikan statistik postingan (contoh. jumlah favorit)",
"hide_post_stats": "Sembunyikan statistik postingan (seperti jumlah favorit)",
"hide_shoutbox": "Sembunyikan kotak suara instansi",
"hide_user_stats": "Sembunyikan statistik pengguna (contoh. jumlah pengikut)",
"hide_user_stats": "Sembunyikan statistik pengguna (seperti jumlah pengikut)",
"hide_wallpaper": "Sembunyikan latar belakang instansi",
"import_blocks_from_a_csv_file": "Impor blokiran dari berkas csv",
"instance_default": "(bawaan: {value})",
"instance_default_simple": "(bawaan)",
"interface": "Antarmuka",
"interfaceLanguage": "Bahasa antarmuka",
"invalid_theme_imported": "Berkas yang dipilih bukan sebuah tema yang didukung Pleroma. Tidak ada perbuahan yang dibuat pada tema Anda.",
"limited_availability": "Tidak tersedia di browser Anda",
"invalid_theme_imported": "Berkas yang dipilih bukan sebuah tema yang didukung Pleroma. Tidak ada perubahan yang dibuat pada tema kamu.",
"limited_availability": "Tidak tersedia di browser kamu",
"links": "Tautan",
"lock_account_description": "Batasi akunmu kepada pengikut yang sudah disetujui saja",
"loop_video": "Ulang-ulang video",
"loop_video_silent_only": "Ulang-ulang video tanpa suara (seperti \"gif\" Mastodon)",
"max_thumbnails": "Jumlah thumbnail maksimum per postingan",
"max_thumbnails": "Jumlah thumbnail maksimum per postingan (kosong = tidak terbatas)",
"mention_link_bolden_you": "Sorot sebutan kamu apabila kamu disebut",
"mention_link_display": "Tampilkan tautan sebutan",
"mention_link_display_full": "selalu sebagai nama lengkap (cth. {'@'}foo{'@'}example.org)",
"mention_link_display_full_for_remote": "sebagai nama lengkap hanya untuk pengguna di instansi lain (cth. {'@'}foo{'@'}example.org)",
"mention_link_display_short": "selalu sebagai nama pendek (cth. {'@'}foo)",
"mention_link_show_avatar": "Tampilkan avatar pengguna di samping tautan",
"mfa": {
"authentication_methods": "Metode otentikasi",
"confirm_and_enable": "Konfirmasi & aktifkan OTP",
"generate_new_recovery_codes": "Hasilkan kode pemulihan baru",
"otp": "OTP",
"recovery_codes": "Kode pemulihan.",
"recovery_codes_warning": "Tulis kode-kode nya atau simpan mereka di tempat yang aman - jika tidak Anda tidak akan melihat mereka lagi. Jika Anda tidak dapat mengakses aplikasi 2FA Anda dan kode pemulihan Anda hilang Anda tidak akan bisa mengakses akun Anda.",
"recovery_codes_warning": "Tulis kodenya atau simpan mereka di tempat yang aman - jika tidak kamu tidak akan melihat mereka lagi. Jika kamu tidak dapat mengakses aplikasi 2FA kamu dan kode pemulihanmu hilang, kamu tidak akan bisa mengakses akun kamu.",
"scan": {
"title": "Pindai"
},
"setup_otp": "Siapkan OTP",
"title": "Otentikasi Dua-faktor",
"verify": {
"desc": "Untuk mengaktifkan otentikasi dua-faktor, masukkan kode dari aplikasi dua-faktor Anda:"
"desc": "Untuk mengaktifkan otentikasi dua-faktor, masukkan kode dari aplikasi dua-faktor kamu:"
},
"waiting_a_recovery_codes": "Menerima kode cadangan…",
"warning_of_generate_new_codes": "Ketika Anda menghasilkan kode pemulihan baru, kode lama Anda berhenti bekerja."
"warning_of_generate_new_codes": "Ketika kamu menghasilkan kode pemulihan baru, kode lama kamu berhenti bekerja."
},
"more_settings": "Lebih banyak pengaturan",
"mutes_and_blocks": "Bisuan dan Blokiran",
@ -372,21 +600,21 @@
},
"profile_tab": "Profil",
"reply_visibility_all": "Tampilkan semua balasan",
"reply_visibility_following": "Hanya tampilkan balasan yang ditujukan kepada saya atau orang yang saya ikuti",
"reply_visibility_following_short": "Tampilkan balasan ke orang yang saya ikuti",
"reply_visibility_self": "Hanya tampilkan balasan yang ditujukan kepada saya",
"reply_visibility_following": "Hanya tampilkan balasan yang ditujukan kepadaku atau orang yang aku ikuti",
"reply_visibility_following_short": "Tampilkan balasan ke orang yang aku ikuti",
"reply_visibility_self": "Hanya tampilkan balasan yang ditujukan kepadaku",
"save": "Simpan perubahan",
"saving_err": "Terjadi kesalahan ketika menyimpan pengaturan",
"saving_ok": "Pengaturan disimpan",
"search_user_to_block": "Cari siapa yang Anda ingin blokir",
"search_user_to_mute": "Cari siapa yang ingin Anda bisukan",
"search_user_to_block": "Cari siapa yang ingin kamu blokir",
"search_user_to_mute": "Cari siapa yang ingin kamu bisukan",
"security": "Keamanan",
"security_tab": "Keamanan",
"set_new_avatar": "Tetapkan avatar baru",
"set_new_profile_background": "Tetapkan latar belakang profil baru",
"settings": "Pengaturan",
"show_admin_badge": "Tampilkan lencana \"Admin\" di profil saya",
"show_moderator_badge": "Tampilkan lencana \"Moderator\" di profil saya",
"show_admin_badge": "Tampilkan lencana \"Admin\" di profilku",
"show_moderator_badge": "Tampilkan lencana \"Moderator\" di profilku",
"style": {
"advanced_colors": {
"_tab_label": "Lanjutan",
@ -429,7 +657,7 @@
},
"preview": {
"button": "Tombol",
"checkbox": "Saya telah membaca sekilas syarat dan ketentuan",
"checkbox": "Aku telah membaca sekilas syarat dan ketentuan",
"error": "Contoh kesalahan",
"faint_link": "manual berguna",
"fine_print": "Baca {0} kami untuk belajar sesuatu yang tak ada gunanya!",
@ -447,9 +675,9 @@
"switcher": {
"help": {
"fe_upgraded": "Mesin tema PleromaFE diperbarui setelah pembaruan versi.",
"future_version_imported": "Berkas yang Anda impor dibuat pada versi FE yang lebih baru.",
"older_version_imported": "Berkas yang Anda impor dibuat pada versi FE yang lebih lama.",
"upgraded_from_v2": "PleromaFE telah diperbarui, tema dapat terlihat sedikit berbeda dari apa yang Anda ingat."
"future_version_imported": "Berkas yang kamu impor dibuat pada versi FE yang lebih baru.",
"older_version_imported": "Berkas yang kamu impor dibuat pada versi FE yang lebih lama.",
"upgraded_from_v2": "PleromaFE telah diperbarui, tema dapat terlihat sedikit berbeda dari apa yang kamu ingat."
},
"load_theme": "Muat tema",
"use_snapshot": "Versi lama",
@ -481,7 +709,7 @@
},
"status": {
"delete": "Hapus status",
"delete_confirm": "Apakah Anda benar-benar ingin menghapus status ini?",
"delete_confirm": "Apakah kamu benar-benar ingin menghapus postingan ini?",
"favorites": "Favorit",
"hide_content": "",
"mute_conversation": "Bisukan percakapan",
@ -524,7 +752,7 @@
"conversation": "Percakapan",
"error": "Terjadi kesalahan memuat linimasa: {0}",
"no_more_statuses": "Tidak ada status lagi",
"no_retweet_hint": "Postingan ditandai sebagai hanya-pengikut atau langsung dan tidak dapat diulang",
"no_retweet_hint": "Postingan ditandai sebagai hanya-pengikut atau langsung dan tidak dapat diulang atau dikutip",
"no_statuses": "Tidak ada status",
"reload": "Muat ulang",
"repeated": "diulangi"
@ -576,10 +804,10 @@
"followees": "Mengikuti",
"followers": "Pengikut",
"following": "Diikuti!",
"follows_you": "Mengikuti Anda!",
"follows_you": "Mengikuti kamu!",
"hidden": "Disembunyikan",
"hide_repeats": "Sembunyikan ulangan",
"its_you": "Ini Anda!",
"its_you": "Ini kamu!",
"media": "Media",
"mention": "Sebut",
"message": "Kirimkan pesan",
@ -599,14 +827,14 @@
"timeline_title": "Linimasa pengguna"
},
"user_reporting": {
"add_comment_description": "Laporan ini akan dikirim ke moderator instansi Anda. Anda dapat menyediakan penjelasan mengapa Anda melaporkan akun ini di bawah:",
"add_comment_description": "Laporan ini akan dikirim ke moderator instansi kamu. Kamu dapat menyediakan penjelasan mengapa kamu melaporkan akun ini di bawah:",
"additional_comments": "Komentar tambahan",
"forward_description": "Akun ini berada di server lain. Kirim salinan dari laporannya juga?",
"generic_error": "Sebuah kesalahan terjadi ketika memproses permintaan Anda.",
"generic_error": "Sebuah kesalahan terjadi ketika memproses permintaan kamu.",
"submit": "Kirim",
"title": "Melaporkan {0}"
},
"who_to_follow": {
"more": "Lebih banyak"
}
}
}

View file

@ -1,20 +1,29 @@
{
"about": {
"bubble_instances": "ローカルバブルインスタンス",
"mrf": {
"federation": "フェデレーション",
"keyword": {
"keyword_policies": "キーワードポリシー",
"reject": "おことわり",
"replace": "おきかえ"
},
"mrf_policies": "ゆうこうなMRFポリシー",
"mrf_policies_desc": "MRFポリシーは、このインスタンスのフェデレーションのふるまいを、いじります。これらのMRFポリシーがゆうこうになっています:",
"simple": {
"accept": "うけいれ",
"accept_desc": "このインスンスは、これらのインスタンスからのメッセージのみをうけいれます:",
"ftl_removal": "「つながっているすべてのネットワーク」タイムラインからのぞく",
"ftl_removal_desc": "このインスタンスは、つながっているすべてのネットワーク」タイムラインから、これらのインスタンスを、とりのぞきます:",
"ftl_removal_desc": "このインスタンスは、「つながっているすべてのネットワーク」タイムラインから、これらのインスタンスを、とりのぞきます:",
"instance": "インスタンス",
"media_nsfw": "メディアをすべてセンシティブにする",
"media_nsfw_desc": "このインスタンスは、これらのインスタンスからおくられてきたメディアを、すべて、センシティブにマークします:",
"media_removal": "メディアをのぞく",
"media_removal_desc": "このインスタンスは、これらのインスタンスからおくられてきたメディアを、とりのぞきます:",
"not_applicable": "なし",
"quarantine": "けんえき",
"quarantine_desc": "このインスタンスは、これらのインスタンスに、パブリックなとうこうのみを、おくります:",
"quarantine_desc": "このインスタンスは、これらのインスタンスに、とうこうをおくりません:",
"reason": "りゆう",
"reject": "おことわり",
"reject_desc": "このインスタンスは、これらのインスタンスからのメッセージをうけいれません:",
"simple_policies": "インスタンスのポリシー"
@ -22,6 +31,49 @@
},
"staff": "スタッフ"
},
"announcements": {
"all_day_prompt": "いちにちじゅう",
"cancel_edit_action": "キャンセル",
"close_error": "とじる",
"delete_action": "けす",
"edit_action": "へんしゅう",
"end_time_display": "{time} におわります",
"end_time_prompt": "おわるじかん: ",
"inactive_message": "このおしらせは、アクティブではありません",
"mark_as_read_action": "よんだ!",
"page_header": "おしらせ",
"post_action": "とうこう",
"post_error": "エラーになりました: {error}",
"post_form_header": "おしらせする",
"post_placeholder": "おしらせのほんぶん",
"published_time_display": "{time} に、おしらせされました",
"start_time_display": "{time} から、はじまります",
"start_time_prompt": "はじまるじかん: ",
"submit_edit_action": "そうしん",
"title": "おしらせ"
},
"chats": {
"chats": "チャット",
"delete": "けす",
"delete_confirm": "ほんとうに、このメッセージを、けしてもいいですか?",
"empty_chat_list_placeholder": "チャットはありません。あたらしく、チャットをはじみてみましょう!",
"empty_message_error": "からっぽのメッセージは、おくれません",
"error_loading_chat": "チャットをよみこむことが、できなかったかもしれません。",
"error_sending_message": "メッセージをおくることが、できなかったかもしれません。",
"message_user": "{nickname} にメッセージ",
"more": "つづき",
"new": "あたらしいチャット",
"you": "あなた:"
},
"display_date": {
"today": "きょう"
},
"domain_mute_card": {
"mute": "ミュート",
"mute_progress": "ミュートしています…",
"unmute": "ミュートをやめる",
"unmute_progress": "ミュートをとりけしています…"
},
"emoji": {
"add_emoji": "えもじをうちこむ",
"custom": "カスタムえもじ",
@ -29,6 +81,7 @@
"keep_open": "ピッカーをあけたままにする",
"load_all": "すべてのえもじをロード ({emojiAmount} こあります)",
"load_all_hint": "はじめの {saneAmount} このえもじだけがロードされています。すべてのえもじをロードすると、パフォーマンスがわるくなるかもしれません。",
"recent": "さいきんつかった",
"search_emoji": "えもじをさがす",
"stickers": "ステッカー",
"unicode": "ユニコードえもじ"
@ -42,8 +95,15 @@
"scope_options": "こうかいはんいせんたく",
"text_limit": "もじのかず",
"title": "ゆうこうなきのう",
"upload_limit": "ファイルのおおきさのじょうげん",
"who_to_follow": "おすすめユーザー"
},
"file_type": {
"audio": "おんせい",
"file": "ファイル",
"image": "がぞう",
"video": "ビデオ"
},
"finder": {
"error_fetching_user": "ユーザーけんさくがエラーになりました",
"find_user": "ユーザーをさがす"
@ -51,12 +111,32 @@
"general": {
"apply": "てきよう",
"cancel": "キャンセル",
"close": "とじる",
"confirm": "たしかめる",
"disable": "なし",
"dismiss": "わすれる",
"enable": "あり",
"error_retry": "もういちど、ためしてください",
"flash_content": "クリックすると、Ruffle をつかって、フラッシュさくひんをひょうじします。(うまくうごかないかもしれません)",
"flash_fail": "フラッシュさくひんのロードに、しっぱいしました。コンソールに、くわしいことがかかれています。",
"flash_security": "フラッシュさくひんは、あぶないことをしてくるかもしれないので、ちゅういしてください。",
"generic_error": "エラーになりました",
"loading": "ロードしています…",
"more": "つづき",
"optional": "かかなくてもよい",
"peek": "かくす",
"retry": "もういちど、ためしてください",
"role": {
"admin": "アドミン",
"moderator": "モデレーター"
},
"scope_in_timeline": {
"direct": "ダイレクト",
"local": "ローカル: このとうこうは、このインスタンスのユーザーだけが、みることができます",
"private": "フォロワーげんてい",
"public": "パブリック",
"unlisted": "アンリステッド"
},
"show_less": "たたむ",
"show_more": "つづきをみる",
"submit": "そうしん",
@ -76,7 +156,86 @@
"interactions": {
"favs_repeats": "リピートとおきにいり",
"follows": "あたらしいフォロー",
"load_older": "ふるいやりとりをみる"
"load_older": "ふるいやりとりをみる",
"moves": "ユーザーをひっこしする"
},
"languages": {
"ar": "アラビアご",
"az": "アゼルバイジャンご",
"bg": "ブルガリアご",
"cs": "チェコご",
"da": "デンマークご",
"de": "ドイツご",
"el": "ギリシャご",
"en": "えいご",
"eo": "エスご",
"es": "スペインご",
"fa": "ペルシャご",
"fi": "フィンランドご",
"fr": "フランスご",
"ga": "アイルランドご",
"he": "ヘブライご",
"hi": "ヒンディーご",
"hu": "ハンガリーご",
"id": "インドネシアご",
"it": "イタリアご",
"ja": "にほんご",
"ko": "かんこくご",
"lt": "リトアニアご",
"lv": "ラトビアご",
"nl": "オランダご",
"pl": "ポーランドご",
"pt": "ポルトガルご",
"ru": "ロシアご",
"sk": "スロバキアご",
"sv": "スウェーデンご",
"tr": "トルコご",
"translated_from": {
"ar": "@:languages.ar から、ほんやくされました",
"az": "@:languages.az から、ほんやくされました",
"bg": "@:languages.bg から、ほんやくされました",
"cs": "@:languages.cs から、ほんやくされました",
"da": "@:languages.da から、ほんやくされました",
"de": "@:languages.de から、ほんやくされました",
"el": "@:languages.el から、ほんやくされました",
"en": "@:languages.en から、ほんやくされました",
"eo": "@:languages.eo から、ほんやくされました",
"es": "@:languages.es から、ほんやくされました",
"fa": "@:languages.fa から、ほんやくされました",
"fi": "@:languages.fi から、ほんやくされました",
"fr": "@:languages.fr から、ほんやくされました",
"ga": "@:languages.ga から、ほんやくされました",
"he": "@:languages.he から、ほんやくされました",
"hi": "@:languages.hi から、ほんやくされました",
"hu": "@:languages.hu から、ほんやくされました",
"id": "@:languages.id から、ほんやくされました",
"it": "@:languages.it から、ほんやくされました",
"ja": "@:languages.ja から、ほんやくされました",
"ko": "@:languages.ko から、ほんやくされました",
"lt": "@:languages.lt から、ほんやくされました",
"lv": "@:languages.lv から、ほんやくされました",
"nl": "@:languages.nl から、ほんやくされました",
"pl": "@:languages.pl から、ほんやくされました",
"pt": "@:languages.pt から、ほんやくされました",
"ru": "@:languages.ru から、ほんやくされました",
"sk": "@:languages.sk から、ほんやくされました",
"sv": "@:languages.sv から、ほんやくされました",
"tr": "@:languages.tr から、ほんやくされました",
"uk": "@:languages.uk から、ほんやくされました",
"zh": "@:languages.zh から、ほんやくされました"
},
"uk": "ウクライナご",
"zh": "ちゅうごくご"
},
"lists": {
"create": "つくる",
"delete": "けす",
"following_only": "フォローしているユーザーのみ",
"lists": "リスト",
"new": "リストをつくる",
"save": "セーブ",
"search": "ユーザーをさがす",
"title": "リストのなまえ"
},
"login": {
"authentication_code": "にんしょうコード",
@ -90,41 +249,90 @@
"hint": "はなしあいにくわわるには、ログインしてください",
"login": "ログイン",
"logout": "ログアウト",
"logout_confirm": "ほんとうに、ログアウトしてもいいですか?",
"logout_confirm_accept_button": "ログアウト",
"logout_confirm_cancel_button": "キャンセル",
"logout_confirm_title": "ログアウト",
"password": "パスワード",
"placeholder": "れい: lain",
"placeholder": "ユーザーめい",
"recovery_code": "リカバリーコード",
"register": "はじめる",
"username": "ユーザーめい"
},
"media_modal": {
"counter": "{current} / {total}",
"hide": "とじる",
"next": "つぎ",
"previous": "まえ"
},
"moderation": {
"moderation": "モデレーション",
"reports": {
"add_note": "メモする",
"close": "とじる",
"delete_note": "けす",
"delete_note_accept": "けす",
"delete_note_cancel": "キャンセル",
"delete_note_confirm": "ほんとうに、このメモをけしてもいいですか?",
"delete_note_title": "かくにんしてください",
"no_content": "せつめいはありません",
"no_reports": "つうほうはありません",
"note_placeholder": "メモする",
"notes": "{ count }こ",
"reopen": "ひらきなおす",
"report": "つうほう:",
"reports": "つうほう",
"resolve": "かいけつ",
"show_closed": "かいけつしたつうほうをみる",
"statuses": "{ count } こ",
"tag_policy_notice": "とうこうをせいげんするには、TagPolicy MRF をゆうこうにしてください",
"tags": "とうこうをせいげんする"
},
"statuses": "とうこう",
"users": "ユーザー"
},
"nav": {
"about": "これはなに?",
"administration": "アドミニストレーション",
"announcements": "おしらせ",
"back": "もどる",
"bookmarks": "ブックマーク",
"bubble_timeline": "バブルタイムライン",
"bubble_timeline_description": "アドミンがおすすめするインスタンスからのとうこう",
"chats": "チャット",
"dms": "ダイレクトメッセージ",
"friend_requests": "フォローリクエスト",
"home_timeline": "ホームタイムライン",
"home_timeline_description": "フォローしているユーザーのとうこう",
"interactions": "やりとり",
"lists": "リスト",
"mentions": "メンション",
"moderation": "モデレーション",
"preferences": "せってい",
"public_timeline_description": "このインスタンスからの、パブリックなとうこう",
"public_tl": "パブリックタイムライン",
"search": "さがす",
"timeline": "タイムライン",
"timelines": "タイムライン",
"twkn": "つながっているすべてのネットワーク",
"twkn_timeline_description": "つながっているすべてのネットワークからのとうこう",
"user_search": "ユーザーをさがす",
"who_to_follow": "おすすめユーザー"
},
"notifications": {
"broken_favorite": "ステータスがみつかりません。さがしています…",
"favorited_you": "あなたのステータスがおきにいりされました",
"broken_favorite": "とうこうがみつかりません。さがしています…",
"error": "つうちのしゅとくがエラーになりました: {0}",
"favorited_you": "あなたのとうこうが、おきにいりされました",
"follow_request": "フォローリクエストされました",
"followed_you": "フォローされました",
"load_older": "ふるいつうちをみる",
"migrated_to": "インスタンスをひっこしました",
"no_more_notifications": "つうちはありません",
"notifications": "つうち",
"poll_ended": "いれふだがおわりました",
"reacted_with": "{0} でリアクションされました",
"read": "よんだ!",
"repeated_you": "あなたのステータスがリピートされました"
"repeated_you": "あなたのとうこうが、リピートされました"
},
"password_reset": {
"check_email": "パスワードをリセットするためのリンクがかかれたメールが、とどいているかどうか、みてください。",
@ -147,49 +355,72 @@
"multiple_choices": "いくつでもえらべる",
"not_enough_options": "ユニークなオプションが、たりません",
"option": "オプション",
"people_voted_count": "{count} にんが、ふだをいれています",
"single_choice": "ひとつえらぶ",
"type": "いれふだのかた",
"vote": "ふだをいれる",
"votes": "いれふだ"
"votes": "いれふだ",
"votes_count": "{count} ふだ"
},
"post_status": {
"account_not_locked_warning": "あなたのアカウントは {0} ではありません。あなたをフォローすれば、だれでも、フォロワーげんていのステータスをよむことができます。",
"account_not_locked_warning": "あなたのアカウントは {0} ではありません。あなたをフォローすれば、だれでも、フォロワーげんていのとうこうをよむことができます。",
"account_not_locked_warning_link": "ロックされたアカウント",
"attachments_sensitive": "ファイルをNSFWにする",
"content_type": {
"text/bbcode": "BBCode",
"text/html": "HTML",
"text/markdown": "Markdown",
"text/plain": "プレーンテキスト"
"text/plain": "プレーンテキスト",
"text/x.misskeymarkdown": "MFM"
},
"content_warning": "せつめい (かかなくてもよい)",
"content_warning": "ちゅういがき (かかなくてもよい)",
"default": "はねだくうこうに、つきました。",
"direct_warning_to_all": "このとうこうは、メンションされたすべてのユーザーが、みることができます。",
"direct_warning_to_first_only": "このとうこうは、メッセージのはじめでメンションされたユーザーだけが、みることができます。",
"edit_remote_warning": "へんしゅうしたとうこうは、ほかのインスタンスに、はんえいされないことがあります!",
"edit_status": "へんしゅう",
"edit_unsupported_warning": "いれふだとメンションは、へんしゅうでかえることができません。",
"empty_status_error": "からっぽのとうこうは、おくることができません",
"media_description": "メディアのせつめい",
"media_description_error": "メディアのアップデートにしっぱいしました。もういちど、ためしてください",
"media_not_sensitive_warning": "ちゅういがきがついていますが、NSFWはついていません",
"new_status": "とうこうする",
"post": "とうこう",
"posting": "とうこう",
"preview": "プレビュー",
"preview_empty": "からっぽです",
"scope": {
"direct": "ダイレクト: メンションされたユーザーのみにとどきます",
"local": "ローカル: パブリックタイムラインにとどきますが、ほかのインスタンスにはとどきません",
"private": "フォロワーげんてい: フォロワーのみにとどきます",
"public": "パブリック: パブリックタイムラインにとどきます",
"unlisted": "アンリステッド: パブリックタイムラインにとどきません"
},
"scope_notice": {
"local": "このとうこうは、このインスタンスのユーザーだけが、みることができます",
"private": "このとうこうは、あなたのフォロワーだけが、みることができます",
"public": "このとうこうは、だれでもみることができます",
"unlisted": "このとうこうは、パブリックタイムラインと、つながっているすべてのネットワークでは、みることができません"
}
},
"registration": {
"awaiting_email_confirmation": "あなたのメールアドレスに、メールをおくりました。メールをかくにんして、とうろくをかんりょうさせてください。",
"awaiting_email_confirmation_title": "メールをかくにんするのをまっています",
"bio": "プロフィール",
"bio_placeholder": "れい:\nごきげんよう。わたしはれいん。\nわたしはアニメのおんなのこで、にほんのベッドタウンにすんでいます。ワイヤードで、わたしにあったことが、あるかもしれませんね。",
"captcha": "CAPTCHA",
"email": "Eメール",
"email_language": "サーバーからのメールは、どのことばで、かいてほしいですか?",
"fullname": "スクリーンネーム",
"fullname_placeholder": "れい: いわくら れいん",
"new_captcha": "もじがよめないときは、がぞうをクリックすると、あたらしいがぞうになります",
"password_confirm": "パスワードのかくにん",
"reason": "とうろくしたいりゆう",
"reason_placeholder": "このインスタンスは、しゅどうで、とうろくをうけつけています。\nとうろくしたいりゆうを、アドミニストレーターにおしえてください。",
"register": "はじめる",
"registration": "はじめる",
"request_sent": "とうろくリクエストをおくりました。とうろくがうけいれられると、メールがとどきます。",
"request_sent_title": "とうろくリクエストをおくりました",
"token": "しょうたいトークン",
"username_placeholder": "れい: lain",
"validations": {
@ -217,6 +448,20 @@
"select_all": "すべてえらぶ"
},
"settings": {
"accent": "アクセント",
"account_alias": "アカウントのエイリアス",
"account_alias_table_head": "エイリアス",
"account_backup": "アカウントのバックアップ",
"account_backup_description": "あなたのアカウントのじょうほうと、とうこうの、アーカイブをダウンロードできます。しかし、いまのところは、バックアップを Pleromaのアカウントにインポートすることはできません。",
"account_backup_table_head": "バックアップ",
"account_privacy": "プライバシー",
"add_alias_error": "エイリアスのついかがエラーになりました: {error}",
"add_backup": "あたらしいバックアップをつくる",
"add_backup_error": "バックアップのついかがエラーになりました: {error}",
"added_alias": "エイリアスをついかしました。",
"added_backup": "バックアップをつくりました。",
"allow_following_move": "フォローしているアカウントがひっこしたときに、じどうでフォローしてもよい",
"always_show_post_button": "みぎしたのとうこうボタンをいつでもひょうじする",
"app_name": "アプリのなまえ",
"attachmentRadius": "ファイル",
"attachments": "ファイル",
@ -226,6 +471,7 @@
"avatarRadius": "アバター",
"avatar_size_instruction": "アバターのおおきさは、150×150ピクセルか、それよりもおおきくするといいです。",
"background": "バックグラウンド",
"backup_not_ready": "このバックアップは、まだ、かんせいしていません。",
"bio": "プロフィール",
"block_export": "ブロックのエクスポート",
"block_export_button": "ブロックをCSVファイルにエクスポート",
@ -233,6 +479,7 @@
"block_import_error": "ブロックのインポートがエラーになりました",
"blocks_imported": "ブロックをインポートしました! じっさいにブロックするまでには、もうしばらくかかります。",
"blocks_tab": "ブロック",
"bot": "これは bot アカウントです",
"btnRadius": "ボタン",
"cBlue": "リプライとフォロー",
"cGreen": "リピート",
@ -244,11 +491,29 @@
"change_password_error": "パスワードをかえることが、できなかったかもしれません。",
"changed_email": "メールアドレスをかえることができました!",
"changed_password": "パスワードが、かわりました!",
"chatMessageRadius": "チャットメッセージ",
"checkboxRadius": "チェックボックス",
"collapse_subject": "せつめいのあるとうこうをたたむ",
"collapse_subject": "ちゅういがきのあるとうこうをたたむ",
"columns": "カラム",
"composing": "とうこう",
"confirm_dialogs": "いつ、かくにんがめんをひょうじしますか:",
"confirm_dialogs_approve_follow": "フォローリクエストをうけいれるとき",
"confirm_dialogs_block": "だれかをブロックするとき",
"confirm_dialogs_delete": "とうこうをけすとき",
"confirm_dialogs_deny_follow": "フォローリクエストをおことわりするとき",
"confirm_dialogs_mute": "だれかをミュートするとき",
"confirm_dialogs_repeat": "とうこうをリピートするとき",
"confirm_dialogs_unfollow": "だれかのフォローをやめるとき",
"confirm_new_password": "あたらしいパスワードのかくにん",
"confirmation_dialogs": "かくにんがめんのせってい",
"conversation_display": "スレッドのみため",
"conversation_display_linear": "リニア",
"conversation_display_tree": "ツリー",
"conversation_other_replies_button": "「ほかのへんしん」ボタンをひょうじするばしょ",
"conversation_other_replies_button_below": "とうこうのした",
"conversation_other_replies_button_inside": "とうこうのうちがわ",
"current_avatar": "いまのアバター",
"current_mascot": "いまのマスコット",
"current_password": "いまのパスワード",
"data_import_export_tab": "インポートとエクスポート",
"default_vis": "デフォルトのこうかいはんい",
@ -256,9 +521,15 @@
"delete_account_description": "あなたのアカウントとメッセージが、きえます。",
"delete_account_error": "アカウントをけすことが、できなかったかもしれません。インスタンスのアドミニストレーターに、おといあわせください。",
"delete_account_instructions": "ほんとうにアカウントをけしてもいいなら、パスワードをかいてください。",
"disable_sticky_headers": "カラムヘッダーを、がめんのいちばんうえにくっつけない",
"discoverable": "けんさくなどのサービスで、このアカウントをみつけてもよい",
"domain_mutes": "ドメイン",
"download_backup": "ダウンロード",
"email_language": "メールのことば",
"emoji_reactions_on_timeline": "えもじのリアクションをタイムラインにひょうじする",
"enable_web_push_notifications": "ウェブプッシュつうちをゆるす",
"enter_current_password_to_confirm": "あなたのアイデンティティをたしかめるため、あなたのいまのパスワードをかいてください",
"expert_mode": "こまかいせっていをひょうじ",
"export_theme": "セーブ",
"filtering": "フィルタリング",
"filtering_explanation": "これらのことばをふくむすべてのものがミュートされます。1ぎょうに1つのことばをかいてください",
@ -329,6 +600,7 @@
"no_rich_text_description": "リッチテキストをつかわない",
"notification_blocks": "ブロックしているユーザーからのつうちは、すべてとまります。",
"notification_mutes": "あるユーザーからのつうちをとめるには、ミュートしてください。",
"notification_setting_hide_if_cw": "ちゅういがきがあるとうこうのないようを、つうちからみえないようにする",
"notification_visibility": "ひょうじするつうち",
"notification_visibility_follows": "フォロー",
"notification_visibility_likes": "おきにいり",
@ -361,6 +633,7 @@
"search_user_to_mute": "ミュートしたいひとを、ここでけんさくできます",
"security": "セキュリティ",
"security_tab": "セキュリティ",
"sensitive_if_subject": "ちゅういがきを、つけたときに、がぞうをじどうてきにNSFWにする",
"set_new_avatar": "あたらしいアバターをせっていする",
"set_new_profile_background": "あたらしいプロフィールのバックグラウンドをせっていする",
"set_new_profile_banner": "あたらしいプロフィールバナーを設定する",
@ -478,8 +751,8 @@
"save_load_hint": "「のこす」オプションをONにすると、テーマをえらんだときとロードしたとき、いまのせっていをのこします。また、テーマをエクスポートするとき、これらのオプションをストアします。すべてのチェックボックスをOFFにすると、テーマをエクスポートしたとき、すべてのせっていをセーブします。"
}
},
"subject_input_always_show": "サブジェクトフィールドをいつでもひょうじする",
"subject_line_behavior": "リプライするときサブジェクトをコピーする",
"subject_input_always_show": "ちゅういがきフィールドをいつでもひょうじする",
"subject_line_behavior": "リプライするとき、ちゅういがきをコピーする",
"subject_line_email": "メールふう: \"re: サブジェクト\"",
"subject_line_mastodon": "マストドンふう: そのままコピー",
"subject_line_noop": "コピーしない",
@ -506,8 +779,8 @@
}
},
"status": {
"delete": "ステータスをけす",
"delete_confirm": "ほんとうに、このステータスを、けしてもいいですか?",
"delete": "とうこうをけす",
"delete_confirm": "ほんとうに、このとうこうを、けしてもいいですか?",
"favorites": "おきにいり",
"mute_conversation": "スレッドをミュートする",
"pin": "プロフィールにピンどめする",
@ -515,6 +788,8 @@
"repeats": "リピート",
"replies_list": "へんしん:",
"reply_to": "へんしん:",
"translate": "ほんやく",
"translated_from": "{language} から、ほんやくされました",
"unmute_conversation": "スレッドをミュートするのをやめる",
"unpin": "プロフィールにピンどめするのをやめる"
},
@ -543,7 +818,7 @@
"timeline": {
"collapse": "たたむ",
"conversation": "スレッド",
"load_older": "ふるいステータス",
"load_older": "ふるいとうこう",
"no_more_statuses": "これでおわりです",
"no_retweet_hint": "とうこうを「フォロワーのみ」または「ダイレクト」にすると、リピートできなくなります",
"no_statuses": "ありません",
@ -617,7 +892,7 @@
"remote_follow": "リモートフォロー",
"report": "つうほう",
"show_repeats": "リピートをみる",
"statuses": "ステータス",
"statuses": "とうこう",
"subscribe": "サブスクライブ",
"unblock": "ブロックをやめる",
"unblock_progress": "ブロックをとりけしています…",
@ -643,4 +918,4 @@
"more": "くわしく",
"who_to_follow": "おすすめユーザー"
}
}
}

View file

@ -283,13 +283,18 @@
"no_content": "説明なし",
"no_reports": "通報なし",
"note_placeholder": "メモ",
"notes": "メモ",
"notes": "{count}件",
"reopen": "再開",
"report": "通報:",
"reports": "通報",
"resolve": "完了",
"show_closed": "完了した通報を表示"
}
"show_closed": "完了した通報を表示",
"statuses": "{count}件",
"tag_policy_notice": "TagPolicyのMRFをONにしてください",
"tags": "ポスト制限を付ける"
},
"statuses": "ポスト",
"users": "ユーザー"
},
"nav": {
"about": "このインスタンスについて",
@ -404,6 +409,8 @@
}
},
"registration": {
"awaiting_email_confirmation": "あなたにメールが送られました。メールをご覧くださって、リンクをクリックしてください",
"awaiting_email_confirmation_title": "メール確認中",
"bio": "プロフィール",
"bio_placeholder": "例:\nこんにちは。私は玲音。\n私はアニメのキャラクターで、日本の郊外に住んでいます。私をWiredで見たことがあるかもしれません。",
"captcha": "CAPTCHA",
@ -417,6 +424,8 @@
"reason_placeholder": "このインスタンスは、新規登録を手動で受け付けています。\n登録したい理由を、インスタンスの管理者に教えてください。",
"register": "登録",
"registration": "登録",
"request_sent": "登録リクエストを送りました。登録受け入れたらメールが届きます。",
"request_sent_title": "登録リクエストを送りました",
"token": "招待トークン",
"username_placeholder": "例: lain",
"validations": {
@ -526,6 +535,8 @@
"enable_web_push_notifications": "ウェブプッシュ通知を許可する",
"enter_current_password_to_confirm": "あなたのアイデンティティを証明するため、現在のパスワードを入力してください",
"expert_mode": "詳細設定を表示",
"expire_posts_enabled": "自動削除",
"expire_posts_input_placeholder": "日数",
"export_theme": "保存",
"file_export_import": {
"backup_restore": "設定をバックアップ",
@ -1035,6 +1046,7 @@
"collapse": "たたむ",
"conversation": "スレッド",
"error": "タイムラインの読み込みに失敗しました: {0}",
"follow_tag": "タグをフォロー",
"load_older": "古い投稿",
"no_more_statuses": "これで終わりです",
"no_retweet_hint": "投稿を「フォロワーのみ」または「ダイレクト」にすると、リピートできなくなります",
@ -1044,6 +1056,7 @@
"show_new": "読み込み",
"socket_broke": "コード{0}によりリアルタイム接続が切断されました",
"socket_reconnected": "リアルタイム接続が確立されました",
"unfollow_tag": "タグのフォローを解除",
"up_to_date": "最新"
},
"toast": {

View file

@ -254,6 +254,10 @@
"hint": "Log in om deel te nemen aan de discussie",
"login": "Inloggen",
"logout": "Uitloggen",
"logout_confirm": "Weet je zeker dat je wilt uitloggen?",
"logout_confirm_accept_button": "Uitloggen",
"logout_confirm_cancel_button": "Annuleren",
"logout_confirm_title": "Uitloggen",
"password": "Wachtwoord",
"placeholder": "mijngebruikersnaam",
"recovery_code": "Herstelcode",
@ -266,6 +270,30 @@
"next": "Volgende",
"previous": "Vorige"
},
"moderation": {
"moderation": "Moderatie",
"reports": {
"add_note": "Notitie toevoegen",
"close": "Sluiten",
"delete_note": "Verwijderen",
"delete_note_accept": "Ja, verwijderen",
"delete_note_cancel": "Nee, behouden",
"delete_note_confirm": "Weet je zeker dat je deze notitie wilt verwijderen?",
"delete_note_title": "Verwijderen bevestigen",
"no_content": "Geen omschrijving beschikbaar",
"no_reports": "Geen rapporten beschikbaar",
"notes": "{ count } notitie | { count } notities",
"reopen": "Heropenen",
"reports": "Rapporten",
"resolve": "Oplossen",
"show_closed": "Afgesloten tonen",
"statuses": "{ count } bericht| { count } berichten",
"tag_policy_notice": "Activeer de TagPolicy MRF om bericht-beperkingen in te stellen",
"tags": "Bericht-beperkingen instellen"
},
"statuses": "Berichten",
"users": "Gebruikers"
},
"nav": {
"about": "Over ons",
"administration": "Beheer",
@ -282,6 +310,7 @@
"interactions": "Interacties",
"lists": "Lijsten",
"mentions": "Vermeldingen",
"moderation": "Moderatie",
"preferences": "Voorkeuren",
"public_timeline_description": "Openbare berichten van deze instantie",
"public_tl": "Openbare tijdlijn",
@ -378,6 +407,8 @@
}
},
"registration": {
"awaiting_email_confirmation": "Je account is geregistreerd en een e-mail verzonden naar je e-mailadres ter bevestiging. Controleer je e-mail inbox om de registratie af te ronden.",
"awaiting_email_confirmation_title": "In afwachting van e-mail bevestiging",
"bio": "Bio",
"bio_placeholder": "Bijv.\nHallo! Welkom op mijn bio.\nIk vind anime en games leuk. Hopelijk kunnen we vrienden zijn!",
"captcha": "CAPTCHA",
@ -391,6 +422,8 @@
"reason_placeholder": "Deze instantie keurt registraties handmatig goed.\nLaat de beheerder weten waarom je je wilt registreren.",
"register": "Registreren",
"registration": "Registratie",
"request_sent": "Je registratieverzoek zal worden beoordeeld. Indien je account wordt goed gekeurd, zul je een e-mail ontvangen.",
"request_sent_title": "Registratieverzoek verzonden",
"token": "Uitnodigingstoken",
"username_placeholder": "bijv. akko",
"validations": {
@ -500,6 +533,8 @@
"enable_web_push_notifications": "Web push meldingen inschakelen",
"enter_current_password_to_confirm": "Voer je huidige wachtwoord in om je identiteit te bevestigen",
"expert_mode": "Geavanceerde opties tonen",
"expire_posts_enabled": "Berichten ouder dan een ingesteld aantal dagen verwijderen",
"expire_posts_input_placeholder": "Aantal dagen",
"export_theme": "Preset opslaan",
"file_export_import": {
"backup_restore": "Instellingen back-up",
@ -693,6 +728,18 @@
"setting_changed": "Instelling verschilt van standaard waarde",
"setting_server_side": "Deze instelling is gebonden aan je profiel en beïnvloed alle sessies en clients",
"settings": "Instellingen",
"settings_profile": "Instellingsprofielen",
"settings_profile_creation": "Nieuw profiel aanmaken",
"settings_profile_creation_new_name_label": "Naam",
"settings_profile_creation_submit": "Aanmaken",
"settings_profile_delete": "Verwijderen",
"settings_profile_delete_confirm": "Weet je zeker dat je dit profiel wilt verwijderen?",
"settings_profile_force_sync": "Synchroniseren",
"settings_profile_in_use": "In gebruik",
"settings_profile_use": "Gebruiken",
"settings_profiles_refresh": "Instellingsprofielen verversen",
"settings_profiles_show": "Alle instellingsprofielen tonen",
"settings_profiles_unshow": "Alle instellingsprofielen verbergen",
"show_admin_badge": "\"Beheerder\" badge in mijn profiel tonen",
"show_moderator_badge": "\"Moderator\" badge in mijn profiel tonen",
"show_nav_shortcuts": "Extra navigatie snelkoppelingen tonen in top paneel",
@ -872,6 +919,7 @@
"useStreamingApi": "Berichten en meldingen in real-time ontvangen",
"useStreamingApiWarning": "Iets experimenteels met berichten streamen uwu miss kun je beter uit laten ofzo?",
"use_at_icon": "{'@'} symbool als icoon tonen in plaats van tekst",
"use_blurhash": "Waas tonen over NSFW-miniaturen",
"use_contain_fit": "Bijlage in miniaturen niet bijsnijden",
"use_one_click_nsfw": "Gevoelige bijlagen met slechts één klik openen",
"user_mutes": "Gebruikers",
@ -892,6 +940,12 @@
"word_filter": "Woord filter",
"wordfilter": "Woordfilter"
},
"settings_profile": {
"creating": "Bezig met nieuw profiel \"{profile}\" aan te maken...",
"synchronization_error": "Instellingen konden niet gesynchroniseerd worden: {err}",
"synchronized": "Instellingen gesynchroniseerd!",
"synchronizing": "Bezig met profiel \"{profile}\" te synchroniseren..."
},
"status": {
"ancestor_follow": "{numReplies} ander antwoord onder dit bericht tonen | {numReplies} andere antwoorden onder dit bericht tonen",
"ancestor_follow_with_icon": "{icon} {text}",
@ -925,6 +979,11 @@
"pin": "Aan profiel vastmaken",
"pinned": "Vastgezet",
"plus_more": "+{number} meer",
"redraft": "Verwijderen & opnieuw opstellen",
"redraft_confirm": "Weet je zeker at je dit bericht wilt verwijderen en opnieuw opstellen? Interacties met het originele bericht zullen vervallen.",
"redraft_confirm_accept_button": "Ja, verwijderen en opnieuw opstellen",
"redraft_confirm_cancel_button": "Nee, origineel bericht behouden",
"redraft_confirm_title": "Verwijderen & opnieuw opstellen bevestigen",
"remove_attachment": "Bijlage verwijderen",
"repeat_confirm": "Weet je zeker dat je dit bericht wilt herhalen?",
"repeat_confirm_accept_button": "Ja, herhalen",
@ -985,6 +1044,7 @@
"collapse": "Invouwen",
"conversation": "Gesprek",
"error": "Fout bij het ophalen van tijdlijn: {0}",
"follow_tag": "Hashtag volgen",
"load_older": "Oudere berichten laden",
"no_more_statuses": "Geen verdere berichten",
"no_retweet_hint": "Bericht is gemarkeerd als enkel-volgers of privé en kan niet worden herhaald of geciteerd",
@ -994,6 +1054,7 @@
"show_new": "Nieuwe tonen",
"socket_broke": "Realtime verbinding verloren: CloseEvent code {0}",
"socket_reconnected": "Realtime verbinding opgezet",
"unfollow_tag": "Hashtag ontvolgen",
"up_to_date": "Up-to-date"
},
"toast": {
@ -1058,6 +1119,7 @@
"block_confirm_title": "Gebruiker blokkeren",
"block_progress": "Blokkeren…",
"blocked": "Geblokkeerd!",
"blocks_you": "Blokkeert jou!",
"bot": "Bot",
"deactivated": "Gedeactiveerd",
"deny": "Weigeren",
@ -1072,7 +1134,10 @@
"follow_cancel": "Verzoek annuleren",
"follow_progress": "Aanvragen…",
"follow_sent": "Verzoek verzonden!",
"follow_tag": "Hashtag volgen",
"follow_unfollow": "Ontvolgen",
"followed_tags": "Gevolgde hashtags",
"followed_users": "Gevolgde gebruikers",
"followees": "Volgen",
"followers": "Volgers",
"following": "Gevolgd!",
@ -1097,11 +1162,14 @@
"mute_domain": "Domein blokkeren",
"mute_progress": "Negeren…",
"muted": "Genegeerd",
"not_following_any_hashtags": "Je volgt momenteel geen hashtags",
"note": "Privé notitie",
"per_day": "per dag",
"remote_follow": "Van afstand volgen",
"remove_follower": "Volger verwijderen",
"replies": "Met Antwoorden",
"report": "Rapporteren",
"requested_by": "Heeft verzocht je te volgen",
"show_repeats": "Herhalingen tonen",
"statuses": "Berichten",
"subscribe": "Abonneren",
@ -1111,11 +1179,13 @@
"unfollow_confirm_accept_button": "Ja, ontvolgen",
"unfollow_confirm_cancel_button": "Nee, niet ontvolgen",
"unfollow_confirm_title": "Gebruiker ontvolgen",
"unfollow_tag": "Hashtag ontvolgen",
"unmute": "Negeren opheffen",
"unmute_progress": "Negeren opheffen…",
"unsubscribe": "Abonnement opzeggen"
},
"user_profile": {
"field_validated": "Link geverifieerd",
"profile_does_not_exist": "Sorry, dit profiel bestaat niet.",
"profile_loading_error": "Sorry, er is een fout opgetreden bij het laden van dit profiel.",
"timeline_title": "Gebruikerstijdlijn"

View file

@ -16,12 +16,15 @@
"accept_desc": "Este domínio aceita apenas mensagens dos seguintes domínios:",
"ftl_removal": "Remoção da cronologia da \"Rede conhecida por todos\"",
"ftl_removal_desc": "Este domínio remove os seguintes domínios da cronologia \"Rede conhecida por todos\":",
"instance": "Domínio",
"media_nsfw": "Forçar definição de multimédia como Sensível",
"media_nsfw_desc": "Este domínio força a multimédia a ser marcada como sensível nos seguintes domínios:",
"media_removal": "Remoção de multimédia",
"media_removal_desc": "Este domínio remove multimédia das publicações dos seguintes domínios:",
"not_applicable": "N/A",
"quarantine": "Quarentena",
"quarantine_desc": "Este domínio apenas irá publicar nos seguintes domínios:",
"reason": "Razão",
"reject": "Rejeitar",
"reject_desc": "Este domínio não aceitará mensagens dos seguintes domínios:",
"simple_policies": "Políticas especificas do domínio"
@ -29,6 +32,26 @@
},
"staff": "Staff"
},
"announcements": {
"all_day_prompt": "Este evento dura o dia inteiro",
"cancel_edit_action": "Cancelar",
"close_error": "Fechar",
"delete_action": "Apagar",
"edit_action": "Editar",
"end_time_display": "Termina às {time}",
"inactive_message": "Este anúncio está inativo",
"mark_as_read_action": "Marcar como lido",
"page_header": "Anúncios",
"post_action": "Publicar",
"post_error": "Erro: {error}",
"post_form_header": "Publicar anúncio",
"post_placeholder": "Conteúdo do anúncio",
"published_time_display": "Publicado às {time}",
"start_time_display": "Começa às {time}",
"start_time_prompt": "Hora de início: ",
"submit_edit_action": "Enviar",
"title": "Anúncio"
},
"chats": {
"chats": "Chats",
"delete": "Apagar",
@ -58,6 +81,7 @@
"keep_open": "Manter o seletor aberto",
"load_all": "A carregar todos os {emojiAmount} emojis",
"load_all_hint": "Carregado o primeiro emoji {saneAmount}, carregar todos os emojis pode causar problemas de desempenho.",
"recent": "Usado recentemente",
"search_emoji": "Pesquisar por um emoji",
"stickers": "Autocolantes",
"unicode": "Emoji Unicode"
@ -801,4 +825,4 @@
"more": "Mais",
"who_to_follow": "Quem seguir"
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -19,7 +19,8 @@ const saveImmedeatelyActions = [
'setOption',
'setClientData',
'setToken',
'clearToken'
'clearToken',
'emojiUsed',
]
const defaultStorage = (() => {

View file

@ -0,0 +1,23 @@
const timelineVisibleUnauthenticated = (state, timeline) => (
state.instance.publicTimelineVisibility[timeline] ?? false
);
const currentUser = (state) => state.users.currentUser;
const currentUserOrTimelineVisibleUnauthenticated = (state, timeline) => (
currentUser(state) || timelineVisibleUnauthenticated(state, timeline)
);
const federatedTimelineAvailable = (state) => state.instance.federatedTimelineAvailable;
export const federatedTimelineVisible = (state) => (
federatedTimelineAvailable(state) && currentUserOrTimelineVisibleUnauthenticated(state, 'federated')
);
export const publicTimelineVisible = (state) => (
currentUserOrTimelineVisibleUnauthenticated(state, 'local')
);
export const bubbleTimelineVisible = (state) => (
state.instance.localBubbleInstances.length > 0 && currentUserOrTimelineVisibleUnauthenticated(state, 'bubble')
);

View file

@ -21,6 +21,8 @@ import postStatusModule from './modules/postStatus.js'
import announcementsModule from './modules/announcements.js'
import editStatusModule from './modules/editStatus.js'
import statusHistoryModule from './modules/statusHistory.js'
import tagModule from './modules/tags.js'
import recentEmojisModule from './modules/recentEmojis.js'
import { createI18n } from 'vue-i18n'
@ -46,11 +48,23 @@ const persistedStateOptions = {
paths: [
'config',
'users.lastLoginName',
'oauth'
'oauth',
'recentEmojis.emojis',
]
};
(async () => {
if ('serviceWorker' in navigator) {
// declaring scope manually
navigator.serviceWorker.register('/sw-pleroma.js', {scope: '/'}).then((registration) => {
console.log('Service worker registration succeeded:', registration);
}, /*catch*/ (error) => {
console.error(`Service worker registration failed: ${error}`);
});
} else {
console.error('Service workers are not supported.');
}
let storageError = false
const plugins = [pushNotifications]
try {
@ -85,7 +99,9 @@ const persistedStateOptions = {
postStatus: postStatusModule,
announcements: announcementsModule,
editStatus: editStatusModule,
statusHistory: statusHistoryModule
statusHistory: statusHistoryModule,
tags: tagModule,
recentEmojis: recentEmojisModule,
},
plugins,
strict: false // Socket modifies itself, let's ignore this for now.

View file

@ -1,18 +1,29 @@
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import { WSConnectionStatus } from '../services/api/api.service.js'
import { map } from 'lodash'
const retryTimeout = (multiplier) => 1000 * multiplier
const isVisible = (store, message, visibility) => {
if (visibility === 'all') {
if (visibility == 'all') {
return true
} else if (visibility === 'following') {
return store.getters.relationship(message.in_reply_to_user_id).following
} else if (visibility === 'self') {
}
if (visibility == 'following') {
if (message.in_reply_to_user_id === null) {
return true
} else {
return store.getters.relationship(message.in_reply_to_user_id).following
}
}
if (visibility == 'self') {
return message.in_reply_to_user_id === store.rootState.users.currentUser.id
}
return false
}
const api = {
state: {
retryMultiplier: 1,
@ -40,9 +51,6 @@ const api = {
setSocket (state, socket) {
state.socket = socket
},
setFollowRequests (state, value) {
state.followRequests = value
},
setMastoUserSocketStatus (state, value) {
state.mastoUserSocketStatus = value
},
@ -51,6 +59,15 @@ const api = {
},
resetRetryMultiplier (state) {
state.retryMultiplier = 1
},
setFollowRequests (state, value) {
state.followRequests = [...value]
},
saveFollowRequests (state, requests) {
state.followRequests = [...state.followRequests, ...requests]
},
saveFollowRequestPagination (state, pagination) {
state.followRequestsPagination = pagination
}
},
actions: {
@ -240,24 +257,22 @@ const api = {
...rest
})
},
// Follow requests
startFetchingFollowRequests (store) {
if (store.state.fetchers['followRequests']) return
const fetcher = store.state.backendInteractor.startFetchingFollowRequests({ store })
store.commit('addFetcher', { fetcherName: 'followRequests', fetcher })
},
stopFetchingFollowRequests (store) {
const fetcher = store.state.fetchers.followRequests
if (!fetcher) return
store.commit('removeFetcher', { fetcherName: 'followRequests', fetcher })
},
removeFollowRequest (store, request) {
let requests = store.state.followRequests.filter((it) => it !== request)
let requests = [...store.state.followRequests].filter((it) => it.id !== request.id)
store.commit('setFollowRequests', requests)
},
fetchFollowRequests ({ rootState, commit }) {
const pagination = rootState.api.followRequestsPagination
return rootState.api.backendInteractor.getFollowRequests({ pagination })
.then((requests) => {
if (requests.data.length > 0) {
commit('addNewUsers', requests.data)
commit('saveFollowRequests', requests.data)
commit('saveFollowRequestPagination', requests.pagination)
}
return requests
})
},
// Lists
startFetchingLists (store) {
if (store.state.fetchers['lists']) return

View file

@ -117,7 +117,8 @@ export const defaultState = {
maxDepthInThread: undefined, // instance default
translationLanguage: undefined, // instance default,
supportedTranslationLanguages: {}, // instance default
userProfileDefaultTab: 'statuses'
userProfileDefaultTab: 'statuses',
useBlurhash: true,
}
// caching the instance default properties

View file

@ -178,7 +178,7 @@ const instance = {
async getCustomEmoji ({ commit, state }) {
try {
const res = await window.fetch('/api/pleroma/emoji.json')
const res = await window.fetch('/api/v1/pleroma/emoji')
if (res.ok) {
const result = await res.json()
const values = Array.isArray(result) ? Object.assign({}, ...result) : result

View file

@ -186,7 +186,7 @@ const interfaceMod = {
if (thirdColumnMode === 'none' || !rootState.users.currentUser) {
commit('setLayoutType', normalOrMobile)
} else {
const wideLayout = width >= 1300
const wideLayout = width >= 1280
commit('setLayoutType', wideLayout ? 'wide' : normalOrMobile)
}
},

View file

@ -0,0 +1,50 @@
// each row is 7 emojis, 6 rows chosen arbitrarily. i don't think more than
// that are going to be useful.
const RECENT_MAX = 7 * 6
const defaultState = {
emojis: [],
}
const recentEmojis = {
state: defaultState,
mutations: {
emojiUsed ({ emojis }, emoji) {
if (emoji.displayText === undefined || emoji.displayText === null) {
console.error('emojiUsed was called with a bad emoji object: ', emoji)
return
} else if (emoji.displayText.includes('@')) {
console.error('emojiUsed was called with a remote emoji: ', emoji)
return
}
const i = emojis.indexOf(emoji.displayText)
if (i === -1) {
// not in `emojis` yet, insert and truncate if necessary
const newLength = emojis.unshift(emoji.displayText)
if (newLength > RECENT_MAX) {
emojis.pop()
}
} else if (i !== 0) {
// emoji is already in `emojis` but needs to be bumped to the top
emojis.splice(i, 1)
emojis.unshift(emoji.displayText)
}
},
},
getters: {
recentEmojis: (state, getters, rootState) => state.emojis.reduce((objects, displayText) => {
const allEmojis = rootState.instance.emoji.concat(rootState.instance.customEmoji)
let emojiObject = allEmojis.find(emoji => emoji.displayText === displayText)
if (emojiObject !== undefined) {
objects.push(emojiObject)
}
return objects
}, []),
},
}
export default recentEmojis

43
src/modules/tags.js Normal file
View file

@ -0,0 +1,43 @@
import { merge } from 'lodash'
const tags = {
state: {
// Contains key = name, value = tag json
tags: {}
},
getters: {
findTag: state => query => {
const result = state.tags[query]
return result
},
},
mutations: {
setTag (state, { name, data }) {
state.tags[name] = data
}
},
actions: {
getTag ({ rootState, commit }, tagName) {
return rootState.api.backendInteractor.getHashtag({ tag: tagName }).then(tag => {
commit('setTag', { name: tagName, data: tag })
return tag
})
},
followTag ({ rootState, commit }, tagName) {
return rootState.api.backendInteractor.followHashtag({ tag: tagName })
.then((resp) => {
commit('setTag', { name: tagName, data: resp })
return resp
})
},
unfollowTag ({ rootState, commit }, tagName) {
return rootState.api.backendInteractor.unfollowHashtag({ tag: tagName })
.then((resp) => {
commit('setTag', { name: tagName, data: resp })
return resp
})
}
}
}
export default tags

View file

@ -5,9 +5,9 @@ import { compact, map, each, mergeWith, last, concat, uniq, isArray } from 'loda
import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js'
// TODO: Unify with mergeOrAdd in statuses.js
export const mergeOrAdd = (arr, obj, item) => {
export const mergeOrAdd = (arr, obj, item, key = 'id') => {
if (!item) { return false }
const oldItem = obj[item.id]
const oldItem = obj[item[key]]
if (oldItem) {
// We already have this, so only merge the new info.
mergeWith(oldItem, item, mergeArrayLength)
@ -15,7 +15,7 @@ export const mergeOrAdd = (arr, obj, item) => {
} else {
// This is a new item, prepare it
arr.push(item)
obj[item.id] = item
obj[item[key]] = item
if (item.screen_name && !item.screen_name.includes('@')) {
obj[item.screen_name.toLowerCase()] = item
}
@ -157,6 +157,14 @@ export const mutations = {
const user = state.usersObject[id]
user.followerIds = uniq(concat(user.followerIds || [], followerIds))
},
saveFollowedTagIds (state, { id, followedTagIds }) {
const user = state.usersObject[id]
user.followedTagIds = uniq(concat(user.followedTagIds || [], followedTagIds))
},
saveFollowedTagPagination (state, { id, pagination }) {
const user = state.usersObject[id]
user.followedTagPagination = pagination
},
// Because frontend doesn't have a reason to keep these stuff in memory
// outside of viewing someones user profile.
clearFriends (state, userId) {
@ -171,6 +179,12 @@ export const mutations = {
user['followerIds'] = []
}
},
clearFollowedTags (state, userId) {
const user = state.usersObject[userId]
if (user) {
user['followedTagIds'] = []
}
},
addNewUsers (state, users) {
each(users, (user) => {
if (user.relationship) {
@ -251,6 +265,12 @@ export const mutations = {
signUpFailure (state, errors) {
state.signUpPending = false
state.signUpErrors = errors
},
decrementFollowRequestsCount (store) {
store.currentUser.follow_requests_count--
},
incrementFollowRequestsCount (store) {
store.currentUser.follow_requests_count++
}
}
@ -271,7 +291,7 @@ export const getters = {
relationship: state => id => {
const rel = id && state.relationships[id]
return rel || { id, loading: true }
}
},
}
export const defaultState = {
@ -282,7 +302,9 @@ export const defaultState = {
usersObject: {},
signUpPending: false,
signUpErrors: [],
relationships: {}
relationships: {},
tags: [],
tagsObject: {}
}
const users = {
@ -402,12 +424,27 @@ const users = {
return followers
})
},
fetchFollowedTags ({ rootState, commit }, id) {
const user = rootState.users.usersObject[id]
const pagination = user.followedTagPagination
return rootState.api.backendInteractor.getFollowedHashtags({ pagination })
.then(({ data: tags, pagination }) => {
each(tags, tag => commit('setTag', { name: tag.name, data: tag }))
commit('saveFollowedTagIds', { id, followedTagIds: tags.map(tag => tag.name) })
commit('saveFollowedTagPagination', { id, pagination })
return tags
})
},
clearFriends ({ commit }, userId) {
commit('clearFriends', userId)
},
clearFollowers ({ commit }, userId) {
commit('clearFollowers', userId)
},
clearFollowedTags ({ commit }, userId) {
commit('clearFollowedTags', userId)
},
subscribeUser ({ rootState, commit }, id) {
return rootState.api.backendInteractor.subscribeUser({ id })
.then((relationship) => commit('updateUserRelationship', [relationship]))
@ -473,6 +510,12 @@ const users = {
store.commit('setUserForNotification', notification)
})
},
decrementFollowRequestsCount (store) {
store.commit('decrementFollowRequestsCount')
},
incrementFollowRequestsCount (store) {
store.commit('incrementFollowRequestsCount')
},
searchUsers ({ rootState, commit }, { query }) {
return rootState.api.backendInteractor.searchUsers({ query })
.then((users) => {
@ -489,9 +532,17 @@ const users = {
let data = await rootState.api.backendInteractor.register(
{ params: { ...userInfo } }
)
store.commit('signUpSuccess')
store.commit('setToken', data.access_token)
store.dispatch('loginUser', data.access_token)
if (data.identifier === 'awaiting_approval' || data.identifier === 'missing_confirmed_email') {
store.commit('signUpSuccess')
return data
} else if (data.me !== undefined) {
store.commit('signUpSuccess')
store.commit('setToken', data.access_token)
store.dispatch('loginUser', data.access_token)
return data
} else {
store.commit('signUpFailure', data)
}
} catch (e) {
let errors = e.message
store.commit('signUpFailure', errors)
@ -528,7 +579,6 @@ const users = {
store.dispatch('stopFetchingTimeline', 'friends')
store.commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
store.dispatch('stopFetchingNotifications')
store.dispatch('stopFetchingFollowRequests')
store.dispatch('stopFetchingConfig')
store.commit('clearNotifications')
store.commit('resetStatuses')
@ -587,13 +637,16 @@ const users = {
// Get user mutes
store.dispatch('fetchMutes')
store.dispatch('setLayoutWidth', windowWidth())
store.dispatch('setLayoutHeight', windowHeight())
store.dispatch('getSupportedTranslationlanguages')
store.dispatch('getSettingsProfile')
store.dispatch('listSettingsProfiles')
store.dispatch('startFetchingConfig')
store.dispatch('startFetchingAnnouncements')
if (user.role === 'admin' || user.role === 'moderator') {
store.dispatch('startFetchingReports')
}
// Fetch our friends
store.rootState.api.backendInteractor.fetchFriends({ id: user.id })

View file

@ -1,6 +1,7 @@
import { each, map, concat, last, get } from 'lodash'
import { parseStatus, parseSource, parseUser, parseNotification, parseReport, parseAttachment, parseLinkHeaderPagination } from '../entity_normalizer/entity_normalizer.service.js'
import { RegistrationError, StatusCodeError } from '../errors/errors'
import { Url } from 'url'
/* eslint-env browser */
const MUTES_IMPORT_URL = '/api/pleroma/mutes_import'
@ -11,11 +12,11 @@ const CHANGE_EMAIL_URL = '/api/pleroma/change_email'
const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
const MOVE_ACCOUNT_URL = '/api/pleroma/move_account'
const ALIASES_URL = '/api/pleroma/aliases'
const TAG_USER_URL = '/api/pleroma/admin/users/tag'
const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}`
const ACTIVATE_USER_URL = '/api/pleroma/admin/users/activate'
const DEACTIVATE_USER_URL = '/api/pleroma/admin/users/deactivate'
const ADMIN_USERS_URL = '/api/pleroma/admin/users'
const TAG_USER_URL = '/api/v1/pleroma/admin/users/tag'
const PERMISSION_GROUP_URL = (screenName, right) => `/api/v1/pleroma/admin/users/${screenName}/permission_group/${right}`
const ACTIVATE_USER_URL = '/api/v1/pleroma/admin/users/activate'
const DEACTIVATE_USER_URL = '/api/v1/pleroma/admin/users/deactivate'
const ADMIN_USERS_URL = '/api/v1/pleroma/admin/users'
const SUGGESTIONS_URL = '/api/v1/suggestions'
const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings'
const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read'
@ -108,6 +109,10 @@ const PLEROMA_EDIT_ANNOUNCEMENT_URL = id => `/api/v1/pleroma/admin/announcements
const PLEROMA_DELETE_ANNOUNCEMENT_URL = id => `/api/v1/pleroma/admin/announcements/${id}`
const AKKOMA_SETTING_PROFILE_URL = (name) => `/api/v1/akkoma/frontend_settings/pleroma-fe/${name}`
const AKKOMA_SETTING_PROFILE_LIST = `/api/v1/akkoma/frontend_settings/pleroma-fe`
const MASTODON_TAG_URL = (name) => `/api/v1/tags/${name}`
const MASTODON_FOLLOW_TAG_URL = (name) => `/api/v1/tags/${name}/follow`
const MASTODON_UNFOLLOW_TAG_URL = (name) => `/api/v1/tags/${name}/unfollow`
const MASTODON_FOLLOWED_TAGS_URL = '/api/v1/followed_tags'
const oldfetch = window.fetch
@ -243,7 +248,7 @@ const register = ({ params, credentials }) => {
})
}
const getCaptcha = () => fetch('/api/pleroma/captcha').then(resp => resp.json())
const getCaptcha = () => fetch('/api/v1/pleroma/captcha').then(resp => resp.json())
const authHeaders = (accessToken) => {
if (accessToken) {
@ -401,14 +406,6 @@ const fetchFollowers = ({ id, maxId, sinceId, limit = 20, credentials }) => {
.then((data) => data.json())
.then((data) => data.map(parseUser))
}
const fetchFollowRequests = ({ credentials }) => {
const url = MASTODON_FOLLOW_REQUESTS_URL
return fetch(url, { headers: authHeaders(credentials) })
.then((data) => data.json())
.then((data) => data.map(parseUser))
}
const fetchLists = ({ credentials }) => {
const url = MASTODON_LISTS_URL
return fetch(url, { headers: authHeaders(credentials) })
@ -650,7 +647,7 @@ const getReports = ({ state, limit, page, pageSize, credentials }) => {
url = url + (args ? '?' + args : '')
return fetch(url, { headers: authHeaders(credentials) })
.then((data) => data.json())
.then((data) => data.reports.map(parseReport))
.then((data) => data?.reports?.map(parseReport) ?? [])
}
const updateReportStates = ({ credentials, reports }) => {
@ -875,7 +872,8 @@ const postStatus = ({
quoteId,
contentType,
preview,
idempotencyKey
idempotencyKey,
language
}) => {
const form = new FormData()
const pollOptions = poll.options || []
@ -886,6 +884,7 @@ const postStatus = ({
if (visibility) form.append('visibility', visibility)
if (sensitive) form.append('sensitive', sensitive)
if (contentType) form.append('content_type', contentType)
if (language) form.append('language', language)
mediaIds.forEach(val => {
form.append('media_ids[]', val)
})
@ -1332,7 +1331,7 @@ const fetchEmojiReactions = ({ id, credentials }) => {
const reactWithEmoji = ({ id, emoji, credentials }) => {
return promisedRequest({
url: PLEROMA_EMOJI_REACT_URL(id, emoji),
url: PLEROMA_EMOJI_REACT_URL(id, encodeURIComponent(emoji)),
method: 'PUT',
credentials
}).then(parseStatus)
@ -1340,7 +1339,7 @@ const reactWithEmoji = ({ id, emoji, credentials }) => {
const unreactWithEmoji = ({ id, emoji, credentials }) => {
return promisedRequest({
url: PLEROMA_EMOJI_UNREACT_URL(id, emoji),
url: PLEROMA_EMOJI_UNREACT_URL(id, encodeURIComponent(emoji)),
method: 'DELETE',
credentials
}).then(parseStatus)
@ -1549,6 +1548,71 @@ const listSettingsProfiles = ({ credentials }) => {
})
}
const getHashtag = ({ tag, credentials }) => {
return promisedRequest({
url: MASTODON_TAG_URL(tag),
credentials
})
}
const followHashtag = ({ tag, credentials }) => {
return promisedRequest({
url: MASTODON_FOLLOW_TAG_URL(tag),
method: 'POST',
credentials
})
}
const unfollowHashtag = ({ tag, credentials }) => {
return promisedRequest({
url: MASTODON_UNFOLLOW_TAG_URL(tag),
method: 'POST',
credentials
})
}
const getFollowedHashtags = ({ credentials, pagination: savedPagination }) => {
const queryParams = new URLSearchParams()
if (savedPagination?.maxId) {
queryParams.append('max_id', savedPagination.maxId)
}
const url = `${MASTODON_FOLLOWED_TAGS_URL}?${queryParams.toString()}`
let pagination = {};
return fetch(url, {
credentials
}).then((data) => {
pagination = parseLinkHeaderPagination(data.headers.get('Link'), {
flakeId: false
});
return data.json()
}).then((data) => {
return {
pagination,
data
}
});
}
const getFollowRequests = ({ credentials, pagination: savedPagination }) => {
const queryParams = new URLSearchParams()
if (savedPagination?.maxId) {
queryParams.append('max_id', savedPagination.maxId)
}
const url = `${MASTODON_FOLLOW_REQUESTS_URL}?${queryParams.toString()}`
let pagination = {};
return fetch(url, {
credentials
}).then((data) => {
pagination = parseLinkHeaderPagination(data.headers.get('Link'), { flakeId: true });
return data.json()
}).then((data) => {
return {
pagination,
data: data.map(parseUser)
}
});
}
export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => {
return Object.entries({
...(credentials
@ -1738,7 +1802,6 @@ const apiService = {
mfaConfirmOTP,
addBackup,
listBackups,
fetchFollowRequests,
fetchLists,
createList,
getList,
@ -1784,7 +1847,12 @@ const apiService = {
getReports,
updateReportStates,
addNoteToReport,
deleteNoteFromReport
deleteNoteFromReport,
getHashtag,
followHashtag,
unfollowHashtag,
getFollowedHashtags,
getFollowRequests
}
export default apiService

Some files were not shown because too many files have changed in this diff Show more