Merge branch 'main' into snug.moe

This commit is contained in:
vib 2023-05-16 09:33:20 +03:00
commit 5b046381e7
148 changed files with 2296 additions and 1301 deletions

View file

@ -8,15 +8,15 @@ clone:
pipeline: pipeline:
install: install:
when: when:
event: branch: main
- pull_request event: push
image: node:18.6.0 image: node:18.6.0
commands: commands:
- yarn install - yarn install
build: build:
when: when:
event: branch: main
- pull_request event: push
image: node:18.6.0 image: node:18.6.0
commands: commands:
- yarn build - yarn build

View file

@ -8,15 +8,15 @@ clone:
pipeline: pipeline:
install: install:
when: when:
event: branch: main
- pull_request event: push
image: node:18.6.0 image: node:18.6.0
commands: commands:
- yarn install - yarn install
lint: lint:
when: when:
event: branch: main
- pull_request event: push
image: node:18.6.0 image: node:18.6.0
commands: commands:
- yarn workspace backend run lint - yarn workspace backend run lint

View file

@ -8,15 +8,15 @@ clone:
pipeline: pipeline:
install: install:
when: when:
event: branch: main
- pull_request event: push
image: node:18.6.0 image: node:18.6.0
commands: commands:
- yarn install - yarn install
lint: lint:
when: when:
event: branch: main
- pull_request event: push
image: node:18.6.0 image: node:18.6.0
commands: commands:
- yarn workspace client run lint - yarn workspace client run lint

View file

@ -8,15 +8,15 @@ clone:
pipeline: pipeline:
install: install:
when: when:
event: branch: main
- pull_request event: push
image: node:18.6.0 image: node:18.6.0
commands: commands:
- yarn install - yarn install
lint: lint:
when: when:
event: branch: main
- pull_request event: push
image: node:18.6.0 image: node:18.6.0
commands: commands:
- yarn workspace foundkey-js run lint - yarn workspace foundkey-js run lint

View file

@ -8,15 +8,15 @@ clone:
pipeline: pipeline:
install: install:
when: when:
event: branch: main
- pull_request event: push
image: node:18.6.0 image: node:18.6.0
commands: commands:
- yarn install - yarn install
lint: lint:
when: when:
event: branch: main
- pull_request event: push
image: node:18.6.0 image: node:18.6.0
commands: commands:
- yarn workspace sw run lint - yarn workspace sw run lint

View file

@ -5,11 +5,14 @@ clone:
depth: 1 # CI does not need commit history depth: 1 # CI does not need commit history
recursive: true recursive: true
depends_on:
- build
pipeline: pipeline:
build: build:
when: when:
event: branch: main
- pull_request event: push
image: node:18.6.0 image: node:18.6.0
commands: commands:
- yarn install - yarn install
@ -18,15 +21,15 @@ pipeline:
- yarn build - yarn build
mocha: mocha:
when: when:
event: branch: main
- pull_request event: push
image: node:18.6.0 image: node:18.6.0
commands: commands:
- yarn mocha - yarn mocha
e2e: e2e:
when: when:
event: branch: main
- pull_request event: push
image: cypress/included:10.3.0 image: cypress/included:10.3.0
commands: commands:
- npm run start:test & - npm run start:test &

View file

@ -1,46 +1,134 @@
# Contributor Covenant Code of Conduct # Contributor Covenant Code of Conduct
## Our Pledge ## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards ## Our Standards
Examples of behavior that contributes to creating a positive environment include: Examples of behavior that contributes to a positive environment for our
community include:
* Using welcoming and inclusive language * Demonstrating empathy and kindness toward other people
* Being respectful of differing viewpoints and experiences * Being respectful of differing opinions, viewpoints, and experiences
* Gracefully accepting constructive criticism * Giving and gracefully accepting constructive feedback
* Focusing on what is best for the community * Accepting responsibility and apologizing to those affected by our mistakes,
* Showing empathy towards other community members and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior by participants include: Examples of unacceptable behavior include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances * The use of sexualized language or imagery, and sexual attention or advances of
* Trolling, insulting/derogatory comments, and personal or political attacks any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment * Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission * Publishing others' private information, such as a physical or email address,
* Other conduct which could reasonably be considered inappropriate in a professional setting without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities ## Enforcement Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope ## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement ## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at syuilotan@yahoo.co.jp. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement via email at
johann<EFBFBD>qwertqwefsday.eu and/or toast<73>bunkerlabs.net .
All complaints will be reviewed and investigated promptly and fairly.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution ## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

84
docs/install-docker.md Normal file
View file

@ -0,0 +1,84 @@
# Create FoundKey instance with Docker Compose
This guide describes how to install and setup FoundKey with Docker Compose.
**WARNING:**
Never change the domain name (hostname) of an instance once you start using it!
## Requirements
- Docker or Podman
- Docker Compose plugin (or podman-compose)
If using Podman, replace `docker` with `podman`. Commands using `docker compose` should be replaced with `podman-compose`.
You may need to prefix `docker` commands with `sudo` unless your user is in the `docker` group or you are running Docker in rootless mode.
## Get the repository
```sh
git clone https://akkoma.dev/FoundKeyGang/FoundKey.git
cd FoundKey
```
To make it easier to perform your own changes on top, we suggest making a branch based on the latest tag.
In this example, we'll use `v13.0.0-preview1` as the tag to check out and `my-branch` as the branch name.
```sh
git checkout tags/v13.0.0-preview1 -b my-branch
```
## Configure
Copy example configuration files with following:
```sh
cp .config/docker_example.yml .config/default.yml
cp .config/docker_example.env .config/docker.env
```
Edit `default.yml` and `docker.env` according to the instructions in the files.
You will need to set the database host to `db` and Redis host to `redis` in order to use the internal container network for these services.
Edit `docker-compose.yml` if necessary. (e.g. if you want to change the port).
If you are using SELinux (eg. you're on Fedora or a RHEL derivative), you'll want to add the `Z` mount flag to the volume mounts to allow the containers to access the contents of those volumes.
## Build and initialize
The following command will build FoundKey and initialize the database.
This will take some time.
``` shell
docker compose build
docker compose run --rm web yarn run init
```
## Launch
You can start FoundKey with the following command:
```sh
docker compose up -d
```
In case you are encountering issues, you can run `docker compose logs -f` to get the log output of the running containers.
## How to update your FoundKey server
When updating, be sure to check the [release notes](https://akkoma.dev/FoundKeyGang/FoundKey/src/branch/main/CHANGELOG.md) to know in advance the changes and whether or not additional work is required (in most cases, it is not).
To update your branch to the latest tag (in this example `v13.0.0-preview2`), you can do the following:
```sh
git fetch -t
# Use --squash if you want to merge all of the changes in the tag into a single commit.
# Useful if you have made additional changes.
git merge tags/v13.0.0-preview2
# Rebuild and restart the docker container.
docker compose build
docker compose down && docker compose up -d
```
It may take some time depending on the contents of the update and the size of the database.
## How to execute CLI commands
```sh
docker compose run --rm web node packages/backend/built/tools/foo bar
```

View file

@ -38,22 +38,29 @@ Create a separate non-root user to run FoundKey:
adduser --disabled-password --disabled-login foundkey adduser --disabled-password --disabled-login foundkey
``` ```
The following steps will require logging into the `foundkey` user, so do that now.
```sh
su - foundkey
```
## Install FoundKey ## Install FoundKey
1. Login to the `foundkey` user We recommend using a local branch and merging in upstream releases as they get tagged. This allows for easy local customization of your install.
`su - foundkey` First, clone the FoundKey repo:
```sh
git clone https://akkoma.dev/FoundKeyGang/FoundKey
cd FoundKey
```
2. Clone the FoundKey repository Now create your local branch. In this example, we'll be using `toast.cafe` as the local branch name and release `v13.0.0-preview1` as the tag to track. To create that branch:
```sh
git checkout tags/v13.0.0-preview1 -b toast.cafe
```
`git clone --recursive https://akkoma.dev/FoundKeyGang/FoundKey foundkey` Updating will be covered in a later section. For now you'll want to install the dependencies using Yarn:
```sh
3. Navigate to the repository yarn install
```
`cd foundkey`
4. Install FoundKey's dependencies
`yarn install`
## Configure FoundKey ## Configure FoundKey
1. Copy `.config/example.yml` to `.config/default.yml`. 1. Copy `.config/example.yml` to `.config/default.yml`.
@ -185,14 +192,22 @@ rc-service foundkey start
You can check if the service is running with `rc-service foundkey status`. You can check if the service is running with `rc-service foundkey status`.
### Updating FoundKey ### Updating FoundKey
Use git to pull in the latest changes and rerun the build and migration commands: When a new release comes out, simply fetch and merge in the new tag. If you plan on making additional changes on top of that tag, we suggest using the `--squash` option with `git merge`.
```sh
git fetch -t
git merge tags/v13.0.0-preview2
# you are now on the "next" release
```
Now you'll want to update your dependencies and rebuild:
```sh ```sh
git pull
git submodule update --init
yarn install yarn install
# Use build-parallel if your system has 4GB or more RAM and want faster builds # Use build-parallel if your system has 4GB or more RAM and want faster builds
NODE_ENV=production yarn build NODE_ENV=production yarn build
```
Next, run the database migrations:
```sh
yarn migrate yarn migrate
``` ```

View file

@ -258,8 +258,6 @@ createFolder: "أنشئ مجلدًا"
renameFolder: "إعادة تسمية المجلد" renameFolder: "إعادة تسمية المجلد"
deleteFolder: "احذف هذا المجلد" deleteFolder: "احذف هذا المجلد"
addFile: "إضافة ملف" addFile: "إضافة ملف"
emptyDrive: "قرص التخزين فارغ"
emptyFolder: "هذا المجلد فارغ"
unableToDelete: "لا يمكن حذفه" unableToDelete: "لا يمكن حذفه"
inputNewFileName: "ادخل الإسم الجديد للملف" inputNewFileName: "ادخل الإسم الجديد للملف"
inputNewDescription: "أدخل تعليقًا توضيحيًا" inputNewDescription: "أدخل تعليقًا توضيحيًا"

View file

@ -273,8 +273,6 @@ createFolder: "ফোল্ডার তৈরি করুন"
renameFolder: "ফোল্ডার পুনঃনামকরন" renameFolder: "ফোল্ডার পুনঃনামকরন"
deleteFolder: "ফোল্ডার মুছুন" deleteFolder: "ফোল্ডার মুছুন"
addFile: "ফাইল যোগ করুন" addFile: "ফাইল যোগ করুন"
emptyDrive: "আপনার ড্রাইভ খালি"
emptyFolder: "এই ফোল্ডার খালি"
unableToDelete: "মুছে ফেলা যায়নি" unableToDelete: "মুছে ফেলা যায়নি"
inputNewFileName: "ফাইলের নতুন নাম লিখুন" inputNewFileName: "ফাইলের নতুন নাম লিখুন"
inputNewDescription: "নতুন ক্যাপশন লিখুন" inputNewDescription: "নতুন ক্যাপশন লিখুন"

View file

@ -249,7 +249,6 @@ createFolder: "Vytvořit složku"
renameFolder: "Přejmenovat složku" renameFolder: "Přejmenovat složku"
deleteFolder: "Odstranit složku" deleteFolder: "Odstranit složku"
addFile: "Přidat soubor" addFile: "Přidat soubor"
emptyFolder: "Tato složka je prázdná"
unableToDelete: "Nelze smazat" unableToDelete: "Nelze smazat"
inputNewFileName: "Zadejte nový název" inputNewFileName: "Zadejte nový název"
copyUrl: "Kopírovat URL" copyUrl: "Kopírovat URL"

View file

@ -284,8 +284,6 @@ createFolder: "Ordner erstellen"
renameFolder: "Ordner umbenennen" renameFolder: "Ordner umbenennen"
deleteFolder: "Ordner löschen" deleteFolder: "Ordner löschen"
addFile: "Datei hinzufügen" addFile: "Datei hinzufügen"
emptyDrive: "Deine Drive ist leer"
emptyFolder: "Dieser Ordner ist leer"
unableToDelete: "Nicht löschbar" unableToDelete: "Nicht löschbar"
inputNewFileName: "Gib einen neuen Dateinamen ein" inputNewFileName: "Gib einen neuen Dateinamen ein"
inputNewDescription: "Gib eine neue Beschreibung ein" inputNewDescription: "Gib eine neue Beschreibung ein"
@ -1313,6 +1311,7 @@ _notification:
followRequestAccepted: "Akzeptierte Follow-Anfragen" followRequestAccepted: "Akzeptierte Follow-Anfragen"
groupInvited: "Erhaltene Gruppeneinladungen" groupInvited: "Erhaltene Gruppeneinladungen"
app: "Benachrichtigungen von Apps" app: "Benachrichtigungen von Apps"
move: Account-Umzüge
_actions: _actions:
followBack: "folgt dir nun auch" followBack: "folgt dir nun auch"
reply: "Antworten" reply: "Antworten"
@ -1401,3 +1400,15 @@ oauthErrorGoBack: Bei der Authentifizierung einer Drittanbieter-Anwendung ist ei
Fehler aufgetreten. Bitte geh zurück und versuche es erneut. Fehler aufgetreten. Bitte geh zurück und versuche es erneut.
appAuthorization: Anwendungs-Authorisierung appAuthorization: Anwendungs-Authorisierung
noPermissionsRequested: (Keine Berechtigungen angefordert.) noPermissionsRequested: (Keine Berechtigungen angefordert.)
_remoteInteract:
title: Es tut mir leid, aber das kann ich nicht tun.
description: Diese Aktion kann gerade nicht ausgeführt werden. Vermutlich musst
du diese Aktion auf deiner eigenen Instanz ausführen, oder dich anmelden.
urlInstructions: Kopiere diese URL. Wenn du sie auf deiner Instanz in das Suchfeld
einfügst, solltest du zum richtigen Ort gelangen.
movedTo: Diese Person ist umgezogen zu {handle}.
attachedToNotes: Notizen mit dieser Datei
showAttachedNotes: Zeige Notizen mit dieser Datei
uploadFailed: Hochladen fehlgeschlagen
uploadFailedDescription: Die Datei konnte nicht hochgeladen werden.
uploadFailedSize: Die Datei ist zu groß.

View file

@ -233,6 +233,9 @@ resetAreYouSure: "Really reset?"
saved: "Saved" saved: "Saved"
messaging: "Chat" messaging: "Chat"
upload: "Upload" upload: "Upload"
uploadFailed: "Upload failed"
uploadFailedDescription: "The file could not be uploaded."
uploadFailedSize: "The file is too large to be uploaded."
keepOriginalUploading: "Keep original image" keepOriginalUploading: "Keep original image"
keepOriginalUploadingDescription: "Saves the originally uploaded image as-is. If turned\ keepOriginalUploadingDescription: "Saves the originally uploaded image as-is. If turned\
\ off, a version to display on the web will be generated on upload." \ off, a version to display on the web will be generated on upload."
@ -278,8 +281,6 @@ createFolder: "Create a folder"
renameFolder: "Rename this folder" renameFolder: "Rename this folder"
deleteFolder: "Delete this folder" deleteFolder: "Delete this folder"
addFile: "Add a file" addFile: "Add a file"
emptyDrive: "Your Drive is empty"
emptyFolder: "This folder is empty"
unableToDelete: "Unable to delete" unableToDelete: "Unable to delete"
inputNewFileName: "Enter a new filename" inputNewFileName: "Enter a new filename"
inputNewDescription: "Enter new caption" inputNewDescription: "Enter new caption"
@ -349,6 +350,8 @@ withReplies: "Include replies"
connectedTo: "Following account(s) are connected" connectedTo: "Following account(s) are connected"
notesAndReplies: "Notes and replies" notesAndReplies: "Notes and replies"
withFiles: "Including files" withFiles: "Including files"
attachedToNotes: "Notes with this file"
showAttachedNotes: "Show notes with this file"
silence: "Silence" silence: "Silence"
silenceConfirm: "Are you sure that you want to silence this user?" silenceConfirm: "Are you sure that you want to silence this user?"
unsilence: "Undo silencing" unsilence: "Undo silencing"
@ -829,6 +832,7 @@ oauthErrorGoBack: "An error happened while trying to authenticate a 3rd party ap
\ Please go back and try again." \ Please go back and try again."
appAuthorization: "App authorization" appAuthorization: "App authorization"
noPermissionsRequested: "(No permissions requested.)" noPermissionsRequested: "(No permissions requested.)"
movedTo: "This user has moved to {handle}."
_emailUnavailable: _emailUnavailable:
used: "This email address is already being used" used: "This email address is already being used"
format: "The format of this email address is invalid" format: "The format of this email address is invalid"
@ -1306,6 +1310,7 @@ _notification:
receiveFollowRequest: "Received follow requests" receiveFollowRequest: "Received follow requests"
followRequestAccepted: "Accepted follow requests" followRequestAccepted: "Accepted follow requests"
groupInvited: "Group invitations" groupInvited: "Group invitations"
move: "Others moving accounts"
app: "Notifications from linked apps" app: "Notifications from linked apps"
_actions: _actions:
followBack: "followed you back" followBack: "followed you back"

View file

@ -185,16 +185,17 @@ clearQueueConfirmText: "Las notas aún no entregadas no se federarán. Normalmen
clearCachedFiles: "Limpiar caché" clearCachedFiles: "Limpiar caché"
clearCachedFilesConfirm: "¿Desea borrar todos los archivos remotos cacheados?" clearCachedFilesConfirm: "¿Desea borrar todos los archivos remotos cacheados?"
blockedInstances: "Instancias bloqueadas" blockedInstances: "Instancias bloqueadas"
blockedInstancesDescription: "Seleccione los hosts de las instancias que desea bloquear,\ blockedInstancesDescription: "Seleccione los hosts de las instancias que desea bloquear.\
\ separadas por una linea nueva. Las instancias bloqueadas no podrán comunicarse\ \ Las instancias listadas no podrán comunicarse con esta instancia. Los nombres\
\ con esta instancia." \ de dominios Non-ASCII deben ser codificados con punycode.\nLos sub dominios de\
\ la lista de instancias serán bloqueados también."
muteAndBlock: "Silenciar y bloquear" muteAndBlock: "Silenciar y bloquear"
mutedUsers: "Usuarios silenciados" mutedUsers: "Usuarios silenciados"
blockedUsers: "Usuarios bloqueados" blockedUsers: "Usuarios bloqueados"
noUsers: "No hay usuarios" noUsers: "No hay usuarios"
editProfile: "Editar perfil" editProfile: "Editar perfil"
noteDeleteConfirm: "¿Desea borrar esta nota?" noteDeleteConfirm: "¿Desea borrar esta nota?"
pinLimitExceeded: "Ya no se pueden fijar más posts" pinLimitExceeded: "Ya no se pueden fijar más notas."
intro: "¡La instalación de FoundKey ha terminado! Crea el usuario administrador." intro: "¡La instalación de FoundKey ha terminado! Crea el usuario administrador."
done: "Terminado" done: "Terminado"
processing: "Procesando" processing: "Procesando"
@ -275,8 +276,6 @@ createFolder: "Crear carpeta"
renameFolder: "Renombrar carpeta" renameFolder: "Renombrar carpeta"
deleteFolder: "Borrar carpeta" deleteFolder: "Borrar carpeta"
addFile: "Agregar archivo" addFile: "Agregar archivo"
emptyDrive: "El drive está vacío"
emptyFolder: "La carpeta está vacía"
unableToDelete: "No se puede borrar" unableToDelete: "No se puede borrar"
inputNewFileName: "Ingrese un nuevo nombre de archivo" inputNewFileName: "Ingrese un nuevo nombre de archivo"
inputNewDescription: "Ingrese nueva descripción" inputNewDescription: "Ingrese nueva descripción"
@ -558,7 +557,7 @@ smtpPass: "Contraseña"
emptyToDisableSmtpAuth: "Deje el nombre del usuario y la contraseña en blanco para\ emptyToDisableSmtpAuth: "Deje el nombre del usuario y la contraseña en blanco para\
\ deshabilitar la autenticación SMTP" \ deshabilitar la autenticación SMTP"
smtpSecure: "Usar SSL/TLS implícito en la conexión SMTP" smtpSecure: "Usar SSL/TLS implícito en la conexión SMTP"
smtpSecureInfo: "Apagar cuando se use STARTTLS" smtpSecureInfo: "Apagar cuando se use STARTTLS."
testEmail: "Prueba de envío" testEmail: "Prueba de envío"
wordMute: "Silenciar palabras" wordMute: "Silenciar palabras"
instanceMute: "Instancias silenciadas" instanceMute: "Instancias silenciadas"
@ -599,7 +598,7 @@ openInNewTab: "Abrir en una Nueva Pestaña"
defaultNavigationBehaviour: "Navegación por defecto" defaultNavigationBehaviour: "Navegación por defecto"
instanceTicker: "Información de notas de la instancia" instanceTicker: "Información de notas de la instancia"
system: "Sistema" system: "Sistema"
switchUi: "Cambiar interfaz de usuario" switchUi: "Cambiar interfaz"
desktop: "Escritorio" desktop: "Escritorio"
clip: "Clip" clip: "Clip"
createNew: "Crear" createNew: "Crear"
@ -691,7 +690,7 @@ notSpecifiedMentionWarning: "Algunas menciones no están incluidas en el destino
info: "Información" info: "Información"
userInfo: "Información del usuario" userInfo: "Información del usuario"
unknown: "Desconocido" unknown: "Desconocido"
hideOnlineStatus: "mostrarse como desconectado" hideOnlineStatus: "Mostrarse como desconectado"
hideOnlineStatusDescription: "Ocultar su estado en línea puede reducir la eficacia\ hideOnlineStatusDescription: "Ocultar su estado en línea puede reducir la eficacia\
\ de algunas funciones, como la búsqueda" \ de algunas funciones, como la búsqueda"
online: "En línea" online: "En línea"
@ -730,7 +729,7 @@ misskeyUpdated: "¡FoundKey ha sido actualizado!"
whatIsNew: "Mostrar cambios" whatIsNew: "Mostrar cambios"
translate: "Traducir" translate: "Traducir"
translatedFrom: "Traducido de {x}" translatedFrom: "Traducido de {x}"
accountDeletionInProgress: "La eliminación de la cuenta está en curso" accountDeletionInProgress: "La eliminación de la cuenta está en curso."
usernameInfo: "Un nombre que identifique su cuenta de otras en este servidor. Puede\ usernameInfo: "Un nombre que identifique su cuenta de otras en este servidor. Puede\
\ utilizar el alfabeto (a~z, A~Z), dígitos (0~9) o guiones bajos (_). Los nombres\ \ utilizar el alfabeto (a~z, A~Z), dígitos (0~9) o guiones bajos (_). Los nombres\
\ de usuario no se pueden cambiar posteriormente." \ de usuario no se pueden cambiar posteriormente."
@ -757,30 +756,43 @@ ffVisibility: "Visibilidad de seguidores y seguidos"
hide: "Ocultar" hide: "Ocultar"
indefinitely: "Sin límite de tiempo" indefinitely: "Sin límite de tiempo"
_ffVisibility: _ffVisibility:
public: "Publicar" public: "Público"
private: Privado
followers: Visible solo a seguidores
_accountDelete: _accountDelete:
accountDelete: "Eliminar Cuenta" accountDelete: "Eliminar Cuenta"
started: La eliminación ha iniciado.
sendEmail: Cuando se complete la eliminación de la cuenta, un correo será enviado
a la dirección registrada de la cuenta.
requestAccountDelete: Solicitar eliminación de cuenta
inProgress: Eliminación en progreso
mayTakeTime: Como la eliminación de la cuenta es un proceso que consume muchos recursos,
puede llevar algún tiempo según la cantidad de contenido que se haya creado y
la cantidad de archivos que cargados.
_forgotPassword: _forgotPassword:
contactAdmin: "Esta instancia no admite el uso de direcciones de correo electrónico,\ contactAdmin: "Esta instancia no admite el uso de direcciones de correo electrónico,\
\ póngase en contacto con el administrador de la instancia para restablecer su\ \ póngase en contacto con el administrador de la instancia para restablecer su\
\ contraseña" \ contraseña."
enterEmail: Ingrese el correo que se uso para el registro. Se enviará un link para
que resetear la contraseña.
ifNoEmail: Si no se utilizó un correo durante el registro, por favor contacte con
el administrador de la instancia.
_email: _email:
_follow: _follow:
title: "te ha seguido" title: "Tienes un nuevo seguidor"
_receiveFollowRequest: _receiveFollowRequest:
title: "Has recibido una solicitud de seguimiento" title: "Has recibido una solicitud de seguimiento"
_plugin: _plugin:
install: "Instalar plugins" install: "Instalar plugins"
installWarn: "Por favor no instale plugins que no son de confianza" installWarn: "Por favor no instale plugins que no son de confianza."
_registry: _registry:
scope: "Alcance" scope: "Alcance"
key: "Clave" key: "Clave"
keys: "Clave" keys: "Claves"
domain: "Dominio" domain: "Dominio"
createKey: "Crear una llave" createKey: "Crear una llave"
_aboutMisskey: _aboutMisskey:
about: "FoundKey es un software de código abierto, desarrollado por syuilo desde\ about: "FoundKey es una bifurcación de Misskey desarrollada desde Julio de 2022."
\ el 2014"
allContributors: "Todos los colaboradores" allContributors: "Todos los colaboradores"
source: "Código fuente" source: "Código fuente"
_nsfw: _nsfw:
@ -791,13 +803,13 @@ _mfm:
cheatSheet: "Hoja de referencia de MFM" cheatSheet: "Hoja de referencia de MFM"
intro: "MFM es un lenguaje de marcado dedicado que se puede usar en varios lugares\ intro: "MFM es un lenguaje de marcado dedicado que se puede usar en varios lugares\
\ dentro de FoundKey. Aquí puede ver una lista de sintaxis disponibles en MFM." \ dentro de FoundKey. Aquí puede ver una lista de sintaxis disponibles en MFM."
dummy: "FoundKey expande el mundo de la Fediverso" dummy: "FoundKey expande el mundo del Fediverso"
mention: "Menciones" mention: "Menciones"
mentionDescription: "El signo @ seguido de un nombre de usuario se puede utilizar\ mentionDescription: "El signo @ seguido de un nombre de usuario se puede utilizar\
\ para notificar a un usuario en particular." \ para notificar a un usuario en particular."
hashtag: "Hashtag" hashtag: "Hashtag #"
url: "URL" url: "URL"
urlDescription: "Se pueden mostrar las URL" urlDescription: "Se pueden mostrar las URL."
link: "Vínculo" link: "Vínculo"
bold: "Negrita" bold: "Negrita"
center: "Centrar" center: "Centrar"
@ -812,6 +824,48 @@ _mfm:
\ / derecha." \ / derecha."
font: "Fuente" font: "Fuente"
rotate: "Rotar" rotate: "Rotar"
jump: Animación (Salto)
jumpDescription: Da al contenido un salto animado.
bounce: Animación (Bounce)
centerDescription: Muestra el contenido de manera centrada.
inlineCode: Código (Inline)
blockMath: 'Función matemática (Block)'
blockMathDescription: Muestra líneas múltiples de fórmulas matemáticas (KaTeX) en
un bloque.
inlineCodeDescription: Muestra resaltado de sintaxis en línea para el código (program).
quoteDescription: Muestra el contenido en una cita.
searchDescription: Muestra un cuadro de búsqueda con texto pre introducido.
jellyDescription: Le da al contenido una animación del tipo Jelly.
tada: Animación (Tada)
shake: Animación (Shake)
hashtagDescription: Puedes especificar un hashtag usando un número, signo y texto.
linkDescription: Partes especificas del texto pueden ser mostradas como URL.
boldDescription: Resalta las letras haciéndolas más gruesas.
small: Pequeño
smallDescription: Muestra el contenido de manera pequeña y delgada.
inlineMathDescription: Muestra fórmulas matemáticas (KaTeX) en linea.
tadaDescription: Da al contenido un "Tada!" del tipo animación.
bounceDescription: Brinda al contenido una animación de rebote.
emojiDescription: Al colocar dos puntos en un emoji personalizado, se puede mostrar
un emoji personalizado.
jelly: Animación (Jelly)
twitchDescription: Da al contenido una animación fuertemente temblorosa.
twitch: Animación (Twitch)
spin: Animación (Spin)
shakeDescription: Brinda al contenido una animación temblorosa.
inlineMath: Función matemática (Inline)
rainbow: Arcoíris
x4Description: Muestra el contenido de la manera más grandemente posible.
blurDescription: Muestra borroso el contenido. Se mostrará con claridad cuando se
cubra.
spinDescription: Da al contenido una animación de girar.
x2: Grande
x2Description: Muestra en grande el contenido.
x3Description: Muestra más grande el contenido.
x4: Increíblemente grande
blur: Borroso
fontDescription: Agrega la fuente para mostrar contenido.
x3: Muy grande
_instanceTicker: _instanceTicker:
none: "No mostrar" none: "No mostrar"
remote: "Mostrar a usuarios remotos" remote: "Mostrar a usuarios remotos"
@ -1134,7 +1188,7 @@ _notification:
youWereFollowed: "te ha seguido" youWereFollowed: "te ha seguido"
youReceivedFollowRequest: "Has mandado una solicitud de seguimiento" youReceivedFollowRequest: "Has mandado una solicitud de seguimiento"
yourFollowRequestAccepted: "Tu solicitud de seguimiento fue aceptada" yourFollowRequestAccepted: "Tu solicitud de seguimiento fue aceptada"
youWereInvitedToGroup: "Invitado al grupo" youWereInvitedToGroup: "{userName} invitado al grupo"
_types: _types:
follow: "Siguiendo" follow: "Siguiendo"
mention: "Menciones" mention: "Menciones"
@ -1169,3 +1223,116 @@ _deck:
mentions: "Menciones" mentions: "Menciones"
direct: "Mensaje directo" direct: "Mensaje directo"
_services: {} _services: {}
translationService: Servicio de traducción
translationSettings: Ajustes de traducción
selectAll: Seleccionar todo
setCategory: Establecer categoría
unlimited: Ilimitado
setTag: Establecer tag
recentNHours: Últimas {n} horas
check: Verificar
unrenoteAll: Quitar todas las renotas
unclip: Desenganchar
deleteAllFiles: Borrar todos los archivos.
voteConfirm: ¿Confirmas tu voto para "{choice}"?
tenMinutes: 10 minutos
oneHour: Una hora
failedToFetchAccountInformation: No se pudo obtener la información de la cuenta
rateLimitExceeded: Límite de velocidad excedido
cropImage: Cortar imagen
recentNDays: Últimos {n} días
typeToConfirm: Por favor ingrese {x} para confirmar
cropImageAsk: ¿Desea cortar esta imagen?
cannotAttachFileWhenAccountSwitched: No puedes adjuntar un archivo mientras cambias
de cuenta.
cannotSwitchAccountWhenFileAttached: No puedes cambiar de cuenta mientras se adjuntan
archivos.
threadMuteNotificationsDesc: Selecciona las notificaciones que deseas ver de este
hilo. Los ajustes globales de notificación también se aplicarán. La desactivación
tiene prioridad.
ffVisibilityDescription: Permite configurar quien puede ver a quienes sigues y quienes
te siguen.
deleteAccountConfirm: Esto borrará se manera irreversible tu cuenta {handle}. ¿Quieres
proceder?
incorrectPassword: Contraseña incorrecta.
leaveGroup: Dejar grupo
overridedDeviceKind: Tipo de dispositivo
smartphone: Smartphone
auto: Automático
themeColor: Color de teletipo de instancia
size: Tamaño
numberOfColumn: Número de columnas
instanceDefaultLightTheme: Tema claro predeterminado para la instancia
oneDay: Un día
oneWeek: Una semana
numberOfPageCache: Número de páginas en caché
confirmToUnclipAlreadyClippedNote: Esta nota ya forma parte del clip "{name}". ¿Quieres
eliminarlo de este clip en su lugar?
noEmailServerWarning: Correo del servidor no configurado.
thereIsUnresolvedAbuseReportWarning: Hay reportes sin resolver.
recommended: Recomendado
addTag: Agregar tag
removeTag: Quitar tag
_emailUnavailable:
used: El correo ya se encuentra en uso
format: El formato del correo es invalido
disposable: No se puede utilizar direcciones de correo electrónico desechables
mx: El servidor del correo es inválido
smtp: El servido del correo no responde
exportAll: Exportar todo
exportSelected: Exportar seleccionados
botFollowRequiresApproval: Solicitudes de seguimiento de cuentas marcadas como "bots"
necesitan aprobación
unrenoteAllConfirm: ¿Estas seguro de querer quitar todas las renotas de esta nota?
signinHistoryExpires: Los datos de los intentos de sesión serán borrados automáticamente
luego de 60 días, debido a regulaciones de privacidad.
tablet: Tableta
mutePeriod: Duración de mute
reflectMayTakeTime: Puede tomar cierto tiempo en que se reflejen los cambios.
isSystemAccount: Una cuenta creada y operada automáticamente por el sistema.
deleteAccount: Eliminar cuenta
numberOfPageCacheDescription: Aumentar este número mejorará la comodidad para los
usuarios, pero provocará una mayor carga del servidor y más memoria se utilizará.
externalCssSnippets: Algunos fragmentos de CSS para inspirar (no administrados por
FoundKey)
_signup:
emailSent: Se envió un correo de confirmación a ({email}). Por favor haz click en
link adjunto para completar la creación de cuenta.
almostThere: Ya casi
emailAddressInfo: Por favor ingresa tu correo electrónico. No sera público.
instanceDefaultThemeDescription: Ingrese el código de tema en el formato del objeto
stopActivityDeliveryDescription: Las actividades locales no serán enviadas a esta
instancia. Las actividades recibidas de recepción funcionan como antes.
documentation: Documentación
file: Archivo
federateBlocks: Bloques Federados
federateBlocksDescription: Si esta deshabilitado, las actividades bloqueadas no serán
enviadas.
useDrawerReactionPickerForMobile: Mostrar selección de reacción como cajón en móvil
leaveGroupConfirm: ¿Estas seguro de salir "{name}"?
clickToFinishEmailVerification: Por favor presiona [{ok}] para completar la verificación
email.
oauthErrorGoBack: Ocurrió un error al tratar de autenticar una app de terceros. Por
favor regresa e intenta de nuevo.
appAuthorization: Autorización de app
noPermissionsRequested: (No hay permisos solicitados).
selectMode: Selección múltiple
renoteMute: Ocultar renotas
renoteUnmute: Mostrar renotas
blockThisInstanceDescription: Las actividades locales no serán enviadas a esta instancia.
Las actividades de esta instancia serán descartadas.
instanceDefaultDarkTheme: Tema oscuro predeterminado para la instancia
showLess: Mostrar menos
regexpError: Error de Expresión Regular
regexpErrorDescription: 'Hay un error en la expresión regular de la linea {line} de
{tab} palabras silenciadas:'
unlikeConfirm: ¿En verdad quieres remover tu like?
breakFollow: Quitar seguidor
reporter: Reportero
continueThread: Ver la continuación del hilo
uploadFailedSize: El archivo es muy grande para subirse.
uploadFailed: Subida fallida
uploadFailedDescription: No se pudo subir el archivo.
movedTo: Este usuario se ha movido a {handle}.
attachedToNotes: Notas del archivo
showAttachedNotes: Mostrar notas del archivo

View file

@ -277,8 +277,6 @@ createFolder: "Créer un dossier"
renameFolder: "Renommer le dossier" renameFolder: "Renommer le dossier"
deleteFolder: "Supprimer le dossier" deleteFolder: "Supprimer le dossier"
addFile: "Ajouter un fichier" addFile: "Ajouter un fichier"
emptyDrive: "Le Drive est vide"
emptyFolder: "Le dossier est vide"
unableToDelete: "Suppression impossible" unableToDelete: "Suppression impossible"
inputNewFileName: "Entrez un nouveau nom de fichier" inputNewFileName: "Entrez un nouveau nom de fichier"
inputNewDescription: "Veuillez entrer une nouvelle description" inputNewDescription: "Veuillez entrer une nouvelle description"

View file

@ -12,7 +12,7 @@ password: "Kata sandi"
forgotPassword: "Lupa Kata Sandi" forgotPassword: "Lupa Kata Sandi"
fetchingAsApObject: "Mengambil data dari Fediverse..." fetchingAsApObject: "Mengambil data dari Fediverse..."
ok: "OK" ok: "OK"
gotIt: "Saya mengerti" gotIt: "Saya mengerti!"
cancel: "Batalkan" cancel: "Batalkan"
renotedBy: "direnote oleh {user}" renotedBy: "direnote oleh {user}"
noNotes: "Tidak ada catatan" noNotes: "Tidak ada catatan"
@ -24,7 +24,7 @@ otherSettings: "Pengaturan lainnya"
openInWindow: "Buka di jendela" openInWindow: "Buka di jendela"
profile: "Profil" profile: "Profil"
timeline: "Linimasa" timeline: "Linimasa"
noAccountDescription: "Pengguna ini belum menulis bio" noAccountDescription: "Pengguna ini belum menulis biodata mereka."
login: "Masuk" login: "Masuk"
loggingIn: "Sedang masuk" loggingIn: "Sedang masuk"
logout: "Keluar" logout: "Keluar"
@ -62,8 +62,8 @@ files: "Berkas"
download: "Unduh" download: "Unduh"
driveFileDeleteConfirm: "Hapus {name}? Catatan dengan berkas terkait juga akan terhapus." driveFileDeleteConfirm: "Hapus {name}? Catatan dengan berkas terkait juga akan terhapus."
unfollowConfirm: "Berhenti mengikuti {name}?" unfollowConfirm: "Berhenti mengikuti {name}?"
exportRequested: "Kamu telah meminta ekspor. Ini akan memakan waktu sesaat. Setelah\ exportRequested: "Anda telah meminta ekspor. Ini mungkin memerlukan waktu beberapa\
\ ekspor selesai, berkas yang dihasilkan akan ditambahkan ke Drive" \ saat. File ini akan ditambahkan ke Drive Anda setelah selesai."
importRequested: "Kamu telah meminta impor. Ini akan memakan waktu sesaat." importRequested: "Kamu telah meminta impor. Ini akan memakan waktu sesaat."
lists: "Daftar" lists: "Daftar"
note: "Catat" note: "Catat"
@ -100,8 +100,8 @@ clickToShow: "Klik untuk melihat"
sensitive: "Konten sensitif" sensitive: "Konten sensitif"
add: "Tambahkan" add: "Tambahkan"
reaction: "Reaksi" reaction: "Reaksi"
reactionSettingDescription2: "Geser untuk memindah urutkan, klik untuk menghapus,\ reactionSettingDescription2: "Seret untuk menyusun ulang, klik untuk menghapus, tekan\
\ tekan \"+\" untuk menambahkan" \ \"+\" untuk menambahkan."
attachCancel: "Hapus lampiran" attachCancel: "Hapus lampiran"
markAsSensitive: "Tandai sebagai konten sensitif" markAsSensitive: "Tandai sebagai konten sensitif"
unmarkAsSensitive: "Hapus tanda konten sensitif" unmarkAsSensitive: "Hapus tanda konten sensitif"
@ -193,10 +193,10 @@ blockedUsers: "Pengguna yang diblokir"
noUsers: "Tidak ada pengguna" noUsers: "Tidak ada pengguna"
editProfile: "Sunting profil" editProfile: "Sunting profil"
noteDeleteConfirm: "Apakah kamu yakin ingin menghapus catatan ini?" noteDeleteConfirm: "Apakah kamu yakin ingin menghapus catatan ini?"
pinLimitExceeded: "Kamu tidak dapat menyematkan catatan lagi" pinLimitExceeded: "Anda tidak dapat menyematkan catatan lagi."
intro: "Instalasi FoundKey telah selesai! Mohon untuk membuat pengguna admin." intro: "Instalasi FoundKey telah selesai! Mohon untuk membuat pengguna admin."
done: "Selesai" done: "Selesai"
processing: "Memproses" processing: "Pemrosesan..."
preview: "Pratinjau" preview: "Pratinjau"
default: "Bawaan" default: "Bawaan"
noCustomEmojis: "Tidak ada emoji kustom" noCustomEmojis: "Tidak ada emoji kustom"
@ -210,7 +210,7 @@ publishing: "Sedang menyiarkan langsung"
notResponding: "Tidak ada respon" notResponding: "Tidak ada respon"
changePassword: "Ubah kata sandi" changePassword: "Ubah kata sandi"
security: "Keamanan" security: "Keamanan"
retypedNotMatch: "Input tidak sama" retypedNotMatch: "Input tidak cocok."
currentPassword: "Kata sandi saat ini" currentPassword: "Kata sandi saat ini"
newPassword: "Kata sandi baru" newPassword: "Kata sandi baru"
newPasswordRetype: "Ulangi kata sandi baru" newPasswordRetype: "Ulangi kata sandi baru"
@ -237,7 +237,7 @@ fromUrl: "Dari URL"
uploadFromUrl: "Unggah dari URL" uploadFromUrl: "Unggah dari URL"
uploadFromUrlDescription: "URL berkas yang ingin kamu unggah" uploadFromUrlDescription: "URL berkas yang ingin kamu unggah"
uploadFromUrlRequested: "Pengunggahan telah diminta" uploadFromUrlRequested: "Pengunggahan telah diminta"
uploadFromUrlMayTakeTime: "Membutuhkan beberapa waktu hingga pengunggahan selesai" uploadFromUrlMayTakeTime: "Mungkin diperlukan waktu hingga unggahan selesai."
explore: "Jelajahi" explore: "Jelajahi"
messageRead: "Telah dibaca" messageRead: "Telah dibaca"
noMoreHistory: "Tidak ada sejarah lagi" noMoreHistory: "Tidak ada sejarah lagi"
@ -274,8 +274,6 @@ createFolder: "Buat folder"
renameFolder: "Ubah nama folder" renameFolder: "Ubah nama folder"
deleteFolder: "Hapus folder" deleteFolder: "Hapus folder"
addFile: "Tambahkan berkas" addFile: "Tambahkan berkas"
emptyDrive: "Drive kosong"
emptyFolder: "Folder kosong"
unableToDelete: "Tidak dapat menghapus" unableToDelete: "Tidak dapat menghapus"
inputNewFileName: "Masukkan nama berkas yang baru" inputNewFileName: "Masukkan nama berkas yang baru"
inputNewDescription: "Masukkan keterangan disini" inputNewDescription: "Masukkan keterangan disini"
@ -405,7 +403,7 @@ newMessageExists: "Kamu mendapatkan pesan baru"
onlyOneFileCanBeAttached: "Kamu hanya dapat melampirkan satu berkas ke dalam pesan" onlyOneFileCanBeAttached: "Kamu hanya dapat melampirkan satu berkas ke dalam pesan"
signinRequired: "Silahkan login" signinRequired: "Silahkan login"
invitationCode: "Kode undangan" invitationCode: "Kode undangan"
checking: "Memeriksa" checking: "Memeriksa..."
available: "Tersedia" available: "Tersedia"
unavailable: "Tidak tersedia" unavailable: "Tidak tersedia"
usernameInvalidFormat: "Hanya dapat menerima karakter a-z, A-Z dan angka 0-9." usernameInvalidFormat: "Hanya dapat menerima karakter a-z, A-Z dan angka 0-9."
@ -447,11 +445,10 @@ showFeaturedNotesInTimeline: "Tampilkan catatan yang diunggulkan di linimasa"
objectStorage: "Object Storage" objectStorage: "Object Storage"
useObjectStorage: "Gunakan object storage" useObjectStorage: "Gunakan object storage"
objectStorageBaseUrl: "Base URL" objectStorageBaseUrl: "Base URL"
objectStorageBaseUrlDesc: "Prefix URL digunakan untuk mengkonstruksi URL ke object\ objectStorageBaseUrlDesc: "URL yang digunakan sebagai referensi. Tentukan URL CDN\
\ (media) referencing. Tentukan URL jika kamu menggunakan CDN atau Proxy, jika tidak\ \ atau Proksi Anda jika Anda menggunakan keduanya.\nUntuk S3 gunakan 'https://<bucket>.s3.amazonaws.com'\
\ tentukan alamat yang dapat diakses secara publik sesuai dengan panduan dari layanan\ \ dan untuk GCS atau layanan yang setara gunakan 'https://storage.googleapis.com/<bucket>',\
\ yang akan kamu gunakan, contohnya. 'https://<bucket>.s3.amazonaws.com' untuk AWS\ \ dst."
\ S3, dan 'https://storage.googleapis.com/<bucket>' untuk GCS."
objectStorageBucket: "Bucket" objectStorageBucket: "Bucket"
objectStorageBucketDesc: "Mohon tentukan nama bucket yang digunakan pada layanan yang\ objectStorageBucketDesc: "Mohon tentukan nama bucket yang digunakan pada layanan yang\
\ telah dikonfigurasi." \ telah dikonfigurasi."
@ -649,8 +646,8 @@ contact: "Kontak"
useSystemFont: "Gunakan font bawaan sistem operasi" useSystemFont: "Gunakan font bawaan sistem operasi"
clips: "Klip" clips: "Klip"
makeExplorable: "Buat akun tampil di \"Jelajahi\"" makeExplorable: "Buat akun tampil di \"Jelajahi\""
makeExplorableDescription: "Jika kamu mematikan ini, akun kamu tidak akan muncul di\ makeExplorableDescription: "Jika Anda menonaktifkannya, akun Anda tidak akan muncul\
\ bagian \"Jelajahi:" \ di bagian \"Jelajahi\"."
showGapBetweenNotesInTimeline: "Tampilkan jarak diantara catatan pada linimasa" showGapBetweenNotesInTimeline: "Tampilkan jarak diantara catatan pada linimasa"
duplicate: "Duplikat" duplicate: "Duplikat"
left: "Kiri" left: "Kiri"
@ -761,7 +758,7 @@ ffVisibility: "Visibilitas Mengikuti/Pengikut"
ffVisibilityDescription: "Mengatur siapa yang dapat melihat pengikutmu dan yang kamu\ ffVisibilityDescription: "Mengatur siapa yang dapat melihat pengikutmu dan yang kamu\
\ ikuti." \ ikuti."
continueThread: "Lihat lanjutan thread" continueThread: "Lihat lanjutan thread"
deleteAccountConfirm: "Akun akan dihapus. Apakah kamu yakin?" deleteAccountConfirm: "Ini akan menghapuskan akun {handle} secara permanen. Lanjutkan?"
incorrectPassword: "Kata sandi salah." incorrectPassword: "Kata sandi salah."
voteConfirm: "Konfirmasi suara kamu untuk ({choice})" voteConfirm: "Konfirmasi suara kamu untuk ({choice})"
hide: "Sembunyikan" hide: "Sembunyikan"
@ -1081,7 +1078,7 @@ _auth:
shareAccess: "Apakah kamu ingin mengijinkan \"{name}\" untuk mengakses akun ini?" shareAccess: "Apakah kamu ingin mengijinkan \"{name}\" untuk mengakses akun ini?"
shareAccessAsk: "Apakah kamu ingin mengijinkan aplikasi ini untuk mengakses akun\ shareAccessAsk: "Apakah kamu ingin mengijinkan aplikasi ini untuk mengakses akun\
\ kamu?" \ kamu?"
permissionAsk: "Aplikasi ini membutuhkan beberapa ijin, yaitu:" permissionAsk: "Aplikasi ini meminta izin berikut ini"
pleaseGoBack: "Mohon kembali ke aplikasi kamu" pleaseGoBack: "Mohon kembali ke aplikasi kamu"
callback: "Mengembalikan kamu ke aplikasi" callback: "Mengembalikan kamu ke aplikasi"
denied: "Akses ditolak" denied: "Akses ditolak"
@ -1265,7 +1262,7 @@ _notification:
youWereFollowed: "Mengikuti kamu" youWereFollowed: "Mengikuti kamu"
youReceivedFollowRequest: "Kamu menerima permintaan mengikuti" youReceivedFollowRequest: "Kamu menerima permintaan mengikuti"
yourFollowRequestAccepted: "Permintaan mengikuti kamu telah diterima" yourFollowRequestAccepted: "Permintaan mengikuti kamu telah diterima"
youWereInvitedToGroup: "Telah diundang ke grup" youWereInvitedToGroup: "{userName} mengundang Anda ke grup"
pollEnded: "Hasil Kuesioner telah keluar" pollEnded: "Hasil Kuesioner telah keluar"
emptyPushNotificationMessage: "Pembaruan notifikasi dorong" emptyPushNotificationMessage: "Pembaruan notifikasi dorong"
_types: _types:

View file

@ -268,8 +268,6 @@ createFolder: "Nuova cartella"
renameFolder: "Rinominare cartella" renameFolder: "Rinominare cartella"
deleteFolder: "Elimina cartella" deleteFolder: "Elimina cartella"
addFile: "Allega" addFile: "Allega"
emptyDrive: "Il Drive è vuoto"
emptyFolder: "La cartella è vuota"
unableToDelete: "Eliminazione impossibile" unableToDelete: "Eliminazione impossibile"
inputNewFileName: "Inserisci nome del nuovo file" inputNewFileName: "Inserisci nome del nuovo file"
inputNewDescription: "Inserisci una nuova descrizione" inputNewDescription: "Inserisci una nuova descrizione"

View file

@ -9,9 +9,9 @@ notifications: "通知"
username: "ユーザー名" username: "ユーザー名"
password: "パスワード" password: "パスワード"
forgotPassword: "パスワードを忘れた" forgotPassword: "パスワードを忘れた"
fetchingAsApObject: "連合に照会中" fetchingAsApObject: "連合に照会中..."
ok: "OK" ok: "OK"
gotIt: "わかった" gotIt: "わかった!"
cancel: "キャンセル" cancel: "キャンセル"
renotedBy: "{user}がRenote" renotedBy: "{user}がRenote"
noNotes: "ノートはありません" noNotes: "ノートはありません"
@ -23,7 +23,7 @@ otherSettings: "その他の設定"
openInWindow: "ウィンドウで開く" openInWindow: "ウィンドウで開く"
profile: "プロフィール" profile: "プロフィール"
timeline: "タイムライン" timeline: "タイムライン"
noAccountDescription: "自己紹介はありません" noAccountDescription: "このユーザーはまだ自己紹介文を書いていません。"
login: "ログイン" login: "ログイン"
loggingIn: "ログイン中" loggingIn: "ログイン中"
logout: "ログアウト" logout: "ログアウト"
@ -123,12 +123,12 @@ addEmoji: "絵文字を追加"
cacheRemoteFiles: "リモートのファイルをキャッシュする" cacheRemoteFiles: "リモートのファイルをキャッシュする"
cacheRemoteFilesDescription: "この設定を無効にすると、リモートファイルをキャッシュせず直リンクするようになります。サーバーのストレージを節約できますが、サムネイルが生成されないので通信量が増加します。" cacheRemoteFilesDescription: "この設定を無効にすると、リモートファイルをキャッシュせず直リンクするようになります。サーバーのストレージを節約できますが、サムネイルが生成されないので通信量が増加します。"
flagAsBot: "Botとして設定" flagAsBot: "Botとして設定"
flagAsBotDescription: "このアカウントがプログラムによって運用される場合は、このフラグをオンにします。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、FoundKeyのシステム上での扱いがBotに合ったものになります。" flagAsBotDescription: "このアカウントがプログラムによって制御される場合は、このフラグをオンにします。オンにすると、別のBotとの終わりのないインタラクションの連続を防ぐためのフラグとして他の開発者に役立ったり、このアカウントをBotとして扱うためにFoundKey内部のシステムを調整します。"
flagAsCat: "Catとして設定" flagAsCat: "Catとして設定"
flagAsCatDescription: "このアカウントが猫であることを示す場合は、このフラグをオンにします。" flagAsCatDescription: "このアカウントが猫であることを示す場合は、このフラグをオンにします。"
flagShowTimelineReplies: "タイムラインにノートへの返信を表示する" flagShowTimelineReplies: "タイムラインにノートへの返信を表示する"
flagShowTimelineRepliesDescription: "オンにすると、タイムラインにユーザーのノート以外にもそのユーザーの他のノートへの返信を表示します。" flagShowTimelineRepliesDescription: "オンにすると、タイムラインにユーザーのノート以外にもそのユーザーの他のノートへの返信を表示します。"
autoAcceptFollowed: "フォロー中ユーザーからのフォロリクを自動承認" autoAcceptFollowed: "フォローしているユーザーからのフォローリクエストを自動承認"
addAccount: "アカウントを追加" addAccount: "アカウントを追加"
loginFailed: "ログインに失敗しました" loginFailed: "ログインに失敗しました"
showOnRemote: "リモートで表示" showOnRemote: "リモートで表示"
@ -165,17 +165,17 @@ clearQueueConfirmText: "未配達の投稿は配送されなくなります。
clearCachedFiles: "キャッシュをクリア" clearCachedFiles: "キャッシュをクリア"
clearCachedFilesConfirm: "キャッシュされたリモートファイルをすべて削除しますか?" clearCachedFilesConfirm: "キャッシュされたリモートファイルをすべて削除しますか?"
blockedInstances: "ブロックしたインスタンス" blockedInstances: "ブロックしたインスタンス"
blockedInstancesDescription: "ブロックしたいインスタンスのホストを改行で区切って設定します。ブロックされたインスタンスは、このインスタンスとやり取りできなくなります。" blockedInstancesDescription: "ブロックしたいインスタンスのホストを改行で区切って設定します。ブロックされたインスタンスは、このインスタンスとやり取りできなくなります。非ASCII文字を含むドメイン名はpunycodeでエンコードされている必要があります。設定したインスタンスのサブドメインもブロックされます。"
muteAndBlock: "ミュートとブロック" muteAndBlock: "ミュートとブロック"
mutedUsers: "ミュートしたユーザー" mutedUsers: "ミュートしたユーザー"
blockedUsers: "ブロックしたユーザー" blockedUsers: "ブロックしたユーザー"
noUsers: "ユーザーはいません" noUsers: "ユーザーはいません"
editProfile: "プロフィールを編集" editProfile: "プロフィールを編集"
noteDeleteConfirm: "このノートを削除しますか?" noteDeleteConfirm: "このノートを削除しますか?"
pinLimitExceeded: "これ以上ピン留めできません" pinLimitExceeded: "これ以上ピン留めできません"
intro: "FoundKeyのインストールが完了しました管理者アカウントを作成しましょう。" intro: "FoundKeyのインストールが完了しました管理者アカウントを作成しましょう。"
done: "完了" done: "完了"
processing: "処理中" processing: "処理中..."
preview: "プレビュー" preview: "プレビュー"
default: "デフォルト" default: "デフォルト"
noCustomEmojis: "絵文字はありません" noCustomEmojis: "絵文字はありません"
@ -251,8 +251,6 @@ createFolder: "フォルダーを作成"
renameFolder: "フォルダー名を変更" renameFolder: "フォルダー名を変更"
deleteFolder: "フォルダーを削除" deleteFolder: "フォルダーを削除"
addFile: "ファイルを追加" addFile: "ファイルを追加"
emptyDrive: "ドライブは空です"
emptyFolder: "フォルダーは空です"
unableToDelete: "削除できません" unableToDelete: "削除できません"
inputNewFileName: "新しいファイル名を入力してください" inputNewFileName: "新しいファイル名を入力してください"
inputNewDescription: "新しいキャプションを入力してください" inputNewDescription: "新しいキャプションを入力してください"
@ -309,7 +307,7 @@ name: "名前"
antennaSource: "受信ソース" antennaSource: "受信ソース"
antennaKeywords: "受信キーワード" antennaKeywords: "受信キーワード"
antennaExcludeKeywords: "除外キーワード" antennaExcludeKeywords: "除外キーワード"
antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります" antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります"
notifyAntenna: "新しいノートを通知する" notifyAntenna: "新しいノートを通知する"
withFileAntenna: "ファイルが添付されたノートのみ" withFileAntenna: "ファイルが添付されたノートのみ"
antennaUsersDescription: "ユーザー名を改行で区切って指定します" antennaUsersDescription: "ユーザー名を改行で区切って指定します"
@ -378,10 +376,10 @@ newMessageExists: "新しいメッセージがあります"
onlyOneFileCanBeAttached: "メッセージに添付できるファイルはひとつです" onlyOneFileCanBeAttached: "メッセージに添付できるファイルはひとつです"
signinRequired: "続行する前に、サインアップまたはサインインが必要です" signinRequired: "続行する前に、サインアップまたはサインインが必要です"
invitationCode: "招待コード" invitationCode: "招待コード"
checking: "確認しています" checking: "確認しています..."
available: "利用できます" available: "利用できます"
unavailable: "利用できません" unavailable: "利用できません"
usernameInvalidFormat: "a~z、A~Z、0~9、_が使えます" usernameInvalidFormat: "a~z、A~Z、0~9、_が使えます"
tooShort: "短すぎます" tooShort: "短すぎます"
tooLong: "長すぎます" tooLong: "長すぎます"
weakPassword: "弱いパスワード" weakPassword: "弱いパスワード"
@ -419,7 +417,7 @@ showFeaturedNotesInTimeline: "タイムラインにおすすめのノートを
objectStorage: "オブジェクトストレージ" objectStorage: "オブジェクトストレージ"
useObjectStorage: "オブジェクトストレージを使用" useObjectStorage: "オブジェクトストレージを使用"
objectStorageBaseUrl: "Base URL" objectStorageBaseUrl: "Base URL"
objectStorageBaseUrlDesc: "参照に使用するURL。CDNやProxyを使用している場合はそのURLS3: 'https://<bucket>.s3.amazonaws.com'、GCS等:\ objectStorageBaseUrlDesc: "参照に使用するURL。CDNやProxyを使用している場合はそのURL。\nS3: 'https://<bucket>.s3.amazonaws.com'、GCS等:\
\ 'https://storage.googleapis.com/<bucket>'。" \ 'https://storage.googleapis.com/<bucket>'。"
objectStorageBucket: "Bucket" objectStorageBucket: "Bucket"
objectStorageBucketDesc: "使用サービスのbucket名を指定してください。" objectStorageBucketDesc: "使用サービスのbucket名を指定してください。"
@ -610,7 +608,7 @@ onlineUsersCount: "{n}人がオンライン"
backgroundColor: "背景" backgroundColor: "背景"
accentColor: "アクセント" accentColor: "アクセント"
textColor: "文字" textColor: "文字"
saveAs: "名前を付けて保存" saveAs: "名前を付けて保存..."
createdAt: "作成日時" createdAt: "作成日時"
updatedAt: "更新日時" updatedAt: "更新日時"
deleteConfirm: "削除しますか?" deleteConfirm: "削除しますか?"
@ -624,7 +622,7 @@ apply: "適用"
receiveAnnouncementFromInstance: "インスタンスからのお知らせを受け取る" receiveAnnouncementFromInstance: "インスタンスからのお知らせを受け取る"
emailNotification: "メール通知" emailNotification: "メール通知"
useReactionPickerForContextMenu: "右クリックでリアクションピッカーを開く" useReactionPickerForContextMenu: "右クリックでリアクションピッカーを開く"
typingUsers: "{users}が入力中" typingUsers: "{users}が入力中..."
jumpToSpecifiedDate: "特定の日付にジャンプ" jumpToSpecifiedDate: "特定の日付にジャンプ"
clear: "クリア" clear: "クリア"
markAllAsRead: "全て既読にする" markAllAsRead: "全て既読にする"
@ -641,7 +639,7 @@ unknown: "不明"
hideOnlineStatus: "オンライン状態を隠す" hideOnlineStatus: "オンライン状態を隠す"
hideOnlineStatusDescription: "オンライン状態を隠すと、検索などの一部機能において利便性が低下することがあります。" hideOnlineStatusDescription: "オンライン状態を隠すと、検索などの一部機能において利便性が低下することがあります。"
federateBlocks: "ブロックを連合に送信" federateBlocks: "ブロックを連合に送信"
federateBlocksDescription: "オフにするとBlockのActivityは連合に送信しません" federateBlocksDescription: "オフにすると、BlockのActivityは連合に送信されません。"
online: "オンライン" online: "オンライン"
active: "アクティブ" active: "アクティブ"
offline: "オフライン" offline: "オフライン"
@ -677,7 +675,7 @@ misskeyUpdated: "FoundKeyが更新されました"
whatIsNew: "更新情報を見る" whatIsNew: "更新情報を見る"
translate: "翻訳" translate: "翻訳"
translatedFrom: "{x}から翻訳" translatedFrom: "{x}から翻訳"
accountDeletionInProgress: "アカウントの削除が進行中です" accountDeletionInProgress: "アカウントの削除が進行中です"
usernameInfo: "サーバー上であなたのアカウントを一意に識別するための名前。アルファベット(a~z, A~Z)、数字(0~9)、およびアンダーバー(_)が使用できます。ユーザー名は後から変更することは出来ません。" usernameInfo: "サーバー上であなたのアカウントを一意に識別するための名前。アルファベット(a~z, A~Z)、数字(0~9)、およびアンダーバー(_)が使用できます。ユーザー名は後から変更することは出来ません。"
keepCw: "CWを維持する" keepCw: "CWを維持する"
pubSub: "Pub/Subのアカウント" pubSub: "Pub/Subのアカウント"
@ -701,7 +699,7 @@ threadMuteNotificationsDesc: "このスレッドから表示する通知を選
ffVisibility: "つながりの公開範囲" ffVisibility: "つながりの公開範囲"
ffVisibilityDescription: "自分のフォロー/フォロワー情報の公開範囲を設定できます。" ffVisibilityDescription: "自分のフォロー/フォロワー情報の公開範囲を設定できます。"
continueThread: "さらにスレッドを見る" continueThread: "さらにスレッドを見る"
deleteAccountConfirm: "アカウントが削除されます。よろしいですか?" deleteAccountConfirm: "アカウント {handle} 不可逆的に削除されます。よろしいですか?"
incorrectPassword: "パスワードが間違っています。" incorrectPassword: "パスワードが間違っています。"
voteConfirm: "「{choice}」に投票しますか?" voteConfirm: "「{choice}」に投票しますか?"
hide: "隠す" hide: "隠す"
@ -789,7 +787,7 @@ _registry:
createKey: "キーを作成" createKey: "キーを作成"
_aboutMisskey: _aboutMisskey:
about: "FoundKeyはsyuiloによって2014年から開発されている、オープンソースのソフトウェアです。" about: "FoundKeyは2022年7月から開発されている、Misskeyのフォークです。"
allContributors: "全てのコントリビューター" allContributors: "全てのコントリビューター"
source: "ソースコード" source: "ソースコード"
_nsfw: _nsfw:
@ -945,7 +943,7 @@ _time:
_tutorial: _tutorial:
title: "FoundKeyの使い方" title: "FoundKeyの使い方"
step1_1: "ようこそ" step1_1: "ようこそ!"
step1_2: "この画面は「タイムライン」と呼ばれ、あなたや、あなたが「フォロー」する人の「ノート」が時系列で表示されます。" step1_2: "この画面は「タイムライン」と呼ばれ、あなたや、あなたが「フォロー」する人の「ノート」が時系列で表示されます。"
step1_3: "あなたはまだ何もノートを投稿しておらず、誰もフォローしていないので、タイムラインには何も表示されていないはずです。" step1_3: "あなたはまだ何もノートを投稿しておらず、誰もフォローしていないので、タイムラインには何も表示されていないはずです。"
step2_1: "ノートを作成したり誰かをフォローしたりする前に、まずあなたのプロフィールを完成させましょう。" step2_1: "ノートを作成したり誰かをフォローしたりする前に、まずあなたのプロフィールを完成させましょう。"
@ -953,11 +951,11 @@ _tutorial:
step3_1: "プロフィール設定はうまくできましたか?" step3_1: "プロフィール設定はうまくできましたか?"
step3_2: "では試しに、何かノートを投稿してみてください。画面上にある鉛筆マークのボタンを押すとフォームが開きます。" step3_2: "では試しに、何かノートを投稿してみてください。画面上にある鉛筆マークのボタンを押すとフォームが開きます。"
step3_3: "内容を書いたら、フォーム右上のボタンを押すと投稿できます。" step3_3: "内容を書いたら、フォーム右上のボタンを押すと投稿できます。"
step3_4: "内容が思いつかない「FoundKey始めました」というのはいかがでしょう" step3_4: "内容が思いつかない「FoundKey始めました」というのはいかがでしょう!"
step4_1: "投稿できましたか?" step4_1: "投稿できましたか?"
step4_2: "あなたのノートがタイムラインに表示されていれば成功です。" step4_2: "あなたのノートがタイムラインに表示されていれば成功です。"
step5_1: "次は、他の人をフォローしてタイムラインを賑やかにしたいところです。" step5_1: "次は、他の人をフォローしてタイムラインを賑やかにしたいところです。"
step5_2: "{featured}で人気のノートが見れるので、その中から気になった人を選んでフォローしたり、{explore}で人気のユーザーを探すこともできます" step5_2: "{featured}で人気のノートが見れるので、その中から気になった人を選んでフォローしたり、{explore}で人気のユーザーを探すこともできます!"
step5_3: "ユーザーをフォローするには、ユーザーのアイコンをクリックしてユーザーページを表示し、「フォロー」ボタンを押します。" step5_3: "ユーザーをフォローするには、ユーザーのアイコンをクリックしてユーザーページを表示し、「フォロー」ボタンを押します。"
step5_4: "ユーザーによっては、フォローが承認されるまで時間がかかる場合があります。" step5_4: "ユーザーによっては、フォローが承認されるまで時間がかかる場合があります。"
step6_1: "タイムラインに他のユーザーのノートが表示されていれば成功です。" step6_1: "タイムラインに他のユーザーのノートが表示されていれば成功です。"
@ -979,33 +977,34 @@ _2fa:
securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキーもしくは端末の指紋認証やPINを使用してログインするように設定できます。" securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキーもしくは端末の指紋認証やPINを使用してログインするように設定できます。"
_permissions: _permissions:
"read:account": "アカウントの情報をる" "read:account": "アカウントの情報を読み取る"
"write:account": "アカウントの情報を変更する" "write:account": "アカウントの情報を変更する"
"read:blocks": "ブロックを見る" "read:blocks": "どのユーザーをブロックしているかを読み取る"
"write:blocks": "ブロックを操作する" "write:blocks": "ユーザーをブロック・ブロック解除する"
"read:drive": "ドライブを見る" "read:drive": "ドライブ内のファイルとフォルダをリスト化する"
"write:drive": "ドライブ作する" "write:drive": "ドライブ内でファイルを作成・変更・削除する"
"read:favorites": "お気に入りを見る" "read:favorites": "お気に入りにしたノートをリスト化する"
"write:favorites": "お気に入りを操作する" "write:favorites": "ノートをお気に入りまたはお気に入り解除する"
"read:following": "フォローの情報を見る" "read:following": "自分がフォローしているユーザーおよび自分をフォローしているユーザーをリスト化する"
"write:following": "フォロー・フォロー解除する" "write:following": "ユーザーをフォロー・フォロー解除する"
"read:messaging": "チャットを見る" "read:messaging": "チャットの内容と履歴を見る"
"write:messaging": "チャット作する" "write:messaging": "チャットでメッセージを作成・削除する"
"read:mutes": "ミュートを見る" "read:mutes": "ミュートまたはRenoteをミュートにしたユーザーをリスト化する"
"write:mutes": "ミュートを操作する" "write:mutes": "ユーザーまたはユーザーのRenoteをミュート・ミュート解除する"
"write:notes": "ノートを作成・削除する" "write:notes": "ノートを作成・削除する"
"read:notifications": "通知をる" "read:notifications": "通知を読み取る"
"write:notifications": "通知作する" "write:notifications": "通知の既読化およびカスタム通知を作する"
"write:reactions": "リアクションを作する" "write:reactions": "リアクションを成・削除する"
"write:votes": "投票する" "write:votes": "投票する"
"read:pages": "ページを見る" "read:pages": "ページのリスト化・読み取りをする"
"write:pages": "ページを操作する" "write:pages": "ページを作成・変更・削除する"
"read:page-likes": "ページのいいねを見る" "read:page-likes": "ページのいいねのリスト化・読み取りをする"
"write:page-likes": "ページのいいねを操作する" "write:page-likes": "ページをいいね・いいね解除する"
"read:user-groups": "ユーザーグループを見る" "read:user-groups": "参加・所有している、および招待されているグループのリスト化・読み取りをする"
"write:user-groups": "ユーザーグループを操作する" "write:user-groups": "グループを作成・変更・削除・譲渡・参加、または脱退する。グループから他のユーザーを招待・凍結する。グループへの招待を承認・拒否する。"
"read:channels": "チャンネルを見る" "read:channels": "フォローおよび参加しているチャンネルのリスト化・読み取りをする"
"write:channels": "チャンネルを操作する" "write:channels": "チャンネルを作成・変更・フォロー・フォロー解除する"
"read:reactions": リアクションを見る
_auth: _auth:
shareAccess: "「{name}」がアカウントにアクセスすることを許可しますか?" shareAccess: "「{name}」がアカウントにアクセスすることを許可しますか?"
shareAccessAsk: "アカウントへのアクセスを許可しますか?" shareAccessAsk: "アカウントへのアクセスを許可しますか?"
@ -1051,6 +1050,7 @@ _widgets:
aiscript: "AiScriptコンソール" aiscript: "AiScriptコンソール"
aichan: "藍" aichan: "藍"
rssMarquee: RSSティッカー
_cw: _cw:
hide: "隠す" hide: "隠す"
show: "もっと見る" show: "もっと見る"
@ -1064,8 +1064,8 @@ _poll:
canMultipleVote: "複数回答可" canMultipleVote: "複数回答可"
expiration: "期限" expiration: "期限"
infinite: "無期限" infinite: "無期限"
at: "日時指定" at: "日時指定..."
after: "経過指定" after: "経過指定..."
deadlineDate: "期日" deadlineDate: "期日"
deadlineTime: "時間" deadlineTime: "時間"
duration: "期間" duration: "期間"
@ -1101,7 +1101,7 @@ _postForm:
b: "何かありましたか?" b: "何かありましたか?"
c: "何をお考えですか?" c: "何をお考えですか?"
d: "言いたいことは?" d: "言いたいことは?"
e: "ここに書いてください" e: "ここに書いてください..."
f: "あなたが書くのを待っています..." f: "あなたが書くのを待っています..."
_profile: _profile:
@ -1225,6 +1225,7 @@ _notification:
groupInvited: "グループに招待された" groupInvited: "グループに招待された"
app: "連携アプリからの通知" app: "連携アプリからの通知"
move: 自分以外のアカウントの引っ越し
_actions: _actions:
followBack: "フォローバック" followBack: "フォローバック"
reply: "返信" reply: "返信"
@ -1253,3 +1254,48 @@ _deck:
list: "リスト" list: "リスト"
mentions: "あなた宛て" mentions: "あなた宛て"
direct: "ダイレクト" direct: "ダイレクト"
translationSettings: 翻訳設定
signinHistoryExpires: プライバシー規則に準拠するため、過去のログイン試行に関するデータは60日後に自動的に削除されます。
deleteAllFiles: すべてのファイルを削除
cannotAttachFileWhenAccountSwitched: 別のアカウントに切り替えている間はファイルを添付できません。
translationService: 翻訳サービス
cannotSwitchAccountWhenFileAttached: ファイルを添付したままアカウントを切り替えることはできません。
externalCssSnippets: インスピレーションのためのCSSスニペット群 (FoundKeyによって管理されていません)
botFollowRequiresApproval: Botとして設定されたアカウントからのフォロー申請は承認を必要にする
documentation: ドキュメンテーション
unlimited: 無制限
exportAll: すべてエクスポート
oauthErrorGoBack: サードパーティーアプリの認証中にエラーが発生しました。戻ってもう一度やり直してみてください。
selectMode: 複数選択
renoteMute: Renoteをミュート
renoteUnmute: Renoteのミュートを解除
stopActivityDeliveryDescription: ローカルでのアクティビティはこのインスタンスに対して送信されません。アクティビティの受信はこれまで通り機能します。
unrenoteAll: すべてのRenoteを取り消す
unrenoteAllConfirm: このートのRenoteをすべて取り消します。よろしいですか
addTag: タグを追加
removeTag: タグを削除
appAuthorization: アプリの承認
noPermissionsRequested: (必要な権限はありません。)
setCategory: カテゴリを設定
selectAll: 全選択
setTag: タグを設定
blockThisInstanceDescription: ローカルでのアクティビティはこのインスタンスに対して送信されません。このインスタンスからのアクティビティは破棄されます。
maxCustomEmojiPicker: ピッカー内で提案するカスタム絵文字の最大数
maxUnicodeEmojiPicker: ピッカー内で提案するUnicode絵文字の最大数
exportSelected: 選択をエクスポート
_translationService:
_libreTranslate:
authKey: LibreTranslate認証キー (任意)
endpoint: LibreTranslate API Endpoint
_deepl:
authKey: DeepL認証キー
_remoteInteract:
title: 申し訳ありませんが、残念ながら実行できません。
urlInstructions: 以下のURLをコピーするとよいでしょう。あなたのインスタンスの検索フィールドに貼り付けることで、正しい場所に誘導されるでしょう。
description: 今すぐにこのアクションを実行することはできません。あなたのインスタンス上で、またはログインして行う必要があるかもしれません。
movedTo: このユーザーは {handle} に引っ越しました。
uploadFailedDescription: ファイルをアップロードできませんでした。
uploadFailedSize: ファイルサイズが大きすぎるためアップロードできません。
uploadFailed: アップロード失敗
showAttachedNotes: 添付ノートを表示
attachedToNotes: このファイルが添付されたノート

View file

@ -243,8 +243,6 @@ createFolder: "フォルダー作る"
renameFolder: "フォルダー名を変える" renameFolder: "フォルダー名を変える"
deleteFolder: "フォルダーを消してまう" deleteFolder: "フォルダーを消してまう"
addFile: "ファイルを追加" addFile: "ファイルを追加"
emptyDrive: "ドライブにはなんも残っとらん"
emptyFolder: "ふぉろだーにはなんも残っとらん"
unableToDelete: "消そうおもってんけどな、あかんかったわ" unableToDelete: "消そうおもってんけどな、あかんかったわ"
inputNewFileName: "今度のファイル名は何にするん?" inputNewFileName: "今度のファイル名は何にするん?"
inputNewDescription: "新しいキャプションを入力しましょ" inputNewDescription: "新しいキャプションを入力しましょ"

View file

@ -76,9 +76,9 @@ followsYou: "Volgt jou"
createList: "Creëer lijst" createList: "Creëer lijst"
manageLists: "Beheren lijsten" manageLists: "Beheren lijsten"
error: "Fout" error: "Fout"
somethingHappened: "Er is iets misgegaan." somethingHappened: "Er is iets misgegaan"
retry: "Probeer opnieuw" retry: "Probeer opnieuw"
pageLoadError: "Pagina laden mislukt" pageLoadError: "Pagina laden mislukt."
pageLoadErrorDescription: "Dit wordt normaal gesproken veroorzaakt door netwerkfouten\ pageLoadErrorDescription: "Dit wordt normaal gesproken veroorzaakt door netwerkfouten\
\ of door de cache van de browser. Probeer de cache te wissen en probeer het na\ \ of door de cache van de browser. Probeer de cache te wissen en probeer het na\
\ een tijdje wachten opnieuw." \ een tijdje wachten opnieuw."
@ -217,7 +217,7 @@ resetAreYouSure: "Resetten?"
saved: "Opgeslagen" saved: "Opgeslagen"
messaging: "Chat" messaging: "Chat"
upload: "Uploaden" upload: "Uploaden"
keepOriginalUploading: "Origineel beeld behouden." keepOriginalUploading: "Origineel beeld behouden"
keepOriginalUploadingDescription: "Bewaar de originele versie bij het uploaden van\ keepOriginalUploadingDescription: "Bewaar de originele versie bij het uploaden van\
\ afbeeldingen. Indien uitgeschakeld, wordt bij het uploaden een alternatieve versie\ \ afbeeldingen. Indien uitgeschakeld, wordt bij het uploaden een alternatieve versie\
\ voor webpublicatie genereert." \ voor webpublicatie genereert."
@ -263,8 +263,6 @@ createFolder: "Map aanmaken"
renameFolder: "Map hernoemen" renameFolder: "Map hernoemen"
deleteFolder: "Map verwijderen" deleteFolder: "Map verwijderen"
addFile: "Bestand toevoegen" addFile: "Bestand toevoegen"
emptyDrive: "Jouw Drive is leeg."
emptyFolder: "Deze map is leeg"
unableToDelete: "Kan niet worden verwijderd" unableToDelete: "Kan niet worden verwijderd"
inputNewFileName: "Voer een nieuwe naam in" inputNewFileName: "Voer een nieuwe naam in"
copyUrl: "URL kopiëren" copyUrl: "URL kopiëren"
@ -358,3 +356,12 @@ selectWidget: Kies een widget
editWidgets: Widgets wijzigen editWidgets: Widgets wijzigen
editWidgetsExit: Klaar editWidgetsExit: Klaar
_services: {} _services: {}
botFollowRequiresApproval: Volgverzoeken van als robot gemarkeerde gebruikers vereisen
goedkeuring
unrenoteAll: Alle renotes terugnemen
unrenoteAllConfirm: Weet je zeker dat je alle renotes voor deze note terug wil nemen?
exportAll: Alles exporteren
exportSelected: Selectie exporteren
uploadFailed: Uploaden mislukt
uploadFailedDescription: Het bestand kon niet worden geupload.
uploadFailedSize: Het bestand is te groot om te uploaden.

View file

@ -1,9 +1,10 @@
_lang_: "Polski" _lang_: "Polski"
headlineMisskey: "Sieć połączona wpisami" headlineMisskey: "Sieć połączona wpisami"
introMisskey: "Witaj! FoundKey jest otwartoźródłowym serwisem mikroblogowym.\nTwórz\ introMisskey: "Witaj! Foundkey jest otwartoźródłowym, zdecentralizowanym serwisem\
\ \"wpisy\", aby dzielić się tym, co się dzieje i opowiadać wszystkim o sobie. \U0001F4E1\ \ mikroblogowym.\nTwórz \"wpisy\", aby dzielić się tym, co się dzieje i opowiadać\
\nMożesz również użyć \"reakcji\", aby szybko wyrazić swoje uczucia dotyczące wpisów\ \ wszystkim o sobie. \U0001F4E1\nMożesz również użyć \"reakcji\", aby szybko wyrazić\
\ innych użytkowników. \U0001F44D\nOdkrywaj nowy świat! \U0001F680" \ swoje uczucia dotyczące wpisów innych użytkowników. \U0001F44D\nOdkryjmy nowy\
\ świat! \U0001F680"
monthAndDay: "{month}-{day}" monthAndDay: "{month}-{day}"
search: "Szukaj" search: "Szukaj"
notifications: "Powiadomienia" notifications: "Powiadomienia"
@ -24,7 +25,7 @@ otherSettings: "Pozostałe ustawienia"
openInWindow: "Otwórz w oknie" openInWindow: "Otwórz w oknie"
profile: "Profil" profile: "Profil"
timeline: "Oś czasu" timeline: "Oś czasu"
noAccountDescription: "Ten użytkownik nie napisał jeszcze swojej biografii." noAccountDescription: "Ten użytkownik nie napisał jeszcze swojego opisu."
login: "Zaloguj się" login: "Zaloguj się"
loggingIn: "Logowanie" loggingIn: "Logowanie"
logout: "Wyloguj się" logout: "Wyloguj się"
@ -60,12 +61,12 @@ import: "Importuj"
export: "Eksportuj" export: "Eksportuj"
files: "Pliki" files: "Pliki"
download: "Pobierz" download: "Pobierz"
driveFileDeleteConfirm: "Czy chcesz usunąć plik \"{name}\"? Zniknie również notatka,\ driveFileDeleteConfirm: "Czy chcesz usunąć plik \"{name}\"? Znikną również wpisy,\
\ do której dołączony jest ten plik." \ do których dołączony jest ten plik."
unfollowConfirm: "Czy na pewno chcesz przestać obserwować {name}?" unfollowConfirm: "Czy na pewno chcesz przestać obserwować {name}?"
exportRequested: "Zażądałeś eksportu. Może to zająć trochę czasu. Po zakończeniu eksportu\ exportRequested: "Zażądano eksportu. Może to zająć chwilę. Po zakończeniu eksportu\
\ zostanie on dodany do Twoich \"dysków\"." \ zostanie on dodany do Twojego Dysku."
importRequested: "Zażądano importu. Może to zająć chwilę." importRequested: "Zażądano importu. Może to zająć chwilę."
lists: "Listy" lists: "Listy"
note: "Utwórz wpis" note: "Utwórz wpis"
notes: "Wpisy" notes: "Wpisy"
@ -77,7 +78,7 @@ manageLists: "Zarządzaj listami"
error: "Błąd" error: "Błąd"
somethingHappened: "Coś poszło nie tak" somethingHappened: "Coś poszło nie tak"
retry: "Spróbuj ponownie" retry: "Spróbuj ponownie"
pageLoadError: "Nie udało się załadować strony" pageLoadError: "Nie udało się załadować strony."
pageLoadErrorDescription: "Zwykle jest to spowodowane problemem z siecią lub cache\ pageLoadErrorDescription: "Zwykle jest to spowodowane problemem z siecią lub cache\
\ przeglądarki. Spróbuj wyczyścić cache i sprawdź jeszcze raz za chwilę." \ przeglądarki. Spróbuj wyczyścić cache i sprawdź jeszcze raz za chwilę."
serverIsDead: "Serwer nie odpowiada. Zaczekaj chwilę i spróbuj ponownie." serverIsDead: "Serwer nie odpowiada. Zaczekaj chwilę i spróbuj ponownie."
@ -101,7 +102,7 @@ sensitive: "NSFW"
add: "Dodaj" add: "Dodaj"
reaction: "Reakcja" reaction: "Reakcja"
reactionSettingDescription2: "Przeciągnij aby zmienić kolejność, naciśnij aby usunąć,\ reactionSettingDescription2: "Przeciągnij aby zmienić kolejność, naciśnij aby usunąć,\
\ naciśnij „+” aby dodać" \ naciśnij „+” aby dodać."
attachCancel: "Usuń załącznik" attachCancel: "Usuń załącznik"
markAsSensitive: "Oznacz jako NSFW" markAsSensitive: "Oznacz jako NSFW"
unmarkAsSensitive: "Cofnij NSFW" unmarkAsSensitive: "Cofnij NSFW"
@ -117,7 +118,7 @@ unblockConfirm: "Czy na pewno chcesz odblokować to konto?"
suspendConfirm: "Czy na pewno chcesz zawiesić to konto?" suspendConfirm: "Czy na pewno chcesz zawiesić to konto?"
unsuspendConfirm: "Czy na pewno chcesz cofnąć zawieszenie tego konta?" unsuspendConfirm: "Czy na pewno chcesz cofnąć zawieszenie tego konta?"
selectList: "Wybierz listę" selectList: "Wybierz listę"
selectAntenna: "Wybierz Antennę" selectAntenna: "Wybierz antenę"
selectWidget: "Wybierz widżet" selectWidget: "Wybierz widżet"
editWidgets: "Edytuj widżet" editWidgets: "Edytuj widżet"
editWidgetsExit: "Gotowe" editWidgetsExit: "Gotowe"
@ -133,7 +134,7 @@ flagAsBot: "To konto jest botem"
flagAsBotDescription: "Jeżeli ten kanał jest kontrolowany przez jakiś program, ustaw\ flagAsBotDescription: "Jeżeli ten kanał jest kontrolowany przez jakiś program, ustaw\
\ tę opcję. Jeżeli włączona, będzie działać jako flaga informująca innych programistów,\ \ tę opcję. Jeżeli włączona, będzie działać jako flaga informująca innych programistów,\
\ aby zapobiegać nieskończonej interakcji z różnymi botami i dostosowywać wewnętrzne\ \ aby zapobiegać nieskończonej interakcji z różnymi botami i dostosowywać wewnętrzne\
\ systemy FoundKey, traktując konto jako bota." \ systemy Foundkey, traktując konto jako bota."
flagAsCat: "To konto jest kotem" flagAsCat: "To konto jest kotem"
flagAsCatDescription: "Przełącz tę opcję, aby konto było oznaczone jako kot." flagAsCatDescription: "Przełącz tę opcję, aby konto było oznaczone jako kot."
autoAcceptFollowed: "Automatycznie przyjmuj prośby o możliwość obserwacji od użytkowników,\ autoAcceptFollowed: "Automatycznie przyjmuj prośby o możliwość obserwacji od użytkowników,\
@ -176,17 +177,19 @@ clearCachedFilesConfirm: "Czy na pewno chcesz usunąć wszystkie zdalne pliki z
\ podręcznej?" \ podręcznej?"
blockedInstances: "Zablokowane instancje" blockedInstances: "Zablokowane instancje"
blockedInstancesDescription: "Wypisz nazwy hostów instancji, które powinny zostać\ blockedInstancesDescription: "Wypisz nazwy hostów instancji, które powinny zostać\
\ zablokowane. Wypisane instancje nie będą mogły dłużej komunikować się z tą instancją." \ zablokowane. Wypisane instancje nie będą mogły dłużej komunikować się z tą instancją.\
muteAndBlock: "Wycisz / Zablokuj" \ Nazwy domen non-ASCII muszą być zakodowane w punycode. Poddomeny wypisanych instancji\
\ również będą blokowane."
muteAndBlock: "Wyciszenia i blokady"
mutedUsers: "Wyciszeni użytkownicy" mutedUsers: "Wyciszeni użytkownicy"
blockedUsers: "Zablokowani użytkownicy" blockedUsers: "Zablokowani użytkownicy"
noUsers: "Brak użytkowników" noUsers: "Brak użytkowników"
editProfile: "Edytuj profil" editProfile: "Edytuj profil"
noteDeleteConfirm: "Czy na pewno chcesz usunąć ten wpis?" noteDeleteConfirm: "Czy na pewno chcesz usunąć ten wpis?"
pinLimitExceeded: "Nie możesz przypiąć więcej wpisów." pinLimitExceeded: "Nie możesz przypiąć więcej wpisów."
intro: "Zakończono instalację FoundKey! Utwórz konto administratora." intro: "Zakończono instalację Foundkey! Utwórz konto administratora."
done: "Gotowe" done: "Gotowe"
processing: "Przetwarzanie" processing: "Przetwarzanie..."
preview: "Podgląd" preview: "Podgląd"
default: "Domyślne" default: "Domyślne"
noCustomEmojis: "Brak emoji" noCustomEmojis: "Brak emoji"
@ -261,11 +264,9 @@ createFolder: "Utwórz katalog"
renameFolder: "Zmień nazwę katalogu" renameFolder: "Zmień nazwę katalogu"
deleteFolder: "Usuń ten katalog" deleteFolder: "Usuń ten katalog"
addFile: "Dodaj plik" addFile: "Dodaj plik"
emptyDrive: "Dysk jest pusty"
emptyFolder: "Ten katalog jest pusty"
unableToDelete: "Nie można usunąć" unableToDelete: "Nie można usunąć"
inputNewFileName: "Wprowadź nową nazwę pliku" inputNewFileName: "Wprowadź nową nazwę pliku"
inputNewDescription: "Proszę wpisać nowy napis" inputNewDescription: "Wpisz nowy opis"
inputNewFolderName: "Wprowadź nową nazwę katalogu" inputNewFolderName: "Wprowadź nową nazwę katalogu"
circularReferenceFolder: "Katalog docelowy jest podkatalogiem katalogu, który chcesz\ circularReferenceFolder: "Katalog docelowy jest podkatalogiem katalogu, który chcesz\
\ przenieść." \ przenieść."
@ -276,7 +277,7 @@ avatar: "Awatar"
banner: "Baner" banner: "Baner"
nsfw: "NSFW" nsfw: "NSFW"
whenServerDisconnected: "Po utracie połączenia z serwerem" whenServerDisconnected: "Po utracie połączenia z serwerem"
disconnectedFromServer: "Utracono połączenie z serwerem." disconnectedFromServer: "Utracono połączenie z serwerem"
reload: "Odśwież" reload: "Odśwież"
doNothing: "Ignoruj" doNothing: "Ignoruj"
reloadConfirm: "Czy chcesz odświeżyć oś czasu?" reloadConfirm: "Czy chcesz odświeżyć oś czasu?"
@ -317,13 +318,13 @@ hcaptchaSecretKey: "Tajny klucz"
recaptchaSiteKey: "Klucz strony" recaptchaSiteKey: "Klucz strony"
recaptchaSecretKey: "Tajny klucz" recaptchaSecretKey: "Tajny klucz"
antennas: "Anteny" antennas: "Anteny"
manageAntennas: "Zarządzaj Antenami" manageAntennas: "Zarządzaj antenami"
name: "Nazwa" name: "Nazwa"
antennaSource: "Źródło Anteny" antennaSource: "Źródło anteny"
antennaKeywords: "Słowa kluczowe do obserwacji" antennaKeywords: "Słowa kluczowe do obserwacji"
antennaExcludeKeywords: "Wykluczone słowa kluczowe" antennaExcludeKeywords: "Wykluczone słowa kluczowe"
antennaKeywordsDescription: "Oddziel spacjami dla warunku AND, albo wymuś koniec linii\ antennaKeywordsDescription: "Oddziel spacjami dla warunku AND, albo wymuś koniec linii\
\ dla warunku OR" \ dla warunku OR."
notifyAntenna: "Powiadamiaj o nowych wpisach" notifyAntenna: "Powiadamiaj o nowych wpisach"
withFileAntenna: "Filtruj tylko wpisy z załączonym plikiem" withFileAntenna: "Filtruj tylko wpisy z załączonym plikiem"
antennaUsersDescription: "Wypisz po jednej nazwie użytkownika w linii" antennaUsersDescription: "Wypisz po jednej nazwie użytkownika w linii"
@ -342,7 +343,7 @@ recentlyRegisteredUsers: "Ostatnio zarejestrowani użytkownicy"
recentlyDiscoveredUsers: "Ostatnio odkryci użytkownicy" recentlyDiscoveredUsers: "Ostatnio odkryci użytkownicy"
popularTags: "Tagi na czasie" popularTags: "Tagi na czasie"
userList: "Listy" userList: "Listy"
aboutMisskey: "O FoundKey" aboutMisskey: "O Foundkey"
administrator: "Admin" administrator: "Admin"
token: "Token" token: "Token"
twoStepAuthentication: "Uwierzytelnianie dwuskładnikowe" twoStepAuthentication: "Uwierzytelnianie dwuskładnikowe"
@ -392,10 +393,10 @@ newMessageExists: "Masz nową wiadomość"
onlyOneFileCanBeAttached: "Możesz załączyć tylko jeden plik do wiadomości" onlyOneFileCanBeAttached: "Możesz załączyć tylko jeden plik do wiadomości"
signinRequired: "Proszę się zalogować" signinRequired: "Proszę się zalogować"
invitationCode: "Kod zaproszenia" invitationCode: "Kod zaproszenia"
checking: "Sprawdzam" checking: "Sprawdzam..."
available: "Dostępna" available: "Dostępna"
unavailable: "Niedostępna" unavailable: "Niedostępna"
usernameInvalidFormat: "może zawierać litery, cyfry i podkreślniki." usernameInvalidFormat: "Nazwa użytkownika może zawierać litery, cyfry i podkreślniki."
tooShort: "Zbyt krótka" tooShort: "Zbyt krótka"
tooLong: "Zbyt długa" tooLong: "Zbyt długa"
weakPassword: "Słabe hasło" weakPassword: "Słabe hasło"
@ -409,7 +410,7 @@ tapSecurityKey: "Wybierz swój klucz bezpieczeństwa"
or: "Lub" or: "Lub"
language: "Język" language: "Język"
uiLanguage: "Język wyświetlania UI" uiLanguage: "Język wyświetlania UI"
groupInvited: "Zaproszony(-a) do grupy" groupInvited: "Został*ś zaproszon* do grupy"
useOsNativeEmojis: "Używaj natywnych Emoji systemu" useOsNativeEmojis: "Używaj natywnych Emoji systemu"
youHaveNoGroups: "Nie masz żadnych grup" youHaveNoGroups: "Nie masz żadnych grup"
joinOrCreateGroup: "Uzyskaj zaproszenie do dołączenia do grupy lub utwórz własną grupę." joinOrCreateGroup: "Uzyskaj zaproszenie do dołączenia do grupy lub utwórz własną grupę."
@ -460,7 +461,7 @@ popout: "Popout"
volume: "Głośność" volume: "Głośność"
masterVolume: "Głośność główna" masterVolume: "Głośność główna"
details: "Szczegóły" details: "Szczegóły"
unableToProcess: "Nie udało się dokończyć działania." unableToProcess: "Nie udało się dokończyć działania"
recentUsed: "Ostatnio używane" recentUsed: "Ostatnio używane"
install: "Zainstaluj" install: "Zainstaluj"
uninstall: "Odinstaluj" uninstall: "Odinstaluj"
@ -474,7 +475,7 @@ ascendingOrder: "Rosnąco"
descendingOrder: "Malejąco" descendingOrder: "Malejąco"
scratchpad: "Brudnopis" scratchpad: "Brudnopis"
scratchpadDescription: "Brudnopis zawiera eksperymentalne środowisko dla AiScript.\ scratchpadDescription: "Brudnopis zawiera eksperymentalne środowisko dla AiScript.\
\ Możesz pisać, wykonywać i sprawdzać wyniki w interakcji z FoundKey." \ Możesz pisać, wykonywać i sprawdzać wyniki w interakcji z Foundkey."
output: "Wyjście" output: "Wyjście"
updateRemoteUser: "Aktualizuj zdalne dane o użytkowniku" updateRemoteUser: "Aktualizuj zdalne dane o użytkowniku"
deleteAllFilesConfirm: "Czy na pewno chcesz usunąć wszystkie pliki?" deleteAllFilesConfirm: "Czy na pewno chcesz usunąć wszystkie pliki?"
@ -496,7 +497,7 @@ enablePlayer: "Otwórz odtwarzacz wideo"
disablePlayer: "Zamknij odtwarzacz wideo" disablePlayer: "Zamknij odtwarzacz wideo"
themeEditor: "Edytor motywu" themeEditor: "Edytor motywu"
description: "Opis" description: "Opis"
describeFile: "dodaj podpis" describeFile: "Dodaj opis"
author: "Autor" author: "Autor"
leaveConfirm: "Są niezapisane zmiany. Czy chcesz je odrzucić?" leaveConfirm: "Są niezapisane zmiany. Czy chcesz je odrzucić?"
manage: "Zarządzanie" manage: "Zarządzanie"
@ -531,10 +532,10 @@ smtpUser: "Nazwa użytkownika"
smtpPass: "Hasło" smtpPass: "Hasło"
emptyToDisableSmtpAuth: "Pozostaw adres e-mail i hasło puste, aby wyłączyć weryfikację\ emptyToDisableSmtpAuth: "Pozostaw adres e-mail i hasło puste, aby wyłączyć weryfikację\
\ SMTP" \ SMTP"
smtpSecureInfo: "Wyłącz, jeżeli używasz STARTTLS" smtpSecureInfo: "Wyłącz, jeżeli używasz STARTTLS."
testEmail: "Przetestuj dostarczanie wiadomości e-mail" testEmail: "Przetestuj dostarczanie wiadomości e-mail"
wordMute: "Wyciszenie słowa" wordMute: "Wyciszenie słowa"
userSaysSomething: "{name} powiedział(-a) coś" userSaysSomething: "{name} powiedział* coś"
makeActive: "Aktywuj" makeActive: "Aktywuj"
display: "Wyświetlanie" display: "Wyświetlanie"
copy: "Kopiuj" copy: "Kopiuj"
@ -543,7 +544,7 @@ database: "Baza danych"
channel: "Kanały" channel: "Kanały"
create: "Utwórz" create: "Utwórz"
notificationSetting: "Ustawienia powiadomień" notificationSetting: "Ustawienia powiadomień"
notificationSettingDesc: "Wybierz rodzaj powiadomień do wyświetlania" notificationSettingDesc: "Wybierz rodzaj powiadomień do wyświetlania."
useGlobalSetting: "Użyj globalnych ustawień" useGlobalSetting: "Użyj globalnych ustawień"
useGlobalSettingDesc: "Jeżeli włączone, zostaną wykorzystane ustawienia powiadomień\ useGlobalSettingDesc: "Jeżeli włączone, zostaną wykorzystane ustawienia powiadomień\
\ Twojego konta. Jeżeli wyłączone, mogą zostać wykonane oddzielne konfiguracje." \ Twojego konta. Jeżeli wyłączone, mogą zostać wykonane oddzielne konfiguracje."
@ -559,8 +560,8 @@ reportAbuse: "Zgłoś"
reportAbuseOf: "Zgłoś {name}" reportAbuseOf: "Zgłoś {name}"
fillAbuseReportDescription: "Wypełnij szczegóły zgłoszenia." fillAbuseReportDescription: "Wypełnij szczegóły zgłoszenia."
abuseReported: "Twoje zgłoszenie zostało wysłane. Dziękujemy." abuseReported: "Twoje zgłoszenie zostało wysłane. Dziękujemy."
reporteeOrigin: "Pochodzenie zgłoszonego" reporteeOrigin: "Pochodzenie osoby zgłoszonej"
reporterOrigin: "Pochodzenie zgłaszającego" reporterOrigin: "Pochodzenie osoby zgłaszającej"
send: "Wyślij" send: "Wyślij"
abuseMarkAsResolved: "Oznacz zgłoszenie jako rozwiązane" abuseMarkAsResolved: "Oznacz zgłoszenie jako rozwiązane"
openInNewTab: "Otwórz w nowej karcie" openInNewTab: "Otwórz w nowej karcie"
@ -572,7 +573,7 @@ desktop: "Pulpit"
createNew: "Utwórz nowy" createNew: "Utwórz nowy"
optional: "Nieobowiązkowe" optional: "Nieobowiązkowe"
public: "Publiczny" public: "Publiczny"
i18nInfo: "FoundKey jest tłumaczone na wiele języków przez wolontariuszy. Możesz pomóc\ i18nInfo: "Foundkey jest tłumaczone na wiele języków przez wolontariuszy. Możesz pomóc\
\ na {link}." \ na {link}."
manageAccessTokens: "Zarządzaj tokenami dostępu" manageAccessTokens: "Zarządzaj tokenami dostępu"
accountInfo: "Informacje o koncie" accountInfo: "Informacje o koncie"
@ -594,7 +595,7 @@ driveUsage: "Użycie przestrzeni dyskowej"
noCrawle: "Odrzuć indeksowanie przez crawlery" noCrawle: "Odrzuć indeksowanie przez crawlery"
noCrawleDescription: "Proś wyszukiwarki internetowe, aby nie indeksowały Twojego profilu,\ noCrawleDescription: "Proś wyszukiwarki internetowe, aby nie indeksowały Twojego profilu,\
\ wpisów, stron itd." \ wpisów, stron itd."
lockedAccountInfo: "Dopóki nie ustawisz widoczności wpisu na \"Obserwujący\", twoje\ lockedAccountInfo: "Dopóki nie ustawisz widoczności wpisu na \"Obserwujący\", Twoje\
\ wpisy będą mogli widzieć wszyscy, nawet jeśli ustawisz manualne zatwierdzanie\ \ wpisy będą mogli widzieć wszyscy, nawet jeśli ustawisz manualne zatwierdzanie\
\ obserwujących." \ obserwujących."
alwaysMarkSensitive: "Oznacz domyślnie jako NSFW" alwaysMarkSensitive: "Oznacz domyślnie jako NSFW"
@ -611,7 +612,7 @@ useSystemFont: "Używaj domyślnej czcionki systemu"
makeExplorable: "Pokazuj konto na stronie „Eksploruj”" makeExplorable: "Pokazuj konto na stronie „Eksploruj”"
makeExplorableDescription: "Jeżeli wyłączysz tę opcję, Twoje konto nie będzie wyświetlać\ makeExplorableDescription: "Jeżeli wyłączysz tę opcję, Twoje konto nie będzie wyświetlać\
\ się w sekcji „Eksploruj”." \ się w sekcji „Eksploruj”."
showGapBetweenNotesInTimeline: "Pokazuj odstęp między wpisami na osi czasu." showGapBetweenNotesInTimeline: "Pokazuj odstęp między wpisami na osi czasu"
duplicate: "Duplikuj" duplicate: "Duplikuj"
left: "Lewo" left: "Lewo"
center: "Wyśsrodkuj" center: "Wyśsrodkuj"
@ -619,7 +620,7 @@ wide: "Szerokie"
narrow: "Wąskie" narrow: "Wąskie"
reloadToApplySetting: "To ustawienie zostanie zastosowane po odświeżeniu strony. Chcesz\ reloadToApplySetting: "To ustawienie zostanie zastosowane po odświeżeniu strony. Chcesz\
\ odświeżyć?" \ odświeżyć?"
needReloadToApply: "To ustawienie zostanie zastosowane po odświeżeniu strony" needReloadToApply: "To ustawienie zostanie zastosowane po odświeżeniu strony."
clearCache: "Wyczyść pamięć podręczną" clearCache: "Wyczyść pamięć podręczną"
onlineUsersCount: "{n} osób jest online" onlineUsersCount: "{n} osób jest online"
backgroundColor: "Tło" backgroundColor: "Tło"
@ -638,18 +639,18 @@ editCode: "Edytuj kod"
apply: "Zastosuj" apply: "Zastosuj"
receiveAnnouncementFromInstance: "Otrzymuj powiadomienia e-mail z tej instancji" receiveAnnouncementFromInstance: "Otrzymuj powiadomienia e-mail z tej instancji"
emailNotification: "Powiadomienia e-mail" emailNotification: "Powiadomienia e-mail"
useReactionPickerForContextMenu: "Otwórz wybornik reakcji prawym kliknięciem" useReactionPickerForContextMenu: "Otwórz wybierak reakcji prawym kliknięciem"
typingUsers: "{users} pisze(-ą)..." typingUsers: "{users} pisze(-ą)..."
jumpToSpecifiedDate: "Przejdź do określonej daty" jumpToSpecifiedDate: "Przejdź do określonej daty"
clear: "Wróć" clear: "Wróć"
markAllAsRead: "Oznacz wszystkie jako przeczytane" markAllAsRead: "Oznacz wszystkie jako przeczytane"
goBack: "Wróć" goBack: "Wróć"
unlikeConfirm: "Na pewno chcesz usunąć polubienie?" unlikeConfirm: "Na pewno chcesz usunąć polubienie?"
fullView: "Pełny widok" fullView: "Pełny widok"
quitFullView: "Opuść pełny widok" quitFullView: "Opuść pełny widok"
addDescription: "Dodaj opis" addDescription: "Dodaj opis"
userPagePinTip: "Możesz wyświetlać wpisy w tym miejscu po wybraniu \"Przypnij do profilu\"\ userPagePinTip: "Możesz wyświetlać wpisy w tym miejscu po wybraniu \"Przypnij do profilu\"\
\ z menu pojedyńczego wpisu" \ z menu pojedynczego wpisu."
notSpecifiedMentionWarning: "Ten wpis zawiera wzmianki o użytkownikach niezawartych\ notSpecifiedMentionWarning: "Ten wpis zawiera wzmianki o użytkownikach niezawartych\
\ jako odbiorcy" \ jako odbiorcy"
info: "Informacje" info: "Informacje"
@ -657,7 +658,7 @@ userInfo: "Informacje o użykowniku"
unknown: "Nieznane" unknown: "Nieznane"
hideOnlineStatus: "Ukryj status online" hideOnlineStatus: "Ukryj status online"
hideOnlineStatusDescription: "Ukrywanie statusu online ogranicza wygody niektórych\ hideOnlineStatusDescription: "Ukrywanie statusu online ogranicza wygody niektórych\
\ funkcji, tj. wyszukiwanie" \ funkcji, takich jak wyszukiwanie."
online: "Online" online: "Online"
active: "Aktywny" active: "Aktywny"
offline: "Offline" offline: "Offline"
@ -677,7 +678,7 @@ noBotProtectionWarning: "Zabezpieczenie przed botami nie jest skonfigurowane."
configure: "Skonfiguruj" configure: "Skonfiguruj"
recentPosts: "Ostatnie wpisy" recentPosts: "Ostatnie wpisy"
shareWithNote: "Udostępnij z wpisem" shareWithNote: "Udostępnij z wpisem"
emailNotConfiguredWarning: "Nie podano adresu e-mail" emailNotConfiguredWarning: "Nie podano adresu e-mail."
ratio: "Stosunek" ratio: "Stosunek"
previewNoteText: "Pokaż podgląd" previewNoteText: "Pokaż podgląd"
customCss: "Własny CSS" customCss: "Własny CSS"
@ -687,17 +688,21 @@ squareAvatars: "Wyświetlaj kwadratowe awatary"
hashtags: "Hashtag" hashtags: "Hashtag"
pubSub: "Konta Pub/Sub" pubSub: "Konta Pub/Sub"
hide: "Ukryj" hide: "Ukryj"
indefinitely: "Nigdy" indefinitely: "Dożywotnio"
_ffVisibility: _ffVisibility:
public: "Publikuj" public: "Publikuj"
followers: Widoczne tylko dla obserwujących
private: Prywatna
_forgotPassword: _forgotPassword:
ifNoEmail: "Jeżeli nie podano adresu e-mail podczas rejestracji, skontaktuj się\ ifNoEmail: "Jeżeli nie podano adresu e-mail podczas rejestracji, skontaktuj się\
\ z administratorem zamiast tego." \ z administratorem."
contactAdmin: "Jeżeli Twoja instancja nie obsługuje adresów e-mail, skontaktuj się\ contactAdmin: "Jeżeli Twoja instancja nie obsługuje adresów e-mail, skontaktuj się\
\ zamiast tego z administratorem, aby zresetować hasło." \ zamiast tego z administratorem, aby zresetować hasło."
enterEmail: Wpisz adres email użyty do rejestracji konta. Zostanie wysłany link
który umożliwi zresetowanie hasła.
_email: _email:
_follow: _follow:
title: "Zaobserwował(a) Cię" title: "Masz nowego obserwującego"
_receiveFollowRequest: _receiveFollowRequest:
title: "Otrzymano prośbę o możliwość obserwacji" title: "Otrzymano prośbę o możliwość obserwacji"
_plugin: _plugin:
@ -710,7 +715,7 @@ _registry:
domain: "Domena" domain: "Domena"
createKey: "Utwórz klucz" createKey: "Utwórz klucz"
_aboutMisskey: _aboutMisskey:
about: "FoundKey jest oprogramowanie open source rozwijanym przez syuilo od 2014." about: "Foundkey jest forkiem Misskey rozwijanym od lipca 2022."
allContributors: "Wszyscy twórcy" allContributors: "Wszyscy twórcy"
source: "Kod źródłowy" source: "Kod źródłowy"
_nsfw: _nsfw:
@ -719,8 +724,9 @@ _nsfw:
force: "Ukrywaj wszystkie media" force: "Ukrywaj wszystkie media"
_mfm: _mfm:
cheatSheet: "Ściąga MFM" cheatSheet: "Ściąga MFM"
intro: "MFM to język składniowy wyjątkowy dla FoundKey, który może być użyty w wielu\ intro: "MFM jest językiem składniowym używanym przez m.in. Misskey, forki *key (w\
\ miejscach. Tu znajdziesz listę wszystkich możliwych elementów składni MFM." \ tym Foundkey), oraz Akkomę, który może być użyty w wielu miejscach. Tu znajdziesz\
\ listę wszystkich możliwych elementów składni MFM."
dummy: "FoundKey rozszerza świat Fediwersum" dummy: "FoundKey rozszerza świat Fediwersum"
mention: "Wspomnij" mention: "Wspomnij"
mentionDescription: "Używając znaku @ i nazwy użytkownika, możesz określić danego\ mentionDescription: "Używając znaku @ i nazwy użytkownika, możesz określić danego\
@ -728,7 +734,7 @@ _mfm:
hashtag: "Hashtag" hashtag: "Hashtag"
hashtagDescription: "Używając kratki i tekstu, możesz określić hashtag." hashtagDescription: "Używając kratki i tekstu, możesz określić hashtag."
url: "Adres URL" url: "Adres URL"
urlDescription: "Adresy URL mogą być wyświetlane" urlDescription: "Adresy URL mogą być wyświetlane."
link: "Odnośnik" link: "Odnośnik"
linkDescription: "Określone części tekstu mogą być wyświetlane jako adres URL." linkDescription: "Określone części tekstu mogą być wyświetlane jako adres URL."
bold: "Pogrubienie" bold: "Pogrubienie"
@ -753,11 +759,38 @@ _mfm:
x3: "Bardzo duże" x3: "Bardzo duże"
x3Description: "Czyni treść jeszcze większą." x3Description: "Czyni treść jeszcze większą."
x4: "Ogromne" x4: "Ogromne"
x4Description: "Czyni treść jeszcze większą niż jeszcze większa." x4Description: "Czyni treść nawet większą niż jeszcze większa."
blur: "Rozmycie" blur: "Rozmycie"
font: "Czcionka" font: "Czcionka"
fontDescription: "Wybiera czcionkę do wyświetlania treści." fontDescription: "Wybiera czcionkę do wyświetlania treści."
rotate: "Obróć" rotate: "Obróć"
inlineCodeDescription: Wyświetla podświetlanie składni dla kodu (programu) w linii.
jump: Animacja (Skok)
blockMath: Matematyka (Blok)
tada: Animacja (Tada)
sparkle: Blask
sparkleDescription: Nadaje zawartości efekt iskrzących cząstek.
inlineMathDescription: Pokaż formuły matematyczne (KaTeX) w linii.
inlineMath: Matematyka (Inline)
inlineCode: Kod (inline)
smallDescription: Wyświetla zawartość jako małą i cienką.
blurDescription: Rozmywa zawartość. Zostanie ona wyraźnie wyświetlona przy najechaniu.
bounce: Animacja (Odbijanie)
jelly: Animacja (Żelek)
spin: Animacja (Wirowanie)
twitch: Animacja (Drganie)
jellyDescription: Nadaje zawartości galaretowatą animację.
tadaDescription: Nadaje zawartości animację w stylu "Tada!".
jumpDescription: Nadaje zawartości animację skokową.
bounceDescription: Nadaje zawartości sprężystą animację.
shakeDescription: Nadaje zawartości animację potrzęsania.
twitchDescription: Nadaje zawartości silnie drgającą animację.
spinDescription: Nadaje zawartości animację obrotową.
rainbow: Tęcza
rainbowDescription: Sprawia, że zawartość pojawia się w kolorach tęczy.
rotateDescription: Obraca zawartość o podany kąt.
blockMathDescription: Pokaż wieloliniowe formuły matematyczne (KaTeX) w bloku.
shake: Animacja (Wstrząs)
_instanceTicker: _instanceTicker:
none: "Nigdy nie pokazuj" none: "Nigdy nie pokazuj"
remote: "Pokaż dla zdalnych użytkowników" remote: "Pokaż dla zdalnych użytkowników"
@ -778,12 +811,21 @@ _channel:
notesCount: "{n} wpisy" notesCount: "{n} wpisy"
_menuDisplay: _menuDisplay:
hide: "Ukryj" hide: "Ukryj"
sideFull: Z boku
sideIcon: Z boku (tylko ikony)
top: U góry
_wordMute: _wordMute:
muteWords: "Słowo do wyciszenia" muteWords: "Słowo do wyciszenia"
muteWordsDescription2: "Otocz słowa kluczowe ukośnikami, aby używać wyrażeń regularnych." muteWordsDescription2: "Otocz słowa kluczowe ukośnikami, aby używać wyrażeń regularnych."
soft: "Łagodny" soft: "Łagodny"
hard: "Twardy" hard: "Twardy"
mutedNotes: "Wyciszone wpisy" mutedNotes: "Wyciszone wpisy"
muteWordsDescription: Rozdzielaj spacją dla kondycji AND, lub przerwaniem wiersza
dla kondycji OR.
hardDescription: Zapobiega dodawania do osi czasu wpisów, które spełniają podane
warunki. Dodatkowo, te wpisy nie zostaną dodane do osi czasu, jeśli warunki się
zmienią.
softDescription: Ukryj z osi czasu wpisy, które spełniają podane warunki.
_theme: _theme:
explore: "Przeglądaj motywy" explore: "Przeglądaj motywy"
install: "Zainstaluj motyw" install: "Zainstaluj motyw"
@ -794,7 +836,7 @@ _theme:
installedThemes: "Zainstalowane motywy" installedThemes: "Zainstalowane motywy"
builtinThemes: "Wbudowane motywy" builtinThemes: "Wbudowane motywy"
alreadyInstalled: "Motyw jest już zainstalowany" alreadyInstalled: "Motyw jest już zainstalowany"
invalid: "Format motywu jest nieprawidłowy." invalid: "Format motywu jest nieprawidłowy"
make: "Utwórz motyw" make: "Utwórz motyw"
_sfx: _sfx:
note: "Wpisy" note: "Wpisy"
@ -803,6 +845,7 @@ _sfx:
chat: "Wiadomości" chat: "Wiadomości"
chatBg: "Rozmowy (tło)" chatBg: "Rozmowy (tło)"
channel: "Powiadomienia kanału" channel: "Powiadomienia kanału"
antenna: Anteny
_ago: _ago:
future: "W przyszłości" future: "W przyszłości"
justNow: "Przed chwilą" justNow: "Przed chwilą"
@ -819,14 +862,39 @@ _time:
hour: "godz." hour: "godz."
day: "dzień" day: "dzień"
_tutorial: _tutorial:
title: "Jak korzystać z FoundKey" title: "Jak korzystać z Foundkey"
step1_1: "Witaj!" step1_1: "Witaj!"
step1_3: "Twoja oś czasu jest jeszcze pusta, ponieważ nie opublikowałeś(-aś) jeszcze\ step1_3: "Twoja oś czasu jest jeszcze pusta, ponieważ nie opublikował jeszcze\
\ żadnych wpisów i nie obserwujesz jeszcze nikogo." \ żadnych wpisów i nie obserwujesz jeszcze nikogo."
step2_1: "Ukończmy konfigurację profilu zanim utworzymy wpis lub zaczniemy kogoś\ step2_1: "Ukończmy konfigurację profilu zanim utworzymy wpis lub zaczniemy kogoś\
\ obserwować." \ obserwować."
step3_1: "Zakończyłeś(-aś) konfigurację profilu?" step3_1: "Zakończył*ś konfigurację profilu?"
step3_3: "Wypełnij pole i kliknij przycisk w prawym górnym rogu by wysłać post." step3_3: "Wypełnij pole i kliknij przycisk w prawym górnym rogu by wysłać wpis."
step4_1: Skończył*ś publikować swój pierwszy wpis?
step6_2: Możesz też dodawać reakcję do wpisów innych ludzi, żeby szybko odpowiedzieć
na nie.
step4_2: Hura! Teraz Twój wpis powinien być widoczny na Twojej osi czasu.
step5_2: '{featured} pokaże CI popularne wpisy z tej instancji. {explore} pozwoli
ci znaleźć popularnych użytkowników. Spróbuj tam znaleźć ludzi których chciał*byś
zaobserwować!'
step7_1: Gratulacje! Właśnie ukończył*ś podstawowy samouczek Foundkey.
step6_3: W celu dodania reakcji, kliknij na "+" przy poście innego użytkownika i
wybierz jakieś emoji jako reakcję na niego.
step6_1: Wpisy innych ludzi powinny już się pojawiać na Twojej osi czasu.
step7_2: Jeśli chcesz dowiedzieć się więcej o Foundkey, przejdź do sekcji {help}.
step7_3: A teraz, powodzenia i miłej zabawy z Foundkey! 🚀
step5_1: Teraz spróbujmy ożywić oś czasu poprzez zaobserwowanie innych ludzi.
step1_2: Ta strona jest nazywana "osią czasu". Pokazuje ona chronologicznie ułożone
wpisy ludzi których obserwujesz.
step5_4: Jeśli inny użytkownik ma kłódkę przy nazwie, ręczne zatwierdzenie Twojej
prośby o obserwację przez tego użytkownika może zająć trochę czasu.
step3_2: Spróbujmy opublikować wpis. Możesz to zrobić klikając przycisk z ikoną
ołówka.
step2_2: Dodanie informacji o sobie pomoże innym w decyzji czy chcą widzieć Twoje
wpisy, lub Ciebie obserwować.
step5_3: By zaobserwować innych użytkowników, kliknij na ich ikonę i wciśnij przycisk
"Obserwuj" na ich profilu.
step3_4: Nic nie przychodzi na myśl? Spróbuj "Właśnie wrócił*m z kościoła"!
_2fa: _2fa:
registerDevice: "Zarejestruj nowe urządzenie" registerDevice: "Zarejestruj nowe urządzenie"
step1: "Najpierw, zainstaluj aplikację uwierzytelniającą (taką jak {a} lub {b})\ step1: "Najpierw, zainstaluj aplikację uwierzytelniającą (taką jak {a} lub {b})\
@ -834,32 +902,54 @@ _2fa:
step2: "Następnie, zeskanuje kod QR z ekranu." step2: "Następnie, zeskanuje kod QR z ekranu."
step3: "Wprowadź token podany w aplikacji, aby ukończyć konfigurację." step3: "Wprowadź token podany w aplikacji, aby ukończyć konfigurację."
step4: "Od teraz, przy każdej próbie logowania otrzymasz prośbę o token logowania." step4: "Od teraz, przy każdej próbie logowania otrzymasz prośbę o token logowania."
alreadyRegistered: Już zarejestrował*ś urządzenie do uwierzytelnienia dwuetapowego.
step2Url: 'Możesz też wpisać ten URL jeśli używasz programu komputerowego:'
registerKey: Zarejestruj klucz sprzętowy
securityKeyInfo: Oprócz uwierzytelnienia odciskiem palców lub PIN, możesz również
skonfigurować uwierzytelnienie za pomocą kluczy sprzętowych obsługujących FIDO2,
w celu dalszego zabezpieczenia Twojego konta.
_permissions: _permissions:
"read:account": "Wyświetl informacje o swoim koncie" "read:account": "Wyświetl informacje o swoim koncie"
"write:account": "Edytuj swoje informacje o koncie" "write:account": "Edytuj informacje o koncie"
"read:blocks": "Zobacz listę osób, które zablokowałeś(-aś)" "read:blocks": "Wyświetl listę zablokowanych użytkowników"
"write:blocks": "Edytuj listę osób, które zablokowałeś(-aś)" "write:blocks": "Blokuj i odblokowuj użytkowników"
"read:drive": "Dostęp do plików i katalogów ze Twojego dysku" "read:drive": "Wyświetl pliki i foldery z twojego Dysku"
"write:drive": "Edycja i usuwanie plików i katalogów z Twojego dysku." "write:drive": "Tworzenie, zmienianie i usuwanie plików z Dysku"
"read:favorites": "Wyświetlanie Twojej listy ulubionych." "read:favorites": "Wyświetlanie listy ulubionych wpisów"
"write:favorites": "Edycja Twojej listy ulubionych." "write:favorites": "Edycja listy ulubionych wpisów"
"read:following": "Wyświetlanie informacji o obserwowanych" "read:following": "Wyświetlanie informacji o obserwowanych i obserwujących"
"write:following": "Obserwowanie lub cofanie obserwacji innych kont" "write:following": "Obserwowanie i cofanie obserwacji innych użytkowników"
"read:mutes": "Wyświetlanie listy osób, które wyciszyłeś(-aś)" "read:mutes": "Wyświetlanie użytkowników którzy są wyciszeni, lub których podbicia\
"write:mutes": "Edycja listy osób, które wyciszyłeś(-aś)" \ są wyciszone"
"write:mutes": "Wyciszanie i odciszanie użytkowników, lub ich podbić"
"read:notifications": "Wyświetlanie powiadomień" "read:notifications": "Wyświetlanie powiadomień"
"write:notifications": "Działanie na powiadomieniach" "write:notifications": "Oznaczanie powiadomień jako przeczytanych, oraz tworzenie\
"write:reactions": "Edycja reakcji" \ niestandardowych powiadomień"
"write:votes": "Głosowanie w ankiecie" "write:reactions": "Tworzenie i usuwanie reakcji"
"read:pages": "Wyświetlanie Twoich stron" "write:votes": "Głosowanie w ankietach"
"write:pages": "Edycja lub usuwanie Twoich stron" "read:pages": "Wyświetlanie stron"
"write:pages": "Tworzenie, zmienianie i usuwanie stron"
"read:page-likes": "Wyświetlanie polubień na stronach" "read:page-likes": "Wyświetlanie polubień na stronach"
"write:page-likes": "Edycja polubień na stronach" "write:page-likes": "Dodawanie oraz usuwanie polubień stron"
"read:user-groups": "Wyświetlanie grup użytkownika" "read:user-groups": "Wyświetlanie grup należących do Ciebie, do których dołączył*ś,\
"write:user-groups": "Edycja lub usuwanie grup użytkownika" \ lub został*ś zaproszon*"
"write:user-groups": "Tworzenie, modyfikowanie, usuwanie, przenoszenie grup, dołączanie\
\ i wychodzenie z grup. Zapraszaj i banuj innych z grup. Akceptuj i odrzucaj zaproszenia\
\ do grup."
"read:reactions": Wyświetlaj reakcje
"write:notes": Tworzenie i usuwanie wpisów
"write:messaging": Tworzenie i usuwanie wiadomości czatu
"write:channels": Tworzenie, modyfikowanie, obserwowanie i od-obserwowanie kanałów
"read:messaging": Wyświetlaj wiadomości czatu i jego historię
"read:channels": Wyświetlaj obserwowane kanały i te do których dołączył*ś
_auth: _auth:
shareAccess: "Czy chcesz autoryzować „{name}” do dostępu do tego konta?" shareAccess: "Czy chcesz autoryzować „{name}” do dostępu do tego konta?"
permissionAsk: "Ta aplikacja wymaga następujących uprawnień:" permissionAsk: "Ta aplikacja wymaga następujących uprawnień"
pleaseGoBack: Wróć do aplikacji
shareAccessAsk: Czy na pewno chcesz upoważnić tą aplikację do dostępu do Twojego
konta?
denied: Odmowa dostępu
callback: Wracam do aplikacji
_weekday: _weekday:
sunday: "Niedziela" sunday: "Niedziela"
monday: "Poniedziałek" monday: "Poniedziałek"
@ -881,6 +971,14 @@ _widgets:
postForm: "Utwórz wpis" postForm: "Utwórz wpis"
button: "Przycisk" button: "Przycisk"
jobQueue: "Kolejka zadań" jobQueue: "Kolejka zadań"
rssMarquee: Karuzela RSS
rss: Czytnik RSS
digitalClock: Zegar cyfrowy
onlineUsers: Użytkownicy online
slideshow: Pokaz slajdów
aichan: Ai
aiscript: Konsola AiScript
serverMetric: Wskaźniki serwera
_cw: _cw:
hide: "Ukryj" hide: "Ukryj"
show: "Załaduj więcej" show: "Załaduj więcej"
@ -915,6 +1013,10 @@ _visibility:
followers: "Obserwujący" followers: "Obserwujący"
specified: "Bezpośredni" specified: "Bezpośredni"
specifiedDescription: "Napisz tylko określonym użytkownikom" specifiedDescription: "Napisz tylko określonym użytkownikom"
localOnly: Lokalnie
homeDescription: Wpis będzie publiczny ale nie pojawi się na osi czasu instancji
followersDescription: Wpis pojawi się tylko na osiach czasu Twoich obserwujących
localOnlyDescription: Wpis będzie widoczny tylko dla użytkowników tej instancji
_postForm: _postForm:
_placeholders: _placeholders:
a: "Co się dzieje?" a: "Co się dzieje?"
@ -923,6 +1025,9 @@ _postForm:
d: "Czy masz coś do powiedzenia?" d: "Czy masz coś do powiedzenia?"
e: "Zacznij coś pisać…" e: "Zacznij coś pisać…"
f: "Czekamy, aż coś napiszesz." f: "Czekamy, aż coś napiszesz."
quotePlaceholder: Cytuj ten wpis...
replyPlaceholder: Odpowiedz na ten wpis...
channelPlaceholder: Wyślij na kanał...
_profile: _profile:
name: "Nazwa" name: "Nazwa"
username: "Nazwa użytkownika" username: "Nazwa użytkownika"
@ -942,24 +1047,38 @@ _exportOrImport:
muteList: "Wycisz" muteList: "Wycisz"
blockingList: "Zablokuj" blockingList: "Zablokuj"
userLists: "Listy" userLists: "Listy"
excludeMutingUsers: Wyklucz wyciszonych użytkowników
excludeInactiveUsers: Wyklucz nieaktywnych użytkowników
_charts: _charts:
federation: "Federacja" federation: "Federacja"
apRequest: "Żądania" apRequest: "Żądania"
usersTotal: "Łącznie # użytkowników" usersTotal: "Łącznie # użytkowników"
activeUsers: "Aktywni użytkownicy" activeUsers: "Aktywni użytkownicy"
usersIncDec: Różnica w liczbie użytkowników
notesIncDec: Różnica w liczbie wpisów
localNotesIncDec: Różnica w liczbie lokalnych wpisów
notesTotal: Łączna liczba wpisów
remoteNotesIncDec: Różnica w liczbie zdalnych wpisów
filesIncDec: Różnica w liczbie plików
storageUsageTotal: Łączne użycie dysku
filesTotal: Łączna liczba plików
storageUsageIncDec: Różnica w wykorzystaniu miejsca
_instanceCharts: _instanceCharts:
requests: "Żądania" requests: "Żądania"
notesTotal: "Łącznie # wpisów" notesTotal: "Łącznie # wpisów"
ff: "Różnica w # obserwujących" ff: "Różnica w liczbie obserwowanych / obserwujących użytkowników"
ffTotal: "Łączna liczba # obserwujących" ffTotal: "Łączna liczba # obserwujących"
cacheSize: "Różnica w rozmiarze pamięci podręcznej" cacheSize: "Różnica w rozmiarze pamięci podręcznej"
cacheSizeTotal: "Łączny rozmiar pamięci podręcznej" cacheSizeTotal: "Łączny rozmiar pamięci podręcznej"
files: "Różnica # plików" files: "Różnica # plików"
filesTotal: "Łącznie # plików" filesTotal: "Łącznie # plików"
usersTotal: Łączna liczba użytkowników
notes: Różnica w liczbie wpisów
users: Różnica w liczbie użytkowników
_timelines: _timelines:
home: "Strona główna" home: "Strona główna"
local: "Lokalne" local: "Lokalne"
social: "Społeczność" social: "Społeczna"
global: "Globalna" global: "Globalna"
_pages: _pages:
newPage: "Utwórz stronę" newPage: "Utwórz stronę"
@ -971,7 +1090,7 @@ _pages:
pageSetting: "Ustawienia strony" pageSetting: "Ustawienia strony"
nameAlreadyExists: "Określony adres URL strony już istnieje" nameAlreadyExists: "Określony adres URL strony już istnieje"
invalidNameTitle: "Podany adres URL strony jest nieprawidłowy" invalidNameTitle: "Podany adres URL strony jest nieprawidłowy"
invalidNameText: "Sprawdź, czy nie jest puste" invalidNameText: "Upewnij się, że pole tytułowe strony nie jest puste"
editThisPage: "Edytuj tę stronę" editThisPage: "Edytuj tę stronę"
viewSource: "Zobacz źródło" viewSource: "Zobacz źródło"
viewPage: "Wyświetlanie Twoich stron" viewPage: "Wyświetlanie Twoich stron"
@ -996,32 +1115,37 @@ _relayStatus:
accepted: "Zaakceptowano" accepted: "Zaakceptowano"
rejected: "Odrzucono" rejected: "Odrzucono"
_notification: _notification:
youGotMention: "{name} wspomniał(a) o Tobie" youGotMention: "{name} wspomniał* o Tobie"
youGotReply: "{name} odpowiedział(a) Tobie" youGotReply: "{name} odpowiedział* Tobie"
youGotQuote: "{name} zacytował(a) Ciebie" youGotQuote: "{name} zacytował* Ciebie"
youRenoted: "{name} udostępnił(a) Twój wpis" youRenoted: "{name} udostępnił* Twój wpis"
youGotPoll: "{name} zagłosował(a) w Twojej ankiecie" youGotPoll: "{name} zagłosował* w Twojej ankiecie"
youGotMessagingMessageFromUser: "{name} wysłał(a) Ci wiadomość" youGotMessagingMessageFromUser: "{name} wysłał* Ci wiadomość"
youGotMessagingMessageFromGroup: "Została wysłana wiadomość do grupy {name}" youGotMessagingMessageFromGroup: "Została wysłana wiadomość do grupy {name}"
youWereFollowed: "Zaobserwował(a) Cię" youWereFollowed: "zaobserwował* Cię"
youReceivedFollowRequest: "Otrzymałeś(-aś) prośbę o możliwość obserwacji" youReceivedFollowRequest: "Otrzymał prośbę o możliwość obserwacji"
yourFollowRequestAccepted: "Twoja prośba o możliwość obserwacji została przyjęta" yourFollowRequestAccepted: "Twoja prośba o możliwość obserwacji została przyjęta"
youWereInvitedToGroup: "Zaproszony(-a) do grupy" youWereInvitedToGroup: "{userName} zaprosił* Ciebie do grupy"
_types: _types:
follow: "Obserwowani" follow: "Nowi obserwujący"
mention: "Wspomnij" mention: "Wspomnienia"
reply: "Odpowiedzi" reply: "Odpowiedzi"
renote: "Udostępnij" renote: "Podbicia"
quote: "Cytuj" quote: "Cytaty"
reaction: "Reakcja" reaction: "Reakcje"
pollVote: "Głosy w ankietach" pollVote: "Głosy w ankietach"
receiveFollowRequest: "Otrzymano prośbę o możliwość obserwacji" receiveFollowRequest: "Otrzymane prośby o możliwość obserwacji"
followRequestAccepted: "Przyjęto prośbę o możliwość obserwacji" followRequestAccepted: "Przyjęte prośby o możliwość obserwacji"
groupInvited: "Zaproszono do grup" groupInvited: "Zaproszenia do grup"
app: "Powiadomienia z aplikacji" app: "Powiadomienia z powiązanych aplikacji"
pollEnded: Zakończone ankiety
move: Inni użytkownicy przenoszący konta
_actions: _actions:
reply: "Odpowiedz" reply: "Odpowiedz"
renote: "Udostępnij" renote: "Udostępnij"
followBack: również cię zaobserwował*
emptyPushNotificationMessage: Powiadomienia push zostały zaktualizowane
pollEnded: Są dostępne wyniki ankiety
_deck: _deck:
alwaysShowMainColumn: "Zawsze pokazuj główną kolumnę" alwaysShowMainColumn: "Zawsze pokazuj główną kolumnę"
columnAlign: "Wyrównaj kolumny" columnAlign: "Wyrównaj kolumny"
@ -1054,3 +1178,199 @@ unrenoteAllConfirm: Czy na pewno chcesz cofnąć wszystkie podbicia tego wpisu?
renoteMute: Ukryj podbicia renoteMute: Ukryj podbicia
renoteUnmute: Pokaż podbicia renoteUnmute: Pokaż podbicia
unrenoteAll: Cofnij wszystkie podbicia unrenoteAll: Cofnij wszystkie podbicia
menu: Menu
clips: Klipsy
lastCommunication: Ostatnie połączenie
manageAccounts: Zarządzaj kontami
keepCw: Zostaw ostrzeżenia o zawartości
itsOff: Wyłączone
classic: Klasyczny
controlPanel: Panel sterowania
instanceDefaultLightTheme: Domyślny jasny motyw instancji
instanceDefaultThemeDescription: Wpisz kod motywu w formacie obiektowym.
mutePeriod: Długość wyciszenia
size: Rozmiar
recentNDays: Ostatnie {n} dni
translationSettings: Ustawienia tłumaczeń
breakFollow: Usuń obserwującego
filter: Filtruj
oneDay: Dzień
oneWeek: Tydzień
rateLimitExceeded: Przekroczono ratelimit
_signup:
emailAddressInfo: Wpisz swój adres email. Nie zostanie on upubliczniony.
almostThere: Jeszcze trochę
emailSent: Email potwierdzający został wysłany na Twój adres email {email}. Proszę
kliknij załączony link w celu dokończenia procesu tworzenia konta.
_accountDelete:
inProgress: Usuwanie jest w toku
started: Usuwanie zostało rozpoczęte.
mayTakeTime: Jako że usuwanie konta jest zasobożerną operacją, może zająć ono trochę
czasu, w zależności od ilości utworzonych wpisów oraz wysłanych plików.
accountDelete: Usuń konto
sendEmail: Gdy usuwanie konta będzie ukończone, zostanie wysłany email na adres
email przypisany do tego konta.
requestAccountDelete: Zażądaj usunięcia konta
blockThisInstanceDescription: Lokalna aktywność nie będzie wysyłana do tej instancji.
Aktywność z tej instancji będzie odrzucana.
maxCustomEmojiPicker: Maksymalna ilość sugerowanych niestandardowych emoji w wybieraku
unread: Nieprzeczytane
resolved: Rozwiązano
unresolved: Nierozwiązane
leaveGroup: Wyjdź z grupy
voteConfirm: Potwierdzasz głos na "{choice}"?
documentation: Dokumentacja
file: Plik
makeReactionsPublic: Włącz historię reakcji jako publiczną
unclip: Usuń klipsa
stopActivityDeliveryDescription: Lokalna aktywność nie będzie wysyłana do tej instancji.
Otrzymywanie aktywności działa jak dotychczas.
signinHistoryExpires: Dane dotyczące poprzednich prób logowania są automatycznie usuwane
po 60 dniach, w celu zachowania zgodności z przepisami dotyczącymi ochrony prywatności.
smtpSecure: Użyj implicit SSL/TLS dla połączeń SMTP
federateBlocks: Sfederuj blokady
federateBlocksDescription: Jeśli wyłączone, blokady nie będą wysyłane do instancji
blokowanego użytkownika.
failedToFetchAccountInformation: Nie można uzyskać informacji o koncie
deleteAccountConfirm: To usunie bezpowrotnie konto {handle}. Kontynuować?
_translationService:
_deepl:
authKey: Klucz uwierzytelnienia DeepL
_libreTranslate:
authKey: Klucz uwierzytelnienia LibreTranslate (opcjonalnie)
endpoint: Punkt końcowy API LibreTranslate
itsOn: Włączone
deleteAllFiles: Usuń wszystkie pliki
emailRequiredForSignup: Wymagaj adresu email przy rejestracji
threadMuteNotificationsDesc: Wybierz powiadomienia, które chcesz zobaczyć w tym wątku.
Obowiązują również globalne ustawienia powiadomień. Wyłączenie ma pierwszeństwo.
regexpError: Błąd regularnego wyrażenia
instanceMute: Wyciszone instancje
instanceDefaultDarkTheme: Domyślny ciemny motyw instancji
regexpErrorDescription: 'Wystąpił błąd w regularnym wyrażeniu znajdującym się w linijce
{line} Twoich {tab} wyciszeń słownych:'
reflectMayTakeTime: Może upłynąć trochę czasu, zanim pojawią się zmiany.
numberOfPageCacheDescription: Zwiększenie tej liczby poprawi wygodę użytkowników,
ale spowoduje większe zużycie serwera, jak i pamięci.
_emailUnavailable:
format: Format tego adresu email jest nieprawidłowy
used: Ten adres email już został użyty
disposable: Jednorazowe adresy email nie mogą być użyte
mx: Ten serwer email jest nieprawidłowy
smtp: Ten serwer email nie odpowiada
recommended: Polecane
_antennaSources:
homeTimeline: Wpisy od obserwowanych użytkowników
users: Wpisy od konkretnych użytkowników
all: Wszystkie wpisy
userList: Wpisy od użytkowników z konkretnej listy
userGroup: Wpisy od użytkowników z konkretnej grupy
translationService: Usługa tłumaczeń
ffVisibilityDescription: Pozwala skonfigurować kto może zobaczyć kogo obserwujesz
oraz kto Ciebie obserwuje.
leaveGroupConfirm: Czy na pewno chcesz opuścić "{name}"?
overridedDeviceKind: Typ urządzenia
useDrawerReactionPickerForMobile: Wyświetlaj wybierak reakcji jako szufladę na telefonie
makeReactionsPublicDescription: Dzięki temu lista wszystkich Twoich dotychczasowych
reakcji będzie publicznie widoczna.
muteThread: Wycisz wątek
unmuteThread: Odcisz wątek
ffVisibility: Widoczność obserwowanych/obserwujących
continueThread: Pokaż resztę wątku
incorrectPassword: Nieprawidłowe hasło.
clickToFinishEmailVerification: Kliknij {ok} by dokończyć weryfikację konta email.
cannotAttachFileWhenAccountSwitched: Nie możesz załączyć pliku będąc przełączonym
na inne konto.
cannotSwitchAccountWhenFileAttached: Nie możesz zmienić konta przy załączonych plikach.
tenMinutes: 10 minut
addTag: Dodaj tag
isSystemAccount: Konto założone i automatycznie zarządzane przez system.
auto: Auto
check: Sprawdź
cropImage: Kadruj zdjęcie
deleteAccount: Usuń konto
smartphone: Smartfon
tablet: Tablet
themeColor: Kolor znacznika instancji
oneHour: Godzina
cropImageAsk: Czy chcesz skadrować to zdjęcie?
recentNHours: Ostatnie {n} godzin
typeToConfirm: Wpisz {x} by potwierdzić
numberOfPageCache: Liczba zbuforowanych stron
noEmailServerWarning: Serwer email nie jest skonfigurowany.
thereIsUnresolvedAbuseReportWarning: Istnieją nierozwiązane zgłoszenia.
unlimited: Nieograniczone
selectAll: Wybierz wszystko
setCategory: Ustaw kategorię
setTag: Ustaw tag
removeTag: Usuń tag
externalCssSnippets: Kilka fragmentów CSS dla Twojej inspiracji (nie zarządzane przez
Foundkey)
confirmToUnclipAlreadyClippedNote: Ten wpis jest już częścią klpisa "{name}". Czy
chcesz w takim razie usunąć wpis z tego klipsa?
maxUnicodeEmojiPicker: Maksymalna ilość sugerowanych unicode emoji w wybieraku
_instanceMute:
instanceMuteDescription: Spowoduje to wyciszenie wszystkich wpisów/podbić z podanych
instancji, w tym tych od użytkowników odpowiadających na wpisy z wyciszonych instancji.
instanceMuteDescription2: Oddzielaj nowymi liniami
heading: Lista instancji do wyciszenia
title: Ukrywa wpisy z podanych instancji.
noPermissionsRequested: (Brak oczekiwanych uprawnień.)
appAuthorization: Autoryzacja aplikacji
oauthErrorGoBack: Wystąpił błąd podczas uwierzytelniania zewnętrznej aplikacji. Wróć
i spróbuj ponownie.
selectMode: Wybierz wiele
whatIsNew: Pokaż zmiany
reporter: Osoba zgłaszająca
translate: Tłumacz
translatedFrom: Przetłumaczone z {x}
accountDeletionInProgress: Trwa usuwanie konta.
forwardReport: Przekaż raport do zdalnej instancji
clip: Klipsy
createNewClip: Utwórz nowego klipsa
forwardReportIsAnonymous: Zamiast Twojego konta, anonimowe konto systemowe zostanie
wyświetlone na zdalnej instancji jako zgłaszający.
usernameInfo: Nazwa która odróżnia Twoje konto od innych z tego serwera. Możesz użyć
alfabetu łacińskiego (a-z, A-Z), cyfr (0-9), lub podkreślników (_). Nazwy użytkownika
nie mogą zostać później zmienione.
switchAccount: Przełącz konto
searchResult: Wyniki wyszukiwania
troubleshooting: Rozwiązywanie problemów
useBlurEffect: Używaj efektu rozmycia w interfejsie
learnMore: Dowiedz się więcej
misskeyUpdated: Foundkey zostało zaktualizowane!
flagShowTimelineRepliesDescription: Jeśli włączone, zostaną pokazane odpowiedzi użytkowników
do wpisów innych użytkowników.
flagShowTimelineReplies: Pokazuj odpowiedzi na osi czasu
proxyAccountDescription: Konto proxy jest kontem które w określonych sytuacjach zachowuje
się jak zdalny obserwujący. Na przykład, kiedy użytkownik dodaje zdalnego użytkownika
do listy, oraz żaden lokalny użytkownik nie obserwuje tego konta, aktywność owego
użytkownika nie zostanie dostarczona na oś czasu. W takim razie, użytkownika zaobserwuje
konto proxy.
keepOriginalUploading: Zostaw oryginalne zdjęcie
keepOriginalUploadingDescription: Zapisuje oryginalne zdjęcie. Jeśli wyłączone, wersja
do wyświetlania w sieci zostanie wygenerowana podczas wysłania.
disableDrawer: Nie używaj wysuwanych menu
objectStorageBaseUrlDesc: "URL stosowany jako odniesienie. Podaj URL twojego CDN,\
\ albo proxy, jeśli używasz któregokolwiek.\nDla S3 użyj 'https://<bucket>.s3.amazonaws.com',\
\ a dla GCS i jego odpowiedników użyj 'https://storage.googleapis.com/<bucket>',\
\ itd."
objectStorageSetPublicRead: Ustaw "public-read" podczas wysyłania
removeAllFollowing: Przestań obserwować wszystkich obserwowanych użytkowników
yourAccountSuspendedTitle: To konto jest zawieszone
yourAccountSuspendedDescription: To konto zostało zawieszone z powodu łamania regulaminu
tego serwera. Skontaktuj się z administratorem, jeśli chcesz poznać bardziej dokładny
powód. Prosimy o niezakładanie nowych kont.
numberOfColumn: Liczba kolumn
_remoteInteract:
urlInstructions: Możesz skopiować ten URL. Jeśli wkleisz go w pole wyszukiwania
na twojej instancji, najpewniej zostaniesz przekierowan* do właściwego miejsca.
title: Przepraszam, ale nie mogę tego zrobić.
description: Nie możesz wykonać tej czynności w tej chwili. Musisz najprawdopodobniej
zalogować się, albo wykonać ją na swojej instancji.
movedTo: Ten użytkownik przeniósł się na {handle}.
attachedToNotes: Wpisy z tym plikiem
showAttachedNotes: Pokaż wpisy z tym plikiem
uploadFailedSize: Plik jest za duży do przesłania.
uploadFailed: Przesyłanie nie powiodło się
uploadFailedDescription: Plik nie mógł zostać przesłany.

View file

@ -275,8 +275,6 @@ createFolder: "Crează folder"
renameFolder: "Redenumește acest folder" renameFolder: "Redenumește acest folder"
deleteFolder: "Șterge acest folder" deleteFolder: "Șterge acest folder"
addFile: "Adăugați un fișier" addFile: "Adăugați un fișier"
emptyDrive: "Drive-ul tău e gol"
emptyFolder: "Folder-ul acesta este gol"
unableToDelete: "Nu se poate șterge" unableToDelete: "Nu se poate șterge"
inputNewFileName: "Introdu un nou nume de fișier" inputNewFileName: "Introdu un nou nume de fișier"
inputNewDescription: "Introdu o descriere nouă" inputNewDescription: "Introdu o descriere nouă"

View file

@ -181,14 +181,15 @@ clearCachedFiles: "Очистить кэш"
clearCachedFilesConfirm: "Удалить все закэшированные файлы с других сайтов?" clearCachedFilesConfirm: "Удалить все закэшированные файлы с других сайтов?"
blockedInstances: "Заблокированные инстансы" blockedInstances: "Заблокированные инстансы"
blockedInstancesDescription: "Введите список инстансов, которые хотите заблокировать.\ blockedInstancesDescription: "Введите список инстансов, которые хотите заблокировать.\
\ Они больше не смогут обмениваться с вашим инстансом." \ Они больше не смогут обмениваться с вашим инстансом. Не-ASCII доменные имена должны\
\ быть переведены в punycode. Субдомены тоже будут заблокированы"
muteAndBlock: "Скрытие и блокировка" muteAndBlock: "Скрытие и блокировка"
mutedUsers: "Скрытые пользователи" mutedUsers: "Скрытые пользователи"
blockedUsers: "Заблокированные пользователи" blockedUsers: "Заблокированные пользователи"
noUsers: "Нет ни одного пользователя" noUsers: "Нет ни одного пользователя"
editProfile: "Редактировать профиль" editProfile: "Редактировать профиль"
noteDeleteConfirm: "Вы хотите удалить эту заметку?" noteDeleteConfirm: "Вы хотите удалить эту заметку?"
pinLimitExceeded: "Нельзя закрепить ещё больше заметок" pinLimitExceeded: "Нельзя закрепить ещё больше заметок."
intro: "Установка FoundKey завершена! А теперь создайте учетную запись администратора." intro: "Установка FoundKey завершена! А теперь создайте учетную запись администратора."
done: "Готово" done: "Готово"
processing: "Обработка" processing: "Обработка"
@ -269,8 +270,6 @@ createFolder: "Создать папку"
renameFolder: "Переименовать папку" renameFolder: "Переименовать папку"
deleteFolder: "Удалить папку" deleteFolder: "Удалить папку"
addFile: "Добавить файл" addFile: "Добавить файл"
emptyDrive: "Диск пуст"
emptyFolder: "Папка пуста"
unableToDelete: "Удаление невозможно" unableToDelete: "Удаление невозможно"
inputNewFileName: "Введите имя нового файла" inputNewFileName: "Введите имя нового файла"
inputNewDescription: "Введите новую подпись" inputNewDescription: "Введите новую подпись"
@ -724,7 +723,7 @@ misskeyUpdated: "FoundKey обновился!"
whatIsNew: "Что новенького?" whatIsNew: "Что новенького?"
translate: "Перевод" translate: "Перевод"
translatedFrom: "Перевод. Язык оригинала — {x}" translatedFrom: "Перевод. Язык оригинала — {x}"
accountDeletionInProgress: "В настоящее время выполняется удаление учетной записи" accountDeletionInProgress: "В настоящее время выполняется удаление учетной записи."
usernameInfo: "Имя, которое отличает вашу учетную запись от других на этом сервере.\ usernameInfo: "Имя, которое отличает вашу учетную запись от других на этом сервере.\
\ Вы можете использовать алфавит (a~z, A~Z), цифры (0~9) или символы подчеркивания\ \ Вы можете использовать алфавит (a~z, A~Z), цифры (0~9) или символы подчеркивания\
\ (_). Имена пользователей не могут быть изменены позже." \ (_). Имена пользователей не могут быть изменены позже."
@ -751,7 +750,7 @@ ffVisibility: "Видимость подписок и подписчиков"
ffVisibilityDescription: "Здесь можно настроить, кто будет видеть ваши подписки и\ ffVisibilityDescription: "Здесь можно настроить, кто будет видеть ваши подписки и\
\ подписчиков." \ подписчиков."
continueThread: "Показать следующие ответы" continueThread: "Показать следующие ответы"
deleteAccountConfirm: "Учётная запись будет безвозвратно удалена. Подтверждаете?" deleteAccountConfirm: "Учётная запись {handle} будет безвозвратно удалена. Подтверждаете?"
incorrectPassword: "Пароль неверен." incorrectPassword: "Пароль неверен."
voteConfirm: "Отдать голос за «{choice}»?" voteConfirm: "Отдать голос за «{choice}»?"
hide: "Спрятать" hide: "Спрятать"
@ -1270,3 +1269,81 @@ _deck:
mentions: "Упоминания" mentions: "Упоминания"
direct: "Личное" direct: "Личное"
_services: {} _services: {}
botFollowRequiresApproval: Запросы на подписку от аккаунтов помеченных как бот требуют
подтверждения
showLess: Показать меньше
exportAll: Экспортировать всё
exportSelected: Экспортировать выбранное
cannotAttachFileWhenAccountSwitched: Вы не можете прикрепить файл, перейдя в другую
учетную запись.
cannotSwitchAccountWhenFileAttached: Вы не можете переключать учетные записи, пока
файлы прикреплены.
deleteAccount: Удалить аккаунт
isSystemAccount: Учетная запись, созданная системой и автоматически управляемая ею.
oneDay: Один день
cropImage: Обрезать изображение
documentation: Документация
movedTo: Этот пользователь перешел на {handle}.
typeToConfirm: Пожалуйста введите {x} чтобы подтвердить
rateLimitExceeded: Лимит превышен
numberOfPageCache: Количество кэшированных страниц
numberOfPageCacheDescription: Увеличение этого числа повысит удобство для пользователей,
но приведет к увеличению нагрузки на сервер, а также к использованию большего объема
памяти.
file: Файл
unclip: Удалить из подборки
translationSettings: Настройки перевода
translationService: Служба перевода
threadMuteNotificationsDesc: Выберите уведомления, которые вы хотите просмотреть в
этом треде. Также применяются глобальные настройки уведомлений. Отключение имеет
приоритет.
reflectMayTakeTime: Это может занять некоторое время чтобы вступило в силу.
failedToFetchAccountInformation: Не удалось получить информацию о аккаунте
instanceDefaultThemeDescription: Введите код темы в формате объекта.
tenMinutes: 10 минут
oneHour: Один час
oneWeek: Одна неделя
cropImageAsk: Вы хотите обрезать это изображение?
recentNHours: Последние {n} часов
recentNDays: Последние {n} дней
confirmToUnclipAlreadyClippedNote: Эта заметка уже является частью подборки "{name}".
Вы хотите вместо этого удалить это из этой подборки?
noEmailServerWarning: Сервер электронной почты не настроен.
setTag: Установить метку
addTag: Добавить метку
removeTag: Удалить метку
externalCssSnippets: Несколько фрагментов CSS для вашего вдохновения (не управляются
FoundKey)
oauthErrorGoBack: Произошла ошибка при попытке аутентификации стороннего приложения.
Пожалуйста, вернитесь и попробуйте еще раз.
appAuthorization: Авторизация приложения
noPermissionsRequested: (Никаких разрешений не требуется.)
selectMode: Выберите несколько
selectAll: Выбрать все
setCategory: Установить категорию
thereIsUnresolvedAbuseReportWarning: Есть нерасмотренные жалобы.
recommended: Рекомендовано
check: Проверка
unlimited: Неограниченный
mutePeriod: Длительность глушения
uploadFailed: Загрузка не удалась
uploadFailedDescription: Файл не может быть загружен.
uploadFailedSize: Файл слишком большой для загрузки.
renoteUnmute: Показать репосты
stopActivityDeliveryDescription: Локальная активнось не будет отправлена на этот сервер.
Получение активностей работает как раньше.
renoteMute: Скрыть репосты
unrenoteAllConfirm: Вы уверены что хотите отменить все репосты данной замети?
unrenoteAll: Отменить все репосты
blockThisInstanceDescription: Локальная активность не будет отправлена на этот сервер.
Активность этого сервера будет выброшена.
attachedToNotes: Заметки с этим файлом
showAttachedNotes: Показать заметки с этим файлом
signinHistoryExpires: Данные о прошлых попытках войти будут автоматически удалены
после 60 дней для соблюдения правил конфиденциальности.
deleteAllFiles: Удалить все файлы
federateBlocks: Федерировать блоки
federateBlocksDescription: Если выключено, то активности типа "блок" не будут отправлены.
regexpErrorDescription: 'Произошла ошибка в регулярном выражении на строке {line}
ваших {tab} заглушенных слов:'
reporter: Подавший жалобу

View file

@ -269,8 +269,6 @@ createFolder: "Vytvoriť priečinok"
renameFolder: "Premenovať priečinok" renameFolder: "Premenovať priečinok"
deleteFolder: "Odstrániť priečinok" deleteFolder: "Odstrániť priečinok"
addFile: "Pridať súbor" addFile: "Pridať súbor"
emptyDrive: "Váš disk je prázdny"
emptyFolder: "Tento priečinok je prázdny"
unableToDelete: "Nedá sa odstrániť" unableToDelete: "Nedá sa odstrániť"
inputNewFileName: "Zadajte nový názov" inputNewFileName: "Zadajte nový názov"
inputNewDescription: "Zadajte nový popis" inputNewDescription: "Zadajte nový popis"

View file

@ -272,8 +272,6 @@ createFolder: "Створити теку"
renameFolder: "Перейменувати теку" renameFolder: "Перейменувати теку"
deleteFolder: "Видалити теку" deleteFolder: "Видалити теку"
addFile: "Додати файл" addFile: "Додати файл"
emptyDrive: "Диск порожній"
emptyFolder: "Тека порожня"
unableToDelete: "Видалення неможливе" unableToDelete: "Видалення неможливе"
inputNewFileName: "Введіть ім'я нового файлу" inputNewFileName: "Введіть ім'я нового файлу"
inputNewDescription: "Введіть новий заголовок" inputNewDescription: "Введіть новий заголовок"

View file

@ -269,8 +269,6 @@ createFolder: "Tạo thư mục"
renameFolder: "Đổi tên thư mục" renameFolder: "Đổi tên thư mục"
deleteFolder: "Xóa thư mục" deleteFolder: "Xóa thư mục"
addFile: "Thêm tập tin" addFile: "Thêm tập tin"
emptyDrive: "Ổ đĩa của bạn trống trơn"
emptyFolder: "Thư mục trống"
unableToDelete: "Không thể xóa" unableToDelete: "Không thể xóa"
inputNewFileName: "Nhập tên mới cho tập tin" inputNewFileName: "Nhập tên mới cho tập tin"
inputNewDescription: "Nhập mô tả mới" inputNewDescription: "Nhập mô tả mới"

View file

@ -249,8 +249,6 @@ createFolder: "创建文件夹"
renameFolder: "重命名文件夹" renameFolder: "重命名文件夹"
deleteFolder: "删除文件夹" deleteFolder: "删除文件夹"
addFile: "添加文件" addFile: "添加文件"
emptyDrive: "网盘中无文件"
emptyFolder: "此文件夹中无文件"
unableToDelete: "无法删除" unableToDelete: "无法删除"
inputNewFileName: "请输入新文件名" inputNewFileName: "请输入新文件名"
inputNewDescription: "请输入新标题" inputNewDescription: "请输入新标题"

View file

@ -249,8 +249,6 @@ createFolder: "新增資料夾"
renameFolder: "重新命名資料夾" renameFolder: "重新命名資料夾"
deleteFolder: "刪除資料夾" deleteFolder: "刪除資料夾"
addFile: "加入附件" addFile: "加入附件"
emptyDrive: "雲端硬碟為空"
emptyFolder: "資料夾為空"
unableToDelete: "無法刪除" unableToDelete: "無法刪除"
inputNewFileName: "輸入檔案名稱" inputNewFileName: "輸入檔案名稱"
inputNewDescription: "請輸入新標題" inputNewDescription: "請輸入新標題"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,35 @@
export class movedTo1668977715500 {
name = 'movedTo1668977715500';
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "user" ADD "movedToId" character varying(32)`);
await queryRunner.query(`ALTER TABLE "notification" ADD "moveTargetId" character varying(32)`);
await queryRunner.query(`COMMENT ON COLUMN "notification"."moveTargetId" IS 'The ID of the moved to account.'`);
await queryRunner.query(`ALTER TABLE "user" ADD CONSTRAINT "FK_16fef167e4253ccdc8971b01f6e" FOREIGN KEY ("movedToId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_078db271ad52ccc345b7b2b026a" FOREIGN KEY ("moveTargetId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TYPE "notification_type_enum" ADD VALUE 'move'`);
await queryRunner.query(`ALTER TYPE "user_profile_mutingnotificationtypes_enum" ADD VALUE 'move'`);
}
async down(queryRunner) {
// remove 'move' from user muting notifications type enum
await queryRunner.query(`CREATE TYPE "public"."user_profile_mutingnotificationtypes_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app', 'pollEnded')`);
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" DROP DEFAULT`);
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" TYPE "public"."user_profile_mutingnotificationtypes_enum_old"[] USING "mutingNotificationTypes"::"text"::"public"."user_profile_mutingnotificationtypes_enum_old"[]`);
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" SET DEFAULT '{}'`);
await queryRunner.query(`DROP TYPE "public"."user_profile_mutingnotificationtypes_enum"`);
await queryRunner.query(`ALTER TYPE "public"."user_profile_mutingnotificationtypes_enum_old" RENAME TO "user_profile_mutingnotificationtypes_enum"`);
// remove 'move' from notification type enum
await queryRunner.query(`DELETE FROM "notification" WHERE "type" = 'move'`);
await queryRunner.query(`CREATE TYPE "public"."notification_type_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app')`);
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "public"."notification_type_enum_old" USING "type"::"text"::"public"."notification_type_enum_old"`);
await queryRunner.query(`DROP TYPE "public"."notification_type_enum"`);
await queryRunner.query(`ALTER TYPE "public"."notification_type_enum_old" RENAME TO "notification_type_enum"`);
await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_078db271ad52ccc345b7b2b026a"`);
await queryRunner.query(`ALTER TABLE "user" DROP CONSTRAINT "FK_16fef167e4253ccdc8971b01f6e"`);
await queryRunner.query(`ALTER TABLE "notification" DROP COLUMN "moveTargetId"`);
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "movedToId"`);
}
}

View file

@ -0,0 +1,22 @@
export class unifyDriveObjects1679767920029 {
name = 'unifyDriveObjects1679767920029';
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "drive_file" RENAME COLUMN "folderId" TO "parentId"`);
await queryRunner.query(`ALTER TABLE "drive_folder" ALTER COLUMN "name" TYPE character varying(256)`);
// The column name changed so the name that typeorm generates for indices and foreign keys changes too.
// To avoid reindexing, just rename them.
await queryRunner.query(`ALTER TABLE "drive_file" RENAME CONSTRAINT "FK_bb90d1956dafc4068c28aa7560a" TO "FK_84b4e3038e7e64a68764dd7ea3e"`);
await queryRunner.query(`ALTER INDEX "IDX_bb90d1956dafc4068c28aa7560" RENAME TO "IDX_84b4e3038e7e64a68764dd7ea3"`);
await queryRunner.query(`ALTER INDEX "IDX_55720b33a61a7c806a8215b825" RENAME TO "IDX_7c607687cd487292d16617b23e"`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "drive_file" RENAME CONSTRAINT "FK_84b4e3038e7e64a68764dd7ea3e" TO "FK_bb90d1956dafc4068c28aa7560a"`);
await queryRunner.query(`ALTER INDEX "IDX_84b4e3038e7e64a68764dd7ea3" RENAME TO "IDX_bb90d1956dafc4068c28aa7560"`);
await queryRunner.query(`ALTER INDEX "IDX_7c607687cd487292d16617b23e" RENAME TO "IDX_55720b33a61a7c806a8215b825"`);
await queryRunner.query(`ALTER TABLE "drive_folder" ALTER COLUMN "name" TYPE character varying(128) USING substr("name", 1, 128)`);
await queryRunner.query(`ALTER TABLE "drive_file" RENAME COLUMN "parentId" TO "folderId"`);
}
}

View file

@ -69,7 +69,7 @@
"koa-views": "7.0.2", "koa-views": "7.0.2",
"mfm-js": "0.22.1", "mfm-js": "0.22.1",
"mime-types": "2.1.35", "mime-types": "2.1.35",
"mocha": "10.0.0", "mocha": "10.2.0",
"multer": "1.4.5-lts.1", "multer": "1.4.5-lts.1",
"nested-property": "4.0.0", "nested-property": "4.0.0",
"node-fetch": "3.2.6", "node-fetch": "3.2.6",
@ -99,7 +99,7 @@
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0", "stringz": "2.1.0",
"style-loader": "3.3.1", "style-loader": "3.3.1",
"summaly": "2.6.0", "summaly": "2.7.0",
"syslog-pro": "1.0.0", "syslog-pro": "1.0.0",
"systeminformation": "5.11.22", "systeminformation": "5.11.22",
"tinycolor2": "1.4.2", "tinycolor2": "1.4.2",

View file

@ -160,12 +160,24 @@ function spawnWorker(mode: 'web' | 'queue'): Promise<void> {
return new Promise(res => { return new Promise(res => {
const worker = cluster.fork({ mode }); const worker = cluster.fork({ mode });
worker.on('message', message => { worker.on('message', message => {
if (message === 'listenFailed') { switch (message) {
bootLogger.error('The server Listen failed due to the previous error.'); case 'listenFailed':
process.exit(1); bootLogger.error('The server Listen failed due to the previous error.');
process.exit(1);
break;
case 'ready':
res();
break;
case 'metaUpdate':
// forward new instance metadata to all workers
for (const otherWorker of Object.values(cluster.workers)) {
// don't forward the message to the worker that sent it
if (worker.id === otherWorker.id) continue;
otherWorker.send(message);
}
break;
} }
if (message !== 'ready') return;
res();
}); });
}); });
} }

View file

@ -4,6 +4,7 @@ import config from '@/config/index.js';
import { UserProfiles } from '@/models/index.js'; import { UserProfiles } from '@/models/index.js';
import { extractMentions } from '@/misc/extract-mentions.js'; import { extractMentions } from '@/misc/extract-mentions.js';
import { intersperse } from '@/prelude/array.js'; import { intersperse } from '@/prelude/array.js';
import { toPunyNullable } from '@/misc/convert-host.js';
// Transforms MFM to HTML, given the MFM text and a list of user IDs that are // Transforms MFM to HTML, given the MFM text and a list of user IDs that are
// mentioned in the text. If the list of mentions is not given, all mentions // mentioned in the text. If the list of mentions is not given, all mentions
@ -14,6 +15,19 @@ export async function toHtml(mfmText: string, mentions?: string[]): Promise<stri
return null; return null;
} }
let mentionedUsers = [];
const ids = mentions ?? extractMentions(nodes);
if (ids.length > 0) {
mentionedUsers = await UserProfiles.createQueryBuilder('user_profile')
.leftJoin('user_profile.user', 'user')
.select('user.usernameLower', 'username')
.addSelect('user.host', 'host')
// links should preferably use user friendly urls, only fall back to AP ids
.addSelect('COALESCE(user_profile.url, user.uri)', 'url')
.where('"userId" IN (:...ids)', { ids })
.getRawMany();
}
const doc = new JSDOM('').window.document; const doc = new JSDOM('').window.document;
const handlers: { [K in mfm.MfmNode['type']]: (node: mfm.NodeType<K>) => Promise<Node> } = { const handlers: { [K in mfm.MfmNode['type']]: (node: mfm.NodeType<K>) => Promise<Node> } = {
@ -103,30 +117,28 @@ export async function toHtml(mfmText: string, mentions?: string[]): Promise<stri
}, },
async mention(node): Promise<HTMLElement | Text> { async mention(node): Promise<HTMLElement | Text> {
const { username, host, acct } = node.props; let { username, host, acct } = node.props;
const ids = mentions ?? extractMentions(nodes); // normalize username and host for searching the user
if (ids.length > 0) { username = username.toLowerCase();
const mentionedUsers = await UserProfiles.createQueryBuilder('user_profile') host = toPunyNullable(host);
.leftJoin('user_profile.user', 'user') // Discard host if it is the local host. Otherwise mentions of local users where the
.select('user.username', 'username') // hostname is not omitted are not handled correctly.
.addSelect('user.host', 'host') if (host == config.hostname) {
// links should preferably use user friendly urls, only fall back to AP ids host = null;
.addSelect('COALESCE(user_profile.url, user.uri)', 'url') }
.where('"userId" IN (:...ids)', { ids }) const userInfo = mentionedUsers.find(user => user.username === username && user.host === host);
.getRawMany(); if (userInfo != null) {
const userInfo = mentionedUsers.find(user => user.username === username && user.host === host); // Mastodon microformat: span.h-card > a.u-url.mention
if (userInfo != null) { const a = doc.createElement('a');
// Mastodon microformat: span.h-card > a.u-url.mention // The fallback will only be used for local users, so the host part can be discarded.
const a = doc.createElement('a'); a.href = userInfo.url ?? `${config.url}/@${username}`;
a.href = userInfo.url ?? `${config.url}/${acct}`; a.className = 'u-url mention';
a.className = 'u-url mention'; a.textContent = acct;
a.textContent = acct;
const card = doc.createElement('span'); const card = doc.createElement('span');
card.className = 'h-card'; card.className = 'h-card';
card.appendChild(a); card.appendChild(a);
return card; return card;
}
} }
// this user does not actually exist // this user does not actually exist
return doc.createTextNode(acct); return doc.createTextNode(acct);

View file

@ -1,3 +1,4 @@
import process from 'node:process';
import push from 'web-push'; import push from 'web-push';
import { db } from '@/db/postgre.js'; import { db } from '@/db/postgre.js';
import { Meta } from '@/models/entities/meta.js'; import { Meta } from '@/models/entities/meta.js';
@ -17,9 +18,20 @@ export async function setMeta(meta: Meta): Promise<void> {
cache = meta; cache = meta;
/*
The meta is not included here because another process may have updated
the content before the other process receives it.
*/
process.send!('metaUpdated');
unlock(); unlock();
} }
// the primary will forward this message
process.on('message', async message => {
if (message === 'metaUpdated') await getMeta();
});
/** /**
* Performs the primitive database operation to fetch server configuration. * Performs the primitive database operation to fetch server configuration.
* If there is no entry yet, inserts a new one. * If there is no entry yet, inserts a new one.

View file

@ -4,7 +4,7 @@ import { User } from './user.js';
import { DriveFolder } from './drive-folder.js'; import { DriveFolder } from './drive-folder.js';
@Entity() @Entity()
@Index(['userId', 'folderId', 'id']) @Index(['userId', 'parentId', 'id'])
export class DriveFile { export class DriveFile {
@PrimaryColumn(id()) @PrimaryColumn(id())
public id: string; public id: string;
@ -142,13 +142,13 @@ export class DriveFile {
nullable: true, nullable: true,
comment: 'The parent folder ID. If null, it means the DriveFile is located in root.', comment: 'The parent folder ID. If null, it means the DriveFile is located in root.',
}) })
public folderId: DriveFolder['id'] | null; public parentId: DriveFolder['id'] | null;
@ManyToOne(() => DriveFolder, { @ManyToOne(() => DriveFolder, {
onDelete: 'SET NULL', onDelete: 'SET NULL',
}) })
@JoinColumn() @JoinColumn()
public folder: DriveFolder | null; public parent: DriveFolder | null;
@Index() @Index()
@Column('boolean', { @Column('boolean', {

View file

@ -14,7 +14,7 @@ export class DriveFolder {
public createdAt: Date; public createdAt: Date;
@Column('varchar', { @Column('varchar', {
length: 128, length: 256,
comment: 'The name of the DriveFolder.', comment: 'The name of the DriveFolder.',
}) })
public name: string; public name: string;

View file

@ -52,19 +52,20 @@ export class Notification {
public notifier: User | null; public notifier: User | null;
/** /**
* * Type of notification.
* follow - * follow - notifier followed notifiee
* mention - 稿 * mention - notifiee was mentioned
* reply - (Watchしている)稿 * reply - notifiee (author or watching) was replied to
* renote - (Watchしている)稿Renoteされた * renote - notifiee (author or watching) was renoted
* quote - (Watchしている)稿Renoteされた * quote - notifiee (author or watching) was quoted
* reaction - (Watchしている)稿 * reaction - notifiee (author or watching) had a reaction added to the note
* pollVote - (Watchしている)稿 * pollVote - new vote in a poll notifiee authored or watched
* pollEnded - * pollEnded - notifiee's poll ended
* receiveFollowRequest - * receiveFollowRequest - notifiee received a new follow request
* followRequestAccepted - * followRequestAccepted - notifier accepted notifees follow request
* groupInvited - * groupInvited - notifiee was invited into a group
* app - * move - notifier moved
* app - custom application notification
*/ */
@Index() @Index()
@Column('enum', { @Column('enum', {
@ -129,6 +130,19 @@ export class Notification {
}) })
public choice: number | null; public choice: number | null;
@Column({
...id(),
nullable: true,
comment: 'The ID of the moved to account.',
})
public moveTargetId: User['id'] | null;
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
public moveTarget: User | null;
/** /**
* body * body
*/ */

View file

@ -1,4 +1,4 @@
import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm'; import { Entity, Column, Index, OneToOne, ManyToOne, JoinColumn, PrimaryColumn } from 'typeorm';
import { id } from '../id.js'; import { id } from '../id.js';
import { DriveFile } from './drive-file.js'; import { DriveFile } from './drive-file.js';
@ -230,6 +230,18 @@ export class User {
}) })
public federateBlocks: boolean; public federateBlocks: boolean;
@Column({
...id(),
nullable: true,
})
public movedToId: User['id'] | null;
@ManyToOne(() => User, {
onDelete: 'SET NULL'
})
@JoinColumn()
public movedTo: User | null;
constructor(data: Partial<User>) { constructor(data: Partial<User>) {
if (data == null) return; if (data == null) return;

View file

@ -105,8 +105,8 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({
url: opts.self ? file.url : this.getPublicUrl(file, false), url: opts.self ? file.url : this.getPublicUrl(file, false),
thumbnailUrl: this.getPublicUrl(file, true), thumbnailUrl: this.getPublicUrl(file, true),
comment: file.comment, comment: file.comment,
folderId: file.folderId, folderId: file.parentId,
folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, { folder: opts.detail && file.parentId ? DriveFolders.pack(file.parentId, {
detail: true, detail: true,
}) : undefined, }) : undefined,
userId: file.userId, userId: file.userId,

View file

@ -28,7 +28,7 @@ export const DriveFolderRepository = db.getRepository(DriveFolder).extend({
parentId: folder.id, parentId: folder.id,
}), }),
filesCount: DriveFiles.countBy({ filesCount: DriveFiles.countBy({
folderId: folder.id, parentId: folder.id,
}), }),
...(folder.parentId ? { ...(folder.parentId ? {

View file

@ -44,6 +44,9 @@ export const NotificationRepository = db.getRepository(Notification).extend({
...(notification.type === 'groupInvited' ? { ...(notification.type === 'groupInvited' ? {
invitation: UserGroupInvitations.pack(notification.userGroupInvitationId!), invitation: UserGroupInvitations.pack(notification.userGroupInvitationId!),
} : {}), } : {}),
...(notification.type === 'move' ? {
moveTarget: Users.pack(notification.moveTarget ?? notification.moveTargetId),
} : {}),
...(notification.type === 'app' ? { ...(notification.type === 'app' ? {
body: notification.customBody, body: notification.customBody,
header: notification.customHeader || token?.name, header: notification.customHeader || token?.name,

View file

@ -26,6 +26,8 @@ type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends bo
const ajv = new Ajv(); const ajv = new Ajv();
// It is important that localUsernameSchema does not allow any usernames
// containing dots because those are used for system actors.
const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const; const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const;
const passwordSchema = { type: 'string', minLength: 1 } as const; const passwordSchema = { type: 'string', minLength: 1 } as const;
const nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const; const nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
@ -300,6 +302,10 @@ export const UserRepository = db.getRepository(User).extend({
}), }),
emojis: populateEmojis(user.emojis, user.host), emojis: populateEmojis(user.emojis, user.host),
onlineStatus: this.getOnlineStatus(user), onlineStatus: this.getOnlineStatus(user),
movedTo: !user.movedToId ? undefined : this.pack(user.movedTo ?? user.movedToId, me, {
...opts,
detail: false,
}),
...(opts.detail ? { ...(opts.detail ? {
url: profile!.url, url: profile!.url,

View file

@ -29,18 +29,18 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
logger.debug(JSON.stringify(info, null, 2)); logger.debug(JSON.stringify(info, null, 2));
//#endregion //#endregion
const host = toPuny(new URL(signature.keyId).hostname); const keyIdLower = signature.keyId.toLowerCase();
if (keyIdLower.startsWith('acct:')) {
return `Old keyId is no longer supported. ${keyIdLower}`;
}
const host = toPuny(new URL(keyIdLower).hostname);
// Stop if the host is blocked. // Stop if the host is blocked.
if (await shouldBlockInstance(host)) { if (await shouldBlockInstance(host)) {
return `Blocked request: ${host}`; return `Blocked request: ${host}`;
} }
const keyIdLower = signature.keyId.toLowerCase();
if (keyIdLower.startsWith('acct:')) {
return `Old keyId is no longer supported. ${keyIdLower}`;
}
const resolver = new Resolver(); const resolver = new Resolver();
let authUser; let authUser;
@ -107,9 +107,14 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
} }
} }
// Verify that the actor's host is not blocked
const signerHost = extractDbHost(authUser.user.uri!);
if (await shouldBlockInstance(signerHost)) {
return `Blocked request: ${signerHost}`;
}
if (typeof activity.id === 'string') { if (typeof activity.id === 'string') {
// Verify that activity and actor are from the same host. // Verify that activity and actor are from the same host.
const signerHost = extractDbHost(authUser.user.uri!);
const activityIdHost = extractDbHost(activity.id); const activityIdHost = extractDbHost(activity.id);
if (signerHost !== activityIdHost) { if (signerHost !== activityIdHost) {
return `skip: signerHost(${signerHost}) !== activity.id host(${activityIdHost}`; return `skip: signerHost(${signerHost}) !== activity.id host(${activityIdHost}`;
@ -136,7 +141,7 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
federationChart.inbox(i.host); federationChart.inbox(i.host);
}); });
// アクティビティを処理 // process the activity
await perform(authUser.user, activity, resolver); await perform(authUser.user, activity, resolver);
return 'ok'; return 'ok';
}; };

View file

@ -4,7 +4,7 @@ import { Resolver } from '@/remote/activitypub/resolver.js';
import { extractDbHost } from '@/misc/convert-host.js'; import { extractDbHost } from '@/misc/convert-host.js';
import { shouldBlockInstance } from '@/misc/should-block-instance.js'; import { shouldBlockInstance } from '@/misc/should-block-instance.js';
import { apLogger } from '../logger.js'; import { apLogger } from '../logger.js';
import { IObject, isCreate, isDelete, isUpdate, isRead, isFollow, isAccept, isReject, isAdd, isRemove, isAnnounce, isLike, isUndo, isBlock, isCollectionOrOrderedCollection, isCollection, isFlag, getApId } from '../type.js'; import { IObject, isCreate, isDelete, isUpdate, isRead, isFollow, isAccept, isReject, isAdd, isRemove, isAnnounce, isLike, isUndo, isBlock, isCollectionOrOrderedCollection, isCollection, isFlag, isMove, getApId } from '../type.js';
import create from './create/index.js'; import create from './create/index.js';
import performDeleteActivity from './delete/index.js'; import performDeleteActivity from './delete/index.js';
import performUpdateActivity from './update/index.js'; import performUpdateActivity from './update/index.js';
@ -19,6 +19,7 @@ import add from './add/index.js';
import remove from './remove/index.js'; import remove from './remove/index.js';
import block from './block/index.js'; import block from './block/index.js';
import flag from './flag/index.js'; import flag from './flag/index.js';
import { move } from './move/index.js';
export async function performActivity(actor: CacheableRemoteUser, activity: IObject, resolver: Resolver): Promise<void> { export async function performActivity(actor: CacheableRemoteUser, activity: IObject, resolver: Resolver): Promise<void> {
if (isCollectionOrOrderedCollection(activity)) { if (isCollectionOrOrderedCollection(activity)) {
@ -73,6 +74,8 @@ async function performOneActivity(actor: CacheableRemoteUser, activity: IObject,
await block(actor, activity); await block(actor, activity);
} else if (isFlag(activity)) { } else if (isFlag(activity)) {
await flag(actor, activity); await flag(actor, activity);
} else if (isMove(activity)) {
await move(actor, activity, resolver);
} else { } else {
apLogger.warn(`unrecognized activity type: ${(activity as any).type}`); apLogger.warn(`unrecognized activity type: ${(activity as any).type}`);
} }

View file

@ -0,0 +1,62 @@
import { IsNull } from 'typeorm';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { resolvePerson } from '@/remote/activitypub/models/person.js';
import { Followings, Users } from '@/models/index.js';
import { createNotification } from '@/services/create-notification.js';
import Resolver from '../../resolver.js';
import { IMove, isActor, getApId } from '../../type.js';
export async function move(actor: CacheableRemoteUser, activity: IMove, resolver: Resolver): Promise<void> {
// actor is not move origin
if (activity.object == null || getApId(activity.object) !== actor.uri) return;
// actor already moved
if (actor.movedTo != null) return;
// no move target
if (activity.target == null) return;
/* the database resolver can not be used here, because:
* 1. It must be ensured that the latest data is used.
* 2. The AP representation is needed, because `alsoKnownAs`
* is not stored in the database.
* This also checks whether the move target is blocked
*/
const movedToAp = await resolver.resolve(getApId(activity.target));
// move target is not an actor
if (!isActor(movedToAp)) return;
// move destination has not accepted
if (!Array.isArray(movedToAp.alsoKnownAs) || !movedToAp.alsoKnownAs.includes(actor.id)) return;
// ensure the user exists
const movedTo = await resolvePerson(getApId(activity.target), resolver, movedToAp);
// move target is already suspended
if (movedTo.isSuspended) return;
// process move for local followers
const followings = Followings.find({
select: {
followerId: true,
},
where: {
followeeId: actor.id,
followerHost: IsNull(),
},
});
await Promise.all([
Users.update(actor.id, {
movedToId: movedTo.id,
}),
...followings.map(async (following) => {
// TODO: autoAcceptMove?
await createNotification(following.followerId, 'move', {
notifierId: actor.id,
moveTargetId: movedTo.id,
});
}),
]);
}

View file

@ -39,7 +39,7 @@ const summaryLength = 2048;
* @param x Fetched object * @param x Fetched object
* @param uri Fetch target URI * @param uri Fetch target URI
*/ */
function validateActor(x: IObject): IActor { async function validateActor(x: IObject, resolver: Resolver): Promise<IActor> {
if (x == null) { if (x == null) {
throw new Error('invalid Actor: object is null'); throw new Error('invalid Actor: object is null');
} }
@ -61,6 +61,22 @@ function validateActor(x: IObject): IActor {
throw new StatusError('cannot resolve local user', 400, 'cannot resolve local user'); throw new StatusError('cannot resolve local user', 400, 'cannot resolve local user');
} }
if (x.movedTo !== undefined) {
if (!(typeof x.movedTo === 'string' && x.movedTo.length > 0)) {
throw new Error('invalid Actor: wrong movedTo');
}
if (x.movedTo === uri) {
throw new Error('invalid Actor: moved to self');
}
// This may throw an exception if we cannot resolve the move target.
// If we are processing an incoming activity, this is desired behaviour
// because that will cause the activity to be retried.
await resolvePerson(x.movedTo, resolver)
.then(moveTarget => {
x.movedTo = moveTarget.id
});
}
if (!(typeof x.inbox === 'string' && x.inbox.length > 0)) { if (!(typeof x.inbox === 'string' && x.inbox.length > 0)) {
throw new Error('invalid Actor: wrong inbox'); throw new Error('invalid Actor: wrong inbox');
} }
@ -137,7 +153,7 @@ export async function fetchPerson(uri: string): Promise<CacheableUser | null> {
export async function createPerson(value: string | IObject, resolver: Resolver): Promise<User> { export async function createPerson(value: string | IObject, resolver: Resolver): Promise<User> {
const object = await resolver.resolve(value) as any; const object = await resolver.resolve(value) as any;
const person = validateActor(object); const person = await validateActor(object, resolver);
apLogger.info(`Creating the Person: ${person.id}`); apLogger.info(`Creating the Person: ${person.id}`);
@ -177,6 +193,7 @@ export async function createPerson(value: string | IObject, resolver: Resolver):
isBot, isBot,
isCat: (person as any).isCat === true, isCat: (person as any).isCat === true,
showTimelineReplies: false, showTimelineReplies: false,
movedToId: person.movedTo,
})) as IRemoteUser; })) as IRemoteUser;
await transactionalEntityManager.save(new UserProfile({ await transactionalEntityManager.save(new UserProfile({
@ -287,7 +304,7 @@ export async function updatePerson(value: IObject | string, resolver: Resolver):
const object = await resolver.resolve(value); const object = await resolver.resolve(value);
const person = validateActor(object); const person = await validateActor(object, resolver);
apLogger.info(`Updating the Person: ${person.id}`); apLogger.info(`Updating the Person: ${person.id}`);
@ -328,6 +345,7 @@ export async function updatePerson(value: IObject | string, resolver: Resolver):
isCat: (person as any).isCat === true, isCat: (person as any).isCat === true,
isLocked: !!person.manuallyApprovesFollowers, isLocked: !!person.manuallyApprovesFollowers,
isExplorable: !!person.discoverable, isExplorable: !!person.discoverable,
movedToId: person.movedTo,
} as Partial<User>; } as Partial<User>;
if (avatar) { if (avatar) {
@ -376,7 +394,7 @@ export async function updatePerson(value: IObject | string, resolver: Resolver):
* If the target Person is registered in FoundKey, return it; otherwise, fetch it from a remote server and return it. * If the target Person is registered in FoundKey, return it; otherwise, fetch it from a remote server and return it.
* Fetch the person from the remote server, register it in FoundKey, and return it. * Fetch the person from the remote server, register it in FoundKey, and return it.
*/ */
export async function resolvePerson(uri: string, resolver: Resolver): Promise<CacheableUser> { export async function resolvePerson(uri: string, resolver: Resolver, hint?: IObject): Promise<CacheableUser> {
if (typeof uri !== 'string') throw new Error('uri is not string'); if (typeof uri !== 'string') throw new Error('uri is not string');
//#region このサーバーに既に登録されていたらそれを返す //#region このサーバーに既に登録されていたらそれを返す
@ -388,7 +406,7 @@ export async function resolvePerson(uri: string, resolver: Resolver): Promise<Ca
//#endregion //#endregion
// リモートサーバーからフェッチしてきて登録 // リモートサーバーからフェッチしてきて登録
return await createPerson(uri, resolver); return await createPerson(hint ?? uri, resolver);
} }
export function analyzeAttachments(attachments: IObject | IObject[] | undefined) { export function analyzeAttachments(attachments: IObject | IObject[] | undefined) {

View file

@ -13,8 +13,10 @@ export default (object: any, note: Note) => {
} else if (note.visibility === 'home') { } else if (note.visibility === 'home') {
to = [`${attributedTo}/followers`]; to = [`${attributedTo}/followers`];
cc = ['https://www.w3.org/ns/activitystreams#Public']; cc = ['https://www.w3.org/ns/activitystreams#Public'];
} else if (note.visibility === 'followers') {
to = [`${attributedTo}/followers`];
} else { } else {
return null; throw new Error('Invalid visibility for pure renote.');
} }
return { return {

View file

@ -296,6 +296,10 @@ export interface IFlag extends IActivity {
type: 'Flag'; type: 'Flag';
} }
export interface IMove extends IActivity {
type: 'Move';
}
export const isCreate = (object: IObject): object is ICreate => getApType(object) === 'Create'; export const isCreate = (object: IObject): object is ICreate => getApType(object) === 'Create';
export const isDelete = (object: IObject): object is IDelete => getApType(object) === 'Delete'; export const isDelete = (object: IObject): object is IDelete => getApType(object) === 'Delete';
export const isUpdate = (object: IObject): object is IUpdate => getApType(object) === 'Update'; export const isUpdate = (object: IObject): object is IUpdate => getApType(object) === 'Update';
@ -310,6 +314,7 @@ export const isLike = (object: IObject): object is ILike => getApType(object) ==
export const isAnnounce = (object: IObject): object is IAnnounce => getApType(object) === 'Announce'; export const isAnnounce = (object: IObject): object is IAnnounce => getApType(object) === 'Announce';
export const isBlock = (object: IObject): object is IBlock => getApType(object) === 'Block'; export const isBlock = (object: IObject): object is IBlock => getApType(object) === 'Block';
export const isFlag = (object: IObject): object is IFlag => getApType(object) === 'Flag'; export const isFlag = (object: IObject): object is IFlag => getApType(object) === 'Flag';
export const isMove = (object: IObject): object is IMove => getApType(object) === 'Move';
export interface ILink { export interface ILink {
href: string; href: string;

View file

@ -28,7 +28,7 @@ function inbox(ctx: Router.RouterContext): void {
let signature; let signature;
try { try {
signature = httpSignature.parseRequest(ctx.req, { 'headers': [] }); signature = httpSignature.parseRequest(ctx.req);
} catch (e) { } catch (e) {
ctx.status = 401; ctx.status = 401;
return; return;

View file

@ -1,9 +1,11 @@
import { IsNull, Not } from 'typeorm';
import { IdentifiableError } from '@/misc/identifiable-error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js';
import { User } from '@/models/entities/user.js'; import { User } from '@/models/entities/user.js';
import { Note } from '@/models/entities/note.js'; import { Note } from '@/models/entities/note.js';
import { Notes, Users } from '@/models/index.js'; import { Notes, Users } from '@/models/index.js';
import { apiLogger } from '@/server/api/logger.js'; import { apiLogger } from '@/server/api/logger.js';
import { visibilityQuery } from './generate-visibility-query.js'; import { visibilityQuery } from './generate-visibility-query.js';
import { ApiError } from '@/server/api/error.js';
/** /**
* Get note for API processing, taking into account visibility. * Get note for API processing, taking into account visibility.
@ -27,11 +29,15 @@ export async function getNote(noteId: Note['id'], me: { id: User['id'] } | null)
/** /**
* Get user for API processing * Get user for API processing
*/ */
export async function getUser(userId: User['id']) { export async function getUser(userId: User['id'], includeSuspended = false) {
const user = await Users.findOneBy({ id: userId }); const user = await Users.findOneBy({
id: userId,
isDeleted: false,
...(includeSuspended ? {} : {isSuspended: false}),
});
if (user == null) { if (user == null) {
throw new IdentifiableError('15348ddd-432d-49c2-8a5a-8069753becff', 'No such user.'); throw new ApiError('NO_SUCH_USER');
} }
return user; return user;
@ -40,11 +46,16 @@ export async function getUser(userId: User['id']) {
/** /**
* Get remote user for API processing * Get remote user for API processing
*/ */
export async function getRemoteUser(userId: User['id']) { export async function getRemoteUser(userId: User['id'], includeSuspended = false) {
const user = await getUser(userId); const user = await Users.findOneBy({
id: userId,
host: Not(IsNull()),
isDeleted: false,
...(includeSuspended ? {} : {isSuspended: false}),
});
if (!Users.isRemoteUser(user)) { if (user == null) {
throw new Error('user is not a remote user'); throw new ApiError('NO_SUCH_USER');
} }
return user; return user;
@ -53,11 +64,16 @@ export async function getRemoteUser(userId: User['id']) {
/** /**
* Get local user for API processing * Get local user for API processing
*/ */
export async function getLocalUser(userId: User['id']) { export async function getLocalUser(userId: User['id'], includeSuspended = false) {
const user = await getUser(userId); const user = await Users.findOneBy({
id: userId,
host: IsNull(),
isDeleted: false,
...(includeSuspended ? {} : {isSuspended: false}),
});
if (!Users.isLocalUser(user)) { if (user == null) {
throw new Error('user is not a local user'); throw new ApiError('NO_SUCH_USER');
} }
return user; return user;

View file

@ -2,6 +2,7 @@ import { Users } from '@/models/index.js';
import { ApiError } from '@/server/api/error.js'; import { ApiError } from '@/server/api/error.js';
import { deleteAccount } from '@/services/delete-account.js'; import { deleteAccount } from '@/services/delete-account.js';
import define from '@/server/api/define.js'; import define from '@/server/api/define.js';
import { getUser } from '@/server/api/common/getters.js';
export const meta = { export const meta = {
tags: ['admin'], tags: ['admin'],
@ -22,14 +23,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps) => { export default define(meta, paramDef, async (ps) => {
const user = await Users.findOneBy({ const user = await getUser(ps.userId, true);
id: ps.userId,
isDeleted: false,
});
if (user == null) { if (user.isAdmin) {
throw new ApiError('NO_SUCH_USER');
} else if (user.isAdmin) {
throw new ApiError('IS_ADMIN'); throw new ApiError('IS_ADMIN');
} else if (user.isModerator) { } else if (user.isModerator) {
throw new ApiError('IS_MODERATOR'); throw new ApiError('IS_MODERATOR');

View file

@ -3,6 +3,7 @@ import { secureRndstr } from '@/misc/secure-rndstr.js';
import { Users, UserProfiles } from '@/models/index.js'; import { Users, UserProfiles } from '@/models/index.js';
import { ApiError } from '@/server/api/error.js'; import { ApiError } from '@/server/api/error.js';
import define from '@/server/api/define.js'; import define from '@/server/api/define.js';
import { getLocalUser } from '@/server/api/common/getters.js';
export const meta = { export const meta = {
tags: ['admin'], tags: ['admin'],
@ -34,11 +35,7 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps) => { export default define(meta, paramDef, async (ps) => {
const user = await Users.findOneBy({ id: ps.userId }); const user = await getLocalUser(ps.userId);
if (user == null) {
throw new ApiError('NO_SUCH_USER');
}
if (user.isAdmin) { if (user.isAdmin) {
throw new ApiError('IS_ADMIN'); throw new ApiError('IS_ADMIN');

View file

@ -1,6 +1,7 @@
import { Signins, UserProfiles, Users } from '@/models/index.js'; import { Signins, UserProfiles, Users } from '@/models/index.js';
import { ApiError } from '@/server/api/error.js'; import { ApiError } from '@/server/api/error.js';
import define from '@/server/api/define.js'; import define from '@/server/api/define.js';
import { getUser } from '@/server/api/common/getters.js';
export const meta = { export const meta = {
tags: ['admin'], tags: ['admin'],
@ -27,7 +28,7 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, me) => { export default define(meta, paramDef, async (ps, me) => {
const [user, profile] = await Promise.all([ const [user, profile] = await Promise.all([
Users.findOneBy({ id: ps.userId }), getUser(ps.userId, true),
UserProfiles.findOneBy({ userId: ps.userId }), UserProfiles.findOneBy({ userId: ps.userId }),
]); ]);

View file

@ -3,6 +3,7 @@ import { ApiError } from '@/server/api/error.js';
import { insertModerationLog } from '@/services/insert-moderation-log.js'; import { insertModerationLog } from '@/services/insert-moderation-log.js';
import { publishInternalEvent } from '@/services/stream.js'; import { publishInternalEvent } from '@/services/stream.js';
import define from '@/server/api/define.js'; import define from '@/server/api/define.js';
import { getUser } from '@/server/api/common/getters.js';
export const meta = { export const meta = {
tags: ['admin'], tags: ['admin'],
@ -23,11 +24,7 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, me) => { export default define(meta, paramDef, async (ps, me) => {
const user = await Users.findOneBy({ id: ps.userId }); const user = await getUser(ps.userId);
if (user == null) {
throw new ApiError('NO_SUCH_USER');
}
if (user.isAdmin) { if (user.isAdmin) {
throw new ApiError('IS_ADMIN'); throw new ApiError('IS_ADMIN');

View file

@ -42,10 +42,7 @@ export default define(meta, paramDef, async (ps, user) => {
if (user.id === ps.userId) throw new ApiError('BLOCKEE_IS_YOURSELF'); if (user.id === ps.userId) throw new ApiError('BLOCKEE_IS_YOURSELF');
// Get blockee // Get blockee
const blockee = await getUser(ps.userId).catch(e => { const blockee = await getUser(ps.userId);
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
throw e;
});
// Check if already blocking // Check if already blocking
const blocked = await Blockings.countBy({ const blocked = await Blockings.countBy({

View file

@ -42,10 +42,7 @@ export default define(meta, paramDef, async (ps, user) => {
const blocker = await Users.findOneByOrFail({ id: user.id }); const blocker = await Users.findOneByOrFail({ id: user.id });
// Get blockee // Get blockee
const blockee = await getUser(ps.userId).catch(e => { const blockee = await getUser(ps.userId);
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
throw e;
});
// Check not blocking // Check not blocking
const exist = await Blockings.countBy({ const exist = await Blockings.countBy({

View file

@ -38,9 +38,9 @@ export default define(meta, paramDef, async (ps, user) => {
.andWhere('file.userId = :userId', { userId: user.id }); .andWhere('file.userId = :userId', { userId: user.id });
if (ps.folderId) { if (ps.folderId) {
query.andWhere('file.folderId = :folderId', { folderId: ps.folderId }); query.andWhere('file.parentId = :parentId', { parentId: ps.folderId });
} else { } else {
query.andWhere('file.folderId IS NULL'); query.andWhere('file.parentId IS NULL');
} }
if (ps.type) { if (ps.type) {

View file

@ -1,6 +1,7 @@
import { DriveFiles, Notes } from '@/models/index.js'; import { DriveFiles, Notes } from '@/models/index.js';
import define from '@/server/api/define.js'; import define from '@/server/api/define.js';
import { ApiError } from '@/server/api/error.js'; import { ApiError } from '@/server/api/error.js';
import { makePaginationQuery } from '@/server/api/common/make-pagination-query.js';
export const meta = { export const meta = {
tags: ['drive', 'notes'], tags: ['drive', 'notes'],
@ -28,6 +29,9 @@ export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
fileId: { type: 'string', format: 'misskey:id' }, fileId: { type: 'string', format: 'misskey:id' },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
}, },
required: ['fileId'], required: ['fileId'],
} as const; } as const;
@ -42,8 +46,13 @@ export default define(meta, paramDef, async (ps, user) => {
if (file == null) throw new ApiError('NO_SUCH_FILE'); if (file == null) throw new ApiError('NO_SUCH_FILE');
const notes = await Notes.createQueryBuilder('note') const notes = await makePaginationQuery(
.where(':file = ANY(note.fileIds)', { file: file.id }) Notes.createQueryBuilder('note'),
ps.sinceId,
ps.untilId,
)
.andWhere(':file = ANY(note.fileIds)', { file: file.id })
.take(ps.limit)
.getMany(); .getMany();
return await Notes.packMany(notes, user, { return await Notes.packMany(notes, user, {

View file

@ -62,7 +62,7 @@ export default define(meta, paramDef, async (ps, user, _, file, cleanup) => {
try { try {
// Create file // Create file
const driveFile = await addFile({ user, path: file.path, name, comment: ps.comment, folderId: ps.folderId, force: ps.force, sensitive: ps.isSensitive }); const driveFile = await addFile({ user, path: file.path, name, comment: ps.comment, parentId: ps.folderId, force: ps.force, sensitive: ps.isSensitive });
return await DriveFiles.pack(driveFile, { self: true }); return await DriveFiles.pack(driveFile, { self: true });
} catch (e) { } catch (e) {
if (e instanceof Error || typeof e === 'string') { if (e instanceof Error || typeof e === 'string') {

View file

@ -36,7 +36,7 @@ export default define(meta, paramDef, async (ps, user) => {
const files = await DriveFiles.findBy({ const files = await DriveFiles.findBy({
name: ps.name, name: ps.name,
userId: user.id, userId: user.id,
folderId: ps.folderId ?? IsNull(), parentId: ps.folderId ?? IsNull(),
}); });
return await Promise.all(files.map(file => DriveFiles.pack(file, { self: true }))); return await Promise.all(files.map(file => DriveFiles.pack(file, { self: true })));

View file

@ -54,7 +54,7 @@ export default define(meta, paramDef, async (ps, user) => {
if (ps.folderId !== undefined) { if (ps.folderId !== undefined) {
if (ps.folderId === null) { if (ps.folderId === null) {
file.folderId = null; file.parentId = null;
} else { } else {
const folder = await DriveFolders.findOneBy({ const folder = await DriveFolders.findOneBy({
id: ps.folderId, id: ps.folderId,
@ -63,14 +63,14 @@ export default define(meta, paramDef, async (ps, user) => {
if (folder == null) throw new ApiError('NO_SUCH_FOLDER'); if (folder == null) throw new ApiError('NO_SUCH_FOLDER');
file.folderId = folder.id; file.parentId = folder.id;
} }
} }
await DriveFiles.update(file.id, { await DriveFiles.update(file.id, {
name: file.name, name: file.name,
comment: file.comment, comment: file.comment,
folderId: file.folderId, parentId: file.parentId,
isSensitive: file.isSensitive, isSensitive: file.isSensitive,
}); });

View file

@ -34,7 +34,7 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => { export default define(meta, paramDef, async (ps, user) => {
uploadFromUrl({ url: ps.url, user, folderId: ps.folderId, sensitive: ps.isSensitive, force: ps.force, comment: ps.comment }).then(file => { uploadFromUrl({ url: ps.url, user, parentId: ps.folderId, sensitive: ps.isSensitive, force: ps.force, comment: ps.comment }).then(file => {
DriveFiles.pack(file, { self: true }).then(packedFile => { DriveFiles.pack(file, { self: true }).then(packedFile => {
publishMainStream(user.id, 'urlUploadFinished', { publishMainStream(user.id, 'urlUploadFinished', {
marker: ps.marker, marker: ps.marker,

View file

@ -33,7 +33,7 @@ export default define(meta, paramDef, async (ps, user) => {
const [childFoldersCount, childFilesCount] = await Promise.all([ const [childFoldersCount, childFilesCount] = await Promise.all([
DriveFolders.countBy({ parentId: folder.id }), DriveFolders.countBy({ parentId: folder.id }),
DriveFiles.countBy({ folderId: folder.id }), DriveFiles.countBy({ parentId: folder.id }),
]); ]);
if (childFoldersCount !== 0 || childFilesCount !== 0) { if (childFoldersCount !== 0 || childFilesCount !== 0) {

View file

@ -1,11 +1,13 @@
import { In } from 'typeorm';
import { DriveFiles, DriveFolders } from '@/models/index.js'; import { DriveFiles, DriveFolders } from '@/models/index.js';
import define from '../../define.js'; import define from '../../define.js';
import { makePaginationQuery } from '../../common/make-pagination-query.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js';
import { db } from '@/db/postgre.js';
export const meta = { export const meta = {
tags: ['drive'], tags: ['drive'],
description: "Lists all folders and files in the authenticated user's drive. Folders are always listed first. The limit, if specified, is applied over the total number of elements.", description: "Lists all folders and files in the authenticated user's drive. Default sorting is folders first, then newest first. The limit, if specified, is applied over the total number of elements.",
requireCredential: true, requireCredential: true,
@ -31,40 +33,88 @@ export const meta = {
export const paramDef = { export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, limit: {
sinceId: { type: 'string', format: 'misskey:id' }, type: 'integer',
untilId: { type: 'string', format: 'misskey:id' }, minimum: 1,
folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, maximum: 100,
default: 30
},
offset: {
type: 'integer',
default: 0,
},
sort: {
type: 'string',
enum: [
'+createdAt',
'-createdAt',
'+name',
'-name',
],
},
folderId: {
type: 'string',
format: 'misskey:id',
nullable: true,
default: null
},
name: {
description: 'Filters the output for files and folders that contain the given string (case insensitive).',
type: 'string',
default: '',
},
}, },
required: [], required: [],
} as const; } as const;
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => { export default define(meta, paramDef, async (ps, user) => {
const foldersQuery = makePaginationQuery(DriveFolders.createQueryBuilder('folder'), ps.sinceId, ps.untilId) let orderBy = 'type ASC, id DESC';
.andWhere('folder.userId = :userId', { userId: user.id }); switch (ps.sort) {
const filesQuery = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId) case '+createdAt':
.andWhere('file.userId = :userId', { userId: user.id }); orderBy = '"createdAt" DESC';
break;
if (ps.folderId) { case '-createdAt':
foldersQuery.andWhere('folder.parentId = :parentId', { parentId: ps.folderId }); orderBy = '"createdAt" ASC';
filesQuery.andWhere('file.folderId = :folderId', { folderId: ps.folderId }); break;
} else { case '+name':
foldersQuery.andWhere('folder.parentId IS NULL'); orderBy = 'name DESC';
filesQuery.andWhere('file.folderId IS NULL'); break;
case '-name':
orderBy = 'name ASC';
break;
} }
const folders = await foldersQuery.take(ps.limit).getMany(); // due to the way AID is constructed, we can be sure that the IDs are not duplicated across tables.
const ids = await db.query(
'SELECT id FROM (SELECT id, "userId", "parentId", "createdAt", name, 0 AS type FROM drive_folder'
+ ' UNION SELECT id, "userId", "parentId", "createdAt", name, 1 AS type FROM drive_file) AS x'
+ ' WHERE "userId" = $1 AND name ILIKE $2 AND "parentId"'
+ (ps.folderId ? '= $5' : 'IS NULL')
+ ' ORDER BY ' + orderBy
+ ' LIMIT $3 OFFSET $4',
[user.id, '%' + ps.name + '%', ps.limit, ps.offset, ...(ps.folderId ? [ps.folderId] : [])]
).then(items => items.map(({ id }) => id));
const [files, ...packedFolders] = await Promise.all([ const [folders, files] = await Promise.all([
filesQuery.take(ps.limit - folders.length).getMany(), DriveFolders.findBy({
...(folders.map(folder => DriveFolders.pack(folder))), id: In(ids),
})
.then(folders => Promise.all(folders.map(folder => DriveFolders.pack(folder)))),
DriveFiles.findBy({
id: In(ids),
})
.then(files => DriveFiles.packMany(files, { detail: false, self: true })),
]); ]);
const packedFiles = await DriveFiles.packMany(files, { detail: false, self: true }); // merge folders/files into one array, keeping the original sorting
let merged = [];
for (const folder of folders) {
merged[ids.indexOf(folder.id)] = folder;
}
for (const file of files) {
merged[ids.indexOf(file.id)] = file;
}
return [ return merged;
...packedFolders,
...packedFiles,
];
}); });

View file

@ -43,10 +43,7 @@ export default define(meta, paramDef, async (ps, user) => {
if (user.id === ps.userId) throw new ApiError('FOLLOWEE_IS_YOURSELF'); if (user.id === ps.userId) throw new ApiError('FOLLOWEE_IS_YOURSELF');
// Get followee // Get followee
const followee = await getUser(ps.userId).catch(e => { const followee = await getUser(ps.userId);
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
throw e;
});
// Check if already following // Check if already following
const exist = await Followings.countBy({ const exist = await Followings.countBy({

View file

@ -42,10 +42,7 @@ export default define(meta, paramDef, async (ps, user) => {
if (user.id === ps.userId) throw new ApiError('FOLLOWEE_IS_YOURSELF'); if (user.id === ps.userId) throw new ApiError('FOLLOWEE_IS_YOURSELF');
// Get followee // Get followee
const followee = await getUser(ps.userId).catch(e => { const followee = await getUser(ps.userId);
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
throw e;
});
// Check not following // Check not following
const exist = await Followings.countBy({ const exist = await Followings.countBy({

View file

@ -42,10 +42,7 @@ export default define(meta, paramDef, async (ps, user) => {
if (user.id === ps.userId) throw new ApiError('FOLLOWER_IS_YOURSELF'); if (user.id === ps.userId) throw new ApiError('FOLLOWER_IS_YOURSELF');
// Get follower // Get follower
const follower = await getUser(ps.userId).catch(e => { const follower = await getUser(ps.userId);
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
throw e;
});
// Check not following // Check not following
const exist = await Followings.countBy({ const exist = await Followings.countBy({

View file

@ -24,10 +24,7 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => { export default define(meta, paramDef, async (ps, user) => {
// Fetch follower // Fetch follower
const follower = await getUser(ps.userId).catch(e => { const follower = await getUser(ps.userId);
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
throw e;
});
await acceptFollowRequest(user, follower).catch(e => { await acceptFollowRequest(user, follower).catch(e => {
if (e.id === '8884c2dd-5795-4ac9-b27e-6a01d38190f9') throw new ApiError('NO_SUCH_FOLLOW_REQUEST'); if (e.id === '8884c2dd-5795-4ac9-b27e-6a01d38190f9') throw new ApiError('NO_SUCH_FOLLOW_REQUEST');

View file

@ -32,10 +32,7 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => { export default define(meta, paramDef, async (ps, user) => {
// Fetch followee // Fetch followee
const followee = await getUser(ps.userId).catch(e => { const followee = await getUser(ps.userId);
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
throw e;
});
try { try {
await cancelFollowRequest(followee, user); await cancelFollowRequest(followee, user);

View file

@ -24,10 +24,7 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => { export default define(meta, paramDef, async (ps, user) => {
// Fetch follower // Fetch follower
const follower = await getUser(ps.userId).catch(e => { const follower = await getUser(ps.userId);
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
throw e;
});
await rejectFollowRequest(user, follower); await rejectFollowRequest(user, follower);

View file

@ -54,10 +54,7 @@ export const paramDef = {
export default define(meta, paramDef, async (ps, user) => { export default define(meta, paramDef, async (ps, user) => {
if (ps.userId != null) { if (ps.userId != null) {
// Fetch recipient (user) // Fetch recipient (user)
const recipient = await getUser(ps.userId).catch(e => { const recipient = await getUser(ps.userId);
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
throw e;
});
const query = makePaginationQuery(MessagingMessages.createQueryBuilder('message'), ps.sinceId, ps.untilId) const query = makePaginationQuery(MessagingMessages.createQueryBuilder('message'), ps.sinceId, ps.untilId)
.andWhere(new Brackets(qb => { qb .andWhere(new Brackets(qb => { qb

View file

@ -100,10 +100,7 @@ export default define(meta, paramDef, async (ps, user) => {
if (ps.userId === user.id) throw new ApiError('RECIPIENT_IS_YOURSELF'); if (ps.userId === user.id) throw new ApiError('RECIPIENT_IS_YOURSELF');
// Fetch recipient (user) // Fetch recipient (user)
recipientUser = await getUser(ps.userId).catch(e => { recipientUser = await getUser(ps.userId);
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
throw e;
});
// Check blocking // Check blocking
const block = await Blockings.countBy({ const block = await Blockings.countBy({

View file

@ -37,10 +37,7 @@ export default define(meta, paramDef, async (ps, user) => {
if (user.id === ps.userId) throw new ApiError('MUTEE_IS_YOURSELF'); if (user.id === ps.userId) throw new ApiError('MUTEE_IS_YOURSELF');
// Get mutee // Get mutee
const mutee = await getUser(ps.userId).catch(e => { const mutee = await getUser(ps.userId);
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
throw e;
});
// Check if already muting // Check if already muting
const exist = await Mutings.countBy({ const exist = await Mutings.countBy({

View file

@ -30,10 +30,7 @@ export default define(meta, paramDef, async (ps, user) => {
if (user.id === ps.userId) throw new ApiError('MUTEE_IS_YOURSELF'); if (user.id === ps.userId) throw new ApiError('MUTEE_IS_YOURSELF');
// Get mutee // Get mutee
const mutee = await getUser(ps.userId).catch(e => { const mutee = await getUser(ps.userId);
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
throw e;
});
// Check not muting // Check not muting
const exist = await Mutings.findOneBy({ const exist = await Mutings.findOneBy({

View file

@ -69,15 +69,6 @@ export const paramDef = {
maxItems: 16, maxItems: 16,
items: { type: 'string', format: 'misskey:id' }, items: { type: 'string', format: 'misskey:id' },
}, },
mediaIds: {
deprecated: true,
description: 'Use `fileIds` instead. If both are specified, this property is discarded.',
type: 'array',
uniqueItems: true,
minItems: 1,
maxItems: 16,
items: { type: 'string', format: 'misskey:id' },
},
replyId: { type: 'string', format: 'misskey:id', nullable: true }, replyId: { type: 'string', format: 'misskey:id', nullable: true },
renoteId: { type: 'string', format: 'misskey:id', nullable: true }, renoteId: { type: 'string', format: 'misskey:id', nullable: true },
channelId: { type: 'string', format: 'misskey:id', nullable: true }, channelId: { type: 'string', format: 'misskey:id', nullable: true },
@ -111,10 +102,6 @@ export const paramDef = {
// (re)note with files, text and poll are optional // (re)note with files, text and poll are optional
required: ['fileIds'], required: ['fileIds'],
}, },
{
// (re)note with files, text and poll are optional
required: ['mediaIds'],
},
{ {
// (re)note with poll, text and files are optional // (re)note with poll, text and files are optional
properties: { properties: {
@ -139,7 +126,7 @@ export default define(meta, paramDef, async (ps, user) => {
} }
let files: DriveFile[] = []; let files: DriveFile[] = [];
const fileIds = ps.fileIds != null ? ps.fileIds : ps.mediaIds != null ? ps.mediaIds : null; const fileIds = ps.fileIds != null ? ps.fileIds : null;
if (fileIds != null) { if (fileIds != null) {
files = await DriveFiles.createQueryBuilder('file') files = await DriveFiles.createQueryBuilder('file')
.where('file.userId = :userId AND file.id IN (:...fileIds)', { .where('file.userId = :userId AND file.id IN (:...fileIds)', {

View file

@ -43,8 +43,6 @@ export const paramDef = {
}, },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
offset: { type: 'integer', default: 0 }, offset: { type: 'integer', default: 0 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
}, },
required: ['noteId'], required: ['noteId'],
} as const; } as const;

View file

@ -2,15 +2,20 @@ import { createReaction } from '@/services/note/reaction/create.js';
import define from '@/server/api/define.js'; import define from '@/server/api/define.js';
import { getNote } from '@/server/api/common/getters.js'; import { getNote } from '@/server/api/common/getters.js';
import { ApiError } from '@/server/api/error.js'; import { ApiError } from '@/server/api/error.js';
import { HOUR, SECOND } from '@/const.js';
import { limiter } from '@/server/api/limiter.js';
import { NoteReactions } from '@/models/index.js';
export const meta = { export const meta = {
tags: ['reactions', 'notes'], tags: ['reactions', 'notes'],
description: 'Add a reaction to a note. If there already is a reaction to this note, deletes it and is consequently subject to the `delete` rate limiting group as if using `notes/reactions/delete`.',
requireCredential: true, requireCredential: true,
kind: 'write:reactions', kind: 'write:reactions',
errors: ['NO_SUCH_NOTE', 'ALREADY_REACTED', 'BLOCKED'], errors: ['NO_SUCH_NOTE', 'ALREADY_REACTED', 'BLOCKED', 'RATE_LIMIT_EXCEEDED'],
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -24,10 +29,27 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => { export default define(meta, paramDef, async (ps, user) => {
const note = await getNote(ps.noteId, user).catch(err => { const [note, reactionCount] = await Promise.all([
if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE'); getNote(ps.noteId, user).catch(err => {
throw err; if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
}); throw err;
}),
NoteReactions.countBy({
noteId: ps.noteId,
userId: user.id,
}),
]);
if (reactionCount > 0) {
const limit = {
key: 'delete',
duration: HOUR,
max: 30,
minInterval: 10 * SECOND,
};
await limiter(limit, user.id);
}
await createReaction(user, note, ps.reaction).catch(e => { await createReaction(user, note, ps.reaction).catch(e => {
if (e.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') throw new ApiError('ALREADY_REACTED'); if (e.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') throw new ApiError('ALREADY_REACTED');
if (e.id === 'e70412a4-7197-4726-8e74-f3e0deb92aa7') throw new ApiError('BLOCKED'); if (e.id === 'e70412a4-7197-4726-8e74-f3e0deb92aa7') throw new ApiError('BLOCKED');

View file

@ -32,10 +32,7 @@ export default define(meta, paramDef, async (ps, user) => {
if (user.id === ps.userId) throw new ApiError('MUTEE_IS_YOURSELF'); if (user.id === ps.userId) throw new ApiError('MUTEE_IS_YOURSELF');
// Get mutee // Get mutee
const mutee = await getUser(ps.userId).catch(e => { const mutee = await getUser(ps.userId);
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
throw e;
});
// Check if already muting // Check if already muting
const exist = await RenoteMutings.countBy({ const exist = await RenoteMutings.countBy({

View file

@ -30,10 +30,7 @@ export default define(meta, paramDef, async (ps, user) => {
if (user.id === ps.userId) throw new ApiError('MUTEE_IS_YOURSELF'); if (user.id === ps.userId) throw new ApiError('MUTEE_IS_YOURSELF');
// Get mutee // Get mutee
const mutee = await getUser(ps.userId).catch(e => { const mutee = await getUser(ps.userId);
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
throw e;
});
// Check not muting // Check not muting
const exist = await RenoteMutings.findOneBy({ const exist = await RenoteMutings.findOneBy({

View file

@ -38,10 +38,7 @@ export default define(meta, paramDef, async (ps, me) => {
if (userGroup == null) throw new ApiError('NO_SUCH_GROUP'); if (userGroup == null) throw new ApiError('NO_SUCH_GROUP');
// Fetch the user // Fetch the user
const user = await getUser(ps.userId).catch(e => { const user = await getUser(ps.userId);
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
throw e;
});
const joined = await UserGroupJoinings.countBy({ const joined = await UserGroupJoinings.countBy({
userGroupId: userGroup.id, userGroupId: userGroup.id,

View file

@ -35,10 +35,7 @@ export default define(meta, paramDef, async (ps, me) => {
if (userGroup == null) throw new ApiError('NO_SUCH_GROUP'); if (userGroup == null) throw new ApiError('NO_SUCH_GROUP');
// Fetch the user // Fetch the user
const user = await getUser(ps.userId).catch(e => { const user = await getUser(ps.userId);
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
throw e;
});
if (user.id === userGroup.userId) throw new ApiError('GROUP_OWNER'); if (user.id === userGroup.userId) throw new ApiError('GROUP_OWNER');

View file

@ -41,10 +41,7 @@ export default define(meta, paramDef, async (ps, me) => {
if (userGroup == null) throw new ApiError('NO_SUCH_GROUP'); if (userGroup == null) throw new ApiError('NO_SUCH_GROUP');
// Fetch the user // Fetch the user
const user = await getUser(ps.userId).catch(e => { const user = await getUser(ps.userId);
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
throw e;
});
const joined = await UserGroupJoinings.countBy({ const joined = await UserGroupJoinings.countBy({
userGroupId: userGroup.id, userGroupId: userGroup.id,

View file

@ -36,10 +36,7 @@ export default define(meta, paramDef, async (ps, me) => {
if (userList == null) throw new ApiError('NO_SUCH_USER_LIST'); if (userList == null) throw new ApiError('NO_SUCH_USER_LIST');
// Fetch the user // Fetch the user
const user = await getUser(ps.userId).catch(e => { const user = await getUser(ps.userId);
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
throw e;
});
// Pull the user // Pull the user
await UserListJoinings.delete({ userListId: userList.id, userId: user.id }); await UserListJoinings.delete({ userListId: userList.id, userId: user.id });

View file

@ -36,10 +36,7 @@ export default define(meta, paramDef, async (ps, me) => {
if (userList == null) throw new ApiError('NO_SUCH_USER_LIST'); if (userList == null) throw new ApiError('NO_SUCH_USER_LIST');
// Fetch the user // Fetch the user
const user = await getUser(ps.userId).catch(e => { const user = await getUser(ps.userId);
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
throw e;
});
// Check blocking // Check blocking
if (user.id !== me.id) { if (user.id !== me.id) {

View file

@ -49,10 +49,7 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, me) => { export default define(meta, paramDef, async (ps, me) => {
// Lookup user // Lookup user
const user = await getUser(ps.userId).catch(e => { const user = await getUser(ps.userId);
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
throw e;
});
//#region Construct query //#region Construct query
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)

View file

@ -32,10 +32,7 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, me) => { export default define(meta, paramDef, async (ps, me) => {
// Lookup user // Lookup user
const user = await getUser(ps.userId).catch(e => { const user = await getUser(ps.userId);
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
throw e;
});
if (user.id === me.id) throw new ApiError('CANNOT_REPORT_YOURSELF'); if (user.id === me.id) throw new ApiError('CANNOT_REPORT_YOURSELF');

View file

@ -36,7 +36,7 @@ export class ApiError extends Error {
break; break;
case 429: case 429:
if (typeof this.info === 'object' && typeof this.info.reset === 'number') { if (typeof this.info === 'object' && typeof this.info.reset === 'number') {
ctx.respose.set('Retry-After', Math.floor(this.info.reset - (Date.now() / 1000))); ctx.response.set('Retry-After', Math.floor(this.info.reset - (Date.now() / 1000)));
} }
break; break;
} }

View file

@ -6,7 +6,7 @@ import { ApiError } from './error.js';
const logger = new Logger('limiter'); const logger = new Logger('limiter');
export const limiter = (limitation: IEndpointMeta['limit'] & { key: NonNullable<string> }, actor: string) => new Promise<void>((resolve) => { export const limiter = (limitation: IEndpointMeta['limit'] & { key: NonNullable<string> }, actor: string) => new Promise<void>((resolve, reject) => {
if (process.env.NODE_ENV === 'test') resolve(); if (process.env.NODE_ENV === 'test') resolve();
const hasShortTermLimit = typeof limitation.minInterval === 'number'; const hasShortTermLimit = typeof limitation.minInterval === 'number';
@ -15,45 +15,8 @@ export const limiter = (limitation: IEndpointMeta['limit'] & { key: NonNullable<
typeof limitation.duration === 'number' && typeof limitation.duration === 'number' &&
typeof limitation.max === 'number'; typeof limitation.max === 'number';
if (hasShortTermLimit) {
min();
} else if (hasLongTermLimit) {
max();
} else {
resolve();
}
// Short-term limit, calls long term limit if appropriate.
function min(): void {
const minIntervalLimiter = new Limiter({
id: `${actor}:${limitation.key}:min`,
duration: limitation.minInterval,
max: 1,
db: redisClient,
});
minIntervalLimiter.get((err, info) => {
if (err) {
logger.error(err);
throw new ApiError('INTERNAL_ERROR');
}
logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`);
if (info.remaining === 0) {
throw new ApiError('RATE_LIMIT_EXCEEDED', info);
} else {
if (hasLongTermLimit) {
max();
} else {
resolve();
}
}
});
}
// Long term limit // Long term limit
function max(): void { const max = (): void => {
const limiter = new Limiter({ const limiter = new Limiter({
id: `${actor}:${limitation.key}`, id: `${actor}:${limitation.key}`,
duration: limitation.duration, duration: limitation.duration,
@ -64,16 +27,53 @@ export const limiter = (limitation: IEndpointMeta['limit'] & { key: NonNullable<
limiter.get((err, info) => { limiter.get((err, info) => {
if (err) { if (err) {
logger.error(err); logger.error(err);
throw new ApiError('INTERNAL_ERROR'); reject(new ApiError('INTERNAL_ERROR'));
} }
logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`); logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`);
if (info.remaining === 0) { if (info.remaining === 0) {
throw new ApiError('RATE_LIMIT_EXCEEDED', info); reject(new ApiError('RATE_LIMIT_EXCEEDED', info));
} else { } else {
resolve(); resolve();
} }
}); });
} }
// Short-term limit, calls long term limit if appropriate.
const min = (): void => {
const minIntervalLimiter = new Limiter({
id: `${actor}:${limitation.key}:min`,
duration: limitation.minInterval,
max: 1,
db: redisClient,
});
minIntervalLimiter.get((err, info) => {
if (err) {
logger.error(err);
reject(new ApiError('INTERNAL_ERROR'));
}
logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`);
if (info.remaining === 0) {
reject(new ApiError('RATE_LIMIT_EXCEEDED', info));
} else {
if (hasLongTermLimit) {
max();
} else {
resolve();
}
}
});
}
if (hasShortTermLimit) {
min();
} else if (hasLongTermLimit) {
max();
} else {
resolve();
}
}); });

View file

@ -47,6 +47,7 @@ export default async (ctx: Koa.Context) => {
const user = await Users.findOneBy({ const user = await Users.findOneBy({
usernameLower: username.toLowerCase(), usernameLower: username.toLowerCase(),
host: IsNull(), host: IsNull(),
isDeleted: false,
}) as ILocalUser; }) as ILocalUser;
if (user == null) { if (user == null) {

View file

@ -332,7 +332,8 @@ export class Connection {
* @param data The message to be sent. * @param data The message to be sent.
*/ */
private onChannelMessageRequested(data: Record<string, any>) { private onChannelMessageRequested(data: Record<string, any>) {
this.channels[id]?.onMessage?.(data.type, data.body); if (!data.id) return;
this.channels[data.id]?.onMessage?.(data.type, data.body);
} }
private typingOnChannel(channel: ChannelModel['id']) { private typingOnChannel(channel: ChannelModel['id']) {

View file

@ -89,7 +89,7 @@ const nodeinfo2 = async (): Promise<NodeInfo2Base> => {
langs: meta.langs, langs: meta.langs,
tosUrl: meta.ToSUrl, tosUrl: meta.ToSUrl,
repositoryUrl: repository, repositoryUrl: repository,
feedbackUrl: 'ircs://irc.akkoma.dev/foundkey', feedbackUrl: 'https://akkoma.dev/FoundKeyGang/FoundKey/issues',
disableRegistration: meta.disableRegistration, disableRegistration: meta.disableRegistration,
disableLocalTimeline: meta.disableLocalTimeline, disableLocalTimeline: meta.disableLocalTimeline,
disableGlobalTimeline: meta.disableGlobalTimeline, disableGlobalTimeline: meta.disableGlobalTimeline,

View file

@ -223,6 +223,7 @@ const getFeed = async (acct: string) => {
usernameLower: username.toLowerCase(), usernameLower: username.toLowerCase(),
host: host ?? IsNull(), host: host ?? IsNull(),
isSuspended: false, isSuspended: false,
isDeleted: false,
}); });
return user && await packFeed(user); return user && await packFeed(user);
@ -272,6 +273,7 @@ router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => {
usernameLower: username.toLowerCase(), usernameLower: username.toLowerCase(),
host: host ?? IsNull(), host: host ?? IsNull(),
isSuspended: false, isSuspended: false,
isDeleted: false,
}); });
if (user != null) { if (user != null) {
@ -304,6 +306,7 @@ router.get('/users/:user', async ctx => {
id: ctx.params.user, id: ctx.params.user,
host: IsNull(), host: IsNull(),
isSuspended: false, isSuspended: false,
isDeleted: false,
}); });
if (user == null) { if (user == null) {
@ -419,6 +422,8 @@ router.get('/@:user/pages/:page', async (ctx, next) => {
const user = await Users.findOneBy({ const user = await Users.findOneBy({
usernameLower: username.toLowerCase(), usernameLower: username.toLowerCase(),
host: host ?? IsNull(), host: host ?? IsNull(),
isSuspended: false,
isDeleted: false,
}); });
if (user == null) return; if (user == null) return;

View file

@ -1,4 +1,4 @@
import { Users } from '@/models/index.js'; import { AccessTokens, Users } from '@/models/index.js';
import { createDeleteAccountJob } from '@/queue/index.js'; import { createDeleteAccountJob } from '@/queue/index.js';
import { publishUserEvent } from './stream.js'; import { publishUserEvent } from './stream.js';
import { doPostSuspend } from './suspend-user.js'; import { doPostSuspend } from './suspend-user.js';
@ -7,9 +7,15 @@ export async function deleteAccount(user: {
id: string; id: string;
host: string | null; host: string | null;
}): Promise<void> { }): Promise<void> {
await Users.update(user.id, { await Promise.all([
isDeleted: true, Users.update(user.id, {
}); isDeleted: true,
}),
// revoke all of the users access tokens to block API access
AccessTokens.delete({
userId: user.id,
}),
]);
if (Users.isLocalUser(user)) { if (Users.isLocalUser(user)) {
// Terminate streaming // Terminate streaming

View file

@ -322,7 +322,7 @@ type AddFileArgs = {
/** Comment */ /** Comment */
comment?: string | null; comment?: string | null;
/** Folder ID */ /** Folder ID */
folderId?: any; parentId?: any;
/** If set to true, forcibly upload the file even if there is a file with the same hash. */ /** If set to true, forcibly upload the file even if there is a file with the same hash. */
force?: boolean; force?: boolean;
/** Do not save file to local */ /** Do not save file to local */
@ -344,7 +344,7 @@ export async function addFile({
path, path,
name = null, name = null,
comment = null, comment = null,
folderId = null, parentId = null,
force = false, force = false,
isLink = false, isLink = false,
url = null, url = null,
@ -392,12 +392,12 @@ export async function addFile({
//#endregion //#endregion
const fetchFolder = async (): Promise<DriveFolder | null> => { const fetchFolder = async (): Promise<DriveFolder | null> => {
if (!folderId) { if (!parentId) {
return null; return null;
} }
const driveFolder = await DriveFolders.findOneBy({ const driveFolder = await DriveFolders.findOneBy({
id: folderId, id: parentId,
userId: user ? user.id : IsNull(), userId: user ? user.id : IsNull(),
}); });
@ -429,7 +429,7 @@ export async function addFile({
file.createdAt = new Date(); file.createdAt = new Date();
file.userId = user ? user.id : null; file.userId = user ? user.id : null;
file.userHost = user ? user.host : null; file.userHost = user ? user.host : null;
file.folderId = folder?.id ?? null; file.parentId = folder?.id ?? null;
file.comment = comment; file.comment = comment;
file.properties = properties; file.properties = properties;
file.blurhash = info.blurhash || null; file.blurhash = info.blurhash || null;

View file

@ -13,7 +13,7 @@ const logger = driveLogger.createSubLogger('downloader');
type Args = { type Args = {
url: string; url: string;
user: { id: User['id']; host: User['host'] } | null; user: { id: User['id']; host: User['host'] } | null;
folderId?: DriveFolder['id'] | null; parentId?: DriveFolder['id'] | null;
uri?: string | null; uri?: string | null;
sensitive?: boolean; sensitive?: boolean;
force?: boolean; force?: boolean;
@ -24,7 +24,7 @@ type Args = {
export async function uploadFromUrl({ export async function uploadFromUrl({
url, url,
user, user,
folderId = null, parentId = null,
uri = null, uri = null,
sensitive = false, sensitive = false,
force = false, force = false,
@ -50,7 +50,7 @@ export async function uploadFromUrl({
// If the comment is same as the name, skip comment // If the comment is same as the name, skip comment
// (image.name is passed in when receiving attachment) // (image.name is passed in when receiving attachment)
comment: name === comment ? null : comment, comment: name === comment ? null : comment,
folderId, parentId,
force, force,
isLink, isLink,
url, url,

View file

@ -1,7 +1,7 @@
import { IsNull } from 'typeorm'; import { IsNull } from 'typeorm';
import { ILocalUser } from '@/models/entities/user.js'; import { ILocalUser } from '@/models/entities/user.js';
import { Users } from '@/models/index.js'; import { Users } from '@/models/index.js';
import { createSystemUser } from './create-system-user.js'; import { getSystemUser } from './system-user.js';
const ACTOR_USERNAME = 'instance.actor' as const; const ACTOR_USERNAME = 'instance.actor' as const;
@ -12,7 +12,7 @@ let instanceActor = await Users.findOneBy({
export async function getInstanceActor(): Promise<ILocalUser> { export async function getInstanceActor(): Promise<ILocalUser> {
if (!instanceActor) { if (!instanceActor) {
instanceActor = await createSystemUser(ACTOR_USERNAME) as ILocalUser; instanceActor = await getSystemUser(ACTOR_USERNAME) as ILocalUser;
} }
return instanceActor; return instanceActor;

View file

@ -150,7 +150,7 @@ async function findCascadingNotes(note: Note): Promise<Note[]> {
await Promise.all(replies.map(reply => { await Promise.all(replies.map(reply => {
// only add unique notes // only add unique notes
if (cascadingNotes.find((x) => x.id === reply.id) != null) return; if (cascadingNotes.some((x) => x.id === reply.id)) return;
cascadingNotes.push(reply); cascadingNotes.push(reply);
return recursive(reply.id); return recursive(reply.id);
@ -186,4 +186,3 @@ async function getMentionedRemoteUsers(note: Note): Promise<IRemoteUser[]> {
where, where,
}) as IRemoteUser[]; }) as IRemoteUser[];
} }

View file

@ -9,7 +9,7 @@ import { genId } from '@/misc/gen-id.js';
import { Cache } from '@/misc/cache.js'; import { Cache } from '@/misc/cache.js';
import { Relay } from '@/models/entities/relay.js'; import { Relay } from '@/models/entities/relay.js';
import { MINUTE } from '@/const.js'; import { MINUTE } from '@/const.js';
import { createSystemUser } from './create-system-user.js'; import { getSystemUser } from './system-user.js';
const ACTOR_USERNAME = 'relay.actor' as const; const ACTOR_USERNAME = 'relay.actor' as const;
@ -24,16 +24,8 @@ const relaysCache = new Cache<Relay[]>(
}), }),
); );
export async function getRelayActor(): Promise<ILocalUser> { async function getRelayActor(): Promise<ILocalUser> {
const user = await Users.findOneBy({ return await getSystemUser(ACTOR_USERNAME);
host: IsNull(),
username: ACTOR_USERNAME,
});
if (user) return user as ILocalUser;
const created = await createSystemUser(ACTOR_USERNAME);
return created as ILocalUser;
} }
export async function addRelay(inbox: string): Promise<Relay> { export async function addRelay(inbox: string): Promise<Relay> {

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