Merge branch 'develop' into rebrand

This commit is contained in:
ThatOneCalculator 2023-07-15 14:15:01 -07:00
commit bb22a671b0
No known key found for this signature in database
GPG Key ID: 8703CACD01000000
536 changed files with 16570 additions and 4893 deletions

13
.config/LICENSE Normal file
View File

@ -0,0 +1,13 @@
Copyright 2023 Calckey
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -67,6 +67,20 @@ redis:
#db: 1 #db: 1
#user: default #user: default
# ┌─────────────────────────────┐
#───┘ Cache server configuration └─────────────────────────────────────
# A Redis-compatible server (DragonflyDB, Keydb, Redis) for caching
# If left blank, it will use the Redis server from above
#cacheServer:
#host: localhost
#port: 6379
#family: 0 # 0=Both, 4=IPv4, 6=IPv6
#pass: example-pass
#prefix: example-prefix
#db: 1
# Please configure either MeiliSearch *or* Sonic. # Please configure either MeiliSearch *or* Sonic.
# If both MeiliSearch and Sonic configurations are present, MeiliSearch will take precedence. # If both MeiliSearch and Sonic configurations are present, MeiliSearch will take precedence.
@ -107,7 +121,7 @@ redis:
# ┌─────────────────────┐ # ┌─────────────────────┐
#───┘ Other configuration └───────────────────────────────────── #───┘ Other configuration └─────────────────────────────────────
# Maximum length of a post (default 3000, max 8192) # Maximum length of a post (default 3000, max 250000000)
#maxNoteLength: 3000 #maxNoteLength: 3000
# Maximum length of an image caption (default 1500, max 8192) # Maximum length of an image caption (default 1500, max 8192)

View File

@ -1,12 +1,8 @@
# Visual Studio Code # Visual Studio Code
/.vscode .vscode
!/.vscode/extensions.json
# Intelij-IDEA # Intelij-IDEA
/.idea .idea
packages/backend/.idea/backend.iml
packages/backend/.idea/modules.xml
packages/backend/.idea/vcs.xml
# Node.js # Node.js
node_modules node_modules
@ -14,7 +10,7 @@ node_modules
report.*.json report.*.json
# Rust # Rust
packages/backend/native-utils/target/* packages/backend/native-utils/target
# Cypress # Cypress
cypress/screenshots cypress/screenshots
@ -24,9 +20,7 @@ cypress/videos
coverage coverage
# config # config
/.config/* /.config
!/.config/example.yml
!/.config/docker_example.env
# misskey # misskey
built built

4
.gitignore vendored
View File

@ -25,6 +25,7 @@ coverage
!/.config/devenv.yml !/.config/devenv.yml
!/.config/docker_example.env !/.config/docker_example.env
!/.config/helm_values_example.yml !/.config/helm_values_example.yml
!/.config/LICENSE
#docker dev config #docker dev config
/dev/docker-compose.yml /dev/docker-compose.yml
@ -48,6 +49,9 @@ packages/backend/assets/sounds/None.mp3
!packages/backend/src/db !packages/backend/src/db
packages/megalodon/lib
packages/megalodon/.idea
# blender backups # blender backups
*.blend1 *.blend1
*.blend2 *.blend2

View File

@ -2704,7 +2704,7 @@ Co-committed-by: naskya <naskya@noreply.codeberg.org>
Passwords will be automatically re-hashed on sign-in. All new password hashes will be argon2 by default. This uses argon2id and is not configurable. In the very unlikely case someone has more specific needs, a fork is recommended. ChangeLog: Added Co-authored-by: Chloe Kudryavtsev <code@toast.bunkerlabs.net> Passwords will be automatically re-hashed on sign-in. All new password hashes will be argon2 by default. This uses argon2id and is not configurable. In the very unlikely case someone has more specific needs, a fork is recommended. ChangeLog: Added Co-authored-by: Chloe Kudryavtsev <code@toast.bunkerlabs.net>
Breaks Calckey -> Misskey migration, but fixes Foundkey -> Calckey migration Breaks Calckey -> Misskey migration, but fixes FoundKey -> Calckey migration
- Add argon - Add argon

27
COPYING
View File

@ -1,15 +1,24 @@
Unless otherwise stated this repository is Unless specified otherwise, the entirety of this repository is subject to the following:
Copyright © 2014-2022 syuilo and contributers Copyright © 2014-2023 syuilo and contributors
Copyright © 2022 thatonecalculator and contributers Copyright © 2022-2023 Kainoa Kanter and contributors
And is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as LICENSE. And is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as LICENSE.
---
Firefish includes several third-party Open-Source softwares. These specific configuration directories:
Emoji keywords for Unicode 11 and below by Mu-An Chiou - .config/
License: MIT - custom/assets/
https://github.com/muan/emojilib/blob/master/LICENSE
and their contents are
Copyright © 2022-2023 Kainoa Kanter and contributors
And are distributed under The Apache License, Version 2.0, you should have received a copy of the license file as LICENSE in each specified directory.
---
Calckey includes several third-party open-source softwares and software libraries.
RsaSignature2017 implementation by Transmute Industries Inc RsaSignature2017 implementation by Transmute Industries Inc
License: MIT License: MIT
@ -18,3 +27,7 @@ https://github.com/transmute-industries/RsaSignature2017/blob/master/LICENSE
Machine learning model for sensitive images by Infinite Red, Inc. Machine learning model for sensitive images by Infinite Red, Inc.
License: MIT License: MIT
https://github.com/infinitered/nsfwjs/blob/master/LICENSE https://github.com/infinitered/nsfwjs/blob/master/LICENSE
Licenses for all softwares and software libraries installed via the Node Package Manager ("npm") can be found by running the following shell command in the root directory of this repository:
pnpm licenses list

View File

@ -20,7 +20,8 @@ COPY package.json pnpm*.yaml ./
COPY packages/backend/package.json packages/backend/package.json COPY packages/backend/package.json packages/backend/package.json
COPY packages/client/package.json packages/client/package.json COPY packages/client/package.json packages/client/package.json
COPY packages/sw/package.json packages/sw/package.json COPY packages/sw/package.json packages/sw/package.json
COPY packages/firefish-js/package.json packages/firefish-js/package.json COPY packages/calckey-js/package.json packages/calckey-js/package.json
COPY packages/megalodon/package.json packages/megalodon/package.json
COPY packages/backend/native-utils/package.json packages/backend/native-utils/package.json COPY packages/backend/native-utils/package.json packages/backend/native-utils/package.json
COPY packages/backend/native-utils/npm/linux-x64-musl/package.json packages/backend/native-utils/npm/linux-x64-musl/package.json COPY packages/backend/native-utils/npm/linux-x64-musl/package.json packages/backend/native-utils/npm/linux-x64-musl/package.json
COPY packages/backend/native-utils/npm/linux-arm64-musl/package.json packages/backend/native-utils/npm/linux-arm64-musl/package.json COPY packages/backend/native-utils/npm/linux-arm64-musl/package.json packages/backend/native-utils/npm/linux-arm64-musl/package.json
@ -29,10 +30,7 @@ COPY packages/backend/native-utils/npm/linux-arm64-musl/package.json packages/ba
RUN corepack enable && corepack prepare pnpm@latest --activate && pnpm i --frozen-lockfile RUN corepack enable && corepack prepare pnpm@latest --activate && pnpm i --frozen-lockfile
# Copy in the rest of the native-utils rust files # Copy in the rest of the native-utils rust files
COPY packages/backend/native-utils/.cargo packages/backend/native-utils/.cargo COPY packages/backend/native-utils packages/backend/native-utils/
COPY packages/backend/native-utils/build.rs packages/backend/native-utils/
COPY packages/backend/native-utils/src packages/backend/native-utils/src/
COPY packages/backend/native-utils/migration/src packages/backend/native-utils/migration/src/
# Compile native-utils # Compile native-utils
RUN pnpm run --filter native-utils build RUN pnpm run --filter native-utils build
@ -53,6 +51,8 @@ RUN apk add --no-cache --no-progress tini ffmpeg vips-dev zip unzip nodejs-curre
COPY . ./ COPY . ./
COPY --from=build /calckey/packages/megalodon /calckey/packages/megalodon
# Copy node modules # Copy node modules
COPY --from=build /firefish/node_modules /firefish/node_modules COPY --from=build /firefish/node_modules /firefish/node_modules
COPY --from=build /firefish/packages/backend/node_modules /firefish/packages/backend/node_modules COPY --from=build /firefish/packages/backend/node_modules /firefish/packages/backend/node_modules

View File

@ -137,7 +137,7 @@
- 👍 also triggers generic like/favorite - 👍 also triggers generic like/favorite
- [Add additional background for acrylic popups if backdrop-filter is unsupported](https://github.com/misskey-dev/misskey/pull/8671) - [Add additional background for acrylic popups if backdrop-filter is unsupported](https://github.com/misskey-dev/misskey/pull/8671)
- [Add parameters to MFM rotate](https://github.com/misskey-dev/misskey/pull/8549) - [Add parameters to MFM rotate](https://github.com/misskey-dev/misskey/pull/8549)
- Many changes from [Foundkey](https://akkoma.dev/FoundKeyGang/Foundkey) - Many changes from [FoundKey](https://akkoma.dev/FoundKeyGang/FoundKey)
- https://akkoma.dev/FoundKeyGang/FoundKey/commit/0ece67b04c3f0365057624c1068808276ccab981: refactor pages/auth.form.vue to composition API - https://akkoma.dev/FoundKeyGang/FoundKey/commit/0ece67b04c3f0365057624c1068808276ccab981: refactor pages/auth.form.vue to composition API
- https://akkoma.dev/FoundKeyGang/FoundKey/commit/4bc9610d8bf5af736b5e89e4782395705de45d7d: remove unnecessary joins - https://akkoma.dev/FoundKeyGang/FoundKey/commit/4bc9610d8bf5af736b5e89e4782395705de45d7d: remove unnecessary joins
- https://akkoma.dev/FoundKeyGang/FoundKey/commit/9ee609d70082f7a6dc119a5d83c0e7c5e1208676: enhance privacy of notes - https://akkoma.dev/FoundKeyGang/FoundKey/commit/9ee609d70082f7a6dc119a5d83c0e7c5e1208676: enhance privacy of notes

View File

@ -72,6 +72,14 @@
# 🌠 Getting started # 🌠 Getting started
Want to just join a Calckey server? View the list here, pick one, and join:
### https://calckey.org/join
---
Want to make your own? Keep reading!
This guide will work for both **starting from scratch** and **migrating from Misskey**. This guide will work for both **starting from scratch** and **migrating from Misskey**.
## 🔰 Easy installers ## 🔰 Easy installers
@ -88,7 +96,6 @@ If you have access to a server that supports one of the sources below, I recomme
## 🧑‍💻 Dependencies ## 🧑‍💻 Dependencies
- 🐢 At least [NodeJS](https://nodejs.org/en/) v18.16.0 (v20 recommended) - 🐢 At least [NodeJS](https://nodejs.org/en/) v18.16.0 (v20 recommended)
- Install with [nvm](https://github.com/nvm-sh/nvm)
- 🐘 At least [PostgreSQL](https://www.postgresql.org/) v12 (v14 recommended) - 🐘 At least [PostgreSQL](https://www.postgresql.org/) v12 (v14 recommended)
- 🍱 At least [Redis](https://redis.io/) v6 (v7 recommended) - 🍱 At least [Redis](https://redis.io/) v6 (v7 recommended)
- Web Proxy (one of the following) - Web Proxy (one of the following)
@ -104,7 +111,11 @@ If you have access to a server that supports one of the sources below, I recomme
- 🦔 [Sonic](https://crates.io/crates/sonic-server) - 🦔 [Sonic](https://crates.io/crates/sonic-server)
- [MeiliSearch](https://www.meilisearch.com/) - [MeiliSearch](https://www.meilisearch.com/)
- [ElasticSearch](https://www.elastic.co/elasticsearch/) - [ElasticSearch](https://www.elastic.co/elasticsearch/)
- Caching server (one of the following)
- 🐲 [DragonflyDB](https://www.dragonflydb.io/) (recommended)
- 👻 [KeyDB](https://keydb.dev/)
- 🍱 Another [Redis](https://redis.io/) server
### 🏗️ Build dependencies ### 🏗️ Build dependencies
- 🦀 At least [Rust](https://www.rust-lang.org/) v1.68.0 - 🦀 At least [Rust](https://www.rust-lang.org/) v1.68.0
@ -161,6 +172,10 @@ psql postgres -c "create database firefish with encoding = 'UTF8';"
In Firefish's directory, fill out the `db` section of `.config/default.yml` with the correct information, where the `db` key is `firefish`. In Firefish's directory, fill out the `db` section of `.config/default.yml` with the correct information, where the `db` key is `firefish`.
## 💰 Caching server
If you experience a lot of traffic, it's a good idea to set up another Redis-compatible caching server. If you don't set one one up, it'll fall back to the mandatory Redis server. DragonflyDB is the recommended option due to its unrivaled performance and ease of use.
## 🔎 Set up search ## 🔎 Set up search
### 🦔 Sonic ### 🦔 Sonic
@ -201,9 +216,9 @@ Please don't use ElasticSearch unless you already have an ElasticSearch setup an
- Edit `.config/default.yml`, making sure to fill out required fields. - Edit `.config/default.yml`, making sure to fill out required fields.
- Also copy and edit `.config/docker_example.env` to `.config/docker.env` if you're using Docker. - Also copy and edit `.config/docker_example.env` to `.config/docker.env` if you're using Docker.
## 🚚 Migrating from Misskey to Firefish ## 🚚 Migrating from Misskey/FoundKey to Calckey
For migrating from Misskey v13, Misskey v12, and Foundkey, read [this document](https://codeberg.org/firefish/firefish/src/branch/develop/docs/migrate.md). For migrating from Misskey v13, Misskey v12, and FoundKey, read [this document](https://codeberg.org/calckey/calckey/src/branch/develop/docs/migrate.md).
## 🌐 Web proxy ## 🌐 Web proxy

13
custom/assets/LICENSE Normal file
View File

@ -0,0 +1,13 @@
Copyright 2023 Calckey
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,10 +1,11 @@
# 🚚 Migrating from Misskey to Firefish # 🚚 Migrating from Misskey/FoundKey to Calckey
The following procedure may not work depending on your environment and version of Misskey. All the guides below assume you're starting in the root of the repo directory.
**Make sure you** ### Before proceeding
- **stopped all master and worker processes of Misskey.**
- **have backups of the database before performing any commands.** - **Ensure you have stopped all master and worker processes of Misskey.**
- **Ensure you have backups of the database before performing any commands.**
## Misskey v13 and above ## Misskey v13 and above
@ -77,15 +78,16 @@ NODE_ENV=production pnpm run migrate
# build using prefered method # build using prefered method
``` ```
## Foundkey ## FoundKey
```sh ```sh
cd packages/backend cd packages/backend
sed -i '12s/^/\/\//' ./migration/1663399074403-resize-comments-drive-file.js
LINE_NUM="$(npx typeorm migration:show -d ormconfig.js | grep -n uniformThemecolor1652859567549 | cut -d ':' -f 1)" LINE_NUM="$(npx typeorm migration:show -d ormconfig.js | grep -n uniformThemecolor1652859567549 | cut -d ':' -f 1)"
NUM_MIGRATIONS="$(npx typeorm migration:show -d ormconfig.js | tail -n+"$LINE_NUM" | grep '\[X\]' | nl)" NUM_MIGRATIONS="$(npx typeorm migration:show -d ormconfig.js | tail -n+"$LINE_NUM" | grep '\[X\]' | wc -l)"
for i in $(seq 1 $NUM_MIGRAIONS); do for i in $(seq 1 $NUM_MIGRATIONS); do
npx typeorm migration:revert -d ormconfig.js npx typeorm migration:revert -d ormconfig.js
done done
@ -100,4 +102,4 @@ NODE_ENV=production pnpm run migrate
## Reverse ## Reverse
You ***cannot*** migrate back to Misskey from Firefish due to re-hashing passwords on signin with argon2. You can migrate from Firefish to Foundkey, though. You ***cannot*** migrate back to Misskey from Calckey due to re-hashing passwords on signin with argon2. You can migrate from Calckey to FoundKey, although this is not recommended due to FoundKey being end-of-life.

View File

@ -95,7 +95,7 @@ privacy: "Privadesa"
makeFollowManuallyApprove: "Les sol·licituds de seguiment requereixen aprovació" makeFollowManuallyApprove: "Les sol·licituds de seguiment requereixen aprovació"
defaultNoteVisibility: "Visibilitat per defecte" defaultNoteVisibility: "Visibilitat per defecte"
follow: "Segueix" follow: "Segueix"
followRequest: "Segueix" followRequest: "Sol·licitud de Seguiment"
followRequests: "Sol·licituds de seguiment" followRequests: "Sol·licituds de seguiment"
unfollow: "Deixa de seguir" unfollow: "Deixa de seguir"
followRequestPending: "Sol·licituds de seguiment pendents" followRequestPending: "Sol·licituds de seguiment pendents"
@ -1382,7 +1382,7 @@ adminCustomCssWarn: Aquesta configuració només s'ha d'utilitzar si sabeu què
showUpdates: Mostra una finestra emergent quan Firefish s'actualitzi showUpdates: Mostra una finestra emergent quan Firefish s'actualitzi
recommendedInstances: Servidors recomanats recommendedInstances: Servidors recomanats
recommendedInstancesDescription: Servidors recomanats separats per salts de línia recommendedInstancesDescription: Servidors recomanats separats per salts de línia
que apareixen a la línia de temps recomanada. NO afegiu `https://`, NOMÉS el domini. que apareixen a la línia de temps recomanada.
caption: Descripció Automàtica caption: Descripció Automàtica
splash: Pantalla de Benvinguda splash: Pantalla de Benvinguda
swipeOnDesktop: Permet lliscar a l'estil del mòbil a l'escriptori swipeOnDesktop: Permet lliscar a l'estil del mòbil a l'escriptori
@ -1603,6 +1603,12 @@ _aboutMisskey:
patrons: Mecenes de Firefish patrons: Mecenes de Firefish
patronsList: Llistats cronològicament, no per la quantitat donada. Fes una donació patronsList: Llistats cronològicament, no per la quantitat donada. Fes una donació
amb l'enllaç de dalt per veure el teu nom aquí! amb l'enllaç de dalt per veure el teu nom aquí!
donateTitle: T'agrada Calckey?
pleaseDonateToCalckey: Penseu en fer una donació a Calckey per donar suport al seu
desenvolupament.
pleaseDonateToHost: Penseu també en fer una donació a la vostre instància, {host},
per ajudar-lo a suportar els costos de funcionament.
donateHost: Fes una donació a {host}
unknown: Desconegut unknown: Desconegut
pageLikesCount: Nombre de pàgines amb M'agrada pageLikesCount: Nombre de pàgines amb M'agrada
youAreRunningUpToDateClient: Estás fent servir la versió del client més nova. youAreRunningUpToDateClient: Estás fent servir la versió del client més nova.
@ -2142,3 +2148,15 @@ _skinTones:
dark: Fosc dark: Fosc
yellow: Groc yellow: Groc
swipeOnMobile: Permet lliscar entre pàgines swipeOnMobile: Permet lliscar entre pàgines
enableIdenticonGeneration: Habilitar la generació d'Identicon
enableServerMachineStats: Habilitar les estadístiques del maquinari del servidor
showPopup: Notificar els usuaris amb una finestra emergent
showWithSparkles: Mostra amb espurnes
youHaveUnreadAnnouncements: Tens anuncis sense llegir
xl: XL
donationLink: Enllaç a la pàgina de donacions
neverShow: No tornis a mostrar
remindMeLater: Potser després
removeMember: Elimina el membre
removeQuote: Elimina la cita
removeRecipient: Elimina el destinatari

View File

@ -105,7 +105,7 @@ privacy: "Privacy"
makeFollowManuallyApprove: "Follow requests require approval" makeFollowManuallyApprove: "Follow requests require approval"
defaultNoteVisibility: "Default visibility" defaultNoteVisibility: "Default visibility"
follow: "Follow" follow: "Follow"
followRequest: "Follow" followRequest: "Follow Request"
followRequests: "Follow requests" followRequests: "Follow requests"
unfollow: "Unfollow" unfollow: "Unfollow"
followRequestPending: "Follow request pending" followRequestPending: "Follow request pending"
@ -644,6 +644,7 @@ useBlurEffectForModal: "Use blur effect for modals"
useFullReactionPicker: "Use full-size reaction picker" useFullReactionPicker: "Use full-size reaction picker"
width: "Width" width: "Width"
height: "Height" height: "Height"
xl: "XL"
large: "Big" large: "Big"
medium: "Medium" medium: "Medium"
small: "Small" small: "Small"
@ -1049,7 +1050,7 @@ customSplashIconsDescription: "URLs for custom splash screen icons separated by
showUpdates: "Show a popup when Firefish updates" showUpdates: "Show a popup when Firefish updates"
recommendedInstances: "Recommended servers" recommendedInstances: "Recommended servers"
recommendedInstancesDescription: "Recommended servers separated by line breaks to recommendedInstancesDescription: "Recommended servers separated by line breaks to
appear in the recommended timeline. Do NOT add `https://`, ONLY the domain." appear in the recommended timeline."
caption: "Auto Caption" caption: "Auto Caption"
splash: "Splash Screen" splash: "Splash Screen"
updateAvailable: "There might be an update available!" updateAvailable: "There might be an update available!"
@ -1112,6 +1113,17 @@ isModerator: "Moderator"
isAdmin: "Administrator" isAdmin: "Administrator"
isPatron: "Firefish Patron" isPatron: "Firefish Patron"
reactionPickerSkinTone: "Preferred emoji skin tone" reactionPickerSkinTone: "Preferred emoji skin tone"
enableServerMachineStats: "Enable server hardware statistics"
enableIdenticonGeneration: "Enable Identicon generation"
showPopup: "Notify users with popup"
showWithSparkles: "Show with sparkles"
youHaveUnreadAnnouncements: "You have unread announcements"
donationLink: "Link to donation page"
neverShow: "Don't show again"
remindMeLater: "Maybe later"
removeQuote: "Remove quote"
removeRecipient: "Remove recipient"
removeMember: "Remove member"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "Reduces the effort of server moderation through automatically recognizing description: "Reduces the effort of server moderation through automatically recognizing
@ -1208,11 +1220,16 @@ _aboutMisskey:
contributors: "Main contributors" contributors: "Main contributors"
allContributors: "All contributors" allContributors: "All contributors"
source: "Source code" source: "Source code"
translation: "Translate Firefish" translation: "Translate Calckey"
donate: "Donate to Firefish" donate: "Donate to Calckey"
donateTitle: "Enjoying Calckey?"
pleaseDonateToCalckey: "Please consider donating to Calckey to support its development."
pleaseDonateToHost: "Please also consider donating to your home server, {host}, to help support its operation costs."
donateHost: "Donate to {host}"
morePatrons: "We also appreciate the support of many other helpers not listed here. morePatrons: "We also appreciate the support of many other helpers not listed here.
Thank you! 🥰" Thank you! 🥰"
patrons: "Firefish patrons" sponsors: "Calckey sponsors"
patrons: "Calckey patrons"
patronsList: "Listed chronologically, not by donation size. Donate with the link above to get your name on here!" patronsList: "Listed chronologically, not by donation size. Donate with the link above to get your name on here!"
_nsfw: _nsfw:
respect: "Hide NSFW media" respect: "Hide NSFW media"

View File

@ -1,10 +1,10 @@
_lang_: "Français" _lang_: "Français"
headlineMisskey: "Réseau relié par des notes" headlineMisskey: "Réseau relié par des notes"
introMisskey: "Bienvenue ! Firefish est un service de microblogage décentralisé, libre\ introMisskey: "Bienvenue ! Calckey est un service de microblogage décentralisé, libre
\ et ouvert.\nÉcrivez des « notes » et partagez ce qui se passe à linstant présent,\ et ouvert.\nÉcrivez des « notes » et partagez ce qui se passe à linstant présent,
\ autour de vous avec les autres \U0001F4E1\nLa fonction « réactions », vous permet\ autour de vous avec les autres 📡\nLa fonction « réactions », vous permet également
\ également dajouter une réaction rapide aux notes des autres utilisateur·rice·s\ dajouter une réaction rapide aux notes des autres utilisateur·rice·s 👍\nExplorons
\ \U0001F44D\nExplorons un nouveau monde \U0001F680" un nouveau monde 🚀"
monthAndDay: "{day}/{month}" monthAndDay: "{day}/{month}"
search: "Rechercher" search: "Rechercher"
notifications: "Notifications" notifications: "Notifications"
@ -26,8 +26,8 @@ otherSettings: "Paramètres avancés"
openInWindow: "Ouvrir dans une nouvelle fenêtre" openInWindow: "Ouvrir dans une nouvelle fenêtre"
profile: "Profil" profile: "Profil"
timeline: "Fil" timeline: "Fil"
noAccountDescription: "Lutilisateur·rice na pas encore renseigné de biographie de\ noAccountDescription: "Lutilisateur·rice na pas encore renseigné de biographie de
\ présentation sur son profil." présentation sur son profil."
login: "Se connecter" login: "Se connecter"
loggingIn: "Connexion en cours" loggingIn: "Connexion en cours"
logout: "Se déconnecter" logout: "Se déconnecter"
@ -48,8 +48,8 @@ copyContent: "Copier le contenu"
copyLink: "Copier le lien" copyLink: "Copier le lien"
delete: "Supprimer" delete: "Supprimer"
deleteAndEdit: "Supprimer et réécrire" deleteAndEdit: "Supprimer et réécrire"
deleteAndEditConfirm: "Êtes-vous sûr·e de vouloir supprimer cette note et la reformuler\ deleteAndEditConfirm: "Êtes-vous sûr·e de vouloir supprimer cette note et la reformuler
\ ? Vous perdrez toutes les réactions, renotes et réponses y afférentes." ? Vous perdrez toutes les réactions, renotes et réponses y afférentes."
addToList: "Ajouter à une liste" addToList: "Ajouter à une liste"
sendMessage: "Envoyer un message" sendMessage: "Envoyer un message"
copyUsername: "Copier le nom dutilisateur·rice" copyUsername: "Copier le nom dutilisateur·rice"
@ -72,8 +72,8 @@ download: "Télécharger"
driveFileDeleteConfirm: "Êtes-vous sûr·e de vouloir supprimer le fichier \"{name}\"\ driveFileDeleteConfirm: "Êtes-vous sûr·e de vouloir supprimer le fichier \"{name}\"\
\ ? Il sera retiré de toutes ses notes liées." \ ? Il sera retiré de toutes ses notes liées."
unfollowConfirm: "Désirez-vous vous désabonner de {name} ?" unfollowConfirm: "Désirez-vous vous désabonner de {name} ?"
exportRequested: "Vous avez demandé une exportation. Lopération pourrait prendre\ exportRequested: "Vous avez demandé une exportation. Lopération pourrait prendre
\ un peu de temps. Une terminée, le fichier résultant sera ajouté au Drive." un peu de temps. Une terminée, le fichier résultant sera ajouté au Drive."
importRequested: "Vous avez initié un import. Cela pourrait prendre un peu de temps." importRequested: "Vous avez initié un import. Cela pourrait prendre un peu de temps."
lists: "Listes" lists: "Listes"
noLists: "Vous navez aucune liste" noLists: "Vous navez aucune liste"
@ -88,12 +88,12 @@ error: "Erreur"
somethingHappened: "Une erreur est survenue" somethingHappened: "Une erreur est survenue"
retry: "Réessayer" retry: "Réessayer"
pageLoadError: "Le chargement de la page a échoué." pageLoadError: "Le chargement de la page a échoué."
pageLoadErrorDescription: "Cela est généralement causé par le cache du navigateur\ pageLoadErrorDescription: "Cela est généralement causé par le cache du navigateur
\ ou par un problème réseau. Veuillez vider votre cache ou attendre un peu et réessayer." ou par un problème réseau. Veuillez vider votre cache ou attendre un peu et réessayer."
serverIsDead: "Le serveur ne répond pas. Patientez quelques instants puis essayez\ serverIsDead: "Le serveur ne répond pas. Patientez quelques instants puis essayez
\ à nouveau." à nouveau."
youShouldUpgradeClient: "Si la page ne s'affiche pas correctement, rechargez-la pour\ youShouldUpgradeClient: "Si la page ne s'affiche pas correctement, rechargez-la pour
\ mettre votre client à jour." mettre votre client à jour."
enterListName: "Nom de la liste" enterListName: "Nom de la liste"
privacy: "Confidentialité" privacy: "Confidentialité"
makeFollowManuallyApprove: "Accepter manuellement les demandes dabonnement" makeFollowManuallyApprove: "Accepter manuellement les demandes dabonnement"
@ -118,11 +118,11 @@ sensitive: "Contenu sensible"
add: "Ajouter" add: "Ajouter"
reaction: "Réactions" reaction: "Réactions"
reactionSetting: "Réactions à afficher dans le sélecteur de réactions" reactionSetting: "Réactions à afficher dans le sélecteur de réactions"
reactionSettingDescription2: "Déplacer pour réorganiser, cliquer pour effacer, utiliser\ reactionSettingDescription2: "Déplacer pour réorganiser, cliquer pour effacer, utiliser
\ « + » pour ajouter." « + » pour ajouter."
rememberNoteVisibility: "Activer l'option \" se souvenir de la visibilité des notes\ rememberNoteVisibility: "Activer l'option \" se souvenir de la visibilité des notes
\ \" vous permet de réutiliser automatiquement la visibilité utilisée lors de la\ \" vous permet de réutiliser automatiquement la visibilité utilisée lors de la publication
\ publication de votre note précédente." de votre note précédente."
attachCancel: "Supprimer le fichier attaché" attachCancel: "Supprimer le fichier attaché"
markAsSensitive: "Marquer comme sensible" markAsSensitive: "Marquer comme sensible"
unmarkAsSensitive: "Supprimer le marquage comme sensible" unmarkAsSensitive: "Supprimer le marquage comme sensible"
@ -150,20 +150,20 @@ emojiUrl: "URL de lémoji"
addEmoji: "Ajouter un émoji" addEmoji: "Ajouter un émoji"
settingGuide: "Configuration proposée" settingGuide: "Configuration proposée"
cacheRemoteFiles: "Mise en cache des fichiers distants" cacheRemoteFiles: "Mise en cache des fichiers distants"
cacheRemoteFilesDescription: "Lorsque cette option est désactivée, les fichiers distants\ cacheRemoteFilesDescription: "Lorsque cette option est désactivée, les fichiers distants
\ sont chargés directement depuis linstance distante. La désactiver diminuera certes\ sont chargés directement depuis linstance distante. La désactiver diminuera certes
\ lutilisation de lespace de stockage local mais augmentera le trafic réseau puisque\ lutilisation de lespace de stockage local mais augmentera le trafic réseau puisque
\ les miniatures ne seront plus générées." les miniatures ne seront plus générées."
flagAsBot: "Ce compte est un robot" flagAsBot: "Ce compte est un robot"
flagAsBotDescription: "Si ce compte est géré de manière automatisée, choisissez cette\ flagAsBotDescription: "Si ce compte est géré de manière automatisée, choisissez cette
\ option. Si elle est activée, elle agira comme un marqueur pour les autres développeurs\ option. Si elle est activée, elle agira comme un marqueur pour les autres développeurs
\ afin d'éviter des chaînes d'interaction sans fin avec d'autres robots et d'ajuster\ afin d'éviter des chaînes d'interaction sans fin avec d'autres robots et d'ajuster
\ les systèmes internes de Firefish pour traiter ce compte comme un robot." les systèmes internes de Calckey pour traiter ce compte comme un robot."
flagAsCat: "Ce compte est un chat" flagAsCat: "Ce compte est un chat"
flagAsCatDescription: "Activer l'option \" Je suis un chat \" pour ce compte." flagAsCatDescription: "Activer l'option \" Je suis un chat \" pour ce compte."
flagShowTimelineReplies: "Afficher les réponses dans le fil" flagShowTimelineReplies: "Afficher les réponses dans le fil"
autoAcceptFollowed: "Accepter automatiquement les demandes dabonnement venant dutilisateur·rice·s\ autoAcceptFollowed: "Accepter automatiquement les demandes dabonnement venant dutilisateur·rice·s
\ que vous suivez" que vous suivez"
addAccount: "Ajouter un compte" addAccount: "Ajouter un compte"
loginFailed: "Échec de la connexion" loginFailed: "Échec de la connexion"
showOnRemote: "Voir sur linstance distante" showOnRemote: "Voir sur linstance distante"
@ -175,12 +175,12 @@ searchWith: "Recherche : {q}"
youHaveNoLists: "Vous navez aucune liste" youHaveNoLists: "Vous navez aucune liste"
followConfirm: "Êtes-vous sûr·e de vouloir suivre {name} ?" followConfirm: "Êtes-vous sûr·e de vouloir suivre {name} ?"
proxyAccount: "Compte proxy" proxyAccount: "Compte proxy"
proxyAccountDescription: "Un compte proxy se comporte, dans certaines conditions,\ proxyAccountDescription: "Un compte proxy se comporte, dans certaines conditions,
\ comme un·e abonné·e distant·e pour les utilisateurs d'autres instances. Par exemple,\ comme un·e abonné·e distant·e pour les utilisateurs d'autres instances. Par exemple,
\ quand un·e utilisateur·rice ajoute un·e utilisateur·rice distant·e à une liste,\ quand un·e utilisateur·rice ajoute un·e utilisateur·rice distant·e à une liste,
\ ses notes ne seront pas visibles sur l'instance si personne ne suit cet·te utilisateur·rice.\ ses notes ne seront pas visibles sur l'instance si personne ne suit cet·te utilisateur·rice.
\ Le compte proxy va donc suivre cet·te utilisateur·rice pour que ses notes soient\ Le compte proxy va donc suivre cet·te utilisateur·rice pour que ses notes soient
\ acheminées." acheminées."
host: "Serveur distant" host: "Serveur distant"
selectUser: "Sélectionner un·e utilisateur·rice" selectUser: "Sélectionner un·e utilisateur·rice"
recipient: "Destinataire" recipient: "Destinataire"
@ -210,14 +210,14 @@ instanceInfo: "Informations sur linstance"
statistics: "Statistiques" statistics: "Statistiques"
clearQueue: "Vider la file dattente" clearQueue: "Vider la file dattente"
clearQueueConfirmTitle: "Êtes-vous sûr·e de vouloir vider la file dattente ?" clearQueueConfirmTitle: "Êtes-vous sûr·e de vouloir vider la file dattente ?"
clearQueueConfirmText: "Les notes non distribuées ne seront pas délivrées. Normalement,\ clearQueueConfirmText: "Les notes non distribuées ne seront pas délivrées. Normalement,
\ vous n'avez pas besoin d'effectuer cette opération." vous n'avez pas besoin d'effectuer cette opération."
clearCachedFiles: "Vider le cache" clearCachedFiles: "Vider le cache"
clearCachedFilesConfirm: "Êtes-vous sûr·e de vouloir vider tout le cache de fichiers\ clearCachedFilesConfirm: "Êtes-vous sûr·e de vouloir vider tout le cache de fichiers
\ distants ?" distants ?"
blockedInstances: "Instances bloquées" blockedInstances: "Instances bloquées"
blockedInstancesDescription: "Listez les instances que vous désirez bloquer, une par\ blockedInstancesDescription: "Listez les instances que vous désirez bloquer, une par
\ ligne. Ces instances ne seront plus en capacité d'interagir avec votre instance." ligne. Ces instances ne seront plus en capacité d'interagir avec votre instance."
muteAndBlock: "Masqué·e·s / Bloqué·e·s" muteAndBlock: "Masqué·e·s / Bloqué·e·s"
mutedUsers: "Utilisateur·rice·s en sourdine" mutedUsers: "Utilisateur·rice·s en sourdine"
blockedUsers: "Utilisateur·rice·s bloqué·e·s" blockedUsers: "Utilisateur·rice·s bloqué·e·s"
@ -270,8 +270,8 @@ fromUrl: "Depuis une URL"
uploadFromUrl: "Téléverser via une URL" uploadFromUrl: "Téléverser via une URL"
uploadFromUrlDescription: "URL du fichier que vous souhaitez téléverser" uploadFromUrlDescription: "URL du fichier que vous souhaitez téléverser"
uploadFromUrlRequested: "Téléversement demandé" uploadFromUrlRequested: "Téléversement demandé"
uploadFromUrlMayTakeTime: "Le téléversement de votre fichier peut prendre un certain\ uploadFromUrlMayTakeTime: "Le téléversement de votre fichier peut prendre un certain
\ temps." temps."
explore: "Découvrir" explore: "Découvrir"
messageRead: "Lu" messageRead: "Lu"
noMoreHistory: "Il ny a plus dhistorique" noMoreHistory: "Il ny a plus dhistorique"
@ -281,8 +281,8 @@ agreeTo: "Jaccepte {0}"
tos: "les conditions dutilisation" tos: "les conditions dutilisation"
start: "Commencer" start: "Commencer"
home: "Principal" home: "Principal"
remoteUserCaution: "Les informations de ce compte risqueraient dêtre incomplètes\ remoteUserCaution: "Les informations de ce compte risqueraient dêtre incomplètes
\ du fait que lutilisateur·rice provient dune instance distante." du fait que lutilisateur·rice provient dune instance distante."
activity: "Activité" activity: "Activité"
images: "Images" images: "Images"
birthday: "Date de naissance" birthday: "Date de naissance"
@ -315,8 +315,8 @@ unableToDelete: "Suppression impossible"
inputNewFileName: "Entrez un nouveau nom de fichier" inputNewFileName: "Entrez un nouveau nom de fichier"
inputNewDescription: "Veuillez entrer une nouvelle description" inputNewDescription: "Veuillez entrer une nouvelle description"
inputNewFolderName: "Entrez un nouveau nom de dossier" inputNewFolderName: "Entrez un nouveau nom de dossier"
circularReferenceFolder: "Le dossier de destination est un sous-dossier du dossier\ circularReferenceFolder: "Le dossier de destination est un sous-dossier du dossier
\ que vous souhaitez déplacer." que vous souhaitez déplacer."
hasChildFilesOrFolders: "Impossible de supprimer ce dossier car il n'est pas vide." hasChildFilesOrFolders: "Impossible de supprimer ce dossier car il n'est pas vide."
copyUrl: "Copier lURL" copyUrl: "Copier lURL"
rename: "Renommer" rename: "Renommer"
@ -350,8 +350,8 @@ connectService: "Connexion"
disconnectService: "Déconnexion" disconnectService: "Déconnexion"
enableLocalTimeline: "Activer le fil local" enableLocalTimeline: "Activer le fil local"
enableGlobalTimeline: "Activer le fil global" enableGlobalTimeline: "Activer le fil global"
disablingTimelinesInfo: "Même si vous désactivez ces fils, les administrateur·rice·s\ disablingTimelinesInfo: "Même si vous désactivez ces fils, les administrateur·rice·s
\ et les modérateur·rice·s pourront toujours y accéder." et les modérateur·rice·s pourront toujours y accéder."
registration: "Sinscrire" registration: "Sinscrire"
enableRegistration: "Autoriser les nouvelles inscriptions" enableRegistration: "Autoriser les nouvelles inscriptions"
invite: "Inviter" invite: "Inviter"
@ -363,11 +363,11 @@ bannerUrl: "URL de limage de la bannière"
backgroundImageUrl: "URL de l'image d'arrière-plan" backgroundImageUrl: "URL de l'image d'arrière-plan"
basicInfo: "Informations basiques" basicInfo: "Informations basiques"
pinnedUsers: "Utilisateur·rice épinglé·e" pinnedUsers: "Utilisateur·rice épinglé·e"
pinnedUsersDescription: "Listez les utilisateur·rice·s que vous souhaitez voir épinglé·e·s\ pinnedUsersDescription: "Listez les utilisateur·rice·s que vous souhaitez voir épinglé·e·s
\ sur la page \"Découvrir\", un·e par ligne." sur la page \"Découvrir\", un·e par ligne."
pinnedPages: "Pages épinglées" pinnedPages: "Pages épinglées"
pinnedPagesDescription: "Inscrivez le chemin des pages que vous souhaitez épingler\ pinnedPagesDescription: "Inscrivez le chemin des pages que vous souhaitez épingler
\ en haut de la page de l'instance. Séparez les pages d'un retour à la ligne." en haut de la page de l'instance. Séparez les pages d'un retour à la ligne."
pinnedClipId: "Identifiant du clip épinglé" pinnedClipId: "Identifiant du clip épinglé"
pinnedNotes: "Note épinglée" pinnedNotes: "Note épinglée"
hcaptcha: "hCaptcha" hcaptcha: "hCaptcha"
@ -378,17 +378,17 @@ recaptcha: "reCAPTCHA"
enableRecaptcha: "Activer reCAPTCHA" enableRecaptcha: "Activer reCAPTCHA"
recaptchaSiteKey: "Clé du site" recaptchaSiteKey: "Clé du site"
recaptchaSecretKey: "Clé secrète" recaptchaSecretKey: "Clé secrète"
avoidMultiCaptchaConfirm: "Lutilisation de plusieurs Captchas peut provoquer des\ avoidMultiCaptchaConfirm: "Lutilisation de plusieurs Captchas peut provoquer des
\ interférences. Souhaitez-vous désactiver lautre Captcha ? Vous pouvez laisser\ interférences. Souhaitez-vous désactiver lautre Captcha ? Vous pouvez laisser plusieurs
\ plusieurs Captcha activés en appuyant sur Annuler." Captcha activés en appuyant sur Annuler."
antennas: "Antennes" antennas: "Antennes"
manageAntennas: "Gestion des antennes" manageAntennas: "Gestion des antennes"
name: "Nom" name: "Nom"
antennaSource: "Source de lantenne" antennaSource: "Source de lantenne"
antennaKeywords: "Mots clés à recevoir" antennaKeywords: "Mots clés à recevoir"
antennaExcludeKeywords: "Mots clés à exclure" antennaExcludeKeywords: "Mots clés à exclure"
antennaKeywordsDescription: "Séparer avec des espaces pour la condition AND. Séparer\ antennaKeywordsDescription: "Séparer avec des espaces pour la condition AND. Séparer
\ avec un saut de ligne pour une condition OR." avec un saut de ligne pour une condition OR."
notifyAntenna: "Je souhaite recevoir les notifications des nouvelles notes" notifyAntenna: "Je souhaite recevoir les notifications des nouvelles notes"
withFileAntenna: "Notes ayant des attachements uniquement" withFileAntenna: "Notes ayant des attachements uniquement"
enableServiceworker: "Activer ServiceWorker" enableServiceworker: "Activer ServiceWorker"
@ -399,11 +399,11 @@ connectedTo: "Vous êtes connectés aux services suivants"
notesAndReplies: "Notes et Réponses" notesAndReplies: "Notes et Réponses"
withFiles: "Avec fichiers joints" withFiles: "Avec fichiers joints"
silence: "Mettre en sourdine" silence: "Mettre en sourdine"
silenceConfirm: "Êtes-vous sûr·e de vouloir mettre lutilisateur·rice en sourdine\ silenceConfirm: "Êtes-vous sûr·e de vouloir mettre lutilisateur·rice en sourdine
\ ?" ?"
unsilence: "Annuler la sourdine" unsilence: "Annuler la sourdine"
unsilenceConfirm: "Êtes-vous sûr·e de vouloir annuler la mise en sourdine de cet·te\ unsilenceConfirm: "Êtes-vous sûr·e de vouloir annuler la mise en sourdine de cet·te
\ utilisateur·rice ?" utilisateur·rice ?"
popularUsers: "Utilisateur·rice·s populaires" popularUsers: "Utilisateur·rice·s populaires"
recentlyUpdatedUsers: "Utilisateur·rice·s actif·ve·s récemment" recentlyUpdatedUsers: "Utilisateur·rice·s actif·ve·s récemment"
recentlyRegisteredUsers: "Utilisateur·rice·s récemment inscrit·e·s" recentlyRegisteredUsers: "Utilisateur·rice·s récemment inscrit·e·s"
@ -468,8 +468,8 @@ invitationCode: "Code dinvitation"
checking: "Vérification en cours..." checking: "Vérification en cours..."
available: "Disponible" available: "Disponible"
unavailable: "Non disponible" unavailable: "Non disponible"
usernameInvalidFormat: "Le nom d'utilisateur peut contenir uniquement des lettres\ usernameInvalidFormat: "Le nom d'utilisateur peut contenir uniquement des lettres
\ (minuscules et/ou majuscules), des chiffres et des _" (minuscules et/ou majuscules), des chiffres et des _"
tooShort: "Trop court" tooShort: "Trop court"
tooLong: "Trop long" tooLong: "Trop long"
weakPassword: "Mot de passe faible" weakPassword: "Mot de passe faible"
@ -478,8 +478,8 @@ strongPassword: "Mot de passe fort"
passwordMatched: "Les mots de passe correspondent" passwordMatched: "Les mots de passe correspondent"
passwordNotMatched: "Les mots de passe ne correspondent pas" passwordNotMatched: "Les mots de passe ne correspondent pas"
signinWith: "Se connecter avec {x}" signinWith: "Se connecter avec {x}"
signinFailed: "Échec dauthentification. Veuillez vérifier que votre nom dutilisateur\ signinFailed: "Échec dauthentification. Veuillez vérifier que votre nom dutilisateur
\ et mot de passe sont corrects." et mot de passe sont corrects."
tapSecurityKey: "Appuyez sur votre clé de sécurité" tapSecurityKey: "Appuyez sur votre clé de sécurité"
or: "OU" or: "OU"
language: "Langue" language: "Langue"
@ -488,8 +488,8 @@ groupInvited: "Invité au groupe"
aboutX: "À propos de {x}" aboutX: "À propos de {x}"
useOsNativeEmojis: "Utiliser les émojis natifs du système" useOsNativeEmojis: "Utiliser les émojis natifs du système"
youHaveNoGroups: "Vous navez aucun groupe" youHaveNoGroups: "Vous navez aucun groupe"
joinOrCreateGroup: "Vous pouvez être invité·e à rejoindre des groupes existants ou\ joinOrCreateGroup: "Vous pouvez être invité·e à rejoindre des groupes existants ou
\ créer votre propre nouveau groupe." créer votre propre nouveau groupe."
noHistory: "Pas d'historique" noHistory: "Pas d'historique"
signinHistory: "Historique de connexion" signinHistory: "Historique de connexion"
disableAnimatedMfm: "Désactiver MFM ayant des animations" disableAnimatedMfm: "Désactiver MFM ayant des animations"
@ -520,29 +520,29 @@ showFeaturedNotesInTimeline: "Afficher les notes des Tendances dans le fil d'act
objectStorage: "Stockage d'objets" objectStorage: "Stockage d'objets"
useObjectStorage: "Utiliser le stockage d'objets" useObjectStorage: "Utiliser le stockage d'objets"
objectStorageBaseUrl: "Base URL" objectStorageBaseUrl: "Base URL"
objectStorageBaseUrlDesc: "Préfixe dURL utilisé pour construire lURL vers le référencement\ objectStorageBaseUrlDesc: "Préfixe dURL utilisé pour construire lURL vers le référencement
\ dobjet (média). Spécifiez son URL si vous utilisez un CDN ou un proxy, sinon\ dobjet (média). Spécifiez son URL si vous utilisez un CDN ou un proxy, sinon spécifiez
\ spécifiez ladresse accessible au public selon le guide de service que vous allez\ ladresse accessible au public selon le guide de service que vous allez utiliser.
\ utiliser. P.ex. 'https://<bucket>.s3.amazonaws.com' pour AWS S3 et 'https://storage.googleapis.com/<bucket>'\ P.ex. 'https://<bucket>.s3.amazonaws.com' pour AWS S3 et 'https://storage.googleapis.com/<bucket>'
\ pour GCS." pour GCS."
objectStorageBucket: "Bucket" objectStorageBucket: "Bucket"
objectStorageBucketDesc: "Veuillez spécifier le nom du compartiment utilisé sur le\ objectStorageBucketDesc: "Veuillez spécifier le nom du compartiment utilisé sur le
\ service configuré." service configuré."
objectStoragePrefix: "Prefix" objectStoragePrefix: "Prefix"
objectStoragePrefixDesc: "Les fichiers seront stockés sous le répertoire de ce préfixe." objectStoragePrefixDesc: "Les fichiers seront stockés sous le répertoire de ce préfixe."
objectStorageEndpoint: "Endpoint" objectStorageEndpoint: "Endpoint"
objectStorageEndpointDesc: "Laissez ce champ vide si vous utilisez AWS S3, sinon spécifiez\ objectStorageEndpointDesc: "Laissez ce champ vide si vous utilisez AWS S3, sinon spécifiez
\ le point de terminaison comme '<host>' ou '<host>: <port>' selon le guide de service\ le point de terminaison comme '<host>' ou '<host>: <port>' selon le guide de service
\ que vous allez utiliser." que vous allez utiliser."
objectStorageRegion: "Région" objectStorageRegion: "Région"
objectStorageRegionDesc: "Spécifiez une région comme 'xx-east-1'. Si votre service\ objectStorageRegionDesc: "Spécifiez une région comme 'xx-east-1'. Si votre service
\ ne fait pas de distinction entre les régions, laissez-le vide ou remplissez 'us-east-1'." ne fait pas de distinction entre les régions, laissez-le vide ou remplissez 'us-east-1'."
objectStorageUseSSL: "Utiliser SSL" objectStorageUseSSL: "Utiliser SSL"
objectStorageUseSSLDesc: "Désactivez cette option si vous n'utilisez pas HTTPS pour\ objectStorageUseSSLDesc: "Désactivez cette option si vous n'utilisez pas HTTPS pour
\ la connexion API" la connexion API"
objectStorageUseProxy: "Se connecter via proxy" objectStorageUseProxy: "Se connecter via proxy"
objectStorageUseProxyDesc: "Désactivez cette option si vous n'utilisez pas de proxy\ objectStorageUseProxyDesc: "Désactivez cette option si vous n'utilisez pas de proxy
\ pour la connexion API" pour la connexion API"
objectStorageSetPublicRead: "Régler sur « public » lors de l'envoi" objectStorageSetPublicRead: "Régler sur « public » lors de l'envoi"
serverLogs: "Journal du serveur" serverLogs: "Journal du serveur"
deleteAll: "Supprimer tout" deleteAll: "Supprimer tout"
@ -570,9 +570,9 @@ sort: "Trier"
ascendingOrder: "Ascendant" ascendingOrder: "Ascendant"
descendingOrder: "Descendant" descendingOrder: "Descendant"
scratchpad: "ScratchPad" scratchpad: "ScratchPad"
scratchpadDescription: "ScratchPad fournit un environnement expérimental pour AiScript.\ scratchpadDescription: "ScratchPad fournit un environnement expérimental pour AiScript.
\ Vous pouvez vérifier la rédaction de votre code, sa bonne exécution et le résultat\ Vous pouvez vérifier la rédaction de votre code, sa bonne exécution et le résultat
\ de son interaction avec Firefish." de son interaction avec Calckey."
output: "Sortie" output: "Sortie"
script: "Script" script: "Script"
disablePagesScript: "Désactiver AiScript sur les Pages" disablePagesScript: "Désactiver AiScript sur les Pages"
@ -580,15 +580,15 @@ updateRemoteUser: "Mettre à jour les informations de lutilisateur·rice dist
deleteAllFiles: "Supprimer tous les fichiers" deleteAllFiles: "Supprimer tous les fichiers"
deleteAllFilesConfirm: "Êtes-vous sûr·e de vouloir supprimer tous les fichiers ?" deleteAllFilesConfirm: "Êtes-vous sûr·e de vouloir supprimer tous les fichiers ?"
removeAllFollowing: "Retenir tous les abonnements" removeAllFollowing: "Retenir tous les abonnements"
removeAllFollowingDescription: "Se désabonner de tous les comptes de {host}. Veuillez\ removeAllFollowingDescription: "Se désabonner de tous les comptes de {host}. Veuillez
\ lancer cette action uniquement si linstance nexiste plus." lancer cette action uniquement si linstance nexiste plus."
userSuspended: "Cet·te utilisateur·rice a été suspendu·e." userSuspended: "Cet·te utilisateur·rice a été suspendu·e."
userSilenced: "Cette utilisateur·trice a été mis·e en sourdine." userSilenced: "Cette utilisateur·trice a été mis·e en sourdine."
yourAccountSuspendedTitle: "Ce compte est suspendu" yourAccountSuspendedTitle: "Ce compte est suspendu"
yourAccountSuspendedDescription: "Ce compte est suspendu car vous avez enfreint les\ yourAccountSuspendedDescription: "Ce compte est suspendu car vous avez enfreint les
\ conditions d'utilisation de l'instance, ou pour un motif similaire. Si vous souhaitez\ conditions d'utilisation de l'instance, ou pour un motif similaire. Si vous souhaitez
\ connaître en détail les raisons de cette suspension, renseignez-vous auprès de\ connaître en détail les raisons de cette suspension, renseignez-vous auprès de l'administrateur·rice
\ l'administrateur·rice de votre instance. Merci de ne pas créer de nouveau compte." de votre instance. Merci de ne pas créer de nouveau compte."
menu: "Menu" menu: "Menu"
divider: "Séparateur" divider: "Séparateur"
addItem: "Ajouter un élément" addItem: "Ajouter un élément"
@ -611,8 +611,8 @@ description: "Description"
describeFile: "Ajouter une description d'image" describeFile: "Ajouter une description d'image"
enterFileDescription: "Saisissez une description" enterFileDescription: "Saisissez une description"
author: "Auteur·rice" author: "Auteur·rice"
leaveConfirm: "Vous avez des modifications non-sauvegardées. Voulez-vous les ignorer\ leaveConfirm: "Vous avez des modifications non-sauvegardées. Voulez-vous les ignorer
\ ?" ?"
manage: "Gestion" manage: "Gestion"
plugins: "Extensions" plugins: "Extensions"
deck: "Deck" deck: "Deck"
@ -629,14 +629,14 @@ permission: "Autorisations"
enableAll: "Tout activer" enableAll: "Tout activer"
disableAll: "Tout désactiver" disableAll: "Tout désactiver"
tokenRequested: "Autoriser l'accès au compte" tokenRequested: "Autoriser l'accès au compte"
pluginTokenRequestedDescription: "Ce plugin pourra utiliser les autorisations définies\ pluginTokenRequestedDescription: "Ce plugin pourra utiliser les autorisations définies
\ ici." ici."
notificationType: "Type de notifications" notificationType: "Type de notifications"
edit: "Editer" edit: "Editer"
emailServer: "Serveur mail" emailServer: "Serveur mail"
enableEmail: "Activer la distribution de courriel" enableEmail: "Activer la distribution de courriel"
emailConfigInfo: "Utilisé pour confirmer votre adresse de courriel et la réinitialisation\ emailConfigInfo: "Utilisé pour confirmer votre adresse de courriel et la réinitialisation
\ de votre mot de passe en cas doubli." de votre mot de passe en cas doubli."
email: "E-mail " email: "E-mail "
emailAddress: "Adresses e-mail" emailAddress: "Adresses e-mail"
smtpConfig: "Paramètres du serveur SMTP" smtpConfig: "Paramètres du serveur SMTP"
@ -644,8 +644,8 @@ smtpHost: "Serveur distant"
smtpPort: "Port" smtpPort: "Port"
smtpUser: "Nom dutilisateur·rice" smtpUser: "Nom dutilisateur·rice"
smtpPass: "Mot de passe" smtpPass: "Mot de passe"
emptyToDisableSmtpAuth: "Laisser le nom dutilisateur et le mot de passe vides pour\ emptyToDisableSmtpAuth: "Laisser le nom dutilisateur et le mot de passe vides pour
\ désactiver la vérification SMTP" désactiver la vérification SMTP"
smtpSecure: "Utiliser SSL/TLS implicitement dans les connexions SMTP" smtpSecure: "Utiliser SSL/TLS implicitement dans les connexions SMTP"
smtpSecureInfo: "Désactiver cette option lorsque STARTTLS est utilisé" smtpSecureInfo: "Désactiver cette option lorsque STARTTLS est utilisé"
testEmail: "Tester la distribution de courriel" testEmail: "Tester la distribution de courriel"
@ -666,24 +666,24 @@ create: "Créer"
notificationSetting: "Paramètres des notifications " notificationSetting: "Paramètres des notifications "
notificationSettingDesc: "Sélectionnez le type de notification à afficher" notificationSettingDesc: "Sélectionnez le type de notification à afficher"
useGlobalSetting: "Utiliser paramètre général" useGlobalSetting: "Utiliser paramètre général"
useGlobalSettingDesc: "S'il est activé, les paramètres de notification de votre compte\ useGlobalSettingDesc: "S'il est activé, les paramètres de notification de votre compte
\ seront utilisés. S'il est désactivé, des configurations individuelles peuvent\ seront utilisés. S'il est désactivé, des configurations individuelles peuvent être
\ être effectuées." effectuées."
other: "Autre" other: "Autre"
regenerateLoginToken: "Régénérer le jeton de connexion" regenerateLoginToken: "Régénérer le jeton de connexion"
regenerateLoginTokenDescription: "Générer un nouveau jeton d'authentification. Cette\ regenerateLoginTokenDescription: "Générer un nouveau jeton d'authentification. Cette
\ opération ne devrait pas être nécessaire ; lors de la génération d'un nouveau\ opération ne devrait pas être nécessaire ; lors de la génération d'un nouveau jeton,
\ jeton, tous les appareils seront déconnectés. " tous les appareils seront déconnectés. "
setMultipleBySeparatingWithSpace: "Vous pouvez en définir plusieurs, en les séparant\ setMultipleBySeparatingWithSpace: "Vous pouvez en définir plusieurs, en les séparant
\ par des espaces." par des espaces."
fileIdOrUrl: "ID du fichier ou URL" fileIdOrUrl: "ID du fichier ou URL"
behavior: "Comportement" behavior: "Comportement"
sample: "Exemple" sample: "Exemple"
abuseReports: "Signalements" abuseReports: "Signalements"
reportAbuse: "Signaler" reportAbuse: "Signaler"
reportAbuseOf: "Signaler {name}" reportAbuseOf: "Signaler {name}"
fillAbuseReportDescription: "Veuillez expliquer les raisons du signalement. S'il s'agit\ fillAbuseReportDescription: "Veuillez expliquer les raisons du signalement. S'il s'agit
\ d'une note précise, veuillez en donner le lien." d'une note précise, veuillez en donner le lien."
abuseReported: "Le rapport est envoyé. Merci." abuseReported: "Le rapport est envoyé. Merci."
reporter: "Signalé par" reporter: "Signalé par"
reporteeOrigin: "Origine du signalement" reporteeOrigin: "Origine du signalement"
@ -694,8 +694,8 @@ abuseMarkAsResolved: "Marquer le signalement comme résolu"
openInNewTab: "Ouvrir dans un nouvel onglet" openInNewTab: "Ouvrir dans un nouvel onglet"
openInSideView: "Ouvrir en vue latérale" openInSideView: "Ouvrir en vue latérale"
defaultNavigationBehaviour: "Navigation par défaut" defaultNavigationBehaviour: "Navigation par défaut"
editTheseSettingsMayBreakAccount: "La modification de ces paramètres peut endommager\ editTheseSettingsMayBreakAccount: "La modification de ces paramètres peut endommager
\ votre compte." votre compte."
instanceTicker: "Nom de l'instance d'origine des notes" instanceTicker: "Nom de l'instance d'origine des notes"
waitingFor: "En attente de {x}" waitingFor: "En attente de {x}"
random: "Aléatoire" random: "Aléatoire"
@ -707,8 +707,8 @@ createNew: "Créer nouveau"
optional: "Facultatif" optional: "Facultatif"
createNewClip: "Créer un nouveau clip" createNewClip: "Créer un nouveau clip"
public: "Public" public: "Public"
i18nInfo: "Firefish est traduit dans différentes langues par des bénévoles. Vous pouvez\ i18nInfo: "Calckey est traduit dans différentes langues par des bénévoles. Vous pouvez
\ contribuer à {link}." contribuer à {link}."
manageAccessTokens: "Gérer les jetons d'accès" manageAccessTokens: "Gérer les jetons d'accès"
accountInfo: " Informations du compte " accountInfo: " Informations du compte "
notesCount: "Nombre de notes" notesCount: "Nombre de notes"
@ -727,16 +727,16 @@ no: "Non"
driveFilesCount: "Nombre de fichiers dans le Drive" driveFilesCount: "Nombre de fichiers dans le Drive"
driveUsage: "Utilisation du Drive" driveUsage: "Utilisation du Drive"
noCrawle: "Refuser l'indexation par les robots" noCrawle: "Refuser l'indexation par les robots"
noCrawleDescription: "Demandez aux moteurs de recherche de ne pas indexer votre page\ noCrawleDescription: "Demandez aux moteurs de recherche de ne pas indexer votre page
\ de profil, vos notes, vos pages, etc." de profil, vos notes, vos pages, etc."
lockedAccountInfo: "À moins que vous ne définissiez la visibilité de votre note sur\ lockedAccountInfo: "À moins que vous ne définissiez la visibilité de votre note sur
\ \"Abonné-e-s\", vos notes sont visibles par tous, même si vous exigez que les\ \"Abonné-e-s\", vos notes sont visibles par tous, même si vous exigez que les demandes
\ demandes d'abonnement soient approuvées manuellement." d'abonnement soient approuvées manuellement."
alwaysMarkSensitive: "Marquer les médias comme contenu sensible par défaut" alwaysMarkSensitive: "Marquer les médias comme contenu sensible par défaut"
loadRawImages: "Affichage complet des images jointes au lieu des vignettes" loadRawImages: "Affichage complet des images jointes au lieu des vignettes"
disableShowingAnimatedImages: "Désactiver l'animation des images" disableShowingAnimatedImages: "Désactiver l'animation des images"
verificationEmailSent: "Un e-mail de vérification a été envoyé. Veuillez accéder au\ verificationEmailSent: "Un e-mail de vérification a été envoyé. Veuillez accéder au
\ lien pour compléter la vérification." lien pour compléter la vérification."
notSet: "Non défini" notSet: "Non défini"
emailVerified: "Votre adresse e-mail a été vérifiée." emailVerified: "Votre adresse e-mail a été vérifiée."
noteFavoritesCount: "Nombre de notes dans les favoris" noteFavoritesCount: "Nombre de notes dans les favoris"
@ -748,16 +748,16 @@ clips: "Clips"
experimentalFeatures: "Fonctionnalités expérimentales" experimentalFeatures: "Fonctionnalités expérimentales"
developer: "Développeur" developer: "Développeur"
makeExplorable: "Rendre le compte visible sur la page \"Découvrir\"." makeExplorable: "Rendre le compte visible sur la page \"Découvrir\"."
makeExplorableDescription: "Si vous désactivez cette option, votre compte n'apparaîtra\ makeExplorableDescription: "Si vous désactivez cette option, votre compte n'apparaîtra
\ pas sur la page \"Découvrir\"." pas sur la page \"Découvrir\"."
showGapBetweenNotesInTimeline: "Afficher un écart entre les notes sur la Timeline" showGapBetweenNotesInTimeline: "Afficher un écart entre les notes sur la Timeline"
duplicate: "Duliquer" duplicate: "Duliquer"
left: "Gauche" left: "Gauche"
center: "Centrer" center: "Centrer"
wide: "Large" wide: "Large"
narrow: "Condensé" narrow: "Condensé"
reloadToApplySetting: "Vos paramètres seront appliqués lorsque vous rechargerez la\ reloadToApplySetting: "Vos paramètres seront appliqués lorsque vous rechargerez la
\ page. Souhaitez-vous recharger ?" page. Souhaitez-vous recharger ?"
needReloadToApply: "Ce paramètre s'appliquera après un rechargement." needReloadToApply: "Ce paramètre s'appliquera après un rechargement."
showTitlebar: "Afficher la barre de titre" showTitlebar: "Afficher la barre de titre"
clearCache: "Vider le cache" clearCache: "Vider le cache"
@ -765,11 +765,11 @@ onlineUsersCount: "{n} utilisateur(s) en ligne"
nUsers: "{n} utilisateur·rice·s" nUsers: "{n} utilisateur·rice·s"
nNotes: "{n} Notes" nNotes: "{n} Notes"
sendErrorReports: "Envoyer les rapports derreur" sendErrorReports: "Envoyer les rapports derreur"
sendErrorReportsDescription: "Si vous activez l'envoi des rapports d'erreur, vous\ sendErrorReportsDescription: "Si vous activez l'envoi des rapports d'erreur, vous
\ contribuerez à améliorer la qualité de Firefish grâce au partage d'informations\ contribuerez à améliorer la qualité de Calckey grâce au partage d'informations détaillées
\ détaillées sur les erreurs lorsqu'un problème survient.\nCela inclut des informations\ sur les erreurs lorsqu'un problème survient.\nCela inclut des informations telles
\ telles que la version de votre système d'exploitation, le type de navigateur que\ que la version de votre système d'exploitation, le type de navigateur que vous utilisez,
\ vous utilisez, votre historique d'activité, etc." votre historique d'activité, etc."
myTheme: "Mes thèmes" myTheme: "Mes thèmes"
backgroundColor: "Arrière-plan" backgroundColor: "Arrière-plan"
accentColor: "Accentuation" accentColor: "Accentuation"
@ -808,17 +808,17 @@ unlikeConfirm: "Êtes-vous sûr·e de ne plus vouloir aimer cette publication ?"
fullView: "Plein écran" fullView: "Plein écran"
quitFullView: "Quitter le plein écran" quitFullView: "Quitter le plein écran"
addDescription: "Ajouter une description" addDescription: "Ajouter une description"
userPagePinTip: "Vous pouvez afficher des notes ici en sélectionnant l'option « Épingler\ userPagePinTip: "Vous pouvez afficher des notes ici en sélectionnant l'option « Épingler
\ au profil » dans le menu de chaque note." au profil » dans le menu de chaque note."
notSpecifiedMentionWarning: "Vous avez mentionné des utilisateur·rice·s qui ne font\ notSpecifiedMentionWarning: "Vous avez mentionné des utilisateur·rice·s qui ne font
\ pas partie de la liste des destinataires" pas partie de la liste des destinataires"
info: "Informations" info: "Informations"
userInfo: "Informations sur l'utilisateur" userInfo: "Informations sur l'utilisateur"
unknown: "Inconnu" unknown: "Inconnu"
onlineStatus: "Statut" onlineStatus: "Statut"
hideOnlineStatus: "Se rendre invisible" hideOnlineStatus: "Se rendre invisible"
hideOnlineStatusDescription: "Rendre votre statut invisible peut diminuer les performances\ hideOnlineStatusDescription: "Rendre votre statut invisible peut diminuer les performances
\ de certaines fonctionnalités, telles que la Recherche." de certaines fonctionnalités, telles que la Recherche."
online: "En ligne" online: "En ligne"
active: "Actif·ve" active: "Actif·ve"
offline: "Hors ligne" offline: "Hors ligne"
@ -853,9 +853,9 @@ emailNotConfiguredWarning: "Vous n'avez pas configuré d'adresse e-mail."
ratio: "Ratio" ratio: "Ratio"
previewNoteText: "Voir l'aperçu" previewNoteText: "Voir l'aperçu"
customCss: "CSS personnalisé" customCss: "CSS personnalisé"
customCssWarn: "Utilisez cette fonctionnalité uniquement si vous savez exactement\ customCssWarn: "Utilisez cette fonctionnalité uniquement si vous savez exactement
\ ce que vous faites. Une configuration inadaptée peut empêcher le client de s'exécuter\ ce que vous faites. Une configuration inadaptée peut empêcher le client de s'exécuter
\ normalement." normalement."
global: "Global" global: "Global"
squareAvatars: "Avatars carrés" squareAvatars: "Avatars carrés"
sent: "Envoyer" sent: "Envoyer"
@ -870,10 +870,10 @@ whatIsNew: "Voir les derniers changements"
translate: "Traduire" translate: "Traduire"
translatedFrom: "Traduit depuis {x}" translatedFrom: "Traduit depuis {x}"
accountDeletionInProgress: "La suppression de votre compte est en cours" accountDeletionInProgress: "La suppression de votre compte est en cours"
usernameInfo: "C'est un nom qui identifie votre compte sur l'instance de manière unique.\ usernameInfo: "C'est un nom qui identifie votre compte sur l'instance de manière unique.
\ Vous pouvez utiliser des lettres de l'alphabet (minuscules et majuscules), des\ Vous pouvez utiliser des lettres de l'alphabet (minuscules et majuscules), des chiffres
\ chiffres (de 0 à 9), ou bien le tiret « _ ». Vous ne pourrez pas modifier votre\ (de 0 à 9), ou bien le tiret « _ ». Vous ne pourrez pas modifier votre nom d'utilisateur·rice
\ nom d'utilisateur·rice par la suite." par la suite."
aiChanMode: "Mode Ai" aiChanMode: "Mode Ai"
keepCw: "Garder le CW" keepCw: "Garder le CW"
pubSub: "Comptes Pub/Sub" pubSub: "Comptes Pub/Sub"
@ -889,14 +889,14 @@ filter: "Filtre"
controlPanel: "Panneau de contrôle" controlPanel: "Panneau de contrôle"
manageAccounts: "Gérer les comptes" manageAccounts: "Gérer les comptes"
makeReactionsPublic: "Rendre les réactions publiques" makeReactionsPublic: "Rendre les réactions publiques"
makeReactionsPublicDescription: "Ceci rendra la liste de toutes vos réactions données\ makeReactionsPublicDescription: "Ceci rendra la liste de toutes vos réactions données
\ publique." publique."
classic: "Classique" classic: "Classique"
muteThread: "Masquer cette discussion" muteThread: "Masquer cette discussion"
unmuteThread: "Ne plus masquer le fil" unmuteThread: "Ne plus masquer le fil"
ffVisibility: "Visibilité des abonnés/abonnements" ffVisibility: "Visibilité des abonnés/abonnements"
ffVisibilityDescription: "Permet de configurer qui peut voir les personnes que tu\ ffVisibilityDescription: "Permet de configurer qui peut voir les personnes que tu
\ suis et les personnes qui te suivent." suis et les personnes qui te suivent."
continueThread: "Afficher la suite du fil" continueThread: "Afficher la suite du fil"
deleteAccountConfirm: "Votre compte sera supprimé. Êtes vous certain ?" deleteAccountConfirm: "Votre compte sera supprimé. Êtes vous certain ?"
incorrectPassword: "Le mot de passe est incorrect." incorrectPassword: "Le mot de passe est incorrect."
@ -904,11 +904,11 @@ voteConfirm: "Confirmez-vous votre vote pour « {choice} » ?"
hide: "Masquer" hide: "Masquer"
leaveGroup: "Quitter le groupe" leaveGroup: "Quitter le groupe"
leaveGroupConfirm: "Êtes vous sûr de vouloir quitter \"{name}\" ?" leaveGroupConfirm: "Êtes vous sûr de vouloir quitter \"{name}\" ?"
useDrawerReactionPickerForMobile: "Afficher le sélecteur de réactions en tant que\ useDrawerReactionPickerForMobile: "Afficher le sélecteur de réactions en tant que
\ panneau sur mobile" panneau sur mobile"
welcomeBackWithName: "Heureux de vous revoir, {name}" welcomeBackWithName: "Heureux de vous revoir, {name}"
clickToFinishEmailVerification: "Veuillez cliquer sur [{ok}] afin de compléter la\ clickToFinishEmailVerification: "Veuillez cliquer sur [{ok}] afin de compléter la
\ vérification par courriel." vérification par courriel."
overridedDeviceKind: "Type dappareil" overridedDeviceKind: "Type dappareil"
smartphone: "Smartphone" smartphone: "Smartphone"
tablet: "Tablette" tablet: "Tablette"
@ -948,16 +948,16 @@ _ffVisibility:
_signup: _signup:
almostThere: "Bientôt fini" almostThere: "Bientôt fini"
emailAddressInfo: "Insérez votre adresse e-mail." emailAddressInfo: "Insérez votre adresse e-mail."
emailSent: "Un courriel de confirmation vient d'être envoyé à l'adresse que vous\ emailSent: "Un courriel de confirmation vient d'être envoyé à l'adresse que vous
\ avez renseignée ({email}). Cliquez sur le lien contenu dans le message pour\ avez renseignée ({email}). Cliquez sur le lien contenu dans le message pour terminer
\ terminer la création de votre compte." la création de votre compte."
_accountDelete: _accountDelete:
accountDelete: "Supprimer le compte" accountDelete: "Supprimer le compte"
mayTakeTime: "La suppression de compte nécessitant beaucoup de ressources, l'exécution\ mayTakeTime: "La suppression de compte nécessitant beaucoup de ressources, l'exécution
\ du processus peut prendre du temps, en fonction de la quantité de contenus que\ du processus peut prendre du temps, en fonction de la quantité de contenus que
\ vous avez créés et du nombre de fichiers que vous avez téléversés." vous avez créés et du nombre de fichiers que vous avez téléversés."
sendEmail: "Une fois la suppression de votre compte effectuée, un courriel sera\ sendEmail: "Une fois la suppression de votre compte effectuée, un courriel sera
\ envoyé à l'adresse que vous aviez enregistrée." envoyé à l'adresse que vous aviez enregistrée."
requestAccountDelete: "Demander la suppression de votre compte" requestAccountDelete: "Demander la suppression de votre compte"
started: "La procédure de suppression a commencé." started: "La procédure de suppression a commencé."
inProgress: "Suppression en cours" inProgress: "Suppression en cours"
@ -965,14 +965,14 @@ _ad:
back: "Retour" back: "Retour"
reduceFrequencyOfThisAd: "Voir cette publicité moins souvent" reduceFrequencyOfThisAd: "Voir cette publicité moins souvent"
_forgotPassword: _forgotPassword:
enterEmail: "Entrez ici l'adresse e-mail que vous avez enregistrée pour votre compte.\ enterEmail: "Entrez ici l'adresse e-mail que vous avez enregistrée pour votre compte.
\ Un lien vous permettant de réinitialiser votre mot de passe sera envoyé à cette\ Un lien vous permettant de réinitialiser votre mot de passe sera envoyé à cette
\ adresse." adresse."
ifNoEmail: "Si vous n'avez pas enregistré d'adresse e-mail, merci de contacter l'administrateur·rice\ ifNoEmail: "Si vous n'avez pas enregistré d'adresse e-mail, merci de contacter l'administrateur·rice
\ de votre instance." de votre instance."
contactAdmin: "Cette instance ne permettant pas l'utilisation d'adresses e-mail,\ contactAdmin: "Cette instance ne permettant pas l'utilisation d'adresses e-mail,
\ prenez contact avec l'administrateur·rice pour procéder à la réinitialisation\ prenez contact avec l'administrateur·rice pour procéder à la réinitialisation
\ de votre mot de passe." de votre mot de passe."
_gallery: _gallery:
my: "Mes publications" my: "Mes publications"
liked: " Publications que j'ai aimées" liked: " Publications que j'ai aimées"
@ -998,10 +998,10 @@ _aboutMisskey:
contributors: "Principaux contributeurs" contributors: "Principaux contributeurs"
allContributors: "Tous les contributeurs" allContributors: "Tous les contributeurs"
source: "Code source" source: "Code source"
translation: "Traduire Firefish" translation: "Traduire Calckey"
donate: "Soutenir Firefish" donate: "Soutenir Calckey"
morePatrons: "Nous apprécions vraiment le soutien de nombreuses autres personnes\ morePatrons: "Nous apprécions vraiment le soutien de nombreuses autres personnes
\ non mentionnées ici. Merci à toutes et à tous ! \U0001F970" non mentionnées ici. Merci à toutes et à tous ! 🥰"
patrons: "Contributeurs" patrons: "Contributeurs"
_nsfw: _nsfw:
respect: "Cacher les médias marqués comme contenu sensible" respect: "Cacher les médias marqués comme contenu sensible"
@ -1009,22 +1009,22 @@ _nsfw:
force: "Cacher tous les médias" force: "Cacher tous les médias"
_mfm: _mfm:
cheatSheet: "Antisèche MFM" cheatSheet: "Antisèche MFM"
intro: "MFM est un langage Markdown spécifique utilisable ici et là dans Firefish.\ intro: "MFM est un langage Markdown spécifique utilisable ici et là dans Calckey.
\ Vous pouvez vérifier ici les structures utilisables avec MFM." Vous pouvez vérifier ici les structures utilisables avec MFM."
dummy: "La Fédiverse s'agrandit avec Firefish" dummy: "La Fédiverse s'agrandit avec Calckey"
mention: "Mentionner" mention: "Mentionner"
mentionDescription: "Vous pouvez afficher un utilisateur spécifique en indiquant\ mentionDescription: "Vous pouvez afficher un utilisateur spécifique en indiquant
\ une arobase suivie d'un nom d'utilisateur" une arobase suivie d'un nom d'utilisateur"
hashtag: "Hashtags" hashtag: "Hashtags"
hashtagDescription: "Vous pouvez afficher un mot-dièse en utilisant un croisillon\ hashtagDescription: "Vous pouvez afficher un mot-dièse en utilisant un croisillon
\ et du texte" et du texte"
url: "URL" url: "URL"
urlDescription: "L'adresse web peut être affichée." urlDescription: "L'adresse web peut être affichée."
link: "Lien" link: "Lien"
linkDescription: "Une partie précise d'une phrase peut être liée à l'adresse web." linkDescription: "Une partie précise d'une phrase peut être liée à l'adresse web."
bold: "Gras" bold: "Gras"
boldDescription: "Il est possible de mettre le texte en exergue en le mettant en\ boldDescription: "Il est possible de mettre le texte en exergue en le mettant en
\ gras." gras."
small: "Diminuer l'emphase" small: "Diminuer l'emphase"
smallDescription: "Le contenu peut être affiché en petit et fin." smallDescription: "Le contenu peut être affiché en petit et fin."
center: "Centrer" center: "Centrer"
@ -1036,8 +1036,8 @@ _mfm:
inlineMath: "Formule mathématique (inline)" inlineMath: "Formule mathématique (inline)"
inlineMathDescription: "Afficher les formules mathématiques (KaTeX)." inlineMathDescription: "Afficher les formules mathématiques (KaTeX)."
blockMath: "Formule mathématique (bloc)" blockMath: "Formule mathématique (bloc)"
blockMathDescription: "Afficher les formules mathématiques (KaTeX) multi-lignes\ blockMathDescription: "Afficher les formules mathématiques (KaTeX) multi-lignes
\ dans un bloc." dans un bloc."
quote: "Citer" quote: "Citer"
quoteDescription: "Affiche le contenu sous forme de citation." quoteDescription: "Affiche le contenu sous forme de citation."
emoji: "Émojis personnalisés" emoji: "Émojis personnalisés"
@ -1067,8 +1067,8 @@ _mfm:
x4: "Plus grand" x4: "Plus grand"
x4Description: "Afficher le contenu en plus grand." x4Description: "Afficher le contenu en plus grand."
blur: "Flou" blur: "Flou"
blurDescription: "Le contenu peut être flouté ; il sera visible en le survolant\ blurDescription: "Le contenu peut être flouté ; il sera visible en le survolant
\ avec le curseur." avec le curseur."
font: "Police de caractères" font: "Police de caractères"
fontDescription: "Il est possible de choisir la police." fontDescription: "Il est possible de choisir la police."
rainbow: "Arc-en-ciel" rainbow: "Arc-en-ciel"
@ -1109,14 +1109,14 @@ _menuDisplay:
hide: "Masquer" hide: "Masquer"
_wordMute: _wordMute:
muteWords: "Mots à filtrer" muteWords: "Mots à filtrer"
muteWordsDescription: "Séparer avec des espaces pour la condition AND. Séparer avec\ muteWordsDescription: "Séparer avec des espaces pour la condition AND. Séparer avec
\ un saut de ligne pour une condition OR." un saut de ligne pour une condition OR."
muteWordsDescription2: "Pour utiliser des expressions régulières (regex), mettez\ muteWordsDescription2: "Pour utiliser des expressions régulières (regex), mettez
\ les mots-clés entre barres obliques." les mots-clés entre barres obliques."
softDescription: "Masquez les notes de votre fil selon les paramètres que vous définissez." softDescription: "Masquez les notes de votre fil selon les paramètres que vous définissez."
hardDescription: "Empêchez votre fil de charger les notes selon les paramètres que\ hardDescription: "Empêchez votre fil de charger les notes selon les paramètres que
\ vous définissez. Cette action est irréversible : si vous modifiez ces paramètres\ vous définissez. Cette action est irréversible : si vous modifiez ces paramètres
\ plus tard, les notes précédemment filtrées ne seront pas récupérées." plus tard, les notes précédemment filtrées ne seront pas récupérées."
soft: "Doux" soft: "Doux"
hard: "Strict" hard: "Strict"
mutedNotes: "Notes filtrées" mutedNotes: "Notes filtrées"
@ -1155,10 +1155,10 @@ _theme:
darken: "Sombre" darken: "Sombre"
lighten: "Clair" lighten: "Clair"
inputConstantName: "Insérez un nom de constante" inputConstantName: "Insérez un nom de constante"
importInfo: "Vous pouvez importer un thème vers léditeur de thèmes en saisissant\ importInfo: "Vous pouvez importer un thème vers léditeur de thèmes en saisissant
\ son code ici." son code ici."
deleteConstantConfirm: "Êtes-vous sûr·e de vouloir supprimer la constante {const}\ deleteConstantConfirm: "Êtes-vous sûr·e de vouloir supprimer la constante {const}
\ ?" ?"
keys: keys:
accent: "Accentuation" accent: "Accentuation"
bg: "Arrière-plan" bg: "Arrière-plan"
@ -1231,51 +1231,51 @@ _tutorial:
step1_1: "Bienvenue!" step1_1: "Bienvenue!"
step1_2: "On va vous installer. Vous serez opérationnel en un rien de temps" step1_2: "On va vous installer. Vous serez opérationnel en un rien de temps"
step2_1: "Tout d'abord, remplissez votre profil" step2_1: "Tout d'abord, remplissez votre profil"
step2_2: "En fournissant quelques informations sur qui vous êtes, il sera plus facile\ step2_2: "En fournissant quelques informations sur qui vous êtes, il sera plus facile
\ pour les autres de savoir s'ils veulent voir vos notes ou vous suivre." pour les autres de savoir s'ils veulent voir vos notes ou vous suivre."
step3_1: "Maintenant il est temps de suivre des gens !" step3_1: "Maintenant il est temps de suivre des gens !"
step3_2: "Votre page d'accueil et vos timelines sociales sont basées sur les personnes\ step3_2: "Votre page d'accueil et vos timelines sociales sont basées sur les personnes
\ que vous suivez, alors essayez de suivre quelques comptes pour commencer.\n\ que vous suivez, alors essayez de suivre quelques comptes pour commencer.\nCliquez
Cliquez sur le cercle plus en haut à droite d'un profil pour le suivre." sur le cercle plus en haut à droite d'un profil pour le suivre."
step4_1: "On y va." step4_1: "On y va."
step4_2: "Pour votre premier post, certaines personnes aiment faire un post {introduction}\ step4_2: "Pour votre premier post, certaines personnes aiment faire un post {introduction}
\ ou un simple post 'Hello world'." ou un simple post 'Hello world'."
step5_1: "Lignes de temps, lignes de temps partout !" step5_1: "Lignes de temps, lignes de temps partout !"
step5_2: "Votre instance a {timelines} différentes chronologies activées !" step5_2: "Votre instance a {timelines} différentes chronologies activées !"
step5_3: "La timeline Home {icon} est l'endroit où vous pouvez voir les publications\ step5_3: "La timeline Home {icon} est l'endroit où vous pouvez voir les publications
\ de vos followers." de vos followers."
step5_4: "La timeline locale {icon} est l'endroit où vous pouvez voir les messages\ step5_4: "La timeline locale {icon} est l'endroit où vous pouvez voir les messages
\ de tout le monde sur cette instance." de tout le monde sur cette instance."
step5_5: "La chronologie {icon} sociale est l'endroit où vous pouvez voir uniquement\ step5_5: "La chronologie {icon} sociale est l'endroit où vous pouvez voir uniquement
\ les publications des comptes que vous suivez." les publications des comptes que vous suivez."
step5_6: "La chronologie {icon} recommandée est l'endroit où vous pouvez voir les\ step5_6: "La chronologie {icon} recommandée est l'endroit où vous pouvez voir les
\ publications des instances recommandées par les administrateurs." publications des instances recommandées par les administrateurs."
step5_7: "La timeline globale {icon} est l'endroit où vous pouvez voir les messages\ step5_7: "La timeline globale {icon} est l'endroit où vous pouvez voir les messages
\ de toutes les autres instances connectées." de toutes les autres instances connectées."
step6_1: "Alors quel est cet endroit ?" step6_1: "Alors quel est cet endroit ?"
step6_2: "Eh bien, vous ne venez pas de rejoindre Firefish. Vous avez rejoint un\ step6_2: "Eh bien, vous ne venez pas de rejoindre Calckey. Vous avez rejoint un
\ portail vers le Fediverse, un réseau interconnecté de milliers de serveurs,\ portail vers le Fediverse, un réseau interconnecté de milliers de serveurs, appelés
\ appelés \"instances\"." \"instances\"."
step6_3: "Chaque serveur fonctionne différemment, et tous les serveurs n'utilisent\ step6_3: "Chaque serveur fonctionne différemment, et tous les serveurs n'utilisent
\ pas Firefish. Cependant, celui-ci le fait ! C'est un peu délicat, mais vous aurez\ pas Calckey. Cependant, celui-ci le fait ! C'est un peu délicat, mais vous aurez
\ le coup de main en un rien de temps." le coup de main en un rien de temps."
step6_4: "Maintenant, allez-y, explorez et amusez-vous !" step6_4: "Maintenant, allez-y, explorez et amusez-vous !"
_2fa: _2fa:
alreadyRegistered: "Configuration déjà achevée." alreadyRegistered: "Configuration déjà achevée."
registerTOTP: "Ajouter un nouvel appareil" registerTOTP: "Ajouter un nouvel appareil"
registerSecurityKey: "Enregistrer une clef" registerSecurityKey: "Enregistrer une clef"
step1: "Tout d'abord, installez une application d'authentification, telle que {a}\ step1: "Tout d'abord, installez une application d'authentification, telle que {a}
\ ou {b}, sur votre appareil." ou {b}, sur votre appareil."
step2: "Ensuite, scannez le code QR affiché sur lécran." step2: "Ensuite, scannez le code QR affiché sur lécran."
step2Url: "Vous pouvez également saisir cette URL si vous utilisez un programme\ step2Url: "Vous pouvez également saisir cette URL si vous utilisez un programme
\ de bureau :" de bureau :"
step3: "Entrez le jeton affiché sur votre application pour compléter la configuration." step3: "Entrez le jeton affiché sur votre application pour compléter la configuration."
step4: "À partir de maintenant, ce même jeton vous sera demandé à chacune de vos\ step4: "À partir de maintenant, ce même jeton vous sera demandé à chacune de vos
\ connexions." connexions."
securityKeyInfo: "Vous pouvez configurer l'authentification WebAuthN pour sécuriser\ securityKeyInfo: "Vous pouvez configurer l'authentification WebAuthN pour sécuriser
\ davantage le processus de connexion grâce à une clé de sécurité matérielle qui\ davantage le processus de connexion grâce à une clé de sécurité matérielle qui
\ prend en charge FIDO2, ou bien en configurant l'authentification par empreinte\ prend en charge FIDO2, ou bien en configurant l'authentification par empreinte
\ digitale ou par code PIN sur votre appareil." digitale ou par code PIN sur votre appareil."
_permissions: _permissions:
"read:account": "Afficher les informations du compte" "read:account": "Afficher les informations du compte"
"write:account": "Mettre à jour les informations de votre compte" "write:account": "Mettre à jour les informations de votre compte"
@ -1311,8 +1311,8 @@ _permissions:
"write:gallery-likes": "Gérer les mentions « J'aime » dans la galerie" "write:gallery-likes": "Gérer les mentions « J'aime » dans la galerie"
_auth: _auth:
shareAccess: "Autoriser \"{name}\" à accéder à votre compte ?" shareAccess: "Autoriser \"{name}\" à accéder à votre compte ?"
shareAccessAsk: "Voulez-vous vraiment autoriser cette application à accéder à votre\ shareAccessAsk: "Voulez-vous vraiment autoriser cette application à accéder à votre
\ compte?" compte?"
permissionAsk: "Cette application nécessite les autorisations suivantes :" permissionAsk: "Cette application nécessite les autorisations suivantes :"
pleaseGoBack: "Veuillez retourner à lapplication" pleaseGoBack: "Veuillez retourner à lapplication"
callback: "Retour vers lapplication" callback: "Retour vers lapplication"
@ -1412,8 +1412,8 @@ _profile:
youCanIncludeHashtags: "Vous pouvez également inclure des hashtags." youCanIncludeHashtags: "Vous pouvez également inclure des hashtags."
metadata: "Informations supplémentaires" metadata: "Informations supplémentaires"
metadataEdit: "Éditer les informations supplémentaires" metadataEdit: "Éditer les informations supplémentaires"
metadataDescription: "Vous pouvez afficher jusqu'à quatre informations supplémentaires\ metadataDescription: "Vous pouvez afficher jusqu'à quatre informations supplémentaires
\ dans votre profil." dans votre profil."
metadataLabel: "Étiquette" metadataLabel: "Étiquette"
metadataContent: "Contenu" metadataContent: "Contenu"
changeAvatar: "Changer l'image de profil" changeAvatar: "Changer l'image de profil"
@ -1487,8 +1487,8 @@ _pages:
url: "URL de la page" url: "URL de la page"
summary: "Résumé de page" summary: "Résumé de page"
alignCenter: "Centrée" alignCenter: "Centrée"
hideTitleWhenPinned: "Masquer le titre de la page lorsque celle-ci est épinglée\ hideTitleWhenPinned: "Masquer le titre de la page lorsque celle-ci est épinglée
\ au profil" au profil"
font: "Police de caractères" font: "Police de caractères"
fontSerif: "Serif" fontSerif: "Serif"
fontSansSerif: "Sans Serif" fontSansSerif: "Sans Serif"
@ -1538,8 +1538,8 @@ _pages:
note: "Note intégrée" note: "Note intégrée"
_note: _note:
id: "Identifiant de la note" id: "Identifiant de la note"
idDescription: "Pour configurer la note, vous pouvez aussi coller ici l'URL\ idDescription: "Pour configurer la note, vous pouvez aussi coller ici l'URL
\ correspondante." correspondante."
detailed: "Afficher les détails" detailed: "Afficher les détails"
switch: "Interrupteur" switch: "Interrupteur"
_switch: _switch:
@ -1692,8 +1692,8 @@ _pages:
_dailyRannum: _dailyRannum:
arg1: "Minimum" arg1: "Minimum"
arg2: "Maximum" arg2: "Maximum"
dailyRandomPick: "Sélectionné au hasard dans la liste (Quotidien pour chaque\ dailyRandomPick: "Sélectionné au hasard dans la liste (Quotidien pour chaque
\ utilisateur)" utilisateur)"
_dailyRandomPick: _dailyRandomPick:
arg1: "Listes" arg1: "Listes"
seedRandom: "Aléatoire (graine)" seedRandom: "Aléatoire (graine)"
@ -1709,8 +1709,8 @@ _pages:
_seedRandomPick: _seedRandomPick:
arg1: "Graine" arg1: "Graine"
arg2: "Listes" arg2: "Listes"
DRPWPM: "Sélectionné au hasard dans une liste de probabilités (Quotidien pour\ DRPWPM: "Sélectionné au hasard dans une liste de probabilités (Quotidien pour
\ chaque utilisateur)" chaque utilisateur)"
_DRPWPM: _DRPWPM:
arg1: "Liste de texte" arg1: "Liste de texte"
pick: "Sélectionner dans la liste" pick: "Sélectionner dans la liste"
@ -1889,10 +1889,10 @@ adminCustomCssWarn: Ce paramètre ne devrait être utilisé que si vous savez ce
dans vos paramètres utilisateur. dans vos paramètres utilisateur.
swipeOnDesktop: Permettre le style de glissement de fenêtre de mobile sur PC swipeOnDesktop: Permettre le style de glissement de fenêtre de mobile sur PC
moveFromLabel: 'Compte depuis lequel vous migrez :' moveFromLabel: 'Compte depuis lequel vous migrez :'
migrationConfirm: "Êtes-vous absolument certain⋅e que vous voulez migrer votre compte\ migrationConfirm: "Êtes-vous absolument certain⋅e que vous voulez migrer votre compte
\ vers {account} ? Une fois fait, vous ne pourrez pas revenir en arrière, et vous\ vers {account} ? Une fois fait, vous ne pourrez pas revenir en arrière, et vous
\ ne pourrez plus utiliser le compte actuel normalement à nouveau.\nAussi, assurez-vous\ ne pourrez plus utiliser le compte actuel normalement à nouveau.\nAussi, assurez-vous
\ d'avoir configuré le compte actuel comme le compte depuis lequel vous migrez." d'avoir configuré le compte actuel comme le compte depuis lequel vous migrez."
_preferencesBackups: _preferencesBackups:
updatedAt: 'Mis à jour le : {date} {time}' updatedAt: 'Mis à jour le : {date} {time}'
cannotLoad: Le chargement a échoué cannotLoad: Le chargement a échoué
@ -1934,8 +1934,8 @@ enterSendsMessage: Appuyer sur Entrée pendant la rédaction pour envoyer le mes
allowedInstancesDescription: Hôtes des instances autorisées pour la fédération, chacun allowedInstancesDescription: Hôtes des instances autorisées pour la fédération, chacun
séparé par une nouvelle ligne (s'applique uniquement en mode privé). séparé par une nouvelle ligne (s'applique uniquement en mode privé).
enableAutoSensitive: Marquage automatique du contenu sensible (NSFW) enableAutoSensitive: Marquage automatique du contenu sensible (NSFW)
regexpErrorDescription: "Il y a eu une erreur dans l'expression régulière à la ligne\ regexpErrorDescription: "Il y a eu une erreur dans l'expression régulière à la ligne
\ {line} de votre {tab} des mots masqués:" {line} de votre {tab} des mots masqués:"
forwardReportIsAnonymous: À la place de votre compte, un compte système anonyme sera forwardReportIsAnonymous: À la place de votre compte, un compte système anonyme sera
affiché comme rapporteur à l'instance distante. affiché comme rapporteur à l'instance distante.
noThankYou: Non merci noThankYou: Non merci
@ -1944,16 +1944,15 @@ renoteMute: Mettre en silence les renotes
flagSpeakAsCat: Parler comme un chat flagSpeakAsCat: Parler comme un chat
flagSpeakAsCatDescription: Vos messages seront nyanifiés en mode chat flagSpeakAsCatDescription: Vos messages seront nyanifiés en mode chat
hiddenTags: Hashtags cachés hiddenTags: Hashtags cachés
hiddenTagsDescription: "Lister les hashtags (sans le #) que vous souhaitez cacher\ hiddenTagsDescription: "Lister les hashtags (sans le #) que vous souhaitez cacher
\ de tendances et explorer. Les hashtags cachés sont toujours découvrables par d'autres\ de tendances et explorer. Les hashtags cachés sont toujours découvrables par d'autres
\ moyens. Les instances bloqués ne sont pas ne sont pas affectés, même si ils sont\ moyens. Les instances bloqués ne sont pas ne sont pas affectés, même si ils sont
\ présent dans cette liste." présent dans cette liste."
antennaInstancesDescription: Lister un hôte d'instance par ligne antennaInstancesDescription: Lister un hôte d'instance par ligne
userSaysSomethingReason: '{name} a dit {reason}' userSaysSomethingReason: '{name} a dit {reason}'
breakFollowConfirm: Êtes vous sur de vouloir retirer l'abonné ? breakFollowConfirm: Êtes vous sur de vouloir retirer l'abonné ?
recommendedInstancesDescription: Instances recommandées séparées par une nouvelle recommendedInstancesDescription: Instances recommandées séparées par une nouvelle
ligne pour apparaître dans la timeline recommandée. Ne PAS ajouter `https://`, SEULEMENT ligne pour apparaître dans la timeline recommandée.
le domaine.
sendPushNotificationReadMessage: Supprimer les notifications push une fois que les sendPushNotificationReadMessage: Supprimer les notifications push une fois que les
notifications ou messages concernés ont été lus notifications ou messages concernés ont été lus
sendPushNotificationReadMessageCaption: Une notification contenant le texte "{emptyPushNotificationMessage}" sendPushNotificationReadMessageCaption: Une notification contenant le texte "{emptyPushNotificationMessage}"
@ -2005,17 +2004,16 @@ indexNotice: Indexation en cours. Cela prendra certainement du temps, veuillez n
customKaTeXMacro: Macros KaTeX personnalisées customKaTeXMacro: Macros KaTeX personnalisées
enableCustomKaTeXMacro: Activer les macros KaTeX personnalisées enableCustomKaTeXMacro: Activer les macros KaTeX personnalisées
noteId: ID de note noteId: ID de note
customKaTeXMacroDescription: "Définissez des macros pour écrire des expressions mathématiques\ customKaTeXMacroDescription: "Définissez des macros pour écrire des expressions mathématiques
\ simplement ! La notation se conforme aux définitions de commandes LaTeX et s'écrit\ simplement ! La notation se conforme aux définitions de commandes LaTeX et s'écrit
\ \\newcommand{\\name}{content} ou \\newcommand{\\name}[number of arguments]{content}.\ \\newcommand{\\name}{content} ou \\newcommand{\\name}[number of arguments]{content}.
\ Par exemple, \\newcommand{\\add}[2]{#1 + #2} étendra \\add{3}{foo} en 3 + foo.\ Par exemple, \\newcommand{\\add}[2]{#1 + #2} étendra \\add{3}{foo} en 3 + foo. Les
\ Les accolades entourant le nom de la macro peuvent être changés pour des parenthèses\ accolades entourant le nom de la macro peuvent être changés pour des parenthèses
\ ou des crochets. Cela affectera les types de parenthèses utilisées pour les arguments.\ ou des crochets. Cela affectera les types de parenthèses utilisées pour les arguments.
\ Une (et une seule) macro peut être définie par ligne, et vous ne pouvez pas couper\ Une (et une seule) macro peut être définie par ligne, et vous ne pouvez pas couper
\ la ligne au milieu d'une définition. Les lignes invalides sont simplement ignorées.\ la ligne au milieu d'une définition. Les lignes invalides sont simplement ignorées.
\ Seulement de simples fonctions de substitution de chaines sont supportées; la\ Seulement de simples fonctions de substitution de chaines sont supportées; la syntaxe
\ syntaxe avancée, telle que la ramification conditionnelle, ne peut pas être utilisée\ avancée, telle que la ramification conditionnelle, ne peut pas être utilisée ici."
\ ici."
enableRecommendedTimeline: Activer la chronologie recommandée enableRecommendedTimeline: Activer la chronologie recommandée
silenceThisInstance: Ne plus montrer cet instance silenceThisInstance: Ne plus montrer cet instance
silencedInstances: Instances silencieuses silencedInstances: Instances silencieuses
@ -2038,3 +2036,4 @@ signupsDisabled: Les inscriptions sur ce serveur sont actuellement désactivés,
apps: Applications apps: Applications
userSaysSomethingReasonReply: '{noms} a répondu à une note contenant {raison}' userSaysSomethingReasonReply: '{noms} a répondu à une note contenant {raison}'
defaultValueIs: 'défaut: {valeur}' defaultValueIs: 'défaut: {valeur}'
searchPlaceholder: Recherchez sur Calckey

17
locales/gl.yml Normal file
View File

@ -0,0 +1,17 @@
_lang_: Inglés
introMisskey: Benvida! Calckey é unha plataforma de medios sociais de código aberto,
descentralizada e gratuíta para sempre!🚀
monthAndDay: '{day}/{month}'
notifications: Notificacións
password: Contrasinal
forgotPassword: Esquecín o contrasinal
gotIt: Vale!
cancel: Cancelar
noThankYou: Non, grazas
headlineMisskey: Plataforma de medios sociais de código aberto e descentralizada,
gratuíta para sempre!🚀
search: Buscar
searchPlaceholder: Buscar en Calckey
username: Identificador
fetchingAsApObject: Descargando desde o Fediverso
ok: OK

View File

@ -946,7 +946,7 @@ customSplashIconsDescription: "ユーザがページをロード/リロードす
URL。画像は静的なURLで、できればすべて192x192にリサイズしてください。" URL。画像は静的なURLで、できればすべて192x192にリサイズしてください。"
showUpdates: "Firefishの更新時にポップアップを表示する" showUpdates: "Firefishの更新時にポップアップを表示する"
recommendedInstances: "おすすめサーバー" recommendedInstances: "おすすめサーバー"
recommendedInstancesDescription: "おすすめタイムラインに表示するサーバーを改行区切りで入力してください。`https://`は書かず、ドメインのみを入力してください。" recommendedInstancesDescription: "おすすめタイムラインに表示するサーバーを改行区切りで入力してください。"
caption: "自動キャプション" caption: "自動キャプション"
splash: "スプラッシュスクリーン" splash: "スプラッシュスクリーン"
updateAvailable: "アップデートがありますよ!" updateAvailable: "アップデートがありますよ!"
@ -977,7 +977,14 @@ customKaTeXMacroDescription: "数式入力を楽にするためのマクロを
enableCustomKaTeXMacro: "カスタムKaTeXマクロを有効にする" enableCustomKaTeXMacro: "カスタムKaTeXマクロを有効にする"
preventAiLearning: "AIによる学習を防止" preventAiLearning: "AIによる学習を防止"
preventAiLearningDescription: "投稿したート、添付した画像などのコンテンツを学習の対象にしないようAIに要求します。これはnoaiフラグをHTMLレスポンスに含めることによって実現されます。" preventAiLearningDescription: "投稿したート、添付した画像などのコンテンツを学習の対象にしないようAIに要求します。これはnoaiフラグをHTMLレスポンスに含めることによって実現されます。"
noGraze: "ブラウザの拡張機能「Graze for Mastodon」は、Firefishの動作を妨げるため、無効にしてください。" noGraze: "ブラウザの拡張機能「Graze for Mastodon」は、Calckeyの動作を妨げるため、無効にしてください。"
enableServerMachineStats: "サーバーのマシン情報を公開する"
enableIdenticonGeneration: "ユーザーごとのIdenticon生成を有効にする"
showPopup: "ポップアップを表示してユーザーに知らせる"
showWithSparkles: "タイトルをキラキラさせる"
youHaveUnreadAnnouncements: "未読のお知らせがあります"
neverShow: "今後表示しない"
remindMeLater: "また後で"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てられます。サーバーの負荷が少し増えます。" description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てられます。サーバーの負荷が少し増えます。"
@ -1062,6 +1069,11 @@ _aboutMisskey:
donate: "Firefishに寄付" donate: "Firefishに寄付"
morePatrons: "他にも多くの方が支援してくれています。ありがとうございます! 🥰" morePatrons: "他にも多くの方が支援してくれています。ありがとうございます! 🥰"
patrons: "支援者" patrons: "支援者"
patronsList: 寄付額ではなく時系列順に並んでいます。上記のリンクから寄付を行ってここにあなたのIDを載せましょう
pleaseDonateToCalckey: Calckey開発への寄付をご検討ください。
pleaseDonateToHost: また、このサーバー {host} の運営者への寄付もご検討ください。
donateHost: '{host} に寄付する'
donateTitle: Calckeyを気に入りましたか
_nsfw: _nsfw:
respect: "閲覧注意のメディアは隠す" respect: "閲覧注意のメディアは隠す"
ignore: "閲覧注意のメディアを隠さない" ignore: "閲覧注意のメディアを隠さない"
@ -1373,11 +1385,12 @@ _permissions:
_auth: _auth:
shareAccess: "「{name}」がアカウントにアクセスすることを許可しますか?" shareAccess: "「{name}」がアカウントにアクセスすることを許可しますか?"
shareAccessAsk: "アカウントへのアクセスを許可しますか?" shareAccessAsk: "アカウントへのアクセスを許可しますか?"
permissionAsk: "このアプリケーションは次の権限を要求しています" permissionAsk: "このアプリケーションは次の権限を要求しています:"
pleaseGoBack: "アプリケーションに戻り続行してください" pleaseGoBack: "アプリケーションに戻り続行してください"
callback: "アプリケーションに戻っています" callback: "アプリケーションに戻っています"
denied: "アクセスを拒否しました" denied: "アクセスを拒否しました"
copyAsk: "以下の認証コードをアプリケーションにコピーしてください" copyAsk: "以下の認証コードをアプリケーションにコピーしてください:"
allPermissions: 全てのアクセス権
_antennaSources: _antennaSources:
all: "全ての投稿" all: "全ての投稿"
homeTimeline: "フォローしているユーザーの投稿" homeTimeline: "フォローしているユーザーの投稿"
@ -1451,11 +1464,11 @@ _poll:
remainingSeconds: "終了まであと{s}秒" remainingSeconds: "終了まであと{s}秒"
_visibility: _visibility:
public: "公開" public: "公開"
publicDescription: "全てのユーザーに公開" publicDescription: "全ての公開タイムラインに配信されます"
home: "未収載" home: "未収載"
homeDescription: "ホームタイムラインのみに公開" homeDescription: "ホームタイムラインのみに公開"
followers: "フォロワー" followers: "フォロワー"
followersDescription: "自分のフォロワーのみに公開" followersDescription: "フォロワーと会話相手のみに公開"
specified: "ダイレクト" specified: "ダイレクト"
specifiedDescription: "指定したユーザーのみに公開" specifiedDescription: "指定したユーザーのみに公開"
localOnly: "ローカルのみ" localOnly: "ローカルのみ"
@ -1888,14 +1901,14 @@ hiddenTags: 非表示にするハッシュタグ
apps: "アプリ" apps: "アプリ"
_experiments: _experiments:
title: 試験的な機能 title: 試験的な機能
postImportsCaption: postImportsCaption:
ユーザーが過去の投稿をFirefish・Misskey・Mastodon・Akkoma・Pleromaからインポートすることを許可します。キューが溜まっているときにインポートするとサーバーに負荷がかかる可能性があります。 ユーザーが過去の投稿をCalckey・Misskey・Mastodon・Akkoma・Pleromaからインポートすることを許可します。キューが溜まっているときにインポートするとサーバーに負荷がかかる可能性があります。
enablePostImports: 投稿のインポートを有効にする enablePostImports: 投稿のインポートを有効にする
sendModMail: モデレーション通知を送る sendModMail: モデレーション通知を送る
deleted: 削除済み deleted: 削除済み
editNote: 投稿を編集 editNote: 投稿を編集
edited: '編集済み: {date} {time}' edited: '編集済み: {date} {time}'
signupsDisabled: signupsDisabled:
現在、このサーバーでは新規登録が一般開放されていません。招待コードをお持ちの場合には、以下の欄に入力してください。招待コードをお持ちでない場合にも、新規登録を開放している他のサーバーには入れますよ! 現在、このサーバーでは新規登録が一般開放されていません。招待コードをお持ちの場合には、以下の欄に入力してください。招待コードをお持ちでない場合にも、新規登録を開放している他のサーバーには入れますよ!
findOtherInstance: 他のサーバーを探す findOtherInstance: 他のサーバーを探す
newer: 新しい投稿 newer: 新しい投稿
@ -1929,4 +1942,20 @@ video: 動画
isBot: このアカウントはBotです isBot: このアカウントはBotです
isLocked: このアカウントのフォローは承認制です isLocked: このアカウントのフォローは承認制です
isAdmin: 管理者 isAdmin: 管理者
isPatron: Firefish 後援者 isPatron: Calckey 後援者
_skinTones:
light: ペールオレンジ
mediumLight: ミディアムライト
medium: ミディアム
mediumDark: ミディアムダーク
yellow: 黄色
dark: 茶色
removeReaction: リアクションを取り消す
alt: 代替テキスト
swipeOnMobile: ページ間のスワイプを有効にする
reactionPickerSkinTone: 優先する絵文字のスキン色
xl: 特大
donationLink: 寄付ページへのリンク
removeMember: メンバーを削除
removeQuote: 引用を削除
removeRecipient: 宛先を削除

View File

@ -1,2 +1,83 @@
---
_lang_: "Norsk Bokmål" _lang_: "Norsk Bokmål"
search: Søk
monthAndDay: '{day}/{month}'
fetchingAsApObject: Henter fra fediverset
ok: OK
gotIt: Jeg forstår!
profile: Profil
timeline: Tidslinje
save: Lagre
addToList: Legg til liste
searchPlaceholder: Søk Calckey
username: Brukernavn
password: Passord
notifications: Meldinger
forgotPassword: Glemt passord
cancel: Avbryt
noNotes: Ingen poster
instance: Server
settings: Innstillinger
noAccountDescription: Denne brukeren har ikke fylt ut bio'en sin ennå.
login: Logg inn
loggingIn: Logger inn
signup: Oppretter bruker
uploading: Laster opp..
enterUsername: Skriv inn brukernavn
noNotifications: Ingen meldinger
users: Brukere
addUser: Legg til en bruker
favorite: Legg til i bokmerker
cantFavorite: Kunne ikke legges til i bokmerker.
pin: Fest til profilen
copyContent: Kopier innhold
deleteAndEdit: Slett og rediger
sendMessage: Send en melding
copyUsername: Kopier brukernavn
reply: Svar
loadMore: Last mer
showLess: Lukk
receiveFollowRequest: Følgeforespørsel mottatt
directNotes: Direktemelding
importAndExport: Importer/eksporter data
importRequested: Du har bedt om en importering. Dette vil ta litt tid.
lists: Lister
listsDesc: Lister lar deg lage tidslinjer med utvalgte brukere. De kan hentes frem
fra tidslinje-siden.
deleted: Slettet
editNote: Rediger notat
followsYou: Følger deg
createList: Lag liste
newer: nyere
older: eldre
download: Last ned
unfollowConfirm: Er du sikker på at du ikke lenger vil følge {name}?
noLists: Du har ingen lister
following: Følger
files: Filer
note: Post
notes: Poster
followers: Følgere
otherSettings: Andre innstillinger
addInstance: Legg til en server
alreadyFavorited: Allerede lagt til i bokmerker.
delete: Slett
openInWindow: Åpne i vindu
basicSettings: Grunnleggende innstillinger
headlineMisskey: En desentralisert sosialt media-plattform, basert på åpen kildekode,
som alltid vil være gratis! 🚀
introMisskey: Velkommen! Calckey er en desentralisert sosialt media-plattform, basert
på åpen kildekode, som alltid vil være gratis! 🚀
exportRequested: Du har bedt om en eksportering. Dette vil ta litt tid. Den vil bli
lagt til på disken din når den er ferdig.
noThankYou: Nei takk
favorites: Bokmerker
unfavorite: Fjern fra bokmerker
favorited: Lagt til i bokmerker.
copyLink: Kopier lenke
searchUser: Søk etter en bruker
jumpToPrevious: Gå til foregående
showMore: Vis mer
followRequestAccepted: Følgeforespørsel godtatt
import: Importer
export: Eksporter
logout: Logger ut

View File

@ -85,3 +85,28 @@ noLists: Você não possui nenhuma lista
following: Seguindo following: Seguindo
followers: Seguidores followers: Seguidores
followsYou: Segue você followsYou: Segue você
fetchingAsApObject: Buscando do Fediverse
timeline: Linha do tempo
favorite: Adicionar aos marcadores
favorites: Marcadores
unfavorite: Remover dos marcadores
favorited: Adicionado aos marcadores.
alreadyFavorited: Já foi adicionado aos marcadores.
download: Download
pageLoadError: Ocorreu um erro ao carregar a página.
pageLoadErrorDescription: Isso normalmente é causado por erros de rede ou pelo cache
do navegador. Tente limpar o cache e, depois de esperar um pouquinho, tente novamente.
serverIsDead: Esse servidos não está respondendo. Por favor espere um pouco e tente
novamente.
youShouldUpgradeClient: Para visualizar essa página, favor reiniciar para atualizar
seu cliente.
enterListName: Insira um nome para a lista
privacy: Privacidade
defaultNoteVisibility: Visibilidade padrão
makeFollowManuallyApprove: Pedidos de seguimento precisam de aprovação
follow: Seguir
followRequest: Seguir
followRequests: Pedidos de seguimento
unfollow: Parar de seguir
followRequestPending: Pedido de seguimento pendente
enterEmoji: Insira um emoji

View File

@ -1847,7 +1847,7 @@ customMOTDDescription: Пользовательские сообщения дл
разрывами строк, будут отображаться случайным образом каждый раз, когда пользователь разрывами строк, будут отображаться случайным образом каждый раз, когда пользователь
загружает / перезагружает страницу. загружает / перезагружает страницу.
recommendedInstancesDescription: Рекомендуемые инстансы, разделенные разрывами строк, recommendedInstancesDescription: Рекомендуемые инстансы, разделенные разрывами строк,
должны отображаться на рекомендуемой ленте. НЕ добавляйте `https://`, ТОЛЬКО домен. должны отображаться на рекомендуемой ленте.
caption: Автоматическая подпись caption: Автоматическая подпись
splash: Заставка splash: Заставка
updateAvailable: Возможно, доступно обновление! updateAvailable: Возможно, доступно обновление!

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,9 @@
---
_lang_: "Українська" _lang_: "Українська"
headlineMisskey: "Мережа об'єднана записами" headlineMisskey: "Мережа об'єднана записами"
introMisskey: "Ласкаво просимо! Firefish - децентралізована служба мікроблогів з відкритим кодом.\nСтворюйте \"нотатки\", щоб поділитися тим, що відбувається, і розповісти всім про себе 📡\nЗа допомогою \"реакцій\" ви також можете швидко висловити свої почуття щодо нотаток інших 👍\nДосліджуймо новий світ! 🚀" introMisskey: "Ласкаво просимо! Calckey - децентралізована служба мікроблогів з відкритим
кодом.\nСтворюйте \"нотатки\", щоб поділитися тим, що відбувається, і розповісти
всім про себе 📡\nЗа допомогою \"реакцій\" ви також можете швидко висловити свої
почуття щодо нотаток інших 👍\nДосліджуймо новий світ! 🚀"
monthAndDay: "{month}/{day}" monthAndDay: "{month}/{day}"
search: "Пошук" search: "Пошук"
notifications: "Сповіщення" notifications: "Сповіщення"
@ -14,16 +16,16 @@ gotIt: "Зрозуміло!"
cancel: "Скасувати" cancel: "Скасувати"
enterUsername: "Введіть ім'я користувача" enterUsername: "Введіть ім'я користувача"
renotedBy: "Поширено {user}" renotedBy: "Поширено {user}"
noNotes: "Немає нотаток" noNotes: "Немає записів"
noNotifications: "Немає сповіщень" noNotifications: "Немає сповіщень"
instance: "Інстанс" instance: "Сервер"
settings: "Налаштування" settings: "Налаштування"
basicSettings: "Основні налаштування" basicSettings: "Основні налаштування"
otherSettings: "Інші налаштування" otherSettings: "Інші налаштування"
openInWindow: "Відкрити у вікні" openInWindow: "Відкрити у вікні"
profile: "Профіль" profile: "Профіль"
timeline: "Стрічка" timeline: "Стрічка"
noAccountDescription: "Цей користувач ще нічого не написав про себе" noAccountDescription: "Цей користувач ще нічого не написав про себе."
login: "Увійти" login: "Увійти"
loggingIn: "Здійснюємо вхід..." loggingIn: "Здійснюємо вхід..."
logout: "Вийти" logout: "Вийти"
@ -44,7 +46,8 @@ copyContent: "Скопіювати контент"
copyLink: "Скопіювати посилання" copyLink: "Скопіювати посилання"
delete: "Видалити" delete: "Видалити"
deleteAndEdit: "Видалити й редагувати" deleteAndEdit: "Видалити й редагувати"
deleteAndEditConfirm: "Ви впевнені, що хочете видалити цю нотатку та відредагувати її? Ви втратите всі реакції, поширення та відповіді на неї." deleteAndEditConfirm: "Ви впевнені, що хочете видалити цей запис та відредагувати
його? Ви втратите всі реакції, поширення та відповіді на нього."
addToList: "Додати до списку" addToList: "Додати до списку"
sendMessage: "Надіслати повідомлення" sendMessage: "Надіслати повідомлення"
copyUsername: "Скопіювати ім’я користувача" copyUsername: "Скопіювати ім’я користувача"
@ -64,9 +67,11 @@ import: "Імпорт"
export: "Експорт" export: "Експорт"
files: "Файли" files: "Файли"
download: "Завантажити" download: "Завантажити"
driveFileDeleteConfirm: "Ви впевнені, що хочете видалити файл {name}? Нотатки із цим файлом також буде видалено." driveFileDeleteConfirm: "Ви впевнені, що хочете видалити файл {name}? Його буде видалено
з усіх записів які містили його."
unfollowConfirm: "Ви впевнені, що хочете відписатися від {name}?" unfollowConfirm: "Ви впевнені, що хочете відписатися від {name}?"
exportRequested: "Експортування розпочато. Це може зайняти деякий час. Після завершення експорту отриманий файл буде додано на диск." exportRequested: "Експортування розпочато. Це може зайняти деякий час. Після завершення
експорту отриманий файл буде додано на диск."
importRequested: "Імпортування розпочато. Це може зайняти деякий час." importRequested: "Імпортування розпочато. Це може зайняти деякий час."
lists: "Списки" lists: "Списки"
noLists: "Немає списків" noLists: "Немає списків"
@ -80,10 +85,12 @@ manageLists: "Управління списками"
error: "Помилка" error: "Помилка"
somethingHappened: "Щось пішло не так" somethingHappened: "Щось пішло не так"
retry: "Спробувати знову" retry: "Спробувати знову"
pageLoadError: "Помилка при завантаженні сторінки" pageLoadError: "Помилка при завантаженні сторінки."
pageLoadErrorDescription: "Зазвичай це пов’язано з помилками мережі або кешем браузера. Очистіть кеш або почекайте трохи й спробуйте ще раз." pageLoadErrorDescription: "Зазвичай це пов’язано з помилками мережі або кешем браузера.
Очистіть кеш або почекайте трохи й спробуйте ще раз."
serverIsDead: "Відповіді від сервера немає. Зачекайте деякий час і повторіть спробу." serverIsDead: "Відповіді від сервера немає. Зачекайте деякий час і повторіть спробу."
youShouldUpgradeClient: "Перезавантажте та використовуйте нову версію клієнта, щоб переглянути цю сторінку." youShouldUpgradeClient: "Перезавантажте та використовуйте нову версію клієнта, щоб
переглянути цю сторінку."
enterListName: "Введіть назву списку" enterListName: "Введіть назву списку"
privacy: "Конфіденційність" privacy: "Конфіденційність"
makeFollowManuallyApprove: "Підтверджувати підписників уручну" makeFollowManuallyApprove: "Підтверджувати підписників уручну"
@ -95,10 +102,10 @@ unfollow: "Відписатись"
followRequestPending: "Очікуючі запити на підписку" followRequestPending: "Очікуючі запити на підписку"
enterEmoji: "Введіть емодзі" enterEmoji: "Введіть емодзі"
renote: "Поширити" renote: "Поширити"
unrenote: "Відміна поширення" unrenote: "скасувати поширення"
renoted: "Поширити запис." renoted: "Поширено."
cantRenote: "Неможливо поширити." cantRenote: "Цей запис неможливо поширити."
cantReRenote: "Поширення не можливо поширити." cantReRenote: "Поширення неможливо поширити."
quote: "Цитата" quote: "Цитата"
pinnedNote: "Закріплений запис" pinnedNote: "Закріплений запис"
pinned: "Закріпити" pinned: "Закріпити"
@ -108,7 +115,8 @@ sensitive: "NSFW"
add: "Додати" add: "Додати"
reaction: "Реакції" reaction: "Реакції"
reactionSetting: "Налаштування реакцій" reactionSetting: "Налаштування реакцій"
reactionSettingDescription2: "Перемістити щоб змінити порядок, Клацнути мишою щоб видалити, Натиснути \"+\" щоб додати." reactionSettingDescription2: "Перемістити щоб змінити порядок, Клацнути мишою щоб
видалити, Натиснути \"+\" щоб додати."
rememberNoteVisibility: "Пам’ятати параметри видимісті" rememberNoteVisibility: "Пам’ятати параметри видимісті"
attachCancel: "Видалити вкладення" attachCancel: "Видалити вкладення"
markAsSensitive: "Позначити як NSFW" markAsSensitive: "Позначити як NSFW"
@ -137,14 +145,20 @@ emojiUrl: "URL емодзі"
addEmoji: "Додати емодзі" addEmoji: "Додати емодзі"
settingGuide: "Рекомендована конфігурація" settingGuide: "Рекомендована конфігурація"
cacheRemoteFiles: "Кешувати дані з інших інстансів" cacheRemoteFiles: "Кешувати дані з інших інстансів"
cacheRemoteFilesDescription: "Якщо кешування вимкнено, віддалені файли завантажуються безпосередньо з віддаленого інстансу. Це зменшує використання сховища, але збільшує трафік, оскільки не генеруются ескізи." cacheRemoteFilesDescription: "Якщо кешування вимкнено, віддалені файли завантажуються
безпосередньо з віддаленого серверу. Це зменшує використання сховища, але збільшує
трафік, оскільки не генеруются ескізи."
flagAsBot: "Акаунт бота" flagAsBot: "Акаунт бота"
flagAsBotDescription: "Ввімкніть якщо цей обліковий запис використовується ботом. Ця опція позначить обліковий запис як бота. Це потрібно щоб виключити безкінечну інтеракцію між ботами а також відповідного підлаштування Firefish." flagAsBotDescription: "Ввімкніть якщо цей обліковий запис використовується ботом.
Ця опція позначить обліковий запис як бота. Це потрібно щоб виключити безкінечну
інтеракцію між ботами а також відповідного підлаштування Calckey."
flagAsCat: "Акаунт кота" flagAsCat: "Акаунт кота"
flagAsCatDescription: "Ввімкніть, щоб позначити, що обліковий запис є котиком." flagAsCatDescription: "Ввімкніть, щоб позначити, що обліковий запис є котиком."
flagShowTimelineReplies: "Показувати відповіді на нотатки на часовій шкалі" flagShowTimelineReplies: "Показувати відповіді на нотатки на часовій шкалі"
flagShowTimelineRepliesDescription: "Показує відповіді користувачів на нотатки інших користувачів на часовій шкалі." flagShowTimelineRepliesDescription: "Показує відповіді користувачів на нотатки інших
autoAcceptFollowed: "Автоматично приймати запити на підписку від користувачів, на яких ви підписані" користувачів на часовій шкалі."
autoAcceptFollowed: "Автоматично приймати запити на підписку від користувачів, на
яких ви підписані"
addAccount: "Додати акаунт" addAccount: "Додати акаунт"
loginFailed: "Не вдалося увійти" loginFailed: "Не вдалося увійти"
showOnRemote: "Переглянути в оригіналі" showOnRemote: "Переглянути в оригіналі"
@ -156,13 +170,17 @@ searchWith: "Пошук: {q}"
youHaveNoLists: "У вас немає списків" youHaveNoLists: "У вас немає списків"
followConfirm: "Підписатися на {name}?" followConfirm: "Підписатися на {name}?"
proxyAccount: "Проксі-акаунт" proxyAccount: "Проксі-акаунт"
proxyAccountDescription: "Обліковий запис проксі це обліковий запис, який діє як віддалений підписник для користувачів за певних умов. Наприклад, коли користувач додає віддаленого користувача до списку, активність віддаленого користувача не буде доставлена на сервер, якщо жоден локальний користувач не стежить за цим користувачем, то замість нього буде використовуватися обліковий запис проксі-сервера." proxyAccountDescription: "Обліковий запис проксі це обліковий запис, який діє як
віддалений підписник для користувачів за певних умов. Наприклад, коли користувач
додає віддаленого користувача до списку, активність віддаленого користувача не буде
доставлена на сервер, якщо жоден локальний користувач не стежить за цим користувачем,
то замість нього буде використовуватися обліковий запис проксі-сервера."
host: "Хост" host: "Хост"
selectUser: "Виберіть користувача" selectUser: "Виберіть користувача"
recipient: "Отримувач" recipient: "Отримувач"
annotation: "Коментарі" annotation: "Коментарі"
federation: "Федіверс" federation: "Федіверс"
instances: "Інстанс" instances: "Сервери"
registeredAt: "Приєднався(лась)" registeredAt: "Приєднався(лась)"
latestRequestSentAt: "Останній запит надіслано" latestRequestSentAt: "Останній запит надіслано"
latestRequestReceivedAt: "Останній запит прийнято" latestRequestReceivedAt: "Останній запит прийнято"
@ -172,7 +190,7 @@ charts: "Графіки"
perHour: "Щогодинно" perHour: "Щогодинно"
perDay: "Щоденно" perDay: "Щоденно"
stopActivityDelivery: "Припинити розсилання активності" stopActivityDelivery: "Припинити розсилання активності"
blockThisInstance: "Заблокувати цей інстанс" blockThisInstance: "Заблокувати цей сервер"
operations: "Операції" operations: "Операції"
software: "Програмне забезпечення" software: "Програмне забезпечення"
version: "Версія" version: "Версія"
@ -182,15 +200,17 @@ jobQueue: "Черга завдань"
cpuAndMemory: "ЦП та пам'ять" cpuAndMemory: "ЦП та пам'ять"
network: "Мережа" network: "Мережа"
disk: "Диск" disk: "Диск"
instanceInfo: "Про цей інстанс" instanceInfo: "Про цей сервер"
statistics: "Статистика" statistics: "Статистика"
clearQueue: "Очистити чергу" clearQueue: "Очистити чергу"
clearQueueConfirmTitle: "Ви впевнені, що хочете очистити чергу?" clearQueueConfirmTitle: "Ви впевнені, що хочете очистити чергу?"
clearQueueConfirmText: "Будь-які невідправлені нотатки, що залишилися в черзі, не будуть передані. Зазвичай ця операція НЕ потрібна." clearQueueConfirmText: "Будь-які невідправлені записи, що залишилися в черзі, не будуть
передані. Зазвичай ця операція НЕ потрібна."
clearCachedFiles: "Очистити кеш" clearCachedFiles: "Очистити кеш"
clearCachedFilesConfirm: "Ви впевнені, що хочете видалити всі кешовані файли?" clearCachedFilesConfirm: "Ви впевнені, що хочете видалити всі кешовані файли?"
blockedInstances: "Заблоковані інстанси" blockedInstances: "Заблоковані сервери"
blockedInstancesDescription: "Вкажіть інстанси, які потрібно заблокувати. Перелічені інстанси більше не зможуть спілкуватися з цим інстансом." blockedInstancesDescription: "Вкажіть сервери, які потрібно заблокувати. Перелічені
сервери більше не зможуть спілкуватися з цим сервером."
muteAndBlock: "Заглушення і блокування" muteAndBlock: "Заглушення і блокування"
mutedUsers: "Заглушені користувачі" mutedUsers: "Заглушені користувачі"
blockedUsers: "Заблоковані користувачі" blockedUsers: "Заблоковані користувачі"
@ -213,8 +233,8 @@ subscribing: "Підписка"
publishing: "Публікація" publishing: "Публікація"
notResponding: "Не відповідає" notResponding: "Не відповідає"
instanceFollowing: "Підписка на інстанс" instanceFollowing: "Підписка на інстанс"
instanceFollowers: "Підписники інстансу" instanceFollowers: "Підписники серверу"
instanceUsers: "Користувачі цього інстансу" instanceUsers: "Користувачі цього серверу"
changePassword: "Змінити пароль" changePassword: "Змінити пароль"
security: "Безпека" security: "Безпека"
retypedNotMatch: "Введені дані не збігаються." retypedNotMatch: "Введені дані не збігаються."
@ -238,7 +258,8 @@ saved: "Збережено"
messaging: "Чати" messaging: "Чати"
upload: "Завантажити" upload: "Завантажити"
keepOriginalUploading: "Зберегти оригінальне зображення" keepOriginalUploading: "Зберегти оригінальне зображення"
keepOriginalUploadingDescription: "Зберігає початково завантажене зображення як є. Якщо вимкнено, версія для відображення в Інтернеті буде створена під час завантаження." keepOriginalUploadingDescription: "Зберігає початково завантажене зображення як є.
Якщо вимкнено, версія для відображення в Інтернеті буде створена під час завантаження."
fromDrive: "З диска" fromDrive: "З диска"
fromUrl: "З посилання" fromUrl: "З посилання"
uploadFromUrl: "Завантажити з посилання" uploadFromUrl: "Завантажити з посилання"
@ -288,7 +309,7 @@ inputNewFileName: "Введіть ім'я нового файлу"
inputNewDescription: "Введіть новий заголовок" inputNewDescription: "Введіть новий заголовок"
inputNewFolderName: "Введіть ім'я нової теки" inputNewFolderName: "Введіть ім'я нової теки"
circularReferenceFolder: "Ви намагаєтесь перемістити папку в її підпапку." circularReferenceFolder: "Ви намагаєтесь перемістити папку в її підпапку."
hasChildFilesOrFolders: "Ця тека не порожня і не може бути видалена" hasChildFilesOrFolders: "Ця тека не порожня і не може бути видалена."
copyUrl: "Копіювати URL" copyUrl: "Копіювати URL"
rename: "Перейменувати" rename: "Перейменувати"
avatar: "Аватар" avatar: "Аватар"
@ -304,8 +325,8 @@ unwatch: "Не стежити"
accept: "Прийняти" accept: "Прийняти"
reject: "Відхилити" reject: "Відхилити"
normal: "Нормальний" normal: "Нормальний"
instanceName: "Назва інстансу" instanceName: "Назва серверу"
instanceDescription: "Описання інстансу" instanceDescription: "Опис серверу"
maintainerName: "Ім'я адміністратора" maintainerName: "Ім'я адміністратора"
maintainerEmail: "Email адміністратора" maintainerEmail: "Email адміністратора"
tosUrl: "URL умов використання" tosUrl: "URL умов використання"
@ -316,12 +337,13 @@ dayX: "{day}"
monthX: "{month}" monthX: "{month}"
yearX: "{year}" yearX: "{year}"
pages: "Сторінки" pages: "Сторінки"
integration: "Інтеграція" integration: "Інтеграції"
connectService: "Під’єднати" connectService: "Під’єднати"
disconnectService: "Відключитися" disconnectService: "Відключитися"
enableLocalTimeline: "Увімкнути локальну стрічку" enableLocalTimeline: "Увімкнути локальну стрічку"
enableGlobalTimeline: "Увімкнути глобальну стрічку" enableGlobalTimeline: "Увімкнути глобальну стрічку"
disablingTimelinesInfo: "Адміністратори та модератори завжди мають доступ до всіх стрічок, навіть якщо вони вимкнуті." disablingTimelinesInfo: "Адміністратори та модератори завжди мають доступ до всіх
стрічок, навіть якщо вони вимкнуті."
registration: "Реєстрація" registration: "Реєстрація"
enableRegistration: "Дозволити реєстрацію" enableRegistration: "Дозволити реєстрацію"
invite: "Запросити" invite: "Запросити"
@ -333,11 +355,13 @@ bannerUrl: "URL банера"
backgroundImageUrl: "URL-адреса фонового зображення" backgroundImageUrl: "URL-адреса фонового зображення"
basicInfo: "Основна інформація" basicInfo: "Основна інформація"
pinnedUsers: "Закріплені користувачі" pinnedUsers: "Закріплені користувачі"
pinnedUsersDescription: "Впишіть в список користувачів, яких хочете закріпити на сторінці \"Знайти\", ім'я в стовпчик." pinnedUsersDescription: "Впишіть в список користувачів, яких хочете закріпити на сторінці
\"Знайти\", ім'я в стовпчик."
pinnedPages: "Закріплені сторінки" pinnedPages: "Закріплені сторінки"
pinnedPagesDescription: "Введіть шляхи сторінок, які ви бажаєте закріпити на головній сторінці цього інстанса, розділені новими рядками." pinnedPagesDescription: "Введіть шляхи сторінок, які ви бажаєте закріпити на головній
pinnedClipId: "Ідентифікатор закріпленої замітки." сторінці цього інстанса, розділені новими рядками."
pinnedNotes: "Закріплена нотатка" pinnedClipId: "Ідентифікатор закріпленої замітки"
pinnedNotes: "Закріплений запис"
hcaptcha: "hCaptcha" hcaptcha: "hCaptcha"
enableHcaptcha: "Увімкнути hCaptcha" enableHcaptcha: "Увімкнути hCaptcha"
hcaptchaSiteKey: "Ключ сайту" hcaptchaSiteKey: "Ключ сайту"
@ -346,22 +370,25 @@ recaptcha: "reCAPTCHA"
enableRecaptcha: "Увімкнути reCAPTCHA" enableRecaptcha: "Увімкнути reCAPTCHA"
recaptchaSiteKey: "Ключ сайту" recaptchaSiteKey: "Ключ сайту"
recaptchaSecretKey: "Секретний ключ" recaptchaSecretKey: "Секретний ключ"
avoidMultiCaptchaConfirm: "Використання кількох систем Captcha може спричинити перешкоди між ними. Бажаєте вимкнути інші активні системи Captcha? Якщо ви хочете, щоб вони залишалися ввімкненими, натисніть «Скасувати»." avoidMultiCaptchaConfirm: "Використання кількох систем Captcha може спричинити перешкоди
між ними. Бажаєте вимкнути інші активні системи Captcha? Якщо ви хочете, щоб вони
залишалися ввімкненими, натисніть «Скасувати»."
antennas: "Антени" antennas: "Антени"
manageAntennas: "Налаштування антен" manageAntennas: "Налаштування антен"
name: "Ім'я" name: "Ім'я"
antennaSource: "Джерело антени" antennaSource: "Джерело антени"
antennaKeywords: "Ключові слова антени" antennaKeywords: "Ключові слова антени"
antennaExcludeKeywords: "Винятки" antennaExcludeKeywords: "Винятки"
antennaKeywordsDescription: "Розділення ключових слів пробілами для \"І\" або з нової лінійки для \"АБО\"" antennaKeywordsDescription: "Відокремте пробілами для умови \"І\" або перенесенням
notifyAntenna: "Сповіщати про нові нотатки" до нового рядка для умови \"АБО\"."
withFileAntenna: "Тільки нотатки з вкладеними файлами" notifyAntenna: "Сповіщати про нові записи"
withFileAntenna: "Тільки записи з вкладеними файлами"
enableServiceworker: "Ввімкнути ServiceWorker" enableServiceworker: "Ввімкнути ServiceWorker"
antennaUsersDescription: "Список імя користувачів в стопчик" antennaUsersDescription: "Список імя користувачів в стопчик"
caseSensitive: "З урахуванням регістру" caseSensitive: "З урахуванням регістру"
withReplies: "Включаючи відповіді" withReplies: "Включаючи відповіді"
connectedTo: "Наступні акаунти під'єднані" connectedTo: "Наступні акаунти під'єднані"
notesAndReplies: "Нотатки та відповіді" notesAndReplies: "Записи та відповіді"
withFiles: "Файли" withFiles: "Файли"
silence: "Заглушити" silence: "Заглушити"
silenceConfirm: "Ви впевнені, що хочете заглушити цього користувача?" silenceConfirm: "Ви впевнені, що хочете заглушити цього користувача?"
@ -397,7 +424,7 @@ notFoundDescription: "Сторінка за вказаною адресою не
uploadFolder: "Місце для завантаження за замовчуванням" uploadFolder: "Місце для завантаження за замовчуванням"
cacheClear: "Очистити кеш" cacheClear: "Очистити кеш"
markAsReadAllNotifications: "Позначити всі сповіщення як прочитані" markAsReadAllNotifications: "Позначити всі сповіщення як прочитані"
markAsReadAllUnreadNotes: "Позначити всі нотатки як прочитані" markAsReadAllUnreadNotes: "Позначити всі записи як прочитані"
markAsReadAllTalkMessages: "Позначити всі повідомлення як прочитані" markAsReadAllTalkMessages: "Позначити всі повідомлення як прочитані"
help: "Допомога" help: "Допомога"
inputMessageHere: "Введіть повідомлення тут" inputMessageHere: "Введіть повідомлення тут"
@ -418,7 +445,7 @@ text: "Текст"
enable: "Увімкнути" enable: "Увімкнути"
next: "Далі" next: "Далі"
retype: "Введіть ще раз" retype: "Введіть ще раз"
noteOf: "Нотатка {user}" noteOf: "Запис {user}"
inviteToGroup: "Запрошення до групи" inviteToGroup: "Запрошення до групи"
quoteAttached: "Цитата" quoteAttached: "Цитата"
quoteQuestion: "Ви хочете додати цитату?" quoteQuestion: "Ви хочете додати цитату?"
@ -431,7 +458,8 @@ invitationCode: "Код запрошення"
checking: "Перевірка…" checking: "Перевірка…"
available: "Доступно" available: "Доступно"
unavailable: "Недоступно" unavailable: "Недоступно"
usernameInvalidFormat: "літери, цифри та _ є прийнятними" usernameInvalidFormat: "Ви можете використовувати великі та малі літери, цифри та
підкреслення."
tooShort: "Занадто короткий" tooShort: "Занадто короткий"
tooLong: "Занадто довгий" tooLong: "Занадто довгий"
weakPassword: "Слабкий пароль" weakPassword: "Слабкий пароль"
@ -454,7 +482,7 @@ joinOrCreateGroup: "Отримуйте запрошення до груп або
noHistory: "Історія порожня" noHistory: "Історія порожня"
signinHistory: "Історія входів" signinHistory: "Історія входів"
disableAnimatedMfm: "Відключити анімації MFM" disableAnimatedMfm: "Відключити анімації MFM"
doing: "Виконується" doing: "Виконується..."
category: "Категорія" category: "Категорія"
tags: "Теги" tags: "Теги"
docSource: "Джерело цього документа" docSource: "Джерело цього документа"
@ -476,29 +504,34 @@ accountSettings: "Налаштування акаунта"
promotion: "Виділене" promotion: "Виділене"
promote: "Виділити" promote: "Виділити"
numberOfDays: "Кількість днів" numberOfDays: "Кількість днів"
hideThisNote: "Сховати цю нотатку" hideThisNote: "Сховати цей запис"
showFeaturedNotesInTimeline: "Показувати популярні нотатки у стрічці" showFeaturedNotesInTimeline: "Показувати популярні записи у стрічці"
objectStorage: "Object Storage" objectStorage: "Object Storage"
useObjectStorage: "Використовувати object storage" useObjectStorage: "Використовувати object storage"
objectStorageBaseUrl: "Base URL" objectStorageBaseUrl: "Base URL"
objectStorageBaseUrlDesc: "Це початкова частина адреси, що використовується CDN або проксі, наприклад для S3: https://<bucket>.s3.amazonaws.com, або GCS: 'https://storage.googleapis.com/<bucket>'" objectStorageBaseUrlDesc: "Це початкова частина адреси, що використовується CDN або
проксі, наприклад для S3: https://<bucket>.s3.amazonaws.com, або GCS: 'https://storage.googleapis.com/<bucket>'"
objectStorageBucket: "Bucket" objectStorageBucket: "Bucket"
objectStorageBucketDesc: "Будь ласка вкажіть назву відра в налаштованому сервісі." objectStorageBucketDesc: "Будь ласка вкажіть назву відра в налаштованому сервісі."
objectStoragePrefix: "Prefix" objectStoragePrefix: "Prefix"
objectStoragePrefixDesc: "Файли будуть зберігатись у розташуванні з цим префіксом." objectStoragePrefixDesc: "Файли будуть зберігатись у розташуванні з цим префіксом."
objectStorageEndpoint: "Endpoint" objectStorageEndpoint: "Кінцевий пункт"
objectStorageEndpointDesc: "Залиште пустим при використанні AWS S3. Інакше введіть кінцевий пункт як '<host>' або '<host>:<port>' слідуючи інструкціям сервісу, який використовується." objectStorageEndpointDesc: "Залиште пустим при використанні AWS S3. Інакше введіть
кінцевий пункт як '<host>' або '<host>:<port>' слідуючи інструкціям сервісу, який
використовується."
objectStorageRegion: "Region" objectStorageRegion: "Region"
objectStorageRegionDesc: "Введіть регіон у формі 'xx-east-1'. Залиште пустим, якщо ваш сервіс не різниться відповідно до регіонів, або введіть 'us-east-1'." objectStorageRegionDesc: "Введіть регіон у формі 'xx-east-1'. Залиште пустим, якщо
ваш сервіс не різниться відповідно до регіонів, або введіть 'us-east-1'."
objectStorageUseSSL: "Використовувати SSL" objectStorageUseSSL: "Використовувати SSL"
objectStorageUseSSLDesc: "Вимкніть коли не використовується HTTPS для з'єднання API" objectStorageUseSSLDesc: "Вимкніть коли не використовується HTTPS для з'єднання API"
objectStorageUseProxy: "Використовувати Proxy" objectStorageUseProxy: "Використовувати Proxy"
objectStorageUseProxyDesc: "Вимкніть коли проксі не використовується для з'єднання ObjectStorage" objectStorageUseProxyDesc: "Вимкніть коли проксі не використовується для з'єднання
ObjectStorage"
objectStorageSetPublicRead: "Встановіть 'публічне читання' при завантаженні" objectStorageSetPublicRead: "Встановіть 'публічне читання' при завантаженні"
serverLogs: "Журнал сервера" serverLogs: "Журнал сервера"
deleteAll: "Видалити все" deleteAll: "Видалити все"
showFixedPostForm: "Показати форму запису над стрічкою новин." showFixedPostForm: "Показати форму запису над стрічкою новин"
newNoteRecived: "Є нові нотатки" newNoteRecived: "Є нові записи"
sounds: "Звуки" sounds: "Звуки"
listen: "Слухати" listen: "Слухати"
none: "Відсутній" none: "Відсутній"
@ -521,7 +554,8 @@ sort: "Сортування"
ascendingOrder: "За зростанням" ascendingOrder: "За зростанням"
descendingOrder: "За спаданням" descendingOrder: "За спаданням"
scratchpad: "Чернетка" scratchpad: "Чернетка"
scratchpadDescription: "Scratchpad надає середовище для експериментів з AiScript. Ви можете писати, виконувати його і тестувати взаємодію з Firefish." scratchpadDescription: "Scratchpad надає середовище для експериментів з AiScript.
Ви можете писати, виконувати його і тестувати взаємодію з Calckey."
output: "Вихід" output: "Вихід"
script: "Скрипт" script: "Скрипт"
disablePagesScript: "Вимкнути AiScript на Сторінках" disablePagesScript: "Вимкнути AiScript на Сторінках"
@ -529,11 +563,14 @@ updateRemoteUser: "Оновити інформацію про віддалено
deleteAllFiles: "Видалити всі файли" deleteAllFiles: "Видалити всі файли"
deleteAllFilesConfirm: "Ви дійсно хочете видалити всі файли?" deleteAllFilesConfirm: "Ви дійсно хочете видалити всі файли?"
removeAllFollowing: "Скасувати всі підписки" removeAllFollowing: "Скасувати всі підписки"
removeAllFollowingDescription: "Скасувати підписку на всі акаунти з {host}. Будь ласка, робіть це, якщо інстанс більше не існує." removeAllFollowingDescription: "Скасувати підписку на всі акаунти з {host}. Будь ласка,
робіть це, якщо сервер більше не існує."
userSuspended: "Обліковий запис заблокований." userSuspended: "Обліковий запис заблокований."
userSilenced: "Обліковий запис приглушений." userSilenced: "Обліковий запис приглушений."
yourAccountSuspendedTitle: "Цей обліковий запис заблоковано" yourAccountSuspendedTitle: "Цей обліковий запис заблоковано"
yourAccountSuspendedDescription: "Цей обліковий запис було заблоковано через порушення умов надання послуг сервера. Зв'яжіться з адміністратором, якщо ви хочете дізнатися докладнішу причину. Будь ласка, не створюйте новий обліковий запис." yourAccountSuspendedDescription: "Цей обліковий запис було заблоковано через порушення
умов надання послуг сервера. Зв'яжіться з адміністратором, якщо ви хочете дізнатися
докладнішу причину. Будь ласка, не створюйте новий обліковий запис."
menu: "Меню" menu: "Меню"
divider: "Розділювач" divider: "Розділювач"
addItem: "Додати елемент" addItem: "Додати елемент"
@ -542,8 +579,8 @@ addRelay: "Додати ретранслятор"
inboxUrl: "Inbox URL" inboxUrl: "Inbox URL"
addedRelays: "Додані ретранслятори" addedRelays: "Додані ретранслятори"
serviceworkerInfo: "Повинен бути ввімкнений для push-сповіщень." serviceworkerInfo: "Повинен бути ввімкнений для push-сповіщень."
deletedNote: "Видалена нотатка" deletedNote: "Видалений запис"
invisibleNote: "Приховані записи" invisibleNote: "Прихований запис"
enableInfiniteScroll: "Увімкнути нескінченну прокрутку" enableInfiniteScroll: "Увімкнути нескінченну прокрутку"
visibility: "Видимість" visibility: "Видимість"
poll: "Опитування" poll: "Опитування"
@ -573,12 +610,14 @@ permission: "Права"
enableAll: "Увімкнути все" enableAll: "Увімкнути все"
disableAll: "Вимкнути все" disableAll: "Вимкнути все"
tokenRequested: "Надати доступ до акаунту" tokenRequested: "Надати доступ до акаунту"
pluginTokenRequestedDescription: "Цей плагін зможе використовувати дозволи які тут вказані." pluginTokenRequestedDescription: "Цей плагін зможе використовувати дозволи які тут
вказані."
notificationType: "Тип сповіщення" notificationType: "Тип сповіщення"
edit: "Редагувати" edit: "Редагувати"
emailServer: "Сервер електронної пошти" emailServer: "Сервер електронної пошти"
enableEmail: "Увімкнути функцію доставки пошти" enableEmail: "Увімкнути функцію доставки пошти"
emailConfigInfo: "Використовується для підтвердження електронної пошти підчас реєстрації, а також для відновлення паролю." emailConfigInfo: "Використовується для підтвердження електронної пошти під час реєстрації,
а також для відновлення паролю"
email: "E-mail" email: "E-mail"
emailAddress: "E-mail адреса" emailAddress: "E-mail адреса"
smtpConfig: "Налаштування сервера SMTP" smtpConfig: "Налаштування сервера SMTP"
@ -586,14 +625,16 @@ smtpHost: "Хост"
smtpPort: "Порт" smtpPort: "Порт"
smtpUser: "Ім'я користувача" smtpUser: "Ім'я користувача"
smtpPass: "Пароль" smtpPass: "Пароль"
emptyToDisableSmtpAuth: "Залиште назву користувача і пароль пустими для вимкнення підтвердження SMTP" emptyToDisableSmtpAuth: "Залиште назву користувача і пароль пустими для вимкнення
підтвердження SMTP"
smtpSecure: "Використовувати безумовне шифрування SSL/TLS для з'єднань SMTP" smtpSecure: "Використовувати безумовне шифрування SSL/TLS для з'єднань SMTP"
smtpSecureInfo: "Вимкніть при використанні STARTTLS " smtpSecureInfo: "Вимкніть при використанні STARTTLS"
testEmail: "Тестовий email" testEmail: "Тестовий email"
wordMute: "Блокування слів" wordMute: "Блокування слів"
regexpError: "Помилка регулярного виразу" regexpError: "Помилка регулярного виразу"
regexpErrorDescription: "Сталася помилка в регулярному виразі в рядку {line} вашого слова {tab} слова що ігноруються:" regexpErrorDescription: "Сталася помилка в регулярному виразі в рядку {line} вашого
instanceMute: "Приглушення інстансів" слова {tab} слова що ігноруються:"
instanceMute: "Приглушення серверів"
userSaysSomething: "{name} щось сказав(ла)" userSaysSomething: "{name} щось сказав(ла)"
makeActive: "Активувати" makeActive: "Активувати"
display: "Відображення" display: "Відображення"
@ -606,12 +647,15 @@ database: "База даних"
channel: "Канали" channel: "Канали"
create: "Створити" create: "Створити"
notificationSetting: "Параметри сповіщень" notificationSetting: "Параметри сповіщень"
notificationSettingDesc: "Виберіть типи сповіщень для відображення" notificationSettingDesc: "Оберіть типи сповіщень для відображення."
useGlobalSetting: "Застосувати глобальнi параметри" useGlobalSetting: "Застосувати глобальнi параметри"
useGlobalSettingDesc: "Якщо увімкнено, то будуть використовуватись налаштування повідомлень облікового запису, інакше можливо налаштувати індивідуально." useGlobalSettingDesc: "Якщо увімкнено, то будуть використовуватись налаштування повідомлень
облікового запису, інакше можливо налаштувати індивідуально."
other: "Інше" other: "Інше"
regenerateLoginToken: "Оновити Login Token" regenerateLoginToken: "Оновити Login Token"
regenerateLoginTokenDescription: "Регенерувати внутрішній ключ використовуваний під час входу. Зазвичай цього не потрібно робити. При регенерації всі пристрої вийдуть з системи." regenerateLoginTokenDescription: "Регенерувати внутрішній ключ використовуваний під
час входу. Зазвичай цього не потрібно робити. При регенерації всі пристрої вийдуть
з системи."
setMultipleBySeparatingWithSpace: "Можна вказати кілька значень, відділивши їх пробілом." setMultipleBySeparatingWithSpace: "Можна вказати кілька значень, відділивши їх пробілом."
fileIdOrUrl: "Ідентифікатор файлу або посилання" fileIdOrUrl: "Ідентифікатор файлу або посилання"
behavior: "Поведінка" behavior: "Поведінка"
@ -619,19 +663,22 @@ sample: "Приклад"
abuseReports: "Скарги" abuseReports: "Скарги"
reportAbuse: "Поскаржитись" reportAbuse: "Поскаржитись"
reportAbuseOf: "Поскаржитись на {name}" reportAbuseOf: "Поскаржитись на {name}"
fillAbuseReportDescription: "Будь ласка вкажіть подробиці скарги. Якщо скарга стосується запису, вкажіть посилання на нього." fillAbuseReportDescription: "Будь ласка вкажіть подробиці скарги. Якщо скарга стосується
запису, вкажіть посилання на нього."
abuseReported: "Дякуємо, вашу скаргу було відправлено. " abuseReported: "Дякуємо, вашу скаргу було відправлено. "
reporter: "Репортер" reporter: "Репортер"
reporteeOrigin: "Про кого повідомлено" reporteeOrigin: "Про кого повідомлено"
reporterOrigin: "Хто повідомив" reporterOrigin: "Хто повідомив"
forwardReport: "Переслати звіт на віддалений інстанс" forwardReport: "Переслати звіт на віддалений інстанс"
forwardReportIsAnonymous: "Замість вашого облікового запису анонімний системний обліковий запис буде відображатися як доповідач на віддаленому інстансі" forwardReportIsAnonymous: "Замість вашого облікового запису, анонімний системний обліковий
запис буде відображатися як доповідач на віддаленому сервері."
send: "Відправити" send: "Відправити"
abuseMarkAsResolved: "Позначити скаргу як вирішену" abuseMarkAsResolved: "Позначити скаргу як вирішену"
openInNewTab: "Відкрити в новій вкладці" openInNewTab: "Відкрити в новій вкладці"
openInSideView: "Відкрити збоку" openInSideView: "Відкрити збоку"
defaultNavigationBehaviour: "Поведінка навігації за замовчуванням" defaultNavigationBehaviour: "Поведінка навігації за замовчуванням"
editTheseSettingsMayBreakAccount: "Зміна цих параметрів може призвести до пошкодження вашого акаунта." editTheseSettingsMayBreakAccount: "Зміна цих параметрів може призвести до пошкодження
вашого акаунта."
instanceTicker: "Мітка з назвою інстанса в нотатках" instanceTicker: "Мітка з назвою інстанса в нотатках"
waitingFor: "Чекаємо на {x}" waitingFor: "Чекаємо на {x}"
random: "Випадковий" random: "Випадковий"
@ -643,10 +690,11 @@ createNew: "Створити новий"
optional: "Необов'язково" optional: "Необов'язково"
createNewClip: "Створити нотатку" createNewClip: "Створити нотатку"
public: "Публічний" public: "Публічний"
i18nInfo: "Firefish перекладається на різні мови волонтерами. Ви можете допомогти: {link}" i18nInfo: "Calckey перекладається на різні мови волонтерами. Ви можете допомогти за
посиланням: {link}."
manageAccessTokens: "Керування токенами доступу" manageAccessTokens: "Керування токенами доступу"
accountInfo: "Інформація про акаунт" accountInfo: "Інформація про акаунт"
notesCount: "Кількість нотаток" notesCount: "Кількість записів"
repliesCount: "Кількість надісланих відповідей" repliesCount: "Кількість надісланих відповідей"
renotesCount: "Кількість поширень" renotesCount: "Кількість поширень"
repliedCount: "Кількість отриманих відповідей" repliedCount: "Кількість отриманих відповідей"
@ -662,15 +710,19 @@ no: "Ні"
driveFilesCount: "Кількість файлів на диску" driveFilesCount: "Кількість файлів на диску"
driveUsage: "Використання місця на диску" driveUsage: "Використання місця на диску"
noCrawle: "Заборонити індексацію" noCrawle: "Заборонити індексацію"
noCrawleDescription: "Просити пошукові системи не індексувати ваш профіль, нотатки, сторінки тощо." noCrawleDescription: "Просити пошукові системи не індексувати ваш профіль, записи,
lockedAccountInfo: "Якщо видимість вашого запису не встановлена як \"Тільки підписники\", то кожен зможе побачити ваш запис, навіть якщо ви вимагаєте підтвердження підписок вручну." сторінки тощо."
alwaysMarkSensitive: "Позначати NSFW за замовчуванням" lockedAccountInfo: "Якщо видимість вашого запису не встановлена як \"Тільки підписники\"\
, то кожен зможе побачити ваш запис, навіть якщо ви вимагаєте підтвердження підписок
вручну."
alwaysMarkSensitive: "Позначати як NSFW за замовчуванням"
loadRawImages: "Відображати вкладені зображення повністю замість ескізів" loadRawImages: "Відображати вкладені зображення повністю замість ескізів"
disableShowingAnimatedImages: "Не програвати анімовані зображення" disableShowingAnimatedImages: "Не програвати анімовані зображення"
verificationEmailSent: "Електронний лист з підтвердженням відісланий. Будь ласка перейдіть по посиланню в листі для підтвердження." verificationEmailSent: "Електронний лист з підтвердженням відісланий. Будь ласка перейдіть
по посиланню в листі для підтвердження."
notSet: "Не налаштовано" notSet: "Не налаштовано"
emailVerified: "Електронну пошту підтверджено." emailVerified: "Електронну пошту підтверджено"
noteFavoritesCount: "Кількість улюблених нотаток" noteFavoritesCount: "Кількість улюблених записів"
pageLikesCount: "Кількість отриманих вподобань сторінки" pageLikesCount: "Кількість отриманих вподобань сторінки"
pageLikedCount: "Кількість вподобаних сторінок" pageLikedCount: "Кількість вподобаних сторінок"
contact: "Контакт" contact: "Контакт"
@ -679,7 +731,8 @@ clips: "Добірка"
experimentalFeatures: "Експериментальні функції" experimentalFeatures: "Експериментальні функції"
developer: "Розробник" developer: "Розробник"
makeExplorable: "Зробіть обліковий запис видимим у розділі \"Огляд\"" makeExplorable: "Зробіть обліковий запис видимим у розділі \"Огляд\""
makeExplorableDescription: "Вимкніть, щоб обліковий запис не показувався у розділі \"Огляд\"." makeExplorableDescription: "Вимкніть, щоб обліковий запис не показувався у розділі
\"Огляд\"."
showGapBetweenNotesInTimeline: "Показувати розрив між записами у стрічці новин" showGapBetweenNotesInTimeline: "Показувати розрив між записами у стрічці новин"
duplicate: "Дублікат" duplicate: "Дублікат"
left: "Лівий" left: "Лівий"
@ -694,7 +747,10 @@ onlineUsersCount: "{n} користувачів онлайн"
nUsers: "{n} Користувачів" nUsers: "{n} Користувачів"
nNotes: "{n} Записів" nNotes: "{n} Записів"
sendErrorReports: "Надіслати звіт про помилки" sendErrorReports: "Надіслати звіт про помилки"
sendErrorReportsDescription: "При увімкненні детальна інформація про помилки буде надана Firefish у разі виникнення проблем, що дасть можливість покращити Firefish." sendErrorReportsDescription: "Якщо увімкнено, детальна інформація про помилки буде
передаватися до Calckey, коли виникає проблема, це допоможе покращити якість роботи
Calckey.\nЦе буде включати інформацію таку як: версія вашої ОС, який браузер ви
використовуєте, ваша активність в Calckey тощо."
myTheme: "Моя тема" myTheme: "Моя тема"
backgroundColor: "Фон" backgroundColor: "Фон"
accentColor: "Акцент" accentColor: "Акцент"
@ -718,7 +774,7 @@ capacity: "Ємність"
inUse: "Зайнято" inUse: "Зайнято"
editCode: "Редагувати вихідний текст" editCode: "Редагувати вихідний текст"
apply: "Застосувати" apply: "Застосувати"
receiveAnnouncementFromInstance: "Отримувати оповіщення з інстансу" receiveAnnouncementFromInstance: "Отримувати сповіщення з серверу"
emailNotification: "Сповіщення електронною поштою" emailNotification: "Сповіщення електронною поштою"
publish: "Опублікувати" publish: "Опублікувати"
inChannelSearch: "Пошук за каналом" inChannelSearch: "Пошук за каналом"
@ -726,12 +782,12 @@ useReactionPickerForContextMenu: "Відкривати палітру реакц
typingUsers: "Стук клавіш. Це {users}…" typingUsers: "Стук клавіш. Це {users}…"
goBack: "Назад" goBack: "Назад"
info: "Інформація" info: "Інформація"
user: "Користувачі" user: "Користувач"
administration: "Управління" administration: "Управління"
expiration: "Опитування закінчується" expiration: "Опитування закінчується"
middle: "Середній" middle: "Середній"
global: "Глобальна" global: "Глобальна"
sent: "Відправити" sent: "Відправлене"
hashtags: "Хештеґ" hashtags: "Хештеґ"
hide: "Сховати" hide: "Сховати"
searchByGoogle: "Пошук" searchByGoogle: "Пошук"
@ -756,13 +812,15 @@ _registry:
domain: "Домен" domain: "Домен"
createKey: "Створити ключ" createKey: "Створити ключ"
_aboutMisskey: _aboutMisskey:
about: "Misskey - це програмне забезпечення з відкритим кодом, яке розробляє syuilo з 2014 року." about: "Misskey - це програмне забезпечення з відкритим кодом, яке розробляє syuilo
з 2014 року."
contributors: "Головні помічники" contributors: "Головні помічники"
allContributors: "Всі помічники" allContributors: "Всі помічники"
source: "Вихідний код" source: "Вихідний код"
translation: "Перекладати Firefish" translation: "Перекладати Calckey"
donate: "Пожертвувати Firefish" donate: "Пожертвувати Calckey"
morePatrons: "Ми дуже цінуємо підтримку багатьох інших помічників, не перелічених тут. Дякуємо! 🥰" morePatrons: "Ми дуже цінуємо підтримку багатьох інших помічників, не перелічених
тут. Дякуємо! 🥰"
patrons: "Підтримали" patrons: "Підтримали"
_nsfw: _nsfw:
respect: "Приховувати NSFW медіа" respect: "Приховувати NSFW медіа"
@ -770,10 +828,12 @@ _nsfw:
force: "Приховувати всі медіа файли" force: "Приховувати всі медіа файли"
_mfm: _mfm:
cheatSheet: " Довідка MFM" cheatSheet: " Довідка MFM"
intro: "MFM це ексклюзивна мова розмітки тексту в Firefish, яку можна використовувати в багатьох місцях. Тут ви можете переглянути приклади її синтаксису." intro: "MFM це ексклюзивна мова розмітки тексту в Calckey, яку можна використовувати
dummy: "Firefish розширює світ Федіверсу" в багатьох місцях. Тут ви можете переглянути приклади її синтаксису."
dummy: "Calckey розширює світ Федіверсу"
mention: "Згадка" mention: "Згадка"
mentionDescription: "За допомогою знака \"@\" перед ім'ям можна згадати конкретного користувача." mentionDescription: "За допомогою знака \"@\" перед ім'ям можна згадати конкретного
користувача."
hashtag: "Хештеґ" hashtag: "Хештеґ"
hashtagDescription: "За допомогою знака \"решітка\" перед словом задається хештег." hashtagDescription: "За допомогою знака \"решітка\" перед словом задається хештег."
url: "URL" url: "URL"
@ -819,7 +879,8 @@ _mfm:
x4: "Надзвичайно великий" x4: "Надзвичайно великий"
x4Description: "Показує контент надзвичайно великим." x4Description: "Показує контент надзвичайно великим."
blur: "Розмиття" blur: "Розмиття"
blurDescription: "Цей ефект зробить контент розмитим. Контент можна зробити чітким, якщо навести на нього вказівник миші." blurDescription: "Цей ефект зробить контент розмитим. Контент можна зробити чітким,
якщо навести на нього вказівник миші."
font: "Шрифт" font: "Шрифт"
fontDescription: "Встановлює шрифт для контенту." fontDescription: "Встановлює шрифт для контенту."
rotate: "Обертати" rotate: "Обертати"
@ -844,10 +905,14 @@ _menuDisplay:
hide: "Сховати" hide: "Сховати"
_wordMute: _wordMute:
muteWords: "Заглушені слова" muteWords: "Заглушені слова"
muteWordsDescription: "Розділення ключових слів пробілами для \"І\" або з нової лінійки для \"АБО\"" muteWordsDescription: "Розділення ключових слів пробілами для \"І\" або з нової
muteWordsDescription2: "Для використання RegEx, ключові слова потрібно вписати поміж слешів \"/\"." лінійки для \"АБО\""
muteWordsDescription2: "Для використання RegEx, ключові слова потрібно вписати поміж
слешів \"/\"."
softDescription: "Приховати записи які відповідають критеріям зі стрічки подій." softDescription: "Приховати записи які відповідають критеріям зі стрічки подій."
hardDescription: "Приховати записи які відповідають критеріям зі стрічки подій. Також приховані записи не будуть додані до стрічки подій навіть якщо критерії буде змінено." hardDescription: "Приховати записи які відповідають критеріям зі стрічки подій.
Також приховані записи не будуть додані до стрічки подій навіть якщо критерії
буде змінено."
soft: "М'яко" soft: "М'яко"
hard: "Жорстко" hard: "Жорстко"
mutedNotes: "Заблоковані нотатки" mutedNotes: "Заблоковані нотатки"
@ -942,21 +1007,32 @@ _tutorial:
step1_1: "Ласкаво просимо!" step1_1: "Ласкаво просимо!"
step1_2: "Давайте налаштуємо вас. Ви будете працювати в найкоротші терміни!" step1_2: "Давайте налаштуємо вас. Ви будете працювати в найкоротші терміни!"
step2_1: "Спочатку, будь ласка, заповніть свій профіль" step2_1: "Спочатку, будь ласка, заповніть свій профіль"
step2_2: "Надавши деяку інформацію про себе, іншим людям буде легше зрозуміти, чи хочуть вони бачити ваші записи або стежити за вами." step2_2: "Надавши деяку інформацію про себе, іншим людям буде легше зрозуміти, чи
хочуть вони бачити ваші записи або стежити за вами."
step3_1: "Тепер настав час стежити за деякими людьми!" step3_1: "Тепер настав час стежити за деякими людьми!"
step3_2: "Ваша домашня і соціальна стрічки ґрунтуються на тому, за ким ви стежите, тому для початку спробуйте стежити за кількома акаунтами.\nНатисніть на гурток із плюсом у правому верхньому кутку профілю, щоб стежити за ним." step3_2: "Ваша домашня і соціальна стрічки ґрунтуються на тому, за ким ви стежите,
тому для початку спробуйте стежити за кількома акаунтами.\nНатисніть на гурток
із плюсом у правому верхньому кутку профілю, щоб стежити за ним."
step4_1: "Давайте вийдемо на вас" step4_1: "Давайте вийдемо на вас"
step4_2: "Для свого першого повідомлення деякі люди люблять робити {introduction} повідомлення або просте \"Hello world!\"" step4_2: "Для свого першого повідомлення деякі люди люблять робити {introduction}
повідомлення або просте \"Hello world!\""
step5_1: "Тимчасові рамки, скрізь тимчасові рамки!" step5_1: "Тимчасові рамки, скрізь тимчасові рамки!"
step5_2: "У вашому екземплярі включені {timelines} різних часових ліній." step5_2: "У вашому екземплярі включені {timelines} різних часових ліній."
step5_3: "Головна {icon} часова шкала - це шкала, де ви можете бачити повідомлення ваших підписників." step5_3: "Головна {icon} часова шкала - це шкала, де ви можете бачити повідомлення
step5_4: "Місцева {icon} тимчасова шкала - це шкала, де ви можете бачити повідомлення всіх інших користувачів даного екземпляра" ваших підписників."
step5_5: "Тимчасова шкала Рекомендовані {icon} - це шкала, де ви можете бачити повідомлення від інстанцій, рекомендованих адміністраторами." step5_4: "Місцева {icon} тимчасова шкала - це шкала, де ви можете бачити повідомлення
step5_6: "На часовій шкалі Social {icon} відображаються повідомлення від друзів ваших підписників" всіх інших користувачів даного екземпляра"
step5_7: "Глобальна {icon} часова шкала - це місце, де ви можете бачити повідомлення від усіх інших підключених екземплярів" step5_5: "Тимчасова шкала Рекомендовані {icon} - це шкала, де ви можете бачити повідомлення
від інстанцій, рекомендованих адміністраторами."
step5_6: "На часовій шкалі Social {icon} відображаються повідомлення від друзів
ваших підписників"
step5_7: "Глобальна {icon} часова шкала - це місце, де ви можете бачити повідомлення
від усіх інших підключених екземплярів"
step6_1: "Отже, що це за місце?" step6_1: "Отже, що це за місце?"
step6_2: "Ну, ви не просто приєдналися до Кальки. Ви приєдналися до порталу в Fediverse, взаємопов'язаної мережі з тисяч серверів, званих \"інстансами\"." step6_2: "Ну, ви не просто приєдналися до Кальки. Ви приєдналися до порталу в Fediverse,
step6_3: "Кожен сервер працює по-своєму, і не на всіх серверах працює Firefish. Але цей працює! Це трохи складно, але ви швидко розберетеся" взаємопов'язаної мережі з тисяч серверів, званих \"інстансами\"."
step6_3: "Кожен сервер працює по-своєму, і не на всіх серверах працює Calckey. Але
цей працює! Це трохи складно, але ви швидко розберетеся"
step6_4: "Тепер ідіть, вивчайте і розважайтеся!" step6_4: "Тепер ідіть, вивчайте і розважайтеся!"
_2fa: _2fa:
registerSecurityKey: "Зареєструвати новий ключ безпеки" registerSecurityKey: "Зареєструвати новий ключ безпеки"
@ -1078,7 +1154,8 @@ _profile:
youCanIncludeHashtags: "Ви також можете включити хештеги у свій опис." youCanIncludeHashtags: "Ви також можете включити хештеги у свій опис."
metadata: "Додаткова інформація" metadata: "Додаткова інформація"
metadataEdit: "Редагувати додаткову інформацію" metadataEdit: "Редагувати додаткову інформацію"
metadataDescription: "Ви можете вказати до чотирьох пунктів додаткової інформації у своєму профілі." metadataDescription: "Ви можете вказати до чотирьох пунктів додаткової інформації
у своєму профілі."
metadataLabel: "Назва" metadataLabel: "Назва"
metadataContent: "Вміст" metadataContent: "Вміст"
changeAvatar: "Змінити аватар" changeAvatar: "Змінити аватар"
@ -1388,7 +1465,8 @@ _pages:
_for: _for:
arg1: "Кількість повторень" arg1: "Кількість повторень"
arg2: "Дія" arg2: "Дія"
typeError: "Паз {slot} приймає \"{expect}\" тип, але надана змінна має тип \"{actual}\"!" typeError: "Паз {slot} приймає \"{expect}\" тип, але надана змінна має тип \"\
{actual}\"!"
thereIsEmptySlot: "Паз {slot} пустий!" thereIsEmptySlot: "Паз {slot} пустий!"
types: types:
string: "Текст" string: "Текст"
@ -1453,3 +1531,95 @@ _deck:
list: "Списки" list: "Списки"
mentions: "Згадки" mentions: "Згадки"
direct: "Особисте" direct: "Особисте"
removeReaction: Видалити вашу реакцію
renoteMute: Ігнорувати поширення
renoteUnmute: Показувати поширення
flagSpeakAsCat: Говорити як кішка
accessibility: Доступність
priority: Пріорітет
high: Високий
customCss: Користувацькі CSS
itsOn: Увімкнено
showingPastTimeline: Наразі відображається стара стрічка
enabled: Увімкнено
noMaintainerInformationWarning: Інформація про супровідника не налаштована.
recommended: Рекомендоване
resolved: Вирішено
itsOff: Вимкнено
emailRequiredForSignup: Вимагати адресу електронної пошти для реєстрації
moderation: Модерація
selectInstance: Оберіть сервер
instanceSecurity: Безпека сервера
searchPlaceholder: Шукати в Calckey
editNote: Відредагувати запис
enableEmojiReactions: Ввімкнути реакції емодзі
low: Низький
emailNotConfiguredWarning: Адрес електронної пошти не встановлено.
unresolved: Не вирішено
offline: Не в мережі
disabled: Вимкнено
configure: Налаштувати
popularPosts: Популярні сторінки
silenced: Ігнорується
manageGroups: Керування групами
active: Активний
whatIsNew: Показати зміни
deleted: Видалено
selectChannel: Виберіть канал
flagSpeakAsCatDescription: Ваші записи будуть няніфіковані у режимі кота
userSaysSomethingReason: '{name} сказав(ла) {reason}'
clear: Очистити
userInfo: Інформація про користувача
selectAccount: Оберіть обліковий запис
switchAccount: Змінити обліковий запис
accounts: Облікові записи
switch: Змінити
noBotProtectionWarning: Захист від ботів не налаштовано.
gallery: Галерея
recentPosts: Недавні сторінки
privateModeInfo: Якщо увімкнено, лише сервери з білого списку можуть федеруватися
з вашим сервером. Всі повідомлення будуть приховані від публіки.
troubleshooting: Вирішення проблем
customCssWarn: Цей параметр слід використовувати лише тоді, коли ви знаєте, що він
робить. Введення неправильних значень може призвести до того, що клієнт перестане
нормально функціонувати.
newer: новіші
older: старіші
addDescription: Додати опис
notSpecifiedMentionWarning: У цьому записі згадуються користувачі, яких не було включено
до списку одержувачів
markAllAsRead: Позначити все як прочитане
userPagePinTip: Ви можете відображати записи тут, вибравши "Прикріпити до профілю"
в меню окремих записів.
unknown: Невідомо
onlineStatus: Онлайн-статус
hideOnlineStatus: Приховати онлайн-статус
online: В мережі
breakFollow: Видалити підписника
translate: Перекласти
translatedFrom: Перекладено з {x}
userSaysSomethingReasonQuote: '{name} цитував запис з {reason}'
userSaysSomethingReasonRenote: '{name} поширив запис з {reason}'
notRecommended: Не рекомендується
botProtection: Захист від ботів
instanceBlocking: Керування Федерацією
privateMode: Приватний режим
allowedInstances: Сервери у білому списку
previewNoteText: Показати прев'ю
antennaInstancesDescription: Введіть по одному хосту сервера на рядок
breakFollowConfirm: Ви дійсно бажаєте видалити підписника?
ads: Реклама
cw: Попередження про вміст
hiddenTags: Приховані хештеги
noInstances: Немає серверів
misskeyUpdated: Calckey оновлено!
received: Отримане
xl: Надвеликий
searchResult: Результати пошуку
useBlurEffect: Використовувати ефекти розмиття в інтерфейсі
learnMore: Дізнатися більше
usernameInfo: Ім'я, яке ідентифікує ваш обліковий запис серед інших на цьому сервері. Ви
можете використовувати алфавіт (a~z, A~Z), цифри (0~9) або знаки підкреслення (_).
Ім'я користувача не може бути змінено пізніше.
noThankYou: Ні, дякую
keepCw: Зберігати попередження про вміст

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
_lang_: "繁體中文" _lang_: "繁體中文"
headlineMisskey: "貼文連繫網路" headlineMisskey: "貼文連繫網路"
introMisskey: "歡迎! Firefish是一個免費開放原碼去中心化的社群網路🚀" introMisskey: "歡迎! Calckey是一個開源、去中心化且永遠免費的社群網路平台🚀"
monthAndDay: "{month}月 {day}日" monthAndDay: "{month}月 {day}日"
search: "搜尋" search: "搜尋"
notifications: "通知" notifications: "通知"
@ -21,7 +21,7 @@ basicSettings: "基本設定"
otherSettings: "其他設定" otherSettings: "其他設定"
openInWindow: "在新視窗開啟" openInWindow: "在新視窗開啟"
profile: "個人檔案" profile: "個人檔案"
timeline: "時間" timeline: "時間"
noAccountDescription: "此用戶還沒有自我介紹。" noAccountDescription: "此用戶還沒有自我介紹。"
login: "登入" login: "登入"
loggingIn: "登入中" loggingIn: "登入中"
@ -31,7 +31,7 @@ uploading: "上傳中..."
save: "儲存" save: "儲存"
users: "使用者" users: "使用者"
addUser: "新增使用者" addUser: "新增使用者"
favorite: "我的最愛" favorite: "添加至我的最愛"
favorites: "我的最愛" favorites: "我的最愛"
unfavorite: "從我的最愛中移除" unfavorite: "從我的最愛中移除"
favorited: "已添加至我的最愛。" favorited: "已添加至我的最愛。"
@ -43,7 +43,7 @@ copyContent: "複製內容"
copyLink: "複製連結" copyLink: "複製連結"
delete: "刪除" delete: "刪除"
deleteAndEdit: "刪除並編輯" deleteAndEdit: "刪除並編輯"
deleteAndEditConfirm: "要刪除並再次編輯嗎?此貼文的所有情感、轉發和回覆也將會消失。" deleteAndEditConfirm: "要刪除並再次編輯嗎?此貼文的所有反應、轉發和回覆也會消失。"
addToList: "加入至清單" addToList: "加入至清單"
sendMessage: "發送訊息" sendMessage: "發送訊息"
copyUsername: "複製使用者名稱" copyUsername: "複製使用者名稱"
@ -64,7 +64,7 @@ export: "匯出"
files: "檔案" files: "檔案"
download: "下載" download: "下載"
driveFileDeleteConfirm: "確定要刪除檔案「{name}」嗎?使用此附件的貼文也會跟著消失。" driveFileDeleteConfirm: "確定要刪除檔案「{name}」嗎?使用此附件的貼文也會跟著消失。"
unfollowConfirm: "確定要取消追隨{name}嗎?" unfollowConfirm: "確定要取消追隨{name}嗎?"
exportRequested: "已請求匯出。這可能會花一點時間。結束後檔案將會被放到雲端裡。" exportRequested: "已請求匯出。這可能會花一點時間。結束後檔案將會被放到雲端裡。"
importRequested: "已請求匯入。這可能會花一點時間。" importRequested: "已請求匯入。這可能會花一點時間。"
lists: "清單" lists: "清單"
@ -95,9 +95,9 @@ followRequestPending: "追隨許可批准中"
enterEmoji: "輸入表情符號" enterEmoji: "輸入表情符號"
renote: "轉發" renote: "轉發"
unrenote: "取消轉發" unrenote: "取消轉發"
renoted: "已轉。" renoted: "已轉。"
cantRenote: "無法轉發此貼文。" cantRenote: "無法轉發此貼文。"
cantReRenote: "無法轉傳之前已經轉傳過的內容。" cantReRenote: "無法轉發之前已經轉發過的內容。"
quote: "引用" quote: "引用"
pinnedNote: "已置頂的貼文" pinnedNote: "已置頂的貼文"
pinned: "置頂" pinned: "置頂"
@ -105,7 +105,7 @@ you: "您"
clickToShow: "按一下以顯示" clickToShow: "按一下以顯示"
sensitive: "敏感內容" sensitive: "敏感內容"
add: "新增" add: "新增"
reaction: "情感" reaction: "反應"
enableEmojiReaction: "啟用表情符號反應" enableEmojiReaction: "啟用表情符號反應"
showEmojisInReactionNotifications: "在反應通知中顯示表情符號" showEmojisInReactionNotifications: "在反應通知中顯示表情符號"
reactionSetting: "在選擇器中顯示反應" reactionSetting: "在選擇器中顯示反應"
@ -140,14 +140,14 @@ emojiUrl: "表情符號URL"
addEmoji: "加入表情符號" addEmoji: "加入表情符號"
settingGuide: "推薦設定" settingGuide: "推薦設定"
cacheRemoteFiles: "快取遠端檔案" cacheRemoteFiles: "快取遠端檔案"
cacheRemoteFilesDescription: "禁用此設定會停止遠端檔案的緩存,從而節省儲存空間,但資料會因直接連線從而產生額外連接數據。" cacheRemoteFilesDescription: "禁用此設定會停止遠端檔案的緩存,從而節省儲存空間,但資料會因直接連線從而產生額外數據花費。"
flagAsBot: "此使用者是機器人" flagAsBot: "標記此帳號是機器人"
flagAsBotDescription: "如果本帳戶是由程式控制,請啟用此選項。啟用後,會作為標示幫助其他開發者防止機器人之間產生無限互動的行為,並會調整Firefish內部系統將本帳戶識別為機器人。" flagAsBotDescription: "如果本帳戶是由程式控制,請啟用此選項。啟用後,會作為標示幫助其他開發者防止機器人之間產生無限互動的行為,並會調整Calckey內部系統將本帳戶識別為機器人。"
flagAsCat: "此使用者是貓" flagAsCat: "你是喵咪嗎w😺"
flagAsCatDescription: "如果想將本帳戶標示為一隻貓,請開啟此標示!" flagAsCatDescription: "如果想將本帳戶標示為一隻貓,請開啟此標示!"
flagShowTimelineReplies: "在時間上顯示貼文的回覆" flagShowTimelineReplies: "在時間上顯示貼文的回覆"
flagShowTimelineRepliesDescription: "啟用時,時間線除了顯示用戶的貼文以外,還會顯示用戶對其他貼文的回覆。" flagShowTimelineRepliesDescription: "啟用時,時間線除了顯示用戶的貼文以外,還會顯示用戶對其他貼文的回覆。"
autoAcceptFollowed: "自動追隨中使用者的追隨請求" autoAcceptFollowed: "自動准予追隨中使用者的追隨請求"
addAccount: "添加帳戶" addAccount: "添加帳戶"
loginFailed: "登入失敗" loginFailed: "登入失敗"
showOnRemote: "轉到所在伺服器顯示" showOnRemote: "轉到所在伺服器顯示"
@ -157,7 +157,7 @@ setWallpaper: "設定桌布"
removeWallpaper: "移除桌布" removeWallpaper: "移除桌布"
searchWith: "搜尋: {q}" searchWith: "搜尋: {q}"
youHaveNoLists: "你沒有任何清單" youHaveNoLists: "你沒有任何清單"
followConfirm: "你真的要追隨{name}嗎?" followConfirm: "你真的要追隨{name}嗎?"
proxyAccount: "代理帳戶" proxyAccount: "代理帳戶"
proxyAccountDescription: "代理帳戶是在某些情況下充當其他伺服器用戶的帳戶。例如,當使用者將一個來自其他伺服器的帳戶放在列表中時,由於沒有其他使用者追蹤該帳戶,該指令不會傳送到該伺服器上,因此會由代理帳戶追蹤。" proxyAccountDescription: "代理帳戶是在某些情況下充當其他伺服器用戶的帳戶。例如,當使用者將一個來自其他伺服器的帳戶放在列表中時,由於沒有其他使用者追蹤該帳戶,該指令不會傳送到該伺服器上,因此會由代理帳戶追蹤。"
host: "主機" host: "主機"
@ -166,7 +166,7 @@ recipient: "收件人"
annotation: "註解" annotation: "註解"
federation: "站台聯邦" federation: "站台聯邦"
instances: "伺服器" instances: "伺服器"
registeredAt: "初次觀測" registeredAt: "初次註冊"
latestRequestSentAt: "上次發送的請求" latestRequestSentAt: "上次發送的請求"
latestRequestReceivedAt: "上次收到的請求" latestRequestReceivedAt: "上次收到的請求"
latestStatus: "最後狀態" latestStatus: "最後狀態"
@ -234,19 +234,19 @@ lookup: "查詢"
announcements: "公告" announcements: "公告"
imageUrl: "圖片URL" imageUrl: "圖片URL"
remove: "刪除" remove: "刪除"
removed: "已刪除" removed: "已成功刪除"
removeAreYouSure: "確定要刪掉「{x}」嗎?" removeAreYouSure: "確定要刪掉「{x}」嗎?"
deleteAreYouSure: "確定要刪掉「{x}」嗎?" deleteAreYouSure: "確定要刪掉「{x}」嗎?"
resetAreYouSure: "確定要重設嗎?" resetAreYouSure: "確定要重設嗎?"
saved: "已儲存" saved: "已儲存"
messaging: "傳送訊息" messaging: "訊息"
upload: "上傳" upload: "上傳"
keepOriginalUploading: "保留原圖" keepOriginalUploading: "保留原圖"
keepOriginalUploadingDescription: "上傳圖片時保留原始圖片。關閉時,瀏覽器會在上傳時生成一張用於web發布的圖片。" keepOriginalUploadingDescription: "上傳圖片時保留原始圖片。關閉時,瀏覽器會在上傳時自動產生用於貼文發布的圖片。"
fromDrive: "從雲端空間" fromDrive: "從雲端空間"
fromUrl: "從URL" fromUrl: "從網址"
uploadFromUrl: "從網址上傳" uploadFromUrl: "從網址上傳"
uploadFromUrlDescription: "您要上傳的文件的URL" uploadFromUrlDescription: "您要上傳的文件的網址"
uploadFromUrlRequested: "已請求上傳" uploadFromUrlRequested: "已請求上傳"
uploadFromUrlMayTakeTime: "還需要一些時間才能完成上傳。" uploadFromUrlMayTakeTime: "還需要一些時間才能完成上傳。"
explore: "探索" explore: "探索"
@ -258,7 +258,7 @@ agreeTo: "我同意{0}"
tos: "使用條款" tos: "使用條款"
start: "開始" start: "開始"
home: "首頁" home: "首頁"
remoteUserCaution: "由於該使用者來自遠端實例,因此資訊可能非即時的。" remoteUserCaution: "由於該使用者來自遠端實例,因此資料可能是非即時的。"
activity: "動態" activity: "動態"
images: "圖片" images: "圖片"
birthday: "生日" birthday: "生日"
@ -267,12 +267,12 @@ registeredDate: "註冊日期"
location: "位置" location: "位置"
theme: "外觀主題" theme: "外觀主題"
themeForLightMode: "在淺色模式下使用的主題" themeForLightMode: "在淺色模式下使用的主題"
themeForDarkMode: "在模式下使用的主題" themeForDarkMode: "在黑模式下使用的主題"
light: "淺色" light: "淺色"
dark: "" dark: "黑"
lightThemes: "明亮主題" lightThemes: "明亮主題"
darkThemes: "主題" darkThemes: "黑主題"
syncDeviceDarkMode: "將黑暗模式與設備設置同步" syncDeviceDarkMode: "闇黑模式使用裝置設定"
drive: "雲端硬碟" drive: "雲端硬碟"
fileName: "檔案名稱" fileName: "檔案名稱"
selectFile: "選擇檔案" selectFile: "選擇檔案"
@ -281,19 +281,19 @@ selectFolder: "選擇資料夾"
selectFolders: "選擇資料夾" selectFolders: "選擇資料夾"
renameFile: "重新命名檔案" renameFile: "重新命名檔案"
folderName: "資料夾名稱" folderName: "資料夾名稱"
createFolder: "新增資料夾" createFolder: "創建資料夾"
renameFolder: "重新命名資料夾" renameFolder: "重新命名資料夾"
deleteFolder: "刪除資料夾" deleteFolder: "刪除資料夾"
addFile: "加入附件" addFile: "加入附件"
emptyDrive: "雲端硬碟為空" emptyDrive: "你的雲端硬碟沒有任何東西( ̄▽ ̄)\""
emptyFolder: "資料夾為空" emptyFolder: "資料夾裡面沒有東西(⊙_⊙;)"
unableToDelete: "無法刪除" unableToDelete: "無法刪除"
inputNewFileName: "輸入檔案名稱" inputNewFileName: "輸入檔案名稱"
inputNewDescription: "請輸入新標題" inputNewDescription: "請輸入新標題"
inputNewFolderName: "輸入新資料夾的名稱" inputNewFolderName: "輸入新資料夾的名稱"
circularReferenceFolder: "目標文件夾是您要移動的文件夾的子文件夾。" circularReferenceFolder: "目標文件夾是您要移動的文件夾的子文件夾。"
hasChildFilesOrFolders: "此文件夾不是空的,無法刪除。" hasChildFilesOrFolders: "此文件夾不是空的,無法刪除。"
copyUrl: "複製URL" copyUrl: "複製網址"
rename: "重新命名" rename: "重新命名"
avatar: "大頭貼" avatar: "大頭貼"
banner: "橫幅" banner: "橫幅"
@ -304,7 +304,7 @@ reload: "重新整理"
doNothing: "無視" doNothing: "無視"
reloadConfirm: "確定要重新整理嗎?" reloadConfirm: "確定要重新整理嗎?"
watch: "關注" watch: "關注"
unwatch: "取消追隨" unwatch: "取消關注"
accept: "接受" accept: "接受"
reject: "拒絕" reject: "拒絕"
normal: "正常" normal: "正常"
@ -312,7 +312,7 @@ instanceName: "伺服器名稱"
instanceDescription: "伺服器說明" instanceDescription: "伺服器說明"
maintainerName: "管理員名稱" maintainerName: "管理員名稱"
maintainerEmail: "管理員郵箱" maintainerEmail: "管理員郵箱"
tosUrl: "服務條款URL" tosUrl: "服務條款網址"
thisYear: "本年" thisYear: "本年"
thisMonth: "本月" thisMonth: "本月"
today: "本日" today: "本日"
@ -323,23 +323,23 @@ pages: "頁面"
integration: "整合" integration: "整合"
connectService: "己連結" connectService: "己連結"
disconnectService: "己斷開" disconnectService: "己斷開"
enableLocalTimeline: "開啟本地時間" enableLocalTimeline: "開啟本地時間"
enableGlobalTimeline: "啟用公開時間" enableGlobalTimeline: "啟用公開時間"
disablingTimelinesInfo: "即使您關閉了時間線功能,管理員和協調人仍可以繼續使用,以方便您。" disablingTimelinesInfo: "即使您關閉了時間線功能,管理員和版主始終可以訪問所有的時間線。"
registration: "註冊" registration: "註冊"
enableRegistration: "開啟新使用者註冊" enableRegistration: "開啟新使用者註冊"
invite: "邀請" invite: "邀請"
driveCapacityPerLocalAccount: "每個本地用戶的雲端空間大小" driveCapacityPerLocalAccount: "每個本地用戶的雲端空間大小"
driveCapacityPerRemoteAccount: "每個非本地用戶的雲端容量" driveCapacityPerRemoteAccount: "每個非本地用戶的雲端容量"
inMb: "以Mbps為單位" inMb: "以MB為單位"
iconUrl: "圖像URL" iconUrl: "圖標網址"
bannerUrl: "橫幅圖像URL" bannerUrl: "橫幅圖像網址"
backgroundImageUrl: "背景圖片的來源網址" backgroundImageUrl: "背景圖片的來源網址"
basicInfo: "基本資訊" basicInfo: "基本資訊"
pinnedUsers: "置頂用戶" pinnedUsers: "置頂用戶"
pinnedUsersDescription: "在「發現」頁面中使用換行標記想要置頂的使用者。" pinnedUsersDescription: "在「探索」頁面中使用換行標記想要置頂的使用者。"
pinnedPages: "釘選頁面" pinnedPages: "釘選頁面"
pinnedPagesDescription: "輸入要固定至伺服器首頁的頁面路徑,以換行符分隔。" pinnedPagesDescription: "輸入要固定至伺服器首頁的頁面路徑,一行一個。"
pinnedClipId: "置頂的摘錄ID" pinnedClipId: "置頂的摘錄ID"
pinnedNotes: "已置頂的貼文" pinnedNotes: "已置頂的貼文"
hcaptcha: "hCaptcha" hcaptcha: "hCaptcha"
@ -482,7 +482,7 @@ promotion: "推廣"
promote: "推廣" promote: "推廣"
numberOfDays: "有效天數" numberOfDays: "有效天數"
hideThisNote: "隱藏此貼文" hideThisNote: "隱藏此貼文"
showFeaturedNotesInTimeline: "在時間上顯示熱門推薦" showFeaturedNotesInTimeline: "在時間上顯示熱門推薦"
objectStorage: "Object Storage (物件儲存)" objectStorage: "Object Storage (物件儲存)"
useObjectStorage: "使用Object Storage" useObjectStorage: "使用Object Storage"
objectStorageBaseUrl: "根URL" objectStorageBaseUrl: "根URL"
@ -502,7 +502,7 @@ objectStorageUseProxyDesc: "如果不使用代理進行API連接請關閉"
objectStorageSetPublicRead: "上傳時設定為\"public-read\"" objectStorageSetPublicRead: "上傳時設定為\"public-read\""
serverLogs: "伺服器日誌" serverLogs: "伺服器日誌"
deleteAll: "刪除所有記錄" deleteAll: "刪除所有記錄"
showFixedPostForm: "於時間頁頂顯示「發送貼文」方框" showFixedPostForm: "於時間頁頂顯示「發送貼文」方框"
newNoteRecived: "發現新的貼文" newNoteRecived: "發現新的貼文"
sounds: "音效" sounds: "音效"
listen: "聆聽" listen: "聆聽"
@ -661,8 +661,8 @@ repliedCount: "回覆數量"
renotedCount: "轉發次數" renotedCount: "轉發次數"
followingCount: "正在跟隨的用戶數量" followingCount: "正在跟隨的用戶數量"
followersCount: "跟隨者數量" followersCount: "跟隨者數量"
sentReactionsCount: "情感發送次數" sentReactionsCount: "反應發送次數"
receivedReactionsCount: "情感收到次數" receivedReactionsCount: "反應收到次數"
pollVotesCount: "已統計的投票數" pollVotesCount: "已統計的投票數"
pollVotedCount: "已投票數" pollVotedCount: "已投票數"
yes: "確定" yes: "確定"
@ -688,7 +688,7 @@ experimentalFeatures: "實驗中的功能"
developer: "開發者" developer: "開發者"
makeExplorable: "使自己的帳戶能夠在“探索”頁面中顯示" makeExplorable: "使自己的帳戶能夠在“探索”頁面中顯示"
makeExplorableDescription: "如果關閉,帳戶將不會被顯示在\"探索\"頁面中。" makeExplorableDescription: "如果關閉,帳戶將不會被顯示在\"探索\"頁面中。"
showGapBetweenNotesInTimeline: "分開顯示時間上的貼文" showGapBetweenNotesInTimeline: "分開顯示時間上的貼文"
duplicate: "複製" duplicate: "複製"
left: "左" left: "左"
center: "置中" center: "置中"
@ -702,7 +702,8 @@ onlineUsersCount: "{n}人正在線上"
nUsers: "{n}用戶" nUsers: "{n}用戶"
nNotes: "{n}貼文" nNotes: "{n}貼文"
sendErrorReports: "傳送錯誤報告" sendErrorReports: "傳送錯誤報告"
sendErrorReportsDescription: "啟用後問題報告將傳送至Firefish開發者以提升軟體品質。\n問題報告可能包括OS版本瀏覽器類型行為歷史記錄等。" sendErrorReportsDescription: "開啟後,錯誤出現時將會與 Calckey 分享詳細紀錄,對於 Calckey 的開發會有非常大的幫助。\n
這將包括您的操作系統版本、使用的瀏覽器、您在 Calckey 中的活動等資料。"
myTheme: "我的佈景主題" myTheme: "我的佈景主題"
backgroundColor: "背景" backgroundColor: "背景"
accentColor: "重點色彩" accentColor: "重點色彩"
@ -862,7 +863,7 @@ check: "檢查"
driveCapOverrideLabel: "更改這個使用者的雲端硬碟容量上限" driveCapOverrideLabel: "更改這個使用者的雲端硬碟容量上限"
driveCapOverrideCaption: "如果指定0以下的值就會被取消。" driveCapOverrideCaption: "如果指定0以下的值就會被取消。"
requireAdminForView: "必須以管理者帳號登入才可以檢視。" requireAdminForView: "必須以管理者帳號登入才可以檢視。"
isSystemAccount: "由系統自動建立與管理的帳號。" isSystemAccount: "該帳號由系統自動創建並運行。 千千萬萬不要審核、編輯、刪除或以其他方式修改此帳戶,否則可能會破壞您的伺服器。"
typeToConfirm: "要執行這項操作,請輸入 {x}" typeToConfirm: "要執行這項操作,請輸入 {x}"
deleteAccount: "刪除帳號" deleteAccount: "刪除帳號"
document: "文件" document: "文件"
@ -1089,8 +1090,8 @@ _wordMute:
muteWords: "加入靜音文字" muteWords: "加入靜音文字"
muteWordsDescription: "用空格分隔指定AND用換行分隔指定OR。" muteWordsDescription: "用空格分隔指定AND用換行分隔指定OR。"
muteWordsDescription2: "將關鍵字用斜線括起來表示正規表達式。" muteWordsDescription2: "將關鍵字用斜線括起來表示正規表達式。"
softDescription: "隱藏時間中指定條件的貼文。" softDescription: "隱藏時間中指定條件的貼文。"
hardDescription: "具有指定條件的貼文將不添加到時間。 即使您更改條件,未被添加的貼文也會被排除在外。" hardDescription: "具有指定條件的貼文將不添加到時間。 即使您更改條件,未被添加的貼文也會被排除在外。"
soft: "軟性靜音" soft: "軟性靜音"
hard: "硬性靜音" hard: "硬性靜音"
mutedNotes: "已靜音的貼文" mutedNotes: "已靜音的貼文"
@ -1203,16 +1204,16 @@ _tutorial:
step2_1: "首先,請完成你的個人資料。" step2_1: "首先,請完成你的個人資料。"
step2_2: "通過提供一些關於你自己的資料,其他人會更容易了解他們是否想看到你的帖子或關注你。" step2_2: "通過提供一些關於你自己的資料,其他人會更容易了解他們是否想看到你的帖子或關注你。"
step3_1: "現在是時候追隨一些人了!" step3_1: "現在是時候追隨一些人了!"
step3_2: "你的主頁和社交時間軸是基於你所追蹤的人,所以試著先追蹤幾個賬戶。\n點擊個人資料右上角的加號圈就可以關注它。" step3_2: "你的主頁和社交時間線是基於你所追蹤的人,所以試著先追蹤幾個帳戶。\n點擊個人資料右上角的加號圈就可以關注它。"
step4_1: "讓我們出去找你。" step4_1: "讓我們出去找你。"
step4_2: "對於他們的第一條信息,有些人喜歡做 {introduction} 或一個簡單的 \"hello world!\"" step4_2: "對於他們的第一條信息,有些人喜歡做 {introduction} 或一個簡單的 \"hello world!\""
step5_1: "時間軸,到處都是時間軸" step5_1: "時間線,到處都是時間線"
step5_2: "您的伺服器已啟用了{timelines}個時間。" step5_2: "您的伺服器已啟用了{timelines}個時間。"
step5_3: "主 {icon} 時間軸是顯示你追蹤的帳號的帖子。" step5_3: "首頁 {icon} 時間線是顯示你追蹤的帳號的帖子。"
step5_4: "本地 {icon} 時間軸是你可以看到伺服器中所有其他用戶的信息的時間軸。" step5_4: "本地 {icon} 時間線是你可以看到伺服器中所有其他用戶的貼文的時間線。"
step5_5: "社交 {icon} 時間軸是顯示你的主時間軸 + 本地時間軸。" step5_5: "社交 {icon} 時間線是你的 首頁時間線 和 本地時間線 的結合體。"
step5_6: "推薦 {icon} 時間軸是顯示你的伺服器管理員推薦的帖文。" step5_6: "推薦 {icon} 時間線是顯示你的伺服器管理員推薦的貼文。"
step5_7: "全球 {icon} 時間軸是顯示來自所有其他連接的伺服器的帖文。" step5_7: "全球 {icon} 時間線是顯示來自所有其他連接的伺服器的貼文。"
step6_1: "那麼,這裡是什麼地方?" step6_1: "那麼,這裡是什麼地方?"
step6_2: "你不只是加入Firefish。你已經加入了Fediverse的一個門戶這是一個由成千上萬台服務器組成的互聯網絡。" step6_2: "你不只是加入Firefish。你已經加入了Fediverse的一個門戶這是一個由成千上萬台服務器組成的互聯網絡。"
step6_3: "每個服務器也有不同而並不是所有的服務器都運行Firefish。但這個服務器確實是運行Firefish的! 你可能會覺得有點複雜,但你很快就會明白的。" step6_3: "每個服務器也有不同而並不是所有的服務器都運行Firefish。但這個服務器確實是運行Firefish的! 你可能會覺得有點複雜,但你很快就會明白的。"
@ -1245,8 +1246,8 @@ _permissions:
"write:notes": "撰寫或刪除貼文" "write:notes": "撰寫或刪除貼文"
"read:notifications": "查看通知" "read:notifications": "查看通知"
"write:notifications": "編輯通知" "write:notifications": "編輯通知"
"read:reactions": "查看情感" "read:reactions": "查看反應"
"write:reactions": "編輯情感" "write:reactions": "編輯反應"
"write:votes": "投票" "write:votes": "投票"
"read:pages": "顯示頁面" "read:pages": "顯示頁面"
"write:pages": "編輯頁面" "write:pages": "編輯頁面"
@ -1284,7 +1285,7 @@ _weekday:
_widgets: _widgets:
memo: "備忘錄" memo: "備忘錄"
notifications: "通知" notifications: "通知"
timeline: "時間" timeline: "時間"
calendar: "行事曆" calendar: "行事曆"
trends: "發燒貼文" trends: "發燒貼文"
clock: "時鐘" clock: "時鐘"
@ -1335,7 +1336,7 @@ _visibility:
public: "公開" public: "公開"
publicDescription: "發布給所有用戶" publicDescription: "發布給所有用戶"
home: "不在主頁顯示" home: "不在主頁顯示"
homeDescription: "僅發送至首頁的時間" homeDescription: "僅發送至首頁的時間"
followers: "追隨者" followers: "追隨者"
followersDescription: "僅發送至關注者" followersDescription: "僅發送至關注者"
specified: "指定使用者" specified: "指定使用者"
@ -1403,7 +1404,7 @@ _instanceCharts:
_timelines: _timelines:
home: "首頁" home: "首頁"
local: "本地" local: "本地"
social: "社" social: "社"
global: "公開" global: "公開"
recommended: 推薦 recommended: 推薦
_pages: _pages:
@ -1726,7 +1727,7 @@ _notification:
pollEnded: "問卷調查結束" pollEnded: "問卷調查結束"
receiveFollowRequest: "已收到追隨請求" receiveFollowRequest: "已收到追隨請求"
followRequestAccepted: "追隨請求已接受" followRequestAccepted: "追隨請求已接受"
groupInvited: "加入社群邀請" groupInvited: "群組加入邀請"
app: "應用程式通知" app: "應用程式通知"
_actions: _actions:
followBack: "回關" followBack: "回關"
@ -1755,7 +1756,7 @@ _deck:
main: "主列" main: "主列"
widgets: "小工具" widgets: "小工具"
notifications: "通知" notifications: "通知"
tl: "時間" tl: "時間"
antenna: "天線" antenna: "天線"
list: "清單" list: "清單"
mentions: "提及" mentions: "提及"
@ -1782,11 +1783,11 @@ enterSendsMessage: 在 Messaging 中按 Return 發送消息 (如關閉則是 Ctr
migrationConfirm: "您確定要將你的帳戶遷移到 {account} 嗎? 一旦這樣做,你將無法復原,而你將無法再次正常使用您的帳戶。\n另外請確保你已將此當前帳戶設置為您要遷移的帳戶。" migrationConfirm: "您確定要將你的帳戶遷移到 {account} 嗎? 一旦這樣做,你將無法復原,而你將無法再次正常使用您的帳戶。\n另外請確保你已將此當前帳戶設置為您要遷移的帳戶。"
customSplashIconsDescription: 每次用戶加載/重新加載頁面時,以換行符號分隔的自定啟動畫面圖標的網址將隨機顯示。請確保圖片位於靜態網址上,最好所有圖片解析度調整為 customSplashIconsDescription: 每次用戶加載/重新加載頁面時,以換行符號分隔的自定啟動畫面圖標的網址將隨機顯示。請確保圖片位於靜態網址上,最好所有圖片解析度調整為
192x192。 192x192。
accountMoved: '該使用者已移至新帳戶:' accountMoved: '該使用者已移至新帳戶:'
showAds: 顯示廣告 showAds: 顯示廣告
noThankYou: 不用了,謝謝 noThankYou: 不用了,謝謝
selectInstance: 選擇伺服器 selectInstance: 選擇伺服器
enableRecommendedTimeline: 啟用推薦時間 enableRecommendedTimeline: 啟用推薦時間
antennaInstancesDescription: 分行列出一個伺服器 antennaInstancesDescription: 分行列出一個伺服器
moveTo: 遷移此帳戶到新帳戶 moveTo: 遷移此帳戶到新帳戶
moveToLabel: '請輸入你將會遷移到的帳戶:' moveToLabel: '請輸入你將會遷移到的帳戶:'
@ -1838,10 +1839,31 @@ pushNotification: 推送通知
subscribePushNotification: 啟用推送通知 subscribePushNotification: 啟用推送通知
unsubscribePushNotification: 禁用推送通知 unsubscribePushNotification: 禁用推送通知
pushNotificationAlreadySubscribed: 推送通知已經啟用 pushNotificationAlreadySubscribed: 推送通知已經啟用
recommendedInstancesDescription: 以每行分隔的推薦服務器出現在推薦的時間軸中。 不要添加 `https://`,只添加域名 recommendedInstancesDescription: 以每行分隔的推薦伺服器出現在推薦的時間線中
searchPlaceholder: 搜尋 Firefish searchPlaceholder: 在聯邦網路上搜尋
cw: 內容警告 cw: 內容警告
selectChannel: 選擇一個頻道 selectChannel: 選擇一個頻道
newer: 較新 newer: 較新
older: 較舊 older: 較舊
jumpToPrevious: 跳到上一個 jumpToPrevious: 跳到上一個
removeReaction: 移除你的反應
listsDesc: 清單可以創建一個只有您指定用戶的時間線。 可以從時間線頁面訪問它們。
flagSpeakAsCatDescription: 在喵咪模式下你的貼文會被喵化ヾ(•ω•`)o
antennasDesc: "天線會顯示符合您設置條件的新貼文!\n 可以從時間線訪問它們。"
expandOnNoteClick: 點擊以打開貼文
expandOnNoteClickDesc: 如果禁用,您仍然可以通過右鍵單擊菜單或單擊時間戳來打開貼文。
hiddenTagsDescription: '列出您希望隱藏趨勢和探索的主題標籤(不帶 #)。 隱藏的主題標籤仍然可以通過其他方式發現。'
userSaysSomethingReasonQuote: '{name} 引用了一篇包含 {reason} 的貼文'
silencedInstancesDescription: 列出您想要靜音的伺服器的網址。 您列出的伺服器內的帳戶將被視為“沉默”,只能發出追隨請求,如果不追隨則不能提及本地帳戶。
這不會影響被阻止的伺服器。
video: 影片
audio: 音訊
sendPushNotificationReadMessageCaption: 包含文本 “{emptyPushNotificationMessage}” 的通知將顯示一小段時間。
這可能會增加您設備的電池使用量(如果適用)。
channelFederationWarn: 頻道功能尚未與聯邦宇宙連動
swipeOnMobile: 允許在頁面之間滑動
sendPushNotificationReadMessage: 閱讀相關通知或消息後刪除推送通知
image: 圖片
seperateRenoteQuote: 分別獨立的轉傳及引用按鈕
clipsDesc: 摘錄就像一個可以分享的書籤。 你可以從每個貼文的菜單創建新摘錄或將貼文加入已有的摘錄。
noteId: 貼文 ID

View File

@ -1,16 +1,16 @@
{ {
"name": "firefish", "name": "calckey",
"version": "14.0.0-rc3", "version": "14.0.0-dev77",
"codename": "aqua", "codename": "aqua",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://codeberg.org/firefish/firefish.git" "url": "https://codeberg.org/firefish/firefish.git"
}, },
"packageManager": "pnpm@8.6.3", "packageManager": "pnpm@8.6.7",
"private": true, "private": true,
"scripts": { "scripts": {
"rebuild": "pnpm run clean && pnpm node ./scripts/build-greet.js && pnpm -r run build && pnpm run gulp", "rebuild": "pnpm run clean && pnpm node ./scripts/build-greet.js && pnpm -r --parallel run build && pnpm run gulp",
"build": "pnpm node ./scripts/build-greet.js && pnpm -r run build && pnpm run gulp", "build": "pnpm node ./scripts/build-greet.js && pnpm -r --parallel run build && pnpm run gulp",
"start": "pnpm --filter backend run start", "start": "pnpm --filter backend run start",
"start:test": "pnpm --filter backend run start:test", "start:test": "pnpm --filter backend run start:test",
"init": "pnpm run migrate", "init": "pnpm run migrate",
@ -21,13 +21,13 @@
"watch": "pnpm run dev", "watch": "pnpm run dev",
"dev": "pnpm node ./scripts/dev.js", "dev": "pnpm node ./scripts/dev.js",
"dev:staging": "NODE_OPTIONS=--max_old_space_size=3072 NODE_ENV=development pnpm run build && pnpm run start", "dev:staging": "NODE_OPTIONS=--max_old_space_size=3072 NODE_ENV=development pnpm run build && pnpm run start",
"lint": "pnpm -r run lint", "lint": "pnpm -r --parallel run lint",
"cy:open": "cypress open --browser --e2e --config-file=cypress.config.ts", "cy:open": "cypress open --browser --e2e --config-file=cypress.config.ts",
"cy:run": "cypress run", "cy:run": "cypress run",
"e2e": "start-server-and-test start:test http://localhost:61812 cy:run", "e2e": "start-server-and-test start:test http://localhost:61812 cy:run",
"mocha": "pnpm --filter backend run mocha", "mocha": "pnpm --filter backend run mocha",
"test": "pnpm run mocha", "test": "pnpm run mocha",
"format": "pnpm -r run format", "format": "pnpm -r --parallel run format",
"clean": "pnpm node ./scripts/clean.js", "clean": "pnpm node ./scripts/clean.js",
"clean-all": "pnpm node ./scripts/clean-all.js", "clean-all": "pnpm node ./scripts/clean-all.js",
"cleanall": "pnpm run clean-all" "cleanall": "pnpm run clean-all"
@ -36,16 +36,17 @@
"chokidar": "^3.3.1" "chokidar": "^3.3.1"
}, },
"dependencies": { "dependencies": {
"@bull-board/api": "5.2.0", "@bull-board/api": "5.6.0",
"@bull-board/ui": "5.2.0", "@bull-board/ui": "5.6.0",
"@napi-rs/cli": "^2.16.1", "@napi-rs/cli": "^2.16.1",
"@tensorflow/tfjs": "^3.21.0", "@tensorflow/tfjs": "^3.21.0",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"seedrandom": "^3.0.5" "seedrandom": "^3.0.5"
}, },
"devDependencies": { "devDependencies": {
"@types/gulp": "4.0.10", "@types/gulp": "4.0.13",
"@types/gulp-rename": "2.0.1", "@types/gulp-rename": "2.0.2",
"@types/node": "20.4.1",
"chalk": "4.1.2", "chalk": "4.1.2",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "10.11.0", "cypress": "10.11.0",
@ -58,6 +59,6 @@
"install-peers": "^1.0.4", "install-peers": "^1.0.4",
"rome": "^12.1.3", "rome": "^12.1.3",
"start-server-and-test": "1.15.2", "start-server-and-test": "1.15.2",
"typescript": "4.9.4" "typescript": "5.1.6"
} }
} }

View File

@ -6,4 +6,5 @@ This directory contains all of the packages Firefish uses.
- `backend/native-utils`: Backend code written in Rust, bound to NodeJS by [NAPI-RS](https://napi.rs/) - `backend/native-utils`: Backend code written in Rust, bound to NodeJS by [NAPI-RS](https://napi.rs/)
- `client`: Web interface written in Vue3 and TypeScript - `client`: Web interface written in Vue3 and TypeScript
- `sw`: Web [Service Worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) written in TypeScript - `sw`: Web [Service Worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) written in TypeScript
- `firefish-js`: TypeScript SDK for both backend and client, also published on [NPM](https://www.npmjs.com/package/firefish-js) for public use - `calckey-js`: TypeScript SDK for both backend and client, also published on [NPM](https://www.npmjs.com/package/calckey-js) for public use
- `megalodon`: TypeScript library used for partial Mastodon API compatibility

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@ -0,0 +1,10 @@
export class tweakVarcharLength1678426061773 {
name = 'tweakVarcharLength1678426061773'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "smtpUser" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "smtpPass" TYPE character varying(1024)`, undefined);
}
async down(queryRunner) {}
}

View File

@ -0,0 +1,21 @@
export class AddMetaOptions1688280713783 {
name = "AddMetaOptions1688280713783";
async up(queryRunner) {
await queryRunner.query(
`ALTER TABLE "meta" ADD "enableServerMachineStats" boolean NOT NULL DEFAULT false`,
);
await queryRunner.query(
`ALTER TABLE "meta" ADD "enableIdenticonGeneration" boolean NOT NULL DEFAULT true`,
);
}
async down(queryRunner) {
await queryRunner.query(
`ALTER TABLE "meta" DROP COLUMN "enableIdenticonGeneration"`,
);
await queryRunner.query(
`ALTER TABLE "meta" DROP COLUMN "enableServerMachineStats"`,
);
}
}

View File

@ -0,0 +1,21 @@
export class AnnouncementPopup1688845537045 {
name = "AnnouncementPopup1688845537045";
async up(queryRunner) {
await queryRunner.query(
`ALTER TABLE "announcement" ADD "showPopup" boolean NOT NULL DEFAULT false`,
);
await queryRunner.query(
`ALTER TABLE "announcement" ADD "isGoodNews" boolean NOT NULL DEFAULT false`,
);
}
async down(queryRunner) {
await queryRunner.query(
`ALTER TABLE "announcement" DROP COLUMN "isGoodNews"`,
);
await queryRunner.query(
`ALTER TABLE "announcement" DROP COLUMN "showPopup"`,
);
}
}

View File

@ -0,0 +1,15 @@
export class DonationLink1689136347561 {
name = "DonationLink1689136347561";
async up(queryRunner) {
await queryRunner.query(
`ALTER TABLE "meta" ADD "donationLink" character varying(256)`,
);
}
async down(queryRunner) {
await queryRunner.query(
`ALTER TABLE "meta" DROP COLUMN "DonationLink1689136347561"`,
);
}
}

View File

@ -13,7 +13,6 @@ pub enum IdConvertType {
#[napi] #[napi]
pub fn convert_id(in_id: String, id_convert_type: IdConvertType) -> napi::Result<String> { pub fn convert_id(in_id: String, id_convert_type: IdConvertType) -> napi::Result<String> {
println!("converting id: {}", in_id);
use IdConvertType::*; use IdConvertType::*;
match id_convert_type { match id_convert_type {
MastodonId => { MastodonId => {

View File

@ -25,17 +25,16 @@
"@tensorflow/tfjs-node": "3.21.1" "@tensorflow/tfjs-node": "3.21.1"
}, },
"dependencies": { "dependencies": {
"@bull-board/api": "5.2.0", "@bull-board/api": "5.6.0",
"@bull-board/koa": "5.2.0", "@bull-board/koa": "5.6.0",
"@bull-board/ui": "5.2.0", "@bull-board/ui": "5.6.0",
"@firefish/megalodon": "5.2.0",
"@discordapp/twemoji": "14.1.2", "@discordapp/twemoji": "14.1.2",
"@elastic/elasticsearch": "7.17.0", "@elastic/elasticsearch": "7.17.0",
"@koa/cors": "3.4.3", "@koa/cors": "3.4.3",
"@koa/multer": "3.0.2", "@koa/multer": "3.0.2",
"@koa/router": "9.0.1", "@koa/router": "9.0.1",
"@peertube/http-signature": "1.7.0", "@peertube/http-signature": "1.7.0",
"@redocly/openapi-core": "1.0.0-beta.120", "@redocly/openapi-core": "1.0.0-beta.131",
"@sinonjs/fake-timers": "9.1.2", "@sinonjs/fake-timers": "9.1.2",
"@syuilo/aiscript": "0.11.1", "@syuilo/aiscript": "0.11.1",
"@tensorflow/tfjs": "^4.2.0", "@tensorflow/tfjs": "^4.2.0",
@ -43,21 +42,19 @@
"ajv": "8.12.0", "ajv": "8.12.0",
"archiver": "5.3.1", "archiver": "5.3.1",
"argon2": "^0.30.3", "argon2": "^0.30.3",
"async-mutex": "^0.4.0",
"autobind-decorator": "2.4.0",
"autolinker": "4.0.0", "autolinker": "4.0.0",
"autwh": "0.1.0", "autwh": "0.1.0",
"aws-sdk": "2.1277.0", "aws-sdk": "2.1413.0",
"axios": "^1.4.0", "axios": "^1.4.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "1.1.5", "blurhash": "2.0.5",
"bull": "4.10.4", "bull": "4.10.4",
"cacheable-lookup": "7.0.0", "cacheable-lookup": "7.0.0",
"firefish-js": "workspace:*", "firefish-js": "workspace:*",
"cbor": "8.1.0", "cbor": "8.1.0",
"chalk": "5.2.0", "chalk": "5.3.0",
"chalk-template": "0.4.0", "chalk-template": "0.4.0",
"chokidar": "3.5.3", "chokidar": "^3.5.3",
"cli-highlight": "2.1.11", "cli-highlight": "2.1.11",
"color-convert": "2.0.1", "color-convert": "2.0.1",
"content-disposition": "0.5.4", "content-disposition": "0.5.4",
@ -70,15 +67,15 @@
"got": "12.5.3", "got": "12.5.3",
"hpagent": "0.1.2", "hpagent": "0.1.2",
"ioredis": "5.3.2", "ioredis": "5.3.2",
"ip-cidr": "3.0.11", "ip-cidr": "3.1.0",
"is-svg": "4.3.2", "is-svg": "4.3.2",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"jsdom": "20.0.3", "jsdom": "20.0.3",
"jsonld": "8.2.0", "jsonld": "8.2.0",
"jsrsasign": "10.6.1", "jsrsasign": "10.8.6",
"koa": "2.13.4", "koa": "2.14.2",
"koa-body": "^6.0.1", "koa-body": "^6.0.1",
"koa-bodyparser": "4.3.0", "koa-bodyparser": "4.4.1",
"koa-favicon": "2.1.0", "koa-favicon": "2.1.0",
"koa-json-body": "5.3.0", "koa-json-body": "5.3.0",
"koa-logger": "3.2.1", "koa-logger": "3.2.1",
@ -87,9 +84,11 @@
"koa-send": "5.0.1", "koa-send": "5.0.1",
"koa-slow": "2.1.0", "koa-slow": "2.1.0",
"koa-views": "7.0.2", "koa-views": "7.0.2",
"megalodon": "workspace:*",
"meilisearch": "0.33.0", "meilisearch": "0.33.0",
"mfm-js": "0.23.3", "mfm-js": "0.23.3",
"mime-types": "2.1.35", "mime-types": "2.1.35",
"msgpackr": "1.9.5",
"multer": "1.4.4-lts.1", "multer": "1.4.4-lts.1",
"native-utils": "link:native-utils", "native-utils": "link:native-utils",
"nested-property": "4.0.0", "nested-property": "4.0.0",
@ -98,9 +97,9 @@
"nsfwjs": "2.4.2", "nsfwjs": "2.4.2",
"oauth": "^0.10.0", "oauth": "^0.10.0",
"os-utils": "0.0.14", "os-utils": "0.0.14",
"otpauth": "^9.1.2", "otpauth": "^9.1.3",
"parse5": "7.1.2", "parse5": "7.1.2",
"pg": "8.11.0", "pg": "8.11.1",
"private-ip": "2.3.4", "private-ip": "2.3.4",
"probe-image-size": "7.2.3", "probe-image-size": "7.2.3",
"promise-limit": "2.7.0", "promise-limit": "2.7.0",
@ -110,7 +109,7 @@
"qs": "6.11.2", "qs": "6.11.2",
"random-seed": "0.3.0", "random-seed": "0.3.0",
"ratelimiter": "3.4.1", "ratelimiter": "3.4.1",
"re2": "1.19.0", "re2": "1.19.1",
"redis-lock": "0.1.4", "redis-lock": "0.1.4",
"redis-semaphore": "5.3.1", "redis-semaphore": "5.3.1",
"reflect-metadata": "0.1.13", "reflect-metadata": "0.1.13",
@ -119,7 +118,7 @@
"rss-parser": "3.13.0", "rss-parser": "3.13.0",
"sanitize-html": "2.10.0", "sanitize-html": "2.10.0",
"seedrandom": "^3.0.5", "seedrandom": "^3.0.5",
"semver": "7.5.1", "semver": "7.5.4",
"sharp": "0.32.1", "sharp": "0.32.1",
"sonic-channel": "^1.3.1", "sonic-channel": "^1.3.1",
"stringz": "2.1.0", "stringz": "2.1.0",
@ -130,27 +129,26 @@
"tinycolor2": "1.5.2", "tinycolor2": "1.5.2",
"tmp": "0.2.1", "tmp": "0.2.1",
"twemoji-parser": "14.0.0", "twemoji-parser": "14.0.0",
"typeorm": "0.3.11", "typeorm": "0.3.17",
"ulid": "2.3.0", "ulid": "2.3.0",
"uuid": "9.0.0", "uuid": "9.0.0",
"web-push": "3.6.1", "web-push": "3.6.3",
"websocket": "1.0.34", "websocket": "1.0.34",
"xev": "3.0.2" "xev": "3.0.2"
}, },
"devDependencies": { "devDependencies": {
"@swc/cli": "^0.1.62", "@swc/cli": "^0.1.62",
"@swc/core": "^1.3.62", "@swc/core": "^1.3.68",
"@types/adm-zip": "^0.5.0", "@types/adm-zip": "^0.5.0",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/bull": "3.15.9",
"@types/cbor": "6.0.0", "@types/cbor": "6.0.0",
"@types/escape-regexp": "0.0.1", "@types/escape-regexp": "0.0.1",
"@types/fluent-ffmpeg": "2.1.20", "@types/fluent-ffmpeg": "2.1.21",
"@types/js-yaml": "4.0.5", "@types/js-yaml": "4.0.5",
"@types/jsdom": "20.0.1", "@types/jsdom": "21.1.1",
"@types/jsonld": "1.5.8", "@types/jsonld": "1.5.9",
"@types/jsrsasign": "10.5.4", "@types/jsrsasign": "10.5.8",
"@types/koa": "2.13.5", "@types/koa": "2.13.6",
"@types/koa-bodyparser": "4.3.10", "@types/koa-bodyparser": "4.3.10",
"@types/koa-cors": "0.0.2", "@types/koa-cors": "0.0.2",
"@types/koa-favicon": "2.0.21", "@types/koa-favicon": "2.0.21",
@ -169,7 +167,7 @@
"@types/probe-image-size": "^7.2.0", "@types/probe-image-size": "^7.2.0",
"@types/pug": "2.0.6", "@types/pug": "2.0.6",
"@types/punycode": "2.1.0", "@types/punycode": "2.1.0",
"@types/qrcode": "1.5.0", "@types/qrcode": "1.5.1",
"@types/qs": "6.9.7", "@types/qs": "6.9.7",
"@types/random-seed": "0.3.3", "@types/random-seed": "0.3.3",
"@types/ratelimiter": "3.4.4", "@types/ratelimiter": "3.4.4",
@ -177,17 +175,15 @@
"@types/rename": "1.0.4", "@types/rename": "1.0.4",
"@types/sanitize-html": "2.9.0", "@types/sanitize-html": "2.9.0",
"@types/semver": "7.5.0", "@types/semver": "7.5.0",
"@types/sharp": "0.31.1",
"@types/sinonjs__fake-timers": "8.1.2", "@types/sinonjs__fake-timers": "8.1.2",
"@types/tinycolor2": "1.4.3", "@types/tinycolor2": "1.4.3",
"@types/tmp": "0.2.3", "@types/tmp": "0.2.3",
"@types/uuid": "8.3.4", "@types/uuid": "9.0.2",
"@types/web-push": "3.3.2", "@types/web-push": "3.3.2",
"@types/websocket": "1.0.5", "@types/websocket": "1.0.5",
"@types/ws": "8.5.4", "@types/ws": "8.5.5",
"autobind-decorator": "2.4.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint": "^8.42.0", "eslint": "^8.44.0",
"execa": "6.1.0", "execa": "6.1.0",
"json5": "2.2.3", "json5": "2.2.3",
"json5-loader": "4.0.1", "json5-loader": "4.0.1",
@ -195,11 +191,11 @@
"pug": "3.0.2", "pug": "3.0.2",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"swc-loader": "^0.2.3", "swc-loader": "^0.2.3",
"ts-loader": "9.4.3", "ts-loader": "9.4.4",
"ts-node": "10.9.1", "ts-node": "10.9.1",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"typescript": "5.1.3", "typescript": "5.1.6",
"webpack": "^5.85.1", "webpack": "^5.88.1",
"ws": "8.13.0" "ws": "8.13.0"
} }
} }

View File

@ -55,6 +55,8 @@ export default function load() {
mixin.clientEntry = clientManifest["src/init.ts"]; mixin.clientEntry = clientManifest["src/init.ts"];
if (!config.redis.prefix) config.redis.prefix = mixin.host; if (!config.redis.prefix) config.redis.prefix = mixin.host;
if (config.cacheServer && !config.cacheServer.prefix)
config.cacheServer.prefix = mixin.host;
return Object.assign(config, mixin); return Object.assign(config, mixin);
} }

View File

@ -26,6 +26,16 @@ export type Source = {
user?: string; user?: string;
tls?: { [y: string]: string }; tls?: { [y: string]: string };
}; };
cacheServer?: {
host: string;
port: number;
family?: number;
pass?: string;
db?: number;
prefix?: string;
user?: string;
tls?: { [z: string]: string };
};
elasticsearch: { elasticsearch: {
host: string; host: string;
port: number; port: number;

View File

@ -1,8 +1,7 @@
import config from "@/config/index.js"; import config from "@/config/index.js";
import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js"; import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js";
export const MAX_NOTE_TEXT_LENGTH = export const MAX_NOTE_TEXT_LENGTH = config.maxNoteLength ?? 3000;
config.maxNoteLength != null ? config.maxNoteLength : 3000; // <- should we increase this?
export const MAX_CAPTION_TEXT_LENGTH = Math.min( export const MAX_CAPTION_TEXT_LENGTH = Math.min(
config.maxCaptionLength ?? 1500, config.maxCaptionLength ?? 1500,
DB_MAX_IMAGE_COMMENT_LENGTH, DB_MAX_IMAGE_COMMENT_LENGTH,

View File

@ -1,6 +1,7 @@
import si from "systeminformation"; import si from "systeminformation";
import Xev from "xev"; import Xev from "xev";
import * as osUtils from "os-utils"; import * as osUtils from "os-utils";
import { fetchMeta } from "@/misc/fetch-meta.js";
import meilisearch from "../db/meilisearch.js"; import meilisearch from "../db/meilisearch.js";
const ev = new Xev(); const ev = new Xev();
@ -20,6 +21,10 @@ export default function () {
ev.emit(`serverStatsLog:${x.id}`, log.slice(0, x.length || 50)); ev.emit(`serverStatsLog:${x.id}`, log.slice(0, x.length || 50));
}); });
fetchMeta().then((meta) => {
if (!meta.enableServerMachineStats) return;
});
async function tick() { async function tick() {
const cpu = await cpuUsage(); const cpu = await cpuUsage();
const memStats = await mem(); const memStats = await mem();

View File

@ -2,15 +2,19 @@ import Redis from "ioredis";
import config from "@/config/index.js"; import config from "@/config/index.js";
export function createConnection() { export function createConnection() {
let source = config.redis;
if (config.cacheServer) {
source = config.cacheServer;
}
return new Redis({ return new Redis({
port: config.redis.port, port: source.port,
host: config.redis.host, host: source.host,
family: config.redis.family ?? 0, family: source.family ?? 0,
password: config.redis.pass, password: source.pass,
username: config.redis.user ?? "default", username: source.user ?? "default",
keyPrefix: `${config.redis.prefix}:`, keyPrefix: `${source.prefix}:`,
db: config.redis.db || 0, db: source.db || 0,
tls: config.redis.tls, tls: source.tls,
}); });
} }

View File

@ -4,7 +4,7 @@ export type Acct = {
}; };
export function parse(acct: string): Acct { export function parse(acct: string): Acct {
if (acct.startsWith("@")) acct = acct.substr(1); if (acct.startsWith("@")) acct = acct.slice(1);
const split = acct.split("@", 2); const split = acct.split("@", 2);
return { username: split[0], host: split[1] || null }; return { username: split[0], host: split[1] || null };
} }

View File

@ -1,43 +1,85 @@
import { redisClient } from "@/db/redis.js";
import { encode, decode } from "msgpackr";
import { ChainableCommander } from "ioredis";
export class Cache<T> { export class Cache<T> {
public cache: Map<string | null, { date: number; value: T }>; private ttl: number;
private lifetime: number; private prefix: string;
constructor(lifetime: Cache<never>["lifetime"]) { constructor(name: string, ttlSeconds: number) {
this.cache = new Map(); this.ttl = ttlSeconds;
this.lifetime = lifetime; this.prefix = `cache:${name}`;
} }
public set(key: string | null, value: T): void { private prefixedKey(key: string | null): string {
this.cache.set(key, { return key ? `${this.prefix}:${key}` : this.prefix;
date: Date.now(),
value,
});
} }
public get(key: string | null): T | undefined { public async set(
const cached = this.cache.get(key); key: string | null,
if (cached == null) return undefined; value: T,
if (Date.now() - cached.date > this.lifetime) { transaction?: ChainableCommander,
this.cache.delete(key); ): Promise<void> {
return undefined; const _key = this.prefixedKey(key);
const _value = Buffer.from(encode(value));
const commander = transaction ?? redisClient;
await commander.set(_key, _value, "EX", this.ttl);
}
public async get(key: string | null, renew = false): Promise<T | undefined> {
const _key = this.prefixedKey(key);
const cached = await redisClient.getBuffer(_key);
if (cached === null) return undefined;
if (renew) await redisClient.expire(_key, this.ttl);
return decode(cached) as T;
}
public async getAll(renew = false): Promise<Map<string, T>> {
const keys = await redisClient.keys(`${this.prefix}*`);
const map = new Map<string, T>();
if (keys.length === 0) {
return map;
} }
return cached.value; const values = await redisClient.mgetBuffer(keys);
for (const [i, key] of keys.entries()) {
const val = values[i];
if (val !== null) {
map.set(key, decode(val) as T);
}
}
if (renew) {
const trans = redisClient.multi();
for (const key of map.keys()) {
trans.expire(key, this.ttl);
}
await trans.exec();
}
return map;
} }
public delete(key: string | null) { public async delete(...keys: (string | null)[]): Promise<void> {
this.cache.delete(key); if (keys.length > 0) {
const _keys = keys.map(this.prefixedKey);
await redisClient.del(_keys);
}
} }
/** /**
* fetcherを呼び出して結果をキャッシュ& * Returns if cached value exists. Otherwise, calls fetcher and caches.
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします * Overwrites cached value if invalidated by the optional validator.
*/ */
public async fetch( public async fetch(
key: string | null, key: string | null,
fetcher: () => Promise<T>, fetcher: () => Promise<T>,
renew = false,
validator?: (cachedValue: T) => boolean, validator?: (cachedValue: T) => boolean,
): Promise<T> { ): Promise<T> {
const cachedValue = this.get(key); const cachedValue = await this.get(key, renew);
if (cachedValue !== undefined) { if (cachedValue !== undefined) {
if (validator) { if (validator) {
if (validator(cachedValue)) { if (validator(cachedValue)) {
@ -52,20 +94,21 @@ export class Cache<T> {
// Cache MISS // Cache MISS
const value = await fetcher(); const value = await fetcher();
this.set(key, value); await this.set(key, value);
return value; return value;
} }
/** /**
* fetcherを呼び出して結果をキャッシュ& * Returns if cached value exists. Otherwise, calls fetcher and caches if the fetcher returns a value.
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします * Overwrites cached value if invalidated by the optional validator.
*/ */
public async fetchMaybe( public async fetchMaybe(
key: string | null, key: string | null,
fetcher: () => Promise<T | undefined>, fetcher: () => Promise<T | undefined>,
renew = false,
validator?: (cachedValue: T) => boolean, validator?: (cachedValue: T) => boolean,
): Promise<T | undefined> { ): Promise<T | undefined> {
const cachedValue = this.get(key); const cachedValue = await this.get(key, renew);
if (cachedValue !== undefined) { if (cachedValue !== undefined) {
if (validator) { if (validator) {
if (validator(cachedValue)) { if (validator(cachedValue)) {
@ -81,7 +124,7 @@ export class Cache<T> {
// Cache MISS // Cache MISS
const value = await fetcher(); const value = await fetcher();
if (value !== undefined) { if (value !== undefined) {
this.set(key, value); await this.set(key, value);
} }
return value; return value;
} }

View File

@ -11,7 +11,7 @@ import * as Acct from "@/misc/acct.js";
import type { Packed } from "./schema.js"; import type { Packed } from "./schema.js";
import { Cache } from "./cache.js"; import { Cache } from "./cache.js";
const blockingCache = new Cache<User["id"][]>(1000 * 60 * 5); const blockingCache = new Cache<User["id"][]>("blocking", 60 * 5);
// NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている // NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている

View File

@ -1,33 +1,41 @@
import probeImageSize from "probe-image-size"; import probeImageSize from "probe-image-size";
import { Mutex, withTimeout } from "async-mutex"; import { Mutex } from "redis-semaphore";
import { FILE_TYPE_BROWSERSAFE } from "@/const.js"; import { FILE_TYPE_BROWSERSAFE } from "@/const.js";
import Logger from "@/services/logger.js"; import Logger from "@/services/logger.js";
import { Cache } from "./cache.js"; import { Cache } from "./cache.js";
import { redisClient } from "@/db/redis.js";
export type Size = { export type Size = {
width: number; width: number;
height: number; height: number;
}; };
const cache = new Cache<boolean>(1000 * 60 * 10); // once every 10 minutes for the same url const cache = new Cache<boolean>("emojiMeta", 60 * 10); // once every 10 minutes for the same url
const mutex = withTimeout(new Mutex(), 1000); const logger = new Logger("emoji");
export async function getEmojiSize(url: string): Promise<Size> { export async function getEmojiSize(url: string): Promise<Size> {
const logger = new Logger("emoji"); let attempted = true;
await mutex.runExclusive(() => { const lock = new Mutex(redisClient, "getEmojiSize");
const attempted = cache.get(url); await lock.acquire();
if (!attempted) {
cache.set(url, true);
} else {
logger.warn(`Attempt limit exceeded: ${url}`);
throw new Error("Too many attempts");
}
});
try { try {
logger.info(`Retrieving emoji size from ${url}`); attempted = (await cache.get(url)) === true;
if (!attempted) {
await cache.set(url, true);
}
} finally {
await lock.release();
}
if (attempted) {
logger.warn(`Attempt limit exceeded: ${url}`);
throw new Error("Too many attempts");
}
try {
logger.debug(`Retrieving emoji size from ${url}`);
const { width, height, mime } = await probeImageSize(url, { const { width, height, mime } = await probeImageSize(url, {
timeout: 5000, timeout: 5000,
}); });

View File

@ -3,8 +3,9 @@
/** /**
* Maximum note text length that can be stored in DB. * Maximum note text length that can be stored in DB.
* Surrogate pairs count as one * Surrogate pairs count as one
* DEPRECARTED: use const/MAX_NOTE_TEXT_LENGTH instead
*/ */
export const DB_MAX_NOTE_TEXT_LENGTH = 8192; // export const DB_MAX_NOTE_TEXT_LENGTH = 8192;
/** /**
* Maximum image description length that can be stored in DB. * Maximum image description length that can be stored in DB.

View File

@ -3,10 +3,12 @@ import type { User } from "@/models/entities/user.js";
import type { UserKeypair } from "@/models/entities/user-keypair.js"; import type { UserKeypair } from "@/models/entities/user-keypair.js";
import { Cache } from "./cache.js"; import { Cache } from "./cache.js";
const cache = new Cache<UserKeypair>(Infinity); const cache = new Cache<UserKeypair>("keypairStore", 60 * 30);
export async function getUserKeypair(userId: User["id"]): Promise<UserKeypair> { export async function getUserKeypair(userId: User["id"]): Promise<UserKeypair> {
return await cache.fetch(userId, () => return await cache.fetch(
UserKeypairs.findOneByOrFail({ userId: userId }), userId,
() => UserKeypairs.findOneByOrFail({ userId: userId }),
true,
); );
} }

View File

@ -20,5 +20,9 @@ export function nyaize(text: string): string {
) )
.replace(/(다$)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/gm, "다냥") .replace(/(다$)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/gm, "다냥")
.replace(/(야(?=\?))|(야$)|(야(?= ))/gm, "냥") .replace(/(야(?=\?))|(야$)|(야(?= ))/gm, "냥")
// el-GR
.replaceAll("να", "νια")
.replaceAll("ΝΑ", "ΝΙΑ")
.replaceAll("Να", "Νια")
); );
} }

View File

@ -7,8 +7,9 @@ import { isSelfHost, toPunyNullable } from "./convert-host.js";
import { decodeReaction } from "./reaction-lib.js"; import { decodeReaction } from "./reaction-lib.js";
import config from "@/config/index.js"; import config from "@/config/index.js";
import { query } from "@/prelude/url.js"; import { query } from "@/prelude/url.js";
import { redisClient } from "@/db/redis.js";
const cache = new Cache<Emoji | null>(1000 * 60 * 60 * 12); const cache = new Cache<Emoji | null>("populateEmojis", 60 * 60 * 12);
/** /**
* *
@ -75,7 +76,7 @@ export async function populateEmoji(
if (emoji && !(emoji.width && emoji.height)) { if (emoji && !(emoji.width && emoji.height)) {
emoji = await queryOrNull(); emoji = await queryOrNull();
cache.set(cacheKey, emoji); await cache.set(cacheKey, emoji);
} }
if (emoji == null) return null; if (emoji == null) return null;
@ -150,7 +151,7 @@ export async function prefetchEmojis(
emojis: { name: string; host: string | null }[], emojis: { name: string; host: string | null }[],
): Promise<void> { ): Promise<void> {
const notCachedEmojis = emojis.filter( const notCachedEmojis = emojis.filter(
(emoji) => cache.get(`${emoji.name} ${emoji.host}`) == null, async (emoji) => !(await cache.get(`${emoji.name} ${emoji.host}`)),
); );
const emojisQuery: any[] = []; const emojisQuery: any[] = [];
const hosts = new Set(notCachedEmojis.map((e) => e.host)); const hosts = new Set(notCachedEmojis.map((e) => e.host));
@ -169,7 +170,9 @@ export async function prefetchEmojis(
select: ["name", "host", "originalUrl", "publicUrl"], select: ["name", "host", "originalUrl", "publicUrl"],
}) })
: []; : [];
const trans = redisClient.multi();
for (const emoji of _emojis) { for (const emoji of _emojis) {
cache.set(`${emoji.name} ${emoji.host}`, emoji); cache.set(`${emoji.name} ${emoji.host}`, emoji, trans);
} }
await trans.exec();
} }

View File

@ -36,6 +36,16 @@ export class Announcement {
}) })
public imageUrl: string | null; public imageUrl: string | null;
@Column("boolean", {
default: false,
})
public showPopup: boolean;
@Column("boolean", {
default: false,
})
public isGoodNews: boolean;
constructor(data: Partial<Announcement>) { constructor(data: Partial<Announcement>) {
if (data == null) return; if (data == null) return;

View File

@ -326,13 +326,13 @@ export class Meta {
public smtpPort: number | null; public smtpPort: number | null;
@Column("varchar", { @Column("varchar", {
length: 128, length: 1024,
nullable: true, nullable: true,
}) })
public smtpUser: string | null; public smtpUser: string | null;
@Column("varchar", { @Column("varchar", {
length: 128, length: 1024,
nullable: true, nullable: true,
}) })
public smtpPass: string | null; public smtpPass: string | null;
@ -546,4 +546,20 @@ export class Meta {
default: {}, default: {},
}) })
public experimentalFeatures: Record<string, unknown>; public experimentalFeatures: Record<string, unknown>;
@Column("boolean", {
default: false,
})
public enableServerMachineStats: boolean;
@Column("boolean", {
default: true,
})
public enableIdenticonGeneration: boolean;
@Column("varchar", {
length: 256,
nullable: true,
})
public donationLink: string | null;
} }

View File

@ -1,4 +1,3 @@
import { URL } from "url";
import { In, Not } from "typeorm"; import { In, Not } from "typeorm";
import Ajv from "ajv"; import Ajv from "ajv";
import type { ILocalUser, IRemoteUser } from "@/models/entities/user.js"; import type { ILocalUser, IRemoteUser } from "@/models/entities/user.js";
@ -40,7 +39,10 @@ import {
} from "../index.js"; } from "../index.js";
import type { Instance } from "../entities/instance.js"; import type { Instance } from "../entities/instance.js";
const userInstanceCache = new Cache<Instance | null>(1000 * 60 * 60 * 3); const userInstanceCache = new Cache<Instance | null>(
"userInstance",
60 * 60 * 3,
);
type IsUserDetailed<Detailed extends boolean> = Detailed extends true type IsUserDetailed<Detailed extends boolean> = Detailed extends true
? Packed<"UserDetailed"> ? Packed<"UserDetailed">
@ -451,6 +453,7 @@ export const UserRepository = db.getRepository(User).extend({
isAdmin: user.isAdmin || falsy, isAdmin: user.isAdmin || falsy,
isModerator: user.isModerator || falsy, isModerator: user.isModerator || falsy,
isBot: user.isBot || falsy, isBot: user.isBot || falsy,
isLocked: user.isLocked,
isCat: user.isCat || falsy, isCat: user.isCat || falsy,
speakAsCat: user.speakAsCat || falsy, speakAsCat: user.speakAsCat || falsy,
instance: user.host instance: user.host
@ -495,7 +498,6 @@ export const UserRepository = db.getRepository(User).extend({
: null, : null,
bannerBlurhash: user.banner?.blurhash || null, bannerBlurhash: user.banner?.blurhash || null,
bannerColor: null, // 後方互換性のため bannerColor: null, // 後方互換性のため
isLocked: user.isLocked,
isSilenced: user.isSilenced || falsy, isSilenced: user.isSilenced || falsy,
isSuspended: user.isSuspended || falsy, isSuspended: user.isSuspended || falsy,
description: profile!.description, description: profile!.description,

View File

@ -1,4 +1,5 @@
import type Bull from "bull"; import type Bull from "bull";
import type { DoneCallback } from "bull";
import { queueLogger } from "../../logger.js"; import { queueLogger } from "../../logger.js";
import { Notes } from "@/models/index.js"; import { Notes } from "@/models/index.js";
@ -11,7 +12,7 @@ const logger = queueLogger.createSubLogger("index-all-notes");
export default async function indexAllNotes( export default async function indexAllNotes(
job: Bull.Job<Record<string, unknown>>, job: Bull.Job<Record<string, unknown>>,
done: () => void, done: DoneCallback,
): Promise<void> { ): Promise<void> {
logger.info("Indexing all notes..."); logger.info("Indexing all notes...");
@ -20,7 +21,7 @@ export default async function indexAllNotes(
let total: number = (job.data.total as number) ?? 0; let total: number = (job.data.total as number) ?? 0;
let running = true; let running = true;
const take = 100000; const take = 10000;
const batch = 100; const batch = 100;
while (running) { while (running) {
logger.info( logger.info(
@ -41,13 +42,14 @@ export default async function indexAllNotes(
}, },
relations: ["user"], relations: ["user"],
}); });
} catch (e) { } catch (e: any) {
logger.error(`Failed to query notes ${e}`); logger.error(`Failed to query notes ${e}`);
continue; done(e);
break;
} }
if (notes.length === 0) { if (notes.length === 0) {
job.progress(100); await job.progress(100);
running = false; running = false;
break; break;
} }
@ -55,7 +57,7 @@ export default async function indexAllNotes(
try { try {
const count = await Notes.count(); const count = await Notes.count();
total = count; total = count;
job.update({ indexedCount, cursor, total }); await job.update({ indexedCount, cursor, total });
} catch (e) {} } catch (e) {}
for (let i = 0; i < notes.length; i += batch) { for (let i = 0; i < notes.length; i += batch) {
@ -69,12 +71,12 @@ export default async function indexAllNotes(
indexedCount += chunk.length; indexedCount += chunk.length;
const pct = (indexedCount / total) * 100; const pct = (indexedCount / total) * 100;
job.update({ indexedCount, cursor, total }); await job.update({ indexedCount, cursor, total });
job.progress(+pct.toFixed(1)); await job.progress(+pct.toFixed(1));
logger.info(`Indexed notes ${indexedCount}/${total ? total : "?"}`); logger.info(`Indexed notes ${indexedCount}/${total ? total : "?"}`);
} }
cursor = notes[notes.length - 1].id; cursor = notes[notes.length - 1].id;
job.update({ indexedCount, cursor, total }); await job.update({ indexedCount, cursor, total });
if (notes.length < take) { if (notes.length < take) {
running = false; running = false;

View File

@ -50,7 +50,7 @@ export async function importMastoPost(
text: text || undefined, text: text || undefined,
reply, reply,
renote: null, renote: null,
cw: post.sensitive, cw: post.object.sensitive ? post.object.summary : undefined,
localOnly: false, localOnly: false,
visibility: "hidden", visibility: "hidden",
visibleUsers: [], visibleUsers: [],

View File

@ -33,7 +33,7 @@ export async function endedPollNotification(
} }
// Broadcast the poll result once it ends // Broadcast the poll result once it ends
await deliverQuestionUpdate(note.id); if (!note.localOnly) await deliverQuestionUpdate(note.id);
done(); done();
} }

View File

@ -35,8 +35,11 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
info["@context"] = undefined; info["@context"] = undefined;
logger.debug(JSON.stringify(info, null, 2)); logger.debug(JSON.stringify(info, null, 2));
if (!signature?.keyId) return `Invalid signature: ${signature}`; if (!signature?.keyId) {
const err = `Invalid signature: ${signature}`;
job.moveToFailed({ message: err });
return err;
}
//#endregion //#endregion
const host = toPuny(new URL(signature.keyId).hostname); const host = toPuny(new URL(signature.keyId).hostname);

View File

@ -5,7 +5,6 @@ import type {
CacheableRemoteUser, CacheableRemoteUser,
CacheableUser, CacheableUser,
} from "@/models/entities/user.js"; } from "@/models/entities/user.js";
import { User, IRemoteUser } from "@/models/entities/user.js";
import type { UserPublickey } from "@/models/entities/user-publickey.js"; import type { UserPublickey } from "@/models/entities/user-publickey.js";
import type { MessagingMessage } from "@/models/entities/messaging-message.js"; import type { MessagingMessage } from "@/models/entities/messaging-message.js";
import { import {
@ -20,8 +19,11 @@ import type { IObject } from "./type.js";
import { getApId } from "./type.js"; import { getApId } from "./type.js";
import { resolvePerson } from "./models/person.js"; import { resolvePerson } from "./models/person.js";
const publicKeyCache = new Cache<UserPublickey | null>(Infinity); const publicKeyCache = new Cache<UserPublickey | null>("publicKey", 60 * 30);
const publicKeyByUserIdCache = new Cache<UserPublickey | null>(Infinity); const publicKeyByUserIdCache = new Cache<UserPublickey | null>(
"publicKeyByUserId",
60 * 30,
);
export type UriParseResult = export type UriParseResult =
| { | {
@ -123,17 +125,23 @@ export default class DbResolver {
if (parsed.type !== "users") return null; if (parsed.type !== "users") return null;
return ( return (
(await userByIdCache.fetchMaybe(parsed.id, () => (await userByIdCache.fetchMaybe(
Users.findOneBy({ parsed.id,
id: parsed.id, () =>
}).then((x) => x ?? undefined), Users.findOneBy({
id: parsed.id,
}).then((x) => x ?? undefined),
true,
)) ?? null )) ?? null
); );
} else { } else {
return await uriPersonCache.fetch(parsed.uri, () => return await uriPersonCache.fetch(
Users.findOneBy({ parsed.uri,
uri: parsed.uri, () =>
}), Users.findOneBy({
uri: parsed.uri,
}),
true,
); );
} }
} }
@ -156,14 +164,17 @@ export default class DbResolver {
return key; return key;
}, },
true,
(key) => key != null, (key) => key != null,
); );
if (key == null) return null; if (key == null) return null;
return { return {
user: (await userByIdCache.fetch(key.userId, () => user: (await userByIdCache.fetch(
Users.findOneByOrFail({ id: key.userId }), key.userId,
() => Users.findOneByOrFail({ id: key.userId }),
true,
)) as CacheableRemoteUser, )) as CacheableRemoteUser,
key, key,
}; };
@ -183,6 +194,7 @@ export default class DbResolver {
const key = await publicKeyByUserIdCache.fetch( const key = await publicKeyByUserIdCache.fetch(
user.id, user.id,
() => UserPublickeys.findOneBy({ userId: user.id }), () => UserPublickeys.findOneBy({ userId: user.id }),
true,
(v) => v != null, (v) => v != null,
); );

View File

@ -135,23 +135,23 @@ export async function fetchPerson(
): Promise<CacheableUser | null> { ): Promise<CacheableUser | null> {
if (typeof uri !== "string") throw new Error("uri is not string"); if (typeof uri !== "string") throw new Error("uri is not string");
const cached = uriPersonCache.get(uri); const cached = await uriPersonCache.get(uri, true);
if (cached) return cached; if (cached) return cached;
// Fetch from the database if the URI points to this server // Fetch from the database if the URI points to this server
if (uri.startsWith(`${config.url}/`)) { if (uri.startsWith(`${config.url}/`)) {
const id = uri.split("/").pop(); const id = uri.split("/").pop();
const u = await Users.findOneBy({ id }); const u = await Users.findOneBy({ id });
if (u) uriPersonCache.set(uri, u); if (u) await uriPersonCache.set(uri, u);
return u; return u;
} }
//#region Returns if already registered with this server //#region Returns if already registered with this server
const exist = await Users.findOneBy({ uri }); const user = await Users.findOneBy({ uri });
if (exist) { if (user != null) {
uriPersonCache.set(uri, exist); await uriPersonCache.set(uri, user);
return exist; return user;
} }
//#endregion //#endregion
@ -396,9 +396,9 @@ export async function updatePerson(
} }
//#region Already registered on this server? //#region Already registered on this server?
const exist = (await Users.findOneBy({ uri })) as IRemoteUser; const user = (await Users.findOneBy({ uri })) as IRemoteUser;
if (exist == null) { if (user == null) {
return; return;
} }
//#endregion //#endregion
@ -416,17 +416,15 @@ export async function updatePerson(
[person.icon, person.image].map((img) => [person.icon, person.image].map((img) =>
img == null img == null
? Promise.resolve(null) ? Promise.resolve(null)
: resolveImage(exist, img).catch(() => null), : resolveImage(user, img).catch(() => null),
), ),
); );
// Custom pictogram acquisition // Custom pictogram acquisition
const emojis = await extractEmojis(person.tag || [], exist.host).catch( const emojis = await extractEmojis(person.tag || [], user.host).catch((e) => {
(e) => { logger.info(`extractEmojis: ${e}`);
logger.info(`extractEmojis: ${e}`); return [] as Emoji[];
return [] as Emoji[]; });
},
);
const emojiNames = emojis.map((emoji) => emoji.name); const emojiNames = emojis.map((emoji) => emoji.name);
@ -518,11 +516,11 @@ export async function updatePerson(
} }
// Update user // Update user
await Users.update(exist.id, updates); await Users.update(user.id, updates);
if (person.publicKey) { if (person.publicKey) {
await UserPublickeys.update( await UserPublickeys.update(
{ userId: exist.id }, { userId: user.id },
{ {
keyId: person.publicKey.id, keyId: person.publicKey.id,
keyPem: person.publicKey.publicKeyPem, keyPem: person.publicKey.publicKeyPem,
@ -531,7 +529,7 @@ export async function updatePerson(
} }
await UserProfiles.update( await UserProfiles.update(
{ userId: exist.id }, { userId: user.id },
{ {
url: url, url: url,
fields, fields,
@ -543,15 +541,15 @@ export async function updatePerson(
}, },
); );
publishInternalEvent("remoteUserUpdated", { id: exist.id }); publishInternalEvent("remoteUserUpdated", { id: user.id });
// Hashtag Update // Hashtag Update
updateUsertags(exist, tags); updateUsertags(user, tags);
// If the user in question is a follower, followers will also be updated. // If the user in question is a follower, followers will also be updated.
await Followings.update( await Followings.update(
{ {
followerId: exist.id, followerId: user.id,
}, },
{ {
followerSharedInbox: followerSharedInbox:
@ -560,7 +558,7 @@ export async function updatePerson(
}, },
); );
await updateFeatured(exist.id, resolver).catch((err) => logger.error(err)); await updateFeatured(user.id, resolver).catch((err) => logger.error(err));
} }
/** /**
@ -576,10 +574,10 @@ export async function resolvePerson(
if (typeof uri !== "string") throw new Error("uri is not string"); if (typeof uri !== "string") throw new Error("uri is not string");
//#region If already registered on this server, return it. //#region If already registered on this server, return it.
const exist = await fetchPerson(uri); const user = await fetchPerson(uri);
if (exist) { if (user != null) {
return exist; return user;
} }
//#endregion //#endregion

View File

@ -9,7 +9,7 @@ import {
localUserByNativeTokenCache, localUserByNativeTokenCache,
} from "@/services/user-cache.js"; } from "@/services/user-cache.js";
const appCache = new Cache<App>(Infinity); const appCache = new Cache<App>("app", 60 * 30);
export class AuthenticationError extends Error { export class AuthenticationError extends Error {
constructor(message: string) { constructor(message: string) {
@ -49,6 +49,7 @@ export default async (
const user = await localUserByNativeTokenCache.fetch( const user = await localUserByNativeTokenCache.fetch(
token, token,
() => Users.findOneBy({ token }) as Promise<ILocalUser | null>, () => Users.findOneBy({ token }) as Promise<ILocalUser | null>,
true,
); );
if (user == null) { if (user == null) {
@ -82,11 +83,14 @@ export default async (
Users.findOneBy({ Users.findOneBy({
id: accessToken.userId, id: accessToken.userId,
}) as Promise<ILocalUser>, }) as Promise<ILocalUser>,
true,
); );
if (accessToken.appId) { if (accessToken.appId) {
const app = await appCache.fetch(accessToken.appId, () => const app = await appCache.fetch(
Apps.findOneByOrFail({ id: accessToken.appId! }), accessToken.appId,
() => Apps.findOneByOrFail({ id: accessToken.appId! }),
true,
); );
return [ return [

View File

@ -47,6 +47,16 @@ export const meta = {
optional: false, optional: false,
nullable: true, nullable: true,
}, },
showPopup: {
type: "boolean",
optional: true,
nullable: false,
},
isGoodNews: {
type: "boolean",
optional: true,
nullable: false,
},
}, },
}, },
} as const; } as const;
@ -57,6 +67,8 @@ export const paramDef = {
title: { type: "string", minLength: 1 }, title: { type: "string", minLength: 1 },
text: { type: "string", minLength: 1 }, text: { type: "string", minLength: 1 },
imageUrl: { type: "string", nullable: true, minLength: 1 }, imageUrl: { type: "string", nullable: true, minLength: 1 },
showPopup: { type: "boolean" },
isGoodNews: { type: "boolean" },
}, },
required: ["title", "text", "imageUrl"], required: ["title", "text", "imageUrl"],
} as const; } as const;
@ -69,6 +81,8 @@ export default define(meta, paramDef, async (ps) => {
title: ps.title, title: ps.title,
text: ps.text, text: ps.text,
imageUrl: ps.imageUrl, imageUrl: ps.imageUrl,
showPopup: ps.showPopup ?? false,
isGoodNews: ps.isGoodNews ?? false,
}).then((x) => Announcements.findOneByOrFail(x.identifiers[0])); }).then((x) => Announcements.findOneByOrFail(x.identifiers[0]));
return Object.assign({}, announcement, { return Object.assign({}, announcement, {

View File

@ -57,6 +57,16 @@ export const meta = {
optional: false, optional: false,
nullable: false, nullable: false,
}, },
showPopup: {
type: "boolean",
optional: true,
nullable: false,
},
isGoodNews: {
type: "boolean",
optional: true,
nullable: false,
},
}, },
}, },
}, },
@ -100,5 +110,7 @@ export default define(meta, paramDef, async (ps) => {
text: announcement.text, text: announcement.text,
imageUrl: announcement.imageUrl, imageUrl: announcement.imageUrl,
reads: reads.get(announcement)!, reads: reads.get(announcement)!,
showPopup: announcement.showPopup,
isGoodNews: announcement.isGoodNews,
})); }));
}); });

View File

@ -24,6 +24,8 @@ export const paramDef = {
title: { type: "string", minLength: 1 }, title: { type: "string", minLength: 1 },
text: { type: "string", minLength: 1 }, text: { type: "string", minLength: 1 },
imageUrl: { type: "string", nullable: true, minLength: 1 }, imageUrl: { type: "string", nullable: true, minLength: 1 },
showPopup: { type: "boolean" },
isGoodNews: { type: "boolean" },
}, },
required: ["id", "title", "text", "imageUrl"], required: ["id", "title", "text", "imageUrl"],
} as const; } as const;
@ -38,5 +40,7 @@ export default define(meta, paramDef, async (ps, me) => {
title: ps.title, title: ps.title,
text: ps.text, text: ps.text,
imageUrl: ps.imageUrl, imageUrl: ps.imageUrl,
showPopup: ps.showPopup ?? false,
isGoodNews: ps.isGoodNews ?? false,
}); });
}); });

View File

@ -6,7 +6,7 @@ import { ApiError } from "../../../error.js";
import rndstr from "rndstr"; import rndstr from "rndstr";
import { publishBroadcastStream } from "@/services/stream.js"; import { publishBroadcastStream } from "@/services/stream.js";
import { db } from "@/db/postgre.js"; import { db } from "@/db/postgre.js";
import { type Size, getEmojiSize } from "@/misc/emoji-meta.js"; import { getEmojiSize } from "@/misc/emoji-meta.js";
export const meta = { export const meta = {
tags: ["admin"], tags: ["admin"],
@ -40,12 +40,7 @@ export default define(meta, paramDef, async (ps, me) => {
? file.name.split(".")[0] ? file.name.split(".")[0]
: `_${rndstr("a-z0-9", 8)}_`; : `_${rndstr("a-z0-9", 8)}_`;
let size: Size = { width: 0, height: 0 }; const size = await getEmojiSize(file.url);
try {
size = await getEmojiSize(file.url);
} catch {
/* skip if any error happens */
}
const emoji = await Emojis.insert({ const emoji = await Emojis.insert({
id: genId(), id: genId(),

View File

@ -6,7 +6,7 @@ import type { DriveFile } from "@/models/entities/drive-file.js";
import { uploadFromUrl } from "@/services/drive/upload-from-url.js"; import { uploadFromUrl } from "@/services/drive/upload-from-url.js";
import { publishBroadcastStream } from "@/services/stream.js"; import { publishBroadcastStream } from "@/services/stream.js";
import { db } from "@/db/postgre.js"; import { db } from "@/db/postgre.js";
import { type Size, getEmojiSize } from "@/misc/emoji-meta.js"; import { getEmojiSize } from "@/misc/emoji-meta.js";
export const meta = { export const meta = {
tags: ["admin"], tags: ["admin"],
@ -65,12 +65,7 @@ export default define(meta, paramDef, async (ps, me) => {
throw new ApiError(); throw new ApiError();
} }
let size: Size = { width: 0, height: 0 }; const size = await getEmojiSize(driveFile.url);
try {
size = await getEmojiSize(driveFile.url);
} catch {
/* skip if any error happens */
}
const copied = await Emojis.insert({ const copied = await Emojis.insert({
id: genId(), id: genId(),

View File

@ -481,6 +481,21 @@ export const meta = {
}, },
}, },
}, },
enableServerMachineStats: {
type: "boolean",
optional: false,
nullable: false,
},
enableIdenticonGeneration: {
type: "boolean",
optional: false,
nullable: false,
},
donationLink: {
type: "string",
optional: true,
nullable: true,
},
}, },
}, },
} as const; } as const;
@ -592,5 +607,8 @@ export default define(meta, paramDef, async (ps, me) => {
enableIpLogging: instance.enableIpLogging, enableIpLogging: instance.enableIpLogging,
enableActiveEmailValidation: instance.enableActiveEmailValidation, enableActiveEmailValidation: instance.enableActiveEmailValidation,
experimentalFeatures: instance.experimentalFeatures, experimentalFeatures: instance.experimentalFeatures,
enableServerMachineStats: instance.enableServerMachineStats,
enableIdenticonGeneration: instance.enableIdenticonGeneration,
donationLink: instance.donationLink,
}; };
}); });

View File

@ -40,9 +40,9 @@ export default define(meta, paramDef, async (ps, user) => {
throw err; throw err;
}); });
const exist = await PromoNotes.findOneBy({ noteId: note.id }); const exist = await PromoNotes.exist({ where: { noteId: note.id } });
if (exist != null) { if (exist) {
throw new ApiError(meta.errors.alreadyPromoted); throw new ApiError(meta.errors.alreadyPromoted);
} }

View File

@ -1,6 +1,5 @@
import { Meta } from "@/models/entities/meta.js"; import { Meta } from "@/models/entities/meta.js";
import { insertModerationLog } from "@/services/insert-moderation-log.js"; import { insertModerationLog } from "@/services/insert-moderation-log.js";
import { DB_MAX_NOTE_TEXT_LENGTH } from "@/misc/hard-limits.js";
import { db } from "@/db/postgre.js"; import { db } from "@/db/postgre.js";
import define from "../../define.js"; import define from "../../define.js";
@ -177,6 +176,9 @@ export const paramDef = {
postImports: { type: "boolean" }, postImports: { type: "boolean" },
}, },
}, },
enableServerMachineStats: { type: "boolean" },
enableIdenticonGeneration: { type: "boolean" },
donationLink: { type: "string", nullable: true },
}, },
required: [], required: [],
} as const; } as const;
@ -218,6 +220,15 @@ export default define(meta, paramDef, async (ps, me) => {
if (Array.isArray(ps.recommendedInstances)) { if (Array.isArray(ps.recommendedInstances)) {
set.recommendedInstances = ps.recommendedInstances.filter(Boolean); set.recommendedInstances = ps.recommendedInstances.filter(Boolean);
if (set.recommendedInstances?.length > 0) {
set.recommendedInstances.forEach((instance, index) => {
if (/^https?:\/\//i.test(instance)) {
set.recommendedInstances![index] = instance
.replace(/^https?:\/\//i, "")
.replace(/\/$/, "");
}
});
}
} }
if (Array.isArray(ps.hiddenTags)) { if (Array.isArray(ps.hiddenTags)) {
@ -568,6 +579,21 @@ export default define(meta, paramDef, async (ps, me) => {
set.experimentalFeatures = ps.experimentalFeatures || undefined; set.experimentalFeatures = ps.experimentalFeatures || undefined;
} }
if (ps.enableServerMachineStats !== undefined) {
set.enableServerMachineStats = ps.enableServerMachineStats;
}
if (ps.enableIdenticonGeneration !== undefined) {
set.enableIdenticonGeneration = ps.enableIdenticonGeneration;
}
if (ps.donationLink !== undefined) {
set.donationLink = ps.donationLink;
if (set.donationLink && !/^https?:\/\//i.test(set.donationLink)) {
set.donationLink = `https://${set.donationLink}`;
}
}
await db.transaction(async (transactionalEntityManager) => { await db.transaction(async (transactionalEntityManager) => {
const metas = await transactionalEntityManager.find(Meta, { const metas = await transactionalEntityManager.find(Meta, {
order: { order: {

View File

@ -56,6 +56,16 @@ export const meta = {
optional: true, optional: true,
nullable: false, nullable: false,
}, },
showPopup: {
type: "boolean",
optional: false,
nullable: false,
},
isGoodNews: {
type: "boolean",
optional: false,
nullable: false,
},
}, },
}, },
}, },

View File

@ -29,6 +29,11 @@ export const meta = {
code: "TOO_MANY_ANTENNAS", code: "TOO_MANY_ANTENNAS",
id: "c3a5a51e-04d4-11ee-be56-0242ac120002", id: "c3a5a51e-04d4-11ee-be56-0242ac120002",
}, },
noKeywords: {
message: "No keywords",
code: "NO_KEYWORDS",
id: "aa975b74-1ddb-11ee-be56-0242ac120002",
},
}, },
res: { res: {
@ -100,6 +105,7 @@ export const paramDef = {
export default define(meta, paramDef, async (ps, user) => { export default define(meta, paramDef, async (ps, user) => {
if (user.movedToUri != null) throw new ApiError(meta.errors.noSuchUserGroup); if (user.movedToUri != null) throw new ApiError(meta.errors.noSuchUserGroup);
if (ps.keywords.length === 0) throw new ApiError(meta.errors.noKeywords);
let userList; let userList;
let userGroupJoining; let userGroupJoining;

View File

@ -10,7 +10,7 @@ import type { Note } from "@/models/entities/note.js";
import type { CacheableLocalUser, User } from "@/models/entities/user.js"; import type { CacheableLocalUser, User } from "@/models/entities/user.js";
import { isActor, isPost, getApId } from "@/remote/activitypub/type.js"; import { isActor, isPost, getApId } from "@/remote/activitypub/type.js";
import type { SchemaType } from "@/misc/schema.js"; import type { SchemaType } from "@/misc/schema.js";
import { HOUR } from "@/const.js"; import { MINUTE } from "@/const.js";
import { shouldBlockInstance } from "@/misc/should-block-instance.js"; import { shouldBlockInstance } from "@/misc/should-block-instance.js";
import { updateQuestion } from "@/remote/activitypub/models/question.js"; import { updateQuestion } from "@/remote/activitypub/models/question.js";
import { populatePoll } from "@/models/repositories/note.js"; import { populatePoll } from "@/models/repositories/note.js";
@ -22,8 +22,8 @@ export const meta = {
requireCredential: true, requireCredential: true,
limit: { limit: {
duration: HOUR, duration: MINUTE,
max: 30, max: 10,
}, },
errors: { errors: {

View File

@ -41,12 +41,14 @@ export default define(meta, paramDef, async (ps, user) => {
const accessToken = secureRndstr(32, true); const accessToken = secureRndstr(32, true);
// Fetch exist access token // Fetch exist access token
const exist = await AccessTokens.findOneBy({ const exist = await AccessTokens.exist({
appId: session.appId, where: {
userId: user.id, appId: session.appId,
userId: user.id,
},
}); });
if (exist == null) { if (!exist) {
// Lookup app // Lookup app
const app = await Apps.findOneByOrFail({ id: session.appId }); const app = await Apps.findOneByOrFail({ id: session.appId });

View File

@ -69,12 +69,14 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// Check if already blocking // Check if already blocking
const exist = await Blockings.findOneBy({ const exist = await Blockings.exist({
blockerId: blocker.id, where: {
blockeeId: blockee.id, blockerId: blocker.id,
blockeeId: blockee.id,
},
}); });
if (exist != null) { if (exist) {
throw new ApiError(meta.errors.alreadyBlocking); throw new ApiError(meta.errors.alreadyBlocking);
} }

View File

@ -69,12 +69,14 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// Check not blocking // Check not blocking
const exist = await Blockings.findOneBy({ const exist = await Blockings.exist({
blockerId: blocker.id, where: {
blockeeId: blockee.id, blockerId: blocker.id,
blockeeId: blockee.id,
},
}); });
if (exist == null) { if (!exist) {
throw new ApiError(meta.errors.notBlocking); throw new ApiError(meta.errors.notBlocking);
} }

View File

@ -57,12 +57,14 @@ export default define(meta, paramDef, async (ps, user) => {
throw err; throw err;
}); });
const exist = await ClipNotes.findOneBy({ const exist = await ClipNotes.exist({
noteId: note.id, where: {
clipId: clip.id, noteId: note.id,
clipId: clip.id,
},
}); });
if (exist != null) { if (exist) {
throw new ApiError(meta.errors.alreadyClipped); throw new ApiError(meta.errors.alreadyClipped);
} }

View File

@ -26,10 +26,12 @@ export const paramDef = {
} as const; } as const;
export default define(meta, paramDef, async (ps, user) => { export default define(meta, paramDef, async (ps, user) => {
const file = await DriveFiles.findOneBy({ const exist = await DriveFiles.exist({
md5: ps.md5, where: {
userId: user.id, md5: ps.md5,
userId: user.id,
},
}); });
return file != null; return exist;
}); });

View File

@ -82,12 +82,14 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// Check if already following // Check if already following
const exist = await Followings.findOneBy({ const exist = await Followings.exist({
followerId: follower.id, where: {
followeeId: followee.id, followerId: follower.id,
followeeId: followee.id,
},
}); });
if (exist != null) { if (exist) {
throw new ApiError(meta.errors.alreadyFollowing); throw new ApiError(meta.errors.alreadyFollowing);
} }

View File

@ -69,12 +69,14 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// Check not following // Check not following
const exist = await Followings.findOneBy({ const exist = await Followings.exist({
followerId: follower.id, where: {
followeeId: followee.id, followerId: follower.id,
followeeId: followee.id,
},
}); });
if (exist == null) { if (!exist) {
throw new ApiError(meta.errors.notFollowing); throw new ApiError(meta.errors.notFollowing);
} }

View File

@ -69,12 +69,14 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// Check not following // Check not following
const exist = await Followings.findOneBy({ const exist = await Followings.exist({
followerId: follower.id, where: {
followeeId: followee.id, followerId: follower.id,
followeeId: followee.id,
},
}); });
if (exist == null) { if (!exist) {
throw new ApiError(meta.errors.notFollowing); throw new ApiError(meta.errors.notFollowing);
} }

View File

@ -40,12 +40,14 @@ export default define(meta, paramDef, async (ps, user) => {
} }
// if already liked // if already liked
const exist = await GalleryLikes.findOneBy({ const exist = await GalleryLikes.exist({
postId: post.id, where: {
userId: user.id, postId: post.id,
userId: user.id,
},
}); });
if (exist != null) { if (exist) {
throw new ApiError(meta.errors.alreadyLiked); throw new ApiError(meta.errors.alreadyLiked);
} }

View File

@ -38,17 +38,17 @@ export default define(meta, paramDef, async (ps, user) => {
throw new ApiError(meta.errors.noSuchPost); throw new ApiError(meta.errors.noSuchPost);
} }
const exist = await GalleryLikes.findOneBy({ const like = await GalleryLikes.findOneBy({
postId: post.id, postId: post.id,
userId: user.id, userId: user.id,
}); });
if (exist == null) { if (like == null) {
throw new ApiError(meta.errors.notLiked); throw new ApiError(meta.errors.notLiked);
} }
// Delete like // Delete like
await GalleryLikes.delete(exist.id); await GalleryLikes.delete(like.id);
GalleryPosts.decrement({ id: post.id }, "likedCount", 1); GalleryPosts.decrement({ id: post.id }, "likedCount", 1);
}); });

View File

@ -30,19 +30,23 @@ export const paramDef = {
export default define(meta, paramDef, async (ps, user) => { export default define(meta, paramDef, async (ps, user) => {
// Check if announcement exists // Check if announcement exists
const announcement = await Announcements.findOneBy({ id: ps.announcementId }); const exist = await Announcements.exist({
where: { id: ps.announcementId },
});
if (announcement == null) { if (!exist) {
throw new ApiError(meta.errors.noSuchAnnouncement); throw new ApiError(meta.errors.noSuchAnnouncement);
} }
// Check if already read // Check if already read
const read = await AnnouncementReads.findOneBy({ const read = await AnnouncementReads.exist({
announcementId: ps.announcementId, where: {
userId: user.id, announcementId: ps.announcementId,
userId: user.id,
},
}); });
if (read != null) { if (read) {
return; return;
} }

View File

@ -33,7 +33,7 @@ export const paramDef = {
} as const; } as const;
export default define(meta, paramDef, async (ps, user) => { export default define(meta, paramDef, async (ps, user) => {
if (ps.key !== "reactions") return; if (ps.key !== "reactions" && ps.key !== "defaultNoteVisibility") return;
const query = RegistryItems.createQueryBuilder("item") const query = RegistryItems.createQueryBuilder("item")
.where("item.domain IS NULL") .where("item.domain IS NULL")
.andWhere("item.userId = :userId", { userId: user.id }) .andWhere("item.userId = :userId", { userId: user.id })

View File

@ -17,9 +17,9 @@ export const paramDef = {
} as const; } as const;
export default define(meta, paramDef, async (ps, user) => { export default define(meta, paramDef, async (ps, user) => {
const token = await AccessTokens.findOneBy({ id: ps.tokenId }); const exist = await AccessTokens.exist({ where: { id: ps.tokenId } });
if (token) { if (exist) {
await AccessTokens.delete({ await AccessTokens.delete({
id: ps.tokenId, id: ps.tokenId,
userId: user.id, userId: user.id,

View File

@ -389,6 +389,11 @@ export const meta = {
nullable: false, nullable: false,
default: "⭐", default: "⭐",
}, },
donationLink: {
type: "string",
optional: "true",
nullable: true,
},
}, },
}, },
} as const; } as const;
@ -491,6 +496,7 @@ export default define(meta, paramDef, async (ps, me) => {
translatorAvailable: translatorAvailable:
instance.deeplAuthKey != null || instance.libreTranslateApiUrl != null, instance.deeplAuthKey != null || instance.libreTranslateApiUrl != null,
defaultReaction: instance.defaultReaction, defaultReaction: instance.defaultReaction,
donationLink: instance.donationLink,
...(ps.detail ...(ps.detail
? { ? {

View File

@ -64,12 +64,14 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// Check if already muting // Check if already muting
const exist = await Mutings.findOneBy({ const exist = await Mutings.exist({
muterId: muter.id, where: {
muteeId: mutee.id, muterId: muter.id,
muteeId: mutee.id,
},
}); });
if (exist != null) { if (exist) {
throw new ApiError(meta.errors.alreadyMuting); throw new ApiError(meta.errors.alreadyMuting);
} }

View File

@ -56,18 +56,18 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// Check not muting // Check not muting
const exist = await Mutings.findOneBy({ const muting = await Mutings.findOneBy({
muterId: muter.id, muterId: muter.id,
muteeId: mutee.id, muteeId: mutee.id,
}); });
if (exist == null) { if (muting == null) {
throw new ApiError(meta.errors.notMuting); throw new ApiError(meta.errors.notMuting);
} }
// Delete mute // Delete mute
await Mutings.delete({ await Mutings.delete({
id: exist.id, id: muting.id,
}); });
publishUserEvent(user.id, "unmute", mutee); publishUserEvent(user.id, "unmute", mutee);

View File

@ -1,4 +1,3 @@
import { Brackets } from "typeorm";
import { Notes } from "@/models/index.js"; import { Notes } from "@/models/index.js";
import define from "../../define.js"; import define from "../../define.js";
import { makePaginationQuery } from "../../common/make-pagination-query.js"; import { makePaginationQuery } from "../../common/make-pagination-query.js";
@ -11,6 +10,7 @@ export const meta = {
requireCredential: false, requireCredential: false,
requireCredentialPrivateMode: true, requireCredentialPrivateMode: true,
description: "Get threaded/chained replies to a note",
res: { res: {
type: "array", type: "array",
@ -23,13 +23,14 @@ export const meta = {
ref: "Note", ref: "Note",
}, },
}, },
}; } as const;
export const paramDef = { export const paramDef = {
type: "object", type: "object",
properties: { properties: {
noteId: { type: "string", format: "misskey:id" }, noteId: { type: "string", format: "misskey:id" },
limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, limit: { type: "integer", minimum: 1, maximum: 100, default: 10 },
depth: { type: "integer", minimum: 1, maximum: 100, default: 12 },
sinceId: { type: "string", format: "misskey:id" }, sinceId: { type: "string", format: "misskey:id" },
untilId: { type: "string", format: "misskey:id" }, untilId: { type: "string", format: "misskey:id" },
}, },

View File

@ -9,6 +9,7 @@ export const meta = {
requireCredential: false, requireCredential: false,
requireCredentialPrivateMode: true, requireCredentialPrivateMode: true,
description: "Get conversation of a note thread/chain by a reply",
res: { res: {
type: "array", type: "array",
@ -34,7 +35,11 @@ export const meta = {
export const paramDef = { export const paramDef = {
type: "object", type: "object",
properties: { properties: {
noteId: { type: "string", format: "misskey:id" }, noteId: {
type: "string",
format: "misskey:id",
description: "Should be a reply",
},
limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, limit: { type: "integer", minimum: 1, maximum: 100, default: 10 },
offset: { type: "integer", default: 0 }, offset: { type: "integer", default: 0 },
}, },
@ -51,7 +56,7 @@ export default define(meta, paramDef, async (ps, user) => {
const conversation: Note[] = []; const conversation: Note[] = [];
let i = 0; let i = 0;
async function get(id: any) { async function get(id: string) {
i++; i++;
const p = await getNote(id, user).catch((e) => { const p = await getNote(id, user).catch((e) => {
if (e.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") return null; if (e.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") return null;
@ -60,7 +65,7 @@ export default define(meta, paramDef, async (ps, user) => {
if (p == null) return; if (p == null) return;
if (i > ps.offset!) { if (i > ps.offset) {
conversation.push(p); conversation.push(p);
} }

View File

@ -224,11 +224,13 @@ export default define(meta, paramDef, async (ps, user) => {
// Check blocking // Check blocking
if (renote.userId !== user.id) { if (renote.userId !== user.id) {
const block = await Blockings.findOneBy({ const isBlocked = await Blockings.exist({
blockerId: renote.userId, where: {
blockeeId: user.id, blockerId: renote.userId,
blockeeId: user.id,
},
}); });
if (block) { if (isBlocked) {
throw new ApiError(meta.errors.youHaveBeenBlocked); throw new ApiError(meta.errors.youHaveBeenBlocked);
} }
} }
@ -249,11 +251,13 @@ export default define(meta, paramDef, async (ps, user) => {
// Check blocking // Check blocking
if (reply.userId !== user.id) { if (reply.userId !== user.id) {
const block = await Blockings.findOneBy({ const isBlocked = await Blockings.exist({
blockerId: reply.userId, where: {
blockeeId: user.id, blockerId: reply.userId,
blockeeId: user.id,
},
}); });
if (block) { if (isBlocked) {
throw new ApiError(meta.errors.youHaveBeenBlocked); throw new ApiError(meta.errors.youHaveBeenBlocked);
} }
} }

View File

@ -43,12 +43,14 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// if already favorited // if already favorited
const exist = await NoteFavorites.findOneBy({ const exist = await NoteFavorites.exist({
noteId: note.id, where: {
userId: user.id, noteId: note.id,
userId: user.id,
},
}); });
if (exist != null) { if (exist) {
throw new ApiError(meta.errors.alreadyFavorited); throw new ApiError(meta.errors.alreadyFavorited);
} }

View File

@ -42,15 +42,15 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// if already favorited // if already favorited
const exist = await NoteFavorites.findOneBy({ const favorite = await NoteFavorites.findOneBy({
noteId: note.id, noteId: note.id,
userId: user.id, userId: user.id,
}); });
if (exist == null) { if (favorite == null) {
throw new ApiError(meta.errors.notFavorited); throw new ApiError(meta.errors.notFavorited);
} }
// Delete favorite // Delete favorite
await NoteFavorites.delete(exist.id); await NoteFavorites.delete(favorite.id);
}); });

View File

@ -21,6 +21,7 @@ export const meta = {
message: "No such note.", message: "No such note.",
code: "NO_SUCH_NOTE", code: "NO_SUCH_NOTE",
id: "24fcbfc6-2e37-42b6-8388-c29b3861a08d", id: "24fcbfc6-2e37-42b6-8388-c29b3861a08d",
httpStatusCode: 404,
}, },
}, },
} as const; } as const;

View File

@ -40,12 +40,14 @@ export default define(meta, paramDef, async (ps, user) => {
} }
// if already liked // if already liked
const exist = await PageLikes.findOneBy({ const exist = await PageLikes.exist({
pageId: page.id, where: {
userId: user.id, pageId: page.id,
userId: user.id,
},
}); });
if (exist != null) { if (exist) {
throw new ApiError(meta.errors.alreadyLiked); throw new ApiError(meta.errors.alreadyLiked);
} }

View File

@ -38,17 +38,17 @@ export default define(meta, paramDef, async (ps, user) => {
throw new ApiError(meta.errors.noSuchPage); throw new ApiError(meta.errors.noSuchPage);
} }
const exist = await PageLikes.findOneBy({ const like = await PageLikes.findOneBy({
pageId: page.id, pageId: page.id,
userId: user.id, userId: user.id,
}); });
if (exist == null) { if (like == null) {
throw new ApiError(meta.errors.notLiked); throw new ApiError(meta.errors.notLiked);
} }
// Delete like // Delete like
await PageLikes.delete(exist.id); await PageLikes.delete(like.id);
Pages.decrement({ id: page.id }, "likedCount", 1); Pages.decrement({ id: page.id }, "likedCount", 1);
}); });

View File

@ -1,9 +1,15 @@
import define from "../define.js"; import define from "../define.js";
import { redisClient } from "@/db/redis.js"; import { redisClient } from "@/db/redis.js";
import * as fs from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname } from "node:path";
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
export const meta = { export const meta = {
tags: ["meta"], tags: ["meta"],
description: "Get list of Firefish patrons from Codeberg", description: "Get Calckey patrons",
requireCredential: false, requireCredential: false,
requireCredentialPrivateMode: false, requireCredentialPrivateMode: false,
@ -24,21 +30,29 @@ export default define(meta, paramDef, async (ps) => {
patrons = JSON.parse(cachedPatrons); patrons = JSON.parse(cachedPatrons);
} else { } else {
AbortSignal.timeout ??= function timeout(ms) { AbortSignal.timeout ??= function timeout(ms) {
const ctrl = new AbortController() const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), ms) setTimeout(() => ctrl.abort(), ms);
return ctrl.signal return ctrl.signal;
} };
patrons = await fetch( patrons = await fetch(
"https://codeberg.org/firefish/firefish/raw/branch/develop/patrons.json", "https://codeberg.org/calckey/calckey/raw/branch/develop/patrons.json",
{ signal: AbortSignal.timeout(2000) } { signal: AbortSignal.timeout(2000) },
) )
.then((response) => response.json()) .then((response) => response.json())
.catch(() => { .catch(() => {
patrons = cachedPatrons ? JSON.parse(cachedPatrons) : []; const staticPatrons = JSON.parse(
fs.readFileSync(
`${_dirname}/../../../../../../patrons.json`,
"utf-8",
),
);
patrons = cachedPatrons ? JSON.parse(cachedPatrons) : staticPatrons;
}); });
await redisClient.set("patrons", JSON.stringify(patrons), "EX", 3600); await redisClient.set("patrons", JSON.stringify(patrons), "EX", 3600);
} }
return {
return patrons["patrons"]; patrons: patrons["patrons"],
sponsors: patrons["sponsors"],
};
}); });

View File

@ -33,12 +33,14 @@ export default define(meta, paramDef, async (ps, user) => {
throw err; throw err;
}); });
const exist = await PromoReads.findOneBy({ const exist = await PromoReads.exist({
noteId: note.id, where: {
userId: user.id, noteId: note.id,
userId: user.id,
},
}); });
if (exist != null) { if (exist) {
return; return;
} }

View File

@ -47,12 +47,14 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// Check if already muting // Check if already muting
const exist = await RenoteMutings.findOneBy({ const exist = await RenoteMutings.exist({
muterId: muter.id, where: {
muteeId: mutee.id, muterId: muter.id,
muteeId: mutee.id,
},
}); });
if (exist != null) { if (exist) {
throw new ApiError(meta.errors.alreadyMuting); throw new ApiError(meta.errors.alreadyMuting);
} }

View File

@ -45,18 +45,18 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// Check not muting // Check not muting
const exist = await RenoteMutings.findOneBy({ const muting = await RenoteMutings.findOneBy({
muterId: muter.id, muterId: muter.id,
muteeId: mutee.id, muteeId: mutee.id,
}); });
if (exist == null) { if (muting == null) {
throw new ApiError(meta.errors.notMuting); throw new ApiError(meta.errors.notMuting);
} }
// Delete mute // Delete mute
await RenoteMutings.delete({ await RenoteMutings.delete({
id: exist.id, id: muting.id,
}); });
// publishUserEvent(user.id, "unmute", mutee); // publishUserEvent(user.id, "unmute", mutee);

View File

@ -2,11 +2,13 @@ import * as os from "node:os";
import si from "systeminformation"; import si from "systeminformation";
import define from "../define.js"; import define from "../define.js";
import meilisearch from "@/db/meilisearch.js"; import meilisearch from "@/db/meilisearch.js";
import { fetchMeta } from "@/misc/fetch-meta.js";
export const meta = { export const meta = {
requireCredential: false, requireCredential: false,
requireCredentialPrivateMode: true, requireCredentialPrivateMode: true,
allowGet: true,
cacheSec: 30,
tags: ["meta"], tags: ["meta"],
} as const; } as const;
@ -29,6 +31,23 @@ export default define(meta, paramDef, async () => {
} }
} }
const instanceMeta = await fetchMeta();
if (!instanceMeta.enableServerMachineStats) {
return {
machine: "Not specified",
cpu: {
model: "Not specified",
cores: 0,
},
mem: {
total: 0,
},
fs: {
total: 0,
used: 0,
},
};
}
return { return {
machine: os.hostname(), machine: os.hostname(),
cpu: { cpu: {

View File

@ -57,8 +57,7 @@ export const paramDef = {
} as const; } as const;
export default define(meta, paramDef, async (ps, me) => { export default define(meta, paramDef, async (ps, me) => {
// if already subscribed const subscription = await SwSubscriptions.findOneBy({
const exist = await SwSubscriptions.findOneBy({
userId: me.id, userId: me.id,
endpoint: ps.endpoint, endpoint: ps.endpoint,
auth: ps.auth, auth: ps.auth,
@ -67,13 +66,14 @@ export default define(meta, paramDef, async (ps, me) => {
const instance = await fetchMeta(true); const instance = await fetchMeta(true);
if (exist != null) { // if already subscribed
if (subscription != null) {
return { return {
state: "already-subscribed" as const, state: "already-subscribed" as const,
key: instance.swPublicKey, key: instance.swPublicKey,
userId: me.id, userId: me.id,
endpoint: exist.endpoint, endpoint: subscription.endpoint,
sendReadMessage: exist.sendReadMessage, sendReadMessage: subscription.sendReadMessage,
}; };
} }

View File

@ -42,16 +42,16 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, me) => { export default define(meta, paramDef, async (ps, me) => {
const exist = await SwSubscriptions.findOneBy({ const subscription = await SwSubscriptions.findOneBy({
userId: me.id, userId: me.id,
endpoint: ps.endpoint, endpoint: ps.endpoint,
}); });
if (exist != null) { if (subscription != null) {
return { return {
userId: exist.userId, userId: subscription.userId,
endpoint: exist.endpoint, endpoint: subscription.endpoint,
sendReadMessage: exist.sendReadMessage, sendReadMessage: subscription.sendReadMessage,
}; };
} }

View File

@ -98,11 +98,13 @@ export default define(meta, paramDef, async (ps, me) => {
if (me == null) { if (me == null) {
throw new ApiError(meta.errors.forbidden); throw new ApiError(meta.errors.forbidden);
} else if (me.id !== user.id) { } else if (me.id !== user.id) {
const following = await Followings.findOneBy({ const isFollowed = await Followings.exist({
followeeId: user.id, where: {
followerId: me.id, followeeId: user.id,
followerId: me.id,
},
}); });
if (following == null) { if (!isFollowed) {
throw new ApiError(meta.errors.nullFollowers); throw new ApiError(meta.errors.nullFollowers);
} }
} }

View File

@ -97,11 +97,13 @@ export default define(meta, paramDef, async (ps, me) => {
if (me == null) { if (me == null) {
throw new ApiError(meta.errors.forbidden); throw new ApiError(meta.errors.forbidden);
} else if (me.id !== user.id) { } else if (me.id !== user.id) {
const following = await Followings.findOneBy({ const isFollowing = await Followings.exist({
followeeId: user.id, where: {
followerId: me.id, followeeId: user.id,
followerId: me.id,
},
}); });
if (following == null) { if (!isFollowing) {
throw new ApiError(meta.errors.cannot_find); throw new ApiError(meta.errors.cannot_find);
} }
} }

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