Compare commits

...

73 Commits

Author SHA1 Message Date
floatingghost ed0b403c33 Merge pull request 'Auto-approve followbacks (frontend part)' (#365) from Oneric/akkoma-fe:followbacks-fe into develop
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: AkkomaGang/akkoma-fe#365
2024-02-20 16:24:37 +00:00
floatingghost 0f842b300b Merge pull request 'Display profile background of other users' (#371) from Oneric/akkoma-fe:profile-backgrounds into develop
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: AkkomaGang/akkoma-fe#371
2024-02-20 16:20:14 +00:00
floatingghost 865cb6f96a Merge pull request 'Add Indonesian translation' (#366) from leap123/akkoma-fe:leap123-patch-1 into develop
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: AkkomaGang/akkoma-fe#366
2024-02-19 14:04:34 +00:00
Oneric 050c7df2e6 Display profile background of other users
ci/woodpecker/pr/woodpecker Pipeline was successful Details
And add a new frontend setting to hide other people's background.
2024-02-14 18:44:57 +01:00
Oneric a77a9e04d9 Expose new server-side permit_followback setting
ci/woodpecker/pr/woodpecker Pipeline was successful Details
Added to backend in AkkomaGang/akkoma#674
2024-02-04 22:19:14 +01:00
leap123 a57334991e Add Indonesian translation
ci/woodpecker/pr/woodpecker Pipeline was successful Details
The Indonesian translation is technically almost complete, just not added to messages.js
2024-01-19 04:27:26 +00:00
floatingghost 8dce31d0ad Merge pull request 'Improve UX of subject / Content Warning field' (#362) from hazelnoot/akkoma-fe:develop into develop
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: AkkomaGang/akkoma-fe#362
2023-12-20 18:49:40 +00:00
Hazel Koehler ea9ad4d600 fix "always show content warning" setting
ci/woodpecker/pr/woodpecker Pipeline was successful Details
2023-12-20 12:39:31 -05:00
Hazel Koehler 34e2800f59 add button to toggle the spoiler / CW field
ci/woodpecker/pr/woodpecker Pipeline was successful Details
2023-12-16 14:44:26 -05:00
Hazel Koehler 3d65eccf04 use main emoji button for spoiler / CW field 2023-12-16 13:37:59 -05:00
floatingghost d304be654f Merge pull request 'Update build setup instructions' (#343) from norm/pleroma-fe:update-build-setup into develop
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: AkkomaGang/akkoma-fe#343
2023-12-15 12:24:33 +00:00
floatingghost aee97fa948 Merge pull request 'Re-added extension checking for still-image' (#346) from Mergan/pleroma-fe:still-image-ultimate into develop
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: AkkomaGang/akkoma-fe#346
2023-12-15 12:24:07 +00:00
floatingghost 7da1687f31 Merge pull request 'Use relative font size and set appropriate overflow behavior' (#355) from xarvos/pleroma-fe:update-css into develop
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: AkkomaGang/akkoma-fe#355
2023-12-15 12:12:28 +00:00
floatingghost a8f193d4bd Merge pull request 'Stop constant movement of notifications due to changing timestamps' (#353) from Oneric/akkoma-fe:notification-writhing into develop
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: AkkomaGang/akkoma-fe#353
2023-12-15 11:57:47 +00:00
floatingghost 81c82e11bc Merge pull request 'Explicitly set SameSite attribute for cookies' (#352) from Oneric/akkoma-fe:cookie-samesite into develop
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: AkkomaGang/akkoma-fe#352
2023-12-15 11:54:15 +00:00
floatingghost 00cadce5b4 Merge pull request 'Format dates, times with window.navigator.language instead of UI i18n locale' (#354) from smitten/akkoma-fe:date-locale-fix-cherrypick into develop
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: AkkomaGang/akkoma-fe#354
2023-12-15 11:52:59 +00:00
floatingghost 40a08f279b Merge pull request 'Drop broken "@ symbol as icon" setting' (#359) from Oneric/akkoma-fe:at-icon into develop
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: AkkomaGang/akkoma-fe#359
2023-11-16 10:41:17 +00:00
Oneric c524a47e6f Drop broken "@ symbol as icon" setting
ci/woodpecker/pr/woodpecker Pipeline was successful Details
It was merged into pleroma-fe on 2022-02-03 in
76547fe66d and imported
into akkoma-fe on 2022-06-08 with the merge commit
f6cf509a04.

However, something went wrong in the merge and while the setting
and its infrastructure exist, it is never used anywhere and @ is
always displayed as text.

Given it existed in this broken state for nearly one and a half years,
never worked on akkoma-fe and no bugs were filed about this, it appears
nobody cares, so let’s just remove it.
2023-11-15 23:36:19 +01:00
Ngô Ngọc Đức Huy 235c734d37
Use overflow: auto for description
ci/woodpecker/pr/woodpecker Pipeline was successful Details
Previously it sets overflow: scroll, so there's an unnecessary
horizontal scroll.
overflow: auto only shows scrollbar when it overflows
2023-11-05 09:32:19 +07:00
Ngô Ngọc Đức Huy deaef1d0b9
Use relative unit for font size 2023-11-05 09:21:01 +07:00
smitten 1b28ec3b72
Match UI i18n configuration to browser locales
ci/woodpecker/pr/woodpecker Pipeline was successful Details
2023-11-01 23:10:57 -04:00
smitten c9dc8f00f9
Use window.navigator.language before interface i18n language
ci/woodpecker/pr/woodpecker Pipeline was successful Details
2023-10-30 23:56:53 -04:00
Oneric beee99e733 Stop notifications boxes from change size over time
ci/woodpecker/pr/woodpecker Pipeline was successful Details
Notifications about favourites and follows use .notification-right,
notifications about replies instead use .heading-right.

Previously only the former set a min-width, however the
chosen value of 3em was too small to fit the worst case.
As a consequence, when the timestamp text changes over time,
its element width changes, which may result in neighbouring text
(no longer) needing to wrap to a new line in turn changing the size
of the whole notification box pushing older notification boxes down/up.

These constant movements at the side of the screen can be quite
annoying and confusing when the cause cannot be immediately discerned.

Avoid this, by reserving enough space for any timestamp.

For English, the worst case is the five-character 'XXmin', since the
short identifier for minutes is the longest with three letters.
With two exceptions, all other current localisation also do not exceed
three letters in any short unit identifier up to days.
However, some localisations (e.g. Polish) additionally insert a space
between numerical value and unit. This matches SI recommendations
pushing the worst case to 6 characters.

6 characters will be sufficient for timestamps up to 3 weeks in all
languages (minus prev exceptions), which seems reasonable enough
as beyond this timestamps rarely change anyway.

The aforementioned exceptions being Vietnamese and Occitan,
but in the current localisation all or the relevant short unit
identifiers are identical to the long forms indicating this is
just due to incomplete translation.
Indeed, Vietnamese Wikipedia (read through machine translation) suggests
“ph” is commonly used as unit identifiers for minutes, but the current
localisation fully spells it out as “phút”.
2023-10-25 00:37:09 +02:00
Oneric ccb0ffdc8a Don't show direction in notification timestamps
Currently all notifications except follow-related once include
and explicit direction text. (It missing in follow notifs is due to an
omission in 804ba0cdb6b353e0c959c68f44c6a1316c0d6b10 which only added
the newly introduced with-direction to status-related notifs. Before,
presumably all notifs included direction text.)

But in the notification tray horizontal space is scarce
and notifs can already be assumed to only come from the past.
While it might not be too bad for the English localisation’s 4-letter
' ago' suffix, e.g. the Indonesian localisation’s ' yang lalu' needs
10 letters.

Thus instead of fixing the omission for follow-related notifs,
drop direction text from all notification timestamps.
2023-10-24 23:28:45 +02:00
Oneric ab250c2f3a Explicitly set SameSite attribute for cookies
ci/woodpecker/pr/woodpecker Pipeline was successful Details
Modern browsers start to tighten down on third-party access to cookies.
E.g. in current Firefox, a warning about the userLanguage cookie was
shown since it did not yet explicitly set the SameSite attribute and the
default is about to change.

The cookie name being referred to as BACKEND_LANGUAGE_COOKIE_NAME
suggests it should be readable by the actual Akkoma backend, which can
live at a different domain than akkoma-fe. Thus explicitly enable
sharing with third-party sites.

No warnings were shown for other cookies, so I assume
this was the only one not yet setting SameSite.
2023-10-19 01:05:59 +02:00
Norm 1de62fffcd
Update config.example.json link and example domain
ci/woodpecker/pr/woodpecker Pipeline was successful Details
2023-10-06 04:52:04 -04:00
Norm 306cea04a1
Use corepack in build instructions 2023-10-06 04:51:59 -04:00
Mergan d9e1bc4d99 Re-added extension checking for still-image
ci/woodpecker/pr/woodpecker Pipeline was successful Details
- Bonus refactoring
2023-10-02 15:29:54 -07:00
FloatingGhost 52b0b6f008 add VI to messages.js
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-10-02 13:28:23 +01:00
floatingghost 8afbe5e3bc Merge pull request 'Making still-image better' (#341) from Mergan/pleroma-fe:still-image-ultimate into develop
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: AkkomaGang/akkoma-fe#341
2023-09-25 13:29:29 +00:00
floatingghost 58be48d164 Merge pull request 'Do not copy all emojis in recentEmoji getter' (#340) from sn0w/akkoma-fe:feature/optimize-recent-emojis into develop
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: AkkomaGang/akkoma-fe#340
2023-09-25 13:24:12 +00:00
Mergan 1056b89fd1 Disabled aggressive matching for reduced motion (we search for gif now)
ci/woodpecker/pr/woodpecker Pipeline was successful Details
2023-09-12 04:32:01 -07:00
Mergan 3e64d78d05 An oopsie 2023-09-12 04:17:28 -07:00
Mergan 3947aafeba Aligning canvas to image 2023-09-12 04:08:47 -07:00
Mergan 345934c2f3 Make label visible on avatar 2023-09-12 03:37:05 -07:00
Mergan 42a13b0f1b Modify label 2023-09-12 03:35:58 -07:00
Mergan e13c4b6b85 Revamped still-image 2023-09-12 02:48:53 -07:00
sn0w 6a1409e09b
Do not copy all emojis in recentEmoji getter
ci/woodpecker/pr/woodpecker Pipeline was successful Details
2023-09-03 16:19:06 +02:00
FloatingGhost 174f98b1cb don't die on my arm box please
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-08-05 14:17:42 +01:00
FloatingGhost ab146b67ec version
ci/woodpecker/push/woodpecker Pipeline failed Details
2023-08-05 13:29:44 +01:00
FloatingGhost 3b4208ea41 debounce emoji searching
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-08-04 16:48:13 +01:00
floatingghost 856324fa26 Merge pull request 'Make favicon next to post username use Still-Image functionality' (#327) from Mergan/pleroma-fe:still-image-instance-favicon into develop
ci/woodpecker/push/woodpecker Pipeline failed Details
Reviewed-on: AkkomaGang/akkoma-fe#327
2023-08-04 15:09:56 +00:00
floatingghost 5a9322d2c7 Merge pull request 'StillImage: Improved animated image detection' (#335) from yukijoou/akkoma-fe:still-image-better-gif-detection into develop
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: AkkomaGang/akkoma-fe#335
2023-08-04 15:09:32 +00:00
Weblate b52bfbcba0 Merge branch 'origin/develop' into Weblate.
ci/woodpecker/push/woodpecker Pipeline failed Details
2023-08-04 14:56:22 +00:00
floatingghost 2b05a738c9 Merge pull request 'Add replying info for redraft' (#332) from xarvos/pleroma-fe:fix-reply-redraft into develop
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: AkkomaGang/akkoma-fe#332
2023-08-04 14:56:20 +00:00
Weblate fa5d31b793 Merge branch 'origin/develop' into Weblate.
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-08-04 14:54:38 +00:00
tusooa 29cfdcbbcd Add load more to blocks/mutes
ci/woodpecker/push/woodpecker Pipeline failed Details
2023-08-04 15:54:04 +01:00
Weblate 5174b95918 Translated using Weblate (Greek)
Currently translated at 15.4% (162 of 1046 strings)

Added translation using Weblate (Greek)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: getimiskon <getimiskon@disroot.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/el/
Translation: Pleroma fe/pleroma-fe
2023-08-04 13:34:26 +00:00
Weblate 56528206b8 Translated using Weblate (Portuguese)
Currently translated at 62.7% (656 of 1046 strings)

Co-authored-by: Jonathan Soares <jontix@murena.io>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/pt/
Translation: Pleroma fe/pleroma-fe
2023-08-04 13:34:26 +00:00
Weblate 590380c084 Translated using Weblate (Thai)
Currently translated at 1.7% (18 of 1046 strings)

Added translation using Weblate (Thai)

Co-authored-by: Chanakan Mungtin <chanakan5591@chanakancloud.net>
Co-authored-by: Pongsatorn Paewsoongnern <akkoma@miraiverse.me>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/th/
Translation: Pleroma fe/pleroma-fe
2023-08-04 13:34:26 +00:00
Weblate dfcbb3c1ec Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1046 of 1046 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-08-04 13:34:26 +00:00
Weblate 9b01c31283 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1046 of 1046 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.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-08-04 13:34:26 +00:00
Weblate 6be003b2f8 Translated using Weblate (French)
Currently translated at 98.3% (1029 of 1046 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-08-04 13:34:26 +00:00
Weblate cc302fb0e2 Translated using Weblate (Japanese (ja_EASY))
Currently translated at 72.3% (757 of 1046 strings)

Translated using Weblate (Japanese (ja_EASY))

Currently translated at 71.9% (753 of 1046 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-08-04 13:34:26 +00:00
Weblate c4e768f977 Translated using Weblate (Indonesian)
Currently translated at 67.8% (710 of 1046 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Yonle <yonle@lecturify.net>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/id/
Translation: Pleroma fe/pleroma-fe
2023-08-04 13:34:26 +00:00
Weblate d8a7217335 Translated using Weblate (Turkish)
Currently translated at 9.7% (102 of 1046 strings)

Co-authored-by: Hasan Yıldız <hasanyildiz0@yaani.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/tr/
Translation: Pleroma fe/pleroma-fe
2023-08-04 13:34:26 +00:00
floatingghost 35d10ab9ff Merge pull request 'components: Honour 'prefers reduced motion' setting in many components' (#333) from yukijoou/akkoma-fe:fix-reduced-motion into develop
ci/woodpecker/push/woodpecker Pipeline failed Details
Reviewed-on: AkkomaGang/akkoma-fe#333
2023-08-04 13:34:23 +00:00
Yuki Joou 43d0a24547 StillImage: Improved animated image detection
ci/woodpecker/pr/woodpecker Pipeline was successful Details
This patch makes StillImage's animation detection return early in cases
where we can't detect the mimetype of the image. It also sets the image
as animated in those cases if the user agent wants reduced motion.

As reduced motion is an accessibility setting, I think it's best to use
a "better safe than sorry" approach, it's better to accidentally mark
something as animated that isn't than to have unblocked animations.
2023-08-03 16:34:28 +02:00
Yuki Joou 51ebe643d5 components: Honour 'prefers reduced motion' setting in many components
ci/woodpecker/pr/woodpecker Pipeline was successful Details
This helps accessibility for motion-sensitive people such as myself, and
can improve battery life in "battery saving" mode on most devices
2023-08-03 14:45:02 +02:00
Ngô Ngọc Đức Huy 7c14e1a5bd
Add replying info for redraft
ci/woodpecker/pr/woodpecker Pipeline was successful Details
2023-08-02 08:40:19 +07:00
David 0da0e2c814 Make favicon next to post username use Still-Image functionality
ci/woodpecker/pr/woodpecker Pipeline was successful Details
2023-07-24 01:08:11 -07:00
floatingghost af97dd7484 Merge pull request 'docs: Update Pleroma references to Akkoma' (#318) from norm/pleroma-fe:update-docs into develop
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: AkkomaGang/akkoma-fe#318
2023-07-21 19:44:25 +00:00
floatingghost 28bf597443 Merge pull request '[workaround] fix: emoji picker not scrollable on ios' (#323) from SukinoVerse/akkoma-fe:fix/emoji-picker-not-scrollable-ios into develop
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: AkkomaGang/akkoma-fe#323
2023-07-21 19:43:14 +00:00
floatingghost a249baea8c Merge pull request 'Make emoji reactions use Still-Image functionality' (#326) from Mergan/pleroma-fe:still-image-emoji-reactions into develop
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: AkkomaGang/akkoma-fe#326
2023-07-21 19:42:41 +00:00
floatingghost a4e82f7886 Merge pull request '[feat] Support prefers-reduced-motion disabling auto-play of animated images (#324)' (#325) from Mergan/pleroma-fe:support-prefers-reduced-motion into develop
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: AkkomaGang/akkoma-fe#325
2023-07-21 19:37:43 +00:00
David 32dc55b07c Make emoji reactions use Still-Image functionality
ci/woodpecker/pr/woodpecker Pipeline was successful Details
2023-07-20 17:20:54 -07:00
David 91eab22d77 Simplified and fixed and/or oopsie
ci/woodpecker/pr/woodpecker Pipeline was successful Details
2023-07-20 17:18:59 -07:00
David e2125c57d6 Turn on by default
ci/woodpecker/pr/woodpecker Pipeline was successful Details
2023-07-20 16:26:52 -07:00
David e0a6418e91 Add prefers-reduced-motion support 2023-07-20 16:14:36 -07:00
SukinoVERSΞ 14ed359c33 fix: emoji picker not scrollable on ios
ci/woodpecker/pr/woodpecker Pipeline was successful Details
2023-07-11 22:16:37 +07:00
Norm 2d387e2eb4
update more images
ci/woodpecker/pr/woodpecker Pipeline was successful Details
2023-05-26 23:02:38 -04:00
Norm 57f70371a9
use better example emoji 2023-05-26 22:59:55 -04:00
Norm 8feffbcdf6
docs: Update Pleroma references to Akkoma
ci/woodpecker/pr/woodpecker Pipeline was successful Details
2023-05-26 22:49:29 -04:00
73 changed files with 1102 additions and 367 deletions

View File

@ -1,3 +1,4 @@
platform: linux/amd64
pipeline: pipeline:
lint: lint:
when: when:

View File

@ -1,4 +1,4 @@
# Akkoma-FE # Akkoma-FE
![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) ![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)
@ -8,7 +8,7 @@ This is a fork of Akkoma-FE from the Pleroma project, with support for new Akkom
# For Translators # For Translators
The [Weblate UI](https://translate.akkoma.dev/projects/akkoma/pleroma-fe/) is recommended for adding or modifying translations for Akkoma-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. 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.
@ -22,7 +22,7 @@ To use Akkoma-FE in Akkoma, use the [frontend](https://docs.akkoma.dev/stable/ad
``` bash ``` bash
# install dependencies # install dependencies
npm install -g yarn corepack enable
yarn yarn
# serve with hot reload at localhost:8080 # serve with hot reload at localhost:8080
@ -37,7 +37,7 @@ npm run unit
# For Contributors: # For Contributors:
You can create file `/config/local.json` (see [example](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/config/local.example.json)) to enable some convenience dev options: You can create file `/config/local.json` (see [example](https://akkoma.dev/AkkomaGang/akkoma-fe/src/branch/develop/config/local.example.json)) to enable some convenience dev options:
* `target`: makes local dev server redirect to some existing instance's BE instead of local BE, useful for testing things in near-production environment and searching for real-life use-cases. * `target`: makes local dev server redirect to some existing instance's BE instead of local BE, useful for testing things in near-production environment and searching for real-life use-cases.
* `staticConfigPreference`: makes FE's `/static/config.json` take preference of BE-served `/api/statusnet/config.json`. Only works in dev mode. * `staticConfigPreference`: makes FE's `/static/config.json` take preference of BE-served `/api/statusnet/config.json`. Only works in dev mode.
@ -52,4 +52,4 @@ Edit config.json for configuration.
### Login methods ### 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

@ -1,4 +1,4 @@
{ {
"target": "https://pleroma.soykaf.com/", "target": "https://otp.akkoma.dev/",
"staticConfigPreference": false "staticConfigPreference": false
} }

View File

@ -1,15 +1,15 @@
# Pleroma-FE configuration and customization for instance administrators # Akkoma-FE configuration and customization for instance administrators
* *For user configuration, see [Pleroma-FE user guide](../user_guide)* * *For user configuration, see [Akkoma-FE user guide](../user_guide)*
* *For local development server configuration, see [Hacking, tweaking, contributing](HACKING.md)* * *For local development server configuration, see [Hacking, tweaking, contributing](HACKING.md)*
## Where configuration is stored ## Where configuration is stored
PleromaFE gets its configuration from several sources, in order of preference (the one above overrides ones below it) Akkoma-FE gets its configuration from several sources, in order of preference (the one above overrides ones below it)
1. `/api/statusnet/config.json` - this is generated on Backend and contains multiple things including instance name, char limit etc. It also contains FE/Client-specific data, PleromaFE uses `pleromafe` field of it. For more info on changing config on BE, look [here](https://docs.akkoma.dev/stable/configuration/cheatsheet.md#frontend_configurations) 1. `/api/statusnet/config.json` - this is generated on Backend and contains multiple things including instance name, char limit etc. It also contains FE/Client-specific data, Akkoma-FE uses `pleromafe` field of it. For more info on changing config on BE, look [here](https://docs.akkoma.dev/stable/configuration/cheatsheet.md#frontend_configurations)
2. `/static/config.json` - this is a static FE-provided file, containing only FE specific configuration. This file is completely optional and could be removed but is useful as a fallback if some configuration JSON property isn't present in BE-provided config. It's also a reference point to check what default configuration are and what JSON properties even exist. In local dev mode it could be used to override BE configuration, more about that in HACKING.md. File is located [here](https://akkoma.dev/AkkomaGang/pleroma-fe/src/branch/develop/static/config.json). 2. `/static/config.json` - this is a static FE-provided file, containing only FE specific configuration. This file is completely optional and could be removed but is useful as a fallback if some configuration JSON property isn't present in BE-provided config. It's also a reference point to check what default configuration are and what JSON properties even exist. In local dev mode it could be used to override BE configuration, more about that in HACKING.md. File is located [here](https://akkoma.dev/AkkomaGang/akkoma-fe/src/branch/develop/static/config.json).
3. Built-in defaults. Those are hard-coded defaults that are used when `/static/config.json` is not available and BE-provided configuration JSON is missing some JSON properties. ( [Code](https://akkoma.dev/AkkomaGang/pleroma-fe/src/branch/develop/src/modules/instance.js) ) 3. Built-in defaults. Those are hard-coded defaults that are used when `/static/config.json` is not available and BE-provided configuration JSON is missing some JSON properties. ( [Code](https://akkoma.dev/AkkomaGang/akkoma-fe/src/branch/develop/src/modules/instance.js) )
## Instance-defaults ## Instance-defaults
@ -59,7 +59,7 @@ Instance `logo`, could be any image, including svg. By default it assumes logo u
`logoMargin` allows you to adjust vertical margins between logo boundary and navbar borders. The idea is that to have logo's image without any extra margins and instead adjust them to your need in layout. `logoMargin` allows you to adjust vertical margins between logo boundary and navbar borders. The idea is that to have logo's image without any extra margins and instead adjust them to your need in layout.
### `minimalScopesMode` ### `minimalScopesMode`
Limit scope selection to *Direct*, *User default* and *Scope of post replying to*. This also makes it impossible to reply to a DM with a non-DM post from PleromaFE. Limit scope selection to *Direct*, *User default* and *Scope of post replying to*. This also makes it impossible to reply to a DM with a non-DM post from Akkoma-FE.
### `nsfwCensorImage` ### `nsfwCensorImage`
Use custom image for NSFW'd images Use custom image for NSFW'd images
@ -77,7 +77,7 @@ Change alignment of sidebar and panels to the right. Defaults to `false`.
Show panel showcasing instance features/settings to logged-out visitors Show panel showcasing instance features/settings to logged-out visitors
### `showInstanceSpecificPanel` ### `showInstanceSpecificPanel`
This allows you to include arbitrary HTML content in a panel below navigation menu. PleromaFE looks for an html page `instance/panel.html`, by default it's not provided in FE, but BE bundles some [default one](https://git.pleroma.social/pleroma/pleroma/blob/develop/priv/static/instance/panel.html). De-facto instance-defaults, since user can hide instance-specific panel. This allows you to include arbitrary HTML content in a panel below navigation menu. Akkoma-FE looks for an html page `instance/panel.html`, by default it's not provided in FE, but BE bundles some [default one](https://git.pleroma.social/pleroma/pleroma/blob/develop/priv/static/instance/panel.html). De-facto instance-defaults, since user can hide instance-specific panel.
### `subjectLineBehavior` ### `subjectLineBehavior`
How to handle subject line (CW) when replying to a post. How to handle subject line (CW) when replying to a post.

View File

@ -1,8 +1,8 @@
# Hacking, tweaking, contributing # Hacking, tweaking, contributing
## What PleromaFE even is, how it works ## What Akkoma-FE even is, how it works
PleromaFE is an SPA (Single-Page Application) backed by [Vue](https://vuejs.org/) framework. It means that it's just a nearly-empty HTML page with bunch of JavaScript that actually generates and controls DOM (i.e. html elements) in Runtime. Currently, there's no way around it - you have to have Javascript enabled in the browser to make it work, there is a theoretical possibility to generate some HTML server-side but it's not implemented yet. Akkoma-FE is an SPA (Single-Page Application) backed by [Vue](https://vuejs.org/) framework. It means that it's just a nearly-empty HTML page with bunch of JavaScript that actually generates and controls DOM (i.e. html elements) in Runtime. Currently, there's no way around it - you have to have Javascript enabled in the browser to make it work, there is a theoretical possibility to generate some HTML server-side but it's not implemented yet.
You can serve static html page and everything from any HTTP(S) server but currently it will try to access /api/ path at same domain it's running on, meaning that as of right now you cannot put it on one domain and access the other without proxying requests. You can serve static html page and everything from any HTTP(S) server but currently it will try to access /api/ path at same domain it's running on, meaning that as of right now you cannot put it on one domain and access the other without proxying requests.
@ -67,19 +67,19 @@ server {
### API, Data, Operations ### API, Data, Operations
In 99% cases PleromaFE uses [MastoAPI](https://docs.joinmastodon.org/api/) with [Pleroma Extensions](https://docs.akkoma.dev/stable/differences_in_mastoapi_responses.md) to fetch the data. The rest is either QvitterAPI leftovers or pleroma-exclusive APIs. QvitterAPI doesn't exactly have documentation and uses different JSON structure and sometimes different parameters and workflows, [this](https://twitter-api.readthedocs.io/en/latest/index.html) could be a good reference though. Some pleroma-exclusive API may still be using QvitterAPI JSON structure. In 99% cases Akkoma-FE uses [MastoAPI](https://docs.joinmastodon.org/api/) with [Pleroma Extensions](https://docs.akkoma.dev/stable/differences_in_mastoapi_responses.md) to fetch the data. The rest is either QvitterAPI leftovers or pleroma-exclusive APIs. QvitterAPI doesn't exactly have documentation and uses different JSON structure and sometimes different parameters and workflows, [this](https://twitter-api.readthedocs.io/en/latest/index.html) could be a good reference though. Some pleroma-exclusive API may still be using QvitterAPI JSON structure.
PleromaFE supports both formats by transforming them into internal format which is basically QvitterAPI one with some additions and renaming. All data is passed trough [Entity Normalizer](https://git.pleroma.social/pleroma/pleroma-fe/-/blob/develop/src/services/entity_normalizer/entity_normalizer.service.js) which can serve as a reference of API and what's actually used, it's also a host for all the hacks and data transformation. Akkoma-FE supports both formats by transforming them into internal format which is basically QvitterAPI one with some additions and renaming. All data is passed trough [Entity Normalizer](https://akkoma.dev/AkkomaGang/akkoma-fe/src/branch/develop/src/services/entity_normalizer/entity_normalizer.service.js) which can serve as a reference of API and what's actually used, it's also a host for all the hacks and data transformation.
For most part, PleromaFE tries to store all the info it can get in global vuex store - every user and post are passed trough updating mechanism where data is either added or merged with existing data, reactively updating the information throughout UI, so if in newest request user's post counter increased, it will be instantly updated in open user profile cards. This is also used to find users, posts and sometimes to build timelines and/or request parameters. For most part, Akkoma-FE tries to store all the info it can get in global vuex store - every user and post are passed trough updating mechanism where data is either added or merged with existing data, reactively updating the information throughout UI, so if in newest request user's post counter increased, it will be instantly updated in open user profile cards. This is also used to find users, posts and sometimes to build timelines and/or request parameters.
PleromaFE also tries to persist this store, however only stable data is stored, such as user authentication and preferences, user highlights. Persistence is performed by saving and loading chunk of vuex store in browser's LocalStorage/IndexedDB. Akkoma-FE also tries to persist this store, however only stable data is stored, such as user authentication and preferences, user highlights. Persistence is performed by saving and loading chunk of vuex store in browser's LocalStorage/IndexedDB.
TODO: Refactor API code and document it here TODO: Refactor API code and document it here
### Themes ### Themes
PleromaFE uses custom theme "framework" which is pretty much just a style tag rendered by vue which only contains CSS3 variables. Every color used in UI should be derived from theme. Theme is stored in a JSON object containing color, opacity, shadow and font information, with most of it being optional. Akkoma-FE uses custom theme "framework" which is pretty much just a style tag rendered by vue which only contains CSS3 variables. Every color used in UI should be derived from theme. Theme is stored in a JSON object containing color, opacity, shadow and font information, with most of it being optional.
The most basic theme can consist of 4 to 8 "basic colors", which is also what previous version of themes allowed, with all other colors being derived from those basic colors, i.e. "light background" will be "background" color lightened/darkened, "panel header" will be same as "foreground". The idea is that you can specify just basic color palette and everything else will be generated automatically, but if you really need to tweak some specific color - you can. The most basic theme can consist of 4 to 8 "basic colors", which is also what previous version of themes allowed, with all other colors being derived from those basic colors, i.e. "light background" will be "background" color lightened/darkened, "panel header" will be same as "foreground". The idea is that you can specify just basic color palette and everything else will be generated automatically, but if you really need to tweak some specific color - you can.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 491 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 362.83 362.83">
<defs>
<style>
.cls-1, .cls-2 {
fill: #fff;
}
.cls-2 {
stroke: #fff;
stroke-miterlimit: 10;
}
</style>
</defs>
<g id="Layer_6" data-name="Layer 6">
<path class="cls-1" d="M115.2,131.89c6.26-6.54,20.19-20.63,42.39-26.14,15.79-3.92,28.51-1.28,33.51,0,83.72,21.41,116.03,201.78,77.79,226.32-10.28,6.6-26.86,2.7-36.77-3.3-32.63-19.78-29.3-72.87-44.44-73.73-5.11-.29-7.15,5.8-20.91,24.94-19.63,27.3-31.49,43.44-49.21,50.87-2.53,1.06-26.91,12.07-41.84,1.23-38.55-28-2.96-155.84,39.49-200.18Zm56.31,10.45c-27.39-.52-46.38,38.21-37.98,54.55,10.09,19.62,65.5,18.26,74.77-3.3,7.21-16.78-11.38-50.77-36.79-51.24Z"/>
</g>
<g id="Layer_4" data-name="Layer 4">
<path class="cls-1" d="M68.93,86.51c-6.55,27.74,252.45,113.97,267.56,89.66,9.24-14.87-64.9-83.62-163.53-97.57-39.06-5.52-100.95-5.14-104.03,7.91Z"/>
</g>
<g id="Layer_5" data-name="Layer 5">
<path class="cls-2" d="M138.96,93.76c.41-5.25,6.51-5.74,28.85-19.42,26.97-16.51,28.85-22.38,56.86-40.83,30.07-19.81,48.46-31.94,54.82-26.61,9.72,8.15-25.18,43.33-21.31,99.35,.87,12.61,3.12,17.79-.86,23.01-18.25,23.95-120.07-13.68-118.35-35.5Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 530 B

View File

@ -1,92 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="svg4485"
width="512"
height="512"
viewBox="0 0 512 512"
sodipodi:docname="pleroma_logo_vector_nobg.svg"
inkscape:version="0.92.1 r15371">
<metadata
id="metadata4491">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs4489" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1680"
inkscape:window-height="997"
id="namedview4487"
showgrid="false"
inkscape:zoom="0.70710678"
inkscape:cx="26.131594"
inkscape:cy="235.37499"
inkscape:window-x="1912"
inkscape:window-y="22"
inkscape:window-maximized="1"
inkscape:current-layer="svg4485" />
<g
id="g4612">
<path
sodipodi:nodetypes="cccccc"
inkscape:connector-curvature="0"
id="path4495"
d="M 235,89 V 423 H 152 V 115 l 26,-26 z"
style="opacity:1;fill:#fba457;fill-opacity:1;stroke:#009bff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.17587938" />
<circle
r="26"
cx="178"
cy="115"
id="path4497"
style="opacity:1;fill:#fba457;fill-opacity:1;stroke:#009bff;stroke-width:0;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.17587938" />
<circle
r="26"
cx="335"
cy="230"
id="path4497-0"
style="opacity:1;fill:#fba457;fill-opacity:1;stroke:#009bff;stroke-width:0;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.17587938" />
<path
sodipodi:nodetypes="cccccc"
inkscape:connector-curvature="0"
id="path4516"
d="M 277,256 V 89 l 84,3e-6 L 361.00002,230 335,256 Z"
style="fill:#fba457;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<circle
r="26"
cx="335"
cy="397"
id="path4497-0-6"
style="opacity:1;fill:#fba457;fill-opacity:1;stroke:#009bff;stroke-width:0;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.17587938" />
<path
sodipodi:nodetypes="cccccc"
inkscape:connector-curvature="0"
id="path4516-5"
d="m 277,423 v -83 h 84 l 2e-5,57 L 335,423 Z"
style="opacity:1;fill:#fba457;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -1,8 +1,8 @@
# Introduction to Pleroma-FE # Introduction to Akkoma-FE
## What is Pleroma-FE? ## What is Akkoma-FE?
Pleroma-FE is the default user-facing frontend for Pleroma. It's user interface is modeled after Qvitter which is modeled after an older Twitter design. It provides a simple 2-column interface for microblogging. While being simple by default it also provides many powerful customization options. Akkoma-FE is the default user-facing frontend for Pleroma. It's user interface is modeled after Qvitter which is modeled after an older Twitter design. It provides a simple 2-column interface for microblogging. While being simple by default it also provides many powerful customization options.
## How can I use it? ## How can I use it?
If your instance uses Pleroma-FE, you can acces it by going to your instance (e.g. <https://pleroma.soykaf.com>). You can read more about it's basic functionality in the [Pleroma-FE User Guide](./user_guide/). We also have [a guide for administrators](./CONFIGURATION.md) and for [hackers/contributors](./HACKING.md). If your instance uses Akkoma-FE, you can acces it by going to your instance (e.g. <https://pleroma.soykaf.com>). You can read more about it's basic functionality in the [Akkoma-FE User Guide](./user_guide/). We also have [a guide for administrators](./CONFIGURATION.md) and for [hackers/contributors](./HACKING.md).

View File

@ -1,13 +1,13 @@
# Adding stickers # Adding stickers
Pleroma-fe supports stickers, which are essentially little images stored server-side Akkoma-FE supports stickers, which are essentially little images stored server-side
which can be selected by a user to automatically attach them to a post. which can be selected by a user to automatically attach them to a post.
There's no explicit setting for these, they just rely on the existence of certain files. There's no explicit setting for these, they just rely on the existence of certain files.
## Initialising the sticker config file ## Initialising the sticker config file
You're probably serving pleroma-fe from your instance's `instance/static/` directory - You're probably serving Akkoma-FE from your instance's `instance/static/` directory -
this directy can also override files served at a given path. this directy can also override files served at a given path.
The first thing we need to do is set up our `stickers.json` file. At `instance/static/static/stickers.json`, The first thing we need to do is set up our `stickers.json` file. At `instance/static/static/stickers.json`,
@ -50,4 +50,4 @@ The `tabIcon` will appear on the sticker picker itself as a representative of th
You can add as many stickers as you like. They should all be in the same directory as your `pack.json`. You can add as many stickers as you like. They should all be in the same directory as your `pack.json`.
Now you should find that there's a sticky note icon on the emoji picker on pleroma-fe that allows you to attach stickers. Now you should find that there's a sticky note icon on the emoji picker on Akkoma-FE that allows you to attach stickers.

View File

@ -8,13 +8,13 @@
> >
> --Catbag > --Catbag
Pleroma-FE is the default user-facing frontend for Pleroma. If your instance uses Pleroma-FE, you can access it by going to your instance (e.g. <https://pleroma.soykaf.com>). After logging in you will have two columns in front of you. Here we're going to keep it to the default behaviour, but some instances swap the left and right columns. If you're on such an instance what we refer to as the left column will be on your right and vice versa. Akkoma-FE is the default user-facing frontend for Pleroma. If your instance uses Akkoma-FE, you can access it by going to your instance (e.g. <https://pleroma.soykaf.com>). After logging in you will have two columns in front of you. Here we're going to keep it to the default behaviour, but some instances swap the left and right columns. If you're on such an instance what we refer to as the left column will be on your right and vice versa.
### Left column ### Left column
- first block: This section is dedicated to [posting](posting_reading_basic_functions.md) - first block: This section is dedicated to [posting](posting_reading_basic_functions.md)
- second block: Here you can switch between the different views for the right column. - second block: Here you can switch between the different views for the right column.
- Optional third block: This is the Instance panel that can be activated, but is deactivated by default. It's fully customisable by instance admins and by default has links to the Pleroma-FE and Mastodon-FE. - Optional third block: This is the Instance panel that can be activated, but is deactivated by default. It's fully customisable by instance admins and by default has links to the Akkoma-FE and Mastodon-FE.
- fourth block: This is the Notifications block, here you will get notified whenever somebody mentions you, follows you, repeats or favorites one of your statuses - fourth block: This is the Notifications block, here you will get notified whenever somebody mentions you, follows you, repeats or favorites one of your statuses
### Right column ### Right column

View File

@ -15,13 +15,13 @@ Posts will contain the text you are posting, but some content will be modified:
Let's clear up some basic stuff. When you post something it's called a **post** or it could be called a **status** or even a **toot** or a **prööt** depending on whom you ask. Post has body/content but it also has some other stuff in it - from attachments, visibility scope, subject line... Let's clear up some basic stuff. When you post something it's called a **post** or it could be called a **status** or even a **toot** or a **prööt** depending on whom you ask. Post has body/content but it also has some other stuff in it - from attachments, visibility scope, subject line...
**Emoji** are small images embedded in text, there are two major types of emoji: [unicode emoji](https://en.wikipedia.org/wiki/Emoji) and custom emoji. While unicode emoji are universal and standardized, they can appear differently depending on where you are using them or may not appear at all on older systems. Custom emoji are a more *fun* kind - instance administrator can define many images as *custom emoji* for their users. This works very simple - custom emoji is defined by its *shortcode* and an image, so that any shortcode enclosed in colons get replaced with image if such shortcode exist. **Emoji** are small images embedded in text, there are two major types of emoji: [unicode emoji](https://en.wikipedia.org/wiki/Emoji) and custom emoji. While unicode emoji are universal and standardized, they can appear differently depending on where you are using them or may not appear at all on older systems. Custom emoji are a more *fun* kind - instance administrator can define many images as *custom emoji* for their users. This works very simple - custom emoji is defined by its *shortcode* and an image, so that any shortcode enclosed in colons get replaced with image if such shortcode exist.
Let's say there's a `:pleroma:` emoji defined on an instance. That means Let's say there's a `:akkoma:` emoji defined on an instance. That means
> First time using :pleroma: pleroma! > First time using :akkoma: akkoma!
will become will become
> First time using ![pleroma](../assets/example_emoji.png) pleroma! > First time using ![akkoma](../assets/example_emoji.png) akkoma!
Note that you can only use emoji defined on your instance, you cannot "copy" someone else's emoji, and will have to ask your administrator to copy emoji from other instance to yours. Note that you can only use emoji defined on your instance, you cannot "copy" someone else's emoji, and will have to ask your administrator to copy emoji from other instance to yours.
Lastly, there's two convenience options for emoji: an emoji picker (smiley face to the right of "submit" button) and autocomplete suggestions - when you start typing :shortcode: it will automatically try to suggest you emoji and complete the shortcode for you if you select one. If emoji doesn't show up in suggestions nor in emoji picker it means there's no such emoji on your instance, if shortcode doesn't match any defined emoji it will appear as text. Lastly, there's two convenience options for emoji: an emoji picker (smiley face to the right of "submit" button) and autocomplete suggestions - when you start typing :shortcode: it will automatically try to suggest you emoji and complete the shortcode for you if you select one. If emoji doesn't show up in suggestions nor in emoji picker it means there's no such emoji on your instance, if shortcode doesn't match any defined emoji it will appear as text.
**Attachments** are fairly simple - you can attach any file to a post as long as the file is within maximum size limits. If you're uploading explicit material you can mark all of your attachments as sensitive (or add the `#nsfw` tag) - it will hide the images and videos behind a warning so that it won't be displayed instantly. **Attachments** are fairly simple - you can attach any file to a post as long as the file is within maximum size limits. If you're uploading explicit material you can mark all of your attachments as sensitive (or add the `#nsfw` tag) - it will hide the images and videos behind a warning so that it won't be displayed instantly.
@ -42,7 +42,7 @@ A few things to consider about the security and usage of these scopes:
- Changing scopes during a thread or adding people to a direct message will not retroactively make them see the whole conversation. If you add someone to a direct message conversation, they will not see the post that happened before they were mentioned. - Changing scopes during a thread or adding people to a direct message will not retroactively make them see the whole conversation. If you add someone to a direct message conversation, they will not see the post that happened before they were mentioned.
* **Reply-to** if you are replying to someone, your post will also contain a note that your post is referring to the post you're replying to. Person you're replying to will receive a notification *even* if you remove them from mentioned people. You won't receive notifications when replying to your own posts, but it's useful to reply to your own posts to provide people some context if it's a follow-up to a previous post. There's a small "Reply to ..." label under post author's name which you can hover on to see what post it's referring to. * **Reply-to** if you are replying to someone, your post will also contain a note that your post is referring to the post you're replying to. Person you're replying to will receive a notification *even* if you remove them from mentioned people. You won't receive notifications when replying to your own posts, but it's useful to reply to your own posts to provide people some context if it's a follow-up to a previous post. There's a small "Reply to ..." label under post author's name which you can hover on to see what post it's referring to.
Sometimes you may encounter posts that seem different than what they are supposed to. For example, you might see a direct message without any mentions in the text. This can happen because internally, the Fediverse has a different addressing mechanism similar to email, with `to` and `cc` fields. While these are not directly accessible in PleromaFE, other software in the Fediverse might generate those posts. Do not worry in these cases, these are normal and not a bug. Sometimes you may encounter posts that seem different than what they are supposed to. For example, you might see a direct message without any mentions in the text. This can happen because internally, the Fediverse has a different addressing mechanism similar to email, with `to` and `cc` fields. While these are not directly accessible in Akkoma-FE, other software in the Fediverse might generate those posts. Do not worry in these cases, these are normal and not a bug.
## Rich text ## Rich text
@ -51,7 +51,7 @@ By default new posts you make are plaintext, meaning you can't make text **bold*
Here is a small example of some text in markdown. Here is a small example of some text in markdown.
``` ```
This is an example of markdown text using **bold** and *cursive* text. This is an example of markdown text using **bold** and *cursive* text.
To get a newline we add two spaces at the end of the previous line. To get a newline we add two spaces at the end of the previous line.
Let's also add a list Let's also add a list
@ -67,7 +67,7 @@ If you set the input-method to Markdown, and post this, it will look something l
## Misskey markdown ## Misskey markdown
The akkoma version of pleroma-fe includes support for writing and rendering Akkoma-FE includes support for writing and rendering
misskey markdown (MFM). To write this you will need to select "MFM" from misskey markdown (MFM). To write this you will need to select "MFM" from
the content type dropdown (if supported), and then you can format text the content type dropdown (if supported), and then you can format text
[in MFM](https://akkoma.dev/sfr/marked-mfm/src/branch/master/docs/syntax.md). [in MFM](https://akkoma.dev/sfr/marked-mfm/src/branch/master/docs/syntax.md).

View File

@ -83,7 +83,7 @@ Here you can change your password, revoke access tokens, configure 2-factor auth
## Theme ## Theme
Here you can change the look and feel of Pleroma-FE. You can choose from several instance-provided presets and you can load one from file and save current theme to file. Before you apply new theme you can see what it will look like approximately in preview section. Here you can change the look and feel of Akkoma-FE. You can choose from several instance-provided presets and you can load one from file and save current theme to file. Before you apply new theme you can see what it will look like approximately in preview section.
The themes engine was made to be easy to use while giving an option for powerful in-depth customization - you can just tweak colors on "Common" tab and leave everything else as is. The themes engine was made to be easy to use while giving an option for powerful in-depth customization - you can just tweak colors on "Common" tab and leave everything else as is.

View File

@ -4,7 +4,7 @@ When you see someone, you can click on their user picture to view their profile,
**Following** is self-explanatory, it adds them to your Home Timeline, lists you as a follower and gives you access to follower-only posts if they have any. **Following** is self-explanatory, it adds them to your Home Timeline, lists you as a follower and gives you access to follower-only posts if they have any.
**Muting** collapses posts and notifications made by them, giving you an option to see the post if you're curious. Clients other than PleromaFE may completely remove their posts. **Muting** collapses posts and notifications made by them, giving you an option to see the post if you're curious. Clients other than Akkoma-FE may completely remove their posts.
**Blocking** a user removes them from your timeline and notifications and prevents them from following you (automatically unfollows them from you). **Blocking** a user removes them from your timeline and notifications and prevents them from following you (automatically unfollows them from you).

View File

@ -1,11 +1,11 @@
site_name: Pleroma-FE Documentation site_name: Akkoma-FE Documentation
theme: theme:
favicon: 'images/pleroma_logo_vector_bg_32.png' favicon: 'images/akkoma_logo_vector_bg_32.png'
name: 'material' name: 'material'
custom_dir: 'theme' custom_dir: 'theme'
# Disable google fonts # Disable google fonts
font: false font: false
logo: 'images/pleroma_logo_vector_nobg.svg' logo: 'images/akkoma_logo_vector_nobg.svg'
features: features:
- tabs - tabs
palette: palette:
@ -14,8 +14,8 @@ theme:
extra_css: extra_css:
- css/extra.css - css/extra.css
repo_name: 'AkkomaGang/pleroma-fe' repo_name: 'AkkomaGang/akkoma-fe'
repo_url: 'https://akkoma.dev/AkkomaGang/pleroma-fe' repo_url: 'https://akkoma.dev/AkkomaGang/akkoma-fe'
extra: extra:
repo_icon: gitea repo_icon: gitea

View File

@ -38,11 +38,11 @@
{% endif %} {% endif %}
{% if page and page.url.startswith('backend') %} {% if page and page.url.startswith('backend') %}
{% set repo_url = "https://git.pleroma.social/pleroma/pleroma" %} {% set repo_url = "https://akkoma.dev/AkkomaGang/akkoma" %}
{% set repo_name = "pleroma/pleroma" %} {% set repo_name = "AkkomaGang/akkoma" %}
{% elif page and page.url.startswith('frontend') %} {% elif page and page.url.startswith('frontend') %}
{% set repo_url = "https://git.pleroma.social/pleroma/pleroma-fe" %} {% set repo_url = "https://akkoma.dev/AkkomaGang/akkoma-fe" %}
{% set repo_name = "pleroma/pleroma-fe" %} {% set repo_name = "AkkomaGang/akkoma-fe" %}
{% else %} {% else %}
{% set repo_url = config.repo_url %} {% set repo_url = config.repo_url %}
{% set repo_name = config.repo_name %} {% set repo_name = config.repo_name %}

View File

@ -1,6 +1,6 @@
{ {
"name": "pleroma_fe", "name": "pleroma_fe",
"version": "3.5.0", "version": "3.10.0",
"description": "A frontend for Akkoma instances", "description": "A frontend for Akkoma instances",
"author": "Roger Braun <roger@rogerbraun.net>", "author": "Roger Braun <roger@rogerbraun.net>",
"private": true, "private": true,

View File

@ -64,6 +64,11 @@ export default {
'-' + this.layoutType '-' + this.layoutType
] ]
}, },
pageBackground () {
return this.mergedConfig.displayPageBackgrounds
? this.$store.state.users.displayBackground
: null
},
currentUser () { return this.$store.state.users.currentUser }, currentUser () { return this.$store.state.users.currentUser },
userBackground () { return this.currentUser.background_image }, userBackground () { return this.currentUser.background_image },
instanceBackground () { instanceBackground () {
@ -71,7 +76,7 @@ export default {
? null ? null
: this.$store.state.instance.background : this.$store.state.instance.background
}, },
background () { return this.userBackground || this.instanceBackground }, background () { return this.pageBackground || this.userBackground || this.instanceBackground },
bgStyle () { bgStyle () {
if (this.background) { if (this.background) {
return { return {

View File

@ -8,7 +8,7 @@
} }
html { html {
font-size: 14px; font-size: 0.875rem;
// overflow-x: clip causes my browser's tab to crash with SIGILL lul // overflow-x: clip causes my browser's tab to crash with SIGILL lul
} }

View File

@ -37,7 +37,7 @@
white-space: pre-line; white-space: pre-line;
word-break: break-word; word-break: break-word;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: scroll; overflow: auto;
} }
&.-static { &.-static {

View File

@ -7,7 +7,7 @@ import {
faStickyNote, faStickyNote,
faSmileBeam faSmileBeam
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { trim, escapeRegExp, startCase } from 'lodash' import { trim, escapeRegExp, startCase, debounce } from 'lodash'
library.add( library.add(
faBoxOpen, faBoxOpen,
@ -42,6 +42,9 @@ const EmojiPicker = {
EmojiGrid EmojiGrid
}, },
methods: { methods: {
debouncedSearch: debounce(function (e) {
this.keyword = e.target.value
}, 500),
onStickerUploaded (e) { onStickerUploaded (e) {
this.$emit('sticker-uploaded', e) this.$emit('sticker-uploaded', e)
}, },
@ -85,17 +88,6 @@ const EmojiPicker = {
activeGroupView () { activeGroupView () {
return this.showingStickers ? '' : this.activeGroup return this.showingStickers ? '' : this.activeGroup
}, },
stickersAvailable () {
if (this.$store.state.instance.stickers) {
return this.$store.state.instance.stickers.length > 0
}
return 0
},
filteredEmoji () {
return this.filterByKeyword(
this.$store.state.instance.customEmoji || []
)
},
emojis () { emojis () {
const recentEmojis = this.$store.getters.recentEmojis const recentEmojis = this.$store.getters.recentEmojis
const standardEmojis = this.$store.state.instance.emoji || [] const standardEmojis = this.$store.state.instance.emoji || []

View File

@ -44,11 +44,10 @@
> >
<div class="emoji-search"> <div class="emoji-search">
<input <input
v-model="keyword"
type="text" type="text"
class="form-control" class="form-control"
:placeholder="$t('emoji.search_emoji')" :placeholder="$t('emoji.search_emoji')"
@input="$event.target.composing = false" @input="debouncedSearch"
> >
</div> </div>
<EmojiGrid <EmojiGrid

View File

@ -1,5 +1,6 @@
import UserAvatar from '../user_avatar/user_avatar.vue' import UserAvatar from '../user_avatar/user_avatar.vue'
import UserListPopover from '../user_list_popover/user_list_popover.vue' import UserListPopover from '../user_list_popover/user_list_popover.vue'
import StillImage from '../still-image/still-image.vue'
const EMOJI_REACTION_COUNT_CUTOFF = 12 const EMOJI_REACTION_COUNT_CUTOFF = 12
@ -12,7 +13,8 @@ const EmojiReactions = {
name: 'EmojiReactions', name: 'EmojiReactions',
components: { components: {
UserAvatar, UserAvatar,
UserListPopover UserListPopover,
StillImage
}, },
props: ['status'], props: ['status'],
data: () => ({ data: () => ({

View File

@ -14,12 +14,13 @@
<span <span
v-if="reaction.url !== null" v-if="reaction.url !== null"
> >
<img <StillImage
:src="reaction.url" :src="reaction.url"
:title="reaction.name" :title="reaction.name"
:alt="reaction.name"
class="reaction-emoji" class="reaction-emoji"
height="2.55em" height="2.55em"
> />
{{ reaction.count }} {{ reaction.count }}
</span> </span>
<span v-else> <span v-else>

View File

@ -136,18 +136,26 @@ const ExtraButtons = {
}, },
doRedraftStatus () { doRedraftStatus () {
this.$store.dispatch('fetchStatusSource', { id: this.status.id }) this.$store.dispatch('fetchStatusSource', { id: this.status.id })
.then(data => this.$store.dispatch('openPostStatusModal', { .then(data => {
isRedraft: true, let repliedUserId = this.status.in_reply_to_user_id;
statusId: this.status.id, let repliedUser = this.status.attentions.filter(user =>
subject: data.spoiler_text, user.id === repliedUserId);
statusText: data.text, this.$store.dispatch('openPostStatusModal', {
statusIsSensitive: this.status.nsfw, isRedraft: true,
statusPoll: this.status.poll, attentions: this.status.attentions,
statusFiles: [...this.status.attachments], statusId: this.status.id,
statusScope: this.status.visibility, subject: data.spoiler_text,
statusLanguage: this.status.language, statusText: data.text,
statusContentType: data.content_type statusIsSensitive: this.status.nsfw,
})) statusPoll: this.status.poll,
statusFiles: [...this.status.attachments],
statusScope: this.status.visibility,
statusLanguage: this.status.language,
statusContentType: data.content_type,
replyTo: this.status.in_reply_to_status_id,
repliedUser: repliedUser
})
})
this.doDeleteStatus() this.doDeleteStatus()
}, },
showRedraftStatusConfirmDialog () { showRedraftStatusConfirmDialog () {

View File

@ -55,6 +55,9 @@
.interactive { .interactive {
.svg-inline--fa { .svg-inline--fa {
@media (prefers-reduced-motion: reduce) {
animation: unset;
}
animation-duration: 0.6s; animation-duration: 0.6s;
} }

View File

@ -1,9 +1,13 @@
<template> <template>
<div class="list"> <div
class="list"
role="list"
>
<div <div
v-for="item in items" v-for="item in items"
:key="getKey(item)" :key="getKey(item)"
class="list-item" class="list-item"
role="listitem"
> >
<slot <slot
name="item" name="item"

View File

@ -93,9 +93,6 @@ const MentionLink = {
this.highlightType this.highlightType
] ]
}, },
useAtIcon () {
return this.mergedConfig.useAtIcon
},
isRemote () { isRemote () {
return this.userName !== this.userNameFull return this.userName !== this.userNameFull
}, },

View File

@ -157,6 +157,9 @@
box-shadow: var(--panelShadow); box-shadow: var(--panelShadow);
transition-property: transform; transition-property: transform;
transition-duration: 0.25s; transition-duration: 0.25s;
@media (prefers-reduced-motion: reduce) {
transition: unset;
}
transform: translateX(0); transform: translateX(0);
z-index: 1001; z-index: 1001;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;

View File

@ -151,7 +151,6 @@
> >
<Timeago <Timeago
:time="notification.created_at" :time="notification.created_at"
:with-direction="true"
:auto-update="240" :auto-update="240"
/> />
</router-link> </router-link>

View File

@ -105,9 +105,12 @@
flex: 1; flex: 1;
padding-left: 0.8em; padding-left: 0.8em;
min-width: 0; min-width: 0;
}
.heading-right, .notification-right {
.timeago { .timeago {
min-width: 3em; display: inline-block;
min-width: 6em;
text-align: right; text-align: right;
} }
} }

View File

@ -138,7 +138,7 @@ const PostStatusForm = {
statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser) statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser)
} }
const { postContentType: contentType, sensitiveByDefault, sensitiveIfSubject, interfaceLanguage } = this.$store.getters.mergedConfig const { postContentType: contentType, sensitiveByDefault, sensitiveIfSubject, interfaceLanguage, alwaysShowSubjectInput } = this.$store.getters.mergedConfig
let statusParams = { let statusParams = {
spoilerText: this.subject || '', spoilerText: this.subject || '',
@ -199,6 +199,10 @@ const PostStatusForm = {
} }
} }
// When first loading the form, hide the subject (CW) field if it's disabled or doesn't have a starting value.
// "disableSubject" seems to take priority over "alwaysShowSubjectInput".
const showSubject = !this.disableSubject && (statusParams.spoilerText || alwaysShowSubjectInput)
return { return {
dropFiles: [], dropFiles: [],
uploadingFiles: false, uploadingFiles: false,
@ -213,7 +217,10 @@ const PostStatusForm = {
preview: null, preview: null,
previewLoading: false, previewLoading: false,
emojiInputShown: false, emojiInputShown: false,
idempotencyKey: '' idempotencyKey: '',
activeEmojiInput: undefined,
activeTextInput: undefined,
subjectVisible: showSubject
} }
}, },
computed: { computed: {
@ -674,8 +681,33 @@ const PostStatusForm = {
this.$refs['emoji-input'].resize() this.$refs['emoji-input'].resize()
}, },
showEmojiPicker () { showEmojiPicker () {
this.$refs['textarea'].focus() if (!this.activeEmojiInput || !this.activeTextInput)
this.$refs['emoji-input'].triggerShowPicker() this.focusStatusInput()
this.$refs[this.activeTextInput].focus()
this.$refs[this.activeEmojiInput].triggerShowPicker()
},
focusStatusInput() {
this.activeEmojiInput = 'emoji-input'
this.activeTextInput = 'textarea'
},
focusSubjectInput() {
this.activeEmojiInput = 'subject-emoji-input'
this.activeTextInput = 'subject-input'
},
toggleSubjectVisible() {
// If hiding CW, then we need to clear the subject and reset focus
if (this.subjectVisible)
{
this.focusStatusInput()
// "nsfw" property is normally set by the @change listener, but this bypasses it.
// We need to clear it manually instead.
this.newStatus.spoilerText = ''
this.newStatus.nsfw = false
}
this.subjectVisible = !this.subjectVisible
}, },
clearError () { clearError () {
this.error = null this.error = null

View File

@ -118,13 +118,16 @@
/> />
</div> </div>
<EmojiInput <EmojiInput
v-if="!disableSubject && (newStatus.spoilerText || alwaysShowSubject)" ref="subject-emoji-input"
v-if="subjectVisible"
v-model="newStatus.spoilerText" v-model="newStatus.spoilerText"
enable-emoji-picker enable-emoji-picker
hide-emoji-button
:suggest="emojiSuggestor" :suggest="emojiSuggestor"
class="form-control" class="form-control"
> >
<input <input
ref="subject-input"
v-model="newStatus.spoilerText" v-model="newStatus.spoilerText"
type="text" type="text"
:placeholder="$t('post_status.content_warning')" :placeholder="$t('post_status.content_warning')"
@ -132,6 +135,7 @@
size="1" size="1"
class="form-post-subject" class="form-post-subject"
@input="onSubjectInput" @input="onSubjectInput"
@focus="focusSubjectInput()"
> >
</EmojiInput> </EmojiInput>
<i18n-t <i18n-t
@ -173,6 +177,7 @@
@input="resize" @input="resize"
@compositionupdate="resize" @compositionupdate="resize"
@paste="paste" @paste="paste"
@focus="focusStatusInput()"
/> />
<p <p
v-if="hasStatusLengthLimit" v-if="hasStatusLengthLimit"
@ -276,6 +281,15 @@
> >
<FAIcon icon="poll-h" /> <FAIcon icon="poll-h" />
</button> </button>
<button
v-if="!disableSubject"
class="spoiler-icon button-unstyled"
:class="{ selected: subjectVisible }"
:title="$t('post_status.toggle_content_warning')"
@click="toggleSubjectVisible"
>
<FAIcon icon="eye-slash" />
</button>
</div> </div>
<button <button
v-if="posting" v-if="posting"
@ -456,7 +470,7 @@
} }
} }
.media-upload-icon, .poll-icon, .emoji-icon { .media-upload-icon, .poll-icon, .emoji-icon, .spoiler-icon {
font-size: 1.85em; font-size: 1.85em;
line-height: 1.1; line-height: 1.1;
flex: 1; flex: 1;
@ -499,6 +513,11 @@
.poll-icon { .poll-icon {
order: 3; order: 3;
justify-content: center;
}
.spoiler-icon {
order: 4;
justify-content: right; justify-content: right;
} }

View File

@ -74,6 +74,9 @@
.interactive { .interactive {
.svg-inline--fa { .svg-inline--fa {
@media (prefers-reduced-motion: reduce) {
animation: unset;
}
animation-duration: 0.6s; animation-duration: 0.6s;
} }

View File

@ -146,6 +146,11 @@
{{ $t('settings.show_wider_shortcuts') }} {{ $t('settings.show_wider_shortcuts') }}
</BooleanSetting> </BooleanSetting>
</li> </li>
<li>
<BooleanSetting path="displayPageBackgrounds">
{{ $t('settings.show_page_backgrounds') }}
</BooleanSetting>
</li>
<li> <li>
<BooleanSetting path="stopGifs"> <BooleanSetting path="stopGifs">
{{ $t('settings.stop_gifs') }} {{ $t('settings.stop_gifs') }}
@ -483,14 +488,6 @@
</BooleanSetting> </BooleanSetting>
</li> </li>
</ul> </ul>
<li>
<BooleanSetting
path="useAtIcon"
expert="1"
>
{{ $t('settings.use_at_icon') }}
</BooleanSetting>
</li>
<li> <li>
<BooleanSetting path="mentionLinkShowAvatar"> <BooleanSetting path="mentionLinkShowAvatar">
{{ $t('settings.mention_link_show_avatar') }} {{ $t('settings.mention_link_show_avatar') }}

View File

@ -10,17 +10,20 @@ import SelectableList from 'src/components/selectable_list/selectable_list.vue'
import ProgressButton from 'src/components/progress_button/progress_button.vue' import ProgressButton from 'src/components/progress_button/progress_button.vue'
import withSubscription from 'src/components/../hocs/with_subscription/with_subscription' import withSubscription from 'src/components/../hocs/with_subscription/with_subscription'
import Checkbox from 'src/components/checkbox/checkbox.vue' import Checkbox from 'src/components/checkbox/checkbox.vue'
import withLoadMore from 'src/components/../hocs/with_load_more/with_load_more'
const BlockList = withSubscription({ const BlockList = withLoadMore({
fetch: (props, $store) => $store.dispatch('fetchBlocks'), fetch: (props, $store) => $store.dispatch('fetchBlocks'),
select: (props, $store) => get($store.state.users.currentUser, 'blockIds', []), select: (props, $store) => get($store.state.users.currentUser, 'blockIds', []),
childPropName: 'items' childPropName: 'items',
destroy: () => {}
})(SelectableList) })(SelectableList)
const MuteList = withSubscription({ const MuteList = withLoadMore({
fetch: (props, $store) => $store.dispatch('fetchMutes'), fetch: (props, $store) => $store.dispatch('fetchMutes'),
select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []), select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []),
childPropName: 'items' childPropName: 'items',
destroy: () => {}
})(SelectableList) })(SelectableList)
const DomainMuteList = withSubscription({ const DomainMuteList = withSubscription({

View File

@ -33,6 +33,7 @@ const ProfileTab = {
newName: this.$store.state.users.currentUser.name_unescaped, newName: this.$store.state.users.currentUser.name_unescaped,
newBio: unescape(this.$store.state.users.currentUser.description), newBio: unescape(this.$store.state.users.currentUser.description),
newLocked: this.$store.state.users.currentUser.locked, newLocked: this.$store.state.users.currentUser.locked,
newPermitFollowback: this.$store.state.users.currentUser.permit_followback,
newFields: this.$store.state.users.currentUser.fields.map(field => ({ name: field.name, value: field.value })), newFields: this.$store.state.users.currentUser.fields.map(field => ({ name: field.name, value: field.value })),
showRole: this.$store.state.users.currentUser.show_role, showRole: this.$store.state.users.currentUser.show_role,
role: this.$store.state.users.currentUser.role, role: this.$store.state.users.currentUser.role,
@ -135,6 +136,7 @@ const ProfileTab = {
bot: this.bot, bot: this.bot,
show_role: this.showRole, show_role: this.showRole,
status_ttl_days: this.expirePosts ? this.newPostTTLDays : -1, status_ttl_days: this.expirePosts ? this.newPostTTLDays : -1,
permit_followback: this.permit_followback,
accepts_direct_messages_from: this.userAcceptsDirectMessagesFrom accepts_direct_messages_from: this.userAcceptsDirectMessagesFrom
/* eslint-enable camelcase */ /* eslint-enable camelcase */
} }

View File

@ -259,6 +259,19 @@
<BooleanSetting path="serverSide_locked"> <BooleanSetting path="serverSide_locked">
{{ $t('settings.lock_account_description') }} {{ $t('settings.lock_account_description') }}
</BooleanSetting> </BooleanSetting>
<ul
class="setting-list suboptions"
:class="[{disabled: !serverSide_locked}]"
>
<li>
<BooleanSetting
path="serverSide_permitFollowback"
:disabled="!serverSide_locked"
>
{{ $t('settings.permit_followback_description') }}
</BooleanSetting>
</li>
</ul>
</li> </li>
<li> <li>
<BooleanSetting path="serverSide_discoverable"> <BooleanSetting path="serverSide_discoverable">

View File

@ -268,6 +268,10 @@
.side-drawer { .side-drawer {
overflow-x: hidden; overflow-x: hidden;
transition-timing-function: cubic-bezier(0, 1, 0.5, 1); transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
@media (prefers-reduced-motion: reduce) {
transition-timing-function: unset;
transition: unset;
}
transition: 0.35s; transition: 0.35s;
transition-property: transform; transition-property: transform;
margin: 0 0 0 -100px; margin: 0 0 0 -100px;

View File

@ -20,6 +20,7 @@ import generateProfileLink from 'src/services/user_profile_link_generator/user_p
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import { muteWordHits } from '../../services/status_parser/status_parser.js' import { muteWordHits } from '../../services/status_parser/status_parser.js'
import { unescape, uniqBy } from 'lodash' import { unescape, uniqBy } from 'lodash'
import StillImage from '../still-image/still-image.vue'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -117,7 +118,8 @@ const Status = {
RichContent, RichContent,
MentionLink, MentionLink,
MentionsLine, MentionsLine,
QuoteButton QuoteButton,
StillImage
}, },
props: [ props: [
'statusoid', 'statusoid',

View File

@ -174,12 +174,12 @@
> >
@{{ status.user.screen_name_ui }} @{{ status.user.screen_name_ui }}
</router-link> </router-link>
<img <StillImage
v-if="!!(status.user && status.user.favicon)" v-if="!!(status.user && status.user.favicon)"
class="status-favicon" class="status-favicon"
:src="status.user.favicon" :src="status.user.favicon"
:title="faviconAlt(status)" :title="faviconAlt(status)"
> />
</span> </span>
</div> </div>
@ -190,7 +190,7 @@
> >
<Timeago <Timeago
:time="status.created_at" :time="status.created_at"
:with-direction="true" :with-direction="!compact"
:auto-update="60" :auto-update="60"
/> />
</router-link> </router-link>

View File

@ -17,6 +17,9 @@
.emoji:hover { .emoji:hover {
transform: scale(1.4); transform: scale(1.4);
@media (prefers-reduced-motion: reduce) {
transition: unset;
}
transition: 0.05s; transition: 0.05s;
} }

View File

@ -11,8 +11,9 @@ const StillImage = {
], ],
data () { data () {
return { return {
stopGifs: this.$store.getters.mergedConfig.stopGifs, stopGifs: this.$store.getters.mergedConfig.stopGifs || window.matchMedia('(prefers-reduced-motion: reduce)').matches,
isAnimated: false isAnimated: false,
imageTypeLabel: ''
} }
}, },
computed: { computed: {
@ -39,14 +40,24 @@ const StillImage = {
this.imageLoadError && this.imageLoadError() this.imageLoadError && this.imageLoadError()
}, },
detectAnimation (image) { detectAnimation (image) {
if (this.mimetype === 'image/gif' || this.src.endsWith('.gif')) { const mediaProxyAvailable = this.$store.state.instance.mediaProxyAvailable
this.isAnimated = true
return if (!mediaProxyAvailable) {
// It's a bit aggressive to assume all images we can't find the mimetype of is animated, but necessary for
// people in need of reduced motion accessibility. As such, we'll consider those images animated if the user
// agent is set to prefer reduced motion. Otherwise, it'll just be used as an early exit.
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
// Since the canvas and images are not pixel-perfect matching (due to scaling),
// It makes the images jiggle on hover, which is not ideal for accessibility, methinks
this.isAnimated = true
return
}
this.detectWithoutMediaProxy(image)
} else {
this.detectWithMediaProxy(image)
} }
// harmless CORS errors without-- clean console with },
if (!this.$store.state.instance.mediaProxyAvailable) return detectAnimationWithFetch (image) {
// Animated JPEGs?
if (!(this.src.endsWith('.webp') || this.src.endsWith('.png'))) return
// Browser Cache should ensure image doesn't get loaded twice if cache exists // Browser Cache should ensure image doesn't get loaded twice if cache exists
fetch(image.src, { fetch(image.src, {
referrerPolicy: 'same-origin' referrerPolicy: 'same-origin'
@ -55,12 +66,20 @@ const StillImage = {
// We don't need to read the whole file so only call it once // We don't need to read the whole file so only call it once
data.body.getReader().read() data.body.getReader().read()
.then(reader => { .then(reader => {
if (this.src.endsWith('.webp') && this.isAnimatedWEBP(reader.value)) { // Ordered from least to most intensive
if (this.isGIF(reader.value)) {
this.isAnimated = true this.isAnimated = true
this.setLabel('GIF')
return return
} }
if (this.src.endsWith('.png') && this.isAnimatedPNG(reader.value)) { if (this.isAnimatedWEBP(reader.value)) {
this.isAnimated = true this.isAnimated = true
this.setLabel('WEBP')
return
}
if (this.isAnimatedPNG(reader.value)) {
this.isAnimated = true
this.setLabel('APNG')
} }
}) })
}) })
@ -68,6 +87,53 @@ const StillImage = {
// this.imageLoadError && this.imageLoadError() // this.imageLoadError && this.imageLoadError()
}) })
}, },
detectWithMediaProxy (image) {
this.detectAnimationWithFetch(image)
},
detectWithoutMediaProxy (image) {
// We'll just assume that gifs and webp are animated
const extension = image.src.split('.').pop().toLowerCase()
if (extension === 'gif') {
this.isAnimated = true
this.setLabel('GIF')
return
}
if (extension === 'webp') {
this.isAnimated = true
this.setLabel('WEBP')
return
}
// Beware the apng! use this if ye dare
// if (extension === 'png') {
// this.isAnimated = true
// this.setLabel('PNG')
// return
// }
// Hail mary for extensionless
if (extension.includes('/')) {
// Don't mind the CORS error barrage
this.detectAnimationWithFetch(image)
}
},
setLabel (name) {
this.imageTypeLabel = name;
},
isGIF (data) {
// I am a perfectly sane individual
//
// GIF HEADER CHUNK
// === START HEADER ===
// 47 49 46 38 ("GIF8")
const gifHeader = [0x47, 0x49, 0x46];
for (let i = 0; i < 3; i++) {
if (data[i] !== gifHeader[i]) {
return false;
}
}
return true
},
isAnimatedWEBP (data) { isAnimatedWEBP (data) {
/** /**
* WEBP HEADER CHUNK * WEBP HEADER CHUNK
@ -101,16 +167,55 @@ const StillImage = {
const idatPos = str.indexOf('IDAT') const idatPos = str.indexOf('IDAT')
return (str.substring(0, idatPos > 0 ? idatPos : 0).indexOf('acTL') > 0) return (str.substring(0, idatPos > 0 ? idatPos : 0).indexOf('acTL') > 0)
}, },
drawThumbnail () { drawThumbnail() {
const canvas = this.$refs.canvas const canvas = this.$refs.canvas;
if (!this.$refs.canvas) return if (!canvas) return;
const image = this.$refs.src
const width = image.naturalWidth const context = canvas.getContext('2d');
const height = image.naturalHeight const image = this.$refs.src;
canvas.width = width const parentElement = canvas.parentElement;
canvas.height = height
canvas.getContext('2d').drawImage(image, 0, 0, width, height) // Draw the quick, unscaled version first
} context.drawImage(image, 0, 0, parentElement.clientWidth, parentElement.clientHeight);
// Use requestAnimationFrame to schedule the scaling to the next frame
requestAnimationFrame(() => {
// Compute scaling ratio between the natural dimensions of the image and its display dimensions
const scalingRatioWidth = parentElement.clientWidth / image.naturalWidth;
const scalingRatioHeight = parentElement.clientHeight / image.naturalHeight;
// Adjust for high-DPI displays
const ratio = window.devicePixelRatio || 1;
canvas.width = image.naturalWidth * scalingRatioWidth * ratio;
canvas.height = image.naturalHeight * scalingRatioHeight * ratio;
canvas.style.width = `${parentElement.clientWidth}px`;
canvas.style.height = `${parentElement.clientHeight}px`;
context.scale(ratio, ratio);
// Maintain the aspect ratio of the image
const imgAspectRatio = image.naturalWidth / image.naturalHeight;
const canvasAspectRatio = parentElement.clientWidth / parentElement.clientHeight;
let drawWidth, drawHeight;
if (imgAspectRatio > canvasAspectRatio) {
drawWidth = parentElement.clientWidth;
drawHeight = parentElement.clientWidth / imgAspectRatio;
} else {
drawHeight = parentElement.clientHeight;
drawWidth = parentElement.clientHeight * imgAspectRatio;
}
context.clearRect(0, 0, canvas.width, canvas.height); // Clear the previous unscaled image
context.imageSmoothingEnabled = true;
context.imageSmoothingQuality = 'high';
// Draw the good one for realsies
const dx = (parentElement.clientWidth - drawWidth) / 2;
const dy = (parentElement.clientHeight - drawHeight) / 2;
context.drawImage(image, dx, dy, drawWidth, drawHeight);
});
}
}, },
updated () { updated () {
// On computed animated change // On computed animated change

View File

@ -1,9 +1,15 @@
<template> <template>
<div <div
ref="still-image"
class="still-image" class="still-image"
:class="{ animated: animated }" :class="{ animated: animated }"
:style="style" :style="style"
> >
<div
v-if="animated && imageTypeLabel"
class="image-type-label">
{{ imageTypeLabel }}
</div>
<canvas <canvas
v-if="animated" v-if="animated"
ref="canvas" ref="canvas"
@ -57,30 +63,26 @@
} }
} }
&.animated { .image-type-label {
&::before { position: absolute;
zoom: var(--_still_image-label-scale, 1); top: 0.25em;
content: 'gif'; left: 0.25em;
position: absolute; line-height: 1;
line-height: 1; font-size: 0.6em;
font-size: 0.7em; background: rgba(127, 127, 127, 0.5);
top: 0.5em; color: #fff;
left: 0.5em; padding: 2px 4px;
background: rgba(127, 127, 127, 0.5); border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
color: #fff; z-index: 2;
display: block; visibility: var(--_still-image-label-visibility, visible);
padding: 2px 4px; }
border-radius: $fallback--tooltipRadius;
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
z-index: 2;
visibility: var(--_still-image-label-visibility, visible);
}
&.animated {
&:hover canvas { &:hover canvas {
display: none; display: none;
} }
&:hover::before { &:hover .image-type-label {
visibility: var(--_still-image-label-visibility, hidden); visibility: var(--_still-image-label-visibility, hidden);
} }

View File

@ -62,6 +62,9 @@
border-top-right-radius: 0; border-top-right-radius: 0;
border-top-left-radius: 0; border-top-left-radius: 0;
transform: translateY(-100%); transform: translateY(-100%);
@media (prefers-reduced-motion: reduce) {
transition: unset;
}
transition: transform 100ms; transition: transform 100ms;
} }
@ -89,6 +92,9 @@
svg { svg {
margin-left: 0.6em; margin-left: 0.6em;
@media (prefers-reduced-motion: reduce) {
transition: unset;
}
transition: transform 100ms; transition: transform 100ms;
} }

View File

@ -33,7 +33,7 @@
--_avatarShadowBox: var(--avatarStatusShadow); --_avatarShadowBox: var(--avatarStatusShadow);
--_avatarShadowFilter: var(--avatarStatusShadowFilter); --_avatarShadowFilter: var(--avatarStatusShadowFilter);
--_avatarShadowInset: var(--avatarStatusShadowInset); --_avatarShadowInset: var(--avatarStatusShadowInset);
--_still-image-label-visibility: hidden; // --_still-image-label-visibility: hidden;
display: inline-block; display: inline-block;
position: relative; position: relative;

View File

@ -145,10 +145,12 @@ const UserProfile = {
if (user) { if (user) {
loadById(user.id) loadById(user.id)
this.note = user.relationship.note this.note = user.relationship.note
this.$store.dispatch('setDisplayBackground', user.background_image)
} else { } else {
this.$store.dispatch('fetchUser', userNameOrId) this.$store.dispatch('fetchUser', userNameOrId)
.then(({ id, relationship }) => { .then(({ id, relationship, background_image }) => {
this.note = relationship.note this.note = relationship.note
this.$store.dispatch('setDisplayBackground', background_image)
return loadById(id) return loadById(id)
}) })
.catch((reason) => { .catch((reason) => {
@ -225,6 +227,9 @@ const UserProfile = {
Conversation, Conversation,
RichContent, RichContent,
FollowedTagList FollowedTagList
},
beforeRouteLeave(to, from) {
this.$store.dispatch('setDisplayBackground', null)
} }
} }

View File

@ -3,11 +3,13 @@ import * as bodyScrollLock from 'body-scroll-lock'
let previousNavPaddingRight let previousNavPaddingRight
let previousAppBgWrapperRight let previousAppBgWrapperRight
const lockerEls = new Set([]) const lockerEls = new Set([])
const allowedScrollableClasses = ['emoji-tabs-item', 'emoji-item']
const disableBodyScroll = (el) => { const disableBodyScroll = (el) => {
const scrollBarGap = window.innerWidth - document.documentElement.clientWidth const scrollBarGap = window.innerWidth - document.documentElement.clientWidth
bodyScrollLock.disableBodyScroll(el, { bodyScrollLock.disableBodyScroll(el, {
reserveScrollBarGap: true reserveScrollBarGap: true,
allowTouchMove: el => allowedScrollableClasses.includes(el.parentElement.className),
}) })
lockerEls.add(el) lockerEls.add(el)
setTimeout(() => { setTimeout(() => {

View File

@ -884,7 +884,6 @@
"upload_a_photo": "Pujar una foto", "upload_a_photo": "Pujar una foto",
"useStreamingApi": "Rebre apunts i notificacions en temps real", "useStreamingApi": "Rebre apunts i notificacions en temps real",
"useStreamingApiWarning": "És genial emprar-lo. Si es trenca, refresca, suposo?", "useStreamingApiWarning": "És genial emprar-lo. Si es trenca, refresca, suposo?",
"use_at_icon": "Mostra el símbol {'@'} com a icona enlloc de text",
"use_contain_fit": "No retallar els adjunts en miniatures", "use_contain_fit": "No retallar els adjunts en miniatures",
"use_one_click_nsfw": "Obre els adjunts NSFW amb només un clic", "use_one_click_nsfw": "Obre els adjunts NSFW amb només un clic",
"user_mutes": "Usuaris", "user_mutes": "Usuaris",

View File

@ -916,7 +916,6 @@
"upload_a_photo": "Lade ein Foto hoch", "upload_a_photo": "Lade ein Foto hoch",
"useStreamingApi": "Empfange Posts und Benachrichtigungen in Echtzeit", "useStreamingApi": "Empfange Posts und Benachrichtigungen in Echtzeit",
"useStreamingApiWarning": "(Nicht empfohlen, experimentell, bekannt dafür, Posts zu überspringen)", "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_blurhash": "Blurhash für NSFW-Vorschauen verwenden",
"use_contain_fit": "Vorschaubilder nicht zuschneiden", "use_contain_fit": "Vorschaubilder nicht zuschneiden",
"use_one_click_nsfw": "Heikle Anhänge mit nur einem Klick öffnen", "use_one_click_nsfw": "Heikle Anhänge mit nur einem Klick öffnen",

215
src/i18n/el.json Normal file
View File

@ -0,0 +1,215 @@
{
"about": {
"mrf": {
"keyword": {
"keyword_policies": "Πολιτικές λέξεων-κλειδιών",
"reject": "Απόρριψη",
"replace": "Αντικατάσταση"
},
"mrf_policies": "Ενεργοποιημένες πολιτικές MRF",
"mrf_policies_desc": "",
"simple": {
"accept": "Αποδοχή",
"accept_desc": "Αυτό το instance αποδέχεται μηνύματα μόνο από τα ακόλουθα instances:",
"ftl_removal": "Αφαίρεση από το χρονολόγιο \"Γνωστού Δίκτυου\"",
"ftl_removal_desc": "Αυτό το instance αφαιρεί αυτά τα instances από το χρονολόγιο \"Γνωστού Δικτύου\":",
"quarantine": "Καραντίνα",
"quarantine_desc": "Αυτό το instance δε θα στέλνει αναρτήσεις στα ακόλουθα instances:",
"reason": "Λόγος",
"simple_policies": "Πολιτικές του instance"
}
}
},
"announcements": {
"all_day_prompt": "Αυτό είναι ένα ολοήμερο συμβάν",
"cancel_edit_action": "Ακύρωση",
"close_error": "Κλείσιμο",
"delete_action": "Διαγραφή",
"edit_action": "Επεξεργασία",
"end_time_display": "Λήγει στις {time}",
"page_header": "Ανακοινώσεις",
"title": "Ανακοίνωση"
},
"chats": {
"empty_message_error": "Δε μπορεί να σταλεί κενό μήνυμα",
"error_sending_message": "Κάτι πήγε λάθος κατά την αποστολή του μηνύματος.",
"message_user": "Στείλε μήνυμα στον/στην {nickname}",
"more": "Περισσότερα",
"new": "Νέο Chat",
"you": "Εσείς:"
},
"display_date": {
"today": "Σήμερα"
},
"domain_mute_card": {
"mute": "Σίγαση"
},
"emoji": {
"add_emoji": "Εισαγωγή emoji",
"load_all": "Φόρτωση όλων των {emojiAmount} emoji",
"recent": "Χρησιμοποιήθηκαν πρόσφατα",
"search_emoji": "Αναζήτηση για ένα emoji",
"stickers": "Αυτοκόλλητα"
},
"errors": {
"storage_unavailable": "Το Pleroma δε μπόρεσε να προσπελάσει τον αποθηκευτικό χώρο του browser. Η σύνδεσή σας ή οι τοπικές ρυθμίσεις σας δε θα αποθηκευτούν και μπορεί να αντιμετωπίσετε απρόοπτα θέματα. Προσπαθήστε να ενεργοποιήσετε τα cookies."
},
"exporter": {
"export": "Εξαγωγή"
},
"features_panel": {
"text_limit": "Όριο κειμένου"
},
"file_type": {
"audio": "Ήχος",
"file": "Αρχείο",
"image": "Εικόνα",
"video": "Βίντεο"
},
"general": {
"apply": "Εφαρμογή",
"cancel": "Ακύρωση",
"close": "Κλείσιμο",
"disable": "Απενεργοποίηση",
"enable": "Ενεργοποίηση",
"error_retry": "Παρακαλώ δοκιμάστε ξανά",
"flash_content": "Κάντε κλικ για την εμφάνιση Flash περιεχομένου με τη χρήση του Ruffle (Πειραματικό, μπορεί να μη λειτουργεί).",
"loading": "Φόρτωση…",
"more": "Περισσότερα",
"optional": "προαιρετικό",
"retry": "Δοκιμάστε ξανά",
"role": {
"admin": "Διαχειριστής",
"moderator": "Συντονιστής"
},
"scope_in_timeline": {
"direct": "Άμεσο",
"local": "Τοπικό",
"private": "Μόνο για ακόλουθους",
"public": "Δημόσιο",
"unlisted": "Εκτός Λίστας"
},
"show_less": "Δείξε λιγότερα",
"show_more": "Δείξε περισσότερα"
},
"image_cropper": {
"cancel": "Ακύρωση",
"crop_picture": "Περικοπή εικόνας",
"save": "Αποθήκευση",
"save_without_cropping": "Αποθήκευση χωρίς περικοπή"
},
"importer": {
"success": "Εισήχθη επιτυχώς."
},
"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": "tr",
"uk": "Μεταφράστηκε από τα @:languages.uk",
"zh": "Μεταφράστηκε από τα @:languages.zh"
},
"uk": "Ουκρανικά",
"zh": "Κινεζικά"
},
"lists": {
"create": "Δημιουργία",
"delete": "Διαγραφή λίστας",
"lists": "Λίστες",
"new": "Νέα Λίστα",
"save": "Αποθήκευση αλλαγών",
"search": "Αναζήτηση χρηστών",
"title": "Τίτλος λίστας"
},
"login": {
"authentication_code": "Κωδικός επαλήθευσης",
"description": "Σύνδεση με OAuth",
"enter_recovery_code": "Εισάγετε τον κωδικό ανάκτησης",
"hint": "Συνδεθείτε για να μπείτε στη συζήτηση",
"login": "Σύνδεση",
"logout": "Αποσύνδεση",
"logout_confirm": "Θέλετε σίγουρα να αποσυνδεθείτε;",
"logout_confirm_accept_button": "Αποσύνδεση",
"logout_confirm_cancel_button": "Ακύρωση",
"logout_confirm_title": "Αποσύνδεση",
"password": "Κωδικός πρόσβασης",
"placeholder": "τοονομαχρηστημου",
"recovery_code": "Κωδικός ανάκτησης",
"register": "Εγγραφή",
"username": "Όνομα χρήστη"
},
"media_modal": {
"next": "Επόμενο",
"previous": "Προηγούμενο"
},
"moderation": {
"reports": {
"add_note": "Προσθήκη σημείωσης",
"close": "Κλείσιμο",
"delete_note": "Διαγραφή",
"delete_note_accept": "Ναι, διάγραψέ το",
"delete_note_cancel": "Όχι, κράτα το",
"delete_note_confirm": "Θέλετε σίγουρα να διαγράψετε αυτήν τη σημείωση;",
"note_placeholder": "Αφήστε μια σημείωση",
"notes": "{ count } σημείωση | { count } σημειώσεις",
"statuses": "{ count } ανάρτηση| { count } αναρτήσεις"
}
}
}

View File

@ -380,6 +380,7 @@
"text/x.misskeymarkdown": "MFM" "text/x.misskeymarkdown": "MFM"
}, },
"content_warning": "Content Warning (optional)", "content_warning": "Content Warning (optional)",
"toggle_content_warning": "Toggle content warning",
"default": "Just arrived at Luna Nova Academy", "default": "Just arrived at Luna Nova Academy",
"direct_warning_to_all": "This post will be visible to all the mentioned users.", "direct_warning_to_all": "This post will be visible to all the mentioned users.",
"direct_warning_to_first_only": "This post will only be visible to the mentioned users at the beginning of the message.", "direct_warning_to_first_only": "This post will only be visible to the mentioned users at the beginning of the message.",
@ -600,6 +601,7 @@
"list_aliases_error": "Error fetching aliases: {error}", "list_aliases_error": "Error fetching aliases: {error}",
"list_backups_error": "Error fetching backup list: {error}", "list_backups_error": "Error fetching backup list: {error}",
"lock_account_description": "Restrict your account to approved followers only", "lock_account_description": "Restrict your account to approved followers only",
"permit_followback_description": "Automatically approve requests from already followed users",
"loop_video": "Loop videos", "loop_video": "Loop videos",
"loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")", "loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
"mascot": "Mastodon FE Mascot", "mascot": "Mastodon FE Mascot",
@ -749,6 +751,7 @@
"show_nav_shortcuts": "Show extra navigation shortcuts in top panel", "show_nav_shortcuts": "Show extra navigation shortcuts in top panel",
"show_panel_nav_shortcuts": "Show timeline navigation shortcuts at the top of the panel", "show_panel_nav_shortcuts": "Show timeline navigation shortcuts at the top of the panel",
"show_scrollbars": "Show side column's scrollbars", "show_scrollbars": "Show side column's scrollbars",
"show_page_backgrounds": "Show page-specific backgrounds, e.g. for user profiles",
"show_wider_shortcuts": "Show wider gap between top panel shortcuts", "show_wider_shortcuts": "Show wider gap between top panel shortcuts",
"show_yous": "Show (You)s", "show_yous": "Show (You)s",
"stop_gifs": "Pause animated images until you hover on them", "stop_gifs": "Pause animated images until you hover on them",
@ -922,7 +925,6 @@
"upload_a_photo": "Upload a photo", "upload_a_photo": "Upload a photo",
"useStreamingApi": "Receive posts and notifications real-time", "useStreamingApi": "Receive posts and notifications real-time",
"useStreamingApiWarning": "It's cool use it. If it breaks refresh I guess?", "useStreamingApiWarning": "It's cool use it. If it breaks refresh I guess?",
"use_at_icon": "Display {'@'} symbol as an icon instead of text",
"use_contain_fit": "Don't crop the attachment in thumbnails", "use_contain_fit": "Don't crop the attachment in thumbnails",
"use_one_click_nsfw": "Open NSFW attachments with just one click", "use_one_click_nsfw": "Open NSFW attachments with just one click",
"user_mutes": "Users", "user_mutes": "Users",

View File

@ -425,6 +425,8 @@
"reason_placeholder": "Cette instance modère les inscriptions manuellement.\nExpliquer ce qui motive votre inscription à l'administration.", "reason_placeholder": "Cette instance modère les inscriptions manuellement.\nExpliquer ce qui motive votre inscription à l'administration.",
"register": "Enregistrer", "register": "Enregistrer",
"registration": "Inscription", "registration": "Inscription",
"request_sent": "Votre demande d'enregistrement est en attente d'approbation. Vous recevrez un courriel lorsque votre compte sera approuvé.",
"request_sent_title": "Demande d'abonnement envoyée",
"token": "Jeton d'invitation", "token": "Jeton d'invitation",
"username_placeholder": "ex : misato", "username_placeholder": "ex : misato",
"validations": { "validations": {
@ -918,7 +920,6 @@
"upload_a_photo": "Envoyer une photo", "upload_a_photo": "Envoyer une photo",
"useStreamingApi": "Recevoir les messages et notifications en temps réel", "useStreamingApi": "Recevoir les messages et notifications en temps réel",
"useStreamingApiWarning": "(Non recommandé, expérimental, connu pour rater des messages)", "useStreamingApiWarning": "(Non recommandé, expérimental, connu pour rater des messages)",
"use_at_icon": "Afficher le symbol {'@'} comme une image",
"use_contain_fit": "Ne pas rogner les miniatures des pièces-jointes", "use_contain_fit": "Ne pas rogner les miniatures des pièces-jointes",
"use_one_click_nsfw": "Ouvrir les pièces-jointes sensibles avec un seul clic", "use_one_click_nsfw": "Ouvrir les pièces-jointes sensibles avec un seul clic",
"user_mutes": "Comptes", "user_mutes": "Comptes",

View File

@ -1,11 +1,15 @@
{ {
"about": { "about": {
"bubble_instances": "Instance Bubble Lokal",
"bubble_instances_description": "Instansi yang dipilih oleh admin untuk mewakili instance ini",
"mrf": { "mrf": {
"federation": "Federasi", "federation": "Federasi",
"keyword": { "keyword": {
"ftl_removal": "Penghapusan dari Linimasa \"Jaringan Yang Dikenal\"", "ftl_removal": "Penghapusan dari Linimasa \"Jaringan Yang Dikenal\"",
"is_replaced_by": "→", "is_replaced_by": "→",
"reject": "Tolak" "keyword_policies": "Kebijakan Kata Kunci",
"reject": "Tolak",
"replace": "Ganti"
}, },
"mrf_policies": "Kebijakan MRF yang diaktifkan", "mrf_policies": "Kebijakan MRF yang diaktifkan",
"mrf_policies_desc": "Kebijakan MRF memanipulasi federasi yang terjadi pada instansi ini. Kebijakan berikut adalah yang aktif:", "mrf_policies_desc": "Kebijakan MRF memanipulasi federasi yang terjadi pada instansi ini. Kebijakan berikut adalah yang aktif:",
@ -80,6 +84,7 @@
"keep_open": "Tetap buka pemilih", "keep_open": "Tetap buka pemilih",
"load_all": "Memuat semua {emojiAmount} emoji", "load_all": "Memuat semua {emojiAmount} emoji",
"load_all_hint": "Memuat {saneAmount} emoji pertama, memuat semua emoji dapat menyebabkan masalah performa.", "load_all_hint": "Memuat {saneAmount} emoji pertama, memuat semua emoji dapat menyebabkan masalah performa.",
"recent": "Baru-baru ini digunakan",
"search_emoji": "Cari emoji", "search_emoji": "Cari emoji",
"stickers": "Stiker", "stickers": "Stiker",
"unicode": "Emoji Unicode" "unicode": "Emoji Unicode"
@ -93,9 +98,11 @@
}, },
"features_panel": { "features_panel": {
"media_proxy": "Proxy media", "media_proxy": "Proxy media",
"scope_options": "Opsi cakupan",
"text_limit": "Batas teks", "text_limit": "Batas teks",
"title": "Fitur-fitur", "title": "Fitur-fitur",
"upload_limit": "Batas unggahan" "upload_limit": "Batas unggahan",
"who_to_follow": "Siapa untuk diikuti"
}, },
"file_type": { "file_type": {
"audio": "Audio", "audio": "Audio",
@ -113,6 +120,7 @@
"close": "Tutup", "close": "Tutup",
"confirm": "Konfirmasi", "confirm": "Konfirmasi",
"disable": "Nonaktifkan", "disable": "Nonaktifkan",
"dismiss": "Tutup",
"enable": "Aktifkan", "enable": "Aktifkan",
"error_retry": "Harap coba lagi", "error_retry": "Harap coba lagi",
"flash_content": "Klik untuk menampilkan konten Flash menggunakan Ruffle (Eksperimental, mungkin tidak bekerja).", "flash_content": "Klik untuk menampilkan konten Flash menggunakan Ruffle (Eksperimental, mungkin tidak bekerja).",
@ -132,7 +140,8 @@
"direct": "Langsung", "direct": "Langsung",
"local": "Lokal - hanya instansi kamu yang dapat melihat postingan ini", "local": "Lokal - hanya instansi kamu yang dapat melihat postingan ini",
"private": "Hanya pengikut", "private": "Hanya pengikut",
"public": "Publik" "public": "Publik",
"unlisted": "Tidak Tercantum"
}, },
"show_less": "Tampilkan lebih sedikit", "show_less": "Tampilkan lebih sedikit",
"show_more": "Tampilkan lebih banyak", "show_more": "Tampilkan lebih banyak",
@ -227,6 +236,7 @@
"lists": { "lists": {
"create": "Buat", "create": "Buat",
"delete": "Hapus daftar", "delete": "Hapus daftar",
"following_only": "Batas mengikuti",
"lists": "Daftar", "lists": "Daftar",
"new": "Buat Daftar", "new": "Buat Daftar",
"save": "Simpan perubahan", "save": "Simpan perubahan",
@ -276,6 +286,7 @@
"note_placeholder": "Tinggalkan catatan", "note_placeholder": "Tinggalkan catatan",
"notes": "{ count } catatan", "notes": "{ count } catatan",
"reopen": "Buka kembali", "reopen": "Buka kembali",
"report": "Laporan di",
"reports": "Laporan", "reports": "Laporan",
"resolve": "Selesaikan", "resolve": "Selesaikan",
"show_closed": "Tampilkan yang telah ditutup", "show_closed": "Tampilkan yang telah ditutup",
@ -291,6 +302,8 @@
"administration": "Administrasi", "administration": "Administrasi",
"announcements": "Pengumuman", "announcements": "Pengumuman",
"back": "Kembali", "back": "Kembali",
"bookmarks": "Bookmark",
"bubble_timeline": "Timeline Bubble",
"bubble_timeline_description": "Postingan dari instansi yang dekat dengan instansimu, yang direkomendasikan oleh admin kamu", "bubble_timeline_description": "Postingan dari instansi yang dekat dengan instansimu, yang direkomendasikan oleh admin kamu",
"chats": "Obrolan", "chats": "Obrolan",
"dms": "Pesan langsung", "dms": "Pesan langsung",
@ -309,7 +322,8 @@
"timelines": "Linimasa", "timelines": "Linimasa",
"twkn": "Jaringan Yang Dikenal", "twkn": "Jaringan Yang Dikenal",
"twkn_timeline_description": "Postingan dari seluruh jaringan", "twkn_timeline_description": "Postingan dari seluruh jaringan",
"user_search": "Penelusuran Pengguna" "user_search": "Penelusuran Pengguna",
"who_to_follow": "Siapa untuk diikuti"
}, },
"notifications": { "notifications": {
"broken_favorite": "Postingan tak dikenal, mencarinya…", "broken_favorite": "Postingan tak dikenal, mencarinya…",
@ -385,7 +399,8 @@
"direct": "Langsung - posting hanya kepada pengguna yang disebut", "direct": "Langsung - posting hanya kepada pengguna yang disebut",
"local": "Lokal - postingan tidak akan difederasi", "local": "Lokal - postingan tidak akan difederasi",
"private": "Hanya-pengikut - posting hanya kepada pengikut", "private": "Hanya-pengikut - posting hanya kepada pengikut",
"public": "Publik - posting ke linimasa publik" "public": "Publik - posting ke linimasa publik",
"unlisted": "Tidak Tercantum - Tidak memposting ke timeline publik"
}, },
"scope_notice": { "scope_notice": {
"local": "Postingan ini tidak akan terlihat di instansi lain", "local": "Postingan ini tidak akan terlihat di instansi lain",
@ -402,6 +417,7 @@
"captcha": "CAPTCHA", "captcha": "CAPTCHA",
"email": "Surel", "email": "Surel",
"email_language": "Dalam bahasa apa kamu ingin menerima surel dari server ini?", "email_language": "Dalam bahasa apa kamu ingin menerima surel dari server ini?",
"fullname": "Tampilan Nama",
"fullname_placeholder": "cth. Atsuko Kagari", "fullname_placeholder": "cth. Atsuko Kagari",
"new_captcha": "Klik gambarnya untuk mendapatkan captcha baru", "new_captcha": "Klik gambarnya untuk mendapatkan captcha baru",
"password_confirm": "Konfirmasi kata sandi", "password_confirm": "Konfirmasi kata sandi",
@ -424,6 +440,7 @@
}, },
"remote_user_resolver": { "remote_user_resolver": {
"error": "Tidak ditemukan.", "error": "Tidak ditemukan.",
"remote_user_resolver": "Pencarian pengguna jarak jauh",
"searching_for": "Mencari" "searching_for": "Mencari"
}, },
"search": { "search": {
@ -437,16 +454,22 @@
"select_all": "Pilih semua" "select_all": "Pilih semua"
}, },
"settings": { "settings": {
"accent": "Aksen",
"account_alias": "Akun alias",
"account_backup": "Pencadangan akun", "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_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", "account_privacy": "Privasi",
"add_alias_error": "Gagal menambahkan alias: {error}",
"add_backup": "Buat cadangan baru", "add_backup": "Buat cadangan baru",
"add_backup_error": "Gagal menambahkan cadangan baru: {error}", "add_backup_error": "Gagal menambahkan cadangan baru: {error}",
"added_alias": "Alias telah ditambahkan.",
"added_backup": "Cadangan baru ditambahkan.", "added_backup": "Cadangan baru ditambahkan.",
"allow_following_move": "Ikuti otomatis apabila akun yang diikuti pindah", "allow_following_move": "Ikuti otomatis apabila akun yang diikuti pindah",
"always_show_post_button": "Selalu tampilkan tombol posting baru yang mengambang",
"app_name": "Nama aplikasi", "app_name": "Nama aplikasi",
"attachmentRadius": "Lampiran", "attachmentRadius": "Lampiran",
"attachments": "Lampiran", "attachments": "Lampiran",
"autohide_floating_post_button": "Sembunyikan tombol buat posting secara otomatis (Ponsel)",
"avatar": "Avatar", "avatar": "Avatar",
"avatarAltRadius": "Avatar (notifikasi)", "avatarAltRadius": "Avatar (notifikasi)",
"avatarRadius": "Avatar", "avatarRadius": "Avatar",
@ -474,6 +497,8 @@
"changed_password": "Kata sandi berhasil diubah!", "changed_password": "Kata sandi berhasil diubah!",
"chatMessageRadius": "Pesan obrolan", "chatMessageRadius": "Pesan obrolan",
"checkboxRadius": "Kotak centang", "checkboxRadius": "Kotak centang",
"collapse_subject": "Tampilkan post dengan peringatan",
"columns": "Kolum",
"composing": "Menulis", "composing": "Menulis",
"confirm_dialogs": "Perlukan konfirmasi sebelum:", "confirm_dialogs": "Perlukan konfirmasi sebelum:",
"confirm_dialogs_approve_follow": "Menerima permintaan mengikuti", "confirm_dialogs_approve_follow": "Menerima permintaan mengikuti",
@ -484,17 +509,23 @@
"confirm_dialogs_repeat": "Mengulangi postingan", "confirm_dialogs_repeat": "Mengulangi postingan",
"confirm_dialogs_unfollow": "Berhenti mengikuti seseorang", "confirm_dialogs_unfollow": "Berhenti mengikuti seseorang",
"confirm_new_password": "Konfirmasi kata sandi baru", "confirm_new_password": "Konfirmasi kata sandi baru",
"confirmation_dialogs": "Opsi Konfirmasi",
"conversation_display": "Gaya tampilan percakapan", "conversation_display": "Gaya tampilan percakapan",
"conversation_display_linear": "Gaya Linier",
"conversation_display_tree": "Bercabang", "conversation_display_tree": "Bercabang",
"conversation_other_replies_button": "Tampilkan tombol \"Balasan lainnya\"",
"conversation_other_replies_button_below": "Di bawah postingan", "conversation_other_replies_button_below": "Di bawah postingan",
"conversation_other_replies_button_inside": "Di postingan", "conversation_other_replies_button_inside": "Di postingan",
"current_avatar": "Avatarmu saat ini", "current_avatar": "Avatarmu saat ini",
"current_mascot": "Maskot utamamu",
"current_password": "Kata sandi saat ini", "current_password": "Kata sandi saat ini",
"data_import_export_tab": "Impor / ekspor data", "data_import_export_tab": "Impor / ekspor data",
"default_vis": "Cakupan visibilitas default",
"delete_account": "Hapus akun", "delete_account": "Hapus akun",
"delete_account_description": "Hapus data kamu secara permanen dan nonaktifkan akunmu.", "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_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.", "delete_account_instructions": "Ketik kata sandi kamu pada input di bawah untuk mengonfirmasi penghapusan akun.",
"disable_sticky_headers": "Jangan tempel tajuk kolom ke bagian atas layar",
"discoverable": "Izinkan penelusuran akun ini pada hasil pencarian dan layanan lainnya", "discoverable": "Izinkan penelusuran akun ini pada hasil pencarian dan layanan lainnya",
"domain_mutes": "Domain", "domain_mutes": "Domain",
"download_backup": "Unduh", "download_backup": "Unduh",
@ -505,6 +536,7 @@
"expert_mode": "Tampilkan pengaturan lanjutan", "expert_mode": "Tampilkan pengaturan lanjutan",
"expire_posts_enabled": "Hapus postingan setelah jumlah hari yang ditentukan", "expire_posts_enabled": "Hapus postingan setelah jumlah hari yang ditentukan",
"expire_posts_input_placeholder": "Jumlah hari", "expire_posts_input_placeholder": "Jumlah hari",
"export_theme": "Simpan preset",
"file_export_import": { "file_export_import": {
"backup_restore": "Pencadangan pengaturan", "backup_restore": "Pencadangan pengaturan",
"backup_settings": "Cadangkan pengaturan ke berkas", "backup_settings": "Cadangkan pengaturan ke berkas",
@ -519,7 +551,11 @@
}, },
"filtering": "Penyaringan", "filtering": "Penyaringan",
"filtering_explanation": "Semua postingan yang mengandung kata-kata ini akan dibisukan, satu kata per baris", "filtering_explanation": "Semua postingan yang mengandung kata-kata ini akan dibisukan, satu kata per baris",
"follow_export": "Ekspor Follow",
"follow_export_button": "Export yang kamu ikuti ke dalam file csv",
"follow_import": "Import pengikut",
"follow_import_error": "Terjadi kesalahan ketika mengimpor pengikut", "follow_import_error": "Terjadi kesalahan ketika mengimpor pengikut",
"follows_imported": "Pengguna yang diikuti telak diimpor! Proses mungkin membutuhkan beberapa saat.",
"fun": "Seru", "fun": "Seru",
"general": "Umum", "general": "Umum",
"greentext": "Panah meme", "greentext": "Panah meme",

View File

@ -4,6 +4,7 @@
"mrf": { "mrf": {
"federation": "フェデレーション", "federation": "フェデレーション",
"keyword": { "keyword": {
"ftl_removal": "「せつぞくしているすべてのネットワーク」タイムラインからのぞく",
"is_replaced_by": "→", "is_replaced_by": "→",
"keyword_policies": "キーワードポリシー", "keyword_policies": "キーワードポリシー",
"reject": "おことわり", "reject": "おことわり",
@ -120,7 +121,7 @@
"error_retry": "もういちど、ためしてください", "error_retry": "もういちど、ためしてください",
"flash_content": "クリックすると、Ruffle をつかって、フラッシュさくひんをひょうじします。(うまくうごかないかもしれません)", "flash_content": "クリックすると、Ruffle をつかって、フラッシュさくひんをひょうじします。(うまくうごかないかもしれません)",
"flash_fail": "フラッシュさくひんのロードに、しっぱいしました。コンソールに、くわしいことがかかれています。", "flash_fail": "フラッシュさくひんのロードに、しっぱいしました。コンソールに、くわしいことがかかれています。",
"flash_security": "フラッシュさくひんは、あぶないことをしてくるかもしれないので、ちゅういしてください。", "flash_security": "フラッシュさくひんは、あなたのコンピューターに、あぶないことをしてくるかもしれないので、ちゅういしてください。",
"generic_error": "エラーになりました", "generic_error": "エラーになりました",
"loading": "ロードしています…", "loading": "ロードしています…",
"more": "つづき", "more": "つづき",
@ -533,7 +534,7 @@
"expert_mode": "こまかいせっていをひょうじ", "expert_mode": "こまかいせっていをひょうじ",
"export_theme": "セーブ", "export_theme": "セーブ",
"filtering": "フィルタリング", "filtering": "フィルタリング",
"filtering_explanation": "これらのことばをふくむすべてのものがミュートされます。1ぎょうに1つのことばをかいてください", "filtering_explanation": "これらのことばをふくむすべてのとうこうがミュートされます。1ぎょうに1つのことばをかいてください",
"follow_export": "フォローのエクスポート", "follow_export": "フォローのエクスポート",
"follow_export_button": "エクスポート", "follow_export_button": "エクスポート",
"follow_import": "フォローインポート", "follow_import": "フォローインポート",
@ -887,6 +888,7 @@
"media": "メディア", "media": "メディア",
"mention": "メンション", "mention": "メンション",
"mute": "ミュート", "mute": "ミュート",
"mute_domain": "ドメインをブロック",
"mute_progress": "ミュートしています…", "mute_progress": "ミュートしています…",
"muted": "ミュートしています!", "muted": "ミュートしています!",
"per_day": "/日", "per_day": "/日",
@ -897,6 +899,7 @@
"subscribe": "サブスクライブ", "subscribe": "サブスクライブ",
"unblock": "ブロックをやめる", "unblock": "ブロックをやめる",
"unblock_progress": "ブロックをとりけしています…", "unblock_progress": "ブロックをとりけしています…",
"unfollow_confirm": "{user}のフォローをやめますか?",
"unmute": "ミュートをやめる", "unmute": "ミュートをやめる",
"unmute_progress": "ミュートをとりけしています…", "unmute_progress": "ミュートをとりけしています…",
"unsubscribe": "サブスクライブをやめる" "unsubscribe": "サブスクライブをやめる"

View File

@ -921,7 +921,6 @@
"upload_a_photo": "画像をアップロード", "upload_a_photo": "画像をアップロード",
"useStreamingApi": "投稿と通知を、すぐに受け取る", "useStreamingApi": "投稿と通知を、すぐに受け取る",
"useStreamingApiWarning": "(実験中で、投稿を取りこぼすかもしれないので、おすすめしません)", "useStreamingApiWarning": "(実験中で、投稿を取りこぼすかもしれないので、おすすめしません)",
"use_at_icon": "{'@'}マークをアイコンにする",
"use_contain_fit": "画像のサムネイルを、切り抜かない", "use_contain_fit": "画像のサムネイルを、切り抜かない",
"use_one_click_nsfw": "NSFWなファイルを1クリックで開く", "use_one_click_nsfw": "NSFWなファイルを1クリックで開く",
"user_mutes": "ユーザー", "user_mutes": "ユーザー",

View File

@ -21,6 +21,7 @@ const loaders = {
ga: () => import('./ga.json'), ga: () => import('./ga.json'),
he: () => import('./he.json'), he: () => import('./he.json'),
hu: () => import('./hu.json'), hu: () => import('./hu.json'),
id: () => import('./id.json'),
it: () => import('./it.json'), it: () => import('./it.json'),
ja: () => import('./ja_pedantic.json'), ja: () => import('./ja_pedantic.json'),
ja_easy: () => import('./ja_easy.json'), ja_easy: () => import('./ja_easy.json'),
@ -35,6 +36,7 @@ const loaders = {
sk: () => import('./sk.json'), sk: () => import('./sk.json'),
te: () => import('./te.json'), te: () => import('./te.json'),
uk: () => import('./uk.json'), uk: () => import('./uk.json'),
vi: () => import('./vi.json'),
zh: () => import('./zh.json'), zh: () => import('./zh.json'),
zh_Hant: () => import('./zh_Hant.json') zh_Hant: () => import('./zh_Hant.json')
} }

View File

@ -918,7 +918,6 @@
"upload_a_photo": "Foto uploaden", "upload_a_photo": "Foto uploaden",
"useStreamingApi": "Berichten en meldingen in real-time ontvangen", "useStreamingApi": "Berichten en meldingen in real-time ontvangen",
"useStreamingApiWarning": "Iets experimenteels met berichten streamen uwu miss kun je beter uit laten ofzo?", "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_blurhash": "Waas tonen over NSFW-miniaturen",
"use_contain_fit": "Bijlage in miniaturen niet bijsnijden", "use_contain_fit": "Bijlage in miniaturen niet bijsnijden",
"use_one_click_nsfw": "Gevoelige bijlagen met slechts één klik openen", "use_one_click_nsfw": "Gevoelige bijlagen met slechts één klik openen",

View File

@ -30,7 +30,7 @@
"simple_policies": "Políticas especificas do domínio" "simple_policies": "Políticas especificas do domínio"
} }
}, },
"staff": "Staff" "staff": "Equipe"
}, },
"announcements": { "announcements": {
"all_day_prompt": "Este evento dura o dia inteiro", "all_day_prompt": "Este evento dura o dia inteiro",
@ -167,7 +167,7 @@
"password": "Palavra-passe", "password": "Palavra-passe",
"placeholder": "ex. lain", "placeholder": "ex. lain",
"recovery_code": "Código de recuperação", "recovery_code": "Código de recuperação",
"register": "Registar", "register": "Registrar",
"username": "Nome de Utilizador" "username": "Nome de Utilizador"
}, },
"media_modal": { "media_modal": {
@ -244,7 +244,7 @@
"text/plain": "Texto puro" "text/plain": "Texto puro"
}, },
"content_warning": "Assunto (opcional)", "content_warning": "Assunto (opcional)",
"default": "Acabei de chegar a Lisboa.", "default": "Acabei de chegar em",
"direct_warning_to_all": "Esta publicação será visível para todos os utilizadores mencionados.", "direct_warning_to_all": "Esta publicação será visível para todos os utilizadores mencionados.",
"direct_warning_to_first_only": "Esta publicação só será visível para os utilizadores mencionados no início da mensagem.", "direct_warning_to_first_only": "Esta publicação só será visível para os utilizadores mencionados no início da mensagem.",
"empty_status_error": "Não consegues publicar um post vazio e sem ficheiros", "empty_status_error": "Não consegues publicar um post vazio e sem ficheiros",
@ -268,7 +268,7 @@
}, },
"registration": { "registration": {
"bio": "Biografia", "bio": "Biografia",
"bio_placeholder": "ex.\nOlá, sou a Lain\nSou uma menina de anime que vive no Japão suburbano. Devem conhecer-me do \"the Wired\".", "bio_placeholder": "Ex.:\nBem-vindo a minha bio.\nEu amo assistir anime e jogar. Eu espero que possamos ser amigos!",
"captcha": "CAPTCHA", "captcha": "CAPTCHA",
"email": "Endereço de e-mail", "email": "Endereço de e-mail",
"fullname": "Nome para exibição", "fullname": "Nome para exibição",
@ -401,7 +401,7 @@
"scan": { "scan": {
"desc": "Utilizando a sua aplicação de dois fatores, faça scan deste código QR ou insira a chave de texto:", "desc": "Utilizando a sua aplicação de dois fatores, faça scan deste código QR ou insira a chave de texto:",
"secret_code": "Chave", "secret_code": "Chave",
"title": "Scan" "title": "Escanear"
}, },
"setup_otp": "Configurar palavra-passe de utilização única", "setup_otp": "Configurar palavra-passe de utilização única",
"title": "Autenticação de Dois Fatores", "title": "Autenticação de Dois Fatores",
@ -566,7 +566,7 @@
"fine_print": "Leia nosso {0} para não aprender nada!", "fine_print": "Leia nosso {0} para não aprender nada!",
"header": "Pré-visualizar", "header": "Pré-visualizar",
"header_faint": "Isto está bem", "header_faint": "Isto está bem",
"input": "Acabei de chegar a Lisboa.", "input": "Acabei de chegar na Lua",
"link": "um belo link", "link": "um belo link",
"mono": "conteúdo", "mono": "conteúdo",
"text": "Vários {0} e {1}" "text": "Vários {0} e {1}"
@ -648,7 +648,7 @@
"type_domains_to_mute": "Pesquisar domínios para silenciar", "type_domains_to_mute": "Pesquisar domínios para silenciar",
"upload_a_photo": "Enviar uma foto", "upload_a_photo": "Enviar uma foto",
"useStreamingApi": "Receber publicações e notificações em tempo real", "useStreamingApi": "Receber publicações e notificações em tempo real",
"useStreamingApiWarning": "(não recomendado, experimental, pode omitir publicações)", "useStreamingApiWarning": "É legal usar isso. Se der algum problema, atualize, eu acho?",
"use_contain_fit": "Não cortar o anexo na miniatura", "use_contain_fit": "Não cortar o anexo na miniatura",
"use_one_click_nsfw": "Abrir anexos sensíveis com um clique", "use_one_click_nsfw": "Abrir anexos sensíveis com um clique",
"user_mutes": "Utilizadores", "user_mutes": "Utilizadores",

34
src/i18n/th.json Normal file
View File

@ -0,0 +1,34 @@
{
"about": {
"bubble_instances": "เซิร์ฟเวอร์เพื่อนบ้าน",
"bubble_instances_description": "เซิร์ฟเวอร์ที่แอดมินตั้งค่าไว้เพื่อแสดงเป็นเพื่อนบ้าน",
"mrf": {
"keyword": {
"keyword_policies": "นโยบายของคีย์เวิร์ดค้นหา",
"reject": "ไม่อนุมัติ",
"replace": "แทนที่"
},
"simple": {
"accept": "ยอมรับ",
"accept_desc": "เซิร์ฟเวอร์นี้จะรับข้อความเฉพาะเซิฟร์เวอร์ต่อไปนี้:",
"instance": "เซิฟเวอร์",
"media_nsfw": "มีเดียถูกบังคับตั้งให้เป็น NSFW",
"not_applicable": "ไม่สามารถใช้ได้",
"quarantine": "คัดกรอง",
"reason": "เหตุผล",
"reject": "ปฎิเสธ"
}
},
"staff": "ผู้ดูแล"
},
"announcements": {
"cancel_edit_action": "ยกเลิก",
"close_error": "ปิด",
"delete_action": "ลบประกาศ",
"edit_action": "แก้ไขประกาศ",
"end_time_prompt": "เวลาสิ้นสุด: ",
"mark_as_read_action": "ตั้งเป็นอ่านแล้ว",
"page_header": "ประกาศ",
"post_action": "โพสต์"
}
}

View File

@ -49,6 +49,86 @@
"post_error": "Hata: {error}", "post_error": "Hata: {error}",
"post_form_header": "Post sonrası", "post_form_header": "Post sonrası",
"post_placeholder": "Duyuru içeriği", "post_placeholder": "Duyuru içeriği",
"published_time_display": "{time} tarihinde yayınlandı" "published_time_display": "{time} tarihinde yayınlandı",
"start_time_display": "{time} saatinde başlar",
"start_time_prompt": "Başlangıç saati: ",
"submit_edit_action": "Gönder",
"title": "Duyuru"
},
"chats": {
"chats": "Sohbetler",
"delete": "Sil",
"delete_confirm": "Bu mesajı gerçekten silmek istiyor musun?",
"empty_chat_list_placeholder": "Henüz hiç sohbetiniz yok. Yeni bir sohbet başlatın!",
"empty_message_error": "Boş mesaj gönderilemiyor",
"error_loading_chat": "Sohbet yüklenirken bir şeyler ters gitti.",
"error_sending_message": "Mesajı gönderirken bir şeyler ters gitti.",
"message_user": "{nickname} mesajı",
"more": "Daha",
"new": "Yeni Sohbet",
"you": "Sen:"
},
"display_date": {
"today": "Bugün"
},
"domain_mute_card": {
"mute": "Sesini kapat",
"mute_progress": "Ses kapatılıyor…",
"unmute": "Sesini aç",
"unmute_progress": "Sessize alınıyor…"
},
"emoji": {
"add_emoji": "Emoji ekle",
"custom": "Özel emoji",
"emoji": "Emoji",
"keep_open": "Seçiciyi açık tut",
"load_all": "Tüm {emojiAmount} emoji yükleniyor",
"load_all_hint": "İlk {saneAmount} emoji yüklendi, tüm emojilerin yüklenmesi performans sorunlarına neden olabilir.",
"recent": "Son zamanlarda kullanılmış",
"search_emoji": "Emoji ara",
"stickers": ıkartmalar",
"unicode": "Unicode emojisi"
},
"errors": {
"storage_unavailable": "Pleroma, tarayıcı depolama alanına erişemedi. Oturum açma bilgileriniz veya yerel ayarlarınız kaydedilmeyecek ve beklenmeyen sorunlarla karşılaşabilirsiniz. Çerezleri etkinleştirmeyi deneyin."
},
"exporter": {
"export": "Dışa aktar",
"processing": "İşleniyor, yakında dosyanızı indirmeniz istenecek"
},
"features_panel": {
"media_proxy": "Medya proxy'si",
"scope_options": "Kapsam seçenekleri",
"text_limit": "Metin sınırı",
"title": "Özellikler",
"upload_limit": "Yükleme sınırı",
"who_to_follow": "Kimi takip etmeli"
},
"file_type": {
"audio": "Ses",
"file": "Dosya",
"image": "Resim",
"video": "Video"
},
"finder": {
"error_fetching_user": "Kullanıcı getirilirken hata oluştu",
"find_user": "Kullanıcı bul"
},
"general": {
"apply": "Uygula",
"cancel": "İptal",
"close": "Kapalı",
"confirm": "Onayla",
"disable": "Devre dışı bırak",
"dismiss": "Reddetmek",
"enable": "Etkinleştir",
"error_retry": "Lütfen tekrar deneyin",
"flash_content": "Ruffle kullanarak Flash içeriğini göstermek için tıklayın (Deneysel, çalışmayabilir).",
"flash_fail": "Flash içeriği yüklenemedi, ayrıntılar için konsola bakın.",
"flash_security": "Flash içeriği hala isteğe bağlı kod olduğundan, bunun potansiyel olarak tehlikeli olabileceğini unutmayın.",
"generic_error": "Bir hata oluştu",
"loading": "Yükleniyor…",
"more": "Daha",
"optional": "Seçenek"
} }
} }

View File

@ -23,6 +23,7 @@
"media_nsfw_desc": "Сервер примусово позначає дражливими медіа з таких серверів:", "media_nsfw_desc": "Сервер примусово позначає дражливими медіа з таких серверів:",
"media_removal": "Видалення медіа", "media_removal": "Видалення медіа",
"media_removal_desc": "Сервер вилучає медіа в дописах із таких серверів:", "media_removal_desc": "Сервер вилучає медіа в дописах із таких серверів:",
"not_applicable": "Не застосовується",
"quarantine": "Карантин", "quarantine": "Карантин",
"quarantine_desc": "Сервер не надсилатиме дописів таким серверам:", "quarantine_desc": "Сервер не надсилатиме дописів таким серверам:",
"reason": "Підстава", "reason": "Підстава",
@ -31,7 +32,7 @@
"simple_policies": "Відносно цілих серверів" "simple_policies": "Відносно цілих серверів"
} }
}, },
"staff": "Адміністрація" "staff": "Команда"
}, },
"announcements": { "announcements": {
"all_day_prompt": "Триває весь день", "all_day_prompt": "Триває весь день",
@ -71,10 +72,10 @@
"today": "Сьогодні" "today": "Сьогодні"
}, },
"domain_mute_card": { "domain_mute_card": {
"mute": "Ігнорувати", "mute": "Приглушити",
"mute_progress": "Вимикаю…", "mute_progress": "Глушіння…",
"unmute": "Вимкнути заглушення", "unmute": "Відновити",
"unmute_progress": "Вмикаю…" "unmute_progress": "Відновлення…"
}, },
"emoji": { "emoji": {
"add_emoji": "Додати емоджі", "add_emoji": "Додати емоджі",
@ -83,6 +84,7 @@
"keep_open": "Тримати панель відкритою", "keep_open": "Тримати панель відкритою",
"load_all": "Всі {emojiAmount} емоджі завантажуються", "load_all": "Всі {emojiAmount} емоджі завантажуються",
"load_all_hint": "Завантажено {saneAmount} емоджі, більша кількість може спричинити гальмування.", "load_all_hint": "Завантажено {saneAmount} емоджі, більша кількість може спричинити гальмування.",
"recent": "Нещодавно вжиті",
"search_emoji": "Пошук емоджі", "search_emoji": "Пошук емоджі",
"stickers": "Наліпки", "stickers": "Наліпки",
"unicode": "Стандартні емоджі" "unicode": "Стандартні емоджі"
@ -131,19 +133,19 @@
"peek": "Глянути", "peek": "Глянути",
"retry": "Спробуйте ще раз", "retry": "Спробуйте ще раз",
"role": { "role": {
"admin": "Адмініструє", "admin": "Адміністрація",
"moderator": "Модерує" "moderator": "Модерування"
}, },
"scope_in_timeline": { "scope_in_timeline": {
"direct": "Особисто", "direct": "Особисто",
"local": "Місцево — допис видно тільки на вашому сервері", "local": "Місцево — допис видно тільки на вашому сервері",
"private": "Приватно", "private": "Приватно",
"public": "Загальнодоступно", "public": "Загальнодоступно",
"unlisted": "Непублічно" "unlisted": "Приховано"
}, },
"show_less": "Показати менше", "show_less": "Показати менше",
"show_more": "Показати більше", "show_more": "Показати більше",
"submit": "Відправити", "submit": "Надіслати",
"verify": "Перевірити" "verify": "Перевірити"
}, },
"image_cropper": { "image_cropper": {
@ -154,7 +156,7 @@
}, },
"importer": { "importer": {
"error": "Під час імпортування файлу сталася помилка.", "error": "Під час імпортування файлу сталася помилка.",
"submit": "Відправити", "submit": "Надіслати",
"success": "Імпортовано успішно." "success": "Імпортовано успішно."
}, },
"interactions": { "interactions": {
@ -164,8 +166,72 @@
"moves": "Переїзди користувач_ок" "moves": "Переїзди користувач_ок"
}, },
"languages": { "languages": {
"ar": "арабська",
"az": "азербайджанська",
"bg": "болгарська",
"cs": "чеська",
"da": "данська",
"de": "німецька",
"el": "грецька",
"en": "англійська", "en": "англійська",
"uk": "українська" "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": "Перекладено з арабської",
"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": "Перекладено з турецької",
"uk": "Перекладено з української",
"zh": "Перекладено з китайської"
},
"uk": "українська",
"zh": "китайська"
}, },
"lists": { "lists": {
"create": "Створити", "create": "Створити",
@ -173,6 +239,7 @@
"following_only": "Лише за ким ви стежите", "following_only": "Лише за ким ви стежите",
"lists": "Списки", "lists": "Списки",
"new": "Новий список", "new": "Новий список",
"save": "Зберегти зміни",
"search": "Знайти користувач_ок", "search": "Знайти користувач_ок",
"title": "Назва списку" "title": "Назва списку"
}, },
@ -199,6 +266,7 @@
"username": "Логін" "username": "Логін"
}, },
"media_modal": { "media_modal": {
"counter": "{current} / {total}",
"hide": "Закрити медіаперегляд", "hide": "Закрити медіаперегляд",
"next": "Наступна", "next": "Наступна",
"previous": "Попередня" "previous": "Попередня"
@ -222,7 +290,9 @@
"reports": "Скарги", "reports": "Скарги",
"resolve": "Вирішено", "resolve": "Вирішено",
"show_closed": "Показати закриті", "show_closed": "Показати закриті",
"statuses": "Дописів: { count } | Дописів: { count }" "statuses": "Дописів: { count } | Дописів: { count }",
"tag_policy_notice": "Увімкніть MRF TagPolicy, щоб обмежувати окремі обліковки",
"tags": "Обмежити обліковку"
}, },
"statuses": "Дописи", "statuses": "Дописи",
"users": "Користувач_ки" "users": "Користувач_ки"
@ -242,21 +312,21 @@
"home_timeline_description": "Дописи від тих, на кого ви підписані", "home_timeline_description": "Дописи від тих, на кого ви підписані",
"interactions": "Взаємодії", "interactions": "Взаємодії",
"lists": "Списки", "lists": "Списки",
"mentions": "Згадування", "mentions": "Згадки",
"moderation": "Модерування", "moderation": "Модерування",
"preferences": "Налаштування", "preferences": "Налаштування",
"public_timeline_description": "Загальнодоступні дописи всіх на цьому сервері", "public_timeline_description": "Загальнодоступні дописи всіх на цьому сервері",
"public_tl": "Спільна стрічка", "public_tl": "Спільна стрічка",
"search": "Пошук", "search": "Пошук",
"timeline": "Домашня стрічка", "timeline": "Стрічка",
"timelines": "Стрічки", "timelines": "Стрічки",
"twkn": "Уся відома мережа", "twkn": "Уся відома мережа",
"twkn_timeline_description": "Дописи з усієї мережі", "twkn_timeline_description": "Дописи з усієї мережі",
"user_search": "Пошук користувач_ок", "user_search": "Пошук користувач_ок",
"who_to_follow": "Кого відстежувати" "who_to_follow": "На кого підписатись"
}, },
"notifications": { "notifications": {
"broken_favorite": "Невідомий допис, шукаю його…", "broken_favorite": "Допис невідомий, пошук триває…",
"error": "Помилка при оновленні сповіщень: {0}", "error": "Помилка при оновленні сповіщень: {0}",
"favorited_you": "вподобує", "favorited_you": "вподобує",
"follow_request": "хоче підписатись", "follow_request": "хоче підписатись",
@ -285,12 +355,12 @@
"polls": { "polls": {
"add_option": "Додати варіант", "add_option": "Додати варіант",
"add_poll": "Додати опитування", "add_poll": "Додати опитування",
"expired": "Опитування закінчилось {0} тому", "expired": "Завершено {0} тому",
"expires_in": "Опитування закінчується через {0}", "expires_in": "До кінця {0}",
"expiry": "Термін опитування", "expiry": "Термін опитування",
"multiple_choices": "Декілька варіантів", "multiple_choices": "Декілька варіантів",
"not_enough_options": "Замало унікальних варіантів в опитуванні", "not_enough_options": "Замало унікальних варіантів в опитуванні",
"option": "Відповідь", "option": "Варіант",
"people_voted_count": "Осіб: {count} | Осіб: {count}", "people_voted_count": "Осіб: {count} | Осіб: {count}",
"single_choice": "Один варіант", "single_choice": "Один варіант",
"type": "Тип опитування", "type": "Тип опитування",
@ -322,7 +392,7 @@
"media_not_sensitive_warning": "Ви вказали засторогу, але не позначили вкладення дражливими!", "media_not_sensitive_warning": "Ви вказали засторогу, але не позначили вкладення дражливими!",
"new_status": "Новий допис", "new_status": "Новий допис",
"post": "Розмістити", "post": "Розмістити",
"posting": "Відправляється", "posting": "Розміщення",
"preview": "Попередній перегляд", "preview": "Попередній перегляд",
"preview_empty": "Порожньо", "preview_empty": "Порожньо",
"scope": { "scope": {
@ -340,11 +410,13 @@
} }
}, },
"registration": { "registration": {
"awaiting_email_confirmation": "Вам надіслано лист підтвердження створення обліковки. Перевірте е-пошту, щоб завершити реєстрацію.",
"awaiting_email_confirmation_title": "Очікування підтвердження поштою",
"bio": "Опис", "bio": "Опис",
"bio_placeholder": "як-от:\nВолинянка. Подорожую.\nДемократична соціалістка. Пишу драматичні поеми. Жити хочу! Геть, думи сумні!", "bio_placeholder": "як-от:\nВолинянка. Подорожую.\nДемократична соціалістка. Пишу драматичні поеми. Жити хочу! Геть, думи сумні!",
"captcha": "CAPTCHA", "captcha": "CAPTCHA",
"email": "Е-пошта", "email": "Е-пошта",
"email_language": "Якою мовою отримувати листи з сервера?", "email_language": "Мова листів сервера",
"fullname": "Ім'я", "fullname": "Ім'я",
"fullname_placeholder": "як-от Лариса Косач", "fullname_placeholder": "як-от Лариса Косач",
"new_captcha": "Натисніть на зображення, щоб оновити код, якщо він нерозбірливий", "new_captcha": "Натисніть на зображення, щоб оновити код, якщо він нерозбірливий",
@ -353,6 +425,8 @@
"reason_placeholder": "Запити на реєстрацію підтверджуються вручну.\nРозкажіть адміністрації, чому хочете зареєструватися.", "reason_placeholder": "Запити на реєстрацію підтверджуються вручну.\nРозкажіть адміністрації, чому хочете зареєструватися.",
"register": "Зареєструватися", "register": "Зареєструватися",
"registration": "Реєстрація", "registration": "Реєстрація",
"request_sent": "Ваш запит на реєстрацію надіслано на розгляд. Ви отримаєте лист, коли вашу реєстрацію схвалять.",
"request_sent_title": "Запит на реєстрацію надіслано",
"token": "Ключ запрошення", "token": "Ключ запрошення",
"username_placeholder": "як-от lesia", "username_placeholder": "як-от lesia",
"validations": { "validations": {
@ -370,7 +444,7 @@
"searching_for": "Шукаю" "searching_for": "Шукаю"
}, },
"search": { "search": {
"hashtags": "Хештеги", "hashtags": "Теги",
"no_results": "Немає результатів", "no_results": "Немає результатів",
"people": "Люди", "people": "Люди",
"people_talking": "{count} людей говорять про це", "people_talking": "{count} людей говорять про це",
@ -382,34 +456,34 @@
"settings": { "settings": {
"accent": "Акцент", "accent": "Акцент",
"account_alias": "Інші обліковки", "account_alias": "Інші обліковки",
"account_alias_table_head": "Нова обліковка", "account_alias_table_head": "Попередня обліковка",
"account_backup": "Резервне копіювання обліковки", "account_backup": "Резервне копіювання обліковки",
"account_backup_description": "Можете завантажити архів даних і дописів своєї обліковки. Але імпортувати їх на іншу обліковку поки неможливо.", "account_backup_description": "Можете завантажити архів даних і дописів своєї обліковки. Але імпортувати їх на іншу обліковку поки неможливо.",
"account_backup_table_head": "Резервна копія", "account_backup_table_head": "Резервна копія",
"account_privacy": "Приватність", "account_privacy": "Приватність",
"add_alias_error": "Помилка додання нової обліковки: {error}", "add_alias_error": "Помилка додання обліковки: {error}",
"add_backup": "Створити резервну копію", "add_backup": "Створити резервну копію",
"add_backup_error": "Помилка резервного копіювання: {error}", "add_backup_error": "Помилка резервного копіювання: {error}",
"added_alias": "Нову обліковку додано.", "added_alias": "Обліковку додано.",
"added_backup": "Резервну копію створено.", "added_backup": "Резервну копію створено.",
"allow_following_move": "Дозволити автостеження при переміщенні на інший інстанс", "allow_following_move": "Дозволити автостеження при переміщенні на інший інстанс",
"always_show_post_button": "Завжди показувати кнопку «Новий допис»", "always_show_post_button": "Завжди показувати кнопку «Новий допис»",
"app_name": "Назва програми", "app_name": "Назва програми",
"attachmentRadius": "Вкладення", "attachmentRadius": "Вкладення",
"attachments": "Вкладення", "attachments": "Вкладення",
"autohide_floating_post_button": "Автоматично ховати кнопку \"Новий допис\" (в мобільній версії)", "autohide_floating_post_button": "Автоматично ховати кнопку «Новий допис» (на мобільному)",
"avatar": "Аватар", "avatar": "Аватар",
"avatarAltRadius": "Аватари у сповіщеннях", "avatarAltRadius": "Аватари у сповіщеннях",
"avatarRadius": "Аватари", "avatarRadius": "Аватари",
"avatar_size_instruction": "Рекомендований мінімальний розмір для зображень аватара становить 150x150 пікселів.", "avatar_size_instruction": "Рекомендований мінімальний розмір для зображень аватара становить 150x150 пікселів.",
"background": "Обкладинка", "background": "Тло",
"backup_not_ready": "Резервна копія ще не готова.", "backup_not_ready": "Резервна копія ще не готова.",
"bio": "Опис", "bio": "Опис",
"block_export": "Експорт блокувань", "block_export": "Експорт блокувань",
"block_export_button": "Експорт блокувань у файл CSV", "block_export_button": "Експорт блокувань у CSV-файл",
"block_import": "Імпорт блокувань", "block_import": "Імпорт блокувань",
"block_import_error": "Помилка імпортування блокувань", "block_import_error": "Помилка імпорту блокувань",
"blocks_imported": "Блокування імпортовані! Їх обробка триватиме певний час.", "blocks_imported": "Блокування імпортовано! Їх буде застосовано поступово.",
"blocks_tab": "Блокування", "blocks_tab": "Блокування",
"bot": "Це обліковка бота", "bot": "Це обліковка бота",
"btnRadius": "Кнопки", "btnRadius": "Кнопки",
@ -441,6 +515,11 @@
"conversation_display": "Стиль показу розмови", "conversation_display": "Стиль показу розмови",
"conversation_display_linear": "Колонка", "conversation_display_linear": "Колонка",
"conversation_display_tree": "Дерево", "conversation_display_tree": "Дерево",
"conversation_other_replies_button": "Кнопка «Переглянути ще»",
"conversation_other_replies_button_below": "Під дописами",
"conversation_other_replies_button_inside": "Всередині дописів",
"current_avatar": "Ваш поточний аватар",
"current_mascot": "Ваш поточний маскот",
"current_password": "Поточний пароль", "current_password": "Поточний пароль",
"data_import_export_tab": "Імпорт/експорт даних", "data_import_export_tab": "Імпорт/експорт даних",
"default_vis": "Типовий рівень приватності", "default_vis": "Типовий рівень приватності",
@ -461,24 +540,24 @@
"expire_posts_input_placeholder": "Скільки днів", "expire_posts_input_placeholder": "Скільки днів",
"export_theme": "Завантажити", "export_theme": "Завантажити",
"file_export_import": { "file_export_import": {
"backup_restore": "Резервне копіювання налаштувань", "backup_restore": "Копія налаштувань",
"backup_settings": "Завантажити файл налаштувань", "backup_settings": "Завантажити файл налаштувань",
"backup_settings_theme": "Завантажити налаштування й тему", "backup_settings_theme": "Завантажити налаштування й тему",
"errors": { "errors": {
"file_slightly_new": "Другорядна версія файлу відрізняється, деякі налаштування можуть бути не прийняті", "file_slightly_new": "Файл призначено для трохи новішої версії й може спрацювати не повністю",
"file_too_new": "Несумісна основна версія: {fileMajor}, ця версія PleromaFE ({feMajor}) занадто стара для його обробки", "file_too_new": "Версія {fileMajor} новіша, ніж ця AkkomaFE ({feMajor}) може обробити",
"file_too_old": "Несумісна основна версія: {fileMajor}, версія файлу занадто стара і не підтримується (мінімальна версія налаштувань {feMajor})", "file_too_old": "Версія {fileMajor} більше не підтримується (треба {feMajor} чи новіше)",
"invalid_file": "Вибраний файл не є резервною копією налаштувань Pleroma. Ніяких змін не було зроблено." "invalid_file": "Вибраний файл не є резервною копією налаштувань Pleroma. Ніяких змін не було зроблено."
}, },
"restore_settings": "Відновити налаштування з файлу" "restore_settings": "Відновити налаштування з файлу"
}, },
"filtering": "Фільтр", "filtering": "Фільтр",
"filtering_explanation": "Усі дописи з цими словами буде сховано. Пишіть кожне слово з нового рядка", "filtering_explanation": "Усі дописи з цими словами буде сховано. Пишіть кожне слово з нового рядка",
"follow_export": "Експортувати відстежуваних", "follow_export": "Експорт підписок",
"follow_export_button": "Експортувати відстежуваних до csv файлу", "follow_export_button": "Експорт підписок до CSV-файлу",
"follow_import": "Імпортувати відстежуваних", "follow_import": "Імпорт підписок",
"follow_import_error": "Помилка імпортування відстежуваних", "follow_import_error": "Помилка імпорту підписок",
"follows_imported": "Відстежуваних імпортовано! Їхня обробка потребує часу.", "follows_imported": "Підписки імпортовано! Їх буде застосовано поступово.",
"foreground": "Передній план", "foreground": "Передній план",
"fun": "Іграшки", "fun": "Іграшки",
"general": "Загальні", "general": "Загальні",
@ -497,7 +576,7 @@
"hide_list_aliases_error_action": "Закрити", "hide_list_aliases_error_action": "Закрити",
"hide_media_previews": "Приховати попередній перегляд медіа", "hide_media_previews": "Приховати попередній перегляд медіа",
"hide_muted_posts": "Ховати повідомлення приглушених користувач_ок", "hide_muted_posts": "Ховати повідомлення приглушених користувач_ок",
"hide_muted_threads": "Ховати заглушені гілки", "hide_muted_threads": "Ховати приглушені гілки",
"hide_post_stats": "Ховати статистику дописів (скільки вподобань тощо)", "hide_post_stats": "Ховати статистику дописів (скільки вподобань тощо)",
"hide_shoutbox": "Приховати оголошення сервера", "hide_shoutbox": "Приховати оголошення сервера",
"hide_site_favicon": "Вилучити значок сервера з шапки", "hide_site_favicon": "Вилучити значок сервера з шапки",
@ -506,9 +585,9 @@
"hide_user_stats": "Ховати користувацьку статистику (скільки підписок тощо)", "hide_user_stats": "Ховати користувацьку статистику (скільки підписок тощо)",
"hide_wallpaper": "Сховати шпалери сервера", "hide_wallpaper": "Сховати шпалери сервера",
"hide_wordfiltered_statuses": "Ховати дописи зі словами з фільтру", "hide_wordfiltered_statuses": "Ховати дописи зі словами з фільтру",
"import_blocks_from_a_csv_file": "Імпортувати заблокованих з csv файлу", "import_blocks_from_a_csv_file": "Імпортуйте CSV-файл блокувань",
"import_followers_from_a_csv_file": "Імпортувати відстежуваних з csv файлу", "import_followers_from_a_csv_file": "Імпортуйте CSV-файл підписок",
"import_mutes_from_a_csv_file": "Імпорт заглушених з csv файлу", "import_mutes_from_a_csv_file": "Імпортуйте CSV-файл приглушень",
"import_theme": "Вивантажити", "import_theme": "Вивантажити",
"inputRadius": "Поля вводу", "inputRadius": "Поля вводу",
"instance_default": "(усталено: {value})", "instance_default": "(усталено: {value})",
@ -518,13 +597,16 @@
"invalid_theme_imported": "Вибраний файл не є темою Pleroma. У вашу тему не внесено жодних змін.", "invalid_theme_imported": "Вибраний файл не є темою Pleroma. У вашу тему не внесено жодних змін.",
"limited_availability": "Недоступно у вашому браузері", "limited_availability": "Недоступно у вашому браузері",
"links": "Посилання", "links": "Посилання",
"list_aliases_error": "Помилка завантаження нових обліковок: {error}", "list_aliases_error": "Помилка завантаження обліковок: {error}",
"list_backups_error": "Помилка завантаження переліку копій: {error}", "list_backups_error": "Помилка завантаження переліку копій: {error}",
"lock_account_description": "Показувати лише схваленим підписни_цям", "lock_account_description": "Показувати лише схваленим підписни_цям",
"loop_video": "Зациклити відео", "loop_video": "Зациклити відео",
"loop_video_silent_only": "Зациклити відео без звуку (напр. Mastodon \"gifs\")", "loop_video_silent_only": "Зациклити відео без звуку (напр. Mastodon \"gifs\")",
"mascot": "Маскот Mastodon FE",
"max_depth_in_thread": "Максимальна кількість типово показаних рівнів гілки обговорення",
"max_thumbnails": "Максимальна кількість мініатюр на повідомлення", "max_thumbnails": "Максимальна кількість мініатюр на повідомлення",
"mention_link_display": "Показувати згадування", "mention_link_bolden_you": "Підсвічувати посилання, коли вас згадують",
"mention_link_display": "Формат посилань згадок",
"mention_link_display_full": "завжди повністю (як-от {'@'}lesia{'@'}example.org)", "mention_link_display_full": "завжди повністю (як-от {'@'}lesia{'@'}example.org)",
"mention_link_display_full_for_remote": "повністю, якщо це інший сервер (як-от {'@'}lesia{'@'}example.org)", "mention_link_display_full_for_remote": "повністю, якщо це інший сервер (як-от {'@'}lesia{'@'}example.org)",
"mention_link_display_short": "завжди коротко (як-от {'@'}lesia)", "mention_link_display_short": "завжди коротко (як-от {'@'}lesia)",
@ -557,34 +639,37 @@
"more_settings": "Більше налаштувань", "more_settings": "Більше налаштувань",
"move_account": "Переїхати", "move_account": "Переїхати",
"move_account_error": "Помилка переїзду обліковки: {error}", "move_account_error": "Помилка переїзду обліковки: {error}",
"move_account_notes": "Щоб переїхати з цієї обліковки на іншу, вкажіть у її налаштуваннях цю облікову як нову.", "move_account_notes": "Щоб переїхати з цієї обліковки на іншу, додайте в її налаштуваннях цю обліковку як попередню.",
"move_account_target": "Нова обліковка (як-от {example})",
"moved_account": "Переїзд обліковки виконано.",
"mute_bot_posts": "Сховати дописи ботів", "mute_bot_posts": "Сховати дописи ботів",
"mute_export": "Експорт ігнорувань", "mute_export": "Експорт приглушень",
"mute_export_button": "Експорт заглушених у csv файл", "mute_export_button": "Експорт приглушених у csv файл",
"mute_import": "Імпорт ігнорувань", "mute_import": "Імпорт приглушень",
"mute_import_error": "Під час імпорту заглушених сталася помилка", "mute_import_error": "Під час імпорту приглушених сталася помилка",
"mutes_and_blocks": "Заглушення та блокування", "mutes_and_blocks": "Приглушення та блокування",
"mutes_imported": "Заглушені імпортовані! Їх обробка триватиме певний час.", "mutes_imported": "Приглушення імпортовано! Їх буде застосовано поступово.",
"mutes_tab": "Заглушені", "mutes_tab": "Приглушення",
"name": "Ім'я", "name": "Ім'я",
"name_bio": "Особисті дані", "name_bio": "Особисті дані",
"new_alias_target": "Вкажіть нову обліковку (як-от {example})", "new_alias_target": "Додайте стару обліковку ({example})",
"new_email": "Нова е-пошта", "new_email": "Нова е-пошта",
"new_password": "Новий пароль", "new_password": "Новий пароль",
"no_blocks": "Блокування відсутні", "no_blocks": "Нікого не заблоковано",
"no_mutes": "Заглушені відсутні", "no_mutes": "Нікого не приглушено",
"no_rich_text_description": "Видалення всього форматування тексту з усіх дописів", "no_rich_text_description": "Видалення всього форматування тексту з усіх дописів",
"notification_blocks": "Блокування користувача зупиняє всі сповіщення від нього, а також скасовує його відстеження.", "notification_blocks": "Щоб не отримувати сповіщень від когось і вилучити їх із підписни_ць, заблокуйте їх.",
"notification_mutes": "Щоб перестати отримувати сповіщення від певного користувача, заглушіть його.", "notification_mutes": "Щоб просто не отримувати сповіщень від когось, приглушіть їх.",
"notification_setting_block_from_strangers": "Блокувати сповіщення від тих, на кого ви не підписані", "notification_setting_block_from_strangers": "Блокувати сповіщення від тих, на кого ви не підписані",
"notification_setting_filters": "Фільтри", "notification_setting_filters": "Фільтри",
"notification_setting_hide_if_cw": "Ховати вміст push-сповіщення за наявності застороги",
"notification_setting_hide_notification_contents": "Ховати відправника та вміст push-сповіщень", "notification_setting_hide_notification_contents": "Ховати відправника та вміст push-сповіщень",
"notification_setting_privacy": "Приватність", "notification_setting_privacy": "Приватність",
"notification_visibility": "Сповіщати про такі події", "notification_visibility": "Сповіщати про такі події",
"notification_visibility_emoji_reactions": "Реакції", "notification_visibility_emoji_reactions": "Реакції",
"notification_visibility_follows": ідписни_ці", "notification_visibility_follows": ідписни_ці",
"notification_visibility_likes": "Вподобане", "notification_visibility_likes": "Вподобане",
"notification_visibility_mentions": "Згадування", "notification_visibility_mentions": "Згадки",
"notification_visibility_moves": "Переїзди користувач_ок", "notification_visibility_moves": "Переїзди користувач_ок",
"notification_visibility_polls": "Результати голосувань", "notification_visibility_polls": "Результати голосувань",
"notification_visibility_repeats": "Поширення", "notification_visibility_repeats": "Поширення",
@ -593,7 +678,7 @@
"oauth_tokens": "OAuth-ключі", "oauth_tokens": "OAuth-ключі",
"pad_emoji": "Автоматично додавати простір з обидвох сторін емоджі", "pad_emoji": "Автоматично додавати простір з обидвох сторін емоджі",
"panelRadius": "Панелі", "panelRadius": "Панелі",
"pause_on_unfocused": "Призупинити трансляцію, коли вкладка неактивна", "pause_on_unfocused": "Лише коли вкладка активна",
"play_videos_in_modal": "Відтворювати відео у спливаючій рамці", "play_videos_in_modal": "Відтворювати відео у спливаючій рамці",
"post_look_feel": "Вигляд дописів", "post_look_feel": "Вигляд дописів",
"post_status_content_type": "Типова розмітка тексту", "post_status_content_type": "Типова розмітка тексту",
@ -611,7 +696,7 @@
"profile_tab": "Профіль", "profile_tab": "Профіль",
"radii_help": "Радіус заокруглення кутів інтерфейсу (в пікселях)", "radii_help": "Радіус заокруглення кутів інтерфейсу (в пікселях)",
"refresh_token": "Оновити ключ", "refresh_token": "Оновити ключ",
"remove_alias": "Вилучити нову обліковку", "remove_alias": "Вилучити обліковку",
"remove_backup": "Вилучити", "remove_backup": "Вилучити",
"render_mfm": "Показувати розмітку Misskey", "render_mfm": "Показувати розмітку Misskey",
"render_mfm_on_hover": "Анімувати MFM лише при наведенні", "render_mfm_on_hover": "Анімувати MFM лише при наведенні",
@ -633,13 +718,14 @@
"saving_err": "Помилка при збереженні налаштувань", "saving_err": "Помилка при збереженні налаштувань",
"saving_ok": "Налаштування збережені", "saving_ok": "Налаштування збережені",
"scope_copy": "Відповідати на тому ж рівні приватності (завжди ввімкнено для особистих повідомлень)", "scope_copy": "Відповідати на тому ж рівні приватності (завжди ввімкнено для особистих повідомлень)",
"search_user_to_block": "Шукайте кого ви хочете заблокувати", "search_user_to_block": "Шукайте, кого слід заблокувати",
"search_user_to_mute": "Шукайте кого ви хочете заглушити", "search_user_to_mute": "Шукайте, кого слід приглушити",
"security": "Безпека", "security": "Безпека",
"security_tab": "Безпека", "security_tab": "Безпека",
"sensitive_by_default": "Одразу позначати дописи дражливими", "sensitive_by_default": "Одразу позначати дописи дражливими",
"sensitive_if_subject": "Позначати зображення дражливими, якщо вказано застереження", "sensitive_if_subject": "Позначати зображення дражливими, якщо вказано засторогу",
"set_new_avatar": "Встановити новий аватар", "set_new_avatar": "Встановити новий аватар",
"set_new_mascot": "Встановити нового маскота",
"set_new_profile_background": "Встановити нову обкладинку профілю", "set_new_profile_background": "Встановити нову обкладинку профілю",
"set_new_profile_banner": "Встановити новий банер", "set_new_profile_banner": "Встановити новий банер",
"setting_changed": "Конфігурація відрізняється від типової", "setting_changed": "Конфігурація відрізняється від типової",
@ -664,6 +750,7 @@
"show_panel_nav_shortcuts": "Ярлики стрічок у заголовку панелі", "show_panel_nav_shortcuts": "Ярлики стрічок у заголовку панелі",
"show_scrollbars": "Смуги прокрутки в бокових панелях", "show_scrollbars": "Смуги прокрутки в бокових панелях",
"show_wider_shortcuts": "Більша відстань між ярликами в заголовку", "show_wider_shortcuts": "Більша відстань між ярликами в заголовку",
"show_yous": "Показувати «(ви)»",
"stop_gifs": "Анімувати GIF лише при наведенні", "stop_gifs": "Анімувати GIF лише при наведенні",
"streaming": "Автоматично показувати нові дописи вгорі стрічки", "streaming": "Автоматично показувати нові дописи вгорі стрічки",
"style": { "style": {
@ -744,10 +831,10 @@
"faint_link": "корисний підручник", "faint_link": "корисний підручник",
"fine_print": "Прочитайте наш {0} аби нічого нового не дізнатись!", "fine_print": "Прочитайте наш {0} аби нічого нового не дізнатись!",
"header": "Попередній перегляд", "header": "Попередній перегляд",
"header_faint": "Все гаразд", "header_faint": "Усе гаразд",
"input": "Що нового?", "input": "Що нового?",
"link": "невеличке посилання", "link": "невеличке посилання",
"mono": "змісту", "mono": "вмісту",
"text": "Трохи більше {0} та {1}" "text": "Трохи більше {0} та {1}"
}, },
"radii": { "radii": {
@ -819,7 +906,7 @@
"subject_line_noop": "не копіювати", "subject_line_noop": "не копіювати",
"text": "Текст", "text": "Текст",
"theme": "Тема", "theme": "Тема",
"theme_help": "Замініть кольори теми своїми в форматі (#rrggbb).", "theme_help": "Замініть кольори теми своїми в форматі #rrggbb.",
"theme_help_v2_1": "Деякі кольори й прозорості розраховуються автоматично. Позначайте їх пташками, щоб замінити їх вручну. Кнопка «Очистити все» видаляє всі такі заміни.", "theme_help_v2_1": "Деякі кольори й прозорості розраховуються автоматично. Позначайте їх пташками, щоб замінити їх вручну. Кнопка «Очистити все» видаляє всі такі заміни.",
"theme_help_v2_2": "Значки під кольорами показують, наскільки добре видно текст на тлі. При наведенні побачите деталі. Якщо використовуєте прозорість, буде оцінено найгірший варіант.", "theme_help_v2_2": "Значки під кольорами показують, наскільки добре видно текст на тлі. При наведенні побачите деталі. Якщо використовуєте прозорість, буде оцінено найгірший варіант.",
"third_column_mode": "Третя панель, якщо достатньо місця", "third_column_mode": "Третя панель, якщо достатньо місця",
@ -828,13 +915,20 @@
"third_column_mode_postform": "Новий допис і меню", "third_column_mode_postform": "Новий допис і меню",
"token": "Ключ", "token": "Ключ",
"tooltipRadius": "Підказки/попередження", "tooltipRadius": "Підказки/попередження",
"type_domains_to_mute": "Пошук доменів для заглушення", "translation_language": "Мова автоматичного перекладу",
"tree_advanced": "Показувати додаткові кнопки відкриття й закриття ланцюгів відповідей у гілках",
"tree_fade_ancestors": "Показувати менш виразно дописи, що передують поточному",
"type_domains_to_mute": "Шукайте домени, які слід приглушити",
"upload_a_photo": "Вивантажити фото", "upload_a_photo": "Вивантажити фото",
"useStreamingApi": "Отримувати дописи та сповіщення наживо", "useStreamingApi": "Отримувати дописи та сповіщення наживо",
"useStreamingApiWarning": "(Не рекомендується, експериментально, повідомлення можуть зникати)", "useStreamingApiWarning": "Загалом працює. Якщо не зовсім, спробуєте оновити сторінку?",
"use_at_icon": "Значок {'@'} замість символа", "use_blurhash": "Показувати дражливі мініатюри (як розмиті кольори)",
"use_contain_fit": "Не обрізати краї мініатюр", "use_contain_fit": "Не обрізати краї мініатюр",
"use_one_click_nsfw": "Відкривати NSFW вкладення одним кліком миші", "use_one_click_nsfw": "Відкривати NSFW вкладення одним кліком миші",
"user_accepts_direct_messages_from": "Приймати особисті повідомлення",
"user_accepts_direct_messages_from_everybody": "Будь-чиї",
"user_accepts_direct_messages_from_nobody": "Нічиї",
"user_accepts_direct_messages_from_people_i_follow": "Від тих, на кого я підписуюсь",
"user_mutes": "Користувач_ки", "user_mutes": "Користувач_ки",
"user_profile_default_tab": "Типова вкладка профілів", "user_profile_default_tab": "Типова вкладка профілів",
"user_profiles": "Користувацькі профілі", "user_profiles": "Користувацькі профілі",
@ -853,8 +947,18 @@
"word_filter": "Фільтр слів", "word_filter": "Фільтр слів",
"wordfilter": "Фільтр слів" "wordfilter": "Фільтр слів"
}, },
"settings_profile": {
"creating": "Створення профілю налаштувань «{profile}»…",
"synchronization_error": "Не вдалося синхронізувати налаштування: {err}",
"synchronized": "Налаштування синхронізовано!",
"synchronizing": "Синхронізація профілю налаштувань «{profile}»…"
},
"status": { "status": {
"ancestor_follow": "Переглянути ще {numReplies} під цим дописом | Переглянути ще {numReplies} під цим дописом",
"ancestor_follow_with_icon": "{icon} {text}",
"attachment_stop_flash": "Зупинити Flash-плеєр",
"bookmark": "Додати до закладок", "bookmark": "Додати до закладок",
"collapse_attachments": "Згорнути вкладення",
"copy_link": "Скопіювати посилання на допис", "copy_link": "Скопіювати посилання на допис",
"delete": "Видалити допис", "delete": "Видалити допис",
"delete_confirm": "Точно видалити допис?", "delete_confirm": "Точно видалити допис?",
@ -868,11 +972,17 @@
"expand": "Розгорнути", "expand": "Розгорнути",
"external_source": "Зовнішнє джерело", "external_source": "Зовнішнє джерело",
"favorites": "Вподобане", "favorites": "Вподобане",
"hide_attachment": "Сховати вкладення",
"hide_content": "Сховати вміст", "hide_content": "Сховати вміст",
"hide_full_subject": "Сховати всю тему", "hide_full_subject": "Згорнути засторогу",
"mute_conversation": "Заглушити розмову", "many_attachments": "Вкладень: {number} | Вкладень: {number}",
"mentions": "Згадки",
"move_down": "Посунути вкладення праворуч",
"move_up": "Посунути вкладення ліворуч",
"mute_conversation": "Приглушити розмову",
"nsfw": "Дражливий вміст", "nsfw": "Дражливий вміст",
"open_gallery": "Відкрити галерею", "open_gallery": "Відкрити галерею",
"override_translation_source_language": "Замістити початкову мову",
"pin": "Закріпити в профілі", "pin": "Закріпити в профілі",
"pinned": "Закріплено", "pinned": "Закріплено",
"plus_more": "ще {number}", "plus_more": "ще {number}",
@ -881,21 +991,33 @@
"redraft_confirm_accept_button": "Так, видалити й переписати", "redraft_confirm_accept_button": "Так, видалити й переписати",
"redraft_confirm_cancel_button": "Ні, лишити оригінал", "redraft_confirm_cancel_button": "Ні, лишити оригінал",
"redraft_confirm_title": "Підтвердьте видалення й переписання", "redraft_confirm_title": "Підтвердьте видалення й переписання",
"remove_attachment": "Видалити долучення", "remove_attachment": "Видалити вкладення",
"repeat_confirm": "Точно поширити допис?",
"repeat_confirm_accept_button": "Так, поширити",
"repeat_confirm_cancel_button": "Ні, не поширювати",
"repeat_confirm_title": "Підтвердьте поширення",
"repeats": "Поширення", "repeats": "Поширення",
"replies_list": "Відповіді:", "replies_list": "Відповіді:",
"replies_list_with_others": "Ще відповідей: {numReplies} | Ще відповідей: {numReplies}",
"reply_to": "Відповідь", "reply_to": "Відповідь",
"show_all_attachments": "Показати всі вкладення", "show_all_attachments": "Показати всі вкладення",
"show_all_conversation": "Показати всю розмову (ще дописів: {numStatus}) | Показати всю розмову (ще дописів: {numStatus})",
"show_all_conversation_with_icon": "{icon} {text}",
"show_attachment_description": "Переглянути опис (натисніть саме вкладення, якщо опис не вміщається)", "show_attachment_description": "Переглянути опис (натисніть саме вкладення, якщо опис не вміщається)",
"show_attachment_in_modal": "Показати вкладення у вікні", "show_attachment_in_modal": "Показати вкладення у вікні",
"show_content": "Показати вміст", "show_content": "Показати вміст",
"show_full_subject": "Показати всю засторогу", "show_full_subject": "Показати всю засторогу",
"show_only_conversation_under_this": "Показати відповіді лише до цього допису",
"status_deleted": "Цей допис був видалений", "status_deleted": "Цей допис був видалений",
"status_unavailable": "Допис недоступний", "status_unavailable": "Допис недоступний",
"thread_follow": "Ще відповідей: {numStatus} | Ще відповідей: {numStatus}",
"thread_follow_with_icon": "{icon} {text}",
"thread_hide": "Сховати гілку", "thread_hide": "Сховати гілку",
"thread_muted": "Гілку приглушено", "thread_muted": "Гілку приглушено",
"thread_muted_and_words": ", має слова:", "thread_muted_and_words": ", має слова:",
"thread_show": "Показати гілку", "thread_show": "Показати гілку",
"thread_show_full": "Показати відповіді: {numStatus} | Показати відповіді: {numStatus}",
"thread_show_full_with_icon": "{icon} {text}",
"translate": "Перекласти", "translate": "Перекласти",
"translated_from": "В оригіналі: {language}", "translated_from": "В оригіналі: {language}",
"unbookmark": "Видалити із закладок", "unbookmark": "Видалити із закладок",
@ -929,6 +1051,7 @@
"collapse": "Згорнути", "collapse": "Згорнути",
"conversation": "Розмова", "conversation": "Розмова",
"error": "Помилка завантаження стрічки: {0}", "error": "Помилка завантаження стрічки: {0}",
"follow_tag": "Підписатись на тег",
"load_older": "Завантажити давніші дописи", "load_older": "Завантажити давніші дописи",
"no_more_statuses": "Більше немає дописів", "no_more_statuses": "Більше немає дописів",
"no_retweet_hint": "Допис приватний чи особистий, тож його не можна поширити чи цитувати", "no_retweet_hint": "Допис приватний чи особистий, тож його не можна поширити чи цитувати",
@ -938,8 +1061,12 @@
"show_new": "Показати нові", "show_new": "Показати нові",
"socket_broke": "Втрачено з'єднання у реальному часі: код {0}", "socket_broke": "Втрачено з'єднання у реальному часі: код {0}",
"socket_reconnected": "Встановлено з'єднання у реальному часі", "socket_reconnected": "Встановлено з'єднання у реальному часі",
"unfollow_tag": "Відписатись від тегу",
"up_to_date": "Оновлено" "up_to_date": "Оновлено"
}, },
"toast": {
"no_translation_target_set": "Не обрано цільової мови перекладу — може не вдатися. Оберіть у налаштуваннях, на яку мову перекладати."
},
"tool_tip": { "tool_tip": {
"accept_follow_request": "Дозволити підписатись", "accept_follow_request": "Дозволити підписатись",
"add_reaction": "Додати реакцію", "add_reaction": "Додати реакцію",
@ -978,14 +1105,14 @@
"disable_remote_subscription": "Заборонити підписуватись користувач_кам з інших серверів", "disable_remote_subscription": "Заборонити підписуватись користувач_кам з інших серверів",
"force_nsfw": "Позначити всі дописи дражливими", "force_nsfw": "Позначити всі дописи дражливими",
"force_unlisted": "Не показувати дописи в стрічці", "force_unlisted": "Не показувати дописи в стрічці",
"grant_admin": "Надати права адміністратора", "grant_admin": "Дозволити адміністрування",
"grant_moderator": "Надати права модератора", "grant_moderator": "Дозволити модерування",
"moderation": "Модерування", "moderation": "Модерування",
"quarantine": "Не розповсюджувати дописи на інших інстансах", "quarantine": "Не розповсюджувати дописи на інших інстансах",
"revoke_admin": "Позбавити прав адміністратора", "revoke_admin": "Позбавити прав адміністрування",
"revoke_moderator": "Позбавити прав модератора", "revoke_moderator": "Позбавити прав модерування",
"sandbox": "Показувати дописи лише підписни_цям", "sandbox": "Показувати дописи лише підписни_цям",
"strip_media": "Вилучити медіа з дописів користувача" "strip_media": "Вилучити медіа з дописів"
}, },
"approve": "Дозволити", "approve": "Дозволити",
"approve_confirm": "Точно дозволити користувач_ці на вас підписатись?", "approve_confirm": "Точно дозволити користувач_ці на вас підписатись?",
@ -1006,13 +1133,18 @@
"deny_confirm": "Точно відхилити запит на підписку?", "deny_confirm": "Точно відхилити запит на підписку?",
"deny_confirm_accept_button": "Так, відхилити", "deny_confirm_accept_button": "Так, відхилити",
"deny_confirm_cancel_button": "Ні, скасувати", "deny_confirm_cancel_button": "Ні, скасувати",
"deny_confirm_title": "Відхилити запит на підписку",
"domain_muted": "Розблокувати домен",
"edit_profile": "Редагувати профіль", "edit_profile": "Редагувати профіль",
"favorites": "Вподобання", "favorites": "Вподобання",
"follow": "Підписатись", "follow": "Підписатись",
"follow_cancel": "Скасувати запит", "follow_cancel": "Скасувати запит",
"follow_progress": "Запитую…", "follow_progress": "Надсилання…",
"follow_sent": "Запит відправлено!", "follow_sent": "Запит надіслано!",
"follow_tag": "Підписатись на тег",
"follow_unfollow": "Відписатись", "follow_unfollow": "Відписатись",
"followed_tags": "Підписки на теги",
"followed_users": "Підписки на користувач_ок",
"followees": "Підписки", "followees": "Підписки",
"followers": ідписни_ці", "followers": ідписни_ці",
"following": "Ви підписані!", "following": "Ви підписані!",
@ -1036,7 +1168,8 @@
"mute_confirm_title": "Приглушення", "mute_confirm_title": "Приглушення",
"mute_domain": "Заблокувати домен", "mute_domain": "Заблокувати домен",
"mute_progress": "Глушимо…", "mute_progress": "Глушимо…",
"muted": "Заглушено", "muted": "Приглушено",
"not_following_any_hashtags": "У вас нема підписок на теги",
"note": "Приватна нотатка", "note": "Приватна нотатка",
"per_day": "на день", "per_day": "на день",
"remote_follow": "Підписатись", "remote_follow": "Підписатись",
@ -1053,8 +1186,9 @@
"unfollow_confirm_accept_button": "Так, відписатись", "unfollow_confirm_accept_button": "Так, відписатись",
"unfollow_confirm_cancel_button": "Ні, не відписуватись", "unfollow_confirm_cancel_button": "Ні, не відписуватись",
"unfollow_confirm_title": "Відписка", "unfollow_confirm_title": "Відписка",
"unmute": "Зняти глушення", "unfollow_tag": "Відписатись від тегу",
"unmute_progress": "Знімаємо глушення…", "unmute": "Відновити",
"unmute_progress": "Відновлення…",
"unsubscribe": "Відписатись" "unsubscribe": "Відписатись"
}, },
"user_profile": { "user_profile": {
@ -1069,7 +1203,7 @@
"forward_description": "Це обліковка з іншого сервера. Надіслати його адміністрації копію скарги?", "forward_description": "Це обліковка з іншого сервера. Надіслати його адміністрації копію скарги?",
"forward_to": "Переслати до {0}", "forward_to": "Переслати до {0}",
"generic_error": "Виникла помилка під час обробки вашого запиту.", "generic_error": "Виникла помилка під час обробки вашого запиту.",
"submit": "Відправити", "submit": "Надіслати",
"title": "Поскаржитись на {0}" "title": "Поскаржитись на {0}"
}, },
"who_to_follow": { "who_to_follow": {

View File

@ -922,10 +922,13 @@
"upload_a_photo": "上传照片", "upload_a_photo": "上传照片",
"useStreamingApi": "实时接收帖文和通知", "useStreamingApi": "实时接收帖文和通知",
"useStreamingApiWarning": "十分炫酷推荐使用。要是崩了试试刷新?", "useStreamingApiWarning": "十分炫酷推荐使用。要是崩了试试刷新?",
"use_at_icon": "将 {'@'} 符号显示为图标而不是文本",
"use_blurhash": "对NSFW的缩略图使用模糊处理", "use_blurhash": "对NSFW的缩略图使用模糊处理",
"use_contain_fit": "生成缩略图时不要裁剪附件", "use_contain_fit": "生成缩略图时不要裁剪附件",
"use_one_click_nsfw": "点击一次以打开工作场所不适宜NSFW的附件", "use_one_click_nsfw": "点击一次以打开工作场所不适宜NSFW的附件",
"user_accepts_direct_messages_from": "允许私信自",
"user_accepts_direct_messages_from_everybody": "所有人",
"user_accepts_direct_messages_from_nobody": "没有人",
"user_accepts_direct_messages_from_people_i_follow": "我关注的人",
"user_mutes": "用户", "user_mutes": "用户",
"user_profile_default_tab": "用户资料中的默认标签页", "user_profile_default_tab": "用户资料中的默认标签页",
"user_profiles": "用户资料", "user_profiles": "用户资料",

View File

@ -55,6 +55,7 @@ export const defaultState = {
alwaysShowNewPostButton: false, alwaysShowNewPostButton: false,
autohideFloatingPostButton: false, autohideFloatingPostButton: false,
pauseOnUnfocused: true, pauseOnUnfocused: true,
displayPageBackgrounds: true,
stopGifs: undefined, stopGifs: undefined,
replyVisibility: 'all', replyVisibility: 'all',
thirdColumnMode: 'notifications', thirdColumnMode: 'notifications',
@ -95,7 +96,6 @@ export const defaultState = {
disableStickyHeaders: false, disableStickyHeaders: false,
showScrollbars: false, showScrollbars: false,
greentext: undefined, // instance default greentext: undefined, // instance default
useAtIcon: undefined, // instance default
mentionLinkDisplay: undefined, // instance default mentionLinkDisplay: undefined, // instance default
mentionLinkShowTooltip: undefined, // instance default mentionLinkShowTooltip: undefined, // instance default
mentionLinkShowAvatar: undefined, // instance default mentionLinkShowAvatar: undefined, // instance default
@ -229,7 +229,7 @@ const config = {
break break
case 'interfaceLanguage': case 'interfaceLanguage':
messages.setLanguage(this.getters.i18n, value) messages.setLanguage(this.getters.i18n, value)
Cookies.set(BACKEND_LANGUAGE_COOKIE_NAME, localeService.internalToBackendLocale(value)) Cookies.set(BACKEND_LANGUAGE_COOKIE_NAME, localeService.internalToBackendLocale(value), {sameSite: 'Lax'})
dispatch('setInstanceOption', { name: 'interfaceLanguage', value }) dispatch('setInstanceOption', { name: 'interfaceLanguage', value })
break break
case 'thirdColumnMode': case 'thirdColumnMode':

View File

@ -21,7 +21,6 @@ const defaultState = {
background: '/static/aurora_borealis.jpg', background: '/static/aurora_borealis.jpg',
collapseMessageWithSubject: true, collapseMessageWithSubject: true,
greentext: false, greentext: false,
useAtIcon: false,
mentionLinkDisplay: 'short', mentionLinkDisplay: 'short',
mentionLinkShowTooltip: true, mentionLinkShowTooltip: true,
mentionLinkShowAvatar: false, mentionLinkShowAvatar: false,

View File

@ -37,11 +37,18 @@ const recentEmojis = {
getters: { getters: {
recentEmojis: (state, getters, rootState) => state.emojis.reduce((objects, displayText) => { recentEmojis: (state, getters, rootState) => state.emojis.reduce((objects, displayText) => {
const allEmojis = rootState.instance.emoji.concat(rootState.instance.customEmoji) let comparator = emoji => emoji.displayText === displayText
let emojiObject = allEmojis.find(emoji => emoji.displayText === displayText)
let emojiObject = rootState.instance.emoji.find(comparator)
if (emojiObject !== undefined) { if (emojiObject !== undefined) {
objects.push(emojiObject) objects.push(emojiObject)
} else {
emojiObject = rootState.instance.customEmoji.find(comparator)
if (emojiObject !== undefined) {
objects.push(emojiObject)
}
} }
return objects return objects
}, []), }, []),
}, },

View File

@ -47,6 +47,10 @@ export const settingsMap = {
}, },
// Privacy // Privacy
'locked': 'locked', 'locked': 'locked',
'permitFollowback': {
get: 'akkoma.permit_followback',
set: 'permit_followback'
},
'allowFollowingMove': { 'allowFollowingMove': {
get: 'pleroma.allow_following_move', get: 'pleroma.allow_following_move',
set: 'allow_following_move' set: 'allow_following_move'

View File

@ -135,6 +135,10 @@ export const mutations = {
const user = state.usersObject[id] const user = state.usersObject[id]
user['deactivated'] = deactivated user['deactivated'] = deactivated
}, },
setDisplayBackground(state, url) {
console.log("Commiting user profile bg mutation")
state.displayBackground = url
},
setCurrentUser (state, user) { setCurrentUser (state, user) {
state.lastLoginName = user.screen_name state.lastLoginName = user.screen_name
state.currentUser = mergeWith(state.currentUser || {}, user, mergeArrayLength) state.currentUser = mergeWith(state.currentUser || {}, user, mergeArrayLength)
@ -199,21 +203,28 @@ export const mutations = {
}) })
}, },
saveBlockIds (state, blockIds) { saveBlockIds (state, blockIds) {
state.currentUser.blockIds = blockIds console.log("ADDING BLOCK IDS", blockIds);
state.currentUser.blockIds = uniq(concat(state.currentUser.blockIds || [], blockIds))
}, },
addBlockId (state, blockId) { addBlockId (state, blockId) {
if (state.currentUser.blockIds.indexOf(blockId) === -1) { if (state.currentUser.blockIds.indexOf(blockId) === -1) {
state.currentUser.blockIds.push(blockId) state.currentUser.blockIds.push(blockId)
} }
}, },
setBlockIdsMaxId (state, blockIdsMaxId) {
state.currentUser.blockIdsMaxId = blockIdsMaxId
},
saveMuteIds (state, muteIds) { saveMuteIds (state, muteIds) {
state.currentUser.muteIds = muteIds state.currentUser.muteIds = uniq(concat(state.currentUser.muteIds || [], muteIds))
}, },
addMuteId (state, muteId) { addMuteId (state, muteId) {
if (state.currentUser.muteIds.indexOf(muteId) === -1) { if (state.currentUser.muteIds.indexOf(muteId) === -1) {
state.currentUser.muteIds.push(muteId) state.currentUser.muteIds.push(muteId)
} }
}, },
setMuteIdsMaxId (state, muteIdsMaxId) {
state.currentUser.muteIdsMaxId = muteIdsMaxId
},
updateMascot (state, mascotUrl) { updateMascot (state, mascotUrl) {
state.currentUser.mascot = mascotUrl state.currentUser.mascot = mascotUrl
}, },
@ -300,6 +311,7 @@ export const defaultState = {
currentUser: false, currentUser: false,
users: [], users: [],
usersObject: {}, usersObject: {},
displayBackground: null,
signUpPending: false, signUpPending: false,
signUpErrors: [], signUpErrors: [],
relationships: {}, relationships: {},
@ -312,6 +324,10 @@ const users = {
mutations, mutations,
getters, getters,
actions: { actions: {
setDisplayBackground (store, url) {
console.log("Performing user profile bg action...")
store.commit('setDisplayBackground', url)
},
fetchUserIfMissing (store, id) { fetchUserIfMissing (store, id) {
if (!store.getters.findUser(id)) { if (!store.getters.findUser(id)) {
store.dispatch('fetchUser', id) store.dispatch('fetchUser', id)
@ -330,10 +346,21 @@ const users = {
.then((relationships) => store.commit('updateUserRelationship', relationships)) .then((relationships) => store.commit('updateUserRelationship', relationships))
} }
}, },
fetchBlocks (store) { fetchBlocks (store, args) {
return store.rootState.api.backendInteractor.fetchBlocks() const { reset } = args || {}
const maxId = store.state.currentUser.blockIdsMaxId
return store.rootState.api.backendInteractor.fetchBlocks({ maxId })
.then((blocks) => { .then((blocks) => {
store.commit('saveBlockIds', map(blocks, 'id')) store.commit('saveBlockIds', map(blocks, 'id'))
if (reset) {
store.commit('saveBlockIds', map(blocks, 'id'))
} else {
map(blocks, 'id').map(id => store.commit('addBlockId', id))
}
if (blocks.length) {
store.commit('setBlockIdsMaxId', last(blocks).id)
}
store.commit('addNewUsers', blocks) store.commit('addNewUsers', blocks)
return blocks return blocks
}) })
@ -353,10 +380,22 @@ const users = {
unblockUsers (store, ids = []) { unblockUsers (store, ids = []) {
return Promise.all(ids.map(id => unblockUser(store, id))) return Promise.all(ids.map(id => unblockUser(store, id)))
}, },
fetchMutes (store) { fetchMutes (store, args) {
return store.rootState.api.backendInteractor.fetchMutes() const { reset } = args || {}
const maxId = store.state.currentUser.muteIdsMaxId
return store.rootState.api.backendInteractor.fetchMutes({ maxId })
.then((mutes) => { .then((mutes) => {
store.commit('saveMuteIds', map(mutes, 'id')) store.commit('saveMuteIds', map(mutes, 'id'))
if (reset) {
store.commit('saveMuteIds', map(mutes, 'id'))
} else {
map(mutes, 'id').map(id => store.commit('addMuteId', id))
}
if (mutes.length) {
store.commit('setMuteIdsMaxId', last(mutes).id)
}
store.commit('addNewUsers', mutes) store.commit('addNewUsers', mutes)
return mutes return mutes
}) })

View File

@ -1166,8 +1166,13 @@ const generateMfaBackupCodes = ({ credentials }) => {
}).then((data) => data.json()) }).then((data) => data.json())
} }
const fetchMutes = ({ credentials }) => { const fetchMutes = ({ maxId, credentials }) => {
return promisedRequest({ url: MASTODON_USER_MUTES_URL, credentials }) const query = new URLSearchParams({ with_relationships: true })
if (maxId) {
query.append('max_id', maxId)
}
return promisedRequest({ url: `${MASTODON_USER_MUTES_URL}?${query.toString()}`, credentials })
.then((users) => users.map(parseUser)) .then((users) => users.map(parseUser))
} }
@ -1213,8 +1218,12 @@ const unsubscribeUser = ({ id, credentials }) => {
return promisedRequest({ url: MASTODON_UNSUBSCRIBE_USER(id), credentials, method: 'POST' }) return promisedRequest({ url: MASTODON_UNSUBSCRIBE_USER(id), credentials, method: 'POST' })
} }
const fetchBlocks = ({ credentials }) => { const fetchBlocks = ({ maxId, credentials }) => {
return promisedRequest({ url: MASTODON_USER_BLOCKS_URL, credentials }) const query = new URLSearchParams({ with_relationships: true })
if (maxId) {
query.append('max_id', maxId)
}
return promisedRequest({ url: `${MASTODON_USER_BLOCKS_URL}?${query.toString()}`, credentials })
.then((users) => users.map(parseUser)) .then((users) => users.map(parseUser))
} }

View File

@ -95,6 +95,7 @@ export const parseUser = (data) => {
if (data.akkoma) { if (data.akkoma) {
output.instance = data.akkoma.instance output.instance = data.akkoma.instance
output.status_ttl_days = data.akkoma.status_ttl_days output.status_ttl_days = data.akkoma.status_ttl_days
output.permit_followback = data.akkoma.permit_followback
} }
if (data.pleroma) { if (data.pleroma) {

View File

@ -8,7 +8,30 @@ const specialLanguageCodes = {
'zh': 'zh-Hans' 'zh': 'zh-Hans'
} }
const internalToBrowserLocale = code => specialLanguageCodes[code] || code // Find a browser language that matches the configured UI language.
// Browser language should match the configured generic short code prefix:
// eg 'en-GB' browser language matches 'en' UI language.
const findBrowserRegionMatch = genericLang => {
for (const blang of window.navigator.languages) {
if (genericLang === blang.split('-')[0])
return blang;
}
return null;
}
const internalToBrowserLocale = (() => {
const resolvedBrowserLocales = {}
return i18nLocale => {
if (resolvedBrowserLocales[i18nLocale]) {
return resolvedBrowserLocales[i18nLocale]
}
const lang = specialLanguageCodes[i18nLocale] || i18nLocale;
const resolved = findBrowserRegionMatch(lang) || lang;
resolvedBrowserLocales[i18nLocale] = resolved
return resolved
}
})()
const internalToBackendLocale = code => internalToBrowserLocale(code).replace('_', '-') const internalToBackendLocale = code => internalToBrowserLocale(code).replace('_', '-')