Merge branch 'develop' into rebrand

This commit is contained in:
ThatOneCalculator 2023-07-15 14:15:01 -07:00
commit 6bc25eb38b
536 changed files with 16576 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
#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.
# If both MeiliSearch and Sonic configurations are present, MeiliSearch will take precedence.
@ -107,7 +121,7 @@ redis:
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
# Maximum length of a post (default 3000, max 8192)
# Maximum length of a post (default 3000, max 250000000)
#maxNoteLength: 3000
# Maximum length of an image caption (default 1500, max 8192)

View File

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

4
.gitignore vendored
View File

@ -25,6 +25,7 @@ coverage
!/.config/devenv.yml
!/.config/docker_example.env
!/.config/helm_values_example.yml
!/.config/LICENSE
#docker dev config
/dev/docker-compose.yml
@ -48,6 +49,9 @@ packages/backend/assets/sounds/None.mp3
!packages/backend/src/db
packages/megalodon/lib
packages/megalodon/.idea
# blender backups
*.blend1
*.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>
Breaks Calckey -> Misskey migration, but fixes Foundkey -> Calckey migration
Breaks Calckey -> Misskey migration, but fixes FoundKey -> Calckey migration
- Add argon

27
COPYING
View File

@ -1,15 +1,24 @@
Unless otherwise stated this repository is
Copyright © 2014-2022 syuilo and contributers
Copyright © 2022 thatonecalculator and contributers
Unless specified otherwise, the entirety of this repository is subject to the following:
Copyright © 2014-2023 syuilo and contributors
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.
---
Firefish includes several third-party Open-Source softwares.
These specific configuration directories:
Emoji keywords for Unicode 11 and below by Mu-An Chiou
License: MIT
https://github.com/muan/emojilib/blob/master/LICENSE
- .config/
- custom/assets/
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
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.
License: MIT
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/client/package.json packages/client/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/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
@ -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
# 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/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/
COPY packages/backend/native-utils packages/backend/native-utils/
# Compile native-utils
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 --from=build /calckey/packages/megalodon /calckey/packages/megalodon
# Copy node modules
COPY --from=build /firefish/node_modules /firefish/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
- [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)
- 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/4bc9610d8bf5af736b5e89e4782395705de45d7d: remove unnecessary joins
- https://akkoma.dev/FoundKeyGang/FoundKey/commit/9ee609d70082f7a6dc119a5d83c0e7c5e1208676: enhance privacy of notes

View File

@ -72,6 +72,14 @@
# 🌠 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**.
## 🔰 Easy installers
@ -88,7 +96,6 @@ If you have access to a server that supports one of the sources below, I recomme
## 🧑‍💻 Dependencies
- 🐢 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 [Redis](https://redis.io/) v6 (v7 recommended)
- Web Proxy (one of the following)
@ -104,6 +111,10 @@ If you have access to a server that supports one of the sources below, I recomme
- 🦔 [Sonic](https://crates.io/crates/sonic-server)
- [MeiliSearch](https://www.meilisearch.com/)
- [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
@ -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`.
## 💰 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
### 🦔 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.
- 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

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**
- **stopped all master and worker processes of Misskey.**
- **have backups of the database before performing any commands.**
### Before proceeding
- **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
@ -77,15 +78,16 @@ NODE_ENV=production pnpm run migrate
# build using prefered method
```
## Foundkey
## FoundKey
```sh
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)"
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
done
@ -100,4 +102,4 @@ NODE_ENV=production pnpm run migrate
## 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ó"
defaultNoteVisibility: "Visibilitat per defecte"
follow: "Segueix"
followRequest: "Segueix"
followRequest: "Sol·licitud de Seguiment"
followRequests: "Sol·licituds de seguiment"
unfollow: "Deixa de seguir"
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
recommendedInstances: Servidors recomanats
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
splash: Pantalla de Benvinguda
swipeOnDesktop: Permet lliscar a l'estil del mòbil a l'escriptori
@ -1603,6 +1603,12 @@ _aboutMisskey:
patrons: Mecenes de Firefish
patronsList: Llistats cronològicament, no per la quantitat donada. Fes una donació
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
pageLikesCount: Nombre de pàgines amb M'agrada
youAreRunningUpToDateClient: Estás fent servir la versió del client més nova.
@ -2142,3 +2148,15 @@ _skinTones:
dark: Fosc
yellow: Groc
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"
defaultNoteVisibility: "Default visibility"
follow: "Follow"
followRequest: "Follow"
followRequest: "Follow Request"
followRequests: "Follow requests"
unfollow: "Unfollow"
followRequestPending: "Follow request pending"
@ -644,6 +644,7 @@ useBlurEffectForModal: "Use blur effect for modals"
useFullReactionPicker: "Use full-size reaction picker"
width: "Width"
height: "Height"
xl: "XL"
large: "Big"
medium: "Medium"
small: "Small"
@ -1049,7 +1050,7 @@ customSplashIconsDescription: "URLs for custom splash screen icons separated by
showUpdates: "Show a popup when Firefish updates"
recommendedInstances: "Recommended servers"
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"
splash: "Splash Screen"
updateAvailable: "There might be an update available!"
@ -1112,6 +1113,17 @@ isModerator: "Moderator"
isAdmin: "Administrator"
isPatron: "Firefish Patron"
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:
description: "Reduces the effort of server moderation through automatically recognizing
@ -1208,11 +1220,16 @@ _aboutMisskey:
contributors: "Main contributors"
allContributors: "All contributors"
source: "Source code"
translation: "Translate Firefish"
donate: "Donate to Firefish"
translation: "Translate Calckey"
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.
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!"
_nsfw:
respect: "Hide NSFW media"

View File

@ -1,10 +1,10 @@
_lang_: "Français"
headlineMisskey: "Réseau relié par des notes"
introMisskey: "Bienvenue ! Firefish est un service de microblogage décentralisé, libre\
\ 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\
\ également dajouter une réaction rapide aux notes des autres utilisateur·rice·s\
\ \U0001F44D\nExplorons un nouveau monde \U0001F680"
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,
autour de vous avec les autres 📡\nLa fonction « réactions », vous permet également
dajouter une réaction rapide aux notes des autres utilisateur·rice·s 👍\nExplorons
un nouveau monde 🚀"
monthAndDay: "{day}/{month}"
search: "Rechercher"
notifications: "Notifications"
@ -26,8 +26,8 @@ otherSettings: "Paramètres avancés"
openInWindow: "Ouvrir dans une nouvelle fenêtre"
profile: "Profil"
timeline: "Fil"
noAccountDescription: "Lutilisateur·rice na pas encore renseigné de biographie de\
\ présentation sur son profil."
noAccountDescription: "Lutilisateur·rice na pas encore renseigné de biographie de
présentation sur son profil."
login: "Se connecter"
loggingIn: "Connexion en cours"
logout: "Se déconnecter"
@ -48,8 +48,8 @@ copyContent: "Copier le contenu"
copyLink: "Copier le lien"
delete: "Supprimer"
deleteAndEdit: "Supprimer et réécrire"
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."
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."
addToList: "Ajouter à une liste"
sendMessage: "Envoyer un message"
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}\"\
\ ? Il sera retiré de toutes ses notes liées."
unfollowConfirm: "Désirez-vous vous désabonner de {name} ?"
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."
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."
importRequested: "Vous avez initié un import. Cela pourrait prendre un peu de temps."
lists: "Listes"
noLists: "Vous navez aucune liste"
@ -88,12 +88,12 @@ error: "Erreur"
somethingHappened: "Une erreur est survenue"
retry: "Réessayer"
pageLoadError: "Le chargement de la page a échoué."
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."
serverIsDead: "Le serveur ne répond pas. Patientez quelques instants puis essayez\
\ à nouveau."
youShouldUpgradeClient: "Si la page ne s'affiche pas correctement, rechargez-la pour\
\ mettre votre client à jour."
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."
serverIsDead: "Le serveur ne répond pas. Patientez quelques instants puis essayez
à nouveau."
youShouldUpgradeClient: "Si la page ne s'affiche pas correctement, rechargez-la pour
mettre votre client à jour."
enterListName: "Nom de la liste"
privacy: "Confidentialité"
makeFollowManuallyApprove: "Accepter manuellement les demandes dabonnement"
@ -118,11 +118,11 @@ sensitive: "Contenu sensible"
add: "Ajouter"
reaction: "Réactions"
reactionSetting: "Réactions à afficher dans le sélecteur de réactions"
reactionSettingDescription2: "Déplacer pour réorganiser, cliquer pour effacer, utiliser\
\ « + » pour ajouter."
rememberNoteVisibility: "Activer l'option \" se souvenir de la visibilité des notes\
\ \" vous permet de réutiliser automatiquement la visibilité utilisée lors de la\
\ publication de votre note précédente."
reactionSettingDescription2: "Déplacer pour réorganiser, cliquer pour effacer, utiliser
« + » pour ajouter."
rememberNoteVisibility: "Activer l'option \" se souvenir de la visibilité des notes
\" vous permet de réutiliser automatiquement la visibilité utilisée lors de la publication
de votre note précédente."
attachCancel: "Supprimer le fichier attaché"
markAsSensitive: "Marquer comme sensible"
unmarkAsSensitive: "Supprimer le marquage comme sensible"
@ -150,20 +150,20 @@ emojiUrl: "URL de lémoji"
addEmoji: "Ajouter un émoji"
settingGuide: "Configuration proposée"
cacheRemoteFiles: "Mise en cache des 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\
\ lutilisation de lespace de stockage local mais augmentera le trafic réseau puisque\
\ les miniatures ne seront plus générées."
cacheRemoteFilesDescription: "Lorsque cette option est désactivée, les fichiers distants
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
les miniatures ne seront plus générées."
flagAsBot: "Ce compte est un robot"
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\
\ 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."
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
afin d'éviter des chaînes d'interaction sans fin avec d'autres robots et d'ajuster
les systèmes internes de Calckey pour traiter ce compte comme un robot."
flagAsCat: "Ce compte est un chat"
flagAsCatDescription: "Activer l'option \" Je suis un chat \" pour ce compte."
flagShowTimelineReplies: "Afficher les réponses dans le fil"
autoAcceptFollowed: "Accepter automatiquement les demandes dabonnement venant dutilisateur·rice·s\
\ que vous suivez"
autoAcceptFollowed: "Accepter automatiquement les demandes dabonnement venant dutilisateur·rice·s
que vous suivez"
addAccount: "Ajouter un compte"
loginFailed: "Échec de la connexion"
showOnRemote: "Voir sur linstance distante"
@ -175,12 +175,12 @@ searchWith: "Recherche : {q}"
youHaveNoLists: "Vous navez aucune liste"
followConfirm: "Êtes-vous sûr·e de vouloir suivre {name} ?"
proxyAccount: "Compte proxy"
proxyAccountDescription: "Un compte proxy se comporte, dans certaines conditions,\
\ 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,\
\ 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\
\ acheminées."
proxyAccountDescription: "Un compte proxy se comporte, dans certaines conditions,
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,
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
acheminées."
host: "Serveur distant"
selectUser: "Sélectionner un·e utilisateur·rice"
recipient: "Destinataire"
@ -210,14 +210,14 @@ instanceInfo: "Informations sur linstance"
statistics: "Statistiques"
clearQueue: "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,\
\ vous n'avez pas besoin d'effectuer cette opération."
clearQueueConfirmText: "Les notes non distribuées ne seront pas délivrées. Normalement,
vous n'avez pas besoin d'effectuer cette opération."
clearCachedFiles: "Vider le cache"
clearCachedFilesConfirm: "Êtes-vous sûr·e de vouloir vider tout le cache de fichiers\
\ distants ?"
clearCachedFilesConfirm: "Êtes-vous sûr·e de vouloir vider tout le cache de fichiers
distants ?"
blockedInstances: "Instances bloquées"
blockedInstancesDescription: "Listez les instances que vous désirez bloquer, une par\
\ ligne. Ces instances ne seront plus en capacité d'interagir avec votre instance."
blockedInstancesDescription: "Listez les instances que vous désirez bloquer, une par
ligne. Ces instances ne seront plus en capacité d'interagir avec votre instance."
muteAndBlock: "Masqué·e·s / Bloqué·e·s"
mutedUsers: "Utilisateur·rice·s en sourdine"
blockedUsers: "Utilisateur·rice·s bloqué·e·s"
@ -270,8 +270,8 @@ fromUrl: "Depuis une URL"
uploadFromUrl: "Téléverser via une URL"
uploadFromUrlDescription: "URL du fichier que vous souhaitez téléverser"
uploadFromUrlRequested: "Téléversement demandé"
uploadFromUrlMayTakeTime: "Le téléversement de votre fichier peut prendre un certain\
\ temps."
uploadFromUrlMayTakeTime: "Le téléversement de votre fichier peut prendre un certain
temps."
explore: "Découvrir"
messageRead: "Lu"
noMoreHistory: "Il ny a plus dhistorique"
@ -281,8 +281,8 @@ agreeTo: "Jaccepte {0}"
tos: "les conditions dutilisation"
start: "Commencer"
home: "Principal"
remoteUserCaution: "Les informations de ce compte risqueraient dêtre incomplètes\
\ du fait que lutilisateur·rice provient dune instance distante."
remoteUserCaution: "Les informations de ce compte risqueraient dêtre incomplètes
du fait que lutilisateur·rice provient dune instance distante."
activity: "Activité"
images: "Images"
birthday: "Date de naissance"
@ -315,8 +315,8 @@ unableToDelete: "Suppression impossible"
inputNewFileName: "Entrez un nouveau nom de fichier"
inputNewDescription: "Veuillez entrer une nouvelle description"
inputNewFolderName: "Entrez un nouveau nom de dossier"
circularReferenceFolder: "Le dossier de destination est un sous-dossier du dossier\
\ que vous souhaitez déplacer."
circularReferenceFolder: "Le dossier de destination est un sous-dossier du dossier
que vous souhaitez déplacer."
hasChildFilesOrFolders: "Impossible de supprimer ce dossier car il n'est pas vide."
copyUrl: "Copier lURL"
rename: "Renommer"
@ -350,8 +350,8 @@ connectService: "Connexion"
disconnectService: "Déconnexion"
enableLocalTimeline: "Activer le fil local"
enableGlobalTimeline: "Activer le fil global"
disablingTimelinesInfo: "Même si vous désactivez ces fils, les administrateur·rice·s\
\ et les modérateur·rice·s pourront toujours y accéder."
disablingTimelinesInfo: "Même si vous désactivez ces fils, les administrateur·rice·s
et les modérateur·rice·s pourront toujours y accéder."
registration: "Sinscrire"
enableRegistration: "Autoriser les nouvelles inscriptions"
invite: "Inviter"
@ -363,11 +363,11 @@ bannerUrl: "URL de limage de la bannière"
backgroundImageUrl: "URL de l'image d'arrière-plan"
basicInfo: "Informations basiques"
pinnedUsers: "Utilisateur·rice épinglé·e"
pinnedUsersDescription: "Listez les utilisateur·rice·s que vous souhaitez voir épinglé·e·s\
\ sur la page \"Découvrir\", un·e par ligne."
pinnedUsersDescription: "Listez les utilisateur·rice·s que vous souhaitez voir épinglé·e·s
sur la page \"Découvrir\", un·e par ligne."
pinnedPages: "Pages épinglées"
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."
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."
pinnedClipId: "Identifiant du clip épinglé"
pinnedNotes: "Note épinglée"
hcaptcha: "hCaptcha"
@ -378,17 +378,17 @@ recaptcha: "reCAPTCHA"
enableRecaptcha: "Activer reCAPTCHA"
recaptchaSiteKey: "Clé du site"
recaptchaSecretKey: "Clé secrète"
avoidMultiCaptchaConfirm: "Lutilisation de plusieurs Captchas peut provoquer des\
\ interférences. Souhaitez-vous désactiver lautre Captcha ? Vous pouvez laisser\
\ plusieurs Captcha activés en appuyant sur Annuler."
avoidMultiCaptchaConfirm: "Lutilisation de plusieurs Captchas peut provoquer des
interférences. Souhaitez-vous désactiver lautre Captcha ? Vous pouvez laisser plusieurs
Captcha activés en appuyant sur Annuler."
antennas: "Antennes"
manageAntennas: "Gestion des antennes"
name: "Nom"
antennaSource: "Source de lantenne"
antennaKeywords: "Mots clés à recevoir"
antennaExcludeKeywords: "Mots clés à exclure"
antennaKeywordsDescription: "Séparer avec des espaces pour la condition AND. Séparer\
\ avec un saut de ligne pour une condition OR."
antennaKeywordsDescription: "Séparer avec des espaces pour la condition AND. Séparer
avec un saut de ligne pour une condition OR."
notifyAntenna: "Je souhaite recevoir les notifications des nouvelles notes"
withFileAntenna: "Notes ayant des attachements uniquement"
enableServiceworker: "Activer ServiceWorker"
@ -399,11 +399,11 @@ connectedTo: "Vous êtes connectés aux services suivants"
notesAndReplies: "Notes et Réponses"
withFiles: "Avec fichiers joints"
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"
unsilenceConfirm: "Êtes-vous sûr·e de vouloir annuler la mise en sourdine de cet·te\
\ utilisateur·rice ?"
unsilenceConfirm: "Êtes-vous sûr·e de vouloir annuler la mise en sourdine de cet·te
utilisateur·rice ?"
popularUsers: "Utilisateur·rice·s populaires"
recentlyUpdatedUsers: "Utilisateur·rice·s actif·ve·s récemment"
recentlyRegisteredUsers: "Utilisateur·rice·s récemment inscrit·e·s"
@ -468,8 +468,8 @@ invitationCode: "Code dinvitation"
checking: "Vérification en cours..."
available: "Disponible"
unavailable: "Non disponible"
usernameInvalidFormat: "Le nom d'utilisateur peut contenir uniquement des lettres\
\ (minuscules et/ou majuscules), des chiffres et des _"
usernameInvalidFormat: "Le nom d'utilisateur peut contenir uniquement des lettres
(minuscules et/ou majuscules), des chiffres et des _"
tooShort: "Trop court"
tooLong: "Trop long"
weakPassword: "Mot de passe faible"
@ -478,8 +478,8 @@ strongPassword: "Mot de passe fort"
passwordMatched: "Les mots de passe correspondent"
passwordNotMatched: "Les mots de passe ne correspondent pas"
signinWith: "Se connecter avec {x}"
signinFailed: "Échec dauthentification. Veuillez vérifier que votre nom dutilisateur\
\ et mot de passe sont corrects."
signinFailed: "Échec dauthentification. Veuillez vérifier que votre nom dutilisateur
et mot de passe sont corrects."
tapSecurityKey: "Appuyez sur votre clé de sécurité"
or: "OU"
language: "Langue"
@ -488,8 +488,8 @@ groupInvited: "Invité au groupe"
aboutX: "À propos de {x}"
useOsNativeEmojis: "Utiliser les émojis natifs du système"
youHaveNoGroups: "Vous navez aucun groupe"
joinOrCreateGroup: "Vous pouvez être invité·e à rejoindre des groupes existants ou\
\ créer votre propre nouveau groupe."
joinOrCreateGroup: "Vous pouvez être invité·e à rejoindre des groupes existants ou
créer votre propre nouveau groupe."
noHistory: "Pas d'historique"
signinHistory: "Historique de connexion"
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"
useObjectStorage: "Utiliser le stockage d'objets"
objectStorageBaseUrl: "Base URL"
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\
\ spécifiez ladresse accessible au public selon le guide de service que vous allez\
\ utiliser. P.ex. 'https://<bucket>.s3.amazonaws.com' pour AWS S3 et 'https://storage.googleapis.com/<bucket>'\
\ pour GCS."
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 spécifiez
ladresse accessible au public selon le guide de service que vous allez utiliser.
P.ex. 'https://<bucket>.s3.amazonaws.com' pour AWS S3 et 'https://storage.googleapis.com/<bucket>'
pour GCS."
objectStorageBucket: "Bucket"
objectStorageBucketDesc: "Veuillez spécifier le nom du compartiment utilisé sur le\
\ service configuré."
objectStorageBucketDesc: "Veuillez spécifier le nom du compartiment utilisé sur le
service configuré."
objectStoragePrefix: "Prefix"
objectStoragePrefixDesc: "Les fichiers seront stockés sous le répertoire de ce préfixe."
objectStorageEndpoint: "Endpoint"
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\
\ que vous allez utiliser."
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
que vous allez utiliser."
objectStorageRegion: "Région"
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'."
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'."
objectStorageUseSSL: "Utiliser SSL"
objectStorageUseSSLDesc: "Désactivez cette option si vous n'utilisez pas HTTPS pour\
\ la connexion API"
objectStorageUseSSLDesc: "Désactivez cette option si vous n'utilisez pas HTTPS pour
la connexion API"
objectStorageUseProxy: "Se connecter via proxy"
objectStorageUseProxyDesc: "Désactivez cette option si vous n'utilisez pas de proxy\
\ pour la connexion API"
objectStorageUseProxyDesc: "Désactivez cette option si vous n'utilisez pas de proxy
pour la connexion API"
objectStorageSetPublicRead: "Régler sur « public » lors de l'envoi"
serverLogs: "Journal du serveur"
deleteAll: "Supprimer tout"
@ -570,9 +570,9 @@ sort: "Trier"
ascendingOrder: "Ascendant"
descendingOrder: "Descendant"
scratchpad: "ScratchPad"
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\
\ de son interaction avec Firefish."
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
de son interaction avec Calckey."
output: "Sortie"
script: "Script"
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"
deleteAllFilesConfirm: "Êtes-vous sûr·e de vouloir supprimer tous les fichiers ?"
removeAllFollowing: "Retenir tous les abonnements"
removeAllFollowingDescription: "Se désabonner de tous les comptes de {host}. Veuillez\
\ lancer cette action uniquement si linstance nexiste plus."
removeAllFollowingDescription: "Se désabonner de tous les comptes de {host}. Veuillez
lancer cette action uniquement si linstance nexiste plus."
userSuspended: "Cet·te utilisateur·rice a été suspendu·e."
userSilenced: "Cette utilisateur·trice a été mis·e en sourdine."
yourAccountSuspendedTitle: "Ce compte est suspendu"
yourAccountSuspendedDescription: "Ce compte est suspendu car vous avez enfreint les\
\ 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\
\ l'administrateur·rice de votre instance. Merci de ne pas créer de nouveau compte."
yourAccountSuspendedDescription: "Ce compte est suspendu car vous avez enfreint les
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 l'administrateur·rice
de votre instance. Merci de ne pas créer de nouveau compte."
menu: "Menu"
divider: "Séparateur"
addItem: "Ajouter un élément"
@ -611,8 +611,8 @@ description: "Description"
describeFile: "Ajouter une description d'image"
enterFileDescription: "Saisissez une description"
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"
plugins: "Extensions"
deck: "Deck"
@ -629,14 +629,14 @@ permission: "Autorisations"
enableAll: "Tout activer"
disableAll: "Tout désactiver"
tokenRequested: "Autoriser l'accès au compte"
pluginTokenRequestedDescription: "Ce plugin pourra utiliser les autorisations définies\
\ ici."
pluginTokenRequestedDescription: "Ce plugin pourra utiliser les autorisations définies
ici."
notificationType: "Type de notifications"
edit: "Editer"
emailServer: "Serveur mail"
enableEmail: "Activer la distribution de courriel"
emailConfigInfo: "Utilisé pour confirmer votre adresse de courriel et la réinitialisation\
\ de votre mot de passe en cas doubli."
emailConfigInfo: "Utilisé pour confirmer votre adresse de courriel et la réinitialisation
de votre mot de passe en cas doubli."
email: "E-mail "
emailAddress: "Adresses e-mail"
smtpConfig: "Paramètres du serveur SMTP"
@ -644,8 +644,8 @@ smtpHost: "Serveur distant"
smtpPort: "Port"
smtpUser: "Nom dutilisateur·rice"
smtpPass: "Mot de passe"
emptyToDisableSmtpAuth: "Laisser le nom dutilisateur et le mot de passe vides pour\
\ désactiver la vérification SMTP"
emptyToDisableSmtpAuth: "Laisser le nom dutilisateur et le mot de passe vides pour
désactiver la vérification SMTP"
smtpSecure: "Utiliser SSL/TLS implicitement dans les connexions SMTP"
smtpSecureInfo: "Désactiver cette option lorsque STARTTLS est utilisé"
testEmail: "Tester la distribution de courriel"
@ -666,24 +666,24 @@ create: "Créer"
notificationSetting: "Paramètres des notifications "
notificationSettingDesc: "Sélectionnez le type de notification à afficher"
useGlobalSetting: "Utiliser paramètre général"
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\
\ être effectuées."
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 être
effectuées."
other: "Autre"
regenerateLoginToken: "Régénérer le jeton de connexion"
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\
\ jeton, tous les appareils seront déconnectés. "
setMultipleBySeparatingWithSpace: "Vous pouvez en définir plusieurs, en les séparant\
\ par des espaces."
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 jeton,
tous les appareils seront déconnectés. "
setMultipleBySeparatingWithSpace: "Vous pouvez en définir plusieurs, en les séparant
par des espaces."
fileIdOrUrl: "ID du fichier ou URL"
behavior: "Comportement"
sample: "Exemple"
abuseReports: "Signalements"
reportAbuse: "Signaler"
reportAbuseOf: "Signaler {name}"
fillAbuseReportDescription: "Veuillez expliquer les raisons du signalement. S'il s'agit\
\ d'une note précise, veuillez en donner le lien."
fillAbuseReportDescription: "Veuillez expliquer les raisons du signalement. S'il s'agit
d'une note précise, veuillez en donner le lien."
abuseReported: "Le rapport est envoyé. Merci."
reporter: "Signalé par"
reporteeOrigin: "Origine du signalement"
@ -694,8 +694,8 @@ abuseMarkAsResolved: "Marquer le signalement comme résolu"
openInNewTab: "Ouvrir dans un nouvel onglet"
openInSideView: "Ouvrir en vue latérale"
defaultNavigationBehaviour: "Navigation par défaut"
editTheseSettingsMayBreakAccount: "La modification de ces paramètres peut endommager\
\ votre compte."
editTheseSettingsMayBreakAccount: "La modification de ces paramètres peut endommager
votre compte."
instanceTicker: "Nom de l'instance d'origine des notes"
waitingFor: "En attente de {x}"
random: "Aléatoire"
@ -707,8 +707,8 @@ createNew: "Créer nouveau"
optional: "Facultatif"
createNewClip: "Créer un nouveau clip"
public: "Public"
i18nInfo: "Firefish est traduit dans différentes langues par des bénévoles. Vous pouvez\
\ contribuer à {link}."
i18nInfo: "Calckey est traduit dans différentes langues par des bénévoles. Vous pouvez
contribuer à {link}."
manageAccessTokens: "Gérer les jetons d'accès"
accountInfo: " Informations du compte "
notesCount: "Nombre de notes"
@ -727,16 +727,16 @@ no: "Non"
driveFilesCount: "Nombre de fichiers dans le Drive"
driveUsage: "Utilisation du Drive"
noCrawle: "Refuser l'indexation par les robots"
noCrawleDescription: "Demandez aux moteurs de recherche de ne pas indexer votre page\
\ de profil, vos notes, vos pages, etc."
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\
\ demandes d'abonnement soient approuvées manuellement."
noCrawleDescription: "Demandez aux moteurs de recherche de ne pas indexer votre page
de profil, vos notes, vos pages, etc."
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 demandes
d'abonnement soient approuvées manuellement."
alwaysMarkSensitive: "Marquer les médias comme contenu sensible par défaut"
loadRawImages: "Affichage complet des images jointes au lieu des vignettes"
disableShowingAnimatedImages: "Désactiver l'animation des images"
verificationEmailSent: "Un e-mail de vérification a été envoyé. Veuillez accéder au\
\ lien pour compléter la vérification."
verificationEmailSent: "Un e-mail de vérification a été envoyé. Veuillez accéder au
lien pour compléter la vérification."
notSet: "Non défini"
emailVerified: "Votre adresse e-mail a été vérifiée."
noteFavoritesCount: "Nombre de notes dans les favoris"
@ -748,16 +748,16 @@ clips: "Clips"
experimentalFeatures: "Fonctionnalités expérimentales"
developer: "Développeur"
makeExplorable: "Rendre le compte visible sur la page \"Découvrir\"."
makeExplorableDescription: "Si vous désactivez cette option, votre compte n'apparaîtra\
\ pas sur la page \"Découvrir\"."
makeExplorableDescription: "Si vous désactivez cette option, votre compte n'apparaîtra
pas sur la page \"Découvrir\"."
showGapBetweenNotesInTimeline: "Afficher un écart entre les notes sur la Timeline"
duplicate: "Duliquer"
left: "Gauche"
center: "Centrer"
wide: "Large"
narrow: "Condensé"
reloadToApplySetting: "Vos paramètres seront appliqués lorsque vous rechargerez la\
\ page. Souhaitez-vous recharger ?"
reloadToApplySetting: "Vos paramètres seront appliqués lorsque vous rechargerez la
page. Souhaitez-vous recharger ?"
needReloadToApply: "Ce paramètre s'appliquera après un rechargement."
showTitlebar: "Afficher la barre de titre"
clearCache: "Vider le cache"
@ -765,11 +765,11 @@ onlineUsersCount: "{n} utilisateur(s) en ligne"
nUsers: "{n} utilisateur·rice·s"
nNotes: "{n} Notes"
sendErrorReports: "Envoyer les rapports derreur"
sendErrorReportsDescription: "Si vous activez l'envoi des rapports d'erreur, vous\
\ contribuerez à améliorer la qualité de Firefish grâce au partage d'informations\
\ détaillées sur les erreurs lorsqu'un problème survient.\nCela inclut des informations\
\ telles que la version de votre système d'exploitation, le type de navigateur que\
\ vous utilisez, votre historique d'activité, etc."
sendErrorReportsDescription: "Si vous activez l'envoi des rapports d'erreur, vous
contribuerez à améliorer la qualité de Calckey grâce au partage d'informations détaillées
sur les erreurs lorsqu'un problème survient.\nCela inclut des informations telles
que la version de votre système d'exploitation, le type de navigateur que vous utilisez,
votre historique d'activité, etc."
myTheme: "Mes thèmes"
backgroundColor: "Arrière-plan"
accentColor: "Accentuation"
@ -808,17 +808,17 @@ unlikeConfirm: "Êtes-vous sûr·e de ne plus vouloir aimer cette publication ?"
fullView: "Plein écran"
quitFullView: "Quitter le plein écran"
addDescription: "Ajouter une description"
userPagePinTip: "Vous pouvez afficher des notes ici en sélectionnant l'option « Épingler\
\ au profil » dans le menu de chaque note."
notSpecifiedMentionWarning: "Vous avez mentionné des utilisateur·rice·s qui ne font\
\ pas partie de la liste des destinataires"
userPagePinTip: "Vous pouvez afficher des notes ici en sélectionnant l'option « Épingler
au profil » dans le menu de chaque note."
notSpecifiedMentionWarning: "Vous avez mentionné des utilisateur·rice·s qui ne font
pas partie de la liste des destinataires"
info: "Informations"
userInfo: "Informations sur l'utilisateur"
unknown: "Inconnu"
onlineStatus: "Statut"
hideOnlineStatus: "Se rendre invisible"
hideOnlineStatusDescription: "Rendre votre statut invisible peut diminuer les performances\
\ de certaines fonctionnalités, telles que la Recherche."
hideOnlineStatusDescription: "Rendre votre statut invisible peut diminuer les performances
de certaines fonctionnalités, telles que la Recherche."
online: "En ligne"
active: "Actif·ve"
offline: "Hors ligne"
@ -853,9 +853,9 @@ emailNotConfiguredWarning: "Vous n'avez pas configuré d'adresse e-mail."
ratio: "Ratio"
previewNoteText: "Voir l'aperçu"
customCss: "CSS personnalisé"
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\
\ normalement."
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
normalement."
global: "Global"
squareAvatars: "Avatars carrés"
sent: "Envoyer"
@ -870,10 +870,10 @@ whatIsNew: "Voir les derniers changements"
translate: "Traduire"
translatedFrom: "Traduit depuis {x}"
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.\
\ Vous pouvez utiliser des lettres de l'alphabet (minuscules et majuscules), des\
\ chiffres (de 0 à 9), ou bien le tiret « _ ». Vous ne pourrez pas modifier votre\
\ nom d'utilisateur·rice par la suite."
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 chiffres
(de 0 à 9), ou bien le tiret « _ ». Vous ne pourrez pas modifier votre nom d'utilisateur·rice
par la suite."
aiChanMode: "Mode Ai"
keepCw: "Garder le CW"
pubSub: "Comptes Pub/Sub"
@ -889,14 +889,14 @@ filter: "Filtre"
controlPanel: "Panneau de contrôle"
manageAccounts: "Gérer les comptes"
makeReactionsPublic: "Rendre les réactions publiques"
makeReactionsPublicDescription: "Ceci rendra la liste de toutes vos réactions données\
\ publique."
makeReactionsPublicDescription: "Ceci rendra la liste de toutes vos réactions données
publique."
classic: "Classique"
muteThread: "Masquer cette discussion"
unmuteThread: "Ne plus masquer le fil"
ffVisibility: "Visibilité des abonnés/abonnements"
ffVisibilityDescription: "Permet de configurer qui peut voir les personnes que tu\
\ suis et les personnes qui te suivent."
ffVisibilityDescription: "Permet de configurer qui peut voir les personnes que tu
suis et les personnes qui te suivent."
continueThread: "Afficher la suite du fil"
deleteAccountConfirm: "Votre compte sera supprimé. Êtes vous certain ?"
incorrectPassword: "Le mot de passe est incorrect."
@ -904,11 +904,11 @@ voteConfirm: "Confirmez-vous votre vote pour « {choice} » ?"
hide: "Masquer"
leaveGroup: "Quitter le groupe"
leaveGroupConfirm: "Êtes vous sûr de vouloir quitter \"{name}\" ?"
useDrawerReactionPickerForMobile: "Afficher le sélecteur de réactions en tant que\
\ panneau sur mobile"
useDrawerReactionPickerForMobile: "Afficher le sélecteur de réactions en tant que
panneau sur mobile"
welcomeBackWithName: "Heureux de vous revoir, {name}"
clickToFinishEmailVerification: "Veuillez cliquer sur [{ok}] afin de compléter la\
\ vérification par courriel."
clickToFinishEmailVerification: "Veuillez cliquer sur [{ok}] afin de compléter la
vérification par courriel."
overridedDeviceKind: "Type dappareil"
smartphone: "Smartphone"
tablet: "Tablette"
@ -948,16 +948,16 @@ _ffVisibility:
_signup:
almostThere: "Bientôt fini"
emailAddressInfo: "Insérez votre adresse e-mail."
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\
\ terminer la création de votre compte."
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 terminer
la création de votre compte."
_accountDelete:
accountDelete: "Supprimer le compte"
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\
\ 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\
\ envoyé à l'adresse que vous aviez enregistrée."
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
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
envoyé à l'adresse que vous aviez enregistrée."
requestAccountDelete: "Demander la suppression de votre compte"
started: "La procédure de suppression a commencé."
inProgress: "Suppression en cours"
@ -965,14 +965,14 @@ _ad:
back: "Retour"
reduceFrequencyOfThisAd: "Voir cette publicité moins souvent"
_forgotPassword:
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\
\ adresse."
ifNoEmail: "Si vous n'avez pas enregistré d'adresse e-mail, merci de contacter l'administrateur·rice\
\ de votre instance."
contactAdmin: "Cette instance ne permettant pas l'utilisation d'adresses e-mail,\
\ prenez contact avec l'administrateur·rice pour procéder à la réinitialisation\
\ de votre mot de passe."
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
adresse."
ifNoEmail: "Si vous n'avez pas enregistré d'adresse e-mail, merci de contacter l'administrateur·rice
de votre instance."
contactAdmin: "Cette instance ne permettant pas l'utilisation d'adresses e-mail,
prenez contact avec l'administrateur·rice pour procéder à la réinitialisation
de votre mot de passe."
_gallery:
my: "Mes publications"
liked: " Publications que j'ai aimées"
@ -998,10 +998,10 @@ _aboutMisskey:
contributors: "Principaux contributeurs"
allContributors: "Tous les contributeurs"
source: "Code source"
translation: "Traduire Firefish"
donate: "Soutenir Firefish"
morePatrons: "Nous apprécions vraiment le soutien de nombreuses autres personnes\
\ non mentionnées ici. Merci à toutes et à tous ! \U0001F970"
translation: "Traduire Calckey"
donate: "Soutenir Calckey"
morePatrons: "Nous apprécions vraiment le soutien de nombreuses autres personnes
non mentionnées ici. Merci à toutes et à tous ! 🥰"
patrons: "Contributeurs"
_nsfw:
respect: "Cacher les médias marqués comme contenu sensible"
@ -1009,22 +1009,22 @@ _nsfw:
force: "Cacher tous les médias"
_mfm:
cheatSheet: "Antisèche MFM"
intro: "MFM est un langage Markdown spécifique utilisable ici et là dans Firefish.\
\ Vous pouvez vérifier ici les structures utilisables avec MFM."
dummy: "La Fédiverse s'agrandit avec 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."
dummy: "La Fédiverse s'agrandit avec Calckey"
mention: "Mentionner"
mentionDescription: "Vous pouvez afficher un utilisateur spécifique en indiquant\
\ une arobase suivie d'un nom d'utilisateur"
mentionDescription: "Vous pouvez afficher un utilisateur spécifique en indiquant
une arobase suivie d'un nom d'utilisateur"
hashtag: "Hashtags"
hashtagDescription: "Vous pouvez afficher un mot-dièse en utilisant un croisillon\
\ et du texte"
hashtagDescription: "Vous pouvez afficher un mot-dièse en utilisant un croisillon
et du texte"
url: "URL"
urlDescription: "L'adresse web peut être affichée."
link: "Lien"
linkDescription: "Une partie précise d'une phrase peut être liée à l'adresse web."
bold: "Gras"
boldDescription: "Il est possible de mettre le texte en exergue en le mettant en\
\ gras."
boldDescription: "Il est possible de mettre le texte en exergue en le mettant en
gras."
small: "Diminuer l'emphase"
smallDescription: "Le contenu peut être affiché en petit et fin."
center: "Centrer"
@ -1036,8 +1036,8 @@ _mfm:
inlineMath: "Formule mathématique (inline)"
inlineMathDescription: "Afficher les formules mathématiques (KaTeX)."
blockMath: "Formule mathématique (bloc)"
blockMathDescription: "Afficher les formules mathématiques (KaTeX) multi-lignes\
\ dans un bloc."
blockMathDescription: "Afficher les formules mathématiques (KaTeX) multi-lignes
dans un bloc."
quote: "Citer"
quoteDescription: "Affiche le contenu sous forme de citation."
emoji: "Émojis personnalisés"
@ -1067,8 +1067,8 @@ _mfm:
x4: "Plus grand"
x4Description: "Afficher le contenu en plus grand."
blur: "Flou"
blurDescription: "Le contenu peut être flouté ; il sera visible en le survolant\
\ avec le curseur."
blurDescription: "Le contenu peut être flouté ; il sera visible en le survolant
avec le curseur."
font: "Police de caractères"
fontDescription: "Il est possible de choisir la police."
rainbow: "Arc-en-ciel"
@ -1109,14 +1109,14 @@ _menuDisplay:
hide: "Masquer"
_wordMute:
muteWords: "Mots à filtrer"
muteWordsDescription: "Séparer avec des espaces pour la condition AND. Séparer avec\
\ un saut de ligne pour une condition OR."
muteWordsDescription2: "Pour utiliser des expressions régulières (regex), mettez\
\ les mots-clés entre barres obliques."
muteWordsDescription: "Séparer avec des espaces pour la condition AND. Séparer avec
un saut de ligne pour une condition OR."
muteWordsDescription2: "Pour utiliser des expressions régulières (regex), mettez
les mots-clés entre barres obliques."
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\
\ 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."
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
plus tard, les notes précédemment filtrées ne seront pas récupérées."
soft: "Doux"
hard: "Strict"
mutedNotes: "Notes filtrées"
@ -1155,10 +1155,10 @@ _theme:
darken: "Sombre"
lighten: "Clair"
inputConstantName: "Insérez un nom de constante"
importInfo: "Vous pouvez importer un thème vers léditeur de thèmes en saisissant\
\ son code ici."
deleteConstantConfirm: "Êtes-vous sûr·e de vouloir supprimer la constante {const}\
\ ?"
importInfo: "Vous pouvez importer un thème vers léditeur de thèmes en saisissant
son code ici."
deleteConstantConfirm: "Êtes-vous sûr·e de vouloir supprimer la constante {const}
?"
keys:
accent: "Accentuation"
bg: "Arrière-plan"
@ -1231,51 +1231,51 @@ _tutorial:
step1_1: "Bienvenue!"
step1_2: "On va vous installer. Vous serez opérationnel en un rien de temps"
step2_1: "Tout d'abord, remplissez votre profil"
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."
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."
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\
\ que vous suivez, alors essayez de suivre quelques comptes pour commencer.\n\
Cliquez sur le cercle plus en haut à droite d'un profil pour le suivre."
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.\nCliquez
sur le cercle plus en haut à droite d'un profil pour le suivre."
step4_1: "On y va."
step4_2: "Pour votre premier post, certaines personnes aiment faire un post {introduction}\
\ ou un simple post 'Hello world'."
step4_2: "Pour votre premier post, certaines personnes aiment faire un post {introduction}
ou un simple post 'Hello world'."
step5_1: "Lignes de temps, lignes de temps partout !"
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\
\ de vos followers."
step5_4: "La timeline locale {icon} est l'endroit où vous pouvez voir les messages\
\ de tout le monde sur cette instance."
step5_5: "La chronologie {icon} sociale est l'endroit où vous pouvez voir uniquement\
\ les publications des comptes que vous suivez."
step5_6: "La chronologie {icon} recommandée est l'endroit où vous pouvez voir les\
\ publications des instances recommandées par les administrateurs."
step5_7: "La timeline globale {icon} est l'endroit où vous pouvez voir les messages\
\ de toutes les autres instances connectées."
step5_3: "La timeline Home {icon} est l'endroit où vous pouvez voir les publications
de vos followers."
step5_4: "La timeline locale {icon} est l'endroit où vous pouvez voir les messages
de tout le monde sur cette instance."
step5_5: "La chronologie {icon} sociale est l'endroit où vous pouvez voir uniquement
les publications des comptes que vous suivez."
step5_6: "La chronologie {icon} recommandée est l'endroit où vous pouvez voir les
publications des instances recommandées par les administrateurs."
step5_7: "La timeline globale {icon} est l'endroit où vous pouvez voir les messages
de toutes les autres instances connectées."
step6_1: "Alors quel est cet endroit ?"
step6_2: "Eh bien, vous ne venez pas de rejoindre Firefish. Vous avez rejoint un\
\ portail vers le Fediverse, un réseau interconnecté de milliers de serveurs,\
\ appelés \"instances\"."
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\
\ le coup de main en un rien de temps."
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, appelés
\"instances\"."
step6_3: "Chaque serveur fonctionne différemment, et tous les serveurs n'utilisent
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."
step6_4: "Maintenant, allez-y, explorez et amusez-vous !"
_2fa:
alreadyRegistered: "Configuration déjà achevée."
registerTOTP: "Ajouter un nouvel appareil"
registerSecurityKey: "Enregistrer une clef"
step1: "Tout d'abord, installez une application d'authentification, telle que {a}\
\ ou {b}, sur votre appareil."
step1: "Tout d'abord, installez une application d'authentification, telle que {a}
ou {b}, sur votre appareil."
step2: "Ensuite, scannez le code QR affiché sur lécran."
step2Url: "Vous pouvez également saisir cette URL si vous utilisez un programme\
\ de bureau :"
step2Url: "Vous pouvez également saisir cette URL si vous utilisez un programme
de bureau :"
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\
\ connexions."
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\
\ prend en charge FIDO2, ou bien en configurant l'authentification par empreinte\
\ digitale ou par code PIN sur votre appareil."
step4: "À partir de maintenant, ce même jeton vous sera demandé à chacune de vos
connexions."
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
prend en charge FIDO2, ou bien en configurant l'authentification par empreinte
digitale ou par code PIN sur votre appareil."
_permissions:
"read:account": "Afficher les informations du 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"
_auth:
shareAccess: "Autoriser \"{name}\" à accéder à votre compte ?"
shareAccessAsk: "Voulez-vous vraiment autoriser cette application à accéder à votre\
\ compte?"
shareAccessAsk: "Voulez-vous vraiment autoriser cette application à accéder à votre
compte?"
permissionAsk: "Cette application nécessite les autorisations suivantes :"
pleaseGoBack: "Veuillez retourner à lapplication"
callback: "Retour vers lapplication"
@ -1412,8 +1412,8 @@ _profile:
youCanIncludeHashtags: "Vous pouvez également inclure des hashtags."
metadata: "Informations supplémentaires"
metadataEdit: "Éditer les informations supplémentaires"
metadataDescription: "Vous pouvez afficher jusqu'à quatre informations supplémentaires\
\ dans votre profil."
metadataDescription: "Vous pouvez afficher jusqu'à quatre informations supplémentaires
dans votre profil."
metadataLabel: "Étiquette"
metadataContent: "Contenu"
changeAvatar: "Changer l'image de profil"
@ -1487,8 +1487,8 @@ _pages:
url: "URL de la page"
summary: "Résumé de page"
alignCenter: "Centrée"
hideTitleWhenPinned: "Masquer le titre de la page lorsque celle-ci est épinglée\
\ au profil"
hideTitleWhenPinned: "Masquer le titre de la page lorsque celle-ci est épinglée
au profil"
font: "Police de caractères"
fontSerif: "Serif"
fontSansSerif: "Sans Serif"
@ -1538,8 +1538,8 @@ _pages:
note: "Note intégrée"
_note:
id: "Identifiant de la note"
idDescription: "Pour configurer la note, vous pouvez aussi coller ici l'URL\
\ correspondante."
idDescription: "Pour configurer la note, vous pouvez aussi coller ici l'URL
correspondante."
detailed: "Afficher les détails"
switch: "Interrupteur"
_switch:
@ -1692,8 +1692,8 @@ _pages:
_dailyRannum:
arg1: "Minimum"
arg2: "Maximum"
dailyRandomPick: "Sélectionné au hasard dans la liste (Quotidien pour chaque\
\ utilisateur)"
dailyRandomPick: "Sélectionné au hasard dans la liste (Quotidien pour chaque
utilisateur)"
_dailyRandomPick:
arg1: "Listes"
seedRandom: "Aléatoire (graine)"
@ -1709,8 +1709,8 @@ _pages:
_seedRandomPick:
arg1: "Graine"
arg2: "Listes"
DRPWPM: "Sélectionné au hasard dans une liste de probabilités (Quotidien pour\
\ chaque utilisateur)"
DRPWPM: "Sélectionné au hasard dans une liste de probabilités (Quotidien pour
chaque utilisateur)"
_DRPWPM:
arg1: "Liste de texte"
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.
swipeOnDesktop: Permettre le style de glissement de fenêtre de mobile sur PC
moveFromLabel: 'Compte depuis lequel vous migrez :'
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\
\ 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."
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
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."
_preferencesBackups:
updatedAt: 'Mis à jour le : {date} {time}'
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
séparé par une nouvelle ligne (s'applique uniquement en mode privé).
enableAutoSensitive: Marquage automatique du contenu sensible (NSFW)
regexpErrorDescription: "Il y a eu une erreur dans l'expression régulière à la ligne\
\ {line} de votre {tab} des mots masqués:"
regexpErrorDescription: "Il y a eu une erreur dans l'expression régulière à la ligne
{line} de votre {tab} des mots masqués:"
forwardReportIsAnonymous: À la place de votre compte, un compte système anonyme sera
affiché comme rapporteur à l'instance distante.
noThankYou: Non merci
@ -1944,16 +1944,15 @@ renoteMute: Mettre en silence les renotes
flagSpeakAsCat: Parler comme un chat
flagSpeakAsCatDescription: Vos messages seront nyanifiés en mode chat
hiddenTags: Hashtags cachés
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\
\ moyens. Les instances bloqués ne sont pas ne sont pas affectés, même si ils sont\
\ présent dans cette liste."
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
moyens. Les instances bloqués ne sont pas ne sont pas affectés, même si ils sont
présent dans cette liste."
antennaInstancesDescription: Lister un hôte d'instance par ligne
userSaysSomethingReason: '{name} a dit {reason}'
breakFollowConfirm: Êtes vous sur de vouloir retirer l'abonné ?
recommendedInstancesDescription: Instances recommandées séparées par une nouvelle
ligne pour apparaître dans la timeline recommandée. Ne PAS ajouter `https://`, SEULEMENT
le domaine.
ligne pour apparaître dans la timeline recommandée.
sendPushNotificationReadMessage: Supprimer les notifications push une fois que les
notifications ou messages concernés ont été lus
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
enableCustomKaTeXMacro: Activer les macros KaTeX personnalisées
noteId: ID de note
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\
\ \\newcommand{\\name}{content} ou \\newcommand{\\name}[number of arguments]{content}.\
\ Par exemple, \\newcommand{\\add}[2]{#1 + #2} étendra \\add{3}{foo} en 3 + foo.\
\ Les 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.\
\ 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.\
\ Seulement de simples fonctions de substitution de chaines sont supportées; la\
\ syntaxe avancée, telle que la ramification conditionnelle, ne peut pas être utilisée\
\ ici."
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
\\newcommand{\\name}{content} ou \\newcommand{\\name}[number of arguments]{content}.
Par exemple, \\newcommand{\\add}[2]{#1 + #2} étendra \\add{3}{foo} en 3 + foo. Les
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.
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.
Seulement de simples fonctions de substitution de chaines sont supportées; la syntaxe
avancée, telle que la ramification conditionnelle, ne peut pas être utilisée ici."
enableRecommendedTimeline: Activer la chronologie recommandée
silenceThisInstance: Ne plus montrer cet instance
silencedInstances: Instances silencieuses
@ -2038,3 +2036,4 @@ signupsDisabled: Les inscriptions sur ce serveur sont actuellement désactivés,
apps: Applications
userSaysSomethingReasonReply: '{noms} a répondu à une note contenant {raison}'
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にリサイズしてください。"
showUpdates: "Firefishの更新時にポップアップを表示する"
recommendedInstances: "おすすめサーバー"
recommendedInstancesDescription: "おすすめタイムラインに表示するサーバーを改行区切りで入力してください。`https://`は書かず、ドメインのみを入力してください。"
recommendedInstancesDescription: "おすすめタイムラインに表示するサーバーを改行区切りで入力してください。"
caption: "自動キャプション"
splash: "スプラッシュスクリーン"
updateAvailable: "アップデートがありますよ!"
@ -977,7 +977,14 @@ customKaTeXMacroDescription: "数式入力を楽にするためのマクロを
enableCustomKaTeXMacro: "カスタムKaTeXマクロを有効にする"
preventAiLearning: "AIによる学習を防止"
preventAiLearningDescription: "投稿したート、添付した画像などのコンテンツを学習の対象にしないようAIに要求します。これはnoaiフラグをHTMLレスポンスに含めることによって実現されます。"
noGraze: "ブラウザの拡張機能「Graze for Mastodon」は、Firefishの動作を妨げるため、無効にしてください。"
noGraze: "ブラウザの拡張機能「Graze for Mastodon」は、Calckeyの動作を妨げるため、無効にしてください。"
enableServerMachineStats: "サーバーのマシン情報を公開する"
enableIdenticonGeneration: "ユーザーごとのIdenticon生成を有効にする"
showPopup: "ポップアップを表示してユーザーに知らせる"
showWithSparkles: "タイトルをキラキラさせる"
youHaveUnreadAnnouncements: "未読のお知らせがあります"
neverShow: "今後表示しない"
remindMeLater: "また後で"
_sensitiveMediaDetection:
description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てられます。サーバーの負荷が少し増えます。"
@ -1062,6 +1069,11 @@ _aboutMisskey:
donate: "Firefishに寄付"
morePatrons: "他にも多くの方が支援してくれています。ありがとうございます! 🥰"
patrons: "支援者"
patronsList: 寄付額ではなく時系列順に並んでいます。上記のリンクから寄付を行ってここにあなたのIDを載せましょう
pleaseDonateToCalckey: Calckey開発への寄付をご検討ください。
pleaseDonateToHost: また、このサーバー {host} の運営者への寄付もご検討ください。
donateHost: '{host} に寄付する'
donateTitle: Calckeyを気に入りましたか
_nsfw:
respect: "閲覧注意のメディアは隠す"
ignore: "閲覧注意のメディアを隠さない"
@ -1373,11 +1385,12 @@ _permissions:
_auth:
shareAccess: "「{name}」がアカウントにアクセスすることを許可しますか?"
shareAccessAsk: "アカウントへのアクセスを許可しますか?"
permissionAsk: "このアプリケーションは次の権限を要求しています"
permissionAsk: "このアプリケーションは次の権限を要求しています:"
pleaseGoBack: "アプリケーションに戻り続行してください"
callback: "アプリケーションに戻っています"
denied: "アクセスを拒否しました"
copyAsk: "以下の認証コードをアプリケーションにコピーしてください"
copyAsk: "以下の認証コードをアプリケーションにコピーしてください:"
allPermissions: 全てのアクセス権
_antennaSources:
all: "全ての投稿"
homeTimeline: "フォローしているユーザーの投稿"
@ -1451,11 +1464,11 @@ _poll:
remainingSeconds: "終了まであと{s}秒"
_visibility:
public: "公開"
publicDescription: "全てのユーザーに公開"
publicDescription: "全ての公開タイムラインに配信されます"
home: "未収載"
homeDescription: "ホームタイムラインのみに公開"
followers: "フォロワー"
followersDescription: "自分のフォロワーのみに公開"
followersDescription: "フォロワーと会話相手のみに公開"
specified: "ダイレクト"
specifiedDescription: "指定したユーザーのみに公開"
localOnly: "ローカルのみ"
@ -1889,7 +1902,7 @@ apps: "アプリ"
_experiments:
title: 試験的な機能
postImportsCaption:
ユーザーが過去の投稿をFirefish・Misskey・Mastodon・Akkoma・Pleromaからインポートすることを許可します。キューが溜まっているときにインポートするとサーバーに負荷がかかる可能性があります。
ユーザーが過去の投稿をCalckey・Misskey・Mastodon・Akkoma・Pleromaからインポートすることを許可します。キューが溜まっているときにインポートするとサーバーに負荷がかかる可能性があります。
enablePostImports: 投稿のインポートを有効にする
sendModMail: モデレーション通知を送る
deleted: 削除済み
@ -1929,4 +1942,20 @@ video: 動画
isBot: このアカウントはBotです
isLocked: このアカウントのフォローは承認制です
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"
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
followers: Seguidores
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: Рекомендуемые инстансы, разделенные разрывами строк,
должны отображаться на рекомендуемой ленте. НЕ добавляйте `https://`, ТОЛЬКО домен.
должны отображаться на рекомендуемой ленте.
caption: Автоматическая подпись
splash: Заставка
updateAvailable: Возможно, доступно обновление!

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,9 @@
---
_lang_: "Українська"
headlineMisskey: "Мережа об'єднана записами"
introMisskey: "Ласкаво просимо! Firefish - децентралізована служба мікроблогів з відкритим кодом.\nСтворюйте \"нотатки\", щоб поділитися тим, що відбувається, і розповісти всім про себе 📡\nЗа допомогою \"реакцій\" ви також можете швидко висловити свої почуття щодо нотаток інших 👍\nДосліджуймо новий світ! 🚀"
introMisskey: "Ласкаво просимо! Calckey - децентралізована служба мікроблогів з відкритим
кодом.\nСтворюйте \"нотатки\", щоб поділитися тим, що відбувається, і розповісти
всім про себе 📡\nЗа допомогою \"реакцій\" ви також можете швидко висловити свої
почуття щодо нотаток інших 👍\nДосліджуймо новий світ! 🚀"
monthAndDay: "{month}/{day}"
search: "Пошук"
notifications: "Сповіщення"
@ -14,16 +16,16 @@ gotIt: "Зрозуміло!"
cancel: "Скасувати"
enterUsername: "Введіть ім'я користувача"
renotedBy: "Поширено {user}"
noNotes: "Немає нотаток"
noNotes: "Немає записів"
noNotifications: "Немає сповіщень"
instance: "Інстанс"
instance: "Сервер"
settings: "Налаштування"
basicSettings: "Основні налаштування"
otherSettings: "Інші налаштування"
openInWindow: "Відкрити у вікні"
profile: "Профіль"
timeline: "Стрічка"
noAccountDescription: "Цей користувач ще нічого не написав про себе"
noAccountDescription: "Цей користувач ще нічого не написав про себе."
login: "Увійти"
loggingIn: "Здійснюємо вхід..."
logout: "Вийти"
@ -44,7 +46,8 @@ copyContent: "Скопіювати контент"
copyLink: "Скопіювати посилання"
delete: "Видалити"
deleteAndEdit: "Видалити й редагувати"
deleteAndEditConfirm: "Ви впевнені, що хочете видалити цю нотатку та відредагувати її? Ви втратите всі реакції, поширення та відповіді на неї."
deleteAndEditConfirm: "Ви впевнені, що хочете видалити цей запис та відредагувати
його? Ви втратите всі реакції, поширення та відповіді на нього."
addToList: "Додати до списку"
sendMessage: "Надіслати повідомлення"
copyUsername: "Скопіювати ім’я користувача"
@ -64,9 +67,11 @@ import: "Імпорт"
export: "Експорт"
files: "Файли"
download: "Завантажити"
driveFileDeleteConfirm: "Ви впевнені, що хочете видалити файл {name}? Нотатки із цим файлом також буде видалено."
driveFileDeleteConfirm: "Ви впевнені, що хочете видалити файл {name}? Його буде видалено
з усіх записів які містили його."
unfollowConfirm: "Ви впевнені, що хочете відписатися від {name}?"
exportRequested: "Експортування розпочато. Це може зайняти деякий час. Після завершення експорту отриманий файл буде додано на диск."
exportRequested: "Експортування розпочато. Це може зайняти деякий час. Після завершення
експорту отриманий файл буде додано на диск."
importRequested: "Імпортування розпочато. Це може зайняти деякий час."
lists: "Списки"
noLists: "Немає списків"
@ -80,10 +85,12 @@ manageLists: "Управління списками"
error: "Помилка"
somethingHappened: "Щось пішло не так"
retry: "Спробувати знову"
pageLoadError: "Помилка при завантаженні сторінки"
pageLoadErrorDescription: "Зазвичай це пов’язано з помилками мережі або кешем браузера. Очистіть кеш або почекайте трохи й спробуйте ще раз."
pageLoadError: "Помилка при завантаженні сторінки."
pageLoadErrorDescription: "Зазвичай це пов’язано з помилками мережі або кешем браузера.
Очистіть кеш або почекайте трохи й спробуйте ще раз."
serverIsDead: "Відповіді від сервера немає. Зачекайте деякий час і повторіть спробу."
youShouldUpgradeClient: "Перезавантажте та використовуйте нову версію клієнта, щоб переглянути цю сторінку."
youShouldUpgradeClient: "Перезавантажте та використовуйте нову версію клієнта, щоб
переглянути цю сторінку."
enterListName: "Введіть назву списку"
privacy: "Конфіденційність"
makeFollowManuallyApprove: "Підтверджувати підписників уручну"
@ -95,9 +102,9 @@ unfollow: "Відписатись"
followRequestPending: "Очікуючі запити на підписку"
enterEmoji: "Введіть емодзі"
renote: "Поширити"
unrenote: "Відміна поширення"
renoted: "Поширити запис."
cantRenote: "Неможливо поширити."
unrenote: "скасувати поширення"
renoted: "Поширено."
cantRenote: "Цей запис неможливо поширити."
cantReRenote: "Поширення неможливо поширити."
quote: "Цитата"
pinnedNote: "Закріплений запис"
@ -108,7 +115,8 @@ sensitive: "NSFW"
add: "Додати"
reaction: "Реакції"
reactionSetting: "Налаштування реакцій"
reactionSettingDescription2: "Перемістити щоб змінити порядок, Клацнути мишою щоб видалити, Натиснути \"+\" щоб додати."
reactionSettingDescription2: "Перемістити щоб змінити порядок, Клацнути мишою щоб
видалити, Натиснути \"+\" щоб додати."
rememberNoteVisibility: "Пам’ятати параметри видимісті"
attachCancel: "Видалити вкладення"
markAsSensitive: "Позначити як NSFW"
@ -137,14 +145,20 @@ emojiUrl: "URL емодзі"
addEmoji: "Додати емодзі"
settingGuide: "Рекомендована конфігурація"
cacheRemoteFiles: "Кешувати дані з інших інстансів"
cacheRemoteFilesDescription: "Якщо кешування вимкнено, віддалені файли завантажуються безпосередньо з віддаленого інстансу. Це зменшує використання сховища, але збільшує трафік, оскільки не генеруются ескізи."
cacheRemoteFilesDescription: "Якщо кешування вимкнено, віддалені файли завантажуються
безпосередньо з віддаленого серверу. Це зменшує використання сховища, але збільшує
трафік, оскільки не генеруются ескізи."
flagAsBot: "Акаунт бота"
flagAsBotDescription: "Ввімкніть якщо цей обліковий запис використовується ботом. Ця опція позначить обліковий запис як бота. Це потрібно щоб виключити безкінечну інтеракцію між ботами а також відповідного підлаштування Firefish."
flagAsBotDescription: "Ввімкніть якщо цей обліковий запис використовується ботом.
Ця опція позначить обліковий запис як бота. Це потрібно щоб виключити безкінечну
інтеракцію між ботами а також відповідного підлаштування Calckey."
flagAsCat: "Акаунт кота"
flagAsCatDescription: "Ввімкніть, щоб позначити, що обліковий запис є котиком."
flagShowTimelineReplies: "Показувати відповіді на нотатки на часовій шкалі"
flagShowTimelineRepliesDescription: "Показує відповіді користувачів на нотатки інших користувачів на часовій шкалі."
autoAcceptFollowed: "Автоматично приймати запити на підписку від користувачів, на яких ви підписані"
flagShowTimelineRepliesDescription: "Показує відповіді користувачів на нотатки інших
користувачів на часовій шкалі."
autoAcceptFollowed: "Автоматично приймати запити на підписку від користувачів, на
яких ви підписані"
addAccount: "Додати акаунт"
loginFailed: "Не вдалося увійти"
showOnRemote: "Переглянути в оригіналі"
@ -156,13 +170,17 @@ searchWith: "Пошук: {q}"
youHaveNoLists: "У вас немає списків"
followConfirm: "Підписатися на {name}?"
proxyAccount: "Проксі-акаунт"
proxyAccountDescription: "Обліковий запис проксі це обліковий запис, який діє як віддалений підписник для користувачів за певних умов. Наприклад, коли користувач додає віддаленого користувача до списку, активність віддаленого користувача не буде доставлена на сервер, якщо жоден локальний користувач не стежить за цим користувачем, то замість нього буде використовуватися обліковий запис проксі-сервера."
proxyAccountDescription: "Обліковий запис проксі це обліковий запис, який діє як
віддалений підписник для користувачів за певних умов. Наприклад, коли користувач
додає віддаленого користувача до списку, активність віддаленого користувача не буде
доставлена на сервер, якщо жоден локальний користувач не стежить за цим користувачем,
то замість нього буде використовуватися обліковий запис проксі-сервера."
host: "Хост"
selectUser: "Виберіть користувача"
recipient: "Отримувач"
annotation: "Коментарі"
federation: "Федіверс"
instances: "Інстанс"
instances: "Сервери"
registeredAt: "Приєднався(лась)"
latestRequestSentAt: "Останній запит надіслано"
latestRequestReceivedAt: "Останній запит прийнято"
@ -172,7 +190,7 @@ charts: "Графіки"
perHour: "Щогодинно"
perDay: "Щоденно"
stopActivityDelivery: "Припинити розсилання активності"
blockThisInstance: "Заблокувати цей інстанс"
blockThisInstance: "Заблокувати цей сервер"
operations: "Операції"
software: "Програмне забезпечення"
version: "Версія"
@ -182,15 +200,17 @@ jobQueue: "Черга завдань"
cpuAndMemory: "ЦП та пам'ять"
network: "Мережа"
disk: "Диск"
instanceInfo: "Про цей інстанс"
instanceInfo: "Про цей сервер"
statistics: "Статистика"
clearQueue: "Очистити чергу"
clearQueueConfirmTitle: "Ви впевнені, що хочете очистити чергу?"
clearQueueConfirmText: "Будь-які невідправлені нотатки, що залишилися в черзі, не будуть передані. Зазвичай ця операція НЕ потрібна."
clearQueueConfirmText: "Будь-які невідправлені записи, що залишилися в черзі, не будуть
передані. Зазвичай ця операція НЕ потрібна."
clearCachedFiles: "Очистити кеш"
clearCachedFilesConfirm: "Ви впевнені, що хочете видалити всі кешовані файли?"
blockedInstances: "Заблоковані інстанси"
blockedInstancesDescription: "Вкажіть інстанси, які потрібно заблокувати. Перелічені інстанси більше не зможуть спілкуватися з цим інстансом."
blockedInstances: "Заблоковані сервери"
blockedInstancesDescription: "Вкажіть сервери, які потрібно заблокувати. Перелічені
сервери більше не зможуть спілкуватися з цим сервером."
muteAndBlock: "Заглушення і блокування"
mutedUsers: "Заглушені користувачі"
blockedUsers: "Заблоковані користувачі"
@ -213,8 +233,8 @@ subscribing: "Підписка"
publishing: "Публікація"
notResponding: "Не відповідає"
instanceFollowing: "Підписка на інстанс"
instanceFollowers: "Підписники інстансу"
instanceUsers: "Користувачі цього інстансу"
instanceFollowers: "Підписники серверу"
instanceUsers: "Користувачі цього серверу"
changePassword: "Змінити пароль"
security: "Безпека"
retypedNotMatch: "Введені дані не збігаються."
@ -238,7 +258,8 @@ saved: "Збережено"
messaging: "Чати"
upload: "Завантажити"
keepOriginalUploading: "Зберегти оригінальне зображення"
keepOriginalUploadingDescription: "Зберігає початково завантажене зображення як є. Якщо вимкнено, версія для відображення в Інтернеті буде створена під час завантаження."
keepOriginalUploadingDescription: "Зберігає початково завантажене зображення як є.
Якщо вимкнено, версія для відображення в Інтернеті буде створена під час завантаження."
fromDrive: "З диска"
fromUrl: "З посилання"
uploadFromUrl: "Завантажити з посилання"
@ -288,7 +309,7 @@ inputNewFileName: "Введіть ім'я нового файлу"
inputNewDescription: "Введіть новий заголовок"
inputNewFolderName: "Введіть ім'я нової теки"
circularReferenceFolder: "Ви намагаєтесь перемістити папку в її підпапку."
hasChildFilesOrFolders: "Ця тека не порожня і не може бути видалена"
hasChildFilesOrFolders: "Ця тека не порожня і не може бути видалена."
copyUrl: "Копіювати URL"
rename: "Перейменувати"
avatar: "Аватар"
@ -304,8 +325,8 @@ unwatch: "Не стежити"
accept: "Прийняти"
reject: "Відхилити"
normal: "Нормальний"
instanceName: "Назва інстансу"
instanceDescription: "Описання інстансу"
instanceName: "Назва серверу"
instanceDescription: "Опис серверу"
maintainerName: "Ім'я адміністратора"
maintainerEmail: "Email адміністратора"
tosUrl: "URL умов використання"
@ -316,12 +337,13 @@ dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
pages: "Сторінки"
integration: "Інтеграція"
integration: "Інтеграції"
connectService: "Під’єднати"
disconnectService: "Відключитися"
enableLocalTimeline: "Увімкнути локальну стрічку"
enableGlobalTimeline: "Увімкнути глобальну стрічку"
disablingTimelinesInfo: "Адміністратори та модератори завжди мають доступ до всіх стрічок, навіть якщо вони вимкнуті."
disablingTimelinesInfo: "Адміністратори та модератори завжди мають доступ до всіх
стрічок, навіть якщо вони вимкнуті."
registration: "Реєстрація"
enableRegistration: "Дозволити реєстрацію"
invite: "Запросити"
@ -333,11 +355,13 @@ bannerUrl: "URL банера"
backgroundImageUrl: "URL-адреса фонового зображення"
basicInfo: "Основна інформація"
pinnedUsers: "Закріплені користувачі"
pinnedUsersDescription: "Впишіть в список користувачів, яких хочете закріпити на сторінці \"Знайти\", ім'я в стовпчик."
pinnedUsersDescription: "Впишіть в список користувачів, яких хочете закріпити на сторінці
\"Знайти\", ім'я в стовпчик."
pinnedPages: "Закріплені сторінки"
pinnedPagesDescription: "Введіть шляхи сторінок, які ви бажаєте закріпити на головній сторінці цього інстанса, розділені новими рядками."
pinnedClipId: "Ідентифікатор закріпленої замітки."
pinnedNotes: "Закріплена нотатка"
pinnedPagesDescription: "Введіть шляхи сторінок, які ви бажаєте закріпити на головній
сторінці цього інстанса, розділені новими рядками."
pinnedClipId: "Ідентифікатор закріпленої замітки"
pinnedNotes: "Закріплений запис"
hcaptcha: "hCaptcha"
enableHcaptcha: "Увімкнути hCaptcha"
hcaptchaSiteKey: "Ключ сайту"
@ -346,22 +370,25 @@ recaptcha: "reCAPTCHA"
enableRecaptcha: "Увімкнути reCAPTCHA"
recaptchaSiteKey: "Ключ сайту"
recaptchaSecretKey: "Секретний ключ"
avoidMultiCaptchaConfirm: "Використання кількох систем Captcha може спричинити перешкоди між ними. Бажаєте вимкнути інші активні системи Captcha? Якщо ви хочете, щоб вони залишалися ввімкненими, натисніть «Скасувати»."
avoidMultiCaptchaConfirm: "Використання кількох систем Captcha може спричинити перешкоди
між ними. Бажаєте вимкнути інші активні системи Captcha? Якщо ви хочете, щоб вони
залишалися ввімкненими, натисніть «Скасувати»."
antennas: "Антени"
manageAntennas: "Налаштування антен"
name: "Ім'я"
antennaSource: "Джерело антени"
antennaKeywords: "Ключові слова антени"
antennaExcludeKeywords: "Винятки"
antennaKeywordsDescription: "Розділення ключових слів пробілами для \"І\" або з нової лінійки для \"АБО\""
notifyAntenna: "Сповіщати про нові нотатки"
withFileAntenna: "Тільки нотатки з вкладеними файлами"
antennaKeywordsDescription: "Відокремте пробілами для умови \"І\" або перенесенням
до нового рядка для умови \"АБО\"."
notifyAntenna: "Сповіщати про нові записи"
withFileAntenna: "Тільки записи з вкладеними файлами"
enableServiceworker: "Ввімкнути ServiceWorker"
antennaUsersDescription: "Список імя користувачів в стопчик"
caseSensitive: "З урахуванням регістру"
withReplies: "Включаючи відповіді"
connectedTo: "Наступні акаунти під'єднані"
notesAndReplies: "Нотатки та відповіді"
notesAndReplies: "Записи та відповіді"
withFiles: "Файли"
silence: "Заглушити"
silenceConfirm: "Ви впевнені, що хочете заглушити цього користувача?"
@ -397,7 +424,7 @@ notFoundDescription: "Сторінка за вказаною адресою не
uploadFolder: "Місце для завантаження за замовчуванням"
cacheClear: "Очистити кеш"
markAsReadAllNotifications: "Позначити всі сповіщення як прочитані"
markAsReadAllUnreadNotes: "Позначити всі нотатки як прочитані"
markAsReadAllUnreadNotes: "Позначити всі записи як прочитані"
markAsReadAllTalkMessages: "Позначити всі повідомлення як прочитані"
help: "Допомога"
inputMessageHere: "Введіть повідомлення тут"
@ -418,7 +445,7 @@ text: "Текст"
enable: "Увімкнути"
next: "Далі"
retype: "Введіть ще раз"
noteOf: "Нотатка {user}"
noteOf: "Запис {user}"
inviteToGroup: "Запрошення до групи"
quoteAttached: "Цитата"
quoteQuestion: "Ви хочете додати цитату?"
@ -431,7 +458,8 @@ invitationCode: "Код запрошення"
checking: "Перевірка…"
available: "Доступно"
unavailable: "Недоступно"
usernameInvalidFormat: "літери, цифри та _ є прийнятними"
usernameInvalidFormat: "Ви можете використовувати великі та малі літери, цифри та
підкреслення."
tooShort: "Занадто короткий"
tooLong: "Занадто довгий"
weakPassword: "Слабкий пароль"
@ -454,7 +482,7 @@ joinOrCreateGroup: "Отримуйте запрошення до груп або
noHistory: "Історія порожня"
signinHistory: "Історія входів"
disableAnimatedMfm: "Відключити анімації MFM"
doing: "Виконується"
doing: "Виконується..."
category: "Категорія"
tags: "Теги"
docSource: "Джерело цього документа"
@ -476,29 +504,34 @@ accountSettings: "Налаштування акаунта"
promotion: "Виділене"
promote: "Виділити"
numberOfDays: "Кількість днів"
hideThisNote: "Сховати цю нотатку"
showFeaturedNotesInTimeline: "Показувати популярні нотатки у стрічці"
hideThisNote: "Сховати цей запис"
showFeaturedNotesInTimeline: "Показувати популярні записи у стрічці"
objectStorage: "Object Storage"
useObjectStorage: "Використовувати object storage"
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"
objectStorageBucketDesc: "Будь ласка вкажіть назву відра в налаштованому сервісі."
objectStoragePrefix: "Prefix"
objectStoragePrefixDesc: "Файли будуть зберігатись у розташуванні з цим префіксом."
objectStorageEndpoint: "Endpoint"
objectStorageEndpointDesc: "Залиште пустим при використанні AWS S3. Інакше введіть кінцевий пункт як '<host>' або '<host>:<port>' слідуючи інструкціям сервісу, який використовується."
objectStorageEndpoint: "Кінцевий пункт"
objectStorageEndpointDesc: "Залиште пустим при використанні AWS S3. Інакше введіть
кінцевий пункт як '<host>' або '<host>:<port>' слідуючи інструкціям сервісу, який
використовується."
objectStorageRegion: "Region"
objectStorageRegionDesc: "Введіть регіон у формі 'xx-east-1'. Залиште пустим, якщо ваш сервіс не різниться відповідно до регіонів, або введіть 'us-east-1'."
objectStorageRegionDesc: "Введіть регіон у формі 'xx-east-1'. Залиште пустим, якщо
ваш сервіс не різниться відповідно до регіонів, або введіть 'us-east-1'."
objectStorageUseSSL: "Використовувати SSL"
objectStorageUseSSLDesc: "Вимкніть коли не використовується HTTPS для з'єднання API"
objectStorageUseProxy: "Використовувати Proxy"
objectStorageUseProxyDesc: "Вимкніть коли проксі не використовується для з'єднання ObjectStorage"
objectStorageUseProxyDesc: "Вимкніть коли проксі не використовується для з'єднання
ObjectStorage"
objectStorageSetPublicRead: "Встановіть 'публічне читання' при завантаженні"
serverLogs: "Журнал сервера"
deleteAll: "Видалити все"
showFixedPostForm: "Показати форму запису над стрічкою новин."
newNoteRecived: "Є нові нотатки"
showFixedPostForm: "Показати форму запису над стрічкою новин"
newNoteRecived: "Є нові записи"
sounds: "Звуки"
listen: "Слухати"
none: "Відсутній"
@ -521,7 +554,8 @@ sort: "Сортування"
ascendingOrder: "За зростанням"
descendingOrder: "За спаданням"
scratchpad: "Чернетка"
scratchpadDescription: "Scratchpad надає середовище для експериментів з AiScript. Ви можете писати, виконувати його і тестувати взаємодію з Firefish."
scratchpadDescription: "Scratchpad надає середовище для експериментів з AiScript.
Ви можете писати, виконувати його і тестувати взаємодію з Calckey."
output: "Вихід"
script: "Скрипт"
disablePagesScript: "Вимкнути AiScript на Сторінках"
@ -529,11 +563,14 @@ updateRemoteUser: "Оновити інформацію про віддалено
deleteAllFiles: "Видалити всі файли"
deleteAllFilesConfirm: "Ви дійсно хочете видалити всі файли?"
removeAllFollowing: "Скасувати всі підписки"
removeAllFollowingDescription: "Скасувати підписку на всі акаунти з {host}. Будь ласка, робіть це, якщо інстанс більше не існує."
removeAllFollowingDescription: "Скасувати підписку на всі акаунти з {host}. Будь ласка,
робіть це, якщо сервер більше не існує."
userSuspended: "Обліковий запис заблокований."
userSilenced: "Обліковий запис приглушений."
yourAccountSuspendedTitle: "Цей обліковий запис заблоковано"
yourAccountSuspendedDescription: "Цей обліковий запис було заблоковано через порушення умов надання послуг сервера. Зв'яжіться з адміністратором, якщо ви хочете дізнатися докладнішу причину. Будь ласка, не створюйте новий обліковий запис."
yourAccountSuspendedDescription: "Цей обліковий запис було заблоковано через порушення
умов надання послуг сервера. Зв'яжіться з адміністратором, якщо ви хочете дізнатися
докладнішу причину. Будь ласка, не створюйте новий обліковий запис."
menu: "Меню"
divider: "Розділювач"
addItem: "Додати елемент"
@ -542,8 +579,8 @@ addRelay: "Додати ретранслятор"
inboxUrl: "Inbox URL"
addedRelays: "Додані ретранслятори"
serviceworkerInfo: "Повинен бути ввімкнений для push-сповіщень."
deletedNote: "Видалена нотатка"
invisibleNote: "Приховані записи"
deletedNote: "Видалений запис"
invisibleNote: "Прихований запис"
enableInfiniteScroll: "Увімкнути нескінченну прокрутку"
visibility: "Видимість"
poll: "Опитування"
@ -573,12 +610,14 @@ permission: "Права"
enableAll: "Увімкнути все"
disableAll: "Вимкнути все"
tokenRequested: "Надати доступ до акаунту"
pluginTokenRequestedDescription: "Цей плагін зможе використовувати дозволи які тут вказані."
pluginTokenRequestedDescription: "Цей плагін зможе використовувати дозволи які тут
вказані."
notificationType: "Тип сповіщення"
edit: "Редагувати"
emailServer: "Сервер електронної пошти"
enableEmail: "Увімкнути функцію доставки пошти"
emailConfigInfo: "Використовується для підтвердження електронної пошти підчас реєстрації, а також для відновлення паролю."
emailConfigInfo: "Використовується для підтвердження електронної пошти під час реєстрації,
а також для відновлення паролю"
email: "E-mail"
emailAddress: "E-mail адреса"
smtpConfig: "Налаштування сервера SMTP"
@ -586,14 +625,16 @@ smtpHost: "Хост"
smtpPort: "Порт"
smtpUser: "Ім'я користувача"
smtpPass: "Пароль"
emptyToDisableSmtpAuth: "Залиште назву користувача і пароль пустими для вимкнення підтвердження SMTP"
emptyToDisableSmtpAuth: "Залиште назву користувача і пароль пустими для вимкнення
підтвердження SMTP"
smtpSecure: "Використовувати безумовне шифрування SSL/TLS для з'єднань SMTP"
smtpSecureInfo: "Вимкніть при використанні STARTTLS"
testEmail: "Тестовий email"
wordMute: "Блокування слів"
regexpError: "Помилка регулярного виразу"
regexpErrorDescription: "Сталася помилка в регулярному виразі в рядку {line} вашого слова {tab} слова що ігноруються:"
instanceMute: "Приглушення інстансів"
regexpErrorDescription: "Сталася помилка в регулярному виразі в рядку {line} вашого
слова {tab} слова що ігноруються:"
instanceMute: "Приглушення серверів"
userSaysSomething: "{name} щось сказав(ла)"
makeActive: "Активувати"
display: "Відображення"
@ -606,12 +647,15 @@ database: "База даних"
channel: "Канали"
create: "Створити"
notificationSetting: "Параметри сповіщень"
notificationSettingDesc: "Виберіть типи сповіщень для відображення"
notificationSettingDesc: "Оберіть типи сповіщень для відображення."
useGlobalSetting: "Застосувати глобальнi параметри"
useGlobalSettingDesc: "Якщо увімкнено, то будуть використовуватись налаштування повідомлень облікового запису, інакше можливо налаштувати індивідуально."
useGlobalSettingDesc: "Якщо увімкнено, то будуть використовуватись налаштування повідомлень
облікового запису, інакше можливо налаштувати індивідуально."
other: "Інше"
regenerateLoginToken: "Оновити Login Token"
regenerateLoginTokenDescription: "Регенерувати внутрішній ключ використовуваний під час входу. Зазвичай цього не потрібно робити. При регенерації всі пристрої вийдуть з системи."
regenerateLoginTokenDescription: "Регенерувати внутрішній ключ використовуваний під
час входу. Зазвичай цього не потрібно робити. При регенерації всі пристрої вийдуть
з системи."
setMultipleBySeparatingWithSpace: "Можна вказати кілька значень, відділивши їх пробілом."
fileIdOrUrl: "Ідентифікатор файлу або посилання"
behavior: "Поведінка"
@ -619,19 +663,22 @@ sample: "Приклад"
abuseReports: "Скарги"
reportAbuse: "Поскаржитись"
reportAbuseOf: "Поскаржитись на {name}"
fillAbuseReportDescription: "Будь ласка вкажіть подробиці скарги. Якщо скарга стосується запису, вкажіть посилання на нього."
fillAbuseReportDescription: "Будь ласка вкажіть подробиці скарги. Якщо скарга стосується
запису, вкажіть посилання на нього."
abuseReported: "Дякуємо, вашу скаргу було відправлено. "
reporter: "Репортер"
reporteeOrigin: "Про кого повідомлено"
reporterOrigin: "Хто повідомив"
forwardReport: "Переслати звіт на віддалений інстанс"
forwardReportIsAnonymous: "Замість вашого облікового запису анонімний системний обліковий запис буде відображатися як доповідач на віддаленому інстансі"
forwardReportIsAnonymous: "Замість вашого облікового запису, анонімний системний обліковий
запис буде відображатися як доповідач на віддаленому сервері."
send: "Відправити"
abuseMarkAsResolved: "Позначити скаргу як вирішену"
openInNewTab: "Відкрити в новій вкладці"
openInSideView: "Відкрити збоку"
defaultNavigationBehaviour: "Поведінка навігації за замовчуванням"
editTheseSettingsMayBreakAccount: "Зміна цих параметрів може призвести до пошкодження вашого акаунта."
editTheseSettingsMayBreakAccount: "Зміна цих параметрів може призвести до пошкодження
вашого акаунта."
instanceTicker: "Мітка з назвою інстанса в нотатках"
waitingFor: "Чекаємо на {x}"
random: "Випадковий"
@ -643,10 +690,11 @@ createNew: "Створити новий"
optional: "Необов'язково"
createNewClip: "Створити нотатку"
public: "Публічний"
i18nInfo: "Firefish перекладається на різні мови волонтерами. Ви можете допомогти: {link}"
i18nInfo: "Calckey перекладається на різні мови волонтерами. Ви можете допомогти за
посиланням: {link}."
manageAccessTokens: "Керування токенами доступу"
accountInfo: "Інформація про акаунт"
notesCount: "Кількість нотаток"
notesCount: "Кількість записів"
repliesCount: "Кількість надісланих відповідей"
renotesCount: "Кількість поширень"
repliedCount: "Кількість отриманих відповідей"
@ -662,15 +710,19 @@ no: "Ні"
driveFilesCount: "Кількість файлів на диску"
driveUsage: "Використання місця на диску"
noCrawle: "Заборонити індексацію"
noCrawleDescription: "Просити пошукові системи не індексувати ваш профіль, нотатки, сторінки тощо."
lockedAccountInfo: "Якщо видимість вашого запису не встановлена як \"Тільки підписники\", то кожен зможе побачити ваш запис, навіть якщо ви вимагаєте підтвердження підписок вручну."
alwaysMarkSensitive: "Позначати NSFW за замовчуванням"
noCrawleDescription: "Просити пошукові системи не індексувати ваш профіль, записи,
сторінки тощо."
lockedAccountInfo: "Якщо видимість вашого запису не встановлена як \"Тільки підписники\"\
, то кожен зможе побачити ваш запис, навіть якщо ви вимагаєте підтвердження підписок
вручну."
alwaysMarkSensitive: "Позначати як NSFW за замовчуванням"
loadRawImages: "Відображати вкладені зображення повністю замість ескізів"
disableShowingAnimatedImages: "Не програвати анімовані зображення"
verificationEmailSent: "Електронний лист з підтвердженням відісланий. Будь ласка перейдіть по посиланню в листі для підтвердження."
verificationEmailSent: "Електронний лист з підтвердженням відісланий. Будь ласка перейдіть
по посиланню в листі для підтвердження."
notSet: "Не налаштовано"
emailVerified: "Електронну пошту підтверджено."
noteFavoritesCount: "Кількість улюблених нотаток"
emailVerified: "Електронну пошту підтверджено"
noteFavoritesCount: "Кількість улюблених записів"
pageLikesCount: "Кількість отриманих вподобань сторінки"
pageLikedCount: "Кількість вподобаних сторінок"
contact: "Контакт"
@ -679,7 +731,8 @@ clips: "Добірка"
experimentalFeatures: "Експериментальні функції"
developer: "Розробник"
makeExplorable: "Зробіть обліковий запис видимим у розділі \"Огляд\""
makeExplorableDescription: "Вимкніть, щоб обліковий запис не показувався у розділі \"Огляд\"."
makeExplorableDescription: "Вимкніть, щоб обліковий запис не показувався у розділі
\"Огляд\"."
showGapBetweenNotesInTimeline: "Показувати розрив між записами у стрічці новин"
duplicate: "Дублікат"
left: "Лівий"
@ -694,7 +747,10 @@ onlineUsersCount: "{n} користувачів онлайн"
nUsers: "{n} Користувачів"
nNotes: "{n} Записів"
sendErrorReports: "Надіслати звіт про помилки"
sendErrorReportsDescription: "При увімкненні детальна інформація про помилки буде надана Firefish у разі виникнення проблем, що дасть можливість покращити Firefish."
sendErrorReportsDescription: "Якщо увімкнено, детальна інформація про помилки буде
передаватися до Calckey, коли виникає проблема, це допоможе покращити якість роботи
Calckey.\nЦе буде включати інформацію таку як: версія вашої ОС, який браузер ви
використовуєте, ваша активність в Calckey тощо."
myTheme: "Моя тема"
backgroundColor: "Фон"
accentColor: "Акцент"
@ -718,7 +774,7 @@ capacity: "Ємність"
inUse: "Зайнято"
editCode: "Редагувати вихідний текст"
apply: "Застосувати"
receiveAnnouncementFromInstance: "Отримувати оповіщення з інстансу"
receiveAnnouncementFromInstance: "Отримувати сповіщення з серверу"
emailNotification: "Сповіщення електронною поштою"
publish: "Опублікувати"
inChannelSearch: "Пошук за каналом"
@ -726,12 +782,12 @@ useReactionPickerForContextMenu: "Відкривати палітру реакц
typingUsers: "Стук клавіш. Це {users}…"
goBack: "Назад"
info: "Інформація"
user: "Користувачі"
user: "Користувач"
administration: "Управління"
expiration: "Опитування закінчується"
middle: "Середній"
global: "Глобальна"
sent: "Відправити"
sent: "Відправлене"
hashtags: "Хештеґ"
hide: "Сховати"
searchByGoogle: "Пошук"
@ -756,13 +812,15 @@ _registry:
domain: "Домен"
createKey: "Створити ключ"
_aboutMisskey:
about: "Misskey - це програмне забезпечення з відкритим кодом, яке розробляє syuilo з 2014 року."
about: "Misskey - це програмне забезпечення з відкритим кодом, яке розробляє syuilo
з 2014 року."
contributors: "Головні помічники"
allContributors: "Всі помічники"
source: "Вихідний код"
translation: "Перекладати Firefish"
donate: "Пожертвувати Firefish"
morePatrons: "Ми дуже цінуємо підтримку багатьох інших помічників, не перелічених тут. Дякуємо! 🥰"
translation: "Перекладати Calckey"
donate: "Пожертвувати Calckey"
morePatrons: "Ми дуже цінуємо підтримку багатьох інших помічників, не перелічених
тут. Дякуємо! 🥰"
patrons: "Підтримали"
_nsfw:
respect: "Приховувати NSFW медіа"
@ -770,10 +828,12 @@ _nsfw:
force: "Приховувати всі медіа файли"
_mfm:
cheatSheet: " Довідка MFM"
intro: "MFM це ексклюзивна мова розмітки тексту в Firefish, яку можна використовувати в багатьох місцях. Тут ви можете переглянути приклади її синтаксису."
dummy: "Firefish розширює світ Федіверсу"
intro: "MFM це ексклюзивна мова розмітки тексту в Calckey, яку можна використовувати
в багатьох місцях. Тут ви можете переглянути приклади її синтаксису."
dummy: "Calckey розширює світ Федіверсу"
mention: "Згадка"
mentionDescription: "За допомогою знака \"@\" перед ім'ям можна згадати конкретного користувача."
mentionDescription: "За допомогою знака \"@\" перед ім'ям можна згадати конкретного
користувача."
hashtag: "Хештеґ"
hashtagDescription: "За допомогою знака \"решітка\" перед словом задається хештег."
url: "URL"
@ -819,7 +879,8 @@ _mfm:
x4: "Надзвичайно великий"
x4Description: "Показує контент надзвичайно великим."
blur: "Розмиття"
blurDescription: "Цей ефект зробить контент розмитим. Контент можна зробити чітким, якщо навести на нього вказівник миші."
blurDescription: "Цей ефект зробить контент розмитим. Контент можна зробити чітким,
якщо навести на нього вказівник миші."
font: "Шрифт"
fontDescription: "Встановлює шрифт для контенту."
rotate: "Обертати"
@ -844,10 +905,14 @@ _menuDisplay:
hide: "Сховати"
_wordMute:
muteWords: "Заглушені слова"
muteWordsDescription: "Розділення ключових слів пробілами для \"І\" або з нової лінійки для \"АБО\""
muteWordsDescription2: "Для використання RegEx, ключові слова потрібно вписати поміж слешів \"/\"."
muteWordsDescription: "Розділення ключових слів пробілами для \"І\" або з нової
лінійки для \"АБО\""
muteWordsDescription2: "Для використання RegEx, ключові слова потрібно вписати поміж
слешів \"/\"."
softDescription: "Приховати записи які відповідають критеріям зі стрічки подій."
hardDescription: "Приховати записи які відповідають критеріям зі стрічки подій. Також приховані записи не будуть додані до стрічки подій навіть якщо критерії буде змінено."
hardDescription: "Приховати записи які відповідають критеріям зі стрічки подій.
Також приховані записи не будуть додані до стрічки подій навіть якщо критерії
буде змінено."
soft: "М'яко"
hard: "Жорстко"
mutedNotes: "Заблоковані нотатки"
@ -942,21 +1007,32 @@ _tutorial:
step1_1: "Ласкаво просимо!"
step1_2: "Давайте налаштуємо вас. Ви будете працювати в найкоротші терміни!"
step2_1: "Спочатку, будь ласка, заповніть свій профіль"
step2_2: "Надавши деяку інформацію про себе, іншим людям буде легше зрозуміти, чи хочуть вони бачити ваші записи або стежити за вами."
step2_2: "Надавши деяку інформацію про себе, іншим людям буде легше зрозуміти, чи
хочуть вони бачити ваші записи або стежити за вами."
step3_1: "Тепер настав час стежити за деякими людьми!"
step3_2: "Ваша домашня і соціальна стрічки ґрунтуються на тому, за ким ви стежите, тому для початку спробуйте стежити за кількома акаунтами.\nНатисніть на гурток із плюсом у правому верхньому кутку профілю, щоб стежити за ним."
step3_2: "Ваша домашня і соціальна стрічки ґрунтуються на тому, за ким ви стежите,
тому для початку спробуйте стежити за кількома акаунтами.\nНатисніть на гурток
із плюсом у правому верхньому кутку профілю, щоб стежити за ним."
step4_1: "Давайте вийдемо на вас"
step4_2: "Для свого першого повідомлення деякі люди люблять робити {introduction} повідомлення або просте \"Hello world!\""
step4_2: "Для свого першого повідомлення деякі люди люблять робити {introduction}
повідомлення або просте \"Hello world!\""
step5_1: "Тимчасові рамки, скрізь тимчасові рамки!"
step5_2: "У вашому екземплярі включені {timelines} різних часових ліній."
step5_3: "Головна {icon} часова шкала - це шкала, де ви можете бачити повідомлення ваших підписників."
step5_4: "Місцева {icon} тимчасова шкала - це шкала, де ви можете бачити повідомлення всіх інших користувачів даного екземпляра"
step5_5: "Тимчасова шкала Рекомендовані {icon} - це шкала, де ви можете бачити повідомлення від інстанцій, рекомендованих адміністраторами."
step5_6: "На часовій шкалі Social {icon} відображаються повідомлення від друзів ваших підписників"
step5_7: "Глобальна {icon} часова шкала - це місце, де ви можете бачити повідомлення від усіх інших підключених екземплярів"
step5_3: "Головна {icon} часова шкала - це шкала, де ви можете бачити повідомлення
ваших підписників."
step5_4: "Місцева {icon} тимчасова шкала - це шкала, де ви можете бачити повідомлення
всіх інших користувачів даного екземпляра"
step5_5: "Тимчасова шкала Рекомендовані {icon} - це шкала, де ви можете бачити повідомлення
від інстанцій, рекомендованих адміністраторами."
step5_6: "На часовій шкалі Social {icon} відображаються повідомлення від друзів
ваших підписників"
step5_7: "Глобальна {icon} часова шкала - це місце, де ви можете бачити повідомлення
від усіх інших підключених екземплярів"
step6_1: "Отже, що це за місце?"
step6_2: "Ну, ви не просто приєдналися до Кальки. Ви приєдналися до порталу в Fediverse, взаємопов'язаної мережі з тисяч серверів, званих \"інстансами\"."
step6_3: "Кожен сервер працює по-своєму, і не на всіх серверах працює Firefish. Але цей працює! Це трохи складно, але ви швидко розберетеся"
step6_2: "Ну, ви не просто приєдналися до Кальки. Ви приєдналися до порталу в Fediverse,
взаємопов'язаної мережі з тисяч серверів, званих \"інстансами\"."
step6_3: "Кожен сервер працює по-своєму, і не на всіх серверах працює Calckey. Але
цей працює! Це трохи складно, але ви швидко розберетеся"
step6_4: "Тепер ідіть, вивчайте і розважайтеся!"
_2fa:
registerSecurityKey: "Зареєструвати новий ключ безпеки"
@ -1078,7 +1154,8 @@ _profile:
youCanIncludeHashtags: "Ви також можете включити хештеги у свій опис."
metadata: "Додаткова інформація"
metadataEdit: "Редагувати додаткову інформацію"
metadataDescription: "Ви можете вказати до чотирьох пунктів додаткової інформації у своєму профілі."
metadataDescription: "Ви можете вказати до чотирьох пунктів додаткової інформації
у своєму профілі."
metadataLabel: "Назва"
metadataContent: "Вміст"
changeAvatar: "Змінити аватар"
@ -1388,7 +1465,8 @@ _pages:
_for:
arg1: "Кількість повторень"
arg2: "Дія"
typeError: "Паз {slot} приймає \"{expect}\" тип, але надана змінна має тип \"{actual}\"!"
typeError: "Паз {slot} приймає \"{expect}\" тип, але надана змінна має тип \"\
{actual}\"!"
thereIsEmptySlot: "Паз {slot} пустий!"
types:
string: "Текст"
@ -1453,3 +1531,95 @@ _deck:
list: "Списки"
mentions: "Згадки"
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_: "繁體中文"
headlineMisskey: "貼文連繫網路"
introMisskey: "歡迎! Firefish是一個免費開放原碼去中心化的社群網路🚀"
introMisskey: "歡迎! Calckey是一個開源、去中心化且永遠免費的社群網路平台🚀"
monthAndDay: "{month}月 {day}日"
search: "搜尋"
notifications: "通知"
@ -21,7 +21,7 @@ basicSettings: "基本設定"
otherSettings: "其他設定"
openInWindow: "在新視窗開啟"
profile: "個人檔案"
timeline: "時間"
timeline: "時間"
noAccountDescription: "此用戶還沒有自我介紹。"
login: "登入"
loggingIn: "登入中"
@ -31,7 +31,7 @@ uploading: "上傳中..."
save: "儲存"
users: "使用者"
addUser: "新增使用者"
favorite: "我的最愛"
favorite: "添加至我的最愛"
favorites: "我的最愛"
unfavorite: "從我的最愛中移除"
favorited: "已添加至我的最愛。"
@ -43,7 +43,7 @@ copyContent: "複製內容"
copyLink: "複製連結"
delete: "刪除"
deleteAndEdit: "刪除並編輯"
deleteAndEditConfirm: "要刪除並再次編輯嗎?此貼文的所有情感、轉發和回覆也將會消失。"
deleteAndEditConfirm: "要刪除並再次編輯嗎?此貼文的所有反應、轉發和回覆也會消失。"
addToList: "加入至清單"
sendMessage: "發送訊息"
copyUsername: "複製使用者名稱"
@ -64,7 +64,7 @@ export: "匯出"
files: "檔案"
download: "下載"
driveFileDeleteConfirm: "確定要刪除檔案「{name}」嗎?使用此附件的貼文也會跟著消失。"
unfollowConfirm: "確定要取消追隨{name}嗎?"
unfollowConfirm: "確定要取消追隨{name}嗎?"
exportRequested: "已請求匯出。這可能會花一點時間。結束後檔案將會被放到雲端裡。"
importRequested: "已請求匯入。這可能會花一點時間。"
lists: "清單"
@ -95,9 +95,9 @@ followRequestPending: "追隨許可批准中"
enterEmoji: "輸入表情符號"
renote: "轉發"
unrenote: "取消轉發"
renoted: "已轉。"
renoted: "已轉。"
cantRenote: "無法轉發此貼文。"
cantReRenote: "無法轉傳之前已經轉傳過的內容。"
cantReRenote: "無法轉發之前已經轉發過的內容。"
quote: "引用"
pinnedNote: "已置頂的貼文"
pinned: "置頂"
@ -105,7 +105,7 @@ you: "您"
clickToShow: "按一下以顯示"
sensitive: "敏感內容"
add: "新增"
reaction: "情感"
reaction: "反應"
enableEmojiReaction: "啟用表情符號反應"
showEmojisInReactionNotifications: "在反應通知中顯示表情符號"
reactionSetting: "在選擇器中顯示反應"
@ -140,14 +140,14 @@ emojiUrl: "表情符號URL"
addEmoji: "加入表情符號"
settingGuide: "推薦設定"
cacheRemoteFiles: "快取遠端檔案"
cacheRemoteFilesDescription: "禁用此設定會停止遠端檔案的緩存,從而節省儲存空間,但資料會因直接連線從而產生額外連接數據。"
flagAsBot: "此使用者是機器人"
flagAsBotDescription: "如果本帳戶是由程式控制,請啟用此選項。啟用後,會作為標示幫助其他開發者防止機器人之間產生無限互動的行為,並會調整Firefish內部系統將本帳戶識別為機器人。"
flagAsCat: "此使用者是貓"
cacheRemoteFilesDescription: "禁用此設定會停止遠端檔案的緩存,從而節省儲存空間,但資料會因直接連線從而產生額外數據花費。"
flagAsBot: "標記此帳號是機器人"
flagAsBotDescription: "如果本帳戶是由程式控制,請啟用此選項。啟用後,會作為標示幫助其他開發者防止機器人之間產生無限互動的行為,並會調整Calckey內部系統將本帳戶識別為機器人。"
flagAsCat: "你是喵咪嗎w😺"
flagAsCatDescription: "如果想將本帳戶標示為一隻貓,請開啟此標示!"
flagShowTimelineReplies: "在時間上顯示貼文的回覆"
flagShowTimelineReplies: "在時間上顯示貼文的回覆"
flagShowTimelineRepliesDescription: "啟用時,時間線除了顯示用戶的貼文以外,還會顯示用戶對其他貼文的回覆。"
autoAcceptFollowed: "自動追隨中使用者的追隨請求"
autoAcceptFollowed: "自動准予追隨中使用者的追隨請求"
addAccount: "添加帳戶"
loginFailed: "登入失敗"
showOnRemote: "轉到所在伺服器顯示"
@ -157,7 +157,7 @@ setWallpaper: "設定桌布"
removeWallpaper: "移除桌布"
searchWith: "搜尋: {q}"
youHaveNoLists: "你沒有任何清單"
followConfirm: "你真的要追隨{name}嗎?"
followConfirm: "你真的要追隨{name}嗎?"
proxyAccount: "代理帳戶"
proxyAccountDescription: "代理帳戶是在某些情況下充當其他伺服器用戶的帳戶。例如,當使用者將一個來自其他伺服器的帳戶放在列表中時,由於沒有其他使用者追蹤該帳戶,該指令不會傳送到該伺服器上,因此會由代理帳戶追蹤。"
host: "主機"
@ -166,7 +166,7 @@ recipient: "收件人"
annotation: "註解"
federation: "站台聯邦"
instances: "伺服器"
registeredAt: "初次觀測"
registeredAt: "初次註冊"
latestRequestSentAt: "上次發送的請求"
latestRequestReceivedAt: "上次收到的請求"
latestStatus: "最後狀態"
@ -234,19 +234,19 @@ lookup: "查詢"
announcements: "公告"
imageUrl: "圖片URL"
remove: "刪除"
removed: "已刪除"
removed: "已成功刪除"
removeAreYouSure: "確定要刪掉「{x}」嗎?"
deleteAreYouSure: "確定要刪掉「{x}」嗎?"
resetAreYouSure: "確定要重設嗎?"
saved: "已儲存"
messaging: "傳送訊息"
messaging: "訊息"
upload: "上傳"
keepOriginalUploading: "保留原圖"
keepOriginalUploadingDescription: "上傳圖片時保留原始圖片。關閉時,瀏覽器會在上傳時生成一張用於web發布的圖片。"
keepOriginalUploadingDescription: "上傳圖片時保留原始圖片。關閉時,瀏覽器會在上傳時自動產生用於貼文發布的圖片。"
fromDrive: "從雲端空間"
fromUrl: "從URL"
fromUrl: "從網址"
uploadFromUrl: "從網址上傳"
uploadFromUrlDescription: "您要上傳的文件的URL"
uploadFromUrlDescription: "您要上傳的文件的網址"
uploadFromUrlRequested: "已請求上傳"
uploadFromUrlMayTakeTime: "還需要一些時間才能完成上傳。"
explore: "探索"
@ -258,7 +258,7 @@ agreeTo: "我同意{0}"
tos: "使用條款"
start: "開始"
home: "首頁"
remoteUserCaution: "由於該使用者來自遠端實例,因此資訊可能非即時的。"
remoteUserCaution: "由於該使用者來自遠端實例,因此資料可能是非即時的。"
activity: "動態"
images: "圖片"
birthday: "生日"
@ -267,12 +267,12 @@ registeredDate: "註冊日期"
location: "位置"
theme: "外觀主題"
themeForLightMode: "在淺色模式下使用的主題"
themeForDarkMode: "在模式下使用的主題"
themeForDarkMode: "在黑模式下使用的主題"
light: "淺色"
dark: ""
dark: "黑"
lightThemes: "明亮主題"
darkThemes: "主題"
syncDeviceDarkMode: "將黑暗模式與設備設置同步"
darkThemes: "黑主題"
syncDeviceDarkMode: "闇黑模式使用裝置設定"
drive: "雲端硬碟"
fileName: "檔案名稱"
selectFile: "選擇檔案"
@ -281,19 +281,19 @@ selectFolder: "選擇資料夾"
selectFolders: "選擇資料夾"
renameFile: "重新命名檔案"
folderName: "資料夾名稱"
createFolder: "新增資料夾"
createFolder: "創建資料夾"
renameFolder: "重新命名資料夾"
deleteFolder: "刪除資料夾"
addFile: "加入附件"
emptyDrive: "雲端硬碟為空"
emptyFolder: "資料夾為空"
emptyDrive: "你的雲端硬碟沒有任何東西( ̄▽ ̄)\""
emptyFolder: "資料夾裡面沒有東西(⊙_⊙;)"
unableToDelete: "無法刪除"
inputNewFileName: "輸入檔案名稱"
inputNewDescription: "請輸入新標題"
inputNewFolderName: "輸入新資料夾的名稱"
circularReferenceFolder: "目標文件夾是您要移動的文件夾的子文件夾。"
hasChildFilesOrFolders: "此文件夾不是空的,無法刪除。"
copyUrl: "複製URL"
copyUrl: "複製網址"
rename: "重新命名"
avatar: "大頭貼"
banner: "橫幅"
@ -304,7 +304,7 @@ reload: "重新整理"
doNothing: "無視"
reloadConfirm: "確定要重新整理嗎?"
watch: "關注"
unwatch: "取消追隨"
unwatch: "取消關注"
accept: "接受"
reject: "拒絕"
normal: "正常"
@ -312,7 +312,7 @@ instanceName: "伺服器名稱"
instanceDescription: "伺服器說明"
maintainerName: "管理員名稱"
maintainerEmail: "管理員郵箱"
tosUrl: "服務條款URL"
tosUrl: "服務條款網址"
thisYear: "本年"
thisMonth: "本月"
today: "本日"
@ -323,23 +323,23 @@ pages: "頁面"
integration: "整合"
connectService: "己連結"
disconnectService: "己斷開"
enableLocalTimeline: "開啟本地時間"
enableGlobalTimeline: "啟用公開時間"
disablingTimelinesInfo: "即使您關閉了時間線功能,管理員和協調人仍可以繼續使用,以方便您。"
enableLocalTimeline: "開啟本地時間"
enableGlobalTimeline: "啟用公開時間"
disablingTimelinesInfo: "即使您關閉了時間線功能,管理員和版主始終可以訪問所有的時間線。"
registration: "註冊"
enableRegistration: "開啟新使用者註冊"
invite: "邀請"
driveCapacityPerLocalAccount: "每個本地用戶的雲端空間大小"
driveCapacityPerRemoteAccount: "每個非本地用戶的雲端容量"
inMb: "以Mbps為單位"
iconUrl: "圖像URL"
bannerUrl: "橫幅圖像URL"
inMb: "以MB為單位"
iconUrl: "圖標網址"
bannerUrl: "橫幅圖像網址"
backgroundImageUrl: "背景圖片的來源網址"
basicInfo: "基本資訊"
pinnedUsers: "置頂用戶"
pinnedUsersDescription: "在「發現」頁面中使用換行標記想要置頂的使用者。"
pinnedPages: "釘選頁面"
pinnedPagesDescription: "輸入要固定至伺服器首頁的頁面路徑,以換行符分隔。"
pinnedUsersDescription: "在「探索」頁面中使用換行標記想要置頂的使用者。"
pinnedPages: "釘選頁面"
pinnedPagesDescription: "輸入要固定至伺服器首頁的頁面路徑,一行一個。"
pinnedClipId: "置頂的摘錄ID"
pinnedNotes: "已置頂的貼文"
hcaptcha: "hCaptcha"
@ -482,7 +482,7 @@ promotion: "推廣"
promote: "推廣"
numberOfDays: "有效天數"
hideThisNote: "隱藏此貼文"
showFeaturedNotesInTimeline: "在時間上顯示熱門推薦"
showFeaturedNotesInTimeline: "在時間上顯示熱門推薦"
objectStorage: "Object Storage (物件儲存)"
useObjectStorage: "使用Object Storage"
objectStorageBaseUrl: "根URL"
@ -502,7 +502,7 @@ objectStorageUseProxyDesc: "如果不使用代理進行API連接請關閉"
objectStorageSetPublicRead: "上傳時設定為\"public-read\""
serverLogs: "伺服器日誌"
deleteAll: "刪除所有記錄"
showFixedPostForm: "於時間頁頂顯示「發送貼文」方框"
showFixedPostForm: "於時間頁頂顯示「發送貼文」方框"
newNoteRecived: "發現新的貼文"
sounds: "音效"
listen: "聆聽"
@ -661,8 +661,8 @@ repliedCount: "回覆數量"
renotedCount: "轉發次數"
followingCount: "正在跟隨的用戶數量"
followersCount: "跟隨者數量"
sentReactionsCount: "情感發送次數"
receivedReactionsCount: "情感收到次數"
sentReactionsCount: "反應發送次數"
receivedReactionsCount: "反應收到次數"
pollVotesCount: "已統計的投票數"
pollVotedCount: "已投票數"
yes: "確定"
@ -688,7 +688,7 @@ experimentalFeatures: "實驗中的功能"
developer: "開發者"
makeExplorable: "使自己的帳戶能夠在“探索”頁面中顯示"
makeExplorableDescription: "如果關閉,帳戶將不會被顯示在\"探索\"頁面中。"
showGapBetweenNotesInTimeline: "分開顯示時間上的貼文"
showGapBetweenNotesInTimeline: "分開顯示時間上的貼文"
duplicate: "複製"
left: "左"
center: "置中"
@ -702,7 +702,8 @@ onlineUsersCount: "{n}人正在線上"
nUsers: "{n}用戶"
nNotes: "{n}貼文"
sendErrorReports: "傳送錯誤報告"
sendErrorReportsDescription: "啟用後問題報告將傳送至Firefish開發者以提升軟體品質。\n問題報告可能包括OS版本瀏覽器類型行為歷史記錄等。"
sendErrorReportsDescription: "開啟後,錯誤出現時將會與 Calckey 分享詳細紀錄,對於 Calckey 的開發會有非常大的幫助。\n
這將包括您的操作系統版本、使用的瀏覽器、您在 Calckey 中的活動等資料。"
myTheme: "我的佈景主題"
backgroundColor: "背景"
accentColor: "重點色彩"
@ -862,7 +863,7 @@ check: "檢查"
driveCapOverrideLabel: "更改這個使用者的雲端硬碟容量上限"
driveCapOverrideCaption: "如果指定0以下的值就會被取消。"
requireAdminForView: "必須以管理者帳號登入才可以檢視。"
isSystemAccount: "由系統自動建立與管理的帳號。"
isSystemAccount: "該帳號由系統自動創建並運行。 千千萬萬不要審核、編輯、刪除或以其他方式修改此帳戶,否則可能會破壞您的伺服器。"
typeToConfirm: "要執行這項操作,請輸入 {x}"
deleteAccount: "刪除帳號"
document: "文件"
@ -1089,8 +1090,8 @@ _wordMute:
muteWords: "加入靜音文字"
muteWordsDescription: "用空格分隔指定AND用換行分隔指定OR。"
muteWordsDescription2: "將關鍵字用斜線括起來表示正規表達式。"
softDescription: "隱藏時間中指定條件的貼文。"
hardDescription: "具有指定條件的貼文將不添加到時間。 即使您更改條件,未被添加的貼文也會被排除在外。"
softDescription: "隱藏時間中指定條件的貼文。"
hardDescription: "具有指定條件的貼文將不添加到時間。 即使您更改條件,未被添加的貼文也會被排除在外。"
soft: "軟性靜音"
hard: "硬性靜音"
mutedNotes: "已靜音的貼文"
@ -1203,16 +1204,16 @@ _tutorial:
step2_1: "首先,請完成你的個人資料。"
step2_2: "通過提供一些關於你自己的資料,其他人會更容易了解他們是否想看到你的帖子或關注你。"
step3_1: "現在是時候追隨一些人了!"
step3_2: "你的主頁和社交時間軸是基於你所追蹤的人,所以試著先追蹤幾個賬戶。\n點擊個人資料右上角的加號圈就可以關注它。"
step3_2: "你的主頁和社交時間線是基於你所追蹤的人,所以試著先追蹤幾個帳戶。\n點擊個人資料右上角的加號圈就可以關注它。"
step4_1: "讓我們出去找你。"
step4_2: "對於他們的第一條信息,有些人喜歡做 {introduction} 或一個簡單的 \"hello world!\""
step5_1: "時間軸,到處都是時間軸"
step5_2: "您的伺服器已啟用了{timelines}個時間。"
step5_3: "主 {icon} 時間軸是顯示你追蹤的帳號的帖子。"
step5_4: "本地 {icon} 時間軸是你可以看到伺服器中所有其他用戶的信息的時間軸。"
step5_5: "社交 {icon} 時間軸是顯示你的主時間軸 + 本地時間軸。"
step5_6: "推薦 {icon} 時間軸是顯示你的伺服器管理員推薦的帖文。"
step5_7: "全球 {icon} 時間軸是顯示來自所有其他連接的伺服器的帖文。"
step5_1: "時間線,到處都是時間線"
step5_2: "您的伺服器已啟用了{timelines}個時間。"
step5_3: "首頁 {icon} 時間線是顯示你追蹤的帳號的帖子。"
step5_4: "本地 {icon} 時間線是你可以看到伺服器中所有其他用戶的貼文的時間線。"
step5_5: "社交 {icon} 時間線是你的 首頁時間線 和 本地時間線 的結合體。"
step5_6: "推薦 {icon} 時間線是顯示你的伺服器管理員推薦的貼文。"
step5_7: "全球 {icon} 時間線是顯示來自所有其他連接的伺服器的貼文。"
step6_1: "那麼,這裡是什麼地方?"
step6_2: "你不只是加入Firefish。你已經加入了Fediverse的一個門戶這是一個由成千上萬台服務器組成的互聯網絡。"
step6_3: "每個服務器也有不同而並不是所有的服務器都運行Firefish。但這個服務器確實是運行Firefish的! 你可能會覺得有點複雜,但你很快就會明白的。"
@ -1245,8 +1246,8 @@ _permissions:
"write:notes": "撰寫或刪除貼文"
"read:notifications": "查看通知"
"write:notifications": "編輯通知"
"read:reactions": "查看情感"
"write:reactions": "編輯情感"
"read:reactions": "查看反應"
"write:reactions": "編輯反應"
"write:votes": "投票"
"read:pages": "顯示頁面"
"write:pages": "編輯頁面"
@ -1284,7 +1285,7 @@ _weekday:
_widgets:
memo: "備忘錄"
notifications: "通知"
timeline: "時間"
timeline: "時間"
calendar: "行事曆"
trends: "發燒貼文"
clock: "時鐘"
@ -1335,7 +1336,7 @@ _visibility:
public: "公開"
publicDescription: "發布給所有用戶"
home: "不在主頁顯示"
homeDescription: "僅發送至首頁的時間"
homeDescription: "僅發送至首頁的時間"
followers: "追隨者"
followersDescription: "僅發送至關注者"
specified: "指定使用者"
@ -1403,7 +1404,7 @@ _instanceCharts:
_timelines:
home: "首頁"
local: "本地"
social: "社"
social: "社"
global: "公開"
recommended: 推薦
_pages:
@ -1726,7 +1727,7 @@ _notification:
pollEnded: "問卷調查結束"
receiveFollowRequest: "已收到追隨請求"
followRequestAccepted: "追隨請求已接受"
groupInvited: "加入社群邀請"
groupInvited: "群組加入邀請"
app: "應用程式通知"
_actions:
followBack: "回關"
@ -1755,7 +1756,7 @@ _deck:
main: "主列"
widgets: "小工具"
notifications: "通知"
tl: "時間"
tl: "時間"
antenna: "天線"
list: "清單"
mentions: "提及"
@ -1782,11 +1783,11 @@ enterSendsMessage: 在 Messaging 中按 Return 發送消息 (如關閉則是 Ctr
migrationConfirm: "您確定要將你的帳戶遷移到 {account} 嗎? 一旦這樣做,你將無法復原,而你將無法再次正常使用您的帳戶。\n另外請確保你已將此當前帳戶設置為您要遷移的帳戶。"
customSplashIconsDescription: 每次用戶加載/重新加載頁面時,以換行符號分隔的自定啟動畫面圖標的網址將隨機顯示。請確保圖片位於靜態網址上,最好所有圖片解析度調整為
192x192。
accountMoved: '該使用者已移至新帳戶:'
accountMoved: '該使用者已移至新帳戶:'
showAds: 顯示廣告
noThankYou: 不用了,謝謝
selectInstance: 選擇伺服器
enableRecommendedTimeline: 啟用推薦時間
enableRecommendedTimeline: 啟用推薦時間
antennaInstancesDescription: 分行列出一個伺服器
moveTo: 遷移此帳戶到新帳戶
moveToLabel: '請輸入你將會遷移到的帳戶:'
@ -1838,10 +1839,31 @@ pushNotification: 推送通知
subscribePushNotification: 啟用推送通知
unsubscribePushNotification: 禁用推送通知
pushNotificationAlreadySubscribed: 推送通知已經啟用
recommendedInstancesDescription: 以每行分隔的推薦服務器出現在推薦的時間軸中。 不要添加 `https://`,只添加域名
searchPlaceholder: 搜尋 Firefish
recommendedInstancesDescription: 以每行分隔的推薦伺服器出現在推薦的時間線中
searchPlaceholder: 在聯邦網路上搜尋
cw: 內容警告
selectChannel: 選擇一個頻道
newer: 較新
older: 較舊
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",
"version": "14.0.0-rc3",
"name": "calckey",
"version": "14.0.0-dev77",
"codename": "aqua",
"repository": {
"type": "git",
"url": "https://codeberg.org/firefish/firefish.git"
},
"packageManager": "pnpm@8.6.3",
"packageManager": "pnpm@8.6.7",
"private": true,
"scripts": {
"rebuild": "pnpm run clean && pnpm node ./scripts/build-greet.js && pnpm -r run build && pnpm run gulp",
"build": "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 --parallel run build && pnpm run gulp",
"start": "pnpm --filter backend run start",
"start:test": "pnpm --filter backend run start:test",
"init": "pnpm run migrate",
@ -21,13 +21,13 @@
"watch": "pnpm run dev",
"dev": "pnpm node ./scripts/dev.js",
"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:run": "cypress run",
"e2e": "start-server-and-test start:test http://localhost:61812 cy:run",
"mocha": "pnpm --filter backend run mocha",
"test": "pnpm run mocha",
"format": "pnpm -r run format",
"format": "pnpm -r --parallel run format",
"clean": "pnpm node ./scripts/clean.js",
"clean-all": "pnpm node ./scripts/clean-all.js",
"cleanall": "pnpm run clean-all"
@ -36,16 +36,17 @@
"chokidar": "^3.3.1"
},
"dependencies": {
"@bull-board/api": "5.2.0",
"@bull-board/ui": "5.2.0",
"@bull-board/api": "5.6.0",
"@bull-board/ui": "5.6.0",
"@napi-rs/cli": "^2.16.1",
"@tensorflow/tfjs": "^3.21.0",
"js-yaml": "4.1.0",
"seedrandom": "^3.0.5"
},
"devDependencies": {
"@types/gulp": "4.0.10",
"@types/gulp-rename": "2.0.1",
"@types/gulp": "4.0.13",
"@types/gulp-rename": "2.0.2",
"@types/node": "20.4.1",
"chalk": "4.1.2",
"cross-env": "7.0.3",
"cypress": "10.11.0",
@ -58,6 +59,6 @@
"install-peers": "^1.0.4",
"rome": "^12.1.3",
"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/)
- `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
- `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

BIN
packages/backend/assets/avatar.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/backend/assets/transparent.png (Stored with Git LFS) Normal file

Binary file not shown.

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]
pub fn convert_id(in_id: String, id_convert_type: IdConvertType) -> napi::Result<String> {
println!("converting id: {}", in_id);
use IdConvertType::*;
match id_convert_type {
MastodonId => {

View File

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

View File

@ -55,6 +55,8 @@ export default function load() {
mixin.clientEntry = clientManifest["src/init.ts"];
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);
}

View File

@ -26,6 +26,16 @@ export type Source = {
user?: 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: {
host: string;
port: number;

View File

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

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@ import * as Acct from "@/misc/acct.js";
import type { Packed } from "./schema.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: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている

View File

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

View File

@ -3,8 +3,9 @@
/**
* Maximum note text length that can be stored in DB.
* 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.

View File

@ -3,10 +3,12 @@ import type { User } from "@/models/entities/user.js";
import type { UserKeypair } from "@/models/entities/user-keypair.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> {
return await cache.fetch(userId, () =>
UserKeypairs.findOneByOrFail({ userId: userId }),
return await cache.fetch(
userId,
() => UserKeypairs.findOneByOrFail({ userId: userId }),
true,
);
}

View File

@ -20,5 +20,9 @@ export function nyaize(text: string): string {
)
.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 config from "@/config/index.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)) {
emoji = await queryOrNull();
cache.set(cacheKey, emoji);
await cache.set(cacheKey, emoji);
}
if (emoji == null) return null;
@ -150,7 +151,7 @@ export async function prefetchEmojis(
emojis: { name: string; host: string | null }[],
): Promise<void> {
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 hosts = new Set(notCachedEmojis.map((e) => e.host));
@ -169,7 +170,9 @@ export async function prefetchEmojis(
select: ["name", "host", "originalUrl", "publicUrl"],
})
: [];
const trans = redisClient.multi();
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;
@Column("boolean", {
default: false,
})
public showPopup: boolean;
@Column("boolean", {
default: false,
})
public isGoodNews: boolean;
constructor(data: Partial<Announcement>) {
if (data == null) return;

View File

@ -326,13 +326,13 @@ export class Meta {
public smtpPort: number | null;
@Column("varchar", {
length: 128,
length: 1024,
nullable: true,
})
public smtpUser: string | null;
@Column("varchar", {
length: 128,
length: 1024,
nullable: true,
})
public smtpPass: string | null;
@ -546,4 +546,20 @@ export class Meta {
default: {},
})
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 Ajv from "ajv";
import type { ILocalUser, IRemoteUser } from "@/models/entities/user.js";
@ -40,7 +39,10 @@ import {
} from "../index.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
? Packed<"UserDetailed">
@ -451,6 +453,7 @@ export const UserRepository = db.getRepository(User).extend({
isAdmin: user.isAdmin || falsy,
isModerator: user.isModerator || falsy,
isBot: user.isBot || falsy,
isLocked: user.isLocked,
isCat: user.isCat || falsy,
speakAsCat: user.speakAsCat || falsy,
instance: user.host
@ -495,7 +498,6 @@ export const UserRepository = db.getRepository(User).extend({
: null,
bannerBlurhash: user.banner?.blurhash || null,
bannerColor: null, // 後方互換性のため
isLocked: user.isLocked,
isSilenced: user.isSilenced || falsy,
isSuspended: user.isSuspended || falsy,
description: profile!.description,

View File

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

View File

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

View File

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

View File

@ -35,8 +35,11 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
info["@context"] = undefined;
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
const host = toPuny(new URL(signature.keyId).hostname);

View File

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

View File

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

View File

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

View File

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

View File

@ -57,6 +57,16 @@ export const meta = {
optional: 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,
imageUrl: announcement.imageUrl,
reads: reads.get(announcement)!,
showPopup: announcement.showPopup,
isGoodNews: announcement.isGoodNews,
}));
});

View File

@ -24,6 +24,8 @@ export const paramDef = {
title: { type: "string", minLength: 1 },
text: { type: "string", minLength: 1 },
imageUrl: { type: "string", nullable: true, minLength: 1 },
showPopup: { type: "boolean" },
isGoodNews: { type: "boolean" },
},
required: ["id", "title", "text", "imageUrl"],
} as const;
@ -38,5 +40,7 @@ export default define(meta, paramDef, async (ps, me) => {
title: ps.title,
text: ps.text,
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 { publishBroadcastStream } from "@/services/stream.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 = {
tags: ["admin"],
@ -40,12 +40,7 @@ export default define(meta, paramDef, async (ps, me) => {
? file.name.split(".")[0]
: `_${rndstr("a-z0-9", 8)}_`;
let size: Size = { width: 0, height: 0 };
try {
size = await getEmojiSize(file.url);
} catch {
/* skip if any error happens */
}
const size = await getEmojiSize(file.url);
const emoji = await Emojis.insert({
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 { publishBroadcastStream } from "@/services/stream.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 = {
tags: ["admin"],
@ -65,12 +65,7 @@ export default define(meta, paramDef, async (ps, me) => {
throw new ApiError();
}
let size: Size = { width: 0, height: 0 };
try {
size = await getEmojiSize(driveFile.url);
} catch {
/* skip if any error happens */
}
const size = await getEmojiSize(driveFile.url);
const copied = await Emojis.insert({
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;
@ -592,5 +607,8 @@ export default define(meta, paramDef, async (ps, me) => {
enableIpLogging: instance.enableIpLogging,
enableActiveEmailValidation: instance.enableActiveEmailValidation,
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;
});
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);
}

View File

@ -1,6 +1,5 @@
import { Meta } from "@/models/entities/meta.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 define from "../../define.js";
@ -177,6 +176,9 @@ export const paramDef = {
postImports: { type: "boolean" },
},
},
enableServerMachineStats: { type: "boolean" },
enableIdenticonGeneration: { type: "boolean" },
donationLink: { type: "string", nullable: true },
},
required: [],
} as const;
@ -218,6 +220,15 @@ export default define(meta, paramDef, async (ps, me) => {
if (Array.isArray(ps.recommendedInstances)) {
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)) {
@ -568,6 +579,21 @@ export default define(meta, paramDef, async (ps, me) => {
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) => {
const metas = await transactionalEntityManager.find(Meta, {
order: {

View File

@ -56,6 +56,16 @@ export const meta = {
optional: true,
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",
id: "c3a5a51e-04d4-11ee-be56-0242ac120002",
},
noKeywords: {
message: "No keywords",
code: "NO_KEYWORDS",
id: "aa975b74-1ddb-11ee-be56-0242ac120002",
},
},
res: {
@ -100,6 +105,7 @@ export const paramDef = {
export default define(meta, paramDef, async (ps, user) => {
if (user.movedToUri != null) throw new ApiError(meta.errors.noSuchUserGroup);
if (ps.keywords.length === 0) throw new ApiError(meta.errors.noKeywords);
let userList;
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 { isActor, isPost, getApId } from "@/remote/activitypub/type.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 { updateQuestion } from "@/remote/activitypub/models/question.js";
import { populatePoll } from "@/models/repositories/note.js";
@ -22,8 +22,8 @@ export const meta = {
requireCredential: true,
limit: {
duration: HOUR,
max: 30,
duration: MINUTE,
max: 10,
},
errors: {

View File

@ -41,12 +41,14 @@ export default define(meta, paramDef, async (ps, user) => {
const accessToken = secureRndstr(32, true);
// Fetch exist access token
const exist = await AccessTokens.findOneBy({
const exist = await AccessTokens.exist({
where: {
appId: session.appId,
userId: user.id,
},
});
if (exist == null) {
if (!exist) {
// Lookup app
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
const exist = await Blockings.findOneBy({
const exist = await Blockings.exist({
where: {
blockerId: blocker.id,
blockeeId: blockee.id,
},
});
if (exist != null) {
if (exist) {
throw new ApiError(meta.errors.alreadyBlocking);
}

View File

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

View File

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

View File

@ -26,10 +26,12 @@ export const paramDef = {
} as const;
export default define(meta, paramDef, async (ps, user) => {
const file = await DriveFiles.findOneBy({
const exist = await DriveFiles.exist({
where: {
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
const exist = await Followings.findOneBy({
const exist = await Followings.exist({
where: {
followerId: follower.id,
followeeId: followee.id,
},
});
if (exist != null) {
if (exist) {
throw new ApiError(meta.errors.alreadyFollowing);
}

View File

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

View File

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

View File

@ -40,12 +40,14 @@ export default define(meta, paramDef, async (ps, user) => {
}
// if already liked
const exist = await GalleryLikes.findOneBy({
const exist = await GalleryLikes.exist({
where: {
postId: post.id,
userId: user.id,
},
});
if (exist != null) {
if (exist) {
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);
}
const exist = await GalleryLikes.findOneBy({
const like = await GalleryLikes.findOneBy({
postId: post.id,
userId: user.id,
});
if (exist == null) {
if (like == null) {
throw new ApiError(meta.errors.notLiked);
}
// Delete like
await GalleryLikes.delete(exist.id);
await GalleryLikes.delete(like.id);
GalleryPosts.decrement({ id: post.id }, "likedCount", 1);
});

View File

@ -30,19 +30,23 @@ export const paramDef = {
export default define(meta, paramDef, async (ps, user) => {
// 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);
}
// Check if already read
const read = await AnnouncementReads.findOneBy({
const read = await AnnouncementReads.exist({
where: {
announcementId: ps.announcementId,
userId: user.id,
},
});
if (read != null) {
if (read) {
return;
}

View File

@ -33,7 +33,7 @@ export const paramDef = {
} as const;
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")
.where("item.domain IS NULL")
.andWhere("item.userId = :userId", { userId: user.id })

View File

@ -17,9 +17,9 @@ export const paramDef = {
} as const;
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({
id: ps.tokenId,
userId: user.id,

View File

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

View File

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

View File

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

View File

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

View File

@ -9,6 +9,7 @@ export const meta = {
requireCredential: false,
requireCredentialPrivateMode: true,
description: "Get conversation of a note thread/chain by a reply",
res: {
type: "array",
@ -34,7 +35,11 @@ export const meta = {
export const paramDef = {
type: "object",
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 },
offset: { type: "integer", default: 0 },
},
@ -51,7 +56,7 @@ export default define(meta, paramDef, async (ps, user) => {
const conversation: Note[] = [];
let i = 0;
async function get(id: any) {
async function get(id: string) {
i++;
const p = await getNote(id, user).catch((e) => {
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 (i > ps.offset!) {
if (i > ps.offset) {
conversation.push(p);
}

View File

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

View File

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

View File

@ -42,15 +42,15 @@ export default define(meta, paramDef, async (ps, user) => {
});
// if already favorited
const exist = await NoteFavorites.findOneBy({
const favorite = await NoteFavorites.findOneBy({
noteId: note.id,
userId: user.id,
});
if (exist == null) {
if (favorite == null) {
throw new ApiError(meta.errors.notFavorited);
}
// 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.",
code: "NO_SUCH_NOTE",
id: "24fcbfc6-2e37-42b6-8388-c29b3861a08d",
httpStatusCode: 404,
},
},
} as const;

View File

@ -40,12 +40,14 @@ export default define(meta, paramDef, async (ps, user) => {
}
// if already liked
const exist = await PageLikes.findOneBy({
const exist = await PageLikes.exist({
where: {
pageId: page.id,
userId: user.id,
},
});
if (exist != null) {
if (exist) {
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);
}
const exist = await PageLikes.findOneBy({
const like = await PageLikes.findOneBy({
pageId: page.id,
userId: user.id,
});
if (exist == null) {
if (like == null) {
throw new ApiError(meta.errors.notLiked);
}
// Delete like
await PageLikes.delete(exist.id);
await PageLikes.delete(like.id);
Pages.decrement({ id: page.id }, "likedCount", 1);
});

View File

@ -1,9 +1,15 @@
import define from "../define.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 = {
tags: ["meta"],
description: "Get list of Firefish patrons from Codeberg",
description: "Get Calckey patrons",
requireCredential: false,
requireCredentialPrivateMode: false,
@ -24,21 +30,29 @@ export default define(meta, paramDef, async (ps) => {
patrons = JSON.parse(cachedPatrons);
} else {
AbortSignal.timeout ??= function timeout(ms) {
const ctrl = new AbortController()
setTimeout(() => ctrl.abort(), ms)
return ctrl.signal
}
const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), ms);
return ctrl.signal;
};
patrons = await fetch(
"https://codeberg.org/firefish/firefish/raw/branch/develop/patrons.json",
{ signal: AbortSignal.timeout(2000) }
"https://codeberg.org/calckey/calckey/raw/branch/develop/patrons.json",
{ signal: AbortSignal.timeout(2000) },
)
.then((response) => response.json())
.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);
}
return patrons["patrons"];
return {
patrons: patrons["patrons"],
sponsors: patrons["sponsors"],
};
});

View File

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

View File

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

View File

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

View File

@ -2,11 +2,13 @@ import * as os from "node:os";
import si from "systeminformation";
import define from "../define.js";
import meilisearch from "@/db/meilisearch.js";
import { fetchMeta } from "@/misc/fetch-meta.js";
export const meta = {
requireCredential: false,
requireCredentialPrivateMode: true,
allowGet: true,
cacheSec: 30,
tags: ["meta"],
} 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 {
machine: os.hostname(),
cpu: {

View File

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

View File

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

View File

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

View File

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

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