diff --git a/.config/docker_example.yml b/.config/docker_example.yml
index b3d26ca5a6..db34a5036d 100644
--- a/.config/docker_example.yml
+++ b/.config/docker_example.yml
@@ -95,6 +95,14 @@ redis:
# #prefix: example-prefix
# #db: 1
+#redisForTimelines:
+# host: redis
+# port: 6379
+# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
+# #pass: example-pass
+# #prefix: example-prefix
+# #db: 1
+
# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────
@@ -114,6 +122,7 @@ redis:
# Available methods:
# aid ... Short, Millisecond accuracy
+# aidx ... Millisecond accuracy
# meid ... Similar to ObjectID, Millisecond accuracy
# ulid ... Millisecond accuracy
# objectid ... This is left for backward compatibility
@@ -121,7 +130,7 @@ redis:
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# ID SETTINGS AFTER THAT!
-id: 'aid'
+id: 'aidx'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
@@ -165,8 +174,8 @@ proxyBypassHosts:
# Media Proxy
#mediaProxy: https://example.com/proxy
-# Proxy remote files (default: false)
-#proxyRemoteFiles: true
+# Proxy remote files (default: true)
+proxyRemoteFiles: true
# Sign to ActivityPub GET request (default: true)
signToActivityPubGet: true
diff --git a/.config/example.yml b/.config/example.yml
index 400f827492..7bf5ddf3b3 100644
--- a/.config/example.yml
+++ b/.config/example.yml
@@ -30,7 +30,7 @@ url: https://example.tld/
# The port that your Misskey server should listen on.
port: 3000
-# You can also use UNIX domain socket.
+# You can also use UNIX domain socket.
# socket: /path/to/misskey.sock
# chmodSocket: '777'
@@ -60,17 +60,17 @@ dbReplications: false
# You can configure any number of replicas here
#dbSlaves:
# -
-# host:
-# port:
-# db:
-# user:
-# pass:
+# host:
+# port:
+# db:
+# user:
+# pass:
# -
-# host:
-# port:
-# db:
-# user:
-# pass:
+# host:
+# port:
+# db:
+# user:
+# pass:
# ┌─────────────────────┐
#───┘ Redis configuration └─────────────────────────────────────
@@ -105,6 +105,16 @@ redis:
# # You can specify more ioredis options...
# #username: example-username
+#redisForTimelines:
+# host: localhost
+# port: 6379
+# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
+# #pass: example-pass
+# #prefix: example-prefix
+# #db: 1
+# # You can specify more ioredis options...
+# #username: example-username
+
# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────
@@ -125,6 +135,7 @@ redis:
# Available methods:
# aid ... Short, Millisecond accuracy
+# aidx ... Millisecond accuracy
# meid ... Similar to ObjectID, Millisecond accuracy
# ulid ... Millisecond accuracy
# objectid ... This is left for backward compatibility
@@ -132,7 +143,7 @@ redis:
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# ID SETTINGS AFTER THAT!
-id: 'aid'
+id: 'aidx'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
@@ -159,6 +170,9 @@ id: 'aid'
#deliverJobMaxAttempts: 12
#inboxJobMaxAttempts: 8
+# Local address used for outgoing requests
+#outgoingAddress: 127.0.0.1
+
# IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4
@@ -183,9 +197,9 @@ proxyBypassHosts:
# * Perform image compression (on a different server resource than the main process)
#mediaProxy: https://example.com/proxy
-# Proxy remote files (default: false)
+# Proxy remote files (default: true)
# Proxy remote files by this instance or mediaProxy to prevent remote files from running in remote domains.
-#proxyRemoteFiles: true
+proxyRemoteFiles: true
# Movie Thumbnail Generation URL
# There is no reference implementation.
@@ -203,5 +217,8 @@ signToActivityPubGet: true
# Upload or download file size limits (bytes)
#maxFileSize: 262144000
+# PID File of master process
+#pidFile: /tmp/misskey.pid
+
# Value of Content-Security-Policy header
#contentSecurityPolicy: "script-src 'self' 'unsafe-eval' https://challenges.cloudflare.com https://hcaptcha.com https://*.hcaptcha.com https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ https://www.recaptcha.net/recaptcha/; base-uri 'self'; object-src 'self';"
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index d0689667ac..a50068f1d7 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -4,7 +4,9 @@
"service": "app",
"workspaceFolder": "/workspace",
"features": {
- "ghcr.io/devcontainers-contrib/features/pnpm:2": {},
+ "ghcr.io/devcontainers-contrib/features/pnpm:2": {
+ "version": "8.9.2"
+ },
"ghcr.io/devcontainers/features/node:1": {
"version": "20"
}
diff --git a/.devcontainer/devcontainer.yml b/.devcontainer/devcontainer.yml
index 9e95ca4b41..54ef6a3dc7 100644
--- a/.devcontainer/devcontainer.yml
+++ b/.devcontainer/devcontainer.yml
@@ -95,6 +95,14 @@ redis:
# #prefix: example-prefix
# #db: 1
+#redisForTimelines:
+# host: redis
+# port: 6379
+# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
+# #pass: example-pass
+# #prefix: example-prefix
+# #db: 1
+
# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────
@@ -114,6 +122,7 @@ redis:
# Available methods:
# aid ... Short, Millisecond accuracy
+# aidx ... Millisecond accuracy
# meid ... Similar to ObjectID, Millisecond accuracy
# ulid ... Millisecond accuracy
# objectid ... This is left for backward compatibility
@@ -121,7 +130,7 @@ redis:
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# ID SETTINGS AFTER THAT!
-id: 'aid'
+id: 'aidx'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
@@ -165,8 +174,8 @@ proxyBypassHosts:
# Media Proxy
#mediaProxy: https://example.com/proxy
-# Proxy remote files (default: false)
-#proxyRemoteFiles: true
+# Proxy remote files (default: true)
+proxyRemoteFiles: true
# Sign to ActivityPub GET request (default: true)
signToActivityPubGet: true
diff --git a/.github/ISSUE_TEMPLATE/01_bug-report.md b/.github/ISSUE_TEMPLATE/01_bug-report.md
deleted file mode 100644
index b889d96eb3..0000000000
--- a/.github/ISSUE_TEMPLATE/01_bug-report.md
+++ /dev/null
@@ -1,60 +0,0 @@
----
-name: 🐛 Bug Report
-about: Create a report to help us improve
-title: ''
-labels: ⚠️bug?
-assignees: ''
-
----
-
-
-
-## 💡 Summary
-
-
-
-## 🥰 Expected Behavior
-
-
-
-## 🤬 Actual Behavior
-
-
-
-## 📝 Steps to Reproduce
-
-1.
-2.
-3.
-
-## 📌 Environment
-
-
-
-
-### 💻 Frontend
-* Model and OS of the device(s):
-
-* Browser:
-
-* Server URL:
-
-* Misskey:
- 13.x.x
-
-### 🛰 Backend (for server admin)
-
-
-* Installation Method or Hosting Service:
-* Misskey: 13.x.x
-* Node: 20.x.x
-* PostgreSQL: 15.x.x
-* Redis: 7.x.x
-* OS and Architecture:
diff --git a/.github/ISSUE_TEMPLATE/01_bug-report.yml b/.github/ISSUE_TEMPLATE/01_bug-report.yml
new file mode 100644
index 0000000000..f74719989f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/01_bug-report.yml
@@ -0,0 +1,91 @@
+name: 🐛 Bug Report
+description: Create a report to help us improve
+labels: ["⚠️bug?"]
+
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for reporting!
+ First, in order to avoid duplicate Issues, please search to see if the problem you found has already been reported.
+ Also, If you are NOT owner/admin of server, PLEASE DONT REPORT SERVER SPECIFIC ISSUES TO HERE! (e.g. feature XXX is not working in misskey.example) Please try with another misskey servers, and if your issue is only reproducible with specific server, contact your server's owner/admin first.
+
+ - type: textarea
+ attributes:
+ label: 💡 Summary
+ description: Tell us what the bug is
+ validations:
+ required: true
+
+ - type: textarea
+ attributes:
+ label: 🥰 Expected Behavior
+ description: Tell us what should happen
+ validations:
+ required: true
+
+ - type: textarea
+ attributes:
+ label: 🤬 Actual Behavior
+ description: |
+ Tell us what happens instead of the expected behavior.
+ Please include errors from the developer console and/or server log files if you have access to them.
+ validations:
+ required: true
+
+ - type: textarea
+ attributes:
+ label: 📝 Steps to Reproduce
+ placeholder: |
+ 1.
+ 2.
+ 3.
+ validations:
+ required: false
+
+ - type: textarea
+ attributes:
+ label: 💻 Frontend Environment
+ description: |
+ Tell us where on the platform it happens
+ DO NOT WRITE "latest". Please provide the specific version.
+
+ Examples:
+ * Model and OS of the device(s): MacBook Pro (14inch, 2021), macOS Ventura 13.4
+ * Browser: Chrome 113.0.5672.126
+ * Server URL: misskey.io
+ * Misskey: 13.x.x
+ value: |
+ * Model and OS of the device(s):
+ * Browser:
+ * Server URL:
+ * Misskey:
+ render: markdown
+ validations:
+ required: false
+
+ - type: textarea
+ attributes:
+ label: 🛰 Backend Environment (for server admin)
+ description: |
+ Tell us where on the platform it happens
+ DO NOT WRITE "latest". Please provide the specific version.
+ If you are using a managed service, put that after the version.
+
+ Examples:
+ * Installation Method or Hosting Service: docker compose, k8s/docker, systemd, "Misskey install shell script", development environment
+ * Misskey: 13.x.x
+ * Node: 20.x.x
+ * PostgreSQL: 15.x.x
+ * Redis: 7.x.x
+ * OS and Architecture: Ubuntu 22.04.2 LTS aarch64
+ value: |
+ * Installation Method or Hosting Service:
+ * Misskey:
+ * Node:
+ * PostgreSQL:
+ * Redis:
+ * OS and Architecture:
+ render: markdown
+ validations:
+ required: false
diff --git a/.github/ISSUE_TEMPLATE/02_feature-request.md b/.github/ISSUE_TEMPLATE/02_feature-request.md
deleted file mode 100644
index 5045b17712..0000000000
--- a/.github/ISSUE_TEMPLATE/02_feature-request.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-name: ✨ Feature Request
-about: Suggest an idea for this project
-title: ''
-labels: ✨Feature
-assignees: ''
-
----
-
-## Summary
-
-
diff --git a/.github/ISSUE_TEMPLATE/02_feature-request.yml b/.github/ISSUE_TEMPLATE/02_feature-request.yml
new file mode 100644
index 0000000000..8420475b3e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/02_feature-request.yml
@@ -0,0 +1,17 @@
+name: ✨ Feature Request
+description: Suggest an idea for this project
+labels: ["✨Feature"]
+
+body:
+ - type: textarea
+ attributes:
+ label: Summary
+ description: Tell us what the suggestion is
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Purpose
+ description: Describe the specific problem or need you think this feature will solve, and who it will help.
+ validations:
+ required: true
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 730647b086..e8b65dc3b9 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,7 +1,4 @@
contact_links:
- - name: 👪 Misskey Forum
- url: https://forum.misskey.io/
- about: Ask questions and share knowledge
- name: 💬 Misskey official Discord
url: https://discord.gg/Wp8gVStHW3
about: Chat freely about Misskey
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index e878e5836a..5955f6b5d9 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -9,24 +9,24 @@ updates:
directory: "/"
schedule:
interval: daily
- open-pull-requests-limit: 0
+ open-pull-requests-limit: 100
+
+# Add only the root, not each workspace item
+# https://github.com/dependabot/dependabot-core/issues/4993#issuecomment-1289133027
- package-ecosystem: npm
directory: "/"
schedule:
interval: daily
+ # PNPM has an issue with dependabot. See:
+ # https://github.com/dependabot/dependabot-core/issues/7258
+ # https://github.com/pnpm/pnpm/issues/6530
+ # TODO: Restore this when the issue is solved
open-pull-requests-limit: 0
-- package-ecosystem: npm
- directory: "/packages/backend"
- schedule:
- interval: daily
- open-pull-requests-limit: 0
-- package-ecosystem: npm
- directory: "/packages/frontend"
- schedule:
- interval: daily
- open-pull-requests-limit: 0
-- package-ecosystem: npm
- directory: "/packages/sw"
- schedule:
- interval: daily
- open-pull-requests-limit: 0
+ groups:
+ swc:
+ patterns:
+ - "@swc/*"
+ storybook:
+ patterns:
+ - "storybook*"
+ - "@storybook/*"
diff --git a/.github/misskey/test.yml b/.github/misskey/test.yml
index f43f74be14..7a4aa4ae6c 100644
--- a/.github/misskey/test.yml
+++ b/.github/misskey/test.yml
@@ -12,4 +12,4 @@ db:
redis:
host: 127.0.0.1
port: 56312
-id: aid
+id: aidx
diff --git a/.github/workflows/api-misskey-js.yml b/.github/workflows/api-misskey-js.yml
index ed004c78dc..3be8f095f1 100644
--- a/.github/workflows/api-misskey-js.yml
+++ b/.github/workflows/api-misskey-js.yml
@@ -9,12 +9,12 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v3.3.0
+ uses: actions/checkout@v4.1.1
- run: corepack enable
- name: Setup Node.js
- uses: actions/setup-node@v3.6.0
+ uses: actions/setup-node@v4.0.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
diff --git a/.github/workflows/check_copyright_year.yml b/.github/workflows/check_copyright_year.yml
index 8daea44a83..03dfcd0a0b 100644
--- a/.github/workflows/check_copyright_year.yml
+++ b/.github/workflows/check_copyright_year.yml
@@ -10,7 +10,7 @@ jobs:
check_copyright_year:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3.2.0
+ - uses: actions/checkout@v4.1.1
- run: |
if [ "$(grep Copyright COPYING | sed -e 's/.*2014-\([0-9]*\) .*/\1/g')" -ne "$(date +%Y)" ]; then
echo "Please change copyright year!"
diff --git a/.github/workflows/docker-io.yml b/.github/workflows/docker-io.yml
index 0e9531a40f..29f2687fa8 100644
--- a/.github/workflows/docker-io.yml
+++ b/.github/workflows/docker-io.yml
@@ -14,19 +14,19 @@ jobs:
if: github.repository == 'MisskeyIO/misskey'
steps:
- name: Check out the repo
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Set up Docker Buildx
id: buildx
- uses: docker/setup-buildx-action@v2
+ uses: docker/setup-buildx-action@v3
with:
platforms: linux/amd64
- name: Docker meta
id: meta
- uses: docker/metadata-action@v4
+ uses: docker/metadata-action@v5
with:
images: ghcr.io/misskeyio/misskey
- name: Log in to GitHub Container Registry
- uses: docker/login-action@v2
+ uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -35,7 +35,7 @@ jobs:
run: |
echo "FORMATTED_BRANCH_NAME=$(echo ${{ github.ref_name }} | sed -e 's/\//-/g' )" >> $GITHUB_ENV
- name: Build and Push to GitHub Container Registry
- uses: docker/build-push-action@v4
+ uses: docker/build-push-action@v5
with:
builder: ${{ steps.buildx.outputs.name }}
context: .
diff --git a/.github/workflows/dockle.yml b/.github/workflows/dockle.yml
index 292e68be13..d9366d8f34 100644
--- a/.github/workflows/dockle.yml
+++ b/.github/workflows/dockle.yml
@@ -12,9 +12,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Build an image from Dockerfile
- uses: docker/build-push-action@v4
+ uses: docker/build-push-action@v5
with:
context: .
push: false
diff --git a/.github/workflows/get-api-diff.yml b/.github/workflows/get-api-diff.yml
new file mode 100644
index 0000000000..8e3437ad86
--- /dev/null
+++ b/.github/workflows/get-api-diff.yml
@@ -0,0 +1,186 @@
+# this name is used in report-api-diff.yml so be careful when change name
+name: Get api.json from Misskey
+
+on:
+ pull_request:
+ branches:
+ - master
+ - develop
+
+jobs:
+ get-base:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+
+ strategy:
+ matrix:
+ node-version: [20.5.1]
+
+ services:
+ db:
+ image: postgres:13
+ ports:
+ - 5432:5432
+ env:
+ POSTGRES_DB: misskey
+ POSTGRES_HOST_AUTH_METHOD: trust
+ POSTGRES_USER: example-misskey-user
+ POSTGRESS_PASS: example-misskey-pass
+ redis:
+ image: redis:7
+ ports:
+ - 6379:6379
+
+ steps:
+ - uses: actions/checkout@v4.1.1
+ with:
+ repository: ${{ github.event.pull_request.base.repo.full_name }}
+ ref: ${{ github.base_ref }}
+ submodules: true
+ - name: Install pnpm
+ uses: pnpm/action-setup@v2
+ with:
+ version: 8
+ run_install: false
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v4.0.0
+ with:
+ node-version: ${{ matrix.node-version }}
+ cache: 'pnpm'
+ - run: corepack enable
+ - run: pnpm i --frozen-lockfile
+ - name: Check pnpm-lock.yaml
+ run: git diff --exit-code pnpm-lock.yaml
+ - name: Copy Configure
+ run: cp .config/example.yml .config/default.yml
+ - name: Build
+ run: pnpm build
+ - name : Migrate
+ run: pnpm migrate
+ - name: Launch misskey
+ run: |
+ screen -S misskey -dm pnpm run dev
+ sleep 30s
+ - name: Wait for Misskey to be ready
+ run: |
+ MAX_RETRIES=12
+ RETRY_DELAY=5
+ count=0
+ until $(curl --output /dev/null --silent --head --fail http://localhost:3000) || [[ $count -eq $MAX_RETRIES ]]; do
+ printf '.'
+ sleep $RETRY_DELAY
+ count=$((count + 1))
+ done
+
+ if [[ $count -eq $MAX_RETRIES ]]; then
+ echo "Failed to connect to Misskey after $MAX_RETRIES attempts."
+ exit 1
+ fi
+ - id: fetch
+ name: Get api.json from Misskey
+ run: |
+ RESULT=$(curl --retry 5 --retry-delay 5 --retry-max-time 60 http://localhost:3000/api.json)
+ echo $RESULT > api-base.json
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v3
+ with:
+ name: api-artifact
+ path: api-base.json
+ - name: Kill Misskey Job
+ run: screen -S misskey -X quit
+
+ get-head:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+
+ strategy:
+ matrix:
+ node-version: [20.5.1]
+
+ services:
+ db:
+ image: postgres:13
+ ports:
+ - 5432:5432
+ env:
+ POSTGRES_DB: misskey
+ POSTGRES_HOST_AUTH_METHOD: trust
+ POSTGRES_USER: example-misskey-user
+ POSTGRESS_PASS: example-misskey-pass
+ redis:
+ image: redis:7
+ ports:
+ - 6379:6379
+
+ steps:
+ - uses: actions/checkout@v4.1.1
+ with:
+ repository: ${{ github.event.pull_request.head.repo.full_name }}
+ ref: ${{ github.head_ref }}
+ submodules: true
+ - name: Install pnpm
+ uses: pnpm/action-setup@v2
+ with:
+ version: 8
+ run_install: false
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v4.0.0
+ with:
+ node-version: ${{ matrix.node-version }}
+ cache: 'pnpm'
+ - run: corepack enable
+ - run: pnpm i --frozen-lockfile
+ - name: Check pnpm-lock.yaml
+ run: git diff --exit-code pnpm-lock.yaml
+ - name: Copy Configure
+ run: cp .config/example.yml .config/default.yml
+ - name: Build
+ run: pnpm build
+ - name : Migrate
+ run: pnpm migrate
+ - name: Launch misskey
+ run: |
+ screen -S misskey -dm pnpm run dev
+ sleep 30s
+ - name: Wait for Misskey to be ready
+ run: |
+ MAX_RETRIES=12
+ RETRY_DELAY=5
+ count=0
+ until $(curl --output /dev/null --silent --head --fail http://localhost:3000) || [[ $count -eq $MAX_RETRIES ]]; do
+ printf '.'
+ sleep $RETRY_DELAY
+ count=$((count + 1))
+ done
+
+ if [[ $count -eq $MAX_RETRIES ]]; then
+ echo "Failed to connect to Misskey after $MAX_RETRIES attempts."
+ exit 1
+ fi
+ - id: fetch
+ name: Get api.json from Misskey
+ run: |
+ RESULT=$(curl --retry 5 --retry-delay 5 --retry-max-time 60 http://localhost:3000/api.json)
+ echo $RESULT > api-head.json
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v3
+ with:
+ name: api-artifact
+ path: api-head.json
+ - name: Kill Misskey Job
+ run: screen -S misskey -X quit
+
+ save-pr-number:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Save PR number
+ env:
+ PR_NUMBER: ${{ github.event.number }}
+ run: |
+ echo "$PR_NUMBER" > ./pr_number
+ - uses: actions/upload-artifact@v3
+ with:
+ name: api-artifact
+ path: pr_number
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 0f3702f958..5096e54af8 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -11,7 +11,7 @@ jobs:
pnpm_install:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3.3.0
+ - uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
submodules: true
@@ -19,7 +19,7 @@ jobs:
with:
version: 8
run_install: false
- - uses: actions/setup-node@v3.6.0
+ - uses: actions/setup-node@v4.0.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
@@ -38,7 +38,7 @@ jobs:
- sw
- misskey-js
steps:
- - uses: actions/checkout@v3.3.0
+ - uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
submodules: true
@@ -46,7 +46,7 @@ jobs:
with:
version: 7
run_install: false
- - uses: actions/setup-node@v3.6.0
+ - uses: actions/setup-node@v4.0.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
@@ -64,7 +64,7 @@ jobs:
- backend
- misskey-js
steps:
- - uses: actions/checkout@v3.3.0
+ - uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
submodules: true
@@ -72,7 +72,7 @@ jobs:
with:
version: 7
run_install: false
- - uses: actions/setup-node@v3.6.0
+ - uses: actions/setup-node@v4.0.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
diff --git a/.github/workflows/report-api-diff.yml b/.github/workflows/report-api-diff.yml
new file mode 100644
index 0000000000..2868d6cc09
--- /dev/null
+++ b/.github/workflows/report-api-diff.yml
@@ -0,0 +1,85 @@
+name: Report API Diff
+
+on:
+ workflow_run:
+ types: [completed]
+ workflows:
+ - Get api.json from Misskey # get-api-diff.yml
+
+jobs:
+ compare-diff:
+ runs-on: ubuntu-latest
+ if: ${{ github.event.workflow_run.conclusion == 'success' }}
+ permissions:
+ pull-requests: write
+
+# api-artifact
+ steps:
+ - name: Download artifact
+ uses: actions/github-script@v7
+ with:
+ script: |
+ let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ run_id: context.payload.workflow_run.id,
+ });
+ let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
+ return artifact.name == "api-artifact"
+ })[0];
+ let download = await github.rest.actions.downloadArtifact({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ artifact_id: matchArtifact.id,
+ archive_format: 'zip',
+ });
+ let fs = require('fs');
+ fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/api-artifact.zip`, Buffer.from(download.data));
+ - name: Extract artifact
+ run: unzip api-artifact.zip -d artifacts
+ - name: Load PR Number
+ id: load-pr-num
+ run: echo "pr-number=$(cat artifacts/pr_number)" >> "$GITHUB_OUTPUT"
+
+ - name: Output base
+ run: cat ./artifacts/api-base.json
+ - name: Output head
+ run: cat ./artifacts/api-head.json
+ - name: Arrange json files
+ run: |
+ jq '.' ./artifacts/api-base.json > ./api-base.json
+ jq '.' ./artifacts/api-head.json > ./api-head.json
+ - name: Get diff of 2 files
+ run: diff -u --label=base --label=head ./api-base.json ./api-head.json | cat > api.json.diff
+ - name: Get full diff
+ run: diff --label=base --label=head --new-line-format='+%L' --old-line-format='-%L' --unchanged-line-format=' %L' ./api-base.json ./api-head.json | cat > api-full.json.diff
+ - name: Echo full diff
+ run: cat ./api-full.json.diff
+ - name: Upload full diff to Artifact
+ uses: actions/upload-artifact@v3
+ with:
+ name: api-artifact
+ path: |
+ api-full.json.diff
+ api-base.json
+ api-head.json
+ - id: out-diff
+ name: Build diff Comment
+ run: |
+ cat <<- EOF > ./output.md
+ このPRによるapi.jsonの差分
+
+ 差分はこちら
+
+ \`\`\`diff
+ $(cat ./api.json.diff)
+ \`\`\`
+
+
+ [Get diff files from Workflow Page](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})
+ EOF
+ - uses: thollander/actions-comment-pull-request@v2
+ with:
+ pr_number: ${{ steps.load-pr-num.outputs.pr-number }}
+ comment_tag: show_diff
+ filePath: ./output.md
diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml
index d133cf4d99..3628d762dc 100644
--- a/.github/workflows/test-backend.yml
+++ b/.github/workflows/test-backend.yml
@@ -29,7 +29,7 @@ jobs:
- 56312:6379
steps:
- - uses: actions/checkout@v3.3.0
+ - uses: actions/checkout@v4.1.1
with:
submodules: true
- name: Install pnpm
@@ -38,7 +38,7 @@ jobs:
version: 8
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3.6.0
+ uses: actions/setup-node@v4.0.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml
index 87b9a26f9b..59e243c80f 100644
--- a/.github/workflows/test-frontend.yml
+++ b/.github/workflows/test-frontend.yml
@@ -16,7 +16,7 @@ jobs:
node-version: [20.x]
steps:
- - uses: actions/checkout@v3.3.0
+ - uses: actions/checkout@v4.1.1
with:
submodules: true
- name: Install pnpm
@@ -25,7 +25,7 @@ jobs:
version: 8
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3.6.0
+ uses: actions/setup-node@v4.0.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
@@ -68,7 +68,7 @@ jobs:
- 56312:6379
steps:
- - uses: actions/checkout@v3.3.0
+ - uses: actions/checkout@v4.1.1
with:
submodules: true
# https://github.com/cypress-io/cypress-docker-images/issues/150
@@ -83,7 +83,7 @@ jobs:
version: 7
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3.6.0
+ uses: actions/setup-node@v4.0.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
@@ -101,7 +101,7 @@ jobs:
- name: Cypress install
run: pnpm exec cypress install
- name: Cypress run
- uses: cypress-io/github-action@v5
+ uses: cypress-io/github-action@v6
with:
install: false
start: pnpm start:test
diff --git a/.github/workflows/test-misskey-js.yml b/.github/workflows/test-misskey-js.yml
index 213657ce1f..3e880b57ea 100644
--- a/.github/workflows/test-misskey-js.yml
+++ b/.github/workflows/test-misskey-js.yml
@@ -21,12 +21,12 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v3.3.0
+ uses: actions/checkout@v4.1.1
- run: corepack enable
- name: Setup Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3.6.0
+ uses: actions/setup-node@v4.0.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
diff --git a/.github/workflows/test-production.yml b/.github/workflows/test-production.yml
index 8429465b5b..46038173cf 100644
--- a/.github/workflows/test-production.yml
+++ b/.github/workflows/test-production.yml
@@ -19,7 +19,7 @@ jobs:
node-version: [20.x]
steps:
- - uses: actions/checkout@v3.3.0
+ - uses: actions/checkout@v4.1.1
with:
submodules: true
- name: Install pnpm
@@ -28,7 +28,7 @@ jobs:
version: 8
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3.6.0
+ uses: actions/setup-node@v4.0.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5385b800ef..a5ee835466 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,5 @@
+## 2023.11.1
+
+### General
+- Feat: 管理者がコントロールパネルからメールアドレスの照会を行えるようになりました
+- Enhance: ローカリゼーションの更新
+- Enhance: 依存関係の更新
+
+### Client
+- Enhance: MFMでルビを振れるように
+ - 例: `$[ruby 三須木 みすき]`
+- Enhance: MFMでUNIX時間を指定して日時を表示できるように
+ - 例: `$[unixtime 1701356400]`
+- Enhance: プラグインでエラーが発生した場合のハンドリングを強化
+- Enhance: 細かなUIのブラッシュアップ
+- Fix: 効果音が再生されるとデバイスで再生している動画や音声が停止する問題を修正 #12339
+- Fix: デッキに表示されたチャンネルの表示先チャンネルを切り替えた際、即座に反映されない問題を修正 #12236
+- Fix: プラグインでノートの表示を書き換えられない問題を修正
+- Fix: アイコンデコレーションが見切れる場合がある問題を修正
+- Fix: 「フォロー中の人全員の返信を含める/含めないようにする」のボタンを押下した際の確認が機能していない問題を修正
+- Fix: 非ログイン時に「メモを追加」を表示しないように変更 #12309
+- Fix: 絵文字ピッカーでの検索が更新されない問題を修正
+- Fix: 特定の条件下でノートがnyaizeされない問題を修正
+
+### Server
+- Enhance: FTTのデータベースへのフォールバック処理を行うかどうかを設定可能に
+- Fix: トークンのないプラグインをアンインストールするときにエラーが出ないように
+- Fix: 投稿通知がオンでもダイレクト投稿はユーザーに通知されないようにされました
+- Fix: ユーザタイムラインの「ノート」選択時にリノートが混ざり込んでしまうことがある問題の修正 #12306
+- Fix: LTLに特定条件下にてチャンネルへの投稿が混ざり込む現象を修正
+- Fix: ActivityPub: 追加情報のカスタム絵文字がユーザー情報のtagに含まれない問題を修正
+- Fix: ActivityPubに関するセキュリティの向上
+- Fix: 非公開の投稿に対して返信できないように
+
+## 2023.11.0
+
+### Note
+- iOS 16.4未満を使用している場合はiOS 16.4以上にアップデートをお願いします
+
+### General
+- Feat: アイコンデコレーション機能
+ - サーバーで用意された画像をアイコンに重ねることができます
+ - 画像のテンプレートはこちらです: https://misskey-hub.net/avatar-decoration-template.png
+ - 最大でも黄色いエリア内にデコレーションを収めることを推奨します。
+ - 画像は512x512pxを推奨します。
+- Feat: チャンネル設定にリノート/引用リノートの可否を設定できる項目を追加
+- Enhance: アカウント登録時のメールアドレス認証に30分の有効期限を設定
+ - 有効期限が切れた後であれば、登録時に使用した招待コードを再度利用できるように変更しました。
+ - ユーザーが誤ったメールアドレスを入力した場合に招待コードが失効してしまう問題が解消されます。
+- Enhance: すでにフォローしたすべての人の返信をTLに追加できるように
+- Enhance: 未読の通知数を表示できるように
+- Enhance: 通知されず、確認の必要もないお知らせ(silence)を作成可能になりました
+- Enhance: ローカリゼーションの更新
+- Enhance: 依存関係の更新
+- Change: CWを使用する場合、注釈を空にすることは許可されなくなりました
+
+### Client
+- Feat: プラグイン・テーマを外部サイトから直接インストールできるようになりました
+ - 外部サイトでの実装が必要です。詳細は Misskey Hub をご覧ください
+ https://misskey-hub.net/docs/advanced/publish-on-your-website.html
+- Feat: 通知をグルーピングして表示するオプション(オプトアウト)
+- Feat: Misskeyの基本的なチュートリアルを実装
+- Feat: スワイプしてタイムラインを再読込できるように
+ - PCの場合は右上のボタンからでも再読込できます
+- Enhance: タイムラインの自動更新を無効にできるように
+- Enhance: コードのシンタックスハイライトエンジンをShikiに変更
+ - AiScriptのシンタックスハイライトに対応
+ - MFMでAiScriptをハイライトする場合、コードブロックの開始部分を ` ```is ` もしくは ` ```aiscript ` としてください
+- Enhance: データセーバー有効時はアニメーション付きのアバター画像が停止するように
+- Enhance: プラグインを削除した際には、使用されていたアクセストークンも同時に削除されるようになりました
+- Enhance: プラグインで`Plugin:register_note_view_interruptor`を用いてnoteの代わりにnullを返却することでノートを非表示にできるようになりました
+- Enhance: AiScript関数`Mk:nyaize()`が追加されました
+- Enhance: 情報→ツール はナビゲーションバーにツールとして独立した項目になりました
+- Enhance: ノート内の絵文字をクリックすることで、コピーおよびリアクションができるように
+- Enhance: その他細かなブラッシュアップ
+- Fix: 投稿フォームでのユーザー変更がプレビューに反映されない問題を修正
+- Fix: ユーザーページの ノート > ファイル付き タブにリプライが表示されてしまう
+- Fix: 「検索」MFMにおいて一部の検索キーワードが正しく認識されない問題を修正
+- Fix: 一部の言語でMisskey Webがクラッシュする問題を修正
+- Fix: チャンネルの作成・更新時に失敗した場合何も表示されない問題を修正 #11983
+- Fix: 個人カードのemojiがバッテリーになっている問題を修正
+- Fix: 標準テーマと同じIDを使用してインストールできてしまう問題を修正
+- Fix: 絵文字ピッカーでバッテリーの絵文字が複数表示される問題を修正 #12197
+- Fix: 11以上されているリアクションにおいてツールチップで示されるリアクション数が本来よりも1多い問題を修正 #12174
+- Fix: サイレンス状態で公開範囲のパブリックを選択できてしまう問題を修正 #12224
+- Fix: In deck layout, replies option is not saved after refresh
+- Fix: アーカイブしたお知らせがコントロールパネルに表示される問題を修正
+- Note: アップデート後、サウンドに関する設定が初期化されます
+
+### Server
+- Feat: Registry APIがサードパーティから利用可能になりました
+- Enhance: RedisへのTLのキャッシュ(FTT)をオフにできるように
+- Enhance: フォローしているチャンネルをフォロー解除した時(またはその逆)、タイムラインに反映される間隔を改善
+- Enhance: プロフィールの自己紹介欄のMFMが連合するようになりました
+ - 相手がMisskey v2023.11.0以降である必要があります
+- Enhance: チャンネル取得時のパフォーマンスを向上
+- Enhance: AP: ApplicationタイプのアカウントをisBotとして扱うように
+- Fix: リストTLに自分のフォロワー限定投稿が含まれない問題を修正
+- Fix: ローカルタイムラインに投稿者自身の投稿への返信が含まれない問題を修正
+- Fix: 自分のフォローしているユーザーの自分のフォローしていないユーザーの visibility: followers な投稿への返信がストリーミングで流れてくる問題を修正
+- Fix: RedisへのTLキャッシュが有効の場合にHTL/LTL/STLが空になることがある問題を修正
+- Fix: STLでフォローしていないチャンネルが取得される問題を修正
+- Fix: `hashtags/trend`にてRedisからトレンドの情報が取得できない際にInternal Server Errorになる問題を修正
+- Fix: HTLをリロードまたは遡行したとき、フォローしているチャンネルのノートが含まれない問題を修正 #11765 #12181
+- Fix: リノートをリノートできるのを修正
+- Fix: アクセストークンを削除すると、通知が取得できなくなる場合がある問題を修正
+- Fix: 自身の宛先なしダイレクト投稿がストリーミングで流れてこない問題を修正
+- Fix: サーバーサイドからのテスト通知を正しく行えるように修正
+- Fix: GTLの「リノートを表示」オプションが機能しないのを修正 #12233
+
+## 2023.10.2
+
+### General
+- Feat: アンテナでローカルの投稿のみ収集できるようになりました
+- Feat: サーバーサイレンス機能が追加されました
+- Enhance: 新規にフォローした人の返信をデフォルトでTLに追加できるオプションを追加
+- Enhance: HTL/LTL/STLを2023.10.0アップデート以前まで遡れるように
+- Enhance: フォロー/フォロー解除したときに過去分のHTLにも含まれる投稿が反映されるように
+- Enhance: ローカリゼーションの更新
+- Enhance: 依存関係の更新
+
+### Client
+- Enhance: TLの返信表示オプションを記憶するように
+- Enhance: 投稿されてから時間が経過しているノートであることを視覚的に分かりやすく
+
+### Server
+- Enhance: タイムライン取得時のパフォーマンスを向上
+- Enhance: ストリーミングAPIのパフォーマンスを向上
+- Fix: users/notesでDBから参照した際にチャンネル投稿のみ取得される問題を修正
+- Fix: コントロールパネルの設定項目が正しく保存できない問題を修正
+- Fix: 管理者権限のロールを持っていても一部のAPIが使用できないことがある問題を修正
+- Change: ユーザーのisCatがtrueでも、サーバーではnyaizeが行われなくなりました
+ - isCatな場合、クライアントでnyaize処理を行うことを推奨します
+
+## 2023.10.1
+### General
+- Enhance: ローカルタイムライン、ソーシャルタイムラインで返信を含むかどうか設定可能に
+
+### Client
+- Fix: 絵文字ピッカーで横に長いカスタム絵文字が見切れる問題を修正
+
+### Server
+- Fix: フォローしているユーザーからの自分の投稿への返信がタイムラインに含まれない問題を修正
+- Fix: users/notesでセンシティブチャンネルの投稿が含まれる場合がある問題を修正
+
+## 2023.10.0
+### NOTE
+- 2023.9.2で導入されたノート編集機能はクオリティの高い実装が困難であることが判明したため撤回されました
+- アップデートを行うと、タイムラインが一時的にリセットされます
+ - アンテナ内のノートも含む
+- ソフトミュート設定はクライアントではなくサーバー側に保存されるようになったため、アップデートを行うとソフトミュートの設定がリセットされます
+
+### Changes
+- API: users/notes, notes/local-timeline で fileType 指定はできなくなりました
+- API: notes/featured でページネーションは他APIと同様 untilId を使って行うようになりました
+
+### General
+- Feat: ユーザーごとに他ユーザーへの返信をタイムラインに含めるか設定可能になりました
+- Feat: ユーザーリスト内のメンバーごとに他ユーザーへの返信をユーザーリストタイムラインに含めるか設定可能になりました
+- Feat: ユーザーごとのハイライト
+- Feat: プライバシーポリシー・運営者情報(Impressum)の指定が可能になりました
+ - プライバシーポリシーはサーバー登録時に同意確認が入ります
+- Feat: タイムラインがリアルタイム更新中に広告を挿入できるようになりました
+ - デフォルトは無効
+ - 頻度はコントロールパネルから設定できます。運営中のサーバーのTLの流速を見て、最適な値を指定してください。
+- Enhance: ソフトワードミュートとハードワードミュートは統合されました
+- Enhance: モデレーションログ機能の強化
+- Enhance: ローカリゼーションの更新
+- Enhance: 依存関係の更新
+- Fix: ダイレクト投稿をリノートできてしまう問題を修正
+- Fix: ユーザーリストTLにチャンネル投稿が含まれる問題を修正
+
+### Client
+- Feat: 「ファイルの詳細」ページを追加
+ - ドライブのファイルの拡大プレビューができるように
+ - ファイルが添付されたノートの一覧が表示できるように
+- Enhance: 二要素認証のバックアップコード一覧をテキストファイルでダウンロード可能に
+- Enhance: 動画再生時のデフォルトボリュームを30%に
+- Fix: リアクションしたユーザ一覧のUIが稀に左上に残ってしまう不具合を修正
+
+### Server
+- Enhance: drive/files/attached-notes がページネーションに対応しました
+- Enhance: タイムライン取得時のパフォーマンスを大幅に向上
+- Enhance: ハイライト取得時のパフォーマンスを大幅に向上
+- Enhance: トレンドハッシュタグ取得時のパフォーマンスを大幅に向上
+- Enhance: WebSocket接続が多い場合のパフォーマンスを向上
+- Enhance: 不要なPostgreSQLのインデックスを削除しパフォーマンスを向上
+- Fix: 連合なしアンケートに投票をするとUpdateがリモートに配信されてしまうのを修正
+- Fix: nodeinfoにおいてCORS用のヘッダーが設定されていないのを修正
+- Fix: 同じ種類のTLのストリーミングを複数接続できない問題を修正
+- Fix: アンテナTLを途中までしかページネーションできなくなることがある問題を修正
+- Fix: 「ファイル付きのみ」のTLでファイル無しの新着ノートが流れる問題を修正
+
+## 2023.9.3
+### General
+- Enhance: ノートの翻訳機能の利用可否をロールで設定可能に
+
+### Client
+- Enhance: AiScriptでホストのアドレスを参照する定数`SERVER_URL`を追加
+- Enhance: モデレーションログ機能の強化
+- Enhance: ローカリゼーションの更新
+
+### Server
+- Fix: Redisに古いバージョンのキャッシュが残っている場合、キャッシュが消えるまでの間通知が届かなくなる問題を修正
+- Fix: 後方互換性の修正
+
+## 2023.9.2
+
+### General
+- Feat: ノートの編集をできるように
+ - ロールで編集可否を設定可能
+- Feat: 通知を種類ごとに 全員から受け取る/フォロー中のユーザーのみ受け取る/フォロワーのみ受け取る/相互のみ受け取る/指定したリストのメンバーのみ受け取る/受け取らない から選べるように
+- Enhance: タイムラインからRenoteを除外するオプションを追加
+- Enhance: ユーザーページのノート一覧でRenoteを除外できるように
+- Enhance: タイムラインでファイルが添付されたノートのみ表示するオプションを追加
+- Enhance: モデレーションログ機能の強化
+- Enhance: 依存関係の更新
+- Enhance: ローカリゼーションの更新
+
+### Client
+- Enhance: Plugin:register_post_form_actionを用いてCWを取得・変更できるように
+- Enhance: admin/ad/listにて掲載中の広告が絞り込めるように
+- Enhance: AiScriptにリモートサーバーのAPIを叩く用の関数を追加(`Mk:apiExternal`)
+
+### Server
+- Enhance: MasterプロセスのPIDを書き出せるように
+- Enhance: admin/ad/createにてレスポンス200、設定した広告情報を返すように
+
+## 2023.9.1
+
+### General
+- Enhance: モデレーションログ機能の強化
+
+### Client
+- Fix: ノートのメニューにある「詳細」ボタンの表示がログイン/ログアウト状態で統一されていない問題を修正
+
+### Server
+- Fix: お知らせのページネーションが機能しない
+- Fix: 「ユーザーの新規投稿」の通知設定を切り替えるとサーバー内部エラーが出る
+
+## 2023.9.0
+
+### Note
+- meilisearchを使用する場合、v1.2以上が必要です
+
+### General
+- Feat: OAuth 2.0のサポート
+- Feat: お知らせ機能の強化
+ - ユーザー個別のお知らせを作成可能に
+ - お知らせのバナー表示やダイアログ表示が可能に
+ - お知らせのアイコンを設定可能に
+- Feat: チャンネルをセンシティブ指定できるようになりました
+ - センシティブチャンネルのNoteのReNoteはデフォルトでHome TLに流れるようになりました
+ - センシティブチャンネルのノートはユーザープロフィールに表示されません
+- Feat: 二要素認証のバックアップコードが生成されるようになりました
+ - ref. https://github.com/MisskeyIO/misskey/pull/121
+- Feat: 二要素認証でパスキーをサポートするようになりました
+- Feat: 指定したユーザーが投稿したときに通知できるようになりました
+- Feat: プロフィールでのリンク検証
+- Feat: モデレーションログ機能
+- Feat: 通知をテストできるようになりました
+- Feat: PWAのアイコンが設定できるようになりました
+- Enhance: サーバー名の略称が設定できるようになりました
+- Enhance: アンテナの受信ソースに指定したユーザを除外するものを追加
+- Enhance: 二要素認証設定時のセキュリティを強化
+ - パスワード入力が必要な操作を行う際、二要素認証が有効であれば確認コードの入力も必要になりました
+- Enhance: manifest.jsonをオーバーライド可能に
+- Enhance: 依存関係の更新
+- Enhance: ローカリゼーションの更新
+
+### Client
+- Feat: 任意のユーザーリストをタイムラインページにピン留めできるように
+ - 設定->クライアント設定->全般 から設定可能です
+- Feat: Playで直接投稿フォームを埋め込めるように(`Ui:C:postForm`)
+- Feat: クライアントを起動している間、デバイスの画面が自動でオフになるのを防ぐオプションを追加
+- Feat: 新しい実績を追加
+- Enhance: ノート詳細ページでリノート一覧、リアクション一覧タブを追加
+ - ノートのメニューからは当該項目は消えました
+- Enhance: センシティブなメディアを目立たせる設定を追加
+- Enhance: プロフィールにその人が作ったPlayの一覧出せるように
+- Enhance: メニューのスイッチの動作を改善
+- Enhance: 絵文字ピッカーの検索の表示件数を100件に増加
+- Enhance: 投稿フォームのプレビューの表示状態を記憶するように
+- Enhance: ユーザーメニューでスイッチでユーザーリストに追加・削除できるように
+- Enhance: 自分が押したリアクションのデザインを改善
+- Enhance: ノート検索にローカルのみ検索可能なオプションの追加
+- Enhance: Renote自体を通報できるように
+- Enhance: データセーバーモードの強化
+- Enhance: Renoteを管理者権限で削除可能に
+- Enhance: `$[rainbow ]`記法が、動きのあるMFMが無効になっていても使用できるようになりました
+- Enhance: Playの操作を行うAPI TokenをAPIコンソールから発行できるように
+- Enhance: リアクションの表示サイズをより大きくできるように
+- Enhance: AiScriptを0.16.0に更新
+- Enhance: AiScriptからMisskeyサーバーAPIを呼び出す際の制限を撤廃
+- Enhance: AiScriptで`LOCALE`として現在の設定言語を取得できるように
+- Enhance: Mk:apiが失敗した時にエラー型の値(AiScript 0.16.0で追加)を返すように
+- Enhance: ScratchpadでAsync:系関数やボタンのコールバックなどのエラーにもダイアログを出すように(試験的なためPlayなどには未実装)
+- Enhance: ノート詳細ページ読み込み時のパフォーマンスが向上しました
+- Enhance: タイムラインでリスト/アンテナ選択時のパフォーマンスを改善
+- Enhance: 「Moderation note」、「Add moderation note」をローカライズできるように
+- Enhance: プラグインのソースコードを確認・コピーできるように
+- Enhance: 細かなデザインの調整
+- Fix: サーバー情報画面(`/instance-info/{domain}`)でブロックができないのを修正
+- Fix: 未読のお知らせの「わかった」をクリック・タップしてもその場で「わかった」が消えない問題を修正
+- Fix: iOSで画面を回転させるとテキストサイズが変わる問題を修正
+- Fix: word mute for sub note is not applied
+- Fix: タイムラインを下にスクロールしてノート画面に移動して再び戻ったら以前のスクロール位置を失う問題を修正
+- Fix: Misskeyプラグインをインストールする際のAiScriptバージョンのチェックが0.14.0以降に対応していない問題を修正
+- Fix: 他のサーバーのユーザーへ「メッセージを送信」した時の初期テキストのメンションが間違っている問題を修正
+- Fix: 環境によってはMisskey Webが開けない問題を修正
+- Fix: プラグインの権限リストが見れない問題を修正
+- Fix: 複数の階層があるメニューで、短くタップすると正常に動かない場合がある問題を修正
+- Fix: アニメーションがオフのとき、スマホで子メニューの選択ができない問題を修正
+- Fix: ドロワーメニューで、親メニュー項目をマウスでホバーすると子メニューが表示されてしまう問題を修正
+- Fix: AiScriptでMk:apiが外部と通信できる問題を修正
+
+### Server
+- Change: cacheRemoteFilesの初期値はfalseになりました
+- Enhance: ファイルアップロード時等にファイル名の拡張子を修正する関数(correctFilename)の挙動を改善
+- Enhance: Webhookのペイロードにサーバーのurlが含まれるようになりました
+- Enhance: Webhook設定でsecretを空に出来るように
+- Enhance: 使われていないアンテナの自動停止を設定可能に
+- Enhance: nodeinfo 2.1対応
+- Enhance: 自分へのメンション一覧を取得する際のパフォーマンスを向上
+- Enhance: Docker環境でjemallocを使用することでメモリ使用量を削減
+- Enhance: ID生成方式としてaidxを追加、かつデフォルトに
+- Enhance: Add address bind config option (outgoingAddress)
+- Fix: MK_ONLY_SERVERオプションを指定した際にクラッシュする問題を修正
+- Fix: notes/reactionsのページネーションが機能しない問題を修正
+- Fix: ノート検索 `notes/search` にてhostを指定した際に検索結果に反映されるように
+- Fix: 一部のfeatured noteを照会できない問題を修正
+- Fix: muteがapiからのuser list timeline取得で機能しない問題を修正
+- Fix: ジョブキュー管理画面の認証を回避できる問題を修正
+- Fix: 一部のサーバー内部エラーがスタックトレースを返さないように修正
+- Fix: 一部のリモートユーザーをフォローすることができない問題を修正
+
## 13.14.2
### Client
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index cd9cf8302a..1bbfb082af 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -2,45 +2,131 @@
## 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
-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
-* Being respectful of differing viewpoints and experiences
-* Gracefully accepting constructive criticism
-* Focusing on what is best for the community
-* Showing empathy towards other community members
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+ 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
-* Trolling, insulting/derogatory comments, and personal or political attacks
+* The use of sexualized language or imagery, and sexual attention or advances of
+ any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
-* Publishing others' private information, such as a physical or electronic address, without explicit permission
-* Other conduct which could reasonably be considered inappropriate in a professional setting
+* Publishing others' private information, such as a physical or email address,
+ 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
-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
-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 at
+.
+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
-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].
-[homepage]: http://contributor-covenant.org
-[version]: http://contributor-covenant.org/version/1/4/
+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
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 62bc11cd99..13e0656041 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -15,7 +15,7 @@ Before creating an issue, please check the following:
- To avoid duplication, please search for similar issues before creating a new issue.
- Do not use Issues to ask questions or troubleshooting.
- Issues should only be used to feature requests, suggestions, and bug tracking.
- - Please ask questions or troubleshooting in ~~the [Misskey Forum](https://forum.misskey.io/)~~ [GitHub Discussions](https://github.com/misskey-dev/misskey/discussions) or [Discord](https://discord.gg/Wp8gVStHW3).
+ - Please ask questions or troubleshooting in [GitHub Discussions](https://github.com/misskey-dev/misskey/discussions) or [Discord](https://discord.gg/Wp8gVStHW3).
> **Warning**
> Do not close issues that are about to be resolved. It should remain open until a commit that actually resolves it is merged.
@@ -301,6 +301,12 @@ export const handlers = [
Don't forget to re-run the `.storybook/generate.js` script after adding, editing, or removing the above files.
## Notes
+
+### Misskeyのドメイン固有の概念は`Mi`をprefixする
+例えばGoogleが自社サービスをMap、Earth、DriveではなくGoogle Map、Google Earth、Google Driveのように命名するのと同じ
+コード上でMisskeyのドメイン固有の概念には`Mi`をprefixすることで、他のドメインの同様の概念と区別できるほか、名前の衝突を防ぐ。
+ただし、文脈上Misskeyのものを指すことが明らかであり、名前の衝突の恐れがない場合は、一時的なローカル変数に限って`Mi`を省略してもよい。
+
### How to resolve conflictions occurred at pnpm-lock.yaml?
Just execute `pnpm` to fix it.
@@ -430,3 +436,6 @@ marginはそのコンポーネントを使う側が設定する
## その他
### HTMLのクラス名で follow という単語は使わない
広告ブロッカーで誤ってブロックされる
+
+### indexというファイル名を使うな
+ESMではディレクトリインポートは廃止されているのと、ディレクトリインポートせずともファイル名が index だと何故か一部のライブラリ?でディレクトリインポートだと見做されてエラーになる
diff --git a/Dockerfile b/Dockerfile
index bd08de841b..2b3ec0da78 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -68,7 +68,7 @@ ARG GID="991"
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
- ffmpeg tini curl libjemalloc2 \
+ ffmpeg tini curl libjemalloc-dev libjemalloc2 \
&& ln -s /usr/lib/$(uname -m)-linux-gnu/libjemalloc.so.2 /usr/local/lib/libjemalloc.so \
&& corepack enable \
&& groupadd -g "${GID}" misskey \
diff --git a/ROADMAP.md b/ROADMAP.md
index 420f728758..3077c41e73 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -22,7 +22,7 @@ This is the phase we are at now. We need to make a high-maintenance environment
Once Phase 1 is complete and an environment conducive to the development of a stable system is in place, the implementation of new functions can begin gradually.
- Improve features for moderation
-- OAuth2 support https://github.com/misskey-dev/misskey/issues/8262
+- ~~OAuth2 support https://github.com/misskey-dev/misskey/issues/8262~~ → Done ✔️
- GraphQL support?
## (3) Improve scalability
diff --git a/chart/files/default.yml b/chart/files/default.yml
index e62032abfd..87b2f677eb 100644
--- a/chart/files/default.yml
+++ b/chart/files/default.yml
@@ -116,6 +116,14 @@ redis:
# #prefix: example-prefix
# #db: 1
+#redisForTimelines:
+# host: redis
+# port: 6379
+# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
+# #pass: example-pass
+# #prefix: example-prefix
+# #db: 1
+
# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────
@@ -135,6 +143,7 @@ redis:
# Available methods:
# aid ... Short, Millisecond accuracy
+# aidx ... Millisecond accuracy
# meid ... Similar to ObjectID, Millisecond accuracy
# ulid ... Millisecond accuracy
# objectid ... This is left for backward compatibility
@@ -142,7 +151,7 @@ redis:
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# ID SETTINGS AFTER THAT!
-id: "aid"
+id: "aidx"
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
diff --git a/cypress/e2e/widgets.cy.js b/cypress/e2e/widgets.cy.js
index f5a982eb0a..df6ec8357d 100644
--- a/cypress/e2e/widgets.cy.js
+++ b/cypress/e2e/widgets.cy.js
@@ -1,3 +1,4 @@
+/* flaky
describe('After user signed in', () => {
beforeEach(() => {
cy.resetState();
@@ -67,3 +68,4 @@ describe('After user signed in', () => {
buildWidgetTest('aiscript');
buildWidgetTest('aichan');
});
+*/
diff --git a/docker-compose.yml.example b/docker-compose.yml.example
index e4f8a7f167..830c1a294d 100644
--- a/docker-compose.yml.example
+++ b/docker-compose.yml.example
@@ -50,7 +50,7 @@ services:
# meilisearch:
# restart: always
-# image: getmeili/meilisearch:v1.1.1
+# image: getmeili/meilisearch:v1.3.4
# environment:
# - MEILI_NO_ANALYTICS=true
# - MEILI_ENV=production
diff --git a/gulpfile.mjs b/gulpfile.mjs
deleted file mode 100644
index aaa96cf4d1..0000000000
--- a/gulpfile.mjs
+++ /dev/null
@@ -1,73 +0,0 @@
-/**
- * Gulp tasks
- */
-
-import * as fs from 'node:fs';
-import gulp from 'gulp';
-import rename from 'gulp-rename';
-import replace from 'gulp-replace';
-import terser from 'gulp-terser';
-import cssnano from 'gulp-cssnano';
-
-import locales from './locales/index.js';
-import meta from './package.json' assert { type: "json" };
-
-gulp.task('copy:backend:views', () =>
- gulp.src('./packages/backend/src/server/web/views/**/*').pipe(gulp.dest('./packages/backend/built/server/web/views'))
-);
-
-gulp.task('copy:frontend:fonts', () =>
- gulp.src('./packages/frontend/node_modules/three/examples/fonts/**/*').pipe(gulp.dest('./built/_frontend_dist_/fonts/'))
-);
-
-gulp.task('copy:frontend:tabler-icons', () =>
- gulp.src('./packages/frontend/node_modules/@tabler/icons-webfont/**/*').pipe(gulp.dest('./built/_frontend_dist_/tabler-icons/'))
-);
-
-gulp.task('copy:frontend:locales', cb => {
- fs.mkdirSync('./built/_frontend_dist_/locales', { recursive: true });
-
- const v = { '_version_': meta.version };
-
- for (const [lang, locale] of Object.entries(locales)) {
- fs.writeFileSync(`./built/_frontend_dist_/locales/${lang}.${meta.version}.json`, JSON.stringify({ ...locale, ...v }), 'utf-8');
- }
-
- cb();
-});
-
-gulp.task('build:backend:script', () => {
- const clientManifestExists = fs.existsSync('./built/_vite_/manifest.json');
- const clientEntry = clientManifestExists ?
- JSON.parse(fs.readFileSync('./built/_vite_/manifest.json', 'utf-8'))['src/_boot_.ts'].file
- : 'src/_boot_.ts'
-
- return gulp.src(['./packages/backend/src/server/web/boot.js', './packages/backend/src/server/web/bios.js', './packages/backend/src/server/web/cli.js', './packages/backend/src/server/web/flush.js'])
- .pipe(replace('LANGS', JSON.stringify(Object.keys(locales))))
- .pipe(replace('CLIENT_ENTRY', JSON.stringify(clientEntry)))
- .pipe(terser({
- toplevel: true
- }))
- .pipe(rename({ suffix: `.${meta.version}` }))
- .pipe(gulp.dest('./built/_frontend_dist_/'));
-});
-
-gulp.task('build:backend:style', () => {
- return gulp.src(['./packages/backend/src/server/web/style.css', './packages/backend/src/server/web/bios.css', './packages/backend/src/server/web/cli.css', './packages/backend/src/server/web/error.css'])
- .pipe(cssnano({
- zindex: false
- }))
- .pipe(gulp.dest('./packages/backend/built/server/web/'));
-});
-
-gulp.task('build', gulp.parallel(
- 'copy:frontend:locales', 'copy:backend:views', 'build:backend:script', 'build:backend:style', 'copy:frontend:fonts', 'copy:frontend:tabler-icons'
-));
-
-gulp.task('default', gulp.task('build'));
-
-gulp.task('watch', () => {
- gulp.watch([
- './packages/*/src/**/*',
- ], { ignoreInitial: false }, gulp.task('build'));
-});
diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml
index d7df0233f0..d62990b7b7 100644
--- a/locales/ar-SA.yml
+++ b/locales/ar-SA.yml
@@ -348,7 +348,6 @@ invite: "دعوة"
driveCapacityPerLocalAccount: "حصة التخزين لكل مستخدم محلي"
driveCapacityPerRemoteAccount: "حصة التخزين لكل مستخدم بعيد"
inMb: "بالميغابايت"
-iconUrl: "رابط الأيقونة"
bannerUrl: "رابط صورة اللافتة"
backgroundImageUrl: "رابط صورة الخلفية"
basicInfo: "المعلومات الأساسية "
@@ -645,6 +644,7 @@ optional: "اختياري"
createNewClip: "أنشئ مِشبكَا جديدًا"
confirmToUnclipAlreadyClippedNote: "هذه الملاحظة تنتمي للمشبك {name} سلفًا، أتريد حذفها منه⸮"
public: "علني"
+private: "خاص"
i18nInfo: "يترجم متطوعون ميسكي إلى عدة لغات، يمكنك المساعدة عبر {link}"
manageAccessTokens: "إدارة رموز الوصول"
accountInfo: "معلومات الحساب"
@@ -798,6 +798,7 @@ accountDeletionInProgress: "حذف الحساب جارٍ"
usernameInfo: "الاسم الذي يميزك عن بافي مستخدمي هذا الخادم، يمكنك استخدام الحروف اللاتينية (a~z, A~Z) والأرقام (0~9) والشرطة السفلية (_). لا يمكنك تغييره بعد تسجيله."
devMode: "وضع المُطوّر"
keepCw: "أبقِ على تحذيرات المحتوى"
+pubSub: "حسابات Pub/Sub"
lastCommunication: "آخر تواصل"
resolved: "عولج"
unresolved: "لم يعالج"
@@ -806,6 +807,7 @@ breakFollowConfirm: "أمتأكد من إزالة المتابِع ؟"
itsOn: "مفعّل"
itsOff: "معطّل"
on: "مفعل"
+off: "معطل"
emailRequiredForSignup: "عنوان البريد الإلكتروني إلزامي للتسجيل"
unread: "غير مقروءة"
filter: "رشّح"
@@ -852,6 +854,7 @@ recentNDays: "آخر {n} أيام"
noEmailServerWarning: "خادم البريد غير مضبوط."
thereIsUnresolvedAbuseReportWarning: "توجد بلاغات غير معالجة."
recommended: "مقترح"
+check: "التحقق"
driveCapOverrideLabel: "غيّر حجم قرص التخزين لهذا المستخدم"
driveCapOverrideCaption: "أعد الحجم إلى القيمة الافتراضية بإدخال 0 أو أقل."
requireAdminForView: "لاستعراض هذه الصفحة وجب عليك الولوج كمدير."
@@ -875,6 +878,7 @@ slow: "بطيء"
fast: "سريع"
sensitiveMediaDetection: "التعرف على المحتوى الحساس"
localOnly: "المحلي فقط"
+remoteOnly: "بُعدي فقط"
failedToUpload: "فشل الرفع"
cannotUploadBecauseInappropriate: "تعذر رفع الملف لوجود محتوى حساس فيه."
cannotUploadBecauseNoFreeSpace: "تعذر رفع الملف لنقص مساحة التخزين."
@@ -894,6 +898,7 @@ pushNotificationAlreadySubscribed: "إرسال الإشعارات مفعل سل
pushNotificationNotSupported: "متصفحك لا يدعم إرسال الإشعارات أو المثيل لا يدعمها."
sendPushNotificationReadMessage: "احذف الإشعارات فور قراءتها"
sendPushNotificationReadMessageCaption: "هذا قد يزيد من معدل استهلاك الطاقة لجهازك."
+windowMaximize: "املأ الشاشة"
windowRestore: "استرجاع"
caption: "التعليق التوضيحي"
loggedInAsBot: "والج كآلي"
@@ -951,6 +956,8 @@ accountMoved: "نقل هذا المستخدم حسابه:"
accountMovedShort: "رُحل هذا الحساب."
operationForbidden: "عملية ممنوعة"
forceShowAds: "أظهر الإعلانات التجارية دائما"
+reactionsList: "التفاعلات"
+renotesList: "إعادات النشر"
leftTop: "أعلى اليسار"
rightTop: "أعلى اليمين"
leftBottom: "أسفل اليسار"
@@ -986,6 +993,13 @@ later: "لاحقاً"
goToMisskey: "لميسكي"
additionalEmojiDictionary: "قواميس إيموجي إضافية"
installed: "مُثبت"
+expirationDate: "تاريخ انتهاء الصلاحية"
+unused: "غير مستعمَل"
+expired: "منتهية صلاحيته"
+icon: "الصورة الرمزية"
+replies: "رد"
+renotes: "أعد النشر"
+flip: "اقلب"
_initialAccountSetting:
accountCreated: "نجح إنشاء حسابك!"
letsStartAccountSetup: "إذا كنت جديدًا لنعدّ حسابك الشخصي."
@@ -1071,6 +1085,7 @@ _role:
description: "وصف الدور"
permission: "أذونات الدور"
assignTarget: "نوع الإسناد"
+ condition: "الشرط"
options: "خيارات"
policies: "السياسة العامة"
priority: "الأولوية"
@@ -1126,6 +1141,10 @@ _plugin:
install: "ثبّت إضافات"
installWarn: "رجاءً لا تثبت إضافات غير موثوقة."
manage: "إدارة الإضافات"
+ viewSource: "اظهر المصدر"
+_preferencesBackups:
+ createdAt: "تم إنشاؤه: {date} {time}"
+ updatedAt: "آخر تحديث: {date} {time}"
_registry:
scope: "الحيّز"
key: "مفتاح"
@@ -1166,11 +1185,6 @@ _wordMute:
muteWords: "الكلمات المحظورة"
muteWordsDescription: "افصل بينهم بمسافة لاستخدام معامل \"و\" أو بسطر لاستخدام معامل \"أو\"."
muteWordsDescription2: "احصر الكلمات المفتاحية بين بين شرطتين مائلتين لاستخدامها كتعابير نمطية"
- softDescription: "اخف الملاحظات التي تستوف الشروط من الخيط الزمني."
- hardDescription: "اخف الملاحظات التي تستوف الشروط من الخيط الزمني.بالإضافة إلى أن هذه الملاحظات ستبقى مخفية حتى وإن تغيرت الشروط."
- soft: "لينة"
- hard: "قاسية"
- mutedNotes: "الملاحظات المكتومة"
_instanceMute:
instanceMuteDescription: "هذه سيحجب كل ملاحظات الخوادم المحجوبة ومشاركاتها والردود على تلك الملاحظات حتى وإن كانت من خادم غير محجوب."
instanceMuteDescription2: "مدخلة لكل سطر"
@@ -1230,8 +1244,6 @@ _sfx:
note: "الملاحظات"
noteMy: "ملاحظتي"
notification: "الإشعارات"
- chat: "المحادثة"
- chatBg: "المحادثة (الخلفية)"
antenna: "الهوائيات"
channel: "إشعارات القنات"
_ago:
@@ -1255,7 +1267,7 @@ _2fa:
step1: "أولًا ثبّت تطبيق استيثاق على جهازك (مثل {a} و{b})."
step2: "امسح رمز الاستجابة السريعة الموجد على الشاشة."
step3: "أدخل الرمز الموجود في تطبيقك لإكمال التثبيت."
- step4: "من هذه اللحظة أثناء ولوجك سيُطلب منك الرمز.\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}"
+ step4: "من هذه اللحظة أثناء ولوجك سيُطلب منك الرمز."
renewTOTPCancel: "ليس اﻵن"
_permissions:
"read:account": "اعرض معلومات حسابك"
@@ -1487,22 +1499,22 @@ _notification:
fileUploaded: "نجح رفع الملف"
youGotMention: "{name} أشار إليك"
youGotReply: "ردّ عليك {name}"
- youGotQuote: "اقتبس منك {name}"
- youRenoted: "إعادت نشر من {name}"
+ youGotQuote: "اقتبس {name} منشورك"
+ youRenoted: "أعاد {name} نشر منشورك"
youWereFollowed: "يتابعك"
youReceivedFollowRequest: "تلقيتَ طلب متابعة"
yourFollowRequestAccepted: "قُبل طلب المتابعة"
- pollEnded: "ظهرت نتائج الاستطلاع"
+ pollEnded: "انتهى الاستطلاع"
unreadAntennaNote: "هوائي {name}"
_types:
all: "الكل"
follow: "متابِعون جدد"
mention: "الإشارات"
reply: "الردود"
- renote: "أعد النشر"
+ renote: "أعاد النشر"
quote: "الاقتباسات"
- reaction: "التفاعلات"
- receiveFollowRequest: "طلبات المتابعة المتلقاة"
+ reaction: "التفاعل"
+ receiveFollowRequest: "طلبات المتابعة"
followRequestAccepted: "طلبات المتابعة المقبولة"
app: "إشعارات التطبيقات المرتبطة"
_actions:
@@ -1510,26 +1522,31 @@ _notification:
reply: "رد"
renote: "أعد النشر"
_deck:
- alwaysShowMainColumn: "أظهر العمود الرئيسي دائمًا"
- columnAlign: "حاذِ الأعمدة"
- addColumn: "أضف عمودًا"
- swapLeft: "حرّك لليسار"
- swapRight: "حرّك لليمين"
- swapUp: "حرّك لأعلى"
- swapDown: "حرّك لأسفل"
- profile: "الملف الشخصي"
+ alwaysShowMainColumn: "أظهر العمود الأساسي دائمًا"
+ columnAlign: "محاذاة الأعمدة"
+ addColumn: "إضافة عمود"
+ swapLeft: "التحريك إلى اليسار"
+ swapRight: "التحريك إلى اليمين"
+ swapUp: "التحريك إلى الأعلى"
+ swapDown: "التحريك إلى الأسفل"
+ profile: "حسابي الشخصي"
+ newProfile: "ملف تعريفي جديد"
+ deleteProfile: "حذف الملف التعريفي"
_columns:
- main: "الرئيسي"
- widgets: "الودجات"
+ main: "الرئيسية"
+ widgets: "التطبيقات المُصغّرة"
notifications: "الإشعارات"
- tl: "الخيط الزمني"
+ tl: "الخط الزمني"
antenna: "الهوائيات"
list: "القوائم"
channel: "القنوات"
mentions: "الإشارات"
direct: "مباشرة"
_webhookSettings:
- name: "الإسم"
- active: "مفعّل"
+ name: "الاسم"
+ active: "مُفعّل"
_events:
- reaction: "عند تلقي تفاعل"
+ reaction: "عند التفاعل"
+_moderationLogTypes:
+ suspend: "علِق"
+ resetPassword: "أعد تعيين كلمتك السرية"
diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml
index de9502acf4..31f2b948ed 100644
--- a/locales/bn-BD.yml
+++ b/locales/bn-BD.yml
@@ -328,7 +328,6 @@ invite: "আমন্ত্রণ"
driveCapacityPerLocalAccount: "প্রত্যেক স্থানীয় ব্যাবহারকারীর জন্য ড্রাইভের জায়গা"
driveCapacityPerRemoteAccount: "প্রত্যেক রিমোট ব্যাবহারকারীর জন্য ড্রাইভের জায়গা"
inMb: "মেগাবাইটে লিখুন"
-iconUrl: "আইকনের URL (ফ্যাভিকন, ইত্যাদি)"
bannerUrl: "ব্যানার ছবির URL"
backgroundImageUrl: "পটভূমির চিত্রের URL"
basicInfo: "আপনার ব্যক্তিগত তথ্য"
@@ -628,6 +627,7 @@ createNew: "নতুন"
optional: "প্রয়োজনীয় নয়"
createNewClip: "নতুন ক্লিপ তৈরি করুন"
public: "সর্বজনীন"
+private: "ব্যাক্তিগত"
i18nInfo: "Misskey স্বেচ্ছাসেবকদের দ্বারা বিভিন্ন ভাষায় অনুবাদ করা হচ্ছে। আপনি {link} এ গিয়ে অনুবাদে সহযোগিতা করতে পারেন।"
manageAccessTokens: "অ্যাক্সেস টোকেন পরিচালনা করুন"
accountInfo: "অ্যাকাউন্টের তথ্য"
@@ -837,6 +837,10 @@ show: "প্রদর্শন"
color: "রং"
horizontal: "পাশে"
youFollowing: "অনুসরণ করা হচ্ছে"
+icon: "প্রোফাইল ছবি"
+replies: "জবাব"
+renotes: "রিনোট"
+flip: "উল্টান"
_role:
priority: "অগ্রাধিকার"
_priority:
@@ -886,6 +890,7 @@ _plugin:
install: "প্লাগইন ইন্সটল করুন"
installWarn: "অবিশ্বস্ত প্লাগইন ইনস্টল করবেন না।"
manage: "প্লাগইন ম্যানেজ করুন"
+ viewSource: "উৎস দেখুন"
_registry:
scope: "স্কোপ"
key: "কী"
@@ -928,11 +933,6 @@ _wordMute:
muteWords: "নিঃশব্দ করা শব্দগুলি"
muteWordsDescription: "স্পেস দিয়ে আলাদা করলে AND শর্ত তৈরি হবে এবং আলাদা লাইনে লিখলে OR শর্ত তৈরি হবে।"
muteWordsDescription2: "রেগুলার এক্সপ্রেশন ব্যবহার করতে স্ল্যাশ দিয়ে কীওয়ার্ডকে ঘিরে রাখুন।"
- softDescription: "টাইমলাইন থেকে নির্দিষ্ট শর্তানুযায়ী নোট লুকিয়ে রাখে।"
- hardDescription: "নির্দিষ্ট শর্তানুযায়ী নোটগুলিকে টাইমলাইন থেকে বাদ দেয়। আপনি শর্ত পরিবর্তন করলেও যে নোটগুলি যোগ করা হয়নি সেগুলি বাদ দেওয়া হবে।"
- soft: "নমনীয়"
- hard: "কঠোর"
- mutedNotes: "মিউট করা নোটগুলি"
_instanceMute:
instanceMuteDescription: "কনফিগার করা ইন্সট্যান্সের সব নোট এবং রিনোট মিউট করুন, মিউট করা ইন্সট্যান্সের ব্যবহারকারীদের উত্তর সহ।"
instanceMuteDescription2: "প্রতিটিকে আলাদা লাইনে লিখুন"
@@ -996,9 +996,6 @@ _theme:
infoFg: "তথ্যের পাঠ্য"
infoWarnBg: "ওয়ার্নিং এর পটভূমি"
infoWarnFg: "ওয়ার্নিং এর পাঠ্য"
- cwBg: "CW বাটনের পটভূমি"
- cwFg: "CW বাটনের পাঠ্য"
- cwHoverBg: "CW বাটনের পটভূমি (হভার)"
toastBg: "বিজ্ঞপ্তির পটভূমি"
toastFg: "বিজ্ঞপ্তির পাঠ্য"
buttonBg: "বাটনের পটভূমি"
@@ -1016,8 +1013,6 @@ _sfx:
note: "নোটগুলি"
noteMy: "নোট (আপনার)"
notification: "বিজ্ঞপ্তি"
- chat: "চ্যাট"
- chatBg: "চ্যাট (ব্যাকগ্রাউন্ড)"
antenna: "অ্যান্টেনাগুলি"
channel: "চ্যানেলের বিজ্ঞপ্তি"
_ago:
@@ -1040,9 +1035,8 @@ _2fa:
alreadyRegistered: "আপনি ইতিমধ্যে একটি 2-ফ্যাক্টর অথেনটিকেশন ডিভাইস নিবন্ধন করেছেন৷"
step1: "প্রথমে, আপনার ডিভাইসে {a} বা {b} এর মতো একটি অথেনটিকেশন অ্যাপ ইনস্টল করুন৷"
step2: "এরপরে, অ্যাপের সাহায্যে প্রদর্শিত QR কোডটি স্ক্যান করুন।"
- step2Url: "ডেস্কটপ অ্যাপে, নিম্নলিখিত URL লিখুন:"
step3: "অ্যাপে প্রদর্শিত টোকেনটি লিখুন এবং আপনার কাজ শেষ।"
- step4: "আপনাকে এখন থেকে লগ ইন করার সময়, এইভাবে টোকেন লিখতে হবে।\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}"
+ step4: "আপনাকে এখন থেকে লগ ইন করার সময়, এইভাবে টোকেন লিখতে হবে।"
securityKeyInfo: "আপনি একটি হার্ডওয়্যার সিকিউরিটি কী ব্যবহার করে লগ ইন করতে পারেন যা FIDO2 বা ডিভাইসের ফিঙ্গারপ্রিন্ট সেন্সর বা পিন সমর্থন করে৷"
_permissions:
"read:account": "অ্যাকাউন্টের তথ্য দেখুন"
@@ -1330,3 +1324,6 @@ _deck:
_webhookSettings:
name: "নাম"
active: "চালু"
+_moderationLogTypes:
+ suspend: "স্থগিত করা"
+ resetPassword: "পাসওয়ার্ড রিসেট করুন"
diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index e3a6ec11b2..62ef511bc7 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -13,12 +13,14 @@ fetchingAsApObject: "Cercant en el Fediverse..."
ok: "OK"
gotIt: "Ho he entès!"
cancel: "Cancel·lar"
+noThankYou: "No, gràcies"
enterUsername: "Introdueix el teu nom d'usuari"
renotedBy: "Impulsat per {usuari}"
noNotes: "Cap nota"
noNotifications: "Cap notificació"
instance: "Servidor"
settings: "Preferències"
+notificationSettings: "Paràmetres de notificacions"
basicSettings: "Configuració bàsica"
otherSettings: "Configuració avançada"
openInWindow: "Obrir en una nova finestra"
@@ -43,12 +45,20 @@ pin: "Fixar al perfil"
unpin: "Para de fixar del perfil"
copyContent: "Copiar el contingut"
copyLink: "Copiar l'enllaç"
+copyLinkRenote: "Copiar l'enllaç de la renota"
delete: "Elimina"
deleteAndEdit: "Elimina i edita"
deleteAndEditConfirm: "Segur que vols eliminar aquesta publicació i editar-la? Perdràs totes les reaccions, impulsos i respostes."
addToList: "Afegir a una llista"
+addToAntenna: "Afegir a l'antena"
sendMessage: "Enviar un missatge"
+copyRSS: "Copiar RSS"
copyUsername: "Copiar nom d'usuari"
+copyUserId: "Copiar ID d'usuari"
+copyNoteId: "Copiar ID de nota"
+copyFileId: "Copiar ID d'arxiu"
+copyFolderId: "Copiar ID de carpeta"
+copyProfileUrl: "Copiar URL del perfil"
searchUser: "Cercar un usuari"
reply: "Respondre"
loadMore: "Carregar més"
@@ -101,6 +111,8 @@ renoted: "S'ha impulsat"
cantRenote: "No es pot impulsar aquesta publicació"
cantReRenote: "No es pot impulsar l'impuls."
quote: "Cita"
+inChannelRenote: "Renotar només al Canal"
+inChannelQuote: "Citar només al Canal"
pinnedNote: "Nota fixada"
pinned: "Fixar al perfil"
you: "Tu"
@@ -118,6 +130,8 @@ unmarkAsSensitive: "Deixar de marcar com a sensible"
enterFileName: "Defineix nom del fitxer"
mute: "Silencia"
unmute: "Deixa de silenciar"
+renoteMute: "Silenciar Renotes"
+renoteUnmute: "Treure el silenci de les renotes"
block: "Bloqueja"
unblock: "Desbloqueja"
suspend: "Suspèn"
@@ -127,7 +141,10 @@ unblockConfirm: "Vols desbloquejar-lo?"
suspendConfirm: "Estàs segur que vols suspendre aquest compte?"
unsuspendConfirm: "Estàs segur que vols treure la suspensió d'aquest compte?"
selectList: "Tria una llista"
+editList: "Editar llista"
+selectChannel: "Selecciona un canal"
selectAntenna: "Tria una antena"
+editAntenna: "Modificar antena"
selectWidget: "Triar un giny"
editWidgets: "Editar ginys"
editWidgetsExit: "Fet"
@@ -298,8 +315,10 @@ manageAntennas: "Gestiona les antenes"
antennaSource: "Font de l'antena"
antennaKeywords: "Paraules clau a seguir"
antennaExcludeKeywords: "Paraules clau a excloure"
+antennaKeywordsDescription: "Separar amb espais per la condició AND o amb salts de línia per la condició OR."
notifyAntenna: "Notifica'm les publicacions noves"
withFileAntenna: "Només les publicacions amb fitxers"
+antennaUsersDescription: "Llistar un nom d'usuari per línia"
notesAndReplies: "Amb respostes"
silence: "Silencia"
silenceConfirm: "Segur que vols silenciar aquest usuari?"
@@ -369,6 +388,11 @@ user: "Usuaris"
global: "Global"
searchByGoogle: "Cercar"
file: "Fitxers"
+replies: "Respondre"
+renotes: "Impulsa"
+_role:
+ _options:
+ antennaMax: "Nombre màxim d'antenes"
_email:
_follow:
title: "t'ha seguit"
@@ -381,10 +405,9 @@ _theme:
_sfx:
note: "Notes"
notification: "Notificacions"
- chat: "Xat"
antenna: "Antenes"
_2fa:
- step2Url: "També pots inserir aquest enllaç i utilitzes una aplicació d'escriptori:"
+ renewTOTPCancel: "No, gràcies"
_antennaSources:
all: "Totes les publicacions"
homeTimeline: "Publicacions dels usuaris seguits"
@@ -430,6 +453,7 @@ _pages:
_notification:
youRenoted: "Impulsat per {name}"
youWereFollowed: "t'ha seguit"
+ unreadAntennaNote: "Antena {name}"
_types:
all: "Tots"
follow: "Seguint"
@@ -461,3 +485,6 @@ _deck:
list: "Llistes"
mentions: "Mencions"
direct: "Publicacions directes"
+_moderationLogTypes:
+ suspend: "Suspèn"
+ resetPassword: "Restableix la contrasenya"
diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml
index 21d0c46482..5d6487d6df 100644
--- a/locales/cs-CZ.yml
+++ b/locales/cs-CZ.yml
@@ -226,6 +226,7 @@ preview: "Náhled"
default: "Výchozí"
defaultValueIs: "Základní hodnota: {value}"
noCustomEmojis: "Bez Emoji"
+noJobs: "Žádné úlohy"
federating: "Sdružování"
blocked: "Blokováno"
suspended: "Suspendováno"
@@ -353,7 +354,6 @@ invite: "Pozvat"
driveCapacityPerLocalAccount: "Kapacita disku na lokálního uživatele"
driveCapacityPerRemoteAccount: "Kapacita disku na vzdáleného uživatele"
inMb: "V megabajtech"
-iconUrl: "Favicon URL"
bannerUrl: "Baner URL"
backgroundImageUrl: "Adresa URL obrázku pozadí"
basicInfo: "Základní informace"
@@ -659,10 +659,13 @@ reporter: "Nahlásil"
reporteeOrigin: "Původ nahlášení"
reporterOrigin: "Původ nahlasovače"
forwardReport: "Přeposlat nahlášení do vzdálené instance"
+forwardReportIsAnonymous: "Místo vašeho účtu se ve vzdálené instanci zobrazí anonymní systémový účet jako nahlašovač."
send: "Odeslat"
+abuseMarkAsResolved: "Označit nahlášení jako vyřešené"
openInNewTab: "Otevřít v nové kartě"
openInSideView: "Otevřít v bočním panelu"
defaultNavigationBehaviour: "Výchozí chování navigace"
+editTheseSettingsMayBreakAccount: "Uprávou těchto nastavení si můžete poškodit účet."
instanceTicker: "Informace instance o poznámkách"
waitingFor: "Čeká se na {x}"
random: "Náhodně"
@@ -676,6 +679,7 @@ createNewClip: "Vytvořit nový klip"
unclip: "Odepnout"
confirmToUnclipAlreadyClippedNote: "Tahle poznámku je už součásti \"{name}\" klipu. Chcete ji místo toho odepnout z tohodle klipu?"
public: "Veřejný"
+private: "Soukromý"
i18nInfo: "Misskey je překládán do jiných jazyků dobrovolníkama. Můžete pomoci na {link}."
manageAccessTokens: "Spravovat přístupové tokeny"
accountInfo: "Informace o účtu"
@@ -695,6 +699,7 @@ no: "Ne"
driveFilesCount: "Počet souborů na disku"
driveUsage: "Využití disku"
noCrawle: "Odmítat indexování crawleru"
+noCrawleDescription: "Požádat vyhledávače aby neindexovali váš profil, poznámky, stránky, atd."
lockedAccountInfo: "Pokud nenastavíte viditelnost poznámek na \"Pouze pro sledující\", budou poznámky viditelné všem i přesto že vyžadujete manuální potvrzení pro sledování."
alwaysMarkSensitive: "Výchozně označovat jako citlivý"
loadRawImages: "Načítat originální obrázky místo náhledů"
@@ -702,25 +707,40 @@ disableShowingAnimatedImages: "Nepřehrávat animované obrázky"
verificationEmailSent: "Ověřovací email byl zaslán. Ověření dokončíte kliknutím na odkaz v emailu."
notSet: "Není nastaveno"
emailVerified: "Váš e-mail byl ověřen"
+noteFavoritesCount: "Počet oblíbených poznámek"
+pageLikesCount: "Počet oblíbených stránek"
+pageLikedCount: "Počet přijatých \"Libí se mi\""
contact: "Kontakt"
useSystemFont: "Použít výchozí font systému"
clips: "Oříznout"
experimentalFeatures: "Experimentální funkce"
+experimental: "Experimentální"
+thisIsExperimentalFeature: "Tohle je experimentální funkce. Její funkce se může změnit a nemusí fungovat tak, jak bylo zamýšleno."
developer: "Vývojář"
+makeExplorable: "Udělat účet viditelný v \"Objevit\""
+makeExplorableDescription: "Pokud tohle vypnete, tak se účet přestane zobrazovat v sekci \"Objevit\"."
+showGapBetweenNotesInTimeline: "Zobrazit mezeru mezi příspěvkama na časové ose"
duplicate: "Duplikovat"
left: "Vlevo"
center: "Uprostřed"
wide: "Široké"
narrow: "Úzké"
+reloadToApplySetting: "Tohle nastavení se použije až po obnovení stránky. Obnovit teď?"
+needReloadToApply: "K projevení nastavení je zapotřebí obnovit stránku."
+showTitlebar: "Zobrazit řádek s nadpisem"
clearCache: "Vyprázdnit mezipaměť"
+onlineUsersCount: "{n} uživatelů je online"
nUsers: "{n} užívatelů"
nNotes: "{n} poznámek"
+sendErrorReports: "Odesílat chybové záznamy"
+sendErrorReportsDescription: "Pokud je tato funkce zapnutá, budou se při výskytu problému sdílet podrobné informace o chybách se službou Misskey, což pomůže zlepšit kvalitu služby Misskey. Tyto informace budou zahrnovat například verzi operačního systému, jaký prohlížeč používáte, vaši aktivitu v Misskey atd."
myTheme: "Moje vzhledy"
backgroundColor: "Pozadí"
accentColor: "Akcent"
textColor: "Barva textu"
saveAs: "Uložit jako…"
advanced: "Pokročilé"
+advancedSettings: "Pokročilá nastavení"
value: "Hodnota"
createdAt: "Vytvořeno"
updatedAt: "Upraveno"
@@ -728,7 +748,35 @@ saveConfirm: "Uložit změny?"
deleteConfirm: "Opravdu smazat?"
invalidValue: "Neplatná hodnota."
registry: "Registr"
+closeAccount: "Uzavřít účet"
+currentVersion: "Aktuální verze"
+latestVersion: "Nejnovější verze"
+youAreRunningUpToDateClient: "Používáte nejnovější verzi klienta."
+newVersionOfClientAvailable: "Nová verze klienta je k dispozici."
+usageAmount: "Využití"
+capacity: "Kapacita"
+inUse: "Používáno"
+editCode: "Upravit kód"
+apply: "Potvrdit"
+receiveAnnouncementFromInstance: "Dostávat oznámení z téhle instance"
+emailNotification: "Emailové oznámení"
+publish: "Zveřejnit"
+inChannelSearch: "Vyhledat v kanálech"
+useReactionPickerForContextMenu: "Otevřít výběr reakce na kliknutí pravého tlačítka myši"
+typingUsers: "{users} píše..."
+jumpToSpecifiedDate: "Skočit do konkrétního datumu"
+showingPastTimeline: "Právě je zobrazována stará časová osa"
+clear: "Vrátit"
+markAllAsRead: "Označit všechno jako přečtené"
+goBack: "Zpět"
+unlikeConfirm: "Opravdu chcete odstranit like?"
+fullView: "Plné zobrazení"
+quitFullView: "Odejít z plného zobrazení"
+addDescription: "Přidat popis"
+userPagePinTip: "Zde můžete zobrazovat poznámky vybráním \"Připnout na profil\" z menu jednotlivých poznámek."
+notSpecifiedMentionWarning: "Tahle poznámka zmiňuje uživatele, které nejsou mezi adresáty"
info: "Informace"
+userInfo: "Informace o uživateli"
unknown: "Neznámý"
onlineStatus: "Online status"
hideOnlineStatus: "Skrýt Váš online status"
@@ -748,10 +796,18 @@ user: "Uživatelé"
administration: "Administrace"
accounts: "Účty"
switch: "Přepnout"
+noMaintainerInformationWarning: "Informace o správci nejsou nastavené"
+noBotProtectionWarning: "Ochrana proti botům není nastavena"
configure: "Nastavit"
+postToGallery: "Vytvořit nový příspěvek v galerii"
+postToHashtag: "Přidat příspěvek k tomuhle hastagu"
gallery: "Galerie"
recentPosts: "Poslední příspěvky"
+popularPosts: "Populární příspěvky"
+shareWithNote: "Sdílet s poznámkou"
ads: "Reklamy"
+expiration: "Ukončit hlasování"
+startingperiod: "Začátek"
memo: "Memo"
priority: "Priorita"
high: "Vysoká"
@@ -759,63 +815,702 @@ middle: "Střední"
low: "Nízká"
emailNotConfiguredWarning: "E-mailová adresa není nastavena."
ratio: "Poměr"
+previewNoteText: "Zobrazit náhled"
+customCss: "Vlastní CSS"
+customCssWarn: "Tohle nastavení by mělo být použito pouze v případě pokud víte co děláte. Vložením nesprávných hodnot může způsobit nefunkčnost klienta."
global: "Globální"
+squareAvatars: "Zobrazovat čtvercové avatary"
sent: "Odeslat"
+received: "Přijaté"
+searchResult: "Výsledky hledání"
hashtags: "Hashtagy"
troubleshooting: "Poradce při potížích"
+useBlurEffect: "Použít efekt rozostření v UI"
+learnMore: "Zjistit více"
+misskeyUpdated: "Misskey byl aktualizován!"
whatIsNew: "Zobrazit změny"
translate: "Přeložit"
+translatedFrom: "Přeloženo z {x}"
+accountDeletionInProgress: "Smazání účtu právě probíhá"
+usernameInfo: "Jméno které identifikuje váš účet od jiných na tomhle serveru. Můžete použít abecedu (a~z, A~Z), čísla (0~9) nebo podtržítka (_). Uživatelské jména nemůžou být změněna později."
+aiChanMode: "Režim Ai"
+devMode: "Vývojářský režim"
+keepCw: "Zachovat varování o obsahu"
+pubSub: "Pub/Sub účty"
+lastCommunication: "Poslední komunikace"
+resolved: "Vyřešeno"
+unresolved: "Nevyřešené"
+breakFollow: "Odstranit sledujícího"
+breakFollowConfirm: "Opravdu chcete odstranit tohodle sledujícího?"
+itsOn: "Zapnuto"
+itsOff: "Vypnuto"
+on: "Zapnuto"
+off: "Vypnuto"
+emailRequiredForSignup: "Vyžadovat email pro registraci"
+unread: "Nepřečtený"
+filter: "Filtr"
+controlPanel: "Ovládací panel"
+manageAccounts: "Spravovat účty"
+makeReactionsPublic: "Nastavit historii reakcí jako veřejnou"
+makeReactionsPublicDescription: "Tohle zviditelný seznam vašich předchozích reakcí veřejně."
+classic: "Klasický"
+muteThread: "Ztlumit vlákno"
+unmuteThread: "Zrušit ztlumení vlákna"
+ffVisibility: "Viditelnost Sledovaných/Sledujících"
+ffVisibilityDescription: "Umožní vám nastavit kdo uvidí koho sledujete a kdo vás sleduje."
+continueThread: "Zobrazit pokračování vlákna"
+deleteAccountConfirm: "Tohle nenávratně smaže váš účet, chcete pokračovat?"
+incorrectPassword: "Nesprávné heslo."
+voteConfirm: "Potvrdit hlas pro \"{choice}\"?"
hide: "Skrýt"
+useDrawerReactionPickerForMobile: "Zobrazit výběr reakcí jako šuplík na mobilním zařízení"
+welcomeBackWithName: "Vítejte zpět, {name}"
+clickToFinishEmailVerification: "Prosíme klikněte na [{ok}] pro dokončení ověření emailu."
+overridedDeviceKind: "Typ zařízení"
smartphone: "Telefon"
tablet: "Tablet"
auto: "Auto"
+themeColor: "Barva motivu"
size: "Velikost"
numberOfColumn: "Počet sloupců"
searchByGoogle: "Vyhledávání"
+instanceDefaultLightTheme: "Výchozí světlý motiv instance"
+instanceDefaultDarkTheme: "Výhozí tmavý motiv instance"
+instanceDefaultThemeDescription: "Zadejte kód motivu v objektovém formátu"
+mutePeriod: "Délka ztlumení"
+period: "Časový limit"
indefinitely: "Navždy"
tenMinutes: "10 minut"
oneHour: "1 hodina"
oneDay: "1 den"
oneWeek: "1 týden"
+oneMonth: "1 měsíc"
reflectMayTakeTime: "Může trvat nějakou dobu, než se projeví změny."
+failedToFetchAccountInformation: "Nepodařily se načíst informace o účtě"
+rateLimitExceeded: "Překročení rychlostního limitu"
cropImage: "Oříznout obrázek"
+cropImageAsk: "Chcete oříznout tenhle obrázek?"
+cropYes: "Uříznout"
+cropNo: "Použít tak jak je"
file: "Soubor(ů)"
recentNHours: "Posledních {n} hodin"
recentNDays: "Posledních {n} dnů"
+noEmailServerWarning: "Emailový server není nastavený"
+thereIsUnresolvedAbuseReportWarning: "Jsou k dispozici nevyřešené nahlášení zneužití"
recommended: "Doporučeno"
+check: "Zkontrolovat"
+driveCapOverrideLabel: "Změnit velikost disku pro tohoto uživatele"
+driveCapOverrideCaption: "K vyresetování velikosti na výchozí hodnotu zadejte hodnotu 0 nebo nižší."
+requireAdminForView: "Pro zobrazení se musíte přihlásit administrátorským účtem."
+isSystemAccount: "Účet automaticky vytvořený a ovládaný serverem."
+typeToConfirm: "Prosíme zadejte {x} pro potvrzení"
deleteAccount: "Odstranit účet"
document: "Dokumentace"
+numberOfPageCache: "Počet stránek uložených v mezipaměti"
+numberOfPageCacheDescription: "Zvýšením čísla zlepšíte pohodlí pro uživatele ale může to způsobit větší zátěž na server a na paměť."
logoutConfirm: "Opravdu se chcete odhlásit?"
+lastActiveDate: "Naposledy použito"
+statusbar: "Stavový řádek"
pleaseSelect: "Vybrat možnost"
reverse: "Otočit"
colored: "Barevné"
+refreshInterval: "Interval obnovení"
+label: "Popisek"
type: "Typ"
speed: "Rychlost"
slow: "Pomalá"
fast: "Rychlá"
+sensitiveMediaDetection: "Detekce citlivého média"
+localOnly: "Jenom lokální"
+remoteOnly: "Jenom vzdáleně"
+failedToUpload: "Nahrání se nezdařilo"
+cannotUploadBecauseInappropriate: "Tenhle soubor se nenahrál, protože některé části byly detekovány jako nevhodné."
+cannotUploadBecauseNoFreeSpace: "Nahrání se nezdařilo z důvodu nedostatku místa na disku."
+cannotUploadBecauseExceedsFileSizeLimit: "Tenhle soubor nemůže být nahráný protože překračuje velikostní limit."
+beta: "Beta verze"
+enableAutoSensitive: "Automaticky označovat jako citlivé"
+enableAutoSensitiveDescription: "Umožňuje automatickou detekci a označování citlivého média skrze strojového účení všude kde je možno. I pokud je tahle možnost vypnutá, může být povolena instancí."
+activeEmailValidationDescription: "Umožňuje striktní validaci emailové adresy, která zahrnuje kontrolu pro jednorázové adresy a pokud je možno s ní komunikovat. Pokud je to vypnuté, bude se kontrolovat pouze formát emailu."
+navbar: "Navigační panel"
+shuffle: "Zamíchat"
account: "Účty"
+move: "Přesunout"
+pushNotification: "Push oznámení"
+subscribePushNotification: "Povolit push oznamení"
+unsubscribePushNotification: "Vypnout push oznámení"
+pushNotificationAlreadySubscribed: "Push oznámení jsou už zapnuté"
+pushNotificationNotSupported: "Tenhle prohlížeč nepodporuje push oznámení"
+sendPushNotificationReadMessage: "Odstraněnit oznámení push po jejich přečtení"
+sendPushNotificationReadMessageCaption: "Tohle může zvýšit spotřebu energie vašeho zařízení."
+windowMaximize: "Maximalizovat"
+windowMinimize: "Minimalizovat"
+windowRestore: "Obnovit"
+caption: "Titulek"
+loggedInAsBot: "Právě jste přihlášen jako bot"
+tools: "Nástroje"
+cannotLoad: "Načtení se nezdařilo"
+numberOfProfileView: "Počet zobrazení profilu"
+like: "To se mi líbí"
+unlike: "Už se mi to nelíbí"
+numberOfLikes: "Počet \"To se mi líbí\""
show: "Zobrazit"
+neverShow: "Znovu nezobrazovat"
+remindMeLater: "Možná později"
+didYouLikeMisskey: "Oblíbili jste si Misskey?"
+pleaseDonate: "{host} používá bezplatný software Misskey. Velmi bychom ocenili vaše dary, aby mohl vývoj Misskey pokračovat!"
+roles: "Role"
+role: "Role"
+noRole: "Role nenalezena"
+normalUser: "Normální uživatel"
+undefined: "Neurčeno"
+assign: "Přiřadit"
+unassign: "Zrušit přirazení"
color: "Barva"
+manageCustomEmojis: "Spravovat vlastní emoji"
+youCannotCreateAnymore: "Narazili jste na limit pro vytváření."
+cannotPerformTemporary: "Dočasně nedostupné"
+cannotPerformTemporaryDescription: "Tuto akci nelze dočasně provést z důvodu překročení limitu provedení. Chvíli počkejte a zkuste to znovu."
+invalidParamError: "Neplatné parametry"
+invalidParamErrorDescription: "Parametry požadavku jsou neplatné. Obvykle je to způsobeno chybou, ale může to být také způsobeno překročením limitů velikosti vstupů nebo podobně."
+permissionDeniedError: "Operace zamítnuta"
+permissionDeniedErrorDescription: "Tento účet nemá oprávnění k provedení této akce."
+preset: "Předvolba"
+selectFromPresets: "Vybrat z předvoleb"
+achievements: "Úspěchy"
+gotInvalidResponseError: "Neplatná odpověď serveru"
+gotInvalidResponseErrorDescription: "Server může být nedostupný nebo na něm probíhá údržba. Zkuste to prosím později."
+thisPostMayBeAnnoying: "Tato poznámka může ostatní obtěžovat."
+thisPostMayBeAnnoyingHome: "Zveřejnit na domovskou časovou osu"
+thisPostMayBeAnnoyingCancel: "Zrušit"
+thisPostMayBeAnnoyingIgnore: "I přesto zveřejnit"
+collapseRenotes: "Sbalit poznámky, které jste již viděli"
+internalServerError: "Interní chyba serveru"
+internalServerErrorDescription: "Server narazil na neočekávanou chybu."
+copyErrorInfo: "Zkopírovat detaily erroru"
+joinThisServer: "Zaregistrovat se v této instanci"
+exploreOtherServers: "Podívat se na ostatní instance"
+letsLookAtTimeline: "Podívejte se na časovou osu"
+disableFederationConfirm: "Chcete opravdu vypnout federace?"
+disableFederationConfirmWarn: "I v případě defederace budou příspěvky nadále veřejné, pokud nebude nastaveno jinak. Obvykle to není nutné."
+disableFederationOk: "Vypnout"
+invitationRequiredToRegister: "Tahle instance je pouze na pozvánku. Musíte zadat validní kód pozvánky."
+emailNotSupported: "Tahle instance nepodporuje zasílání emailů"
+postToTheChannel: "Vložit do kanálu"
+cannotBeChangedLater: "Tohle nemůže být změněno později."
+reactionAcceptance: "Přijímání reakcí"
+likeOnly: "Jenom \"oblíbené\""
+likeOnlyForRemote: "Všechny (Pouze \"oblíbené\" pro vzdálenou instanci)"
+nonSensitiveOnly: "Pouze bez citlivých medií"
+nonSensitiveOnlyForLocalLikeOnlyForRemote: "Pouze bez citlivých medií (Pouze vzdálený \"oblíbený\")"
+rolesAssignedToMe: "Přiřazené role ke mně"
+resetPasswordConfirm: "Opravdu chcete resetovat heslo?"
+sensitiveWords: "Citlivá slova"
+sensitiveWordsDescription: "Viditelnost všech poznámek obsahujících některé z nakonfigurovaných slov bude automaticky nastavena na \"Domů\". Můžete jich uvést více tak, že je oddělíte pomocí řádků."
+sensitiveWordsDescription2: "Použití mezer vytvoří výrazy AND a obklopení klíčových slov lomítky je změní na regulární výraz."
+notesSearchNotAvailable: "Vyhledávání poznámek je nedostupné."
+license: "Licence"
+unfavoriteConfirm: "Opravdu chcete odstranit z oblíbených?"
+myClips: "Moje klipy"
+drivecleaner: "Čistič disku"
+retryAllQueuesNow: "Obnovit všechny běžící fronty"
+retryAllQueuesConfirmTitle: "Opravdu chcete obnovit všechno?"
+retryAllQueuesConfirmText: "Tohle dočasně zvýší zatěž na server."
+enableChartsForRemoteUser: "Vygenerovat grafy dat vzdálených uživatelů"
+enableChartsForFederatedInstances: "Vygenerovat grafy dat vzdálených instancí"
+showClipButtonInNoteFooter: "Přidat \"Připnout\" do akčního menu poznámky"
+noteIdOrUrl: "ID nebo URL poznámky"
+video: "Video"
+videos: "Videa"
+dataSaver: "Spořič dat"
+accountMigration: "Migrace účtu"
+accountMoved: "Tenhle uživatel se přesunul na nový účet:"
+accountMovedShort: "Tenhle účet byl migrován."
+operationForbidden: "Zakázaná operace"
+forceShowAds: "Vždycky zobrazovat reklamy"
+addMemo: "Přidat memo"
+editMemo: "Upravit memo"
+reactionsList: "Reakce"
+renotesList: "Poznámky"
+notificationDisplay: "Oznámení"
+leftTop: "Vlevo nahoře"
+rightTop: "Vpravo nahoře"
+leftBottom: "Vlevo dole"
+rightBottom: "Vpravo dole"
+stackAxis: "Směr ukládání"
+vertical: "Svisle"
+horizontal: "Vodorovně"
+position: "Pozice"
+serverRules: "Pravidla serveru"
+pleaseConfirmBelowBeforeSignup: "Abyste se mohli přihlásit na server, musíte souhlasit s následujícím."
+pleaseAgreeAllToContinue: "Musíte souhlasit se vším abyste mohli pokračovat."
+continue: "Pokračovat"
+preservedUsernames: "Rezervované uživatelské jména"
+preservedUsernamesDescription: "Seznam uživatelských jmén na rezervaci oddělené mezerama. Tyhle jména se potom nebudou moc použít při normálním procesu vytvoření účtu ale můžou být použiti manuálně administratorém. Existujících účtů se to nedotkne."
+createNoteFromTheFile: "Vytvořit poznámku z tohodle souboru"
+archive: "Archiv"
+channelArchiveConfirmTitle: "Opravdu chcete archivovat {name}?"
+channelArchiveConfirmDescription: "Archivovaný kanál se objeví v seznamu kanálů nebo ve výsledcích hledání. Nové poznámky se nedají vložit do seznamu."
+thisChannelArchived: "Tenhle kanál je archivovaný"
+displayOfNote: "Zobrazit poznámku"
+initialAccountSetting: "Nastavení profilu"
+youFollowing: "Sleduji"
+preventAiLearning: "Odmítnout použití v strojovém učení (Generative AI)"
+preventAiLearningDescription: "Požaduje, aby prohlížeče nepoužívaly zveřejněný textový nebo obrazový materiál atd. v datových sadách pro strojové učení (prediktivní / generativní umělá inteligence). Toho se dosáhne přidáním příznaku \"noai\" HTML-Response k příslušnému obsahu. Úplné prevence však tímto příznakem nelze dosáhnout, protože může být jednoduše ignorován."
+options: "Možnosti"
+specifyUser: "Upřesnit uživatele"
+failedToPreviewUrl: "Náhled se nezdařil"
+update: "Aktualizovat"
+rolesThatCanBeUsedThisEmojiAsReaction: "Role, které můžou tuhle emoji použít jako reakci"
+rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "Pokud nejsou určena role, tak pak každý může použít tenhle emoji."
+rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Role musí být veřejné."
+cancelReactionConfirm: "Opravdu chcete odstranit vaší reakci?"
+changeReactionConfirm: "Opravdu chcete změnit vaši reakci?"
+later: "Později"
+goToMisskey: "Jít na Misskey"
+additionalEmojiDictionary: "Další slovníky emoji"
+installed: "Nainstalováno"
+branding: "Značka"
+enableServerMachineStats: "Zveřejněnit statistiky hardwaru serveru"
+enableIdenticonGeneration: "Povolit generování identicon uživatele"
+turnOffToImprovePerformance: "Vypnutí této funkce může zvýšit výkon."
+createInviteCode: "Vygenerovat pozvánku"
+createWithOptions: "Vygenerovat s nastavením"
+createCount: "Počet vytvořených pozvánek"
+inviteCodeCreated: "Pozvánka vygenerována"
+inviteLimitExceeded: "Překročili jste limit pozvánek, které můžete vygenerovat."
+createLimitRemaining: "Limit pozvánek: {limit} zbývá"
+inviteLimitResetCycle: "Tento limit se obnoví na hodnotu {limit} v {time}."
+expirationDate: "Datum expirace"
+noExpirationDate: "Bez expirace"
+inviteCodeUsedAt: "Kód pozvánky použitý na"
+registeredUserUsingInviteCode: "Pozvánku používá"
+waitingForMailAuth: "Čeká se na ověření emailu"
+inviteCodeCreator: "Pozvánku vytvořil"
+usedAt: "Používá se v"
+unused: "Nepoužívaná"
+used: "Používaná"
+expired: "Prošlá"
+doYouAgree: "Souhlasíte?"
+beSureToReadThisAsItIsImportant: "Přečtěte si prosím tyto důležité informace."
+iHaveReadXCarefullyAndAgree: "Přečetl jsem si text \"{x}\" a souhlasím s ním."
+icon: "Avatar"
+replies: "Odpovědět"
+renotes: "Přeposlat"
+flip: "Otočit"
+_initialAccountSetting:
+ accountCreated: "Váš účet byl úspěšně vytvořen!"
+ letsStartAccountSetup: "Pro začátek si nastavte svůj profil."
+ letsFillYourProfile: "Nejprve si nastavte svůj profil."
+ profileSetting: "Nastavení profilu"
+ privacySetting: "Nastavení soukromí"
+ theseSettingsCanEditLater: "Tato nastavení můžete vždy později změnit."
+ youCanEditMoreSettingsInSettingsPageLater: "Na stránce \"Nastavení\" můžete nakonfigurovat mnoho dalších nastavení. Nezapomeňte ji navštívit později."
+ followUsers: "Zkuste sledovat některé uživatele, kteří vás zajímají pro vystavění časový osy."
+ pushNotificationDescription: "Povolení push oznámení vám umožní přijímat oznámení od {name} přímo ve vašem zařízení."
+ initialAccountSettingCompleted: "Nastavení profilu dokončeno!"
+ haveFun: "Užívejte {name}!"
+ skipAreYouSure: "Opravdu chcete přeskočit nastavení profilu?"
+ laterAreYouSure: "Opravdu chcete provést nastavení profilu později?"
+_serverRules:
+ description: "Soubor pravidel, která se zobrazí před registrací. Doporučuje se nastavit shrnutí podmínek služby."
+_serverSettings:
+ iconUrl: "URL ikony"
+_accountMigration:
+ moveFrom: "Migrace jiného účtu na tento účet"
+ moveFromSub: "Vytvořit alias na jiný účet"
+ moveFromLabel: "Původní účet #{n}"
+ moveFromDescription: "Pro účet, ze kterého se chcete přesunout, musíte vytvořit alias na tomto účtu.\nZadejte účet, ze kterého chcete přejít, v následujícím formátu: @username@server.example.com\nChcete-li alias odstranit, ponechte pole prázdné (nedoporučuje se)."
+ moveTo: "Přesunout tenhle účet do jiného"
+ moveToLabel: "Cílový účet pro přesunutí:"
+ moveCannotBeUndone: "Migrace účtu nemůže být vrácena."
+ moveAccountDescription: "Tím dojde k migraci vašeho účtu na jiný účet.\n ・Sledovatelé z tohoto účtu budou automaticky převedeni na nový účet.\n ・Tento účet zruší sledování všech uživatelů, které aktuálně sleduje.\n ・Na tomto účtu nebude možné vytvářet nové poznámky atd.\n\nZatímco migrace sledovaných uživatelů probíhá automaticky, pro migraci seznamu sledovaných uživatelů je nutné připravit některé kroky ručně. Za tímto účelem proveďte export sledovaných, který později naimportujete na nový účet v nabídce nastavení. Stejný postup platí pro seznamy i pro ztlumené a zablokované uživatele.\n\n(Tento výklad platí pro Misskey v13.12.0 a novější. Jiný software ActivityPub, například Mastodon, může fungovat jinak.)"
+ moveAccountHowTo: "Chcete-li migrovat, vytvořte nejprve alias tohoto účtu na účtu, na který chcete přejít.\nPo vytvoření aliasu zadejte účet, na který chcete přejít, v následujícím formátu: @username@server.example.com"
+ startMigration: "Migrovat"
+ migrationConfirm: "Opravdu chcete migrovat tento účet na {account}? Jednou zahájený proces nelze zastavit ani vrátit zpět a tento účet již nebudete moci používat v původním stavu."
+ movedAndCannotBeUndone: "\nTento účet byl převeden.\nMigraci nelze vrátit zpět."
+ postMigrationNote: "Tento účet zruší sledování všech účtů, které aktuálně sleduje, 24 hodin po dokončení migrace.\nPočet sledujících i následovníků se poté vynuluje. Aby se zabránilo tomu, že vaši sledující nebudou moci vidět příspěvky tohoto účtu určené pouze pro sledující, budou však tento účet sledovat i nadále."
+ movedTo: "Cílový účet pro přesunutí:"
+_achievements:
+ earnedAt: "Odemčeno v"
+ _types:
+ _notes1:
+ title: "Dobrý den Misskey!"
+ description: "Zveřejněte vaší první poznámku"
+ flavor: "Užijte si to s Misskey!"
+ _notes10:
+ title: "Pár poznámek"
+ description: "Zveřejněte 10 poznámek"
+ _notes100:
+ title: "Hodně poznámek"
+ description: "Zveřejněte 100 poznámek"
+ _notes500:
+ title: "Zahlcen poznámkama"
+ description: "Zveřejněte 500 poznámek"
+ _notes1000:
+ title: "Hora poznámek"
+ description: "Zveřejněte 1000 poznámek"
+ _notes5000:
+ title: "Přetékající poznámky"
+ description: "Zveřejněte 5000 poznámek"
+ _notes10000:
+ title: "Super poznámka"
+ description: "Zveřejněte 10 000 poznámek"
+ _notes20000:
+ title: "Potřebuju... více... poznámek..."
+ description: "Zveřejněte 20 000 poznámek"
+ _notes30000:
+ title: "Poznámky, poznámky, POZNÁMKY!"
+ description: "Zveřejněte 30 000 poznámek"
+ _notes40000:
+ title: "Továrna na poznámky"
+ description: "Zveřejněte 40 000 poznámek"
+ _notes50000:
+ title: "Planeta poznámek"
+ description: "Zveřejněte 50 000 poznámek"
+ _notes60000:
+ title: "Poznámkový kvasar"
+ description: "Zveřejněte 60 000 poznámek"
+ _notes70000:
+ title: "Černá díra poznámek"
+ description: "Zveřejněte 70 000 poznámek"
+ _notes80000:
+ title: "Galaxie poznámek"
+ description: "Zveřejněte 80 000 poznámek"
+ _notes90000:
+ title: "Vesmír poznámek"
+ description: "Zveřejněte 90 000 poznámek"
+ _notes100000:
+ title: "ALL YOUR NOTE ARE BELONG TO US"
+ description: "Zveřejněte 100 000 poznámek"
+ flavor: "Máte toho hodně co říct."
+ _login3:
+ title: "Začátečník I"
+ description: "Přihlaste se celkově za 3 dny"
+ flavor: "Ode dneška mi říkejte Misskista."
+ _login7:
+ title: "Začátečník II"
+ description: "Přihlaste se celkově za 7 dní"
+ flavor: "Máte pocit, že už jste se v tom vyznali?"
+ _login15:
+ title: "Začátečník III"
+ description: "Přihlaste se celkově za 15 dní"
+ _login30:
+ title: "Misskista I"
+ description: "Přihlaste se celkově za 30 dní"
+ _login60:
+ title: "Misskista II"
+ description: "Přihlaste se celkově za 60 dní"
+ _login100:
+ title: "Misskista III"
+ description: "Přihlaste se celkově za 100 dní"
+ flavor: "Violent Misskista"
+ _login200:
+ title: "Stálý zákazník I"
+ description: "Přihlaste se celkově za 200 dní"
+ _login300:
+ title: "Stálý zákazník II"
+ description: "Přihlaste se celkově za 300 dní"
+ _login400:
+ title: "Stálý zákazník III"
+ description: "Přihlaste se celkově za 400 dní"
+ _login500:
+ title: "Expert I"
+ description: "Přihlaste se celkově za 500 dní"
+ flavor: "Moji přátelé, často se říká, že mám rád poznámky."
+ _login600:
+ title: "Expert II"
+ description: "Přihlaste se celkově za 600 dní"
+ _login700:
+ title: "Expert III"
+ description: "Přihlaste se celkově za 700 dní"
+ _login800:
+ title: "Mistr poznámek I"
+ description: "Přihlaste se celkově za 800 dní"
+ _login900:
+ title: "Mistr poznámek II"
+ description: "Přihlaste se celkově za 900 dní"
+ _login1000:
+ title: "Mistr poznámek III"
+ description: "Přihlaste se celkově za 1000 dní"
+ flavor: "Děkujeme, že používáte Misskey!"
+ _noteClipped1:
+ title: "Musím... připnout..."
+ description: "Připněte si první poznámku"
+ _noteFavorited1:
+ title: "Hvězdář"
+ description: "Oblíbena první poznámka"
+ _myNoteFavorited1:
+ title: "Hledání hvězd"
+ description: "Někdo si oblíbil jednu z vašich poznámek"
+ _profileFilled:
+ title: "Dobře připravený"
+ description: "Nastavte si profil"
+ _markedAsCat:
+ title: "Já jsem kočka"
+ description: "Označte váš účet \"jako kočka\""
+ flavor: "Jméno ti dám později."
+ _following1:
+ title: "Sledujte prvního uživatele"
+ description: "Sledujte uživatele"
+ _following10:
+ title: "Drž se... drž se..."
+ description: "Sledujte 10 uživatelů"
+ _following50:
+ title: "Hodně přátel"
+ description: "Sledujte 50 uživatelů"
+ _following100:
+ title: "100 přátel"
+ description: "Sledujte 100 uživatelů"
+ _following300:
+ title: "Přetížení přátel"
+ description: "Sledujte 300 účtů"
+ _followers1:
+ title: "První sledující"
+ description: "Získejte 1 sledujícího"
+ _followers10:
+ title: "Sledujte mě!"
+ description: "Získejte 10 sledujících"
+ _followers50:
+ title: "Přicházejí davy"
+ description: "Získejte 50 sledujících"
+ _followers100:
+ title: "Populární"
+ description: "Získejte 100 sledujících"
+ _followers300:
+ title: "Prosíme srovnejte se do jedné řady!"
+ description: "Získejte 300 sledujících"
+ _followers500:
+ title: "Rádiová věž"
+ description: "Získejte 500 sledujících"
+ _followers1000:
+ title: "Influencer"
+ description: "Získejte 1000 sledujících"
+ _collectAchievements30:
+ title: "Sběratel úspěchů"
+ description: "Získejte 30 úspěchů"
+ _viewAchievements3min:
+ title: "Máš rád úspěchy"
+ description: "Koukejte na váš seznam úspěchů alespoň po dobu 3 minut"
+ _iLoveMisskey:
+ title: "Miluju Misskey"
+ description: "Zveřejněte \" I ❤ #Misskey\""
+ flavor: "Vývojový tým Misskey si velmi váží vaší podpory!"
+ _foundTreasure:
+ title: "Hon za pokladem"
+ description: "Našli jste schovaný poklad!"
+ _client30min:
+ title: "Krátká pauza"
+ description: "Mějte otevřený Misskey alespoň po dobu 30 minut"
+ _client60min:
+ title: "Žádný \"Miss\" v Misskey"
+ description: "Mějte otevřený Misskey alespoň po dobu 60 minut"
+ _noteDeletedWithin1min:
+ title: "Ups, nevadí"
+ description: "Vymažte poznámku během minuty co ji zveřejníte"
+ _postedAtLateNight:
+ title: "Noční typ"
+ description: "Zveřejněte poznámku pozdě v noci"
+ flavor: "Je nejvyšší čas jít spát."
+ _postedAt0min0sec:
+ title: "Mluvící hodiny"
+ description: "Zveřejněte poznámku přesně v 00:00"
+ flavor: "Klik Klik Klik Bum"
+ _selfQuote:
+ title: "Sebereference"
+ description: "Citujte vlastní poznámku"
+ _htl20npm:
+ title: "Plynoucí časová osa"
+ description: "Mějte rychlost vaší domovské časové osy vyšší než 20 pzm (poznámek za minutu)."
+ _viewInstanceChart:
+ title: "Analytik"
+ description: "Zobrazte graf instance"
+ _outputHelloWorldOnScratchpad:
+ title: "Hello, world!"
+ description: "Dostaňte výpis \"hello world\" do Scratchpadu"
+ _open3windows:
+ title: "Splitscreen"
+ description: "Mějte otevřená alespoň 3 okna zároveň"
+ _driveFolderCircularReference:
+ title: "Okružní reference"
+ description: "Pokuste se o vytvoření rekurzivně vnořené složky v disku"
+ _reactWithoutRead:
+ title: "Opravdu jste to četl/a?"
+ description: "Reagujte na poznámku, která má více než 100 znaků, do 3 sekund od jejího zveřejnění."
+ _clickedClickHere:
+ title: "Klikněte sem"
+ description: "Kliknul si tam"
+ _justPlainLucky:
+ title: "Čisté štěstí"
+ description: "Mějte šanci na získání s pravděpodobností 0,005 % každých 10 sekund."
+ _setNameToSyuilo:
+ title: "Boží komplex"
+ description: "Nastavte si jméno na \"syuilo\""
+ _passedSinceAccountCreated1:
+ title: "Roční výročí"
+ description: "Od vytvoření vašeho účtu uplynul jeden rok"
+ _passedSinceAccountCreated2:
+ title: "Dvouleté výročí"
+ description: "Od vytvoření vašeho účtu uplynuly dva roky"
+ _passedSinceAccountCreated3:
+ title: "Tříleté výročí"
+ description: "Od vytvoření vašeho účtu uplynuly tři roky"
+ _loggedInOnBirthday:
+ title: "Všechno nejlepší!"
+ description: "Přihlašte se v den vašich narozenin"
+ _loggedInOnNewYearsDay:
+ title: "Štastný nový rok!"
+ description: "Přihlašte se v den nového roku"
+ flavor: "Na další skvělý rok v této instanci"
+ _cookieClicked:
+ title: "Hra, ve které klikáte na sušenky"
+ description: "Klikněte na soubor cookie"
+ flavor: "Počkejte, jste na správné webové stránce?"
+ _brainDiver:
+ title: "Brain Diver"
+ description: "Zveřejněte odkaz na Brain Diver"
+ flavor: "Misskey-Misskey La-Tu-Ma"
_role:
+ new: "Nová role"
+ edit: "Upravit roli"
+ name: "Název role"
+ description: "Popis role"
+ permission: "Oprávnění role"
+ descriptionOfPermission: "Moderators může provádět základní operace moderování.\nAdministrators může měnit všechna nastavení instance."
+ assignTarget: "Přiřadit"
+ descriptionOfAssignTarget: "Manual ručně změnit, kdo je součástí této role a kdo ne.\nConditional mít uživatelé automaticky přiřazováni a odebíráni z této role na základě podmínky."
+ manual: "Dokumentace"
+ conditional: "Podmíněné"
+ condition: "Podmínky"
+ isConditionalRole: "Tato role je podmíněná."
+ isPublic: "Veřejná role"
+ descriptionOfIsPublic: "Tato role se zobrazí v profilech přiřazených uživatelů."
+ options: "Nastavení"
+ policies: "Zásady"
+ baseRole: "Šablona role"
+ useBaseValue: "Použít hodnotu šablony role"
+ chooseRoleToAssign: "Vyberte roli, kterou chcete přiřadit"
+ iconUrl: "URL ikony"
+ asBadge: "Zobrazovat jako odznak"
+ descriptionOfAsBadge: "Ikona této role se zobrazí vedle uživatelského jména uživatelů s touto rolí, pokud je zapnuta."
+ isExplorable: "Udělat roli objevitelnou"
+ descriptionOfIsExplorable: "Časová osa této role a seznam uživatelů s touto rolí budou zveřejněny, pokud jsou povoleny."
+ displayOrder: "Pozice"
+ descriptionOfDisplayOrder: "Čím vyšší číslo, tím vyšší pozice v uživatelském rozhraní."
+ canEditMembersByModerator: "Umožnit moderátorům upravovat seznam členů pro tuto roli"
+ descriptionOfCanEditMembersByModerator: "Po zapnutí této role budou moci moderátoři i administrátoři přiřazovat a odebírat uživatele do této role. Pokud je tato funkce vypnutá, budou moci uživatele přiřazovat pouze správci."
priority: "Priorita"
_priority:
low: "Nízká"
middle: "Střední"
high: "Vysoká"
+ _options:
+ gtlAvailable: "Může zobrazit globální časovou osu"
+ ltlAvailable: "Může zobrazit místní časovou osu"
+ canPublicNote: "Může posílat veřejné poznámky"
+ canInvite: "Může vytvářet kódy pozvánek instance"
+ inviteLimit: "Limit pozvánek"
+ inviteLimitCycle: "Limit mezi pozvánkama"
+ inviteExpirationTime: "Interval vypršení platnosti pozvánky"
+ canManageCustomEmojis: "Spravovat vlastní emoji"
+ driveCapacity: "Velikost disku"
+ alwaysMarkNsfw: "Vždy označovat soubory jako NSFW"
+ pinMax: "Maximální počet připnutých poznámek"
+ antennaMax: "Maximální počet antén"
+ wordMuteMax: "Maximální počet znaků povolených v ztlumených slovech"
+ webhookMax: "Maximální počet Webhooků"
+ clipMax: "Maximální počet připnutí"
+ noteEachClipsMax: "Maximální počet poznámek v připnutí"
+ userListMax: "Maximální počet seznamů uživatelů"
+ userEachUserListsMax: "Maximální počet uživatelů v seznamu uživatelů"
+ rateLimitFactor: "Limit rychlosti"
+ descriptionOfRateLimitFactor: "Nižší limity rychlosti jsou méně omezující, vyšší více omezující. "
+ canHideAds: "Může schovat reklamy"
+ canSearchNotes: "Použití vyhledávání poznámek"
+ _condition:
+ isLocal: "Místní uživatel"
+ isRemote: "Vzdálený uživatel"
+ createdLessThan: "Od vytvoření účtu uplynulo méně než X"
+ createdMoreThan: "Od vytvoření účtu uplynulo více než X"
+ followersLessThanOrEq: "Má X nebo méně sledujících"
+ followersMoreThanOrEq: "Má X nebo více sledujících"
+ followingLessThanOrEq: "Sleduje X nebo méně účtů"
+ followingMoreThanOrEq: "Sleduje X nebo více účtů"
+ notesLessThanOrEq: "Počet příspěvků je menší než/rovná se"
+ notesMoreThanOrEq: "Počet příspěvků je větší než/rovná se"
+ and: "AND kondice"
+ or: "OR kondice"
+ not: "NOT kondice"
+_sensitiveMediaDetection:
+ description: "Snižuje náročnost moderování serveru díky automatickému rozpoznávání citlivých médií pomocí strojového učení. Tím se mírně zvýší zatížení serveru."
+ sensitivity: "Detekce citlivosti"
+ sensitivityDescription: "Snížení citlivosti povede k menšímu počtu chybných detekcí (falešně pozitivních), zatímco její zvýšení povede k menšímu počtu chybných detekcí (falešně negativních)."
+ setSensitiveFlagAutomatically: "Označit jako citlivé"
+ setSensitiveFlagAutomaticallyDescription: "Výsledky interní detekce se zachovají, i když je tato možnost vypnutá."
+ analyzeVideos: "Povolit analýzy videí"
+ analyzeVideosDescription: "Kromě obrázků analyzuje i videa. Tím se mírně zvýší zatížení serveru."
+_emailUnavailable:
+ used: "Tato emailová adresa se již používá"
+ format: "Formát této emailové adresy je neplatný"
+ disposable: "Jednorázové emailové adresy se nesmí používat"
+ mx: "Tento e-mailový server je neplatný"
+ smtp: "Tento emailový server neodpovídá"
+_ffVisibility:
+ public: "Zveřejnit"
+ followers: "Viditelné pouze pro sledující"
+ private: "Soukromý"
+_signup:
+ almostThere: "Už to skoro je"
+ emailAddressInfo: "Zadejte prosím svou emailovou adresu. Nebude zveřejněna."
+ emailSent: "Na vaši e-mailovou adresu ({email}) byl odeslán potvrzovací e-mail. Kliknutím na přiložený odkaz dokončete vytvoření účtu."
+_accountDelete:
+ accountDelete: "Smazat účet"
+ mayTakeTime: "Vzhledem k tomu, že odstranění účtu je proces náročný na zdroje, může jeho dokončení trvat určitou dobu v závislosti na tom, kolik obsahu jste vytvořili a kolik souborů jste nahráli."
+ sendEmail: "Po dokončení odstranění účtu bude na emailovou adresu registrovanou k tomuto účtu zaslán email."
+ requestAccountDelete: "Žádost o smazání účtu"
+ started: "Bylo zahájeno mazání."
+ inProgress: "V současné době probíhá mazání"
_ad:
back: "Zpět"
+ reduceFrequencyOfThisAd: "Zobrazovat tuto reklamu méně"
+ hide: "Schovat"
+ timezoneinfo: "Den v týdnu se určuje podle časového pásma serveru."
+_forgotPassword:
+ enterEmail: "Zadejte emailovou adresu, kterou jste použili při registraci. Na ni vám pak bude zaslán odkaz, pomocí kterého si můžete obnovit heslo."
+ ifNoEmail: "Pokud jste při registraci nepoužili email, obraťte se na správce instance."
+ contactAdmin: "Tato instance nepodporuje používání emailových adres, pro obnovení hesla se obraťte na správce instance."
_gallery:
my: "Moje galerie"
+ liked: "Oblíbené příspěvky"
+ like: "To se mi líbí"
+ unlike: "Už se mi to nelíbí"
_email:
_follow:
title: "Máte nového následovníka"
+ _receiveFollowRequest:
+ title: "Obdrželi jste žádost o sledování"
_plugin:
install: "Instalovat plugin"
+ installWarn: "Neinstalujte nedůvěryhodné pluginy."
manage: "Správce pluginů"
+ viewSource: "Zobrazit zdroj"
_preferencesBackups:
list: "Vytvořit backup"
+ saveNew: "Uložit novou zálohu"
loadFile: "Načíst ze souboru"
+ apply: "Použít pro toto zařízení"
save: "Uložit změny"
+ inputName: "Zadejte prosím název pro tuto zálohu"
+ cannotSave: "Uložení selhalo"
+ nameAlreadyExists: "Záloha s názvem \"{name}\" již existuje. Zadejte prosím jiný název."
+ applyConfirm: "Opravdu chcete na toto zařízení použít zálohu \"{name}\"? Stávající nastavení tohoto zařízení bude přepsáno."
+ saveConfirm: "Uložit zálohu jako {name}?"
+ deleteConfirm: "Odstranit zálohu {name}?"
+ renameConfirm: "Přejmenovat tuto zálohu z \"{old}\" na \"{new}\"?"
+ noBackups: "Neexistují žádné zálohy. Nastavení klienta na tomto serveru můžete zálohovat pomocí \"Vytvořit novou zálohu\"."
+ createdAt: "Vytvořeno v: {date} {time}"
+ updatedAt: "Aktualizováno: {date} {time}"
+ cannotLoad: "Načítání selhalo"
+ invalidFile: "Neplatný typ souboru"
_registry:
scope: "Rozsah"
key: "Klíč"
@@ -823,46 +1518,215 @@ _registry:
domain: "Doména"
createKey: "Vytvořit klíč"
_aboutMisskey:
+ about: "Misskey je open-source software vyvíjený syuilo od roku 2014."
+ contributors: "Hlavní přispěvatelé"
allContributors: "Všichni přispěvatelé"
source: "Zdrojový kód"
+ translation: "Přeložit Misskey"
+ donate: "Přispějte na Misskey"
+ morePatrons: "Vážíme si také podpory mnoha dalších pomocníků, kteří zde nejsou uvedeni. Děkujeme! 🥰"
+ patrons: "Patroni"
+_displayOfSensitiveMedia:
+ respect: "Skrýt média označená jako citlivá"
+ ignore: "Zobrazit média označená jako citlivá"
+ force: "Skrýt všechna média"
+_instanceTicker:
+ none: "Nikdy nezobrazovat"
+ remote: "Zobrazit pro vzdálené uživatelé"
+ always: "Vždy zobrazovat"
+_serverDisconnectedBehavior:
+ reload: "Automatické znovunačtení"
+ dialog: "Zobrazení dialogového okna s varováním"
+ quiet: "Zobrazit nerušivé upozornění"
_channel:
+ create: "Vytvořit kanál"
+ edit: "Upravit kanál"
+ setBanner: "Nastavit banner"
+ removeBanner: "Odstranit banner"
featured: "Trendy"
+ owned: "Vlastněný"
+ following: "Sledovaný"
+ usersCount: "{n} Účastníků"
+ notesCount: "{n} Poznámek"
+ nameAndDescription: "Název a popis"
+ nameOnly: "Pouze název"
_menuDisplay:
+ sideFull: "Postranně"
+ sideIcon: "Postranně (Ikony)"
top: "Nahoru"
hide: "Skrýt"
+_wordMute:
+ muteWords: "Ztlumená slova"
+ muteWordsDescription: "Podmínku AND oddělujte mezerami, podmínku OR oddělujte řádkovými zlomy."
+ muteWordsDescription2: "Chcete-li použít regulární výrazy, obklopte klíčová slova lomítky."
+_instanceMute:
+ instanceMuteDescription: "Tímhle se ztlumí všechny poznámky/poznámky z uvedených instancí, včetně poznámek uživatelů, kteří odpovídají uživateli ze ztlumené instance."
+ instanceMuteDescription2: "Oddělte novými řádky"
+ title: "Skryje poznámky z uvedených případů."
+ heading: "Seznam instancí, které mají být ztlumeny"
_theme:
+ explore: "Objevit témata"
install: "Nainstalovat vzhled"
manage: "Správa vzhledů"
code: "Kód vzhledu"
description: "Popis"
+ installed: "{name} byl nainstalován"
installedThemes: "Nainstalované vzhledy"
+ builtinThemes: "Vestavěné temáta"
+ alreadyInstalled: "Tento vzhled je již nainstalován."
+ invalid: "Formát tohoto tématu je neplatný"
+ make: "Vytvořit téma"
+ base: "Základ"
+ addConstant: "Přidat konstantu"
constant: "Konstanta"
defaultValue: "Výchozí hodnota"
color: "Barva"
+ refProp: "Odkázat na vlastnost"
+ refConst: "Odkázat na konstantu"
key: "Klíč"
func: "Funkce "
+ funcKind: "Typ funkce"
+ argument: "Argument"
+ basedProp: "Odkazovaná vlastnost"
+ alpha: "Průhlednost"
+ darken: "Ztmavit"
+ lighten: "Zesvětlit"
+ inputConstantName: "Zadejte název pro tuto konstantu"
+ importInfo: "Pokud zde zadáte kód motivu, můžete jej importovat do editoru motivu."
+ deleteConstantConfirm: "Opravdu chcete odstranit konstantu {const}?"
keys:
+ accent: "Akcent"
+ bg: "Pozadí"
+ fg: "Text"
+ focus: "Fokus"
+ indicator: "Indikátor"
+ panel: "Panely"
shadow: "Stín"
header: "Nadpis"
+ navBg: "Pozadí postranního panelu"
+ navFg: "Text na postranním panelu"
+ navHoverFg: "Text na postranním panelu (Hover)"
+ navActive: "Text na postranním panelu (Aktivní)"
+ navIndicator: "Indikátor na postranním panelu"
link: "Odkaz"
hashtag: "Hashtag"
mention: "Zmínění"
+ mentionMe: "Zmínky (mě)"
renote: "Přeposlat"
+ modalBg: "Pozadí Modalu"
divider: "Dělící čára"
+ scrollbarHandle: "Rukojeť posuvníku"
+ scrollbarHandleHover: "Rukojeť posuvníku (Hover)"
+ dateLabelFg: "Text štítku s datem"
+ infoBg: "Pozadí informací"
+ infoFg: "Text informací"
+ infoWarnBg: "Pozadí varování"
+ infoWarnFg: "Text varování"
+ toastBg: "Pozadí oznámení"
+ toastFg: "Text oznámení"
+ buttonBg: "Pozadí tlačítka"
+ buttonHoverBg: "Pozadí tlačítka (Hover)"
+ inputBorder: "Ohraničení vstupního pole"
+ listItemHoverBg: "Pozadí položky seznamu (Hover)"
+ driveFolderBg: "Pozadí složky disku"
+ wallpaperOverlay: "Překrytí tapety"
+ badge: "Odznak"
+ messageBg: "Pozadí chatu"
+ accentDarken: "Akcent (Ztmavený)"
+ accentLighten: "Akcent (Zesvětlený)"
+ fgHighlighted: "Zvýrazněný text"
_sfx:
note: "Poznámky"
+ noteMy: "Moje poznámka"
notification: "Oznámení"
- chat: "Zprávy"
+ antenna: "Antény"
+ channel: "Oznámení kanálu"
_ago:
future: "Budoucí"
justNow: "Teď"
+ secondsAgo: "Před {n}s"
+ minutesAgo: "Před {n}min"
+ hoursAgo: "Před {n}h"
+ daysAgo: "Před {n}d"
+ weeksAgo: "Před {n}t"
+ monthsAgo: "Před {n}m"
+ yearsAgo: "Před {n}r"
invalid: "Nic nebylo nalezeno"
_time:
second: "Sekund"
minute: "Minut"
hour: "Hodin"
+ day: "Dnů"
_2fa:
+ alreadyRegistered: "Již jste zaregistrovali dvoufaktorové ověřovací zařízení."
+ registerTOTP: "Registrovat aplikaci autentizátoru"
+ step1: "Nejprve si do zařízení nainstalujte aplikaci pro ověřování (například {a} nebo {b})."
+ step2: "Poté naskenujte QR kód zobrazený na této obrazovce."
+ step2Click: "Kliknutím na tento QR kód můžete zaregistrovat 2FA do bezpečnostního klíče nebo aplikace autentizace telefonu."
+ step3Title: "Zadejte ověřovací kód"
+ step3: "Pro dokončení nastavení zadejte token poskytnutý vaší aplikací."
+ step4: "Od této chvíle budou všechny budoucí pokusy o přihlášení vyžadovat tento přihlašovací token."
+ securityKeyNotSupported: "Váš prohlížeč nepodporuje bezpečnostní klíče."
+ registerTOTPBeforeKey: "Nastavte aplikaci autentizátoru pro registraci bezpečnostního nebo přístupového klíče."
+ securityKeyInfo: "Kromě ověřování otiskem prstu nebo PIN můžete nastavit také ověřování pomocí hardwarových bezpečnostních klíčů, které podporují FIDO2, a svůj účet tak dále zabezpečit."
+ registerSecurityKey: "Registrace bezpečnostního nebo přístupového klíče"
+ securityKeyName: "Zadejte název klíče"
+ tapSecurityKey: "Při registraci bezpečnostního nebo přístupového klíče postupujte podle svého prohlížeče."
+ removeKey: "Odstranit bezpečnostní klíč"
+ removeKeyConfirm: "Opravdu chcete odstranit klíč {name}?"
+ whyTOTPOnlyRenew: "Aplikaci autentizátoru nelze odstranit, dokud je zaregistrován bezpečnostní klíč."
+ renewTOTP: "Překonfigurování aplikace autentizátor"
+ renewTOTPConfirm: "Tohle způsobí, že ověřovací kódy z předchozí aplikace přestanou fungovat."
+ renewTOTPOk: "Přenastavit"
renewTOTPCancel: "Ne děkuji"
+_permissions:
+ "read:account": "Zobrazit informace o účtu"
+ "write:account": "Upravit informace o účtu"
+ "read:blocks": "Zobrazit seznam blokovaných uživatelů"
+ "write:blocks": "Upravit seznam blokovaných uživatelů"
+ "read:drive": "Přístup k souborům a složkám na disku"
+ "write:drive": "Úprava nebo odstranění souborů a složek na disku"
+ "read:favorites": "Zobrazit seznam oblíbených"
+ "write:favorites": "Upravit seznam oblíbených"
+ "read:following": "Zobrazit informace o tom, koho sledujete"
+ "write:following": "Sledování nebo zrušení sledování jiných účtů"
+ "read:messaging": "Zobrazit chat"
+ "write:messaging": "Sestavit nebo mazat zprávy chatu"
+ "read:mutes": "Zobrazit seznam ztlumených uživatelů"
+ "write:mutes": "Upravit seznam ztlumených uživatelů"
+ "write:notes": "Sestavit nebo odstranit poznámky"
+ "read:notifications": "Zobrazit oznámení"
+ "write:notifications": "Spravit oznámení"
+ "read:reactions": "Zobrazit vaše reakce"
+ "write:reactions": "Upravit své reakce"
+ "write:votes": "Hlasovat v anketě"
+ "read:pages": "Zobrazit své stránky"
+ "write:pages": "Upravit nebo odstranit stránky"
+ "read:page-likes": "Zobrazit to se mi líbí na stránkách"
+ "write:page-likes": "Upravit to se mi líbí na stránkách"
+ "read:user-groups": "Zobrazit skupiny uživatelů"
+ "write:user-groups": "Upravit nebo odstranit skupiny uživatelů"
+ "read:channels": "Zobrazit své kanály"
+ "write:channels": "Upravit kanály"
+ "read:gallery": "Zobrazit galerii"
+ "write:gallery": "Upravit galerii"
+ "read:gallery-likes": "Zobrazit seznam to se mi líbí příspěvků v galerii"
+ "write:gallery-likes": "Upravit seznam to se mi líbí příspěvků v galerii"
+_auth:
+ shareAccessTitle: "Udělovat oprávnění k aplikacím"
+ shareAccess: "Chcete autorizovat \"{name}\" pro přístup k tomuto účtu?"
+ shareAccessAsk: "Opravdu chcete této aplikaci povolit přístup k vašemu účtu?"
+ permission: "{jméno} požaduje tato oprávnění"
+ permissionAsk: "Tato aplikace požaduje následující oprávnění"
+ pleaseGoBack: "Vraťte se prosím zpět do aplikace"
+ callback: "Návrat k aplikaci"
+ denied: "Přístup odepřen"
+ pleaseLogin: "Pro autorizaci aplikací se prosím přihlaste."
+_antennaSources:
+ all: "Všechny poznámky"
+ homeTimeline: "Poznámky sledovaných uživatelů"
+ users: "Poznámky konkrétních uživatelů"
+ userList: "Poznámky z určitého seznamu uživatelů"
_weekday:
sunday: "Neděle"
monday: "Pondělí"
@@ -874,38 +1738,81 @@ _weekday:
_widgets:
profile: "Váš profil"
instanceInfo: "Informace o instanci"
+ memo: "Přilepené poznámky"
notifications: "Oznámení"
timeline: "Časová osa"
calendar: "Kalendář"
trends: "Trendy"
clock: "Hodiny"
rss: "RSS čtečka"
+ rssTicker: "RSS Ticker"
activity: "Aktivita"
photos: "Fotky"
digitalClock: "Digitální hodiny"
+ unixClock: "Hodiny UNIX"
federation: "Federace"
+ instanceCloud: "Cloud instance"
+ postForm: "Formulář pro odeslání"
slideshow: "Prezentace"
button: "Tlačítko"
onlineUsers: "Online uživatelé"
jobQueue: "Fronta úloh"
+ serverMetric: "Metriky serveru"
aiscript: "AiScript conzole"
+ aiscriptApp: "Aplikace AiScript"
aichan: "Ai"
+ userList: "Seznam uživatelů"
_userList:
chooseList: "Vybrat seznam"
+ clicker: "Clicker"
_cw:
hide: "Skrýt"
show: "Zobrazit více"
+ chars: "{count} charakterů"
+ files: "{count} souborů"
_poll:
+ noOnlyOneChoice: "Jsou zapotřebí alespoň dvě možnosti"
+ choiceN: "Volba {n}"
noMore: "Více už přidat nemůžete"
+ canMultipleVote: "Umožnit výběr více možností"
+ expiration: "Ukončení ankety"
infinite: "Nikdy"
+ at: "Ukončit v"
+ after: "Ukončit po"
deadlineDate: "Datum ukončení"
deadlineTime: "Hodin"
duration: "Trvání"
+ votesCount: "{n} hlasů"
+ totalVotes: "{n} hlasů celkově"
+ vote: "Hlasovat v anketě"
+ showResult: "Zobrazit výsledky"
+ voted: "Odhlasováno"
+ closed: "Uzavřeno"
+ remainingDays: "Zbývá {d} den/dní a {h} hodin/a"
+ remainingHours: "Zbývá {h} hodin/a a {m} minut/a"
+ remainingMinutes: "Zbývá {m} minut/a a {s} sekund/a"
+ remainingSeconds: "Zbývá {s} sekund/a"
_visibility:
+ public: "Veřejný"
+ publicDescription: "Vaše poznámka bude viditelná pro všechny uživatele"
home: "Domů"
+ homeDescription: "Zveřejnit příspěvek pouze na domovskou časovou osu"
followers: "Sledující"
+ followersDescription: "Zviditelnit pouze pro své sledující"
+ specified: "Přímý"
+ specifiedDescription: "Zviditelnit pouze pro určité uživatele"
+ disableFederation: "Defederace"
+ disableFederationDescription: "Nepřenášet do jiných instancí"
_postForm:
+ replyPlaceholder: "Odpovědět na tuto poznámku..."
+ quotePlaceholder: "Citovat tuto poznámku..."
+ channelPlaceholder: "Zveřejnit příspěvek do kanálu..."
_placeholders:
+ a: "Co máte v plánu?"
+ b: "Co se děje kolem vás?"
+ c: "Co máte na mysli?"
+ d: "Co chcete říct?"
+ e: "Začít psát..."
f: "Čekám, až něco napíšete..."
_profile:
name: "Jméno"
@@ -913,36 +1820,100 @@ _profile:
description: "O mně"
youCanIncludeHashtags: "V popisku o Vás můžete použít i hastagy."
metadata: "Doplňující informace"
+ metadataEdit: "Upravit doplňující informace"
+ metadataDescription: "Pomocí nich můžete ve svém profilu zobrazit doplňující informační pole."
+ metadataLabel: "Popisek"
metadataContent: "Obsah"
+ changeAvatar: "Změnit avatara"
+ changeBanner: "Změnit banner"
_exportOrImport:
allNotes: "Všechny poznámky"
+ favoritedNotes: "Oblíbené poznámky"
followingList: "Sledovaní"
muteList: "Ztlumit"
blockingList: "Zablokovat"
userLists: "Seznamy"
+ excludeMutingUsers: "Vyloučit ztlumené uživatele"
+ excludeInactiveUsers: "Vyloučit neaktivní uživatele"
_charts:
federation: "Federace"
apRequest: "Požadavek"
+ usersIncDec: "Rozdíl v počtech uživatelů"
usersTotal: "Celkem uživatelů"
activeUsers: "Aktivní uživatelé"
+ notesIncDec: "Rozdíl v počtu poznámek"
+ localNotesIncDec: "Rozdíl v počtu místních poznámek"
+ remoteNotesIncDec: "Rozdíl v počtu vzdálených poznámek"
notesTotal: "Celkový počet poznámek"
+ filesIncDec: "Rozdíl v počtu souborů"
+ filesTotal: "Celkový počet souborů"
+ storageUsageIncDec: "Rozdíl ve využití úložiště"
+ storageUsageTotal: "Celkové využití úložiště"
+_instanceCharts:
+ requests: "Požadavky"
+ users: "Rozdíl v počtech uživatelů"
+ usersTotal: "Kumulativní počet uživatelů"
+ notes: "Rozdíl v počtu poznámek"
+ notesTotal: "Kumulativní počet poznámek"
+ ff: "Rozdíl v počtu sledovaných uživatelů / sledujících"
+ ffTotal: "Kumulativní počet sledovaných uživatelů / sledujících"
+ cacheSize: "Rozdíl ve velikosti mezipaměti"
+ cacheSizeTotal: "Kumulativní celková velikost mezipaměti"
+ files: "Rozdíl v počtu souborů"
+ filesTotal: "Kumulativní počet souborů"
_timelines:
home: "Domů"
+ local: "Místní"
+ social: "Sociální síť"
global: "Globální"
_play:
+ new: "Vytvořit Play"
+ edit: "Upravit Play"
+ created: "Play vytvořen"
+ updated: "Play upraven"
+ deleted: "Play smazán"
+ pageSetting: "Nastavení Play"
+ editThisPage: "Upravit tenhle Play"
+ viewSource: "Zobrazit zdroj"
+ my: "Moje Plays"
+ liked: "To se mi líbí Plays"
+ featured: "Populární"
+ title: "Titulek"
script: "Skript"
summary: "Popis"
_pages:
newPage: "Vytvořit novou stránku"
editPage: "Upravit stránku"
+ readPage: "Prohlížení zdroje této stránky"
created: "Stránka byla úspěšně vytvořena"
updated: "Stránka byla úspěšně aktualizována"
deleted: "Stránka byla úspěšně smazána"
pageSetting: "Nastavení stránky"
+ nameAlreadyExists: "Zadaná adresa URL stránky již existuje"
+ invalidNameTitle: "Zadaná adresa URL stránky je neplatná"
invalidNameText: "Ujistěte se že jméno stránky je vyplněno"
+ editThisPage: "Upravit tuto stránku"
+ viewSource: "Zobrazit zdroj"
+ viewPage: "Zobrazit své stránky"
+ like: "To se mi líbí"
+ unlike: "Už se mi to nelíbí"
+ my: "Moje stránky"
+ liked: "To se mi líbí stránky"
+ featured: "Populární"
+ inspector: "Inspektor"
contents: "Obsah"
+ content: "Blok stránky"
+ variables: "Proměnné"
+ title: "Titulek"
+ url: "URL stránky"
+ summary: "Přehled stránky"
+ alignCenter: "Vycentrovat prvky"
+ hideTitleWhenPinned: "Skrytí názvu stránky při připnutí k profilu"
+ font: "Písmo"
fontSerif: "Serif"
fontSansSerif: "Sans Serif"
+ eyeCatchingImageSet: "Nastavení miniatury"
+ eyeCatchingImageRemove: "Smazání miniatury"
chooseBlock: "Přidat blok"
selectType: "Vyberte typ"
contentBlocks: "Obsah"
@@ -954,8 +1925,28 @@ _pages:
section: "Sekce"
image: "Obrázky"
button: "Tlačítko"
+ note: "Vestavěná poznámka"
+ _note:
+ id: "ID poznámky"
+ idDescription: "Adresu URL poznámky můžete vložit také sem."
+ detailed: "Podrobné zobrazení"
+_relayStatus:
+ requesting: "Čeká se"
+ accepted: "Schváleno"
+ rejected: "Odmítnuto"
_notification:
+ fileUploaded: "Soubor úspěšně nahrán"
+ youGotMention: "{name} vás zmínil"
+ youGotReply: "{name} vám odpověděl"
+ youGotQuote: "{name} vás citoval"
+ youRenoted: "Poznámka od {jméno}"
youWereFollowed: "Máte nového následovníka"
+ youReceivedFollowRequest: "Obdrželi jste žádost o sledování"
+ yourFollowRequestAccepted: "Vaše žádost o sledování byla přijata"
+ pollEnded: "Výsledky ankety jsou k dispozici"
+ unreadAntennaNote: "Anténa {name}"
+ emptyPushNotificationMessage: "Push oznámení byla aktualizována"
+ achievementEarned: "Úspěch odemčen"
_types:
all: "Vše"
follow: "Sledovaní"
@@ -964,17 +1955,68 @@ _notification:
renote: "Přeposlat"
quote: "Citovat"
reaction: "Reakce"
+ pollEnded: "Anketa končí"
+ receiveFollowRequest: "Obdržené žádosti o sledování"
+ followRequestAccepted: "Přijaté žádosti o sledování"
+ achievementEarned: "Úspěch odemčen"
+ app: "Oznámení z propojených aplikací"
_actions:
+ followBack: "vás začal sledovat zpět"
reply: "Odpovědět"
renote: "Přeposlat"
_deck:
+ alwaysShowMainColumn: "Vždy zobrazovat hlavní sloupec"
+ columnAlign: "Zarovnat sloupce"
+ addColumn: "Přidat sloupec"
+ configureColumn: "Nastavení sloupců"
+ swapLeft: "Prohodit s levým sloupcem"
+ swapRight: "Prohodit s pravým sloupcem"
+ swapUp: "Prohodit s výše uvedeným sloupcem"
+ swapDown: "Prohodit s níže uvedeným sloupcem"
+ stackLeft: "Nahromadit v levém sloupci"
+ popRight: "Popnout sloupec na pravou stranu"
+ profile: "Profil"
+ newProfile: "Nový profil"
+ deleteProfile: "Smazat profil"
+ introduction: "Vytvořte si dokonalé rozhraní volným uspořádáním sloupců!"
+ introduction2: "Kliknutím na tlačítko + v pravé části obrazovky můžete kdykoli přidat nové sloupce."
+ widgetsIntroduction: "V nabídce sloupce vyberte možnost \"Upravit widgety\" a přidejte widget."
+ useSimpleUiForNonRootPages: "Použít zjednodušené uživatelské rozhraní pro navigaci na stránkách"
_columns:
+ main: "Hlavní"
+ widgets: "Widgety"
notifications: "Oznámení"
tl: "Časová osa"
antenna: "Antény"
list: "Seznamy"
channel: "Kanály"
mentions: "Zmínění"
+ direct: "Přímý"
+ roleTimeline: "Časová osa role"
+_dialog:
+ charactersExceeded: "Překročili jste maximální počet znaků! V současné době je na hodnotě {current} z {max}."
+ charactersBelow: "Nedosahujete minimálního limitu znaků! V současné době je na {current} z {min}."
+_disabledTimeline:
+ title: "Časová osa vypnuta"
+ description: "Tuto časovou osu nemůžete používat v rámci svých současných rolí."
+_drivecleaner:
+ orderBySizeDesc: "Sestupná velikost souborů"
+ orderByCreatedAtAsc: "Vzestupné datumy"
_webhookSettings:
+ createWebhook: "Vytvořit Webhook"
name: "Jméno"
+ secret: "Tajné"
+ events: "Události Webhook"
active: "Zapnuto"
+ _events:
+ follow: "Při sledování uživatele"
+ followed: "Při sledování"
+ note: "Při zveřejňování poznámky"
+ reply: "Při obdržení odpovědi"
+ renote: "Při renotaci poznámky"
+ reaction: "Při obdržení reakce"
+ mention: "Při zmínce"
+_moderationLogTypes:
+ suspend: "Zmrazit"
+ resetPassword: "Resetovat heslo"
+ createInvitation: "Vygenerovat pozvánku"
diff --git a/locales/de-DE.yml b/locales/de-DE.yml
index 874c70dd85..af927586d0 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -2,7 +2,7 @@
_lang_: "Deutsch"
headlineMisskey: "Ein durch Notizen verbundenes Netzwerk"
introMisskey: "Willkommen! Misskey ist eine dezentralisierte Open-Source Microblogging-Platform.\nVerfasse „Notizen“ um mitzuteilen, was gerade passiert oder um Ereignisse mit anderen zu teilen. 📡\nMit „Reaktionen“ kannst du außerdem schnell deine Gefühle über Notizen anderer Benutzer zum Ausdruck bringen. 👍\nEine neue Welt wartet auf dich! 🚀"
-poweredByMisskeyDescription: "{name} ist einer der durch die Open-Source-Plattform Misskey betriebenen Dienste (meist als \"Misskey-Instanz\" bezeichnet)."
+poweredByMisskeyDescription: "{name} ist einer der durch die Open-Source-Plattform Misskey betriebenen Dienste."
monthAndDay: "{day}.{month}."
search: "Suchen"
notifications: "Benachrichtigungen"
@@ -45,6 +45,7 @@ pin: "An dein Profil anheften"
unpin: "Von deinem Profil lösen"
copyContent: "Inhalt kopieren"
copyLink: "Link kopieren"
+copyLinkRenote: "Renote-Link kopieren"
delete: "Löschen"
deleteAndEdit: "Löschen und Bearbeiten"
deleteAndEditConfirm: "Möchtest du diese Notiz wirklich löschen und bearbeiten? Alle Reaktionen, Renotes und Antworten dieser Notiz werden verloren gehen."
@@ -74,7 +75,7 @@ import: "Import"
export: "Export"
files: "Dateien"
download: "Herunterladen"
-driveFileDeleteConfirm: "Möchtest du die Datei „{name}“ wirklich löschen? Sie wird in allen Inhalten, die sie verwenden, auch verschwinden."
+driveFileDeleteConfirm: "Möchtest du die Datei „{name}“ wirklich löschen? Einige Inhalte, die diese Datei verwenden, werden auch verschwinden."
unfollowConfirm: "Möchtest du {name} wirklich nicht mehr folgen?"
exportRequested: "Du hast einen Export angefragt. Dies kann etwas Zeit in Anspruch nehmen. Sobald der Export abgeschlossen ist, wird er deiner Drive hinzugefügt."
importRequested: "Du hast einen Import angefragt. Dies kann etwas Zeit in Anspruch nehmen."
@@ -156,6 +157,7 @@ addEmoji: "Emoji hinzufügen"
settingGuide: "Empfohlene Einstellung"
cacheRemoteFiles: "Dateien von fremden Instanzen im Cache speichern"
cacheRemoteFilesDescription: "Ist diese Einstellung deaktiviert, so werden Dateien fremder Instanzen direkt von dort geladen. Hierdurch wird Speicherplatz auf diesem Server gespart, aber durch fehlende Generierung von Vorschaubildern mehr Bandbreite verwendet."
+youCanCleanRemoteFilesCache: "Klicke auf den 🗑️-Knopf der Dateiverwaltungsansicht, um den Cache zu leeren."
cacheRemoteSensitiveFiles: "Sensitive Dateien von fremden Instanzen im Cache speichern"
cacheRemoteSensitiveFilesDescription: "Ist diese Einstellung deaktiviert, so werden sensitive Dateien fremder Instanzen direkt von dort ohne Zwischenspeicherung geladen."
flagAsBot: "Als Bot markieren"
@@ -193,6 +195,7 @@ perHour: "Pro Stunde"
perDay: "Pro Tag"
stopActivityDelivery: "Senden von Aktivitäten einstellen"
blockThisInstance: "Diese Instanz blockieren"
+silenceThisInstance: "Instanz stummschalten"
operations: "Aktionen"
software: "Software"
version: "Version"
@@ -212,6 +215,8 @@ clearCachedFiles: "Cache leeren"
clearCachedFilesConfirm: "Sollen alle im Cache gespeicherten Dateien von anderen Instanzen wirklich gelöscht werden?"
blockedInstances: "Blockierte Instanzen"
blockedInstancesDescription: "Gib die Hostnamen der Instanzen, welche blockiert werden sollen, durch Zeilenumbrüche getrennt an. Blockierte Instanzen können mit dieser instanz nicht mehr kommunizieren."
+silencedInstances: "Stummgeschaltete Instanzen"
+silencedInstancesDescription: "Gib die Hostnamen der Instanzen, welche stummgeschaltet werden sollen, durch Zeilenumbrüche getrennt an. Alle Konten dieser Instanzen werden als stummgeschaltet behandelt, können nur noch Follow-Anfragen stellen und wenn nicht gefolgt keine lokalen Konten erwähnen. Blockierte Instanzen sind davon nicht betroffen."
muteAndBlock: "Stummschaltungen und Blockierungen"
mutedUsers: "Stummgeschaltete Benutzer"
blockedUsers: "Blockierte Benutzer"
@@ -230,7 +235,7 @@ noJobs: "Keine Jobs vorhanden"
federating: "Wird föderiert"
blocked: "Blockiert"
suspended: "Gesperrt"
-all: "Alles"
+all: "Alle"
subscribing: "Wird abonniert"
publishing: "Wird veröffentlicht"
notResponding: "Antwortet nicht"
@@ -319,7 +324,7 @@ copyUrl: "URL kopieren"
rename: "Umbenennen"
avatar: "Profilbild"
banner: "Banner"
-displayOfSensitiveMedia: "Anzeige von sensiblen Medien"
+displayOfSensitiveMedia: "Darstellung sensibler Medien"
whenServerDisconnected: "Bei Verbindungsverlust zum Server"
disconnectedFromServer: "Die Verbindung zum Server wurde getrennt"
reload: "Aktualisieren"
@@ -354,7 +359,6 @@ invite: "Einladen"
driveCapacityPerLocalAccount: "Drive-Kapazität pro lokalem Benutzerkonto"
driveCapacityPerRemoteAccount: "Drive-Kapazität pro Benutzer fremder Instanzen"
inMb: "In Megabytes"
-iconUrl: "Icon-URL (favicon etc)"
bannerUrl: "Banner-URL"
backgroundImageUrl: "Hintergrundbild-URL"
basicInfo: "Grundlegende Informationen"
@@ -410,13 +414,17 @@ aboutMisskey: "Über Misskey"
administrator: "Administrator"
token: "Token"
2fa: "Zwei-Faktor-Authentifizierung"
+setupOf2fa: "Zweifaktorauthentifizierung einrichten"
totp: "Authentifizierungs-App"
totpDescription: "Logge dich via Authentifizierungs-App mit Einmalpasswort ein"
moderator: "Moderator"
moderation: "Moderation"
+moderationNote: "Moderationsnotiz"
+addModerationNote: "Moderationsnotiz hinzufügen"
+moderationLogs: "Moderationsprotokolle"
nUsersMentioned: "Von {n} Benutzern erwähnt"
-securityKeyAndPasskey: "Security-Tokens und Passkeys"
-securityKey: "Sicherheitsschlüssel"
+securityKeyAndPasskey: "Hardware-Sicherheitsschlüssel und Passkeys"
+securityKey: "Hardware-Sicherheitsschlüssel"
lastUsed: "Zuletzt benutzt"
lastUsedAt: "Zuletzt verwendet: {t}"
unregister: "Deaktivieren"
@@ -526,6 +534,7 @@ serverLogs: "Serverprotokolle"
deleteAll: "Alle löschen"
showFixedPostForm: "Bereich zum Schreiben neuer Notizen am Anfang der Chronik anzeigen"
showFixedPostFormInChannel: "Bereich zum Schreiben neuer Notizen am Anfang der Chronik anzeigen (Kanäle)"
+withRepliesByDefaultForNewlyFollowed: "Standardmäßig Antworten von neu gefolgten Benutzern in der Chronik anzeigen"
newNoteRecived: "Es gibt neue Notizen"
sounds: "Töne"
sound: "Töne"
@@ -540,7 +549,7 @@ chooseEmoji: "Emoji auswählen"
unableToProcess: "Der Vorgang konnte nicht abgeschlossen werden"
recentUsed: "Vor kurzem verwendet"
install: "Installieren"
-uninstall: "Uninstallieren"
+uninstall: "Deinstallieren"
installedApps: "Authorisierte Anwendungen"
nothing: "Hier gibt es nichts zu sehen"
installedDate: "Authorisiert am"
@@ -631,11 +640,11 @@ regexpErrorDescription: "Im regulären Ausdruck deiner {tab}en Wortstummschaltun
instanceMute: "Instanzstummschaltungen"
userSaysSomething: "{name} hat etwas gesagt"
makeActive: "Aktivieren"
-display: "Anzeigen"
+display: "Anzeigeart"
copy: "Kopieren"
metrics: "Metriken"
overview: "Übersicht"
-logs: "Logs"
+logs: "Protokolle"
delayed: "Verzögert"
database: "Datenbank"
channel: "Kanäle"
@@ -653,6 +662,7 @@ behavior: "Verhalten"
sample: "Beispiel"
abuseReports: "Meldungen"
reportAbuse: "Melden"
+reportAbuseRenote: "Renote melden"
reportAbuseOf: "{name} melden"
fillAbuseReportDescription: "Bitte gib zusätzliche Informationen zu dieser Meldung an. Falls es sich um eine spezielle Notiz handelt, bitte gib dessen URL an."
abuseReported: "Deine Meldung wurde versendet. Vielen Dank."
@@ -680,6 +690,7 @@ createNewClip: "Neuen Clip erstellen"
unclip: "Aus Clip entfernen"
confirmToUnclipAlreadyClippedNote: "Diese Notiz ist bereits im \"{name}\" Clip enthalten. Möchtest du sie aus diesem Clip entfernen?"
public: "Öffentlich"
+private: "Privat"
i18nInfo: "Misskey wird durch freiwillige Helfer in viele verschiedene Sprachen übersetzt. Auf {link} kannst du mithelfen."
manageAccessTokens: "Zugriffstokens verwalten"
accountInfo: "Benutzerkonto-Informationen"
@@ -704,6 +715,7 @@ lockedAccountInfo: "Auch wenn du Follow-Anfragen auf manuelle Bestätigung setzt
alwaysMarkSensitive: "Medien standardmäßig als sensibel markieren"
loadRawImages: "Anstatt Vorschaubilder immer Originalbilder anzeigen"
disableShowingAnimatedImages: "Animierte Bilder nicht abspielen"
+highlightSensitiveMedia: "Sensitive Medien markieren"
verificationEmailSent: "Eine Bestätigungsmail wurde an deine Email-Adresse versendet. Besuche den dort enthaltenen Link, um die Verifizierung abzuschließen."
notSet: "Nicht konfiguriert"
emailVerified: "Email-Adresse bestätigt"
@@ -786,7 +798,7 @@ active: "Aktiv"
offline: "Offline"
notRecommended: "Nicht empfohlen"
botProtection: "Schutz vor Bots"
-instanceBlocking: "Blockierte Instanzen"
+instanceBlocking: "Blockierte/Stummgeschaltete Instanzen"
selectAccount: "Benutzerkonto auswählen"
switchAccount: "Konto wechseln"
enabled: "Aktiviert"
@@ -907,7 +919,7 @@ typeToConfirm: "Bitte gib zur Bestätigung {x} ein"
deleteAccount: "Benutzerkonto löschen"
document: "Dokumentation"
numberOfPageCache: "Seitencachegröße"
-numberOfPageCacheDescription: "Das Erhöhen dieses Caches führt zu einer angenehmerern Benutzererfahrung, erhöht aber Serverlast und Arbeitsspeicherauslastung."
+numberOfPageCacheDescription: "Das Erhöhen dieses Caches führt zu einer angenehmerern Benutzererfahrung, aber erhöht Last und Arbeitsspeicherauslastung auf dem Nutzergerät."
logoutConfirm: "Wirklich abmelden?"
lastActiveDate: "Zuletzt verwendet am"
statusbar: "Statusleiste"
@@ -967,6 +979,7 @@ assign: "Zuweisen"
unassign: "Entfernen"
color: "Farbe"
manageCustomEmojis: "Kann benutzerdefinierte Emojis verwalten"
+manageAvatarDecorations: "Profilbilddekorationen verwalten"
youCannotCreateAnymore: "Du hast das Erstellungslimit erreicht."
cannotPerformTemporary: "Vorübergehend nicht verfügbar"
cannotPerformTemporaryDescription: "Diese Aktion ist wegen des Überschreitenes des Ausführungslimits temporär nicht verfügbar. Bitte versuche es nach einiger Zeit erneut."
@@ -1018,7 +1031,7 @@ retryAllQueuesConfirmText: "Dies wird zu einer temporären Erhöhung der Serverl
enableChartsForRemoteUser: "Diagramme für Nutzer fremder Instanzen erstellen"
enableChartsForFederatedInstances: "Diagramme für fremde Instanzen erstellen"
showClipButtonInNoteFooter: "\"Clip\" zum Notizmenu hinzufügen"
-largeNoteReactions: "Reaktionen vergrößert anzeigen"
+reactionsDisplaySize: "Reaktionsanzeigegröße"
noteIdOrUrl: "Notiz-ID oder URL"
video: "Video"
videos: "Videos"
@@ -1042,7 +1055,7 @@ vertical: "Vertikal"
horizontal: "Horizontal"
position: "Position"
serverRules: "Serverregeln"
-pleaseConfirmBelowBeforeSignup: "Lies bitte Untenstehendes vor der Registration."
+pleaseConfirmBelowBeforeSignup: "Lies bitte diese Informationen und stimme ihnen vor der Registration zu."
pleaseAgreeAllToContinue: "Zum Fortfahren muss allen obigen Feldern zugestimmt werden."
continue: "Fortfahren"
preservedUsernames: "Reservierte Benutzernamen"
@@ -1052,7 +1065,7 @@ archive: "Archivieren"
channelArchiveConfirmTitle: "{name} wirklich archivieren?"
channelArchiveConfirmDescription: "Ein archivierter Kanal taucht nicht mehr in der Kanalliste oder in Suchergebnissen auf. Zudem können ihm keine Beiträge mehr hinzugefügt werden."
thisChannelArchived: "Dieser Kanal wurde archiviert."
-displayOfNote: "Anzeige von Notizen"
+displayOfNote: "Darstellung von Notizen"
initialAccountSetting: "Kontoeinrichtung"
youFollowing: "Gefolgt"
preventAiLearning: "Verwendung in machinellem Lernen (Generative bzw. Prediktive AI/KI) ablehnen"
@@ -1094,6 +1107,64 @@ expired: "Abgelaufen"
doYouAgree: "Zustimmen?"
beSureToReadThisAsItIsImportant: "Lies bitte diese wichtige Informationen."
iHaveReadXCarefullyAndAgree: "Ich habe den Text \"{x}\" gelesen und stimme zu."
+dialog: "Dialogfeld"
+icon: "Symbol"
+forYou: "Für dich"
+currentAnnouncements: "Aktuelle Ankündigungen"
+pastAnnouncements: "Alte Ankündigungen"
+youHaveUnreadAnnouncements: "Es gibt neue Ankündigungen."
+useSecurityKey: "Folge bitten den Anweisungen deines Browsers bzw. Gerätes und verwende deinen Hardware-Sicherheitsschlüssel oder Passkey."
+replies: "Antworten"
+renotes: "Renotes"
+loadReplies: "Antworten anzeigen"
+loadConversation: "Unterhaltung anzeigen"
+pinnedList: "Angeheftete Liste"
+keepScreenOn: "Bildschirm angeschaltet lassen"
+verifiedLink: "Link-Besitz wurde verifiziert"
+notifyNotes: "Über neue Notizen benachrichtigen"
+unnotifyNotes: "Nicht über neue Notizen benachrichtigen"
+authentication: "Authentifikation"
+authenticationRequiredToContinue: "Bitte authentifiziere dich, um fortzufahren"
+dateAndTime: "Zeit"
+showRenotes: "Renotes anzeigen"
+edited: "Bearbeitet"
+notificationRecieveConfig: "Benachrichtigungseinstellungen"
+mutualFollow: "Gegenseitig gefolgt"
+fileAttachedOnly: "Nur Notizen mit Dateien"
+showRepliesToOthersInTimeline: "Antworten in Chronik anzeigen"
+hideRepliesToOthersInTimeline: "Antworten nicht in Chronik anzeigen"
+showRepliesToOthersInTimelineAll: "Antworten von allen momentan gefolgten Benutzern in Chronik anzeigen"
+hideRepliesToOthersInTimelineAll: "Antworten von allen momentan gefolgten Benutzern nicht in Chronik anzeigen"
+confirmShowRepliesAll: "Dies ist eine unwiderrufliche Aktion. Wirklich Antworten von allen momentan gefolgten Benutzern in der Chronik anzeigen?"
+confirmHideRepliesAll: "Dies ist eine unwiderrufliche Aktion. Wirklich Antworten von allen momentan gefolgten Benutzern nicht in der Chronik anzeigen?"
+externalServices: "Externe Dienste"
+impressum: "Impressum"
+impressumUrl: "Impressums-URL"
+impressumDescription: "In manchen Ländern, wie Deutschland und dessen Umgebung, ist die Angabe von Betreiberinformationen (ein Impressum) bei kommerziellem Betrieb zwingend."
+privacyPolicy: "Datenschutzerklärung"
+privacyPolicyUrl: "Datenschutzerklärungs-URL"
+tosAndPrivacyPolicy: "Nutzungsbedingungen und Datenschutzerklärung"
+avatarDecorations: "Profilbilddekoration"
+attach: "Anbringen"
+detach: "Entfernen"
+angle: "Winkel"
+flip: "Umdrehen"
+showAvatarDecorations: "Profilbilddekoration anzeigen"
+releaseToRefresh: "Zum Aktualisieren loslassen"
+refreshing: "Wird aktualisiert..."
+pullDownToRefresh: "Zum Aktualisieren ziehen"
+disableStreamingTimeline: "Echtzeitaktualisierung der Chronik deaktivieren"
+useGroupedNotifications: "Benachrichtigungen gruppieren"
+cwNotationRequired: "Ist \"Inhaltswarnung verwenden\" aktiviert, muss eine Beschreibung gegeben werden."
+_announcement:
+ forExistingUsers: "Nur für existierende Nutzer"
+ forExistingUsersDescription: "Ist diese Option aktiviert, wird diese Ankündigung nur Nutzern angezeigt, die zum Zeitpunkt der Ankündigung bereits registriert sind. Ist sie deaktiviert, wird sie auch Nutzern, die sich nach dessen Veröffentlichung registrieren, angezeigt."
+ needConfirmationToRead: "Separate Lesebestätigung erfordern"
+ needConfirmationToReadDescription: "Ist dies aktiviert, so wird beim Markieren dieser Ankündigung als gelesen ein separates Bestätigungsfenster angezeigt. Auch wird sie von der \"Alle als gelesen markieren\"-Funktion ausgenommen."
+ end: "Ankündigung archivieren"
+ tooManyActiveAnnouncementDescription: "Zu viele aktive Ankündigungen können die Benutzerfreundlichkeit verschlechtern. Es wird empfohlen, veraltete Ankündigungen zu archivieren."
+ readConfirmTitle: "Als gelesen markieren?"
+ readConfirmText: "Dies markiert den Inhalt von \"{title}\" als gelesen."
_initialAccountSetting:
accountCreated: "Dein Konto wurde erfolgreich erstellt!"
letsStartAccountSetup: "Lass uns nun dein Konto einrichten."
@@ -1106,11 +1177,20 @@ _initialAccountSetting:
pushNotificationDescription: "Durch die Aktivierung von Push-Benachrichtigungen kannst du von {name} Benachrichtigungen direkt auf dein Gerät erhalten."
initialAccountSettingCompleted: "Kontoeinrichtung abgeschlossen!"
haveFun: "Viel Spaß mit {name}!"
- ifYouNeedLearnMore: "Besuche {link}, falls du mehr über {name} (Misskey) lernen möchtest."
skipAreYouSure: "Die Kontoeinrichtung wirklich überspringen?"
laterAreYouSure: "Die Kontoeinrichtung wirklich später erledigen?"
_serverRules:
description: "Eine Reihe von Regeln, die vor der Registrierung angezeigt werden. Eine Zusammenfassung der Nutzungsbedingungen anzuzeigen ist empfohlen."
+_serverSettings:
+ iconUrl: "Icon-URL"
+ appIconDescription: "Gibt das zu verwendende Icon bei der Anzeige von {host} als App an."
+ appIconUsageExample: "Beispielsweise als PWA, oder bei Lesezeichen auf dem Startbildschirm von Smartphones"
+ appIconStyleRecommendation: "Da das Icon zu einem Kreis oder Quadrat zugeschnitten wird, wird ein Icon mit gefülltem Margin um den Inhalt herum empfohlen."
+ appIconResolutionMustBe: "Die Mindestauflösung ist {resolution}."
+ manifestJsonOverride: "Überschreiben von manifest.json"
+ shortName: "Abkürzung"
+ shortNameDescription: "Ein Kürzel für den Namen der Instanz, der angezeigt werden kann, falls der volle Instanzname lang ist."
+ fanoutTimelineDescription: "Ist diese Option aktiviert, kann eine erhebliche Verbesserung im Abrufen von Chroniken und eine Reduzierung der Datenbankbelastung erzielt werden, im Gegenzug zu einer Steigerung in der Speichernutzung von Redis. Bei geringem Serverspeicher oder Serverinstabilität kann diese Option deaktiviert werden."
_accountMigration:
moveFrom: "Von einem anderen Konto zu diesem migrieren"
moveFromSub: "Alias für ein anderes Konto erstellen"
@@ -1365,6 +1445,9 @@ _achievements:
title: "Brain Diver"
description: "Sende den Link zu Brain Diver"
flavor: "Misskey-Misskey La-Tu-Ma"
+ _smashTestNotificationButton:
+ title: "Testüberfluss"
+ description: "Betätige den Benachrichtigungstest mehrfach innerhalb einer extrem kurzen Zeitspanne"
_role:
new: "Rolle erstellen"
edit: "Rolle bearbeiten"
@@ -1408,6 +1491,7 @@ _role:
inviteLimitCycle: "Zyklus des Einladungslimits"
inviteExpirationTime: "Gültigkeitsdauer von Einladungen"
canManageCustomEmojis: "Benutzerdefinierte Emojis verwalten"
+ canManageAvatarDecorations: "Profilbilddekorationen verwalten"
driveCapacity: "Drive-Kapazität"
alwaysMarkNsfw: "Dateien immer als NSFW markieren"
pinMax: "Maximale Anzahl an angehefteten Notizen"
@@ -1422,6 +1506,7 @@ _role:
descriptionOfRateLimitFactor: "Je niedriger desto weniger restriktiv, je höher destro restriktiver."
canHideAds: "Kann Werbung ausblenden"
canSearchNotes: "Nutzung der Notizsuchfunktion"
+ canUseTranslator: "Verwendung des Übersetzers"
_condition:
isLocal: "Lokaler Benutzer"
isRemote: "Benutzer fremder Instanz"
@@ -1470,6 +1555,10 @@ _ad:
reduceFrequencyOfThisAd: "Diese Werbung weniger anzeigen"
hide: "Ausblenden"
timezoneinfo: "Der Wochentag wird durch die Serverzeitzone bestimmt."
+ adsSettings: "Werbeeinstellungen"
+ notesPerOneAd: "Werbeintervall während Echtzeitaktualisierung (Notizen pro Werbung)"
+ setZeroToDisable: "Setze dies auf 0, um Werbung während Echtzeitaktualisierung zu deaktivieren"
+ adsTooClose: "Durch den momentan sehr niedrigen Werbeintervall kann es zu einer starken Verschlechterung der Benutzererfahrung kommen."
_forgotPassword:
enterEmail: "Gib die Email-Adresse ein, mit der du dich registriert hast. An diese wird ein Link gesendet, mit dem du dein Passwort zurücksetzen kannst."
ifNoEmail: "Solltest du bei der Registrierung keine Email-Adresse angegeben haben, wende dich bitte an den Administrator."
@@ -1488,6 +1577,7 @@ _plugin:
install: "Plugins installieren"
installWarn: "Installiere bitte nur vertrauenswürdige Plugins."
manage: "Plugins verwalten"
+ viewSource: "Quelltext anzeigen"
_preferencesBackups:
list: "Erstellte Backups"
saveNew: "Neu erstellen"
@@ -1521,6 +1611,7 @@ _aboutMisskey:
donate: "An Misskey spenden"
morePatrons: "Wir schätzen ebenso die Unterstützung vieler anderer hier nicht gelisteter Personen sehr. Danke! 🥰"
patrons: "UnterstützerInnen"
+ projectMembers: "Projektmitglieder"
_displayOfSensitiveMedia:
respect: "Sensible Medien verbergen"
ignore: "Sensible Medien anzeigen"
@@ -1554,11 +1645,6 @@ _wordMute:
muteWords: "Stummgeschaltete Wörter"
muteWordsDescription: "Zum Nutzen einer \"UND\"-Verknüpfung Einträge mit Leerzeichen trennen, zum Nutzen einer \"ODER\"-Verknüpfung Einträge mit einem Zeilenumbruch trennen."
muteWordsDescription2: "Umgib Schlüsselworter mit Schrägstrichen, um Reguläre Ausdrücke zu verwenden."
- softDescription: "Notizen, die die angegebenen Konditionen erfüllen, in der Chronik ausblenden."
- hardDescription: "Verhindern, dass Notizen, die die angegebenen Konditionen erfüllen, der Chronik hinzugefügt werden. Zudem werden diese Notizen auch nicht der Chronik hinzugefügt, falls die Konditionen geändert werden."
- soft: "Leicht"
- hard: "Schwer"
- mutedNotes: "Stummgeschaltete Notizen"
_instanceMute:
instanceMuteDescription: "Schaltet alle Notizen/Renotes stumm, die von den gelisteten Instanzen stammen, inklusive Antworten von Benutzern an einen Benutzer einer stummgeschalteten Instanz."
instanceMuteDescription2: "Instanzen getrennt durch Zeilenumbrüchen angeben"
@@ -1622,9 +1708,6 @@ _theme:
infoFg: "Text von Informationen"
infoWarnBg: "Hintergrund von Warnungen"
infoWarnFg: "Text von Warnungen"
- cwBg: "Hintergrund des Inhaltswarnungsknopfs"
- cwFg: "Text des Inhaltswarnungsknopfs"
- cwHoverBg: "Hintergrund des Inhaltswarnungsknopfs (Mouseover)"
toastBg: "Hintergrund von Benachrichtigungen"
toastFg: "Text von Benachrichtigungen"
buttonBg: "Hintergrund von Schaltflächen"
@@ -1642,8 +1725,6 @@ _sfx:
note: "Notizen"
noteMy: "Meine Notizen"
notification: "Benachrichtigungen"
- chat: "Chat"
- chatBg: "Chat (Hintergrund)"
antenna: "Antennen"
channel: "Kanalbenachrichtigung"
_ago:
@@ -1662,31 +1743,21 @@ _time:
minute: "Minute(n)"
hour: "Stunde(n)"
day: "Tag(en)"
-_timelineTutorial:
- title: "Wie du Misskey verwendest"
- step1_1: "Dieser Bildschirm ist die \"Chronik\". Hier werden alle \"Notizen\" von {name} angezeigt."
- step1_2: "Es gibt einige verschiedene Chroniken. Beispielsweise werden in der \"Startseite\" alle Notizen von Nutzern, denen du folgst, angezeigt, und in der \"Lokalen Chronik\" werden Notizen aller Nutzer auf {name} angezeigt."
- step2_1: "Lass uns als nächstes versuchen, eine Notiz zu schreiben. Dies kannst du tun, indem du auf den Knopf mit dem Stift-Icon drückst."
- step2_2: "Stell dich den anderen vor oder schreibe einfach \"Hallo {name}!\", wenn du darauf keine Lust hast oder dir nichts einfällt."
- step3_1: "Fertig mit dem Senden deiner ersten Notiz?"
- step3_2: "Falls deine Notiz nun in deiner Chronik auftaucht, hast du alles richtig gemacht."
- step4_1: "Notizen können zusätzlich mit \"Reaktionen\" ausgestattet werden."
- step4_2: "Um eine Reaktion anzufügen, klicke auf das „+“-Symbol einer Notiz und wähle ein Emoji aus, mit dem du reagieren möchtest."
_2fa:
alreadyRegistered: "Du hast bereits ein Gerät für Zwei-Faktor-Authentifizierung registriert."
registerTOTP: "Authentifizierungs-App registrieren"
- passwordToTOTP: "Bitte Passwort eingeben"
step1: "Installiere zuerst eine Authentifizierungsapp (z.B. {a} oder {b}) auf deinem Gerät."
step2: "Dann, scanne den angezeigten QR-Code mit deinem Gerät."
step2Click: "Durch Klicken dieses QR-Codes kannst du Verifikation mit deinem Security-Token oder einer App registrieren."
- step2Url: "Nutzt du ein Desktopprogramm kannst du alternativ diese URL eingeben:"
+ step2Uri: "Nutzt du ein Desktopprogramm, gib folgende URI eingeben"
step3Title: "Authentifizierungsscode eingeben"
- step3: "Gib zum Abschluss den Token ein, der von deiner App angezeigt wird."
- step4: "Alle folgenden Anmeldeversuche werden ab sofort die Eingabe eines solchen Tokens benötigen.\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}"
- securityKeyNotSupported: "Dein Browser unterstützt keine Security-Tokens."
+ step3: "Gib zum Abschluss den Code (Token) ein, der von deiner App angezeigt wird."
+ setupCompleted: "Einrichtung abgeschlossen"
+ step4: "Alle folgenden Anmeldeversuche werden ab sofort die Eingabe eines solchen Tokens benötigen."
+ securityKeyNotSupported: "Dein Browser unterstützt keine Hardware-Sicherheitsschlüssel."
registerTOTPBeforeKey: "Um einen Security-Token oder einen Passkey zu registrieren, musst du zuerst eine Authentifizierungs-App registrieren."
securityKeyInfo: "Du kannst neben Fingerabdruck- oder PIN-Authentifizierung auf deinem Gerät auch Anmeldung mit Hilfe eines FIDO2-kompatiblen Hardware-Sicherheitsschlüssels einrichten."
- registerSecurityKey: "Security-Token oder Passkey registrieren"
+ registerSecurityKey: "Hardware-Sicherheitsschlüssel oder Passkey registrieren"
securityKeyName: "Schlüsselname eingeben"
tapSecurityKey: "Bitten folge den Anweisungen deines Browsers zur Registrierung"
removeKey: "Sicherheitsschlüssel entfernen"
@@ -1696,6 +1767,11 @@ _2fa:
renewTOTPConfirm: "Codes der bisherigen App werden hierdurch nutzlos"
renewTOTPOk: "Neu einrichten"
renewTOTPCancel: "Abbrechen"
+ checkBackupCodesBeforeCloseThisWizard: "Notiere bitte deine Backup-Codes, bevor du dieses Fenster schließt."
+ backupCodes: "Backup-Codes"
+ backupCodesDescription: "Verwende diese Codes, falls du nicht mehr auf deine App zur Zweifaktorauthentifizierung zugreifen kannst. Jeder Code kann nur einmal verwendet werden. Bewahre sie an einem sicheren Ort auf."
+ backupCodeUsedWarning: "Ein Backup-Code wurde verwendet. Falls du den Zugriff zu deiner Zweifaktorauthentifizierungsapp verloren hast, konfiguriere diese bitte möglichst bald erneut."
+ backupCodesExhaustedWarning: "Alle Backup-Codes wurden verwendet. Falls du den Zugang zu deiner Zweifaktorauthentifizierungsapp verlierst, wirst du dich nicht mehr in dieses Konto einloggen können. Bitte konfiguriere diese App erneut."
_permissions:
"read:account": "Deine Benutzerkontoinformationen lesen"
"write:account": "Deine Benutzerkontoinformationen bearbeiten"
@@ -1729,6 +1805,10 @@ _permissions:
"write:gallery": "Deine Galerie bearbeiten"
"read:gallery-likes": "Liste deiner mit \"Gefällt mir\" markierten Galerie-Beiträge lesen"
"write:gallery-likes": "Liste deiner mit \"Gefällt mir\" markierten Galerie-Beiträge bearbeiten"
+ "read:flash": "Deine Plays lesen"
+ "write:flash": "Deine Plays bearbeiten oder löschen"
+ "read:flash-likes": "Liste der Plays, die mir gefallen, lesen"
+ "write:flash-likes": "Liste der Plays, die mir gefallen, bearbeiten"
_auth:
shareAccessTitle: "Verteilung von App-Berechtigungen"
shareAccess: "Möchtest du „{name}“ authorisieren, auf dieses Benutzerkonto zugreifen zu können?"
@@ -1744,6 +1824,7 @@ _antennaSources:
homeTimeline: "Notizen von Benutzern, denen gefolgt wird"
users: "Notizen von einem oder mehreren angegebenen Benutzern"
userList: "Notizen von allen Benutzern einer Liste"
+ userBlacklist: "Alle Notizen abgesehen derer angegebener Benutzer"
_weekday:
sunday: "Sonntag"
monday: "Montag"
@@ -1843,6 +1924,7 @@ _profile:
metadataContent: "Inhalt"
changeAvatar: "Profilbild ändern"
changeBanner: "Banner ändern"
+ verifiedLinkDescription: "Gibst du hier eine URL ein, die einen Link zu deinem Profile enthält, wird neben diesem Feld ein Icon zur Besitzbestätigung angezeigt."
_exportOrImport:
allNotes: "Alle Notizen"
favoritedNotes: "Als Favorit markierte Notizen"
@@ -1852,6 +1934,7 @@ _exportOrImport:
userLists: "Listen"
excludeMutingUsers: "Stummgeschaltete Benutzer aussortieren"
excludeInactiveUsers: "Inaktive Benutzer aussortieren"
+ withReplies: "Antworten von importierten Benutzern in der Chronik beinhalten"
_charts:
federation: "Föderation"
apRequest: "Anfragen"
@@ -1961,11 +2044,20 @@ _notification:
youReceivedFollowRequest: "Du hast eine Follow-Anfrage erhalten"
yourFollowRequestAccepted: "Deine Follow-Anfrage wurde akzeptiert"
pollEnded: "Umfrageergebnisse sind verfügbar"
+ newNote: "Neue Notiz"
unreadAntennaNote: "Antenne {name}"
emptyPushNotificationMessage: "Push-Benachrichtigungen wurden aktualisiert"
achievementEarned: "Errungenschaft freigeschaltet"
+ testNotification: "Testbenachrichtigung"
+ checkNotificationBehavior: "Aussehen von Benachrichtigungen überprüfen"
+ sendTestNotification: "Testbenachrichtigung senden"
+ notificationWillBeDisplayedLikeThis: "Benachrichtigungen sehen so aus"
+ reactedBySomeUsers: "{n} Benutzer haben eine Reaktion geschickt"
+ renotedBySomeUsers: "Renote von {n} Benutzern"
+ followedBySomeUsers: "Von {n} Benutzern gefolgt"
_types:
all: "Alle"
+ note: "Neue Notizen"
follow: "Neue Follower"
mention: "Erwähnungen"
reply: "Antworten"
@@ -1999,6 +2091,8 @@ _deck:
introduction2: "Klicke auf das + rechts um wann immer du möchtest neue Spalten hinzuzufügen."
widgetsIntroduction: "Drücke bitte \"Widgets bearbeiten\" im Spaltenmenü und füge ein Widget hinzu."
useSimpleUiForNonRootPages: "Simple Benutzeroberfläche für navigierte Seiten verwenden"
+ usedAsMinWidthWhenFlexible: "Ist \"Automatische Breitenanpassung\" aktiviert, wird hierfür die minimale Breite verwendet"
+ flexible: "Automatische Breitenanpassung"
_columns:
main: "Hauptspalte"
widgets: "Widgets"
@@ -2033,3 +2127,86 @@ _webhookSettings:
renote: "Wenn du ein Renote erhältst"
reaction: "Wenn du eine Reaktion erhältst"
mention: "Wenn du erwähnt wirst"
+_moderationLogTypes:
+ createRole: "Rolle erstellt"
+ deleteRole: "Rolle gelöscht"
+ updateRole: "Rolle aktualisiert"
+ assignRole: "Zu Rolle zugewiesen"
+ unassignRole: "Aus Rolle entfernt"
+ suspend: "Gesperrt"
+ unsuspend: "Entsperrt"
+ addCustomEmoji: "Benutzerdefiniertes Emoji hinzugefügt"
+ updateCustomEmoji: "Benutzerdefiniertes Emoji aktualisiert"
+ deleteCustomEmoji: "Benutzerdefiniertes Emoji gelöscht"
+ updateServerSettings: "Servereinstellungen aktualisiert"
+ updateUserNote: "Moderationsnotiz aktualisiert"
+ deleteDriveFile: "Datei gelöscht"
+ deleteNote: "Notiz gelöscht"
+ createGlobalAnnouncement: "Globale Ankündigung erstellt"
+ createUserAnnouncement: "Benutzerspezifische Ankündigung erstellt"
+ updateGlobalAnnouncement: "Globale Ankündigung aktualisiert"
+ updateUserAnnouncement: "Benutzerspezifische Ankündigung aktualisiert"
+ deleteGlobalAnnouncement: "Globale Ankündigung gelöscht"
+ deleteUserAnnouncement: "Benutzerspezifische Ankündigung gelöscht"
+ resetPassword: "Passwort zurückgesetzt"
+ suspendRemoteInstance: "Fremde Instanz gesperrt"
+ unsuspendRemoteInstance: "Fremde Instanz entsperrt"
+ markSensitiveDriveFile: "Datei als sensitiv markiert"
+ unmarkSensitiveDriveFile: "Datei als nicht sensitiv markiert"
+ resolveAbuseReport: "Meldung bearbeitet"
+ createInvitation: "Einladung erstellt"
+ createAd: "Werbung erstellt"
+ deleteAd: "Werbung gelöscht"
+ updateAd: "Werbung aktualisiert"
+ createAvatarDecoration: "Profilbilddekoration erstellt"
+ updateAvatarDecoration: "Profilbilddekoration aktualisiert"
+ deleteAvatarDecoration: "Profilbilddekoration gelöscht"
+_fileViewer:
+ title: "Dateiinformationen"
+ type: "Dateityp"
+ size: "Dateigröße"
+ url: "URL"
+ uploadedAt: "Hochgeladen am"
+ attachedNotes: "Zugehörige Notizen"
+ thisPageCanBeSeenFromTheAuthor: "Nur der Benutzer, der diese Datei hochgeladen hat, kann diese Seite sehen."
+_externalResourceInstaller:
+ title: "Von externer Seite installieren"
+ checkVendorBeforeInstall: "Überprüfe vor Installation die Vertrauenswürdigkeit des Vertreibers."
+ _plugin:
+ title: "Möchtest du dieses Plugin installieren?"
+ metaTitle: "Plugininformation"
+ _theme:
+ title: "Möchten du dieses Farbschema installieren?"
+ metaTitle: "Farbschemainfo"
+ _meta:
+ base: "Farbschemavorlage"
+ _vendorInfo:
+ title: "Vertreiber"
+ endpoint: "Referenzierter Endpunkt"
+ hashVerify: "Hash-Verifikation"
+ _errors:
+ _invalidParams:
+ title: "Ungültige Parameter"
+ description: "Es fehlen Informationen zum Laden der externen Ressource. Überprüfe die übergebene URL."
+ _resourceTypeNotSupported:
+ title: "Diese Ressource wird nicht unterstützt"
+ description: "Dieser Ressourcentyp wird nicht unterstützt. Bitte kontaktiere den Seitenbesitzer."
+ _failedToFetch:
+ title: "Fehler beim Abrufen der Daten"
+ fetchErrorDescription: "Während der Kommunikation mit der externen Seite ist ein Fehler aufgetreten. Kontaktiere den Seitenbesitzer, falls ein erneutes Probieren dieses Problem nicht löst."
+ parseErrorDescription: "Während dem Auslesen der externen Daten ist ein Fehler aufgetreten. Kontaktiere den Seitenbesitzer."
+ _hashUnmatched:
+ title: "Datenverifizierung fehlgeschlagen"
+ description: "Die Integritätsprüfung der geladenen Daten ist fehlgeschlagen. Aus Sicherheitsgründen kann die Installation nicht fortgesetzt werden. Kontaktiere den Seitenbesitzer."
+ _pluginParseFailed:
+ title: "AiScript-Fehler"
+ description: "Die angeforderten Daten wurden erfolgreich abgerufen, jedoch trat während des AiScript-Parsings ein Fehler auf. Kontaktiere den Autor des Plugins. Detaillierte Fehlerinformationen können über die Javascript-Konsole abgerufen werden."
+ _pluginInstallFailed:
+ title: "Das Plugin konnte nicht installiert werden"
+ description: "Während der Installation des Plugin ist ein Problem aufgetreten. Bitte versuche es erneut. Detaillierte Fehlerinformationen können über die Javascript-Konsole abgerufen werden."
+ _themeParseFailed:
+ title: "Parsing des Farbschemas fehlgeschlagen"
+ description: "Die angeforderten Daten wurden erfolgreich abgerufen, jedoch trat während des Farbschema-Parsings ein Fehler auf. Kontaktiere den Autor des Farbschemas. Detaillierte Fehlerinformationen können über die Javascript-Konsole abgerufen werden."
+ _themeInstallFailed:
+ title: "Das Farbschema konnte nicht installiert werden"
+ description: "Während der Installation des Farbschemas ist ein Problem aufgetreten. Bitte versuche es erneut. Detaillierte Fehlerinformationen können über die Javascript-Konsole abgerufen werden."
diff --git a/locales/el-GR.yml b/locales/el-GR.yml
index 41b1ea7c65..9392fd12fe 100644
--- a/locales/el-GR.yml
+++ b/locales/el-GR.yml
@@ -287,6 +287,9 @@ searchByGoogle: "Αναζήτηση"
file: "Αρχεία"
recommended: "Προτεινόμενα"
cannotUploadBecauseNoFreeSpace: "Το ανέβασμα απέτυχε λόγω ανεπαρκούς Αποθηκευτικού Χώρου"
+icon: "Εικονίδιο"
+replies: "Απάντηση"
+renotes: "Κοινοποίηση σημειώματος"
_email:
_follow:
title: "Έχετε ένα νέο ακόλουθο"
@@ -300,8 +303,6 @@ _theme:
_sfx:
note: "Σημειώματα"
notification: "Ειδοποιήσεις"
- chat: "Συνομιλία"
- chatBg: "Συνομιλία (Παρασκήνιο)"
antenna: "Αντένες"
channel: "Ειδοποιήσεις καναλιών"
_ago:
@@ -394,3 +395,5 @@ _deck:
mentions: "Επισημάνσεις"
_webhookSettings:
name: "Όνομα"
+_moderationLogTypes:
+ suspend: "Αποβολή"
diff --git a/locales/en-US.yml b/locales/en-US.yml
index bf3f1ceba3..854bd854f0 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -45,9 +45,10 @@ pin: "Pin to profile"
unpin: "Unpin from profile"
copyContent: "Copy contents"
copyLink: "Copy link"
+copyLinkRenote: "Copy renote link"
delete: "Delete"
deleteAndEdit: "Delete and edit"
-deleteAndEditConfirm: "Are you sure you want to delete this note and edit it? You will lose all reactions, renotes and replies to it."
+deleteAndEditConfirm: "Are you sure you want to redraft this note? This means you will lose all reactions, renotes, and replies to it."
addToList: "Add to list"
addToAntenna: "Add to antenna"
sendMessage: "Send a message"
@@ -74,7 +75,7 @@ import: "Import"
export: "Export"
files: "Files"
download: "Download"
-driveFileDeleteConfirm: "Are you sure you want to delete \"{name}\"? It will also vanish from all contents that use it."
+driveFileDeleteConfirm: "Do you want to remove the file \"{name}\"? Some content using this file will also be removed."
unfollowConfirm: "Are you sure you want to unfollow {name}?"
exportRequested: "You've requested an export. This may take a while. It will be added to your Drive once completed."
importRequested: "You've requested an import. This may take a while."
@@ -105,7 +106,7 @@ unfollow: "Unfollow"
followRequestPending: "Follow request pending"
enterEmoji: "Enter an emoji"
renote: "Renote"
-unrenote: "Take back renote"
+unrenote: "Remove renote"
renoted: "Renoted."
cantRenote: "This post can't be renoted."
cantReRenote: "A renote can't be renoted."
@@ -156,6 +157,7 @@ addEmoji: "Add an emoji"
settingGuide: "Recommended settings"
cacheRemoteFiles: "Cache remote files"
cacheRemoteFilesDescription: "When this setting is disabled, remote files are loaded directly from the remote instance. Disabling this will decrease storage usage, but increase traffic, as thumbnails will not be generated."
+youCanCleanRemoteFilesCache: "You can clear the cache by clicking the 🗑️ button in the file management view."
cacheRemoteSensitiveFiles: "Cache sensitive remote files"
cacheRemoteSensitiveFilesDescription: "When this setting is disabled, sensitive remote files are loaded directly from the remote instance without caching."
flagAsBot: "Mark this account as a bot"
@@ -193,6 +195,7 @@ perHour: "Per Hour"
perDay: "Per Day"
stopActivityDelivery: "Stop sending activities"
blockThisInstance: "Block this instance"
+silenceThisInstance: "Silence this instance"
operations: "Operations"
software: "Software"
version: "Version"
@@ -211,7 +214,9 @@ clearQueueConfirmText: "Any undelivered notes remaining in the queue will not be
clearCachedFiles: "Clear cache"
clearCachedFilesConfirm: "Are you sure that you want to delete all cached remote files?"
blockedInstances: "Blocked Instances"
-blockedInstancesDescription: "List the hostnames of the instances that you want to block separated by linebreaks. Listed instances will no longer be able to communicate with this instance."
+blockedInstancesDescription: "List the hostnames of the instances you want to block separated by linebreaks. Listed instances will no longer be able to communicate with this instance."
+silencedInstances: "Silenced instances"
+silencedInstancesDescription: "List the hostnames of the instances that you want to silence. All accounts of the listed instances will be treated as silenced, can only make follow requests, and cannot mention local accounts if not followed. This will not affect blocked instances."
muteAndBlock: "Mutes and Blocks"
mutedUsers: "Muted users"
blockedUsers: "Blocked users"
@@ -356,7 +361,6 @@ invite: "Invite"
driveCapacityPerLocalAccount: "Drive capacity per local user"
driveCapacityPerRemoteAccount: "Drive capacity per remote user"
inMb: "In megabytes"
-iconUrl: "Icon URL"
bannerUrl: "Banner image URL"
backgroundImageUrl: "Background image URL"
basicInfo: "Basic info"
@@ -412,11 +416,14 @@ aboutMisskey: "About Misskey"
administrator: "Administrator"
token: "Token"
2fa: "Two-factor authentication"
+setupOf2fa: "Setup two-factor authentification"
totp: "Authenticator App"
totpDescription: "Use an authenticator app to enter one-time passwords"
-useSecurityKey: "Please use the security key or passkey according to the browser or device instructions."
moderator: "Moderator"
moderation: "Moderation"
+moderationNote: "Moderation note"
+addModerationNote: "Add moderation note"
+moderationLogs: "Moderation logs"
nUsersMentioned: "Mentioned by {n} users"
securityKeyAndPasskey: "Security- and passkeys"
securityKey: "Security key"
@@ -529,6 +536,7 @@ serverLogs: "Server logs"
deleteAll: "Delete all"
showFixedPostForm: "Display the posting form at the top of the timeline"
showFixedPostFormInChannel: "Display the posting form at the top of the timeline (Channels)"
+withRepliesByDefaultForNewlyFollowed: "Include replies by newly followed users in the timeline by default"
newNoteRecived: "There are new notes"
sounds: "Sounds"
sound: "Sounds"
@@ -586,12 +594,12 @@ serviceworkerInfo: "Must be enabled for push notifications."
deletedNote: "Deleted note"
invisibleNote: "Invisible note"
enableInfiniteScroll: "Automatically load more"
-visibility: "Visiblility"
+visibility: "Visibility"
poll: "Poll"
useCw: "Hide content"
enablePlayer: "Open video player"
disablePlayer: "Close video player"
-expandTweet: "Expand tweet"
+expandTweet: "Expand post"
themeEditor: "Theme editor"
description: "Description"
describeFile: "Add caption"
@@ -660,6 +668,7 @@ behavior: "Behavior"
sample: "Sample"
abuseReports: "Reports"
reportAbuse: "Report"
+reportAbuseRenote: "Report renote"
reportAbuseOf: "Report {name}"
fillAbuseReportDescription: "Please fill in details regarding this report. If it is about a specific note, please include its URL."
abuseReported: "Your report has been sent. Thank you very much."
@@ -687,6 +696,7 @@ createNewClip: "Create new clip"
unclip: "Unclip"
confirmToUnclipAlreadyClippedNote: "This note is already part of the \"{name}\" clip. Do you want to remove it from this clip instead?"
public: "Public"
+private: "Private"
i18nInfo: "Misskey is being translated into various languages by volunteers. You can help at {link}."
manageAccessTokens: "Manage access tokens"
accountInfo: "Account Info"
@@ -711,6 +721,7 @@ lockedAccountInfo: "Unless you set your note visiblity to \"Followers only\", yo
alwaysMarkSensitive: "Mark as sensitive by default"
loadRawImages: "Load original images instead of showing thumbnails"
disableShowingAnimatedImages: "Don't play animated images"
+highlightSensitiveMedia: "Highlight sensitive media"
verificationEmailSent: "A verification email has been sent. Please follow the included link to complete verification."
notSet: "Not set"
emailVerified: "Email has been verified"
@@ -793,7 +804,7 @@ active: "Active"
offline: "Offline"
notRecommended: "Not recommended"
botProtection: "Bot Protection"
-instanceBlocking: "Blocked Instances"
+instanceBlocking: "Blocked/Silenced Instances"
selectAccount: "Select account"
switchAccount: "Switch account"
enabled: "Enabled"
@@ -914,7 +925,7 @@ typeToConfirm: "Please enter {x} to confirm"
deleteAccount: "Delete account"
document: "Documentation"
numberOfPageCache: "Number of cached pages"
-numberOfPageCacheDescription: "Increasing this number will improve convenience for users but cause more server load as well as more memory to be used."
+numberOfPageCacheDescription: "Increasing this number will improve convenience for but cause more load as more memory usage on the user's device."
logoutConfirm: "Really log out?"
lastActiveDate: "Last used at"
statusbar: "Status bar"
@@ -974,6 +985,7 @@ assign: "Assign"
unassign: "Unassign"
color: "Color"
manageCustomEmojis: "Manage Custom Emojis"
+manageAvatarDecorations: "Manage avatar decorations"
youCannotCreateAnymore: "You've hit the creation limit."
cannotPerformTemporary: "Temporarily unavailable"
cannotPerformTemporaryDescription: "This action cannot be performed temporarily due to exceeding the execution limit. Please wait for a while and then try again."
@@ -1025,7 +1037,7 @@ retryAllQueuesConfirmText: "This will temporarily increase the server load."
enableChartsForRemoteUser: "Generate remote user data charts"
enableChartsForFederatedInstances: "Generate remote instance data charts"
showClipButtonInNoteFooter: "Add \"Clip\" to note action menu"
-largeNoteReactions: "Enlargen displayed reactions"
+reactionsDisplaySize: "Reaction display size"
noteIdOrUrl: "Note ID or URL"
video: "Video"
videos: "Videos"
@@ -1102,10 +1114,70 @@ expired: "Expired"
doYouAgree: "Agree?"
beSureToReadThisAsItIsImportant: "Please read this important information."
iHaveReadXCarefullyAndAgree: "I have read the text \"{x}\" and agree."
-releaseToRefresh: "Release to reload"
-refreshing: "Reloading"
-pullDownToRefresh: "Pull down to reload"
-disableStreamingTimeline: "Disable realtime update on timeline"
+dialog: "Dialog"
+icon: "Icon"
+forYou: "For you"
+currentAnnouncements: "Current announcements"
+pastAnnouncements: "Past announcements"
+youHaveUnreadAnnouncements: "There are unread announcements."
+useSecurityKey: "Please follow your browser's or device's instructions to use your security- or passkey."
+replies: "Reply"
+renotes: "Renotes"
+loadReplies: "Show replies"
+loadConversation: "Show conversation"
+pinnedList: "Pinned list"
+keepScreenOn: "Keep screen on"
+verifiedLink: "Link ownership has been verified"
+notifyNotes: "Notify about new notes"
+unnotifyNotes: "Stop notifying about new notes"
+authentication: "Authentication"
+authenticationRequiredToContinue: "Please authenticate to continue"
+dateAndTime: "Timestamp"
+showRenotes: "Show renotes"
+edited: "Edited"
+notificationRecieveConfig: "Notification Settings"
+mutualFollow: "Mutual follow"
+fileAttachedOnly: "Only notes with files"
+showRepliesToOthersInTimeline: "Show replies to others in timeline"
+hideRepliesToOthersInTimeline: "Hide replies to others from timeline"
+showRepliesToOthersInTimelineAll: "Show replies to others from everyone you follow in timeline"
+hideRepliesToOthersInTimelineAll: "Hide replies to others from everyone you follow in timeline"
+confirmShowRepliesAll: "This operation is irreversible. Would you really like to show replies to others from everyone you follow in your timeline?"
+confirmHideRepliesAll: "This operation is irreversible. Would you really like to hide replies to others from everyone you follow in your timeline?"
+externalServices: "External Services"
+impressum: "Impressum"
+impressumUrl: "Impressum URL"
+impressumDescription: "In some countries, like germany, the inclusion of operator contact information (an Impressum) is legally required for commercial websites."
+privacyPolicy: "Privacy Policy"
+privacyPolicyUrl: "Privacy Policy URL"
+tosAndPrivacyPolicy: "Terms of Service and Privacy Policy"
+avatarDecorations: "Avatar decorations"
+attach: "Attach"
+detach: "Remove"
+angle: "Angle"
+flip: "Flip"
+showAvatarDecorations: "Show avatar decorations"
+releaseToRefresh: "Release to refresh"
+refreshing: "Refreshing..."
+pullDownToRefresh: "Pull down to refresh"
+disableStreamingTimeline: "Disable real-time timeline updates"
+useGroupedNotifications: "Display grouped notifications"
+signupPendingError: "There was a problem verifying the email address. The link may have expired."
+cwNotationRequired: "If \"Hide content\" is enabled, a description must be provided."
+doReaction: "Add reaction"
+_announcement:
+ forExistingUsers: "Existing users only"
+ forExistingUsersDescription: "This announcement will only be shown to users existing at the point of publishment if enabled. If disabled, those newly signing up after it has been posted will also see it."
+ needConfirmationToRead: "Require separate read confirmation"
+ needConfirmationToReadDescription: "A separate prompt to confirm marking this announcement as read will be displayed if enabled. This announcement will also be excluded from any \"Mark all as read\" functionality."
+ end: "Archive announcement"
+ tooManyActiveAnnouncementDescription: "Having too many active announcements may worsen the user experience. Please consider archiving announcements that have become obsolete."
+ readConfirmTitle: "Mark as read?"
+ readConfirmText: "This will mark the contents of \"{title}\" as read."
+ shouldNotBeUsedToPresentPermanentInfo: "As it may significantly impact the user experience for new users, it is recommended to use notifications in the flow information rather than stock information."
+ dialogAnnouncementUxWarn: "Having two or more dialog-style notifications simultaneously can significantly impact the user experience, so please use them carefully."
+ silence: "No notification"
+ silenceDescription: "Turning this on will skip the notification of this announcement and the user won't need to read it."
_initialAccountSetting:
accountCreated: "Your account was successfully created!"
letsStartAccountSetup: "For starters, let's set up your profile."
@@ -1118,11 +1190,89 @@ _initialAccountSetting:
pushNotificationDescription: "Enabling push notifications will allow you to receive notifications from {name} directly on your device."
initialAccountSettingCompleted: "Profile setup complete!"
haveFun: "Enjoy {name}!"
- ifYouNeedLearnMore: "If you'd like to learn more about how to use {name} (Misskey), please visit {link}."
+ youCanContinueTutorial: "You can proceed to a tutorial on how to use {name} (Misskey) or you can exit the setup here and start using it immediately."
+ startTutorial: "Start Tutorial"
skipAreYouSure: "Really skip profile setup?"
laterAreYouSure: "Really do profile setup later?"
+_initialTutorial:
+ launchTutorial: "Start Tutorial"
+ title: "Tutorial"
+ wellDone: "Well done!"
+ skipAreYouSure: "Quit Tutorial?"
+ _landing:
+ title: "Welcome to the Tutorial"
+ description: "Here, you can learn the basics of using Misskey and its features."
+ _note:
+ title: "What is a Note?"
+ description: "Posts on Misskey are called 'Notes.' Notes are arranged chronologically on the timeline and are updated in real-time."
+ reply: "Click on this button to reply to a message. It's also possible to reply to replies, continuing the conversation like a thread."
+ renote: "You can share that note to your own timeline. You can also quote them with your comments."
+ reaction: "You can add reactions to the Note. More details will be explained on the next page."
+ menu: "You can view Note details, copy links, and perform various other actions."
+ _reaction:
+ title: "What are Reactions?"
+ description: "Notes can be reacted to with various emojis. Reactions allow you to express nuances that may not be conveyed with just a 'like.'"
+ letsTryReacting: "Reactions can be added by clicking the '+' button on the note. Try reacting to this sample note!"
+ reactToContinue: "Add a reaction to proceed."
+ reactNotification: "You'll receive real-time notifications when someone reacts to your note."
+ reactDone: "You can undo a reaction by pressing the '-' button."
+ _timeline:
+ title: "The Concept of Timelines"
+ description1: "Misskey provides multiple timelines based on usage (some may not be available depending on the server's policies)."
+ home: "You can view notes from accounts you follow."
+ local: "You can view notes from all users on this server."
+ social: "Notes from the Home and Local timelines will be displayed."
+ global: "You can view notes from all connected servers."
+ description2: "You can switch between timelines at the top of the screen at any time."
+ description3: "Additionally, there are list timelines and channel timelines. For more details, please refer to {link}."
+ _postNote:
+ title: "Note Posting Settings"
+ description1: "When posting a note on Misskey, various options are available. The posting form looks like this."
+ _visibility:
+ description: "You can limit who can view your note."
+ public: "Your note will be visible for all users."
+ home: "Public only on the Home timeline. People visiting your profile, via followers, and through renotes can see it."
+ followers: "Visible to followers only. Only followers can see it and no one else, and it cannot be renoted by others."
+ direct: "Visible only to specified users, and the recipient will be notified. It can be used as an alternative to direct messaging."
+ doNotSendConfidencialOnDirect1: "Be careful when sending sensitive information!"
+ doNotSendConfidencialOnDirect2: "Administrators of the server can see what you write. Be careful with sensitive information when sending direct notes to users on untrusted servers."
+ localOnly: "Posting with this flag will not federate the note to other servers. Users on other servers will not be able to view these notes directly, regardless of the display settings above."
+ _cw:
+ title: "Content Warning"
+ description: "Instead of the body, the content written in 'comments' field will be displayed. Pressing \"read more\" will reveal the body."
+ _exampleNote:
+ cw: "This will surely make you hungry!"
+ note: "Just had a chocolate-glazed donut 🍩😋"
+ useCases: "This is used when following the server guidelines for necessary notes or for self-restriction of spoiler or sensitive text."
+ _howToMakeAttachmentsSensitive:
+ title: "How to Mark Attachments as Sensitive?"
+ description: "For attachments that are required by server guidelines or that should not be left intact, add a \"sensitive\" flag."
+ tryThisFile: "Try marking the image attached in this form as sensitive!"
+ _exampleNote:
+ note: "Oops, messed up opening the natto lid..."
+ method: "To mark an attachment as sensitive, click the file thumbnail, open the menu, and click \"Mark as Sensitive.\""
+ sensitiveSucceeded: "When attaching files, please set sensitivities in accordance with the server guidelines."
+ doItToContinue: "Mark the attachment file as sensitive to proceed."
+ _done:
+ title: "The tutorial is complete! 🎉"
+ description: "The functions introduced here are just a small part. For a more detailed understanding of using Misskey, please refer to {link}."
+_timelineDescription:
+ home: "In the Home timeline, you can see notes from accounts you follow."
+ local: "In the Local timeline, you can see notes from all users on this server."
+ social: "The Social timeline displays notes from both the Home and Local timelines."
+ global: "In the Global timeline, you can see notes from all connected servers."
_serverRules:
description: "A set of rules to be displayed before registration. Setting a summary of the Terms of Service is recommended."
+_serverSettings:
+ iconUrl: "Icon URL"
+ appIconDescription: "Specifies the icon to use when {host} is displayed as an app."
+ appIconUsageExample: "E.g. As PWA, or when displayed as a home screen bookmark on a phone"
+ appIconStyleRecommendation: "As the icon may be cropped to a square or circle, an icon with colored margin around the content is recommended."
+ appIconResolutionMustBe: "The minimum resolution is {resolution}."
+ manifestJsonOverride: "manifest.json Override"
+ shortName: "Short name"
+ shortNameDescription: "A shorthand for the instance's name that can be displayed if the full official name is long."
+ fanoutTimelineDescription: "Greatly increases performance of timeline retrieval and reduces load on the database when enabled. In exchange, memory usage of Redis will increase. Consider disabling this in case of low server memory or server instability."
_accountMigration:
moveFrom: "Migrate another account to this one"
moveFromSub: "Create alias to another account"
@@ -1377,6 +1527,12 @@ _achievements:
title: "Brain Diver"
description: "Post the link to Brain Diver"
flavor: "Misskey-Misskey La-Tu-Ma"
+ _smashTestNotificationButton:
+ title: "Test overflow"
+ description: "Trigger the notification test repeatedly within an extremely short time"
+ _tutorialCompleted:
+ title: "Misskey Elementary Course Diploma"
+ description: "Tutorial completed"
_role:
new: "New role"
edit: "Edit role"
@@ -1423,6 +1579,7 @@ _role:
inviteLimitCycle: "Invite limit cooldown"
inviteExpirationTime: "Invite expiration interval"
canManageCustomEmojis: "Can manage custom emojis"
+ canManageAvatarDecorations: "Manage avatar decorations"
driveCapacity: "Drive capacity"
alwaysMarkNsfw: "Always mark files as NSFW"
pinMax: "Maximum number of pinned notes"
@@ -1437,6 +1594,7 @@ _role:
descriptionOfRateLimitFactor: "Lower rate limits are less restrictive, higher ones more restrictive. "
canHideAds: "Can hide ads"
canSearchNotes: "Usage of note search"
+ canUseTranslator: "Translator usage"
_condition:
isLocal: "Local user"
isRemote: "Remote user"
@@ -1485,6 +1643,10 @@ _ad:
reduceFrequencyOfThisAd: "Show this ad less"
hide: "Hide"
timezoneinfo: "The day of the week is determined from the server's timezone."
+ adsSettings: "Ad settings"
+ notesPerOneAd: "Real-time update ad placement interval (Notes per ad)"
+ setZeroToDisable: "Set this value to 0 to disable real-time update ads"
+ adsTooClose: "The current ad interval may significantly worsen the user experience due to being too low."
_forgotPassword:
enterEmail: "Enter the email address you used to register. A link with which you can reset your password will then be sent to it."
ifNoEmail: "If you did not use an email during registration, please contact the instance administrator instead."
@@ -1503,6 +1665,7 @@ _plugin:
install: "Install plugins"
installWarn: "Please do not install untrustworthy plugins."
manage: "Manage plugins"
+ viewSource: "View source"
_preferencesBackups:
list: "Created backups"
saveNew: "Save new backup"
@@ -1536,6 +1699,7 @@ _aboutMisskey:
donate: "Donate to Misskey"
morePatrons: "We also appreciate the support of many other helpers not listed here. Thank you! 🥰"
patrons: "Patrons"
+ projectMembers: "Project members"
_displayOfSensitiveMedia:
respect: "Hide media marked as sensitive"
ignore: "Display media marked as sensitive"
@@ -1560,6 +1724,7 @@ _channel:
notesCount: "{n} Notes"
nameAndDescription: "Name and description"
nameOnly: "Name only"
+ allowRenoteToExternal: "Allow renote and quote outside the channel"
_menuDisplay:
sideFull: "Side"
sideIcon: "Side (Icons)"
@@ -1569,11 +1734,6 @@ _wordMute:
muteWords: "Muted words"
muteWordsDescription: "Separate with spaces for an AND condition or with line breaks for an OR condition."
muteWordsDescription2: "Surround keywords with slashes to use regular expressions."
- softDescription: "Hide notes that fulfil the set conditions from the timeline."
- hardDescription: "Prevents notes fulfilling the set conditions from being added to the timeline. In addition, these notes will not be added to the timeline even if the conditions are changed."
- soft: "Soft"
- hard: "Hard"
- mutedNotes: "Muted notes"
_instanceMute:
instanceMuteDescription: "This will mute any notes/renotes from the listed instances, including those of users replying to a user from a muted instance."
instanceMuteDescription2: "Separate with newlines"
@@ -1637,9 +1797,6 @@ _theme:
infoFg: "Information text"
infoWarnBg: "Warning background"
infoWarnFg: "Warning text"
- cwBg: "CW button background"
- cwFg: "CW button text"
- cwHoverBg: "CW button background (Hover)"
toastBg: "Notification background"
toastFg: "Notification text"
buttonBg: "Button background"
@@ -1657,8 +1814,6 @@ _sfx:
note: "New note"
noteMy: "Own note"
notification: "Notifications"
- chat: "Chat"
- chatBg: "Chat (Background)"
antenna: "Antennas"
channel: "Channel notifications"
_ago:
@@ -1677,29 +1832,17 @@ _time:
minute: "Minute(s)"
hour: "Hour(s)"
day: "Day(s)"
-_timelineTutorial:
- title: "How to use Misskey"
- step1_1: "This is the \"timeline\". All \"notes\" submitted on {name} will be chronologically displayed here."
- step1_2: "There are a few different timelines. For example, the \"Home timeline\" will contain notes of users you follow, and the \"Local timeline\" will contain notes from all users of {name}."
- step2_1: "Let's try posting a note next. You can do so by pressing the button with a pencil icon."
- step2_2: "How about writing a self-introduction, or just \"Hello {name}!\" if you don't feel like it?"
- step3_1: "Finished posting your first note?"
- step3_2: "Your first note should now be displayed on your timeline."
- step4_1: "You can also attach \"Reactions\" to notes."
- step4_2: "To attach a reaction, press the \"+\" mark on a note and choose an emoji you'd like to react with."
_2fa:
alreadyRegistered: "You have already registered a 2-factor authentication device."
registerTOTP: "Register authenticator app"
- passwordToTOTP: "Enter your password"
step1: "First, install an authentication app (such as {a} or {b}) on your device."
step2: "Then, scan the QR code displayed on this screen."
step2Click: "Clicking on this QR code will allow you to register 2FA to your security key or phone authenticator app."
- step2Url: "You can also enter this URL if you're using a desktop program:"
+ step2Uri: "Enter the following URI if you are using a desktop program"
step3Title: "Enter an authentication code"
- step3: "Enter the token provided by your app to finish setup."
- step4: "From now on, any future login attempts will ask for such a login token.\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}"
- twoFactorBackupSecretWarning: "You have used some of your backup codes. If your authentication app is no longer available, please reconfigure your authentication app as soon as possible."
- twoFactorBackupSecretExhausted: "You have exhausted all your backup codes. If your authentication app is no longer available, you will not be able to access your account anymore. please reconfigure your authentication app as soon as possible."
+ step3: "Enter the authentication code (token) provided by your app to finish setup."
+ setupCompleted: "Setup complete"
+ step4: "From now on, any future login attempts will ask for such a login token."
securityKeyNotSupported: "Your browser does not support security keys."
registerTOTPBeforeKey: "Please set up an authenticator app to register a security or pass key."
securityKeyInfo: "Besides fingerprint or PIN authentication, you can also setup authentication via hardware security keys that support FIDO2 to further secure your account."
@@ -1713,6 +1856,11 @@ _2fa:
renewTOTPConfirm: "This will cause verification codes from your previous app and backup codes to stop working"
renewTOTPOk: "Reconfigure"
renewTOTPCancel: "Cancel"
+ checkBackupCodesBeforeCloseThisWizard: "Before you close this window, please note the following backup codes."
+ backupCodes: "Backup codes"
+ backupCodesDescription: "You can use these codes to gain access to your account in case of becoming unable to use your two-factor authentificator app. Each can only be used once. Please keep them in a safe place."
+ backupCodeUsedWarning: "A backup code has been used. Please reconfigure two-factor authentification as soon as possible if you are no longer able to use it."
+ backupCodesExhaustedWarning: "All backup codes have been used. Should you lose access to your two-factor authentification app, you will be unable to access this account. Please reconfigure two-factor authentification."
_permissions:
"read:account": "View your account information"
"write:account": "Edit your account information"
@@ -1734,10 +1882,10 @@ _permissions:
"read:reactions": "View your reactions"
"write:reactions": "Edit your reactions"
"write:votes": "Vote on a poll"
- "read:pages": "View your pages"
- "write:pages": "Edit or delete your pages"
- "read:page-likes": "View your likes on pages"
- "write:page-likes": "Edit your likes on pages"
+ "read:pages": "View your Pages"
+ "write:pages": "Edit or delete your Pages"
+ "read:page-likes": "View list of liked Pages"
+ "write:page-likes": "Edit list of liked Pages"
"read:user-groups": "View your user groups"
"write:user-groups": "Edit or delete your user groups"
"read:channels": "View your channels"
@@ -1746,6 +1894,10 @@ _permissions:
"write:gallery": "Edit your gallery"
"read:gallery-likes": "View your list of liked gallery posts"
"write:gallery-likes": "Edit your list of liked gallery posts"
+ "read:flash": "View Play"
+ "write:flash": "Edit Plays"
+ "read:flash-likes": "View list of liked Plays"
+ "write:flash-likes": "Edit list of liked Plays"
_auth:
shareAccessTitle: "Granting application permissions"
shareAccess: "Would you like to authorize \"{name}\" to access this account?"
@@ -1761,6 +1913,7 @@ _antennaSources:
homeTimeline: "Notes from followed users"
users: "Notes from specific users"
userList: "Notes from a specified list of users"
+ userBlacklist: "All notes except for those of one or more specified users"
_weekday:
sunday: "Sunday"
monday: "Monday"
@@ -1860,6 +2013,7 @@ _profile:
metadataContent: "Content"
changeAvatar: "Change avatar"
changeBanner: "Change banner"
+ verifiedLinkDescription: "By entering an URL that contains a link to your profile here, an ownership verification icon can be displayed next to the field."
_exportOrImport:
allNotes: "All notes"
favoritedNotes: "Favorite notes"
@@ -1869,6 +2023,7 @@ _exportOrImport:
userLists: "User lists"
excludeMutingUsers: "Exclude muted users"
excludeInactiveUsers: "Exclude inactive users"
+ withReplies: "Include replies from imported users in the timeline"
_charts:
federation: "Federation"
apRequest: "Requests"
@@ -1979,11 +2134,20 @@ _notification:
youReceivedFollowRequest: "You've received a follow request"
yourFollowRequestAccepted: "Your follow request was accepted"
pollEnded: "Poll results have become available"
+ newNote: "New note"
unreadAntennaNote: "Antenna {name}"
emptyPushNotificationMessage: "Push notifications have been updated"
achievementEarned: "Achievement unlocked"
+ testNotification: "Test notification"
+ checkNotificationBehavior: "Check notification appearance"
+ sendTestNotification: "Send test notification"
+ notificationWillBeDisplayedLikeThis: "Notifications look like this"
+ reactedBySomeUsers: "{n} users reacted"
+ renotedBySomeUsers: "Renote from {n} users"
+ followedBySomeUsers: "Followed by {n} users"
_types:
all: "All"
+ note: "New notes"
follow: "New followers"
mention: "Mentions"
reply: "Replies"
@@ -2016,7 +2180,9 @@ _deck:
introduction: "Create the perfect interface for you by arranging columns freely!"
introduction2: "Click on the + on the right of the screen to add new colums whenever you want."
widgetsIntroduction: "Please select \"Edit widgets\" in the column menu and add a widget."
- useSimpleUiForNonRootPages: "Use simplified UI to navigated pages"
+ useSimpleUiForNonRootPages: "Use simple UI for navigated pages"
+ usedAsMinWidthWhenFlexible: "Minimum width will be used for this when the \"Auto-adjust width\" option is enabled"
+ flexible: "Auto-adjust width"
_columns:
main: "Main"
widgets: "Widgets"
@@ -2051,3 +2217,86 @@ _webhookSettings:
renote: "When renoted"
reaction: "When receiving a reaction"
mention: "When being mentioned"
+_moderationLogTypes:
+ createRole: "Role created"
+ deleteRole: "Role deleted"
+ updateRole: "Role updated"
+ assignRole: "Assigned to role"
+ unassignRole: "Removed from role"
+ suspend: "Suspended"
+ unsuspend: "Unsuspended"
+ addCustomEmoji: "Custom emoji added"
+ updateCustomEmoji: "Custom emoji updated"
+ deleteCustomEmoji: "Custom emoji deleted"
+ updateServerSettings: "Server settings updated"
+ updateUserNote: "Moderation note updated"
+ deleteDriveFile: "File deleted"
+ deleteNote: "Note deleted"
+ createGlobalAnnouncement: "Global announcement created"
+ createUserAnnouncement: "User announcement created"
+ updateGlobalAnnouncement: "Global announcement updated"
+ updateUserAnnouncement: "User announcement updated"
+ deleteGlobalAnnouncement: "Global announcement deleted"
+ deleteUserAnnouncement: "User announcement deleted"
+ resetPassword: "Password reset"
+ suspendRemoteInstance: "Remote instance suspended"
+ unsuspendRemoteInstance: "Remote instance unsuspended"
+ markSensitiveDriveFile: "File marked as sensitive"
+ unmarkSensitiveDriveFile: "File unmarked as sensitive"
+ resolveAbuseReport: "Report resolved"
+ createInvitation: "Invite generated"
+ createAd: "Ad created"
+ deleteAd: "Ad deleted"
+ updateAd: "Ad updated"
+ createAvatarDecoration: "Avatar decoration created"
+ updateAvatarDecoration: "Avatar decoration updated"
+ deleteAvatarDecoration: "Avatar decoration deleted"
+_fileViewer:
+ title: "File details"
+ type: "File type"
+ size: "Filesize"
+ url: "URL"
+ uploadedAt: "Uploaded at"
+ attachedNotes: "Attached notes"
+ thisPageCanBeSeenFromTheAuthor: "This page can only be seen by the user who uploaded this file."
+_externalResourceInstaller:
+ title: "Install from external site"
+ checkVendorBeforeInstall: "Make sure the distributor of this resource is trustworthy before installation."
+ _plugin:
+ title: "Do you want to install this plugin?"
+ metaTitle: "Plugin information"
+ _theme:
+ title: "Do you want to install this theme?"
+ metaTitle: "Theme information"
+ _meta:
+ base: "Base color scheme"
+ _vendorInfo:
+ title: "Distributor information"
+ endpoint: "Referenced endpoint"
+ hashVerify: "Hash verification"
+ _errors:
+ _invalidParams:
+ title: "Invalid parameters"
+ description: "There is not enough information to load data from an external site. Please confirm the entered URL."
+ _resourceTypeNotSupported:
+ title: "This external resource is not supported"
+ description: "The type of this external resource is not supported. Please contact the site administrator."
+ _failedToFetch:
+ title: "Failed to fetch data"
+ fetchErrorDescription: "An error occurred communicating with the external site. If trying again does not fix this issue, please contact the site administrator."
+ parseErrorDescription: "An error occurred processing the data loaded from the external site. Please contact the site administrator."
+ _hashUnmatched:
+ title: "Data verification failed"
+ description: "An error occurred verifying the integrity of the fetched data. As a security measure, installation cannot continue. Please contact the site administrator."
+ _pluginParseFailed:
+ title: "AiScript Error"
+ description: "The requested data was fetched successfully, but an error occurred during AiScript parsing. Please contact the plugin author. Error details can be viewed in the Javascript console."
+ _pluginInstallFailed:
+ title: "Plugin installation failed"
+ description: "A problem occurred during plugin installation. Please try again. Error details can be viewed in the Javascript console."
+ _themeParseFailed:
+ title: "Theme parsing failed"
+ description: "The requested data was fetched successfully, but an error occurred during theme parsing. Please contact the theme author. Error details can be viewed in the Javascript console."
+ _themeInstallFailed:
+ title: "Failed to install theme"
+ description: "A problem occurred during theme installation. Please try again. Error details can be viewed in the Javascript console."
diff --git a/locales/es-ES.yml b/locales/es-ES.yml
index 83c8732952..df611c2350 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -8,10 +8,10 @@ search: "Buscar"
notifications: "Notificaciones"
username: "Nombre de usuario"
password: "Contraseña"
-forgotPassword: "Olvidé mi Contraseña"
+forgotPassword: "Olvidé mi contraseña"
fetchingAsApObject: "Buscando en el fediverso"
ok: "OK"
-gotIt: "¡Lo tengo!"
+gotIt: "Entendido"
cancel: "Cancelar"
noThankYou: "No gracias"
enterUsername: "Introduce el nombre de usuario"
@@ -20,8 +20,8 @@ noNotes: "No hay notas"
noNotifications: "No hay notificaciones"
instance: "Instancia"
settings: "Configuración"
-notificationSettings: "Configurar las notificaciones"
-basicSettings: "Configuración Básica"
+notificationSettings: "Ajustes de notificaciones"
+basicSettings: "Configuración básica"
otherSettings: "Configuración avanzada"
openInWindow: "Abrir en una ventana"
profile: "Perfil"
@@ -45,6 +45,7 @@ pin: "Fijar al perfil"
unpin: "Desfijar"
copyContent: "Copiar contenido"
copyLink: "Copiar enlace"
+copyLinkRenote: "Copiar enlace de renota"
delete: "Borrar"
deleteAndEdit: "Borrar y editar"
deleteAndEditConfirm: "¿Estás seguro de que quieres borrar esta nota y editarla? Perderás todas las reacciones, renotas y respuestas."
@@ -55,8 +56,8 @@ copyRSS: "Copiar RSS"
copyUsername: "Copiar nombre de usuario"
copyUserId: "Copiar ID del usuario"
copyNoteId: "Copiar ID de la nota"
-copyFileId: "Copiar un archivo ID"
-copyFolderId: "Copiar carpeta ID"
+copyFileId: "Copiar ID del archivo"
+copyFolderId: "Copiar ID de carpeta"
copyProfileUrl: "Copiar la URL del perfil"
searchUser: "Buscar un usuario"
reply: "Responder"
@@ -156,6 +157,7 @@ addEmoji: "Agregar emoji"
settingGuide: "Configuración sugerida"
cacheRemoteFiles: "Mantener en cache los archivos remotos"
cacheRemoteFilesDescription: "Si desactiva esta configuración, Los archivos remotos se cargarán desde el link directo sin usar la caché. Con eso se puede ahorrar almacenamiento del servidor, pero eso aumentará el tráfico al no crear miniaturas."
+youCanCleanRemoteFilesCache: "Puedes vaciar la caché pulsando en el botón 🗑️ en el administrador de archivos."
cacheRemoteSensitiveFiles: "Cachear archivos remotos sensibles"
cacheRemoteSensitiveFilesDescription: "Cuando esta opción está desactivada, los archivos remotos sensibles son cargador directamente de la instancia origen sin ser cacheados."
flagAsBot: "Esta cuenta es un bot"
@@ -193,6 +195,7 @@ perHour: "por hora"
perDay: "por día"
stopActivityDelivery: "Dejar de enviar actividades"
blockThisInstance: "Bloquear instancia"
+silenceThisInstance: "Silenciar esta instancia"
operations: "Operaciones"
software: "Software"
version: "Versión"
@@ -212,6 +215,8 @@ clearCachedFiles: "Limpiar caché"
clearCachedFilesConfirm: "¿Desea borrar todos los archivos remotos cacheados?"
blockedInstances: "Instancias bloqueadas"
blockedInstancesDescription: "Seleccione los hosts de las instancias que desea bloquear, separadas por una linea nueva. Las instancias bloqueadas no podrán comunicarse con esta instancia."
+silencedInstances: "Instancias silenciadas"
+silencedInstancesDescription: "Listar los hostname de las instancias que quieres silenciar. Todas las cuentas de las instancias listadas serán tratadas como silenciadas, solo podrán hacer peticiones de seguimiento, y no podrán mencionar cuentas locales si no las siguen. Esto no afecta a las instancias bloqueadas."
muteAndBlock: "Silenciar y bloquear"
mutedUsers: "Usuarios silenciados"
blockedUsers: "Usuarios bloqueados"
@@ -354,7 +359,6 @@ invite: "Invitar"
driveCapacityPerLocalAccount: "Capacidad del drive por usuario local"
driveCapacityPerRemoteAccount: "Capacidad del drive por usuario remoto"
inMb: "En megabytes"
-iconUrl: "URL de la imagen del avatar"
bannerUrl: "URL de la imagen del banner"
backgroundImageUrl: "URL de la imagen de fondo"
basicInfo: "Información básica"
@@ -410,10 +414,14 @@ aboutMisskey: "Sobre Misskey"
administrator: "Administrador"
token: "Token"
2fa: "Autenticación de doble factor"
+setupOf2fa: "Configurar la autenticación de dos factores"
totp: "Aplicación autentícadora"
totpDescription: "Ingresa una contaseña de un sólo uso usando la aplicación autenticadora"
moderator: "Moderador"
moderation: "Moderación"
+moderationNote: "Nota de moderación"
+addModerationNote: "Añadir nota de moderación"
+moderationLogs: "Log de moderación"
nUsersMentioned: "{n} usuarios mencionados"
securityKeyAndPasskey: "Clave de seguridad / clave de paso"
securityKey: "Clave de seguridad"
@@ -526,6 +534,7 @@ serverLogs: "Registros del servidor"
deleteAll: "Eliminar todos"
showFixedPostForm: "Mostrar el formulario de las entradas encima de la línea de tiempo"
showFixedPostFormInChannel: "Mostrar el formulario de publicación por encima de la cronología (Canales)"
+withRepliesByDefaultForNewlyFollowed: "Incluir por defecto respuestas de usuarios recién seguidos en la línea de tiempo"
newNoteRecived: "Tienes una nota nueva"
sounds: "Sonidos"
sound: "Sonidos"
@@ -653,6 +662,7 @@ behavior: "Comportamiento"
sample: "Muestra"
abuseReports: "Reportes"
reportAbuse: "Reportar"
+reportAbuseRenote: "Reportar renota"
reportAbuseOf: "Reportar a {name}"
fillAbuseReportDescription: "Ingrese los detalles del reporte. Si hay una nota en particular, ingrese la URL de esta."
abuseReported: "Se ha enviado el reporte. Muchas gracias."
@@ -680,6 +690,7 @@ createNewClip: "Crear clip nuevo"
unclip: "Quitar clip"
confirmToUnclipAlreadyClippedNote: "Esta nota ya está incluida en el clip \"{name}\". ¿Quiere quitar la nota del clip?"
public: "Público"
+private: "Privado"
i18nInfo: "Misskey está siendo traducido a varios idiomas gracias a voluntarios. Se puede colaborar traduciendo en {link}"
manageAccessTokens: "Administrar tokens de acceso"
accountInfo: "Información de la Cuenta"
@@ -704,6 +715,7 @@ lockedAccountInfo: "A menos que configures la visibilidad de tus notas como \"S
alwaysMarkSensitive: "Marcar los medios de comunicación como contenido sensible por defecto"
loadRawImages: "Cargar las imágenes originales en lugar de mostrar las miniaturas"
disableShowingAnimatedImages: "No reproducir imágenes animadas"
+highlightSensitiveMedia: "Resaltar medios marcados como sensibles"
verificationEmailSent: "Se le ha enviado un correo electrónico de confirmación. Por favor, acceda al enlace proporcionado en el correo electrónico para completar la configuración."
notSet: "Sin especificar"
emailVerified: "Su dirección de correo electrónico ha sido verificada."
@@ -1018,7 +1030,7 @@ retryAllQueuesConfirmText: "La carga del servidor está incrementándose tempora
enableChartsForRemoteUser: "Generar gráficas de usuarios remotos."
enableChartsForFederatedInstances: "Generar gráficos de servidores remotos"
showClipButtonInNoteFooter: "Añadir \"Clip\" al menú de notas"
-largeNoteReactions: "Agrandar las reacciones de las notas"
+reactionsDisplaySize: "Tamaño de las reacciones"
noteIdOrUrl: "ID o URL de la nota"
video: "Video"
videos: "Video"
@@ -1094,6 +1106,49 @@ expired: "Caducada"
doYouAgree: "¿Está de acuerdo?"
beSureToReadThisAsItIsImportant: "Por favor lea esto que es importante"
iHaveReadXCarefullyAndAgree: "He leído el texto {x} y estoy de acuerdo"
+dialog: "Diálogo"
+icon: "Avatar"
+forYou: "Para ti"
+currentAnnouncements: "Anuncios actuales"
+pastAnnouncements: "Anuncios anteriores"
+youHaveUnreadAnnouncements: "Hay anuncios sin leer"
+useSecurityKey: "Por favor, sigue las instrucciones de tu dispositivo o navegador para usar tu clave de seguridad o tu clave de paso."
+replies: "Responder"
+renotes: "Renotar"
+loadReplies: "Ver respuestas"
+loadConversation: "Ver conversación"
+pinnedList: "Lista fijada"
+keepScreenOn: "Mantener pantalla encendida"
+verifiedLink: "Propiedad del enlace verificada"
+notifyNotes: "Notificar nuevas notas"
+unnotifyNotes: "Dejar de notificar nuevas notas"
+authentication: "Autenticación"
+authenticationRequiredToContinue: "Por favor, autentifícate para continuar"
+dateAndTime: "Fecha y hora"
+showRenotes: "Mostrar renotas"
+edited: "Editado"
+notificationRecieveConfig: "Ajustes de Notificaciones"
+mutualFollow: "Os seguís mutuamente"
+fileAttachedOnly: "Solo notas con archivos"
+showRepliesToOthersInTimeline: "Mostrar respuestas a otros en la línea de tiempo"
+hideRepliesToOthersInTimeline: "Ocultar respuestas a otros en la línea de tiempo"
+externalServices: "Servicios Externos"
+impressum: "Impressum"
+impressumUrl: "Impressum URL"
+impressumDescription: "En algunos países, como Alemania, la inclusión del operador de datos (el Impressum) es requerido legalmente para sitios web comerciales."
+privacyPolicy: "Política de Privacidad"
+privacyPolicyUrl: "URL de la Política de Privacidad"
+tosAndPrivacyPolicy: "Condiciones de Uso y Política de Privacidad"
+flip: "Echar de un capirotazo"
+_announcement:
+ forExistingUsers: "Solo para usuarios registrados"
+ forExistingUsersDescription: "Este anuncio solo se mostrará a aquellos usuarios registrados en el momento de su publicación. Si se deshabilita esta opción, aquellos usuarios que se registren tras su publicación también lo verán."
+ needConfirmationToRead: "Requerir confirmación de lectura aparte"
+ needConfirmationToReadDescription: "Si se habilita esta opción, se pedirá una confirmación de lectura aparte. Además, este anuncio será excluido de cualquier funcionalidad de \"Marcar todos como leídos\"."
+ end: "Anuncios archivados"
+ tooManyActiveAnnouncementDescription: "Tener demasiados anuncios activos empeora la experiencia de usuario. Por favor, considera archivar aquellos anuncios que hayan quedado obsoletos."
+ readConfirmTitle: "¿Marcar como leído?"
+ readConfirmText: "Esto marcará el contenido de \"{title}\" como leído."
_initialAccountSetting:
accountCreated: "¡La cuenta ha sido creada!"
letsStartAccountSetup: "Para empezar, creemos tu perfil."
@@ -1106,11 +1161,19 @@ _initialAccountSetting:
pushNotificationDescription: "Habilitar las notificaciones push te permitirá recibir notificaciones de {name} directamente en tu dispositivo."
initialAccountSettingCompleted: "¡Configuración del perfil completada!"
haveFun: "¡Disfruta de {name}!"
- ifYouNeedLearnMore: "Si quieres aprender cómo usar {name} (Misskey), por favor, visita {link}."
skipAreYouSure: "¿Realmente quieres saltarte la configuración del perfil?"
laterAreYouSure: "¿Realmente quieres configurar tu perfil después?"
_serverRules:
description: "Un conjunto de reglas que serán mostradas antes del registro. Configurar un sumario de términos de servicio es recomendado."
+_serverSettings:
+ iconUrl: "URL del ícono"
+ appIconDescription: "Indica el icono que se va a usar cuando {host} se muestre como una app."
+ appIconUsageExample: "Por ejemplo, como PWA o cuando se muestre como un marcador en la pantalla inicial del dispositivo"
+ appIconStyleRecommendation: "Como el icono puede ser recortado como un cuadrado o un círculo, se recomienda un icono con un margen coloreado alrededor del contenido."
+ appIconResolutionMustBe: "La resolución mínima es {resolution}."
+ manifestJsonOverride: "Sobreescribir manifest.json"
+ shortName: "Nombre corto"
+ shortNameDescription: "Forma corta del nombre de la instancia que puede mostrarse si el nombre completo es demasiado largo."
_accountMigration:
moveFrom: "Trasladar de otra cuenta a ésta"
moveFromSub: "Crear un alias para otra cuenta."
@@ -1365,6 +1428,9 @@ _achievements:
title: "Brain Diver"
description: "Publicaste un vínculo a \"Brain Diver\""
flavor: "Misskey-Misskey La-Tu-Ma"
+ _smashTestNotificationButton:
+ title: "Sobrecarga de pruebas"
+ description: "Envía muchas notificaciones de prueba en un corto espacio de tiempo"
_role:
new: "Crear rol"
edit: "Editar rol"
@@ -1422,6 +1488,7 @@ _role:
descriptionOfRateLimitFactor: "Límites más bajos son menos restrictivos, más altos menos restrictivos"
canHideAds: "Puede ocultar anuncios"
canSearchNotes: "Uso de la búsqueda de notas"
+ canUseTranslator: "Uso de traductor"
_condition:
isLocal: "Usuario local"
isRemote: "Usuario remoto"
@@ -1470,6 +1537,10 @@ _ad:
reduceFrequencyOfThisAd: "Mostrar menos este anuncio."
hide: "No mostrar"
timezoneinfo: "El día de la semana está determidado por la zona horaria del servidor."
+ adsSettings: "Ajustes de anuncios"
+ notesPerOneAd: "Intervalo de actualización de anuncios en tiempo real (Notas por cada anuncio)"
+ setZeroToDisable: "Establece este valor a 0 para deshabilitar la actualización de anuncios en tiempo real"
+ adsTooClose: "El intervalo de anuncios actual puede empeorar la experiencia del usuario por ser demasiado bajo."
_forgotPassword:
enterEmail: "Ingrese el correo usado para registrar la cuenta. Se enviará un link para resetear la contraseña."
ifNoEmail: "Si no utilizó un correo para crear la cuenta, contáctese con el administrador."
@@ -1488,6 +1559,7 @@ _plugin:
install: "Instalar plugins"
installWarn: "Por favor no instale plugins que no son de confianza"
manage: "Gestionar plugins"
+ viewSource: "Ver la fuente"
_preferencesBackups:
list: "Respaldos creados"
saveNew: "Guardar nuevo respaldo"
@@ -1554,11 +1626,6 @@ _wordMute:
muteWords: "Palabras que silenciar"
muteWordsDescription: "Separar con espacios indica una declaracion And, separar con lineas nuevas indica una declaracion Or。"
muteWordsDescription2: "Encerrar las palabras clave entre numerales para usar expresiones regulares"
- softDescription: "Ocultar en la linea de tiempo las notas que cumplen las condiciones"
- hardDescription: "Evitar que se agreguen a la linea de tiempo las notas que cumplen las condiciones. Las notas no agregadas seguirán quitadas aunque cambien las condiciones."
- soft: "Suave"
- hard: "Duro"
- mutedNotes: "Notas silenciadas"
_instanceMute:
instanceMuteDescription: "Silencia todas las notas y reposts de la instancias seleccionadas, incluyendo respuestas a los usuarios de las mismas"
instanceMuteDescription2: "Separar por líneas"
@@ -1622,9 +1689,6 @@ _theme:
infoFg: "Texto de información"
infoWarnBg: "Fondo de advertencias"
infoWarnFg: "Texto de advertencias"
- cwBg: "Fondo del botón CW"
- cwFg: "Texto del botón CW"
- cwHoverBg: "Fondo del botón CW (hover)"
toastBg: "Fondo de notificaciones"
toastFg: "Texto de notificaciones"
buttonBg: "Fondo de botón"
@@ -1642,8 +1706,6 @@ _sfx:
note: "Notas"
noteMy: "Nota (a mí mismo)"
notification: "Notificaciones"
- chat: "Chat"
- chatBg: "Chat (Fondo)"
antenna: "Antena receptora"
channel: "Notificaciones del canal"
_ago:
@@ -1662,27 +1724,17 @@ _time:
minute: "Minutos"
hour: "Horas"
day: "Días"
-_timelineTutorial:
- title: "Cómo usar Misskey"
- step1_1: "Ésta es la \"línea de tiempo\". Todas las \"notas\" que sean publicadas en {name} serán mostradas cronológicamente aquí."
- step1_2: "Hay varias líneas de tiempo. Por ejemplo, la línea temporal \"Inicio\" contiene las notas de otros usuarios que sigues, y la línea \"Local\" contandrá las notas de todos los usuarios de {name}."
- step2_1: "Ahora probemos publicar una nota. Puedes hacerlo presionando el botón que tiene un ícono de lápiz."
- step2_2: "¿Qué tal si escribimos una introducción? o sólo un \"¡Hola {name}!\" ¿No te apetece?"
- step3_1: "¿Terminaste de publicar tu primera nota?"
- step3_2: "Tu primera nota ahora se mostrará en tu línea de tiempo."
- step4_1: "También puedes añadir \"Reacciones\" a notas."
- step4_2: "Para añadir una reacción selecciona el botón \"+\" en la nota y escoge el emoji que quieras para reaccionar."
_2fa:
alreadyRegistered: "Ya has completado la configuración."
registerTOTP: "Registrar aplicación autenticadora"
- passwordToTOTP: "Ingresa tu contraseña"
step1: "Primero, instale en su dispositivo la aplicación de autenticación {a} o {b} u otra."
step2: "Luego, escanee con la aplicación el código QR mostrado en pantalla."
step2Click: "Clicking on this QR code will allow you to register 2FA to your security key or phone authenticator app.\nTocar este código QR te permitirá registrar la autenticación 2FA a tu llave de seguridad o aplicación autenticadora."
- step2Url: "En una aplicación de escritorio se puede ingresar la siguiente URL:"
+ step2Uri: "Si usas una aplicación de escritorio, introduce en ella la siguiente URL."
step3Title: "Ingresa un código de autenticación"
step3: "Para terminar, ingrese el token mostrado en la aplicación."
- step4: "Ahora cuando inicie sesión, ingrese el mismo token\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}"
+ setupCompleted: "Configuración completada"
+ step4: "Ahora cuando inicie sesión, ingrese el mismo token"
securityKeyNotSupported: "Tu navegador no soporta claves de autenticación."
registerTOTPBeforeKey: "Please set up an authenticator app to register a security or pass key.\npor favor. configura una aplicación de autenticación para registrar una llave de seguridad."
securityKeyInfo: "Se puede configurar el inicio de sesión usando una clave de seguridad de hardware que soporte FIDO2 o con un certificado de huella digital o con un PIN"
@@ -1696,6 +1748,11 @@ _2fa:
renewTOTPConfirm: "This will cause verification codes from your previous app to stop working\nEsto hará que los códigos de verificación de la aplicación anterior dejen de funcionar"
renewTOTPOk: "Reconfigurar"
renewTOTPCancel: "No gracias"
+ checkBackupCodesBeforeCloseThisWizard: "Por favor, copia los siguientes códigos de respaldo antes de finalizar el asistente."
+ backupCodes: "Códigos de Respaldo"
+ backupCodesDescription: "En caso de que no puedas usar tu aplicación de autenticación, podrás usar los códigos de respaldo que figuran abajo para acceder a tu cuenta. Asegúrate de guardar en lugar seguro los códigos de respaldo. Cada uno de los códigos de respaldo es de un solo uso."
+ backupCodeUsedWarning: "Has usado todos los códigos de respaldo. Si dejas de tener acceso a tu aplicación de autenticación, no podrás volver a iniciar sesión en tu cuenta. Por favor, reconfigura tu aplicación de autenticación lo antes posible."
+ backupCodesExhaustedWarning: "Has usado todos los códigos de respaldo. Si dejas de tener acceso a tu aplicación de autenticación, no podrás volver a iniciar sesión en la cuenta que figura arriba. Por favor, reconfigura tu aplicación de autenticación lo antes posible."
_permissions:
"read:account": "Ver información de la cuenta"
"write:account": "Editar información de la cuenta"
@@ -1729,6 +1786,10 @@ _permissions:
"write:gallery": "Editar galería"
"read:gallery-likes": "Ver favoritos de la galería"
"write:gallery-likes": "Editar favoritos de la galería"
+ "read:flash": "Ver Play"
+ "write:flash": "Editar Plays"
+ "read:flash-likes": "Ver los Play que me gustan"
+ "write:flash-likes": "Editar lista de Play que me gustan"
_auth:
shareAccessTitle: "Permisos de la aplicación"
shareAccess: "¿Desea permitir el acceso a la cuenta \"{name}\"?"
@@ -1744,6 +1805,7 @@ _antennaSources:
homeTimeline: "Notas de los usuarios que sigues"
users: "Notas de un usuario o varios"
userList: "Notas de los usuarios de una lista"
+ userBlacklist: "Todas las notas excepto aquellas de uno o más usuarios especificados"
_weekday:
sunday: "Domingo"
monday: "Lunes"
@@ -1843,6 +1905,7 @@ _profile:
metadataContent: "Contenido"
changeAvatar: "Cambiar avatar"
changeBanner: "Cambiar banner"
+ verifiedLinkDescription: "Introduciendo una URL que contiene un enlace a tu perfil, se puede mostrar un icono de verificación de propiedad al lado del campo."
_exportOrImport:
allNotes: "Todas las notas"
favoritedNotes: "Notas favoritas"
@@ -1852,6 +1915,7 @@ _exportOrImport:
userLists: "Listas"
excludeMutingUsers: "Excluir usuarios silenciados"
excludeInactiveUsers: "Excluir usuarios inactivos"
+ withReplies: "Incluir respuestas de los usuarios importados en la línea de tiempo"
_charts:
federation: "Federación"
apRequest: "Pedidos"
@@ -1961,11 +2025,17 @@ _notification:
youReceivedFollowRequest: "Has mandado una solicitud de seguimiento"
yourFollowRequestAccepted: "Tu solicitud de seguimiento fue aceptada"
pollEnded: "Estan disponibles los resultados de la encuesta"
+ newNote: "Nueva nota"
unreadAntennaNote: "Antena {name}"
emptyPushNotificationMessage: "Se han actualizado las notificaciones push"
achievementEarned: "Logro desbloqueado"
+ testNotification: "Notificación de prueba"
+ checkNotificationBehavior: "Comprobar comportamiento de la notificación"
+ sendTestNotification: "Enviar notificación de prueba"
+ notificationWillBeDisplayedLikeThis: "Las notificaciones tendrán este aspecto"
_types:
all: "Todo"
+ note: "Nuevas notas"
follow: "Siguiendo"
mention: "Menciones"
reply: "Respuestas"
@@ -1999,6 +2069,8 @@ _deck:
introduction2: "Presiona en la + de la derecha de la pantalla para añadir nuevas columnas donde quieras."
widgetsIntroduction: "Por favor selecciona \"Editar Widgets\" en el menú columna y agrega un widget."
useSimpleUiForNonRootPages: "Mostrar páginas no pertenecientes a la raíz con la interfaz simple"
+ usedAsMinWidthWhenFlexible: "Se usará el ancho mínimo cuando la opción \"Autoajustar ancho\" esté habilitada"
+ flexible: "Autoajustar ancho"
_columns:
main: "Principal"
widgets: "Widgets"
@@ -2033,3 +2105,42 @@ _webhookSettings:
renote: "Cuando reciba un \"re-note\""
reaction: "Cuando se recibe una reacción"
mention: "Cuando hay una mención"
+_moderationLogTypes:
+ createRole: "Rol creado"
+ deleteRole: "Rol eliminado"
+ updateRole: "Rol actualizado"
+ assignRole: "Rol asignado"
+ unassignRole: "Rol retirado"
+ suspend: "Suspender"
+ unsuspend: "Suspensión retirada"
+ addCustomEmoji: "Añadido emoji personalizado"
+ updateCustomEmoji: "Emoji personalizado actualizado"
+ deleteCustomEmoji: "Emoji personalizado eliminado"
+ updateServerSettings: "Ajustes de servidor actualizados"
+ updateUserNote: "Nota de moderación actualizada"
+ deleteDriveFile: "Archivo eliminado"
+ deleteNote: "Nota eliminada"
+ createGlobalAnnouncement: "Anuncio global creado"
+ createUserAnnouncement: "Anuncio de usuario creado"
+ updateGlobalAnnouncement: "Anuncio global actualizado"
+ updateUserAnnouncement: "Anuncio de usuario actualizado"
+ deleteGlobalAnnouncement: "Anuncio global eliminado"
+ deleteUserAnnouncement: "Anuncio de usuario eliminado"
+ resetPassword: "Resetear contraseña"
+ suspendRemoteInstance: "Instancia remota suspendida"
+ unsuspendRemoteInstance: "Suspensión de instancia remota retirada"
+ markSensitiveDriveFile: "Archivo marcado como sensible"
+ unmarkSensitiveDriveFile: "Archivo marcado como no sensible"
+ resolveAbuseReport: "Reporte resuelto"
+ createInvitation: "Generar invitación"
+ createAd: "Anuncio creado"
+ deleteAd: "Anuncio eliminado"
+ updateAd: "Anuncio actualizado"
+_fileViewer:
+ title: "Detalles del archivo"
+ type: "Tipo de archivo"
+ size: "Tamaño del archivo"
+ url: "URL"
+ uploadedAt: "Subido el"
+ attachedNotes: "Notas adjuntas"
+ thisPageCanBeSeenFromTheAuthor: "Esta página solo puede ser vista por el autor."
diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml
index f53613948e..94254fd998 100644
--- a/locales/fr-FR.yml
+++ b/locales/fr-FR.yml
@@ -45,9 +45,10 @@ pin: "Épingler sur le profil"
unpin: "Désépingler"
copyContent: "Copier le contenu"
copyLink: "Copier le lien"
+copyLinkRenote: "Copier le lien de la renote"
delete: "Supprimer"
deleteAndEdit: "Supprimer et réécrire"
-deleteAndEditConfirm: "Êtes-vous sûr·e de vouloir supprimer cette note et la reformuler ? Vous perdrez toutes les réactions, renotes et réponses y afférentes."
+deleteAndEditConfirm: "Êtes-vous sûr de vouloir effacer cette note et la modifier ? Vous perdrez toutes les réactions, renotes et réponses."
addToList: "Ajouter à une liste"
addToAntenna: "Ajouter à l’antenne"
sendMessage: "Envoyer un message"
@@ -58,7 +59,7 @@ copyNoteId: "Copier l'identifiant de la note"
copyFileId: "Copier l'identifiant du fichier"
copyFolderId: "Copier l'identifiant du dossier"
copyProfileUrl: "Copier l'URL du profil"
-searchUser: "Chercher un utilisateur"
+searchUser: "Chercher un·e utilisateur·rice"
reply: "Répondre"
loadMore: "Afficher plus …"
showMore: "Voir plus"
@@ -76,14 +77,14 @@ files: "Fichiers"
download: "Télécharger"
driveFileDeleteConfirm: "Êtes-vous sûr de vouloir supprimer le fichier \"{name}\" ? Les notes liées à ce fichier seront aussi supprimées."
unfollowConfirm: "Désirez-vous vous désabonner de {name} ?"
-exportRequested: "Vous avez demandé une exportation. L’opération pourrait prendre un peu de temps. Une terminée, le fichier résultant sera ajouté au Drive."
+exportRequested: "Vous avez demandé une exportation. L’opération pourrait prendre un peu de temps. Une fois terminée, le fichier sera ajouté au Drive."
importRequested: "Vous avez initié un import. Cela pourrait prendre un peu de temps."
lists: "Listes"
noLists: "Vous n’avez aucune liste"
note: "Note"
notes: "Notes"
following: "Abonnements"
-followers: "Abonnés"
+followers: "Abonné·e·s"
followsYou: "Vous suit"
createList: "Créer une liste"
manageLists: "Gérer les listes"
@@ -129,6 +130,8 @@ unmarkAsSensitive: "Supprimer le marquage comme sensible"
enterFileName: "Entrer le nom du fichier"
mute: "Masquer"
unmute: "Ne plus masquer"
+renoteMute: "Masquer les renotes"
+renoteUnmute: "Ne plus masquer les renotes"
block: "Bloquer"
unblock: "Débloquer"
suspend: "Suspendre"
@@ -138,6 +141,7 @@ unblockConfirm: "Êtes-vous sûr·e de vouloir débloquer ce compte ?"
suspendConfirm: "Êtes-vous sûr·e de vouloir suspendre ce compte ?"
unsuspendConfirm: "Êtes-vous sûr·e de vouloir annuler la suspension de ce compte ?"
selectList: "Sélectionner une liste"
+editList: "Modifier la liste"
selectChannel: "Sélectionner un canal"
selectAntenna: "Sélectionner une antenne"
editAntenna: "Modifier l'antenne"
@@ -180,7 +184,7 @@ selectUser: "Sélectionner un·e utilisateur·rice"
recipient: "Destinataire"
annotation: "Commentaires"
federation: "Fédération"
-instances: "Instance"
+instances: "Instances"
registeredAt: "Premier contact le"
latestRequestReceivedAt: "Dernière requête reçue"
latestStatus: "Dernier statut"
@@ -190,6 +194,7 @@ perHour: "par heure"
perDay: "par jour"
stopActivityDelivery: "Arrêter l’envoi de l’activité"
blockThisInstance: "Bloquer cette instance"
+silenceThisInstance: "Mettre cette instance en sourdine"
operations: "Opérations"
software: "Logiciel"
version: "Version"
@@ -209,6 +214,8 @@ clearCachedFiles: "Vider le cache"
clearCachedFilesConfirm: "Êtes-vous sûr·e de vouloir vider tout le cache de fichiers distants ?"
blockedInstances: "Instances bloquées"
blockedInstancesDescription: "Listez les instances que vous désirez bloquer, une par ligne. Ces instances ne seront plus en capacité d'interagir avec votre instance."
+silencedInstances: "Instances mises en sourdine"
+silencedInstancesDescription: "Énumérer les noms d'hôte des instances à mettre en sourdine. Tous les comptes des instances énumérées seront traités comme mis en sourdine, ne peuvent faire que des demandes de suivi et ne peuvent pas mentionner les comptes locaux s'ils ne sont pas suivis. Cela n'affectera pas les instances bloquées."
muteAndBlock: "Masqué·e·s / Bloqué·e·s"
mutedUsers: "Utilisateur·rice·s en sourdine"
blockedUsers: "Utilisateur·rice·s bloqué·e·s"
@@ -250,7 +257,7 @@ announcements: "Annonces"
imageUrl: "URL de l’image"
remove: "Supprimer"
removed: "Supprimé"
-removeAreYouSure: "Êtes-vous sûr·e de vouloir supprimer「{x}」?"
+removeAreYouSure: "Êtes-vous sûr·e de vouloir supprimer « {x} » ?"
deleteAreYouSure: "Êtes-vous sûr·e de vouloir supprimer「{x}」?"
resetAreYouSure: "Voulez-vous réinitialiser ?"
saved: "Enregistré"
@@ -271,6 +278,8 @@ startMessaging: "Commencer à discuter"
nUsersRead: "Lu par {n} personnes"
agreeTo: "J’accepte {0}"
agree: "Accepter"
+agreeBelow: "J’accepte ce qui suit"
+basicNotesBeforeCreateAccount: "Notes importantes"
termsOfService: "Conditions d'utilisation"
start: "Commencer"
home: "Principal"
@@ -349,7 +358,6 @@ invite: "Inviter"
driveCapacityPerLocalAccount: "Volume du Drive par utilisateur local"
driveCapacityPerRemoteAccount: "Volume du Drive par utilisateur distant"
inMb: "en mégaoctets"
-iconUrl: "URL de l'icône"
bannerUrl: "URL de l’image de la bannière"
backgroundImageUrl: "URL de l'image d'arrière-plan"
basicInfo: "Informations basiques"
@@ -379,7 +387,7 @@ antennaSource: "Source de l’antenne"
antennaKeywords: "Mots clés à recevoir"
antennaExcludeKeywords: "Mots clés à exclure"
antennaKeywordsDescription: "Séparer avec des espaces pour la condition AND. Séparer avec un saut de ligne pour une condition OR."
-notifyAntenna: "Je souhaite recevoir les notifications des nouvelles notes"
+notifyAntenna: "Me notifier pour les nouvelles notes"
withFileAntenna: "Notes ayant des attachements uniquement"
enableServiceworker: "Activer ServiceWorker"
antennaUsersDescription: "Saisissez un seul nom d’utilisateur·rice par ligne"
@@ -405,11 +413,16 @@ aboutMisskey: "À propos de Misskey"
administrator: "Administrateur"
token: "Jeton"
2fa: "Authentification à deux facteurs"
+setupOf2fa: "Configuration de l’authentification à deux facteurs"
totp: "Application d'authentification"
totpDescription: "Entrez un mot de passe à usage unique à l'aide d'une application d'authentification"
moderator: "Modérateur·rice·s"
moderation: "Modérations"
+moderationNote: "Note de modération"
+addModerationNote: "Ajouter une note de modération"
+moderationLogs: "Journal de modération"
nUsersMentioned: "{n} utilisateur·rice·s mentionné·e·s"
+securityKeyAndPasskey: "Sécurité et clés de sécurité"
securityKey: "Clé de sécurité"
lastUsed: "Dernier utilisé"
lastUsedAt: "Dernière utilisation : {t}"
@@ -466,6 +479,7 @@ aboutX: "À propos de {x}"
emojiStyle: "Style des émojis"
native: "Natif"
disableDrawer: "Les menus ne s'affichent pas dans le tiroir"
+showNoteActionsOnlyHover: "Afficher les actions de note uniquement au survol"
noHistory: "Pas d'historique"
signinHistory: "Historique de connexion"
enableAdvancedMfm: "Activer la MFM avancée"
@@ -477,6 +491,7 @@ createAccount: "Créer un compte"
existingAccount: "Compte existant"
regenerate: "Générer à nouveau"
fontSize: "Taille de la police"
+limitTo: "Limiter à {x}"
noFollowRequests: "Vous n’avez aucune demande d’abonnement en attente"
openImageInNewTab: "Ouvrir les images dans un nouvel onglet"
dashboard: "Tableau de bord"
@@ -513,6 +528,7 @@ objectStorageSetPublicRead: "Régler sur « public » lors de l'envoi"
serverLogs: "Journal du serveur"
deleteAll: "Supprimer tout"
showFixedPostForm: "Afficher le formulaire de publication en haut du fil d'actualité"
+withRepliesByDefaultForNewlyFollowed: "Afficher les réponses des nouvelles personnes que vous suivez dans le fil par défaut"
newNoteRecived: "Voir les nouvelles notes"
sounds: "Sons"
sound: "Sons"
@@ -595,14 +611,14 @@ permission: "Autorisations "
enableAll: "Tout activer"
disableAll: "Tout désactiver"
tokenRequested: "Autoriser l'accès au compte"
-pluginTokenRequestedDescription: "Ce plugin pourra utiliser les autorisations définies ici."
+pluginTokenRequestedDescription: "Cette extension pourra utiliser les autorisations définies ici."
notificationType: "Type de notifications"
edit: "Editer"
-emailServer: "Serveur mail"
+emailServer: "Serveur de messagerie"
enableEmail: "Activer la distribution de courriel"
-emailConfigInfo: "Utilisé pour confirmer votre adresse de courriel et la réinitialisation de votre mot de passe en cas d’oubli."
+emailConfigInfo: "Utilisé pour confirmer votre adresse e-mail et réinitialiser votre mot de passe en cas d’oubli"
email: "E-mail "
-emailAddress: "Adresses e-mail"
+emailAddress: "Adresse e-mail"
smtpConfig: "Paramètres du serveur SMTP"
smtpHost: "Serveur distant"
smtpPort: "Port"
@@ -640,6 +656,7 @@ behavior: "Comportement"
sample: "Exemple"
abuseReports: "Signalements"
reportAbuse: "Signaler"
+reportAbuseRenote: "Signaler la renote"
reportAbuseOf: "Signaler {name}"
fillAbuseReportDescription: "Veuillez expliquer les raisons du signalement. S'il s'agit d'une note précise, veuillez en donner le lien."
abuseReported: "Le rapport est envoyé. Merci."
@@ -664,7 +681,10 @@ clip: "Clip"
createNew: "Créer nouveau"
optional: "Facultatif"
createNewClip: "Créer un nouveau clip"
+unclip: "Supprimer le clip"
+confirmToUnclipAlreadyClippedNote: "Cette note fait déjà partie du clip « {name} ». Souhaitez-vous la supprimer de ce clip ?"
public: "Public"
+private: "Privé"
i18nInfo: "Misskey est traduit dans différentes langues par des bénévoles. Vous pouvez contribuer à {link}."
manageAccessTokens: "Gérer les jetons d'accès"
accountInfo: " Informations du compte "
@@ -673,7 +693,7 @@ repliesCount: "Nombre de réponses envoyées"
renotesCount: "Nombre de notes que vous avez renotées"
repliedCount: "Nombre de réponses reçues"
renotedCount: "Nombre de vos notes renotées"
-followingCount: "Nombre de comptes suivis"
+followingCount: "Nombre d'abonnements"
followersCount: "Nombre d'abonnés"
sentReactionsCount: "Nombre de réactions envoyées"
receivedReactionsCount: "Nombre de réactions reçues"
@@ -744,7 +764,7 @@ inUse: "utilisé"
editCode: "Modifier le code"
apply: "Appliquer"
receiveAnnouncementFromInstance: "Recevoir les messages d'information de l'instance"
-emailNotification: "Notifications par mail"
+emailNotification: "Notifications par courriel"
publish: "Public"
inChannelSearch: "Chercher dans le canal"
useReactionPickerForContextMenu: "Clic-droit pour ouvrir le panneau de réactions"
@@ -761,7 +781,7 @@ addDescription: "Ajouter une description"
userPagePinTip: "Vous pouvez afficher des notes ici en sélectionnant l'option « Épingler au profil » dans le menu de chaque note."
notSpecifiedMentionWarning: "Vous avez mentionné des utilisateur·rice·s qui ne font pas partie de la liste des destinataires"
info: "Informations"
-userInfo: "Informations sur l'utilisateur"
+userInfo: "Informations sur l'utilisateur·rice"
unknown: "Inconnu"
onlineStatus: "Statut"
hideOnlineStatus: "Se rendre invisible"
@@ -792,6 +812,7 @@ popularPosts: "Les plus consultées"
shareWithNote: "Partager dans une note"
ads: "Publicité"
expiration: "Échéance"
+startingperiod: "Commencer"
memo: "Pense-bête"
priority: "Priorité"
high: "Haute"
@@ -818,6 +839,7 @@ translatedFrom: "Traduit depuis {x}"
accountDeletionInProgress: "La suppression de votre compte est en cours"
usernameInfo: "C'est un nom qui identifie votre compte sur l'instance de manière unique. Vous pouvez utiliser des lettres de l'alphabet (minuscules et majuscules), des chiffres (de 0 à 9), ou bien le tiret « _ ». Vous ne pourrez pas modifier votre nom d'utilisateur·rice par la suite."
aiChanMode: "Mode Ai"
+devMode: "Mode développement"
keepCw: "Garder le CW"
pubSub: "Comptes Pub/Sub"
lastCommunication: "Dernière communication"
@@ -827,10 +849,12 @@ breakFollow: "Ne plus suivre"
breakFollowConfirm: "Êtes-vous sûr de vouloir vous désabonner ?"
itsOn: "Activé"
itsOff: "Désactivé"
+on: "Activé"
+off: "Désactivé"
emailRequiredForSignup: "Une adresse e-mail est nécessaire pour créer un compte"
unread: "Non lu"
filter: "Filtre"
-controlPanel: "Panneau de contrôle"
+controlPanel: "Panneau de configuration"
manageAccounts: "Gérer les comptes"
makeReactionsPublic: "Rendre les réactions publiques"
makeReactionsPublicDescription: "Ceci rendra la liste de toutes vos réactions données publique."
@@ -907,6 +931,7 @@ remoteOnly: "Distant uniquement"
failedToUpload: "Échec du transfert"
cannotUploadBecauseInappropriate: "Impossible de télécharger le document car il a été déterminé qu'il pouvait contenir un contenu inapproprié."
cannotUploadBecauseNoFreeSpace: "Impossible de télécharger en raison d'un manque d'espace libre sur le disque.\n"
+cannotUploadBecauseExceedsFileSizeLimit: "Ce fichier ne peut pas être téléchargé parce qu'il dépasse la taille maximale."
beta: "Bêta"
enableAutoSensitive: "Détermination automatique de NSFW"
enableAutoSensitiveDescription: "S'il est disponible, le drapeau NSFW est automatiquement défini sur le média en utilisant l'apprentissage automatique. Même si cette fonction est désactivée, elle peut être réglée automatiquement dans certains cas."
@@ -921,48 +946,253 @@ unsubscribePushNotification: "Désactiver les notifications push"
pushNotificationAlreadySubscribed: "Les notifications push sont déjà activées"
pushNotificationNotSupported: "Votre navigateur ou votre instance ne prend pas en charge les notifications push"
sendPushNotificationReadMessage: "Supprimer les notifications push une fois que les notifications ou messages pertinents ont été lus."
+sendPushNotificationReadMessageCaption: "Cela peut augmenter la consommation de batterie de votre appareil."
+windowMaximize: "Maximiser"
+windowMinimize: "Minimaliser"
windowRestore: "Restaurer"
caption: "Libellé"
loggedInAsBot: "Connecté actuellement en tant que bot"
tools: "Outils"
cannotLoad: "Chargement impossible"
+numberOfProfileView: "Nombre de vues du profil"
like: "J'aime"
+unlike: "Ne plus aimer"
numberOfLikes: "Favoris"
show: "Affichage"
neverShow: "Ne plus afficher"
remindMeLater: "Peut-être plus tard"
+didYouLikeMisskey: "Avez-vous aimé Misskey ?"
roles: "Rôles"
role: "Rôles"
noRole: "Aucun rôle"
normalUser: "Simple utilisateur·rice"
+undefined: "Non défini"
assign: "Attribuer"
+unassign: "Retirer"
color: "Couleur"
manageCustomEmojis: "Gestion des émojis personnalisés"
+manageAvatarDecorations: "Gérer les décorations d'avatar"
+youCannotCreateAnymore: "Vous avez atteint la limite de création."
+cannotPerformTemporary: "Temporairement indisponible"
+invalidParamError: "Paramètres invalides"
+permissionDeniedError: "Opération refusée"
preset: "Préréglage"
selectFromPresets: "Sélectionner à partir des préréglages"
+achievements: "Accomplissements"
+gotInvalidResponseError: "Réponse du serveur invalide"
thisPostMayBeAnnoying: "Cette note peut gêner d'autres personnes."
+thisPostMayBeAnnoyingHome: "Publier vers le fil principal"
thisPostMayBeAnnoyingCancel: "Annuler"
+thisPostMayBeAnnoyingIgnore: "Publier quand-même"
+collapseRenotes: "Réduire les renotes déjà vues"
+internalServerError: "Erreur interne du serveur"
+copyErrorInfo: "Copier les détails de l’erreur"
+joinThisServer: "S'inscrire à cette instance"
+exploreOtherServers: "Trouver une autre instance"
+disableFederationOk: "Désactiver"
+postToTheChannel: "Publier au canal"
+likeOnly: "Les favoris uniquement"
+sensitiveWords: "Mots sensibles"
+notesSearchNotAvailable: "La recherche de notes n'est pas disponible."
license: "Licence"
+myClips: "Mes clips"
+retryAllQueuesConfirmText: "Cela peut augmenter temporairement la charge du serveur."
+showClipButtonInNoteFooter: "Ajouter « Clip » au menu d'action de la note"
+reactionsDisplaySize: "Taille de l'affichage des réactions"
+noteIdOrUrl: "Identifiant de la note ou URL"
video: "Vidéo"
videos: "Vidéos"
dataSaver: "Économiseur de données"
accountMigration: "Migration de compte"
accountMoved: "Cet·te utilisateur·rice a migré son compte vers :"
+accountMovedShort: "Ce compte a migré"
+operationForbidden: "Opération non autorisée"
+forceShowAds: "Toujours afficher les publicités"
addMemo: "Ajouter un mémo"
+reactionsList: "Réactions"
+renotesList: "Liste de renotes"
notificationDisplay: "Style des notifications"
leftTop: "En haut à gauche"
rightTop: "En haut à droite"
leftBottom: "En bas à gauche"
rightBottom: "En bas à droite"
+stackAxis: "Direction d'empilement"
vertical: "Vertical"
horizontal: "Latéral"
+position: "Position"
serverRules: "Règles du serveur"
+pleaseAgreeAllToContinue: "Pour continuer, veuillez accepter tous les champs ci-dessus."
+continue: "Continuer"
+preservedUsernames: "Noms d'utilisateur·rice réservés"
+archive: "Archive"
+thisChannelArchived: "Ce canal a été archivé."
+displayOfNote: "Affichage de la note"
+initialAccountSetting: "Configuration initiale du profil"
youFollowing: "Abonné·e"
+preventAiLearning: "Refuser l'usage dans l'apprentissage automatique d'IA générative"
+preventAiLearningDescription: "Demander aux robots d'indexation de ne pas utiliser le contenu publié, tel que les notes et les images, dans l'apprentissage automatique d'IA générative. Cela est réalisé en incluant le drapeau « noai » dans la réponse HTML. Une prévention complète n'est toutefois pas possible, car il est au robot d'indexation de respecter cette demande."
+options: "Options"
+specifyUser: "Spécifier l'utilisateur·rice"
+failedToPreviewUrl: "Aperçu d'URL échoué"
+update: "Mettre à jour"
+later: "Plus tard"
+goToMisskey: "Retour vers Misskey"
+installed: "Installé"
+expirationDate: "Date d’expiration"
+waitingForMailAuth: "En attente de la vérification de l'adresse courriel"
+usedAt: "Utilisé le"
+unused: "Non-utilisé"
+used: "Utilisé"
+expired: "Expiré"
+doYouAgree: "Êtes-vous d’accord ?"
+beSureToReadThisAsItIsImportant: "Assurez-vous de le lire ; c'est important."
+dialog: "Dialogue"
+icon: "Avatar"
+forYou: "Pour vous"
+currentAnnouncements: "Annonces actuelles"
+pastAnnouncements: "Annonces passées"
+replies: "Répondre"
+renotes: "Renoter"
+loadReplies: "Inclure les réponses"
+loadConversation: "Afficher la conversation"
+pinnedList: "Liste épinglée"
+notifyNotes: "Notifier à propos des nouvelles notes"
+authentication: "Authentification"
+authenticationRequiredToContinue: "Veuillez vous authentifier pour continuer"
+dateAndTime: "Date et heure"
+showRenotes: "Afficher les renotes"
+edited: "Modifié"
+notificationRecieveConfig: "Paramètres des notifications"
+mutualFollow: "Abonnement mutuel"
+showRepliesToOthersInTimeline: "Afficher les réponses aux autres dans le fil"
+hideRepliesToOthersInTimeline: "Masquer les réponses aux autres dans le fil"
+showRepliesToOthersInTimelineAll: "Afficher les réponses de toutes les personnes que vous suivez dans le fil"
+hideRepliesToOthersInTimelineAll: "Masquer les réponses de toutes les personnes que vous suivez dans le fil"
+confirmShowRepliesAll: "Cette opération est irréversible. Voulez-vous vraiment afficher les réponses de toutes les personnes que vous suivez dans le fil ?"
+confirmHideRepliesAll: "Cette opération est irréversible. Voulez-vous vraiment masquer les réponses de toutes les personnes que vous suivez dans le fil ?"
+externalServices: "Services externes"
+impressum: "Impressum"
+impressumUrl: "URL de l'impressum"
+impressumDescription: "Dans certains pays comme l'Allemagne, il est obligatoire d'afficher les informations sur l'opérateur d'un site (un impressum)."
+privacyPolicy: "Politique de confidentialité"
+privacyPolicyUrl: "URL de la politique de confidentialité"
+tosAndPrivacyPolicy: "Conditions d'utilisation et politique de confidentialité"
+avatarDecorations: "Décorations d'avatar"
+attach: "Mettre"
+detach: "Enlever"
+angle: "Angle"
+flip: "Inverser"
+showAvatarDecorations: "Afficher les décorations d'avatar"
+releaseToRefresh: "Relâcher pour rafraîchir"
+refreshing: "Rafraîchissement..."
+pullDownToRefresh: "Tirer vers le bas pour rafraîchir"
+disableStreamingTimeline: "Désactiver les mises à jour en temps réel de la ligne du temps"
+useGroupedNotifications: "Grouper les notifications"
+signupPendingError: "Un problème est survenu lors de la vérification de votre adresse e-mail. Le lien a peut-être expiré."
+cwNotationRequired: "Si « Masquer le contenu » est activé, une description doit être fournie."
+doReaction: "Réagir"
+_announcement:
+ readConfirmTitle: "Marquer comme lu ?"
+ shouldNotBeUsedToPresentPermanentInfo: "Puisque cela pourrait nuire considérablement à l'expérience utilisateur pour les nouveaux utilisateurs, il est recommandé d'utiliser les annonces pour afficher des informations temporaires plutôt que des informations persistantes."
+ dialogAnnouncementUxWarn: "Avoir deux ou plus annonces de style dialogue en même temps pourrait nuire considérablement à l'expérience utilisateur. Veuillez les utiliser avec caution."
+ silence: "Ne pas me notifier"
+ silenceDescription: "Si activée, vous ne recevrez pas de notifications sur les annonces et n'aurez pas besoin de les marquer comme lues."
+_initialAccountSetting:
+ profileSetting: "Paramètres du profil"
+ privacySetting: "Paramètres de confidentialité"
+ initialAccountSettingCompleted: "Configuration du profil terminée avec succès !"
+ youCanContinueTutorial: "Vous pouvez procéder au tutoriel sur l'utilisation de {nom}(Misskey) ou vous arrêter ici et commencer à l'utiliser immédiatement."
+ startTutorial: "Démarrer le tutoriel"
+ skipAreYouSure: "Désirez-vous ignorer la configuration du profil ?"
+_initialTutorial:
+ launchTutorial: "Visionner le tutoriel"
+ title: "Tutoriel"
+ wellDone: "Bien joué !"
+ skipAreYouSure: "Quitter le tutoriel ?"
+ _landing:
+ title: "Bienvenue dans le tutoriel"
+ description: "Ici, vous pouvez apprendre l'utilisation de base de Misskey et ses fonctionnalités."
+ _note:
+ title: "Qu'est-ce que les notes ?"
+ description: "Les messages sur Misskey sont appelés des « notes » . Les notes sont classées par ordre chronologique sur le fil et sont mises à jour en temps réel."
+ reply: "Vous pouvez répondre aux messages. Vous pouvez également répondre aux réponses et poursuivre la conversation comme un fil de discussion."
+ renote: "Vous pouvez partager cette note sur votre propre fil. Vous pouvez aussi ajouter du texte en citant."
+ reaction: "Vous pouvez ajouter des réactions. Les détails sont expliqués à la page suivante."
+ menu: "Vous pouvez afficher les détails de la note, copier le lien et effectuer d'autres actions."
+ _reaction:
+ title: "Qu'est-ce que les réactions ?"
+ description: "Vous pouvez ajouter des « réactions » aux notes. Les réactions vous permettent d'exprimer à l'aise des nuances qui ne peuvent pas être exprimées par des mentions j'aime."
+ letsTryReacting: "Des réactions peuvent être ajoutées en cliquant sur le bouton « + » de la note. Essayez d'ajouter une réaction à cet exemple de note !"
+ reactToContinue: "Ajoutez une réaction pour procéder."
+ reactNotification: "Vous recevez des notifications en temps réel lorsque quelqu'un réagit à votre note."
+ reactDone: "Vous pouvez annuler la réaction en cliquant sur le bouton « - » ."
+ _timeline:
+ title: "Fonctionnement des fils"
+ description1: "Misskey offre plusieurs fils selon l'usage (certains peuvent être désactivés par le serveur)."
+ home: "Vous pouvez voir les notes des utilisateurs auxquels vous êtes abonné·e."
+ local: "Vous pouvez voir les notes de tous les utilisateurs sur cette instance."
+ social: "Les notes des fils principal et local sont affichées."
+ global: "Vous pouvez voir les notes de toutes les instances connectées."
+ description2: "Vous pouvez passer d'un fil à l'autre en haut de l'écran à tout moment."
+ description3: "De plus, il y a les fils des listes et des canaux. Pour plus de détails, consultez {link}."
+ _postNote:
+ title: "Paramètres de la publication de note"
+ description1: "Lorsque vous publiez des notes sur Misskey, diverses options sont disponibles. Voici le formulaire de publication."
+ _visibility:
+ description: "Vous pouvez choisir qui peut voir vos notes."
+ public: "Visible à tous les utilisateurs."
+ home: "Uniquement visible sur le fil principal. Les utilisateurs pourront la voir en visitant ton profil, en s'abonnant à vous et par les renotes."
+ followers: "Uniquement visible à vos abonnés. Elle ne pourra être renotée que par vous-même."
+ direct: "Uniquement visible aux utilisateurs de votre choix. Les récipients seront notifiés. Cette option peut être utilisée comme alternative aux messages directs."
+ doNotSendConfidencialOnDirect1: "Faites attention quand vous envoyez vos informations sensibles !"
+ doNotSendConfidencialOnDirect2: "Les administrateurs de l'instance destinataire peuvent voir toutes les notes publiées. Soyez prudent·e avec vos informations sensibles quand vous envoyez des notes directes aux utilisateurs dont vous ne vous fiez pas aux instances."
+ localOnly: "Désactiver la fédération de la note aux autres instances. Les utilisateurs des autres instances ne pourront pas voir directement la note quelle que soit l'étendue de la publication mentionnée ci-dessus."
+ _cw:
+ title: "Masquer le contenu (CW)"
+ description: "Au lieu du corps du texte, le contenu du champ « commentaires » s'affichera. Appuyez sur « afficher le contenu » pour voir le corps du texte."
+ _exampleNote:
+ cw: "Attention : cela vous donnera faim !"
+ note: "J'ai mangé un beignet enrobé de chocolat 🍩😋"
+ useCases: "Utilisé pour désigner certaines notes selon les règles du serveur ou pour cacher des spoilers ou des textes sensibles."
+ _howToMakeAttachmentsSensitive:
+ title: "Comment marquer un fichier joint comme sensible ?"
+ description: "Attachez un drapeau « sensible » aux fichiers joints selon les règles du serveur ou si vous ne voulez pas que le fichier soit vu directement."
+ tryThisFile: "Essayez de marquer l'image jointe à ce formulaire de publication comme sensible !"
+ _exampleNote:
+ note: "Oups, j'ai échoué à ouvrir le couvercle du natto..."
+ method: "Pour marquer un fichier joint comme sensible, cliquez sur la vignette du fichier, ouvrez le menu et cliquez sur « marquer comme sensible » ."
+ sensitiveSucceeded: "Quand vous joignez des fichiers, veuillez indiquer la sensibilité selon les règles du serveur."
+ doItToContinue: "Marquez le fichier joint comme sensible pour procéder."
+ _done:
+ title: "Le tutoriel est terminé ! 🎉"
+ description: "Les fonctionnalités introduites ici ne sont que quelques-unes. Pour savoir plus sur l'utilisation de Misskey, veuillez consulter {lien}."
+_timelineDescription:
+ home: "Sur le fil principal, vous pouvez voir les notes des utilisateurs auxquels vous êtes abonné·e."
+ local: "Sur le fil local, vous pouvez voir les notes de tous les utilisateurs sur cette instance."
+ social: "Sur le fil social, les notes des fils principal et local sont affichées."
+ global: "Sur le fil global, vous pouvez voir les notes de toutes les instances connectées."
+_serverSettings:
+ iconUrl: "URL de l’icône"
+ appIconResolutionMustBe: "La résolution doit être au moins {resolution}."
+ shortName: "Nom court"
+ shortNameDescription: "Si le nom officiel de l'instance est long, cette abréviation peut être affichée à la place."
+ fanoutTimelineDescription: "Si activée, la performance de la récupération de la chronologie augmentera considérablement et la charge sur la base de données sera réduite. En revanche, l'utilisation de la mémoire de Redis augmentera. Considérez désactiver cette option si le serveur est bas en mémoire ou instable."
+ fanoutTimelineDbFallback: "Recours à la base de données"
+ fanoutTimelineDbFallbackDescription: "Si activée, une demande supplémentaire à la base de données est effectuée comme solution de rechange quand le fil n'est pas mis en cache. Si désactivée, la demande à la base de données n'est pas effectuée, ce qui réduit davantage la charge du serveur mais limite l'étendue du fil récupérable."
+_accountMigration:
+ moveFrom: "Migrer un autre compte vers le présent compte"
+ moveFromSub: "Créer un alias vers un autre compte"
+ moveToLabel: "Compte vers lequel vous migrez :"
+ startMigration: "Migrer"
+ movedTo: "Compte vers lequel vous migrez :"
_achievements:
_types:
_notes1:
+ title: "Je viens tout juste de configurer mon msky"
description: "Publiez votre première note"
flavor: "Passez un bon moment avec Misskey !"
+ _notes10:
+ title: "Quelques notes"
_notes100:
title: "Beaucoup de notes"
_notes100000:
@@ -977,16 +1207,23 @@ _achievements:
title: "Débutant Ⅲ"
description: "Se connecter pour un total de 15 jours"
_login30:
+ title: "Misskeynaute I"
description: "Se connecter pour un total de 30 jours"
_login60:
+ title: "Misskeynaute II"
description: "Se connecter pour un total de 60 jours"
_login100:
+ title: "Misskeynaute III"
description: "Se connecter pour un total de 100 jours"
+ flavor: "Misskeynaute acharné·e"
_login200:
+ title: "Régulier I"
description: "Se connecter pour un total de 200 jours"
_login300:
+ title: "Régulier II"
description: "Se connecter pour un total de 300 jours"
_login400:
+ title: "Régulier III"
description: "Se connecter pour un total de 400 jours"
_login500:
description: "Se connecter pour un total de 500 jours"
@@ -1000,25 +1237,102 @@ _achievements:
description: "Se connecter pour un total de 900 jours"
_login1000:
flavor: "Merci d'utiliser Misskey !"
+ _profileFilled:
+ title: "Bien préparé"
+ description: "Configuration de votre profil"
_markedAsCat:
title: "Je suis un chat"
+ description: "Rendre votre compte comme un chat"
flavor: "Je n'ai pas encore de nom"
+ _following1:
+ title: "Vous suivez votre premier·ère utilisateur·rice"
+ _following10:
+ description: "S'abonner à plus de 10 utilisateur·rice·s"
_following50:
title: "Beaucoup d'amis"
+ description: "S'abonner à plus de 50 utilisateur·rice·s"
+ _following100:
+ description: "S'abonner à plus de 100 utilisateur·rice·s"
+ _following300:
+ description: "S'abonner à plus de 300 utilisateur·rice·s"
_followers10:
title: "Abonnez-moi !"
+ description: "Obtenir plus de 10 abonné·e·s"
+ _followers50:
+ description: "Obtenir plus de 50 abonné·e·s"
+ _followers100:
+ title: "Populaire"
+ description: "Obtenir plus de 100 abonné·e·s"
+ _followers300:
+ description: "Obtenir plus de 300 abonné·e·s"
+ _followers500:
+ title: "Tour radio"
+ description: "Obtenir plus de 500 abonné·e·s"
+ _followers1000:
+ title: "Influenceur·euse"
+ description: "Obtenir plus de 1000 abonné·e·s"
_iLoveMisskey:
title: "J’adore Misskey"
+ description: "Publication « J’❤ #Misskey »"
+ flavor: "L'équipe de développement de Misskey apprécie vraiment votre aide !"
+ _foundTreasure:
+ title: "Chasse au trésor"
+ description: "Vous avez trouvé le trésor caché"
+ _client30min:
+ title: "Pause bien méritée"
+ _postedAtLateNight:
+ flavor: "C’est l’heure d’aller au lit."
+ _postedAt0min0sec:
+ title: "Horloge parlante"
+ description: "Publication d’une note à 00:00"
+ flavor: "Tic tac, tic tac, tic tac, ding !"
_viewInstanceChart:
title: "Analyste"
+ _outputHelloWorldOnScratchpad:
+ title: "Hello, world!"
+ _open3windows:
+ title: "Multi-fenêtres"
+ _driveFolderCircularReference:
+ title: "Référence circulaire"
+ _setNameToSyuilo:
+ description: "Vous avez spécifié « syuilo » comme nom"
+ _passedSinceAccountCreated1:
+ title: "Premier anniversaire"
+ _passedSinceAccountCreated2:
+ title: "Second anniversaire"
+ _passedSinceAccountCreated3:
+ title: "3ème anniversaire"
_loggedInOnBirthday:
title: "Joyeux Anniversaire !"
+ description: "Vous vous êtes connecté à la date de votre anniversaire"
_loggedInOnNewYearsDay:
title: "Bonne année !"
_cookieClicked:
flavor: "Attendez une minute, vous êtes sur le mauvais site web ?"
+ _brainDiver:
+ flavor: "Misskey-Misskey La-Tu-Ma"
+ _smashTestNotificationButton:
+ title: "Débordement de tests"
+ description: "Détruire le bouton de test de notifications dans un intervalle extrêmement court"
+ _tutorialCompleted:
+ title: "Diplôme de la course élémentaire de Misskey"
+ description: "Terminer le tutoriel"
_role:
+ new: "Nouveau rôle"
+ edit: "Modifier le rôle"
+ name: "Nom du rôle"
+ description: "Description du rôle"
+ permission: "Rôle et autorisations"
assignTarget: "Attribuer"
+ condition: "Condition"
+ isPublic: "Rôle public"
+ options: "Options"
+ policies: "Stratégies"
+ baseRole: "Modèle de rôle"
+ useBaseValue: "Utiliser la valeur du modèle de rôle"
+ chooseRoleToAssign: "Sélectionner le rôle à assigner"
+ iconUrl: "URL de l’icône"
+ displayOrder: "Classement"
priority: "Priorité"
_priority:
low: "Basse"
@@ -1026,6 +1340,9 @@ _role:
high: "Haute"
_options:
canManageCustomEmojis: "Gestion des émojis personnalisés"
+ canManageAvatarDecorations: "Gestion des décorations d'avatar"
+ wordMuteMax: "Nombre maximal de caractères dans le filtre de mots"
+ canUseTranslator: "Usage de la fonctionnalité de traduction"
_sensitiveMediaDetection:
description: "L'apprentissage automatique peut être utilisé pour détecter automatiquement les médias sensibles à modérer. La sollicitation des serveurs augmente légèrement."
sensitivity: "Sensibilité de la détection"
@@ -1059,6 +1376,10 @@ _ad:
back: "Retour"
reduceFrequencyOfThisAd: "Voir cette publicité moins souvent"
hide: "Cacher "
+ adsSettings: "Paramètres des publicités"
+ notesPerOneAd: "Intervalle de diffusion de publicités lors de la mise à jour en temps réel (nombre de notes par publicité)"
+ setZeroToDisable: "Mettre cette valeur à 0 pour désactiver la diffusion de publicités lors de la mise à jour en temps réel"
+ adsTooClose: "L'expérience utilisateur peut être gravement compromise par un intervalle de diffusion de publicités extrêmement court."
_forgotPassword:
enterEmail: "Entrez ici l'adresse e-mail que vous avez enregistrée pour votre compte. Un lien vous permettant de réinitialiser votre mot de passe sera envoyé à cette adresse."
ifNoEmail: "Si vous n'avez pas enregistré d'adresse e-mail, merci de contacter l'administrateur·rice de votre instance."
@@ -1074,9 +1395,10 @@ _email:
_receiveFollowRequest:
title: "Vous avez reçu une demande de suivi"
_plugin:
- install: "Installation de plugin"
+ install: "Installation d'extensions"
installWarn: "N’installez que des extensions provenant de sources de confiance."
- manage: "Gestion des plugins"
+ manage: "Gestion des extensions"
+ viewSource: "Afficher la source"
_preferencesBackups:
list: "Sauvegardes créées"
saveNew: "Nouvelle sauvegarde"
@@ -1110,6 +1432,9 @@ _aboutMisskey:
donate: "Soutenir Misskey"
morePatrons: "Nous apprécions vraiment le soutien de nombreuses autres personnes non mentionnées ici. Merci à toutes et à tous ! 🥰"
patrons: "Contributeurs"
+ projectMembers: "Membres du projet"
+_displayOfSensitiveMedia:
+ force: "Masquer tous les médias"
_instanceTicker:
none: "Cacher "
remote: "Montrer pour les utilisateur·ice·s distant·e·s"
@@ -1128,6 +1453,9 @@ _channel:
following: "Abonné·e"
usersCount: "{n} Participant·e·s"
notesCount: "{n} Notes"
+ nameAndDescription: "Nom et description"
+ nameOnly: "Nom seulement"
+ allowRenoteToExternal: "Permettre la renote et la citation hors du canal"
_menuDisplay:
sideFull: "Latéral"
sideIcon: "Latéral (icônes)"
@@ -1137,11 +1465,6 @@ _wordMute:
muteWords: "Mots à filtrer"
muteWordsDescription: "Séparer avec des espaces pour la condition AND. Séparer avec un saut de ligne pour une condition OR."
muteWordsDescription2: "Pour utiliser des expressions régulières (regex), mettez les mots-clés entre barres obliques."
- softDescription: "Masquez les notes de votre fil selon les paramètres que vous définissez."
- hardDescription: "Empêchez votre fil de charger les notes selon les paramètres que vous définissez. Cette action est irréversible : si vous modifiez ces paramètres plus tard, les notes précédemment filtrées ne seront pas récupérées."
- soft: "Doux"
- hard: "Strict"
- mutedNotes: "Notes filtrées"
_instanceMute:
instanceMuteDescription: "Met en sourdine toutes les notes et renotes de l'instance configurée, y compris les réponses aux utilisateurs de l'instance muette."
instanceMuteDescription2: "Séparer avec de nouvelles lignes"
@@ -1205,9 +1528,6 @@ _theme:
infoFg: "Texte d'information"
infoWarnBg: "Arrière-plan des avertissements"
infoWarnFg: "Texte d’avertissement"
- cwBg: "Arrière-plan du CW"
- cwFg: "Texte du bouton CW"
- cwHoverBg: "Arrière-plan du bouton CW (survolé)"
toastBg: "Arrière-plan de la bulle de notification"
toastFg: "Texte de la bulle de notification"
buttonBg: "Arrière-plan du bouton"
@@ -1225,8 +1545,6 @@ _sfx:
note: "Nouvelle note"
noteMy: "Ma note"
notification: "Notifications"
- chat: "Discuter"
- chatBg: "Discussion (arrière-plan)"
antenna: "Réception de l’antenne"
channel: "Notifications de canal"
_ago:
@@ -1249,12 +1567,18 @@ _2fa:
alreadyRegistered: "Configuration déjà achevée."
step1: "Tout d'abord, installez une application d'authentification, telle que {a} ou {b}, sur votre appareil."
step2: "Ensuite, scannez le code QR affiché sur l’écran."
- step2Url: "Vous pouvez également saisir cette URL si vous utilisez un programme de bureau :"
+ step3Title: "Veuillez saisir le code d’authentification"
step3: "Entrez le jeton affiché sur votre application pour compléter la configuration."
- step4: "À partir de maintenant, ce même jeton vous sera demandé à chacune de vos connexions.\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}"
+ setupCompleted: "Configuration terminée avec succès !"
+ step4: "À partir de maintenant, ce même jeton vous sera demandé à chacune de vos connexions."
+ securityKeyNotSupported: "Votre navigateur ne prend pas en charge les clés de sécurité."
securityKeyInfo: "Vous pouvez configurer l'authentification WebAuthN pour sécuriser davantage le processus de connexion grâce à une clé de sécurité matérielle qui prend en charge FIDO2, ou bien en configurant l'authentification par empreinte digitale ou par code PIN sur votre appareil."
+ securityKeyName: "Nom de la clé"
+ removeKey: "Supprimer la clé de sécurité"
removeKeyConfirm: "Voulez-vous supprimer {name} ?"
+ renewTOTPOk: "Reconfigurer"
renewTOTPCancel: "Pas maintenant"
+ backupCodes: "Codes de Secours"
_permissions:
"read:account": "Afficher les informations du compte"
"write:account": "Mettre à jour les informations de votre compte"
@@ -1337,7 +1661,7 @@ _widgets:
chooseList: "Sélectionner une liste"
_cw:
hide: "Masquer"
- show: "Afficher plus …"
+ show: "Afficher le contenu"
chars: "{count} caractères"
files: "{count} fichiers"
_poll:
@@ -1374,7 +1698,7 @@ _visibility:
_postForm:
replyPlaceholder: "Répondre à cette note ..."
quotePlaceholder: "Citez cette note ..."
- channelPlaceholder: "Publier vers le canal"
+ channelPlaceholder: "Publier au canal…"
_placeholders:
a: "Quoi de neuf ?"
b: "Il s'est passé quelque chose ?"
@@ -1402,6 +1726,7 @@ _exportOrImport:
userLists: "Listes"
excludeMutingUsers: "Exclure les utilisateur·rice·s mis en sourdine"
excludeInactiveUsers: "Exclure les utilisateur·rice·s inactifs"
+ withReplies: "Inclure les réponses des utilisateur·rice·s importé·e·s dans le fil"
_charts:
federation: "Fédération"
apRequest: "Requêtes"
@@ -1471,7 +1796,7 @@ _pages:
fontSerif: "Serif"
fontSansSerif: "Sans Serif"
eyeCatchingImageSet: "Définir une image attractive"
- eyeCatchingImageRemove: "Supprimer l'image attractive"
+ eyeCatchingImageRemove: "Supprimer la miniature"
chooseBlock: "Ajouter un bloc"
selectType: "Choisir un type"
contentBlocks: "Contenu"
@@ -1498,12 +1823,17 @@ _notification:
youGotReply: "Réponse de {name}"
youGotQuote: "Cité·e par {name}"
youRenoted: "{name} vous a Renoté"
- youWereFollowed: "Vous suit"
+ youWereFollowed: "s'est abonné·e à vous"
youReceivedFollowRequest: "Vous avez reçu une demande d’abonnement"
yourFollowRequestAccepted: "Votre demande d’abonnement a été accepté"
pollEnded: "Les résultats du sondage sont disponibles"
unreadAntennaNote: "Antenne {name}"
emptyPushNotificationMessage: "Les notifications push ont été mises à jour"
+ achievementEarned: "Accomplissement"
+ testNotification: "Tester la notification"
+ reactedBySomeUsers: "{n} utilisateur·rice·s ont réagi"
+ renotedBySomeUsers: "{n} utilisateur·rice·s ont renoté"
+ followedBySomeUsers: "{n} utilisateur·rice·s se sont abonné·e·s à vous"
_types:
all: "Toutes"
follow: "Nouvel·le abonné·e"
@@ -1515,6 +1845,7 @@ _notification:
pollEnded: "Sondages se cloturant"
receiveFollowRequest: "Demande d'abonnement reçue"
followRequestAccepted: "Demande d'abonnement acceptée"
+ achievementEarned: "Accomplissement"
app: "Notifications provenant des apps"
_actions:
followBack: "Suivre"
@@ -1536,6 +1867,7 @@ _deck:
deleteProfile: "Supprimer le profil"
introduction: "Créez l’interface parfaite qui vous sied en arrangeant librement les colonnes !"
introduction2: "Cliquez sur le + à droite de l'écran pour ajouter de nouvelles colonnes quand vous le souhaitez."
+ flexible: "Ajuster automatiquement la largeur"
_columns:
main: "Principale"
widgets: "Widgets"
@@ -1543,9 +1875,92 @@ _deck:
tl: "Fil"
antenna: "Antennes"
list: "Listes"
- channel: "Canaux"
+ channel: "Canal"
mentions: "Mentions"
direct: "Direct"
_webhookSettings:
name: "Nom"
active: "Activé"
+_moderationLogTypes:
+ createRole: "Rôle créé"
+ deleteRole: "Rôle supprimé"
+ updateRole: "Rôle mis à jour"
+ assignRole: "Rôle attribué"
+ unassignRole: "Rôle enlevé"
+ suspend: "Utilisateur suspendu"
+ unsuspend: "Suspension d'un utilisateur levée"
+ addCustomEmoji: "Émoji personnalisé ajouté"
+ updateCustomEmoji: "Émoji personnalisé mis à jour"
+ deleteCustomEmoji: "Émoji personnalisé supprimé"
+ updateServerSettings: "Paramètres du serveur mis à jour"
+ updateUserNote: "Note de modération mise à jour"
+ deleteDriveFile: "Fichier supprimé"
+ deleteNote: "Note supprimée"
+ createGlobalAnnouncement: "Annonce globale créée"
+ createUserAnnouncement: "Annonce individuelle créée"
+ updateGlobalAnnouncement: "Annonce globale mise à jour"
+ updateUserAnnouncement: "Annonce individuelle mise à jour"
+ deleteGlobalAnnouncement: "Annonce globale supprimée"
+ deleteUserAnnouncement: "Annonce individuelle supprimée"
+ resetPassword: "Mot de passe réinitialisé"
+ suspendRemoteInstance: "Instance distante suspendue"
+ unsuspendRemoteInstance: "Suspension d'une instance distante levée"
+ markSensitiveDriveFile: "Fichier marqué comme sensible"
+ unmarkSensitiveDriveFile: "Marquage du fichier comme sensible enlevé"
+ resolveAbuseReport: "Signalement résolu"
+ createInvitation: "Code d'invitation créé"
+ createAd: "Publicité créée"
+ deleteAd: "Publicité supprimée"
+ updateAd: "Publicité mise à jour"
+ createAvatarDecoration: "Décoration d'avatar créée"
+ updateAvatarDecoration: "Décoration d'avatar mise à jour"
+ deleteAvatarDecoration: "Décoration d'avatar supprimée"
+_fileViewer:
+ title: "Détails du fichier"
+ type: "Type du fichier"
+ size: "Taille du fichier"
+ url: "URL"
+ uploadedAt: "Date de téléversement"
+ attachedNotes: "Notes avec ce fichier"
+ thisPageCanBeSeenFromTheAuthor: "Cette page ne peut être vue que par l'utilisateur qui a téléversé ce fichier."
+_externalResourceInstaller:
+ title: "Installer depuis un site externe"
+ checkVendorBeforeInstall: "Veuillez confirmer que le distributeur est fiable avant l'installation."
+ _plugin:
+ title: "Voulez-vous installer cette extension ?"
+ metaTitle: "Informations sur l'extension"
+ _theme:
+ title: "Voulez-vous installer ce thème ?"
+ metaTitle: "Informations sur le thème"
+ _meta:
+ base: "Palette de couleurs de base"
+ _vendorInfo:
+ title: "Informations sur le distributeur"
+ endpoint: "Point de terminaison référencé"
+ hashVerify: "Vérification de l'intégrité du fichier"
+ _errors:
+ _invalidParams:
+ title: "Paramètres invalides"
+ description: "Il y a un manque d'informations nécessaires pour obtenir des données à partir de sites externes. Veuillez vérifier l'URL."
+ _resourceTypeNotSupported:
+ title: "Cette ressource externe n'est pas prise en charge."
+ description: "Le type de ressource obtenue à partir de ce site externe n'est pas pris en charge. Veuillez contacter l'administrateur du site."
+ _failedToFetch:
+ title: "Échec de récupération des données"
+ fetchErrorDescription: "La communication avec le site externe a échoué. Si vous réessayez et que cela ne s'améliore pas, veuillez contacter l'administrateur du site."
+ parseErrorDescription: "Les données obtenues à partir du site externe n'ont pas pu être parsées. Veuillez contacter l'administrateur du site."
+ _hashUnmatched:
+ title: "Échec de vérification des données"
+ description: "La vérification de l'intégrité des données fournies a échoué. Pour des raisons de sécurité, l'installation ne peut pas continuer. Veuillez contacter l'administrateur du site."
+ _pluginParseFailed:
+ title: "Erreur d'AiScript"
+ description: "Bien que les données aient été obtenues, elles n'ont pas pu être lues, car il y a eu une erreur lors du parsage d'AiScript. Veuillez contacter l'auteur de l'extension. Pour plus de détails sur l'erreur, veuillez consulter la console JavaScript."
+ _pluginInstallFailed:
+ title: "Échec d'installation de l'extension"
+ description: "Il y a eu un problème lors de l'installation de l'extension. Veuillez réessayer. Pour plus de détails sur l'erreur, veuillez consulter la console JavaScript."
+ _themeParseFailed:
+ title: "Erreur de parsage du thème"
+ description: "Bien que les données aient été obtenues, elles n'ont pas pu être lues, car il y a eu une erreur lors du parsage du fichier du thème. Veuillez contacter l'auteur du thème. Pour plus de détails sur l'erreur, veuillez consulter la console JavaScript."
+ _themeInstallFailed:
+ title: "Échec d'installation du thème"
+ description: "Il y a eu un problème lors de l'installation du thème. Veuillez réessayer. Pour plus de détails sur l'erreur, veuillez consulter la console JavaScript."
diff --git a/locales/generateDTS.js b/locales/generateDTS.js
index 7369dfbb47..7af773f3b1 100644
--- a/locales/generateDTS.js
+++ b/locales/generateDTS.js
@@ -1,6 +1,11 @@
import * as fs from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { dirname } from 'node:path';
import * as yaml from 'js-yaml';
-import * as ts from 'typescript';
+import ts from 'typescript';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
function createMembers(record) {
return Object.entries(record)
diff --git a/locales/hr-HR.yml b/locales/hr-HR.yml
index ed97d539c0..9cfebdd01a 100644
--- a/locales/hr-HR.yml
+++ b/locales/hr-HR.yml
@@ -1 +1,5 @@
---
+_lang_: "japanski"
+ok: "OK"
+gotIt: "Razumijem"
+cancel: "otkazati"
diff --git a/locales/ht-HT.yml b/locales/ht-HT.yml
index ed97d539c0..e3595c79b6 100644
--- a/locales/ht-HT.yml
+++ b/locales/ht-HT.yml
@@ -1 +1,18 @@
---
+_lang_: "Japonè"
+password: "modpas"
+ok: "OK"
+gotIt: "Konprann"
+cancel: "anile"
+noThankYou: "Sispann"
+instance: "sèvè"
+profile: "pwofil"
+save: "kenbe"
+delete: "efase"
+instances: "sèvè"
+remove: "efase"
+smtpPass: "modpas"
+_2fa:
+ renewTOTPCancel: "Sispann"
+_widgets:
+ profile: "pwofil"
diff --git a/locales/hu-HU.yml b/locales/hu-HU.yml
index fdab9645c1..023a91494d 100644
--- a/locales/hu-HU.yml
+++ b/locales/hu-HU.yml
@@ -77,6 +77,7 @@ smtpUser: "Felhasználónév"
smtpPass: "Jelszó"
user: "Felhasználók"
searchByGoogle: "Keresés"
+renotes: "Renote"
_theme:
keys:
renote: "Renote"
diff --git a/locales/id-ID.yml b/locales/id-ID.yml
index c72538a1a3..041c55cc2d 100644
--- a/locales/id-ID.yml
+++ b/locales/id-ID.yml
@@ -45,6 +45,7 @@ pin: "Sematkan ke profil"
unpin: "Lepas sematan dari profil"
copyContent: "Salin konten"
copyLink: "Salin tautan"
+copyLinkRenote: "Salin tautan renote"
delete: "Hapus"
deleteAndEdit: "Hapus dan sunting"
deleteAndEditConfirm: "Apakah kamu yakin ingin menghapus note ini dan menyuntingnya? Kamu akan kehilangan semua reaksi, renote dan balasan di note ini."
@@ -133,12 +134,12 @@ renoteMute: "Matikan renote"
renoteUnmute: "Batal mematikan renote"
block: "Blokir"
unblock: "Buka blokir"
-suspend: "Bekukan"
-unsuspend: "Buka pembekuan"
+suspend: "Tangguhkan"
+unsuspend: "Batalkan penangguhan"
blockConfirm: "Apakah kamu yakin ingin memblokir akun ini?"
unblockConfirm: "Apakah kamu yakin ingin membuka blokir akun ini?"
-suspendConfirm: "Apakah kamu yakin ingin membekukan akun ini?"
-unsuspendConfirm: "Apakah kamu yakin ingin membuka pembekuan akun ini?"
+suspendConfirm: "Apakah kamu yakin ingin menangguhkan akun ini?"
+unsuspendConfirm: "Apakah kamu yakin ingin membatalkan penangguhan akun ini?"
selectList: "Pilih daftar"
editList: "Sunting daftar"
selectChannel: "Pilih kanal"
@@ -156,6 +157,9 @@ addEmoji: "Tambahkan emoji"
settingGuide: "Pengaturan rekomendasi"
cacheRemoteFiles: "Tembolokkan berkas dari instansi luar"
cacheRemoteFilesDescription: "Ketika pengaturan ini dinonaktifkan, berkas dari instansi luar akan dimuat langsung. Menonaktifkan ini akan mengurangi penggunaan penyimpanan peladen, namun dapat menyebabkan peningkatan lalu lintas bandwidth, karena keluku tidak dihasilkan."
+youCanCleanRemoteFilesCache: "Kamu dapat mengosongkan tembolok dengan mengeklik tombol 🗑️ pada layar manajemen berkas."
+cacheRemoteSensitiveFiles: "Tembolokkan berkas dari instansi luar"
+cacheRemoteSensitiveFilesDescription: "Menonaktifkan pengaturan ini menyebabkan berkas sensitif dari instansi luar ditautkan secara langsung, bukan ditembolok."
flagAsBot: "Atur akun ini sebagai Bot"
flagAsBotDescription: "Jika akun ini dikendalikan oleh program, tetapkanlah opsi ini. Jika diaktifkan, ini akan berfungsi sebagai tanda bagi pengembang lain untuk mencegah interaksi berantai dengan bot lain dan menyesuaikan sistem internal Misskey untuk memperlakukan akun ini sebagai bot."
flagAsCat: "Atur akun ini sebagai kucing"
@@ -191,6 +195,7 @@ perHour: "per Jam"
perDay: "per Hari"
stopActivityDelivery: "Berhenti mengirim aktivitas"
blockThisInstance: "Blokir instansi ini"
+silenceThisInstance: "Senyapkan instansi ini"
operations: "Tindakan"
software: "Perangkat lunak"
version: "Versi"
@@ -210,6 +215,8 @@ clearCachedFiles: "Hapus tembolok"
clearCachedFilesConfirm: "Apakah kamu yakin ingin menghapus seluruh tembolok berkas instansi luar?"
blockedInstances: "Instansi terblokir"
blockedInstancesDescription: "Daftar nama host dari instansi yang diperlukan untuk diblokir. Instansi yang didaftarkan tidak akan dapat berkomunikasi dengan instansi ini."
+silencedInstances: "Instansi yang disenyapkan"
+silencedInstancesDescription: "Daftar nama host dari instansi yang ingin kamu senyapkan. Semua akun dari instansi yang terdaftar akan diperlakukan sebagai disenyapkan. Hal ini membuat akun hanya dapat membuat permintaan mengikuti, dan tidak dapat menyebutkan akun lokal apabila tidak mengikuti. Hal ini tidak akan mempengaruhi instansi yang diblokir."
muteAndBlock: "Bisukan / Blokir"
mutedUsers: "Pengguna yang dibisukan"
blockedUsers: "Pengguna yang diblokir"
@@ -227,7 +234,7 @@ noCustomEmojis: "Tidak ada emoji kustom"
noJobs: "Tidak ada kerja"
federating: "memfederasi"
blocked: "Diblokir"
-suspended: "Diberhentikan"
+suspended: "Ditangguhkan"
all: "Semua"
subscribing: "Berlangganan"
publishing: "Sedang menyiarkan langsung"
@@ -352,7 +359,6 @@ invite: "Undang"
driveCapacityPerLocalAccount: "Kapasitas drive per pengguna lokal"
driveCapacityPerRemoteAccount: "Kapasitas drive per pengguna remote"
inMb: "dalam Megabytes"
-iconUrl: "URL Gambar ikon"
bannerUrl: "URL Banner"
backgroundImageUrl: "URL Gambar latar"
basicInfo: "Informasi Umum"
@@ -391,10 +397,10 @@ withReplies: "Termasuk balasan"
connectedTo: "Akun berikut terhubung"
notesAndReplies: "Catatan dan balasan"
withFiles: "Media"
-silence: "Bungkam"
-silenceConfirm: "Apakah kamu yakin ingin membungkam pengguna ini?"
-unsilence: "Hapus bungkam"
-unsilenceConfirm: "Apakah kamu ingin untuk batal membungkam pengguna ini?"
+silence: "Senyapkan"
+silenceConfirm: "Apakah kamu yakin ingin menyenyapkan pengguna ini?"
+unsilence: "Batalkan senyap"
+unsilenceConfirm: "Apakah kamu ingin untuk batal menyenyapkan pengguna ini?"
popularUsers: "Pengguna populer"
recentlyUpdatedUsers: "Pengguna dengan aktivitas terkini"
recentlyRegisteredUsers: "Pengguna baru saja bergabung"
@@ -408,10 +414,14 @@ aboutMisskey: "Tentang Misskey"
administrator: "Admin"
token: "Token"
2fa: "Autentikasi 2-faktor"
+setupOf2fa: "Atur autentikasi 2-faktor"
totp: "Aplikasi autentikator"
totpDescription: "Gunakan aplikasi autentikator untuk mendapatkan kata sandi sekali pakai"
moderator: "Moderator"
moderation: "Moderasi"
+moderationNote: "Catatan moderasi"
+addModerationNote: "Tambahkan catatan moderasi"
+moderationLogs: "Log moderasi"
nUsersMentioned: "{n} pengguna disebut"
securityKeyAndPasskey: "Security key dan passkey"
securityKey: "Kunci keamanan"
@@ -434,7 +444,7 @@ markAsReadAllTalkMessages: "Tandai semua pesan telah dibaca"
help: "Bantuan"
inputMessageHere: "Ketik pesan disini"
close: "Tutup"
-invites: "Undang"
+invites: "Undangan"
members: "Anggota"
transfer: "Transfer"
title: "Judul"
@@ -449,7 +459,7 @@ noMessagesYet: "Tidak ada pesan"
newMessageExists: "Kamu mendapatkan pesan baru"
onlyOneFileCanBeAttached: "Kamu hanya dapat melampirkan satu berkas ke dalam pesan"
signinRequired: "Silahkan login"
-invitations: "Undang"
+invitations: "Undangan"
invitationCode: "Kode undangan"
checking: "Memeriksa"
available: "Tersedia"
@@ -505,7 +515,7 @@ showFeaturedNotesInTimeline: "Tampilkan catatan yang diunggulkan di lini masa"
objectStorage: "Object Storage"
useObjectStorage: "Gunakan object storage"
objectStorageBaseUrl: "Base URL"
-objectStorageBaseUrlDesc: "Prefix URL digunakan untuk mengkonstruksi URL ke object (media) referencing. Tentukan URL jika kamu menggunakan CDN atau Proxy, jika tidak tentukan alamat yang dapat diakses secara publik sesuai dengan panduan dari layanan yang akan kamu gunakan, contohnya. 'https://.s3.amazonaws.com' untuk AWS S3, dan 'https://storage.googleapis.com/' untuk GCS."
+objectStorageBaseUrlDesc: "Prefix URL digunakan untuk mengonstruksi URL ke object (media) referencing. Tentukan URL jika kamu menggunakan CDN atau Proxy. Jika tidak, tentukan alamat yang dapat diakses secara publik sesuai dengan panduan dari layanan yang akan kamu gunakan. Contohnya: 'https://.s3.amazonaws.com' untuk AWS S3, dan 'https://storage.googleapis.com/' untuk GCS."
objectStorageBucket: "Bucket"
objectStorageBucketDesc: "Mohon tentukan nama bucket yang digunakan pada layanan yang telah dikonfigurasi."
objectStoragePrefix: "Prefix"
@@ -522,8 +532,9 @@ objectStorageSetPublicRead: "Setel \"public-read\" disaat mengunggah"
s3ForcePathStyleDesc: "Jika s3ForcePathStyle dinyalakan, nama bucket harus dimasukkan dalam path URL dan bukan URL nama host tersebut. Kamu perlu menyalakan pengaturan ini jika menggunakan layanan seperti instansi Minio yang self-hosted."
serverLogs: "Log Peladen"
deleteAll: "Hapus semua"
-showFixedPostForm: "Tampilkan form posting di atas lini masa."
+showFixedPostForm: "Tampilkan form posting di atas lini masa"
showFixedPostFormInChannel: "Tampilkan form posting di atas lini masa (Kanal)"
+withRepliesByDefaultForNewlyFollowed: "Termasuk balasan dari pengguna baru yang diikuti pada lini masa secara bawaan"
newNoteRecived: "Kamu mendapat catatan baru"
sounds: "Bunyi"
sound: "Bunyi"
@@ -557,10 +568,10 @@ deleteAllFiles: "Hapus semua berkas"
deleteAllFilesConfirm: "Apakah kamu yakin ingin menghapus semua berkas?"
removeAllFollowing: "Batalkan mengikuti semua pengguna"
removeAllFollowingDescription: "Batal mengikuti semua akun dari {host}. Mohon jalankan ini ketika instansi sudah tidak ada lagi."
-userSuspended: "Pengguna ini telah dibekukan."
-userSilenced: "Pengguna ini telah dibungkam."
-yourAccountSuspendedTitle: "Akun ini dibekukan"
-yourAccountSuspendedDescription: "Akun ini dibekukan karena melanggar ketentuan penggunaan layanan peladen atau semacamnya. Hubungi admin apabila ingin tahu alasan lebih lanjut. Mohon untuk tidak membuat akun baru."
+userSuspended: "Pengguna ini telah ditangguhkan"
+userSilenced: "Pengguna ini telah disenyapkan."
+yourAccountSuspendedTitle: "Akun ini ditangguhkan"
+yourAccountSuspendedDescription: "Akun ini ditangguhkan karena melanggar ketentuan penggunaan layanan peladen atau semacamnya. Hubungi admin apabila ingin mengetahui alasan lebih lanjut. Mohon untuk tidak membuat akun baru."
tokenRevoked: "Token tidak valid"
tokenRevokedDescription: "Token ini telah kedaluwarsa. Mohon masuk lagi."
accountDeleted: "Akun telah dihapus"
@@ -626,7 +637,7 @@ testEmail: "Tes pengiriman surel"
wordMute: "Bisukan kata"
regexpError: "Kesalahan ekspresi reguler"
regexpErrorDescription: "Galat terjadi pada baris {line} ekspresi reguler dari {tab} kata yang dibisukan:"
-instanceMute: "Bisuka instansi"
+instanceMute: "Bisukan instansi"
userSaysSomething: "{name} mengatakan sesuatu"
makeActive: "Aktifkan"
display: "Tampilkan"
@@ -651,6 +662,7 @@ behavior: "Perilaku"
sample: "Contoh"
abuseReports: "Laporkan"
reportAbuse: "Laporkan"
+reportAbuseRenote: "Laporkan renote"
reportAbuseOf: "Laporkan {name}"
fillAbuseReportDescription: "Mohon isi rincian laporan. Jika laporan ini mengenai catatan yang spesifik, mohon lampirkan serta URL catatan tersebut."
abuseReported: "Laporan kamu telah dikirimkan. Terima kasih."
@@ -678,6 +690,7 @@ createNewClip: "Buat klip baru"
unclip: "Batalkan klip"
confirmToUnclipAlreadyClippedNote: "Catatan ini sudah disertakan di klip \"{name}\". Yakin ingin membatalkan catatan dari klip ini?"
public: "Publik"
+private: "Tersembunyi"
i18nInfo: "Misskey diterjemahkan ke dalam banyak bahasa oleh sukarelawan. Kamu juga dapat ikut membantu menerjemahkannya di {link}."
manageAccessTokens: "Kelola token akses"
accountInfo: "Informasi akun"
@@ -702,6 +715,7 @@ lockedAccountInfo: "Kecuali kamu menyetel visibilitas catatan milikmu ke \"Hanya
alwaysMarkSensitive: "Tandai media dalam catatan sebagai media sensitif"
loadRawImages: "Tampilkan lampiran gambar secara penuh daripada thumbnail"
disableShowingAnimatedImages: "Jangan mainkan gambar bergerak"
+highlightSensitiveMedia: "Sorot media sensitif"
verificationEmailSent: "Surel verifikasi telah dikirimkan. Mohon akses tautan yang telah disertakan untuk menyelesaikan verifikasi."
notSet: "Tidak disetel"
emailVerified: "Surel telah diverifikasi"
@@ -987,7 +1001,7 @@ internalServerErrorDescription: "Peladen sedang mengalami galat tak terduga"
copyErrorInfo: "Salin detil galat"
joinThisServer: "Gabung peladen ini"
exploreOtherServers: "Cari peladen lain"
-letsLookAtTimeline: "LIhat timeline"
+letsLookAtTimeline: "LIhat lini masa"
disableFederationConfirm: "Matikan federasi?"
disableFederationConfirmWarn: "Mematikan federasi tidak membuat kiriman menjadi privat. Umumnya, mematikan federasi tidak diperlukan."
disableFederationOk: "Matikan federasi"
@@ -1016,7 +1030,7 @@ retryAllQueuesConfirmText: "Hal ini akan meningkatkan beban sementara ke peladen
enableChartsForRemoteUser: "Buat bagan data pengguna instansi luar"
enableChartsForFederatedInstances: "Buat bagan data peladen instansi luar"
showClipButtonInNoteFooter: "Tambahkan \"Klip\" ke menu aksi catatan"
-largeNoteReactions: "Besarkan reaksi yang ditampilkan"
+reactionsDisplaySize: "Ukuran tampilan reaksi"
noteIdOrUrl: "ID catatan atau URL"
video: "Video"
videos: "Video"
@@ -1073,9 +1087,68 @@ enableServerMachineStats: "Tampilkan informasi mesin peladen menjadi publik"
enableIdenticonGeneration: "Nyalakan pembuatan Identicon per pengguna"
turnOffToImprovePerformance: "Matikan untuk tingkatkan performa."
createInviteCode: "Buat kode undangan"
+createWithOptions: "Buat dengan opsi"
+createCount: "Jumlah undangan"
inviteCodeCreated: "Kode undangan dibuat"
inviteLimitExceeded: "Kamu telah mencapai jumlah maksimum kode undangan yang dapat dibuat."
+createLimitRemaining: "Kode undangan yang dapat dibuat: tersisa {limit}"
+inviteLimitResetCycle: "Kamu dapat membuat hingga {limit} kode undangan dalam {time}."
expirationDate: "Tanggal kedaluwarsa"
+noExpirationDate: "tidak ada tanggal kedaluwarsa"
+inviteCodeUsedAt: "Kode undangan digunakan pada"
+registeredUserUsingInviteCode: "Undangan digunakan oleh"
+waitingForMailAuth: "Menunggu verifikasi surel"
+inviteCodeCreator: "Undangan dibuat oleh"
+usedAt: "Digunakan pada"
+unused: "Tidak digunakan"
+used: "Digunakan"
+expired: "Kedaluwarsa"
+doYouAgree: "Apa kamu setuju?"
+beSureToReadThisAsItIsImportant: "Mohon baca informasi penting berikut."
+iHaveReadXCarefullyAndAgree: "Saya telah membaca \"{x}\" dan menyetujui."
+dialog: "Dialog"
+icon: "Avatar"
+forYou: "Untuk Anda"
+currentAnnouncements: "Pengumuman Saat Ini"
+pastAnnouncements: "Pengumuman Terdahulu"
+youHaveUnreadAnnouncements: "Terdapat pengumuman yang belum dibaca"
+useSecurityKey: "Mohon ikuti instruksi peramban atau perangkat kamu untuk menggunakan kunci pengaman atau passkey."
+replies: "Balas"
+renotes: "Renote"
+loadReplies: "Tampilkan balasan"
+loadConversation: "Tampilkan percakapan"
+pinnedList: "Daftar yang dipin"
+keepScreenOn: "Biarkan layar tetap menyala"
+verifiedLink: "Tautan kepemilikan telah diverifikasi"
+notifyNotes: "Beritahu mengenai catatan baru"
+unnotifyNotes: "Berhenti memberitahu mengenai catatan baru"
+authentication: "Autentikasi"
+authenticationRequiredToContinue: "Mohon autentikasikan terlebih dahulu sebelum melanjutkan"
+dateAndTime: "Tanggal dan Waktu"
+showRenotes: "Tampilkan renote"
+edited: "Telah disunting"
+notificationRecieveConfig: "Pengaturan notifikasi"
+mutualFollow: "Saling mengikuti"
+fileAttachedOnly: "Hanya catatan dengan berkas"
+showRepliesToOthersInTimeline: "Tampilkan balasan ke pengguna lain dalam lini masa"
+hideRepliesToOthersInTimeline: "Sembunyikan balasan ke orang lain dari lini masa"
+externalServices: "Layanan eksternal"
+impressum: "Impressum"
+impressumUrl: "Tautan Impressum"
+impressumDescription: "Pada beberapa negara seperti Jerman, inklusi dari informasi kontak operator (sebuah Impressum) diperlukan secara legal untuk situs web komersil."
+privacyPolicy: "Kebijakan Privasi"
+privacyPolicyUrl: "Tautan Kebijakan Privasi"
+tosAndPrivacyPolicy: "Syarat dan Ketentuan serta Kebijakan Privasi"
+flip: "Balik"
+_announcement:
+ forExistingUsers: "Hanya pengguna yang telah ada"
+ forExistingUsersDescription: "Pengumuman ini akan dimunculkan ke pengguna yang sudah ada dari titik waktu publikasi jika dinyalakan. Apabila dimatikan, mereka yang baru mendaftar setelah publikasi ini akan juga melihatnya."
+ needConfirmationToRead: "Membutuhkan konfirmasi terpisah bahwa telah dibaca"
+ needConfirmationToReadDescription: "Permintaan terpisah untuk mengonfirmasi menandai pengumuman ini telah dibaca akan ditampilkan apabila fitur ini dinyalakan. Pengumuman ini juga akan dikecualikan dari fungsi \"Tandai semua telah dibaca\"."
+ end: "Arsipkan pengumuman"
+ tooManyActiveAnnouncementDescription: "Terlalu banyak pengumuman dapat memperburuk pengalaman pengguna. Mohon pertimbangkan untuk mengarsipkan pengumuman yang sudah usang/tidak relevan."
+ readConfirmTitle: "Tandai telah dibaca?"
+ readConfirmText: "Aksi ini akan menandai konten dari \"{title}\" telah dibaca."
_initialAccountSetting:
accountCreated: "Akun kamu telah sukses dibuat!"
letsStartAccountSetup: "Untuk pemula, ayo atur profilmu dulu."
@@ -1088,11 +1161,19 @@ _initialAccountSetting:
pushNotificationDescription: "Menyalakan notifikasi dorong akan membuatmu menerima notifikasi dari {name} secara langsung ke perangkatmu."
initialAccountSettingCompleted: "Pengaturan profil selesai!"
haveFun: "Selamat menikmati, {name}!"
- ifYouNeedLearnMore: "Kalau kamu ingin mempelajari lebih lanjut bagaimana cara menggunakan {name} (Misskey), silahkan kunjungi {link}."
skipAreYouSure: "Yakin melewati atur profil?"
laterAreYouSure: "Yakin banget untuk atur profil nanti?"
_serverRules:
description: "Daftar peraturan akan ditampilkan sebelum pendaftaran. Mengatur ringkasan dari Syarat dan Ketentuan sangat direkomendasikan."
+_serverSettings:
+ iconUrl: "URL ikon"
+ appIconDescription: "Tentukan ikon yang digunakan ketika {host} ditampilkan sebagai aplikasi."
+ appIconUsageExample: "Contoh: Sebagai PWA, atau ketika ditampilkan sebagai markah layar beranda pada ponsel"
+ appIconStyleRecommendation: "Karena ikon berkemungkinan dipotong menjadi persegi atau lingkaran, ikon dengan margin terwanai di sekeliling konten sangat direkomendasikan."
+ appIconResolutionMustBe: "Minimum resolusi adalah {resolution}."
+ manifestJsonOverride: "Ambil alih manifest.json"
+ shortName: "Nama pendek"
+ shortNameDescription: "Inisial untuk nama instansi yang dapat ditampilkan apabila nama lengkap resmi terlalu panjang."
_accountMigration:
moveFrom: "Pindahkan akun lain ke akun ini"
moveFromSub: "Buat alias ke akun lain"
@@ -1347,6 +1428,9 @@ _achievements:
title: "Brain Diver"
description: "Posting tautan mengenai Brain Diver"
flavor: "Misskey-Misskey La-Tu-Ma"
+ _smashTestNotificationButton:
+ title: "Tes overflow"
+ description: "Picu tes notifikasi secara berulang dalam waktu yang sangat pendek"
_role:
new: "Buat peran"
edit: "Sunting peran"
@@ -1386,6 +1470,9 @@ _role:
ltlAvailable: "Dapat melihat lini masa lokal"
canPublicNote: "Dapat mengirim catatan publik"
canInvite: "Dapat membuat kode undangan instansi"
+ inviteLimit: "Batas jumlah undangan"
+ inviteLimitCycle: "Interval Penerbitan Kode Undangan"
+ inviteExpirationTime: "Interval kedaluwarsa undangan"
canManageCustomEmojis: "Dapat mengelola Emoji kustom"
driveCapacity: "Kapasitas Drive"
alwaysMarkNsfw: "Selalu tandai berkas sebagai NSFW"
@@ -1401,6 +1488,7 @@ _role:
descriptionOfRateLimitFactor: "Batas kecepatan yang rendah tidak begitu membatasi, batas kecepatan tinggi lebih membatasi. "
canHideAds: "Dapat menyembunyikan iklan"
canSearchNotes: "Penggunaan pencarian catatan"
+ canUseTranslator: "Penggunaan penerjemah"
_condition:
isLocal: "Pengguna lokal"
isRemote: "Pengguna remote"
@@ -1448,6 +1536,11 @@ _ad:
back: "Kembali"
reduceFrequencyOfThisAd: "Tampilkan iklan ini lebih sedikit"
hide: "Jangan tampilkan"
+ timezoneinfo: "Hari dalam satu minggu ditentukan dari zona waktu peladen."
+ adsSettings: "Pengaturan iklan"
+ notesPerOneAd: "Interval penempatan pemutakhiran iklan secara real-time (catatan per iklan)"
+ setZeroToDisable: "Atur nilai ini ke 0 untuk menonaktifkan pemutakhiran iklan secara real-time"
+ adsTooClose: "Interval iklan saat ini kemungkinan memperburuk pengalaman pengguna secara signifikan karena diatur pada nilai yang terlalu rendah."
_forgotPassword:
enterEmail: "Masukkan alamat surel yang kamu gunakan pada saat mendaftar. Sebuah tautan untuk mengatur ulang kata sandi kamu akan dikirimkan ke alamat surel tersebut."
ifNoEmail: "Apabila kamu tidak menggunakan surel pada saat pendaftaran, mohon hubungi admin segera."
@@ -1466,6 +1559,7 @@ _plugin:
install: "Memasang plugin"
installWarn: "Mohon jangan memasang plugin yang tidak dapat dipercayai."
manage: "Manajemen plugin"
+ viewSource: "Lihat sumber"
_preferencesBackups:
list: "Cadangan yang dibuat"
saveNew: "Simpan cadangan baru"
@@ -1499,6 +1593,10 @@ _aboutMisskey:
donate: "Donasi ke Misskey"
morePatrons: "Kami sangat mengapresiasi dukungan dari banyak penolong lain yang tidak tercantum disini. Terima kasih! 🥰"
patrons: "Pendukung"
+_displayOfSensitiveMedia:
+ respect: "Sembunyikan media yang ditandai sensitif"
+ ignore: "Tampilkan media yang ditandai sensitif"
+ force: "Sembunyikan semua media"
_instanceTicker:
none: "Jangan tampilkan"
remote: "Tampilkan untuk pengguna instansi luar"
@@ -1528,11 +1626,6 @@ _wordMute:
muteWords: "Kata yang dibisukan"
muteWordsDescription: "Pisahkan dengan spasi untuk kondisi AND. Pisahkan dengan baris baru untuk kondisi OR."
muteWordsDescription2: "Kurung kata kunci dengan garis miring untuk menggunakan ekspresi reguler."
- softDescription: "Sembunyikan catatan yang memenuhi aturan kondisi dari lini masa."
- hardDescription: "Cegah catatan memenuhi aturan kondisi dari ditambahkan ke lini masa. Dengan tambahan, catatan berikut tidak akan ditambahkan ke lini masa meskipun jika kondisi tersebut diubah."
- soft: "Lembut"
- hard: "Keras"
- mutedNotes: "Catatan yang dibisukan"
_instanceMute:
instanceMuteDescription: "Pengaturan ini akan membisukan note/renote apa saja dari instansi yang terdaftar, termasuk pengguna yang membalas pengguna lain dalam instansi yang dibisukan."
instanceMuteDescription2: "Pisah dengan baris baru"
@@ -1596,9 +1689,6 @@ _theme:
infoFg: "Teks informasi"
infoWarnBg: "Latar belakang peringatan"
infoWarnFg: "Teks peringatan"
- cwBg: "Latar belakang tombol Sembunyikan Konten"
- cwFg: "Teks tombol Sembunyikan Konten"
- cwHoverBg: "Latar belakang tombol Sembunyikan Konten (Mengambang)"
toastBg: "Latar belakang notifikasi"
toastFg: "Teks notifikasi"
buttonBg: "Latar belakang tombol"
@@ -1616,8 +1706,6 @@ _sfx:
note: "Catatan"
noteMy: "Catatan (Saya)"
notification: "Notifikasi"
- chat: "Pesan"
- chatBg: "Obrolan (Latar Belakang)"
antenna: "Penerimaan Antenna"
channel: "Notifikasi Kanal"
_ago:
@@ -1636,30 +1724,20 @@ _time:
minute: "menit"
hour: "jam"
day: "hari"
-_timelineTutorial:
- title: "Bagaimana cara menggunakan Misskey"
- step1_1: "Ini adalah \"lini masa\". Semua \"catatan\" yang dikirimkan oleh {name} akan dimunculkan secara kronologis di sini."
- step1_2: "Ada beberapa lini masa yang berbeda. Seperti contoh, \"Lini masa Beranda\" berisi catatan dari pengguna yang kamu ikuti, dan \"Lini masa lokal\" berisi catatan dari semua pengguna dari {name}."
- step2_1: "Selanjutnya, mari kita coba memposting sebuah catatan. Kamu dapat melakukanya dengan menekan tombol dengan ikon pensil."
- step2_2: "Bagaimana dengan menuliskan sedikit perkenalan diri, atau hanya \"Hello {name}\" kalau kamu lagi ngga feeling?"
- step3_1: "Udah selesai memposting catatan pertamamu?"
- step3_2: "Catatan pertamamu seharusnya sekarang sudah tampil di lini masa kamu."
- step4_1: "Kamu dapat menyisipkan \"Reaksi\" ke dalam catatan."
- step4_2: "Untuk menyisipkan reaksi, tekan tanda \"+\" dalam catatan dan pilih emoji yang kamu suka untuk mereaksi catatan tersebut."
_2fa:
- alreadyRegistered: "Kamu telah mendaftarkan perangkat otentikasi dua faktor."
+ alreadyRegistered: "Kamu telah mendaftarkan perangkat autentikasi 2-faktor."
registerTOTP: "Daftarkan aplikasi autentikator"
- passwordToTOTP: "Masukkan kata sandimu"
- step1: "Pertama, pasang aplikasi otentikasi (seperti {a} atau {b}) di perangkat kamu."
+ step1: "Pertama, pasang aplikasi autentikasi (seperti {a} atau {b}) di perangkat kamu."
step2: "Lalu, pindai kode QR yang ada di layar."
step2Click: "Mengeklik kode QR ini akan membolehkanmu untuk mendaftarkan 2FA ke security-key atau aplikasi autentikator ponsel."
- step2Url: "Di aplikasi desktop, masukkan URL berikut:"
+ step2Uri: "Masukkan URI berikut jika kamu menggunakan program desktop"
step3Title: "Masukkan kode autentikasi"
step3: "Masukkan token yang telah disediakan oleh aplikasimu untuk menyelesaikan pemasangan."
- step4: "Mulai sekarang, upaya login apapun akan meminta token login dari aplikasi otentikasi kamu.\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}"
+ setupCompleted: "Penyetelan autentikasi 2-faktor selesai"
+ step4: "Mulai sekarang, upaya login apapun akan meminta token login dari aplikasi autentikasi kamu."
securityKeyNotSupported: "Peramban kamu tidak mendukung security key."
registerTOTPBeforeKey: "Mohon atur aplikasi autentikator untuk mendaftarkan security key atau passkey."
- securityKeyInfo: "Kamu dapat memasang otentikasi WebAuthN untuk mengamankan proses login lebih lanjut dengan tidak hanya perangkat keras kunci keamanan yang mendukung FIDO2, namun juga sidik jari atau otentikasi PIN pada perangkatmu."
+ securityKeyInfo: "Kamu dapat memasang autentikasi WebAuthN untuk mengamankan proses login lebih lanjut dengan tidak hanya perangkat keras kunci keamanan yang mendukung FIDO2, namun juga sidik jari atau autentikasi PIN pada perangkatmu."
registerSecurityKey: "Daftarkan security key atau passkey."
securityKeyName: "Masukkan nama key."
tapSecurityKey: "Mohon ikuti peramban kamu untuk mendaftarkan security key atau passkey"
@@ -1670,6 +1748,11 @@ _2fa:
renewTOTPConfirm: "Hal ini akan menyebabkan kode verifikasi dari aplikasi autentikator sebelumnya berhenti bekerja"
renewTOTPOk: "Atur ulang"
renewTOTPCancel: "Tidak sekarang."
+ checkBackupCodesBeforeCloseThisWizard: "Sebelum kamu menutup jendela ini, pastikan untuk memperhatikan dan mencadangkan kode cadangan berikut."
+ backupCodes: "Kode Pencadangan"
+ backupCodesDescription: "Kamu dapat menggunakan kode ini untuk mendapatkan akses ke akun kamu apabila berada dalam situasi tidak dapat menggunakan aplikasi autentikasi 2-faktor yang kamu miliki. Setiap kode hanya dapat digunakan satu kali. Mohon simpan kode ini di tempat yang aman."
+ backupCodeUsedWarning: "Kode cadangan telah digunakan. Mohon mengatur ulang autentikasi 2-faktor secepatnya apabila kamu sudah tidak dapat menggunakannya lagi."
+ backupCodesExhaustedWarning: "Semua kode cadangan telah digunakan. Apabila kamu kehilangan akses pada aplikasi autentikasi 2-faktor milikmu, kamu tidak dapat mengakses akun ini lagi. Mohon atur ulang autentikasi 2-faktor kamu."
_permissions:
"read:account": "Lihat informasi akun"
"write:account": "Sunting informasi akun"
@@ -1703,6 +1786,10 @@ _permissions:
"write:gallery": "Sunting galeri"
"read:gallery-likes": "Lihat daftar postingan galeri yang disukai"
"write:gallery-likes": "Sunting daftar postingan galeri yang disukai"
+ "read:flash": "Lihat Play"
+ "write:flash": "Sunting Play"
+ "read:flash-likes": "Lihat daftar Play yang disukai"
+ "write:flash-likes": "Sunting daftar Play yang disukai"
_auth:
shareAccessTitle: "Mendapatkan ijin akses aplikasi"
shareAccess: "Apakah kamu ingin mengijinkan \"{name}\" untuk mengakses akun ini?"
@@ -1718,6 +1805,7 @@ _antennaSources:
homeTimeline: "Catatan dari pengguna yang diikuti"
users: "Catatan dari pengguna tertentu"
userList: "Catatan dari daftar tertentu"
+ userBlacklist: "Semua catatan kecuali untuk satu pengguna atau lebih yang telah ditentukan"
_weekday:
sunday: "Minggu"
monday: "Senin"
@@ -1817,6 +1905,7 @@ _profile:
metadataContent: "Isi"
changeAvatar: "Ubah avatar"
changeBanner: "Ubah header"
+ verifiedLinkDescription: "Dengan memasukkan URL yang mengandung tautan ke profil kamu di sini, ikon verifikasi kepemilikan dapat ditampilkan di sebelah kolom ini."
_exportOrImport:
allNotes: "Semua catatan"
favoritedNotes: "Catatan favorit"
@@ -1826,6 +1915,7 @@ _exportOrImport:
userLists: "Daftar"
excludeMutingUsers: "Kecualikan pengguna yang dibisukan"
excludeInactiveUsers: "Kecualikan pengguna tidak aktif"
+ withReplies: "Termasuk balasan dari pengguna yang diimpor ke dalam lini masa"
_charts:
federation: "Federasi"
apRequest: "Permintaan"
@@ -1935,11 +2025,17 @@ _notification:
youReceivedFollowRequest: "Kamu menerima permintaan mengikuti"
yourFollowRequestAccepted: "Permintaan mengikuti kamu telah diterima"
pollEnded: "Hasil Kuesioner telah keluar"
+ newNote: "Catatan baru"
unreadAntennaNote: "Antena {name}"
emptyPushNotificationMessage: "Pembaruan notifikasi dorong"
achievementEarned: "Pencapaian didapatkan"
+ testNotification: "Tes notifikasi"
+ checkNotificationBehavior: "Cek tampilan notifikasi"
+ sendTestNotification: "Kirim tes notifikasi"
+ notificationWillBeDisplayedLikeThis: "Notifikasi akan terlihat seperti ini"
_types:
all: "Semua"
+ note: "Catatan baru"
follow: "Ikuti"
mention: "Sebut"
reply: "Balasan"
@@ -1972,6 +2068,9 @@ _deck:
introduction: "Buat antarmuka sempurna untukmu dengan menata kolom secara bebas!"
introduction2: "Klik \"+\" pada kanan layar untuk menambahkan kolom baru kapanpun yang kamu mau."
widgetsIntroduction: "Mohon pilih \"Sunting gawit\" pada menu kolom dan tambahkan gawit."
+ useSimpleUiForNonRootPages: "Gunakan antarmuka sederhana ke halaman yang dituju"
+ usedAsMinWidthWhenFlexible: "Lebar minimum akan digunakan untuk ini ketika opsi \"Atur-otomatis lebar\" dinyalakan"
+ flexible: "Atur-otomatis lebar"
_columns:
main: "Utama"
widgets: "Widget"
@@ -2006,3 +2105,42 @@ _webhookSettings:
renote: "Ketika direnote"
reaction: "Ketika menerima reaksi"
mention: "Ketika sedang disebut"
+_moderationLogTypes:
+ createRole: "Peran telah dibuat"
+ deleteRole: "Peran telah dihapus"
+ updateRole: "Peran telah diperbaharui"
+ assignRole: "Yang ditugaskan dalam peran"
+ unassignRole: "Dihapus dari peran"
+ suspend: "Tangguhkan"
+ unsuspend: "Batal ditangguhkan"
+ addCustomEmoji: "Emoji kustom ditambahkan"
+ updateCustomEmoji: "Emoji kustom diperbaharui"
+ deleteCustomEmoji: "Emoji kustom dihapus"
+ updateServerSettings: "Pengaturan peladen diperbaharui"
+ updateUserNote: "Catatan moderasi diperbaharui"
+ deleteDriveFile: "Berkas dihapus"
+ deleteNote: "Catatan dihapus"
+ createGlobalAnnouncement: "Pengumuman global dibuat"
+ createUserAnnouncement: "Pengumuman pengguna dibuat"
+ updateGlobalAnnouncement: "Pengumuman global diperbaharui"
+ updateUserAnnouncement: "Pengumuman pengguna diperbaharui"
+ deleteGlobalAnnouncement: "Pengumuman global telah dihapus"
+ deleteUserAnnouncement: "Pengumuman pengguna telah dihapus."
+ resetPassword: "Atur ulang kata sandi"
+ suspendRemoteInstance: "Instansi luar telah ditangguhkan"
+ unsuspendRemoteInstance: "Instansi luar batal ditangguhkan"
+ markSensitiveDriveFile: "Berkas ditandai sensitif"
+ unmarkSensitiveDriveFile: "Berkas batal ditandai sensitif"
+ resolveAbuseReport: "Laporan terselesaikan"
+ createInvitation: "Buat kode undangan"
+ createAd: "Iklan telah dibuat"
+ deleteAd: "Iklan telah dihapus"
+ updateAd: "Iklan telah diperbaharui"
+_fileViewer:
+ title: "Rincian berkas"
+ type: "Jenis berkas"
+ size: "Ukuran berkas"
+ url: "URL"
+ uploadedAt: "Diunggah pada"
+ attachedNotes: "Catatan yang dilampirkan"
+ thisPageCanBeSeenFromTheAuthor: "Halaman ini hanya dapat dilihat oleh pengguna yang mengunggah bekas ini."
diff --git a/locales/index.d.ts b/locales/index.d.ts
index 26c034c476..ace1ad80f4 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -48,6 +48,7 @@ export interface Locale {
"unpin": string;
"copyContent": string;
"copyLink": string;
+ "copyLinkRenote": string;
"delete": string;
"deleteAndEdit": string;
"deleteAndEditConfirm": string;
@@ -159,6 +160,7 @@ export interface Locale {
"settingGuide": string;
"cacheRemoteFiles": string;
"cacheRemoteFilesDescription": string;
+ "youCanCleanRemoteFilesCache": string;
"cacheRemoteSensitiveFiles": string;
"cacheRemoteSensitiveFilesDescription": string;
"flagAsBot": string;
@@ -196,6 +198,7 @@ export interface Locale {
"perDay": string;
"stopActivityDelivery": string;
"blockThisInstance": string;
+ "silenceThisInstance": string;
"operations": string;
"software": string;
"version": string;
@@ -215,6 +218,8 @@ export interface Locale {
"clearCachedFilesConfirm": string;
"blockedInstances": string;
"blockedInstancesDescription": string;
+ "silencedInstances": string;
+ "silencedInstancesDescription": string;
"muteAndBlock": string;
"mutedUsers": string;
"blockedUsers": string;
@@ -359,7 +364,6 @@ export interface Locale {
"driveCapacityPerLocalAccount": string;
"driveCapacityPerRemoteAccount": string;
"inMb": string;
- "iconUrl": string;
"bannerUrl": string;
"backgroundImageUrl": string;
"basicInfo": string;
@@ -415,11 +419,14 @@ export interface Locale {
"administrator": string;
"token": string;
"2fa": string;
+ "setupOf2fa": string;
"totp": string;
"totpDescription": string;
- "useSecurityKey": string;
"moderator": string;
"moderation": string;
+ "moderationNote": string;
+ "addModerationNote": string;
+ "moderationLogs": string;
"nUsersMentioned": string;
"securityKeyAndPasskey": string;
"securityKey": string;
@@ -532,6 +539,7 @@ export interface Locale {
"deleteAll": string;
"showFixedPostForm": string;
"showFixedPostFormInChannel": string;
+ "withRepliesByDefaultForNewlyFollowed": string;
"newNoteRecived": string;
"sounds": string;
"sound": string;
@@ -663,6 +671,7 @@ export interface Locale {
"sample": string;
"abuseReports": string;
"reportAbuse": string;
+ "reportAbuseRenote": string;
"reportAbuseOf": string;
"fillAbuseReportDescription": string;
"abuseReported": string;
@@ -690,6 +699,7 @@ export interface Locale {
"unclip": string;
"confirmToUnclipAlreadyClippedNote": string;
"public": string;
+ "private": string;
"i18nInfo": string;
"manageAccessTokens": string;
"accountInfo": string;
@@ -714,6 +724,7 @@ export interface Locale {
"alwaysMarkSensitive": string;
"loadRawImages": string;
"disableShowingAnimatedImages": string;
+ "highlightSensitiveMedia": string;
"verificationEmailSent": string;
"notSet": string;
"emailVerified": string;
@@ -977,6 +988,7 @@ export interface Locale {
"unassign": string;
"color": string;
"manageCustomEmojis": string;
+ "manageAvatarDecorations": string;
"youCannotCreateAnymore": string;
"cannotPerformTemporary": string;
"cannotPerformTemporaryDescription": string;
@@ -1028,7 +1040,7 @@ export interface Locale {
"enableChartsForRemoteUser": string;
"enableChartsForFederatedInstances": string;
"showClipButtonInNoteFooter": string;
- "largeNoteReactions": string;
+ "reactionsDisplaySize": string;
"noteIdOrUrl": string;
"video": string;
"videos": string;
@@ -1112,11 +1124,51 @@ export interface Locale {
"currentAnnouncements": string;
"pastAnnouncements": string;
"youHaveUnreadAnnouncements": string;
+ "useSecurityKey": string;
+ "replies": string;
+ "renotes": string;
+ "loadReplies": string;
+ "loadConversation": string;
+ "pinnedList": string;
+ "keepScreenOn": string;
+ "verifiedLink": string;
+ "notifyNotes": string;
+ "unnotifyNotes": string;
+ "authentication": string;
+ "authenticationRequiredToContinue": string;
+ "dateAndTime": string;
+ "showRenotes": string;
+ "edited": string;
+ "notificationRecieveConfig": string;
+ "mutualFollow": string;
+ "fileAttachedOnly": string;
+ "showRepliesToOthersInTimeline": string;
+ "hideRepliesToOthersInTimeline": string;
+ "showRepliesToOthersInTimelineAll": string;
+ "hideRepliesToOthersInTimelineAll": string;
+ "confirmShowRepliesAll": string;
+ "confirmHideRepliesAll": string;
"externalServices": string;
+ "impressum": string;
+ "impressumUrl": string;
+ "impressumDescription": string;
+ "privacyPolicy": string;
+ "privacyPolicyUrl": string;
+ "tosAndPrivacyPolicy": string;
+ "avatarDecorations": string;
+ "attach": string;
+ "detach": string;
+ "angle": string;
+ "flip": string;
+ "showAvatarDecorations": string;
"releaseToRefresh": string;
"refreshing": string;
"pullDownToRefresh": string;
"disableStreamingTimeline": string;
+ "useGroupedNotifications": string;
+ "signupPendingError": string;
+ "cwNotationRequired": string;
+ "doReaction": string;
"urlPreviewDenyList": string;
"urlPreviewDenyListDescription": string;
"_announcement": {
@@ -1128,6 +1180,10 @@ export interface Locale {
"tooManyActiveAnnouncementDescription": string;
"readConfirmTitle": string;
"readConfirmText": string;
+ "shouldNotBeUsedToPresentPermanentInfo": string;
+ "dialogAnnouncementUxWarn": string;
+ "silence": string;
+ "silenceDescription": string;
};
"_initialAccountSetting": {
"accountCreated": string;
@@ -1141,13 +1197,107 @@ export interface Locale {
"pushNotificationDescription": string;
"initialAccountSettingCompleted": string;
"haveFun": string;
- "ifYouNeedLearnMore": string;
+ "youCanContinueTutorial": string;
+ "startTutorial": string;
"skipAreYouSure": string;
"laterAreYouSure": string;
};
+ "_initialTutorial": {
+ "launchTutorial": string;
+ "title": string;
+ "wellDone": string;
+ "skipAreYouSure": string;
+ "_landing": {
+ "title": string;
+ "description": string;
+ };
+ "_note": {
+ "title": string;
+ "description": string;
+ "reply": string;
+ "renote": string;
+ "reaction": string;
+ "menu": string;
+ };
+ "_reaction": {
+ "title": string;
+ "description": string;
+ "letsTryReacting": string;
+ "reactToContinue": string;
+ "reactNotification": string;
+ "reactDone": string;
+ };
+ "_timeline": {
+ "title": string;
+ "description1": string;
+ "home": string;
+ "local": string;
+ "social": string;
+ "global": string;
+ "description2": string;
+ "description3": string;
+ };
+ "_postNote": {
+ "title": string;
+ "description1": string;
+ "_visibility": {
+ "description": string;
+ "public": string;
+ "home": string;
+ "followers": string;
+ "direct": string;
+ "doNotSendConfidencialOnDirect1": string;
+ "doNotSendConfidencialOnDirect2": string;
+ "localOnly": string;
+ };
+ "_cw": {
+ "title": string;
+ "description": string;
+ "_exampleNote": {
+ "cw": string;
+ "note": string;
+ };
+ "useCases": string;
+ };
+ };
+ "_howToMakeAttachmentsSensitive": {
+ "title": string;
+ "description": string;
+ "tryThisFile": string;
+ "_exampleNote": {
+ "note": string;
+ };
+ "method": string;
+ "sensitiveSucceeded": string;
+ "doItToContinue": string;
+ };
+ "_done": {
+ "title": string;
+ "description": string;
+ };
+ };
+ "_timelineDescription": {
+ "home": string;
+ "local": string;
+ "social": string;
+ "global": string;
+ };
"_serverRules": {
"description": string;
};
+ "_serverSettings": {
+ "iconUrl": string;
+ "appIconDescription": string;
+ "appIconUsageExample": string;
+ "appIconStyleRecommendation": string;
+ "appIconResolutionMustBe": string;
+ "manifestJsonOverride": string;
+ "shortName": string;
+ "shortNameDescription": string;
+ "fanoutTimelineDescription": string;
+ "fanoutTimelineDbFallback": string;
+ "fanoutTimelineDbFallbackDescription": string;
+ };
"_accountMigration": {
"moveFrom": string;
"moveFromSub": string;
@@ -1477,6 +1627,14 @@ export interface Locale {
"description": string;
"flavor": string;
};
+ "_smashTestNotificationButton": {
+ "title": string;
+ "description": string;
+ };
+ "_tutorialCompleted": {
+ "title": string;
+ "description": string;
+ };
};
};
"_role": {
@@ -1526,6 +1684,7 @@ export interface Locale {
"inviteLimitCycle": string;
"inviteExpirationTime": string;
"canManageCustomEmojis": string;
+ "canManageAvatarDecorations": string;
"driveCapacity": string;
"alwaysMarkNsfw": string;
"pinMax": string;
@@ -1540,6 +1699,7 @@ export interface Locale {
"descriptionOfRateLimitFactor": string;
"canHideAds": string;
"canSearchNotes": string;
+ "canUseTranslator": string;
};
"_condition": {
"isLocal": string;
@@ -1596,6 +1756,10 @@ export interface Locale {
"reduceFrequencyOfThisAd": string;
"hide": string;
"timezoneinfo": string;
+ "adsSettings": string;
+ "notesPerOneAd": string;
+ "setZeroToDisable": string;
+ "adsTooClose": string;
};
"_forgotPassword": {
"enterEmail": string;
@@ -1620,6 +1784,7 @@ export interface Locale {
"install": string;
"installWarn": string;
"manage": string;
+ "viewSource": string;
};
"_preferencesBackups": {
"list": string;
@@ -1656,6 +1821,7 @@ export interface Locale {
"donate": string;
"morePatrons": string;
"patrons": string;
+ "projectMembers": string;
};
"_displayOfSensitiveMedia": {
"respect": string;
@@ -1684,6 +1850,7 @@ export interface Locale {
"notesCount": string;
"nameAndDescription": string;
"nameOnly": string;
+ "allowRenoteToExternal": string;
};
"_menuDisplay": {
"sideFull": string;
@@ -1695,11 +1862,6 @@ export interface Locale {
"muteWords": string;
"muteWordsDescription": string;
"muteWordsDescription2": string;
- "softDescription": string;
- "hardDescription": string;
- "soft": string;
- "hard": string;
- "mutedNotes": string;
};
"_instanceMute": {
"instanceMuteDescription": string;
@@ -1765,9 +1927,6 @@ export interface Locale {
"infoFg": string;
"infoWarnBg": string;
"infoWarnFg": string;
- "cwBg": string;
- "cwFg": string;
- "cwHoverBg": string;
"toastBg": string;
"toastFg": string;
"buttonBg": string;
@@ -1787,8 +1946,6 @@ export interface Locale {
"note": string;
"noteMy": string;
"notification": string;
- "chat": string;
- "chatBg": string;
"antenna": string;
"channel": string;
};
@@ -1804,36 +1961,32 @@ export interface Locale {
"yearsAgo": string;
"invalid": string;
};
+ "_timeIn": {
+ "seconds": string;
+ "minutes": string;
+ "hours": string;
+ "days": string;
+ "weeks": string;
+ "months": string;
+ "years": string;
+ };
"_time": {
"second": string;
"minute": string;
"hour": string;
"day": string;
};
- "_timelineTutorial": {
- "title": string;
- "step1_1": string;
- "step1_2": string;
- "step2_1": string;
- "step2_2": string;
- "step3_1": string;
- "step3_2": string;
- "step4_1": string;
- "step4_2": string;
- };
"_2fa": {
"alreadyRegistered": string;
"registerTOTP": string;
- "passwordToTOTP": string;
"step1": string;
"step2": string;
"step2Click": string;
- "step2Url": string;
+ "step2Uri": string;
"step3Title": string;
"step3": string;
+ "setupCompleted": string;
"step4": string;
- "twoFactorBackupSecretWarning": string;
- "twoFactorBackupSecretExhausted": string;
"securityKeyNotSupported": string;
"registerTOTPBeforeKey": string;
"securityKeyInfo": string;
@@ -1847,6 +2000,11 @@ export interface Locale {
"renewTOTPConfirm": string;
"renewTOTPOk": string;
"renewTOTPCancel": string;
+ "checkBackupCodesBeforeCloseThisWizard": string;
+ "backupCodes": string;
+ "backupCodesDescription": string;
+ "backupCodeUsedWarning": string;
+ "backupCodesExhaustedWarning": string;
};
"_permissions": {
"read:account": string;
@@ -1881,6 +2039,10 @@ export interface Locale {
"write:gallery": string;
"read:gallery-likes": string;
"write:gallery-likes": string;
+ "read:flash": string;
+ "write:flash": string;
+ "read:flash-likes": string;
+ "write:flash-likes": string;
};
"_auth": {
"shareAccessTitle": string;
@@ -1898,6 +2060,7 @@ export interface Locale {
"homeTimeline": string;
"users": string;
"userList": string;
+ "userBlacklist": string;
};
"_weekday": {
"sunday": string;
@@ -2007,6 +2170,7 @@ export interface Locale {
"metadataContent": string;
"changeAvatar": string;
"changeBanner": string;
+ "verifiedLinkDescription": string;
};
"_exportOrImport": {
"allNotes": string;
@@ -2017,6 +2181,7 @@ export interface Locale {
"userLists": string;
"excludeMutingUsers": string;
"excludeInactiveUsers": string;
+ "withReplies": string;
};
"_charts": {
"federation": string;
@@ -2136,11 +2301,20 @@ export interface Locale {
"youReceivedFollowRequest": string;
"yourFollowRequestAccepted": string;
"pollEnded": string;
+ "newNote": string;
"unreadAntennaNote": string;
"emptyPushNotificationMessage": string;
"achievementEarned": string;
+ "testNotification": string;
+ "checkNotificationBehavior": string;
+ "sendTestNotification": string;
+ "notificationWillBeDisplayedLikeThis": string;
+ "reactedBySomeUsers": string;
+ "renotedBySomeUsers": string;
+ "followedBySomeUsers": string;
"_types": {
"all": string;
+ "note": string;
"follow": string;
"mention": string;
"reply": string;
@@ -2177,6 +2351,8 @@ export interface Locale {
"introduction2": string;
"widgetsIntroduction": string;
"useSimpleUiForNonRootPages": string;
+ "usedAsMinWidthWhenFlexible": string;
+ "flexible": string;
"_columns": {
"main": string;
"widgets": string;
@@ -2237,6 +2413,105 @@ export interface Locale {
"list": string;
"resolver": string;
};
+ "_moderationLogTypes": {
+ "createRole": string;
+ "deleteRole": string;
+ "updateRole": string;
+ "assignRole": string;
+ "unassignRole": string;
+ "suspend": string;
+ "unsuspend": string;
+ "addCustomEmoji": string;
+ "updateCustomEmoji": string;
+ "deleteCustomEmoji": string;
+ "updateServerSettings": string;
+ "updateUserNote": string;
+ "deleteDriveFile": string;
+ "deleteNote": string;
+ "createGlobalAnnouncement": string;
+ "createUserAnnouncement": string;
+ "updateGlobalAnnouncement": string;
+ "updateUserAnnouncement": string;
+ "deleteGlobalAnnouncement": string;
+ "deleteUserAnnouncement": string;
+ "resetPassword": string;
+ "suspendRemoteInstance": string;
+ "unsuspendRemoteInstance": string;
+ "markSensitiveDriveFile": string;
+ "unmarkSensitiveDriveFile": string;
+ "resolveAbuseReport": string;
+ "createInvitation": string;
+ "createAd": string;
+ "deleteAd": string;
+ "updateAd": string;
+ "createAvatarDecoration": string;
+ "updateAvatarDecoration": string;
+ "deleteAvatarDecoration": string;
+ };
+ "_fileViewer": {
+ "title": string;
+ "type": string;
+ "size": string;
+ "url": string;
+ "uploadedAt": string;
+ "attachedNotes": string;
+ "thisPageCanBeSeenFromTheAuthor": string;
+ };
+ "_externalResourceInstaller": {
+ "title": string;
+ "checkVendorBeforeInstall": string;
+ "_plugin": {
+ "title": string;
+ "metaTitle": string;
+ };
+ "_theme": {
+ "title": string;
+ "metaTitle": string;
+ };
+ "_meta": {
+ "base": string;
+ };
+ "_vendorInfo": {
+ "title": string;
+ "endpoint": string;
+ "hashVerify": string;
+ };
+ "_errors": {
+ "_invalidParams": {
+ "title": string;
+ "description": string;
+ };
+ "_resourceTypeNotSupported": {
+ "title": string;
+ "description": string;
+ };
+ "_failedToFetch": {
+ "title": string;
+ "fetchErrorDescription": string;
+ "parseErrorDescription": string;
+ };
+ "_hashUnmatched": {
+ "title": string;
+ "description": string;
+ };
+ "_pluginParseFailed": {
+ "title": string;
+ "description": string;
+ };
+ "_pluginInstallFailed": {
+ "title": string;
+ "description": string;
+ };
+ "_themeParseFailed": {
+ "title": string;
+ "description": string;
+ };
+ "_themeInstallFailed": {
+ "title": string;
+ "description": string;
+ };
+ };
+ };
}
declare const locales: {
[lang: string]: Locale;
diff --git a/locales/index.js b/locales/index.js
index 7801f1275b..67a406d98d 100644
--- a/locales/index.js
+++ b/locales/index.js
@@ -53,6 +53,19 @@ const clean = (text) => text.replace(new RegExp(String.fromCodePoint(0x08), 'g')
const locales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(new URL(`${c}.yml`, import.meta.url), 'utf-8'))) || {}, a), {});
+// 空文字列が入ることがあり、フォールバックが動作しなくなるのでプロパティごと消す
+const removeEmpty = (obj) => {
+ for (const [k, v] of Object.entries(obj)) {
+ if (v === '') {
+ delete obj[k];
+ } else if (typeof v === 'object') {
+ removeEmpty(v);
+ }
+ }
+ return obj;
+};
+removeEmpty(locales);
+
export default Object.entries(locales)
.reduce((a, [k ,v]) => (a[k] = (() => {
const [lang] = k.split('-');
@@ -63,7 +76,7 @@ export default Object.entries(locales)
default: return merge(
locales['ja-JP'],
locales['en-US'],
- locales[`${lang}-${primaries[lang]}`] || {},
+ locales[`${lang}-${primaries[lang]}`] ?? {},
v
);
}
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index ee98106bcf..dd0abf2287 100644
--- a/locales/it-IT.yml
+++ b/locales/it-IT.yml
@@ -21,7 +21,7 @@ noNotifications: "Nessuna notifica"
instance: "Istanza"
settings: "Impostazioni"
notificationSettings: "Preferenze di notifica"
-basicSettings: "Impostazioni generali"
+basicSettings: "Impostazioni base"
otherSettings: "Altre impostazioni"
openInWindow: "Apri in una finestra"
profile: "Profilo"
@@ -45,6 +45,7 @@ pin: "Fissa sul profilo"
unpin: "Non fissare sul profilo"
copyContent: "Copia il contenuto"
copyLink: "Copia il link"
+copyLinkRenote: "Copia collegamento alla Rinota"
delete: "Elimina"
deleteAndEdit: "Elimina e modifica"
deleteAndEditConfirm: "Vuoi davvero cancellare questa nota e scriverla di nuovo? Verranno eliminate anche tutte le reazioni, rinote e risposte collegate."
@@ -63,9 +64,9 @@ reply: "Rispondi"
loadMore: "Mostra di più"
showMore: "Espandi"
showLess: "Comprimi"
-youGotNewFollower: "Ha iniziato a seguirti"
+youGotNewFollower: "Adesso ti segue"
receiveFollowRequest: "Hai ricevuto una richiesta di follow"
-followRequestAccepted: "Richiesta di follow accettata"
+followRequestAccepted: "Ha accettato la tua richiesta di follow"
mention: "Menzioni"
mentions: "Menzioni"
directNotes: "Note dirette"
@@ -74,17 +75,17 @@ import: "Importa"
export: "Esporta"
files: "Allegati"
download: "Scarica"
-driveFileDeleteConfirm: "Vuoi davvero eliminare il file \"{name}\"? Anche gli allegati verranno eliminati."
-unfollowConfirm: "Vuoi smettere di seguire {name}?"
+driveFileDeleteConfirm: "Vuoi davvero eliminare il file \"{name}\", e le Note a cui è stato allegato?"
+unfollowConfirm: "Vuoi davvero smettere di seguire {name}?"
exportRequested: "Hai richiesto un'esportazione, e potrebbe volerci tempo. Quando sarà compiuta, il file verrà aggiunto direttamente al Drive."
-importRequested: "Hai richiesto un'importazione. Può volerci tempo. "
+importRequested: "Hai richiesto un'importazione. Potrebbe richiedere un po' di tempo."
lists: "Liste"
noLists: "Nessuna lista"
note: "Nota"
notes: "Note"
following: "Follow"
followers: "Follower"
-followsYou: "Ti segue"
+followsYou: "Segue"
createList: "Aggiungi una nuova lista"
manageLists: "Gestisci liste"
error: "Errore"
@@ -105,18 +106,18 @@ unfollow: "Non seguire"
followRequestPending: "Richiesta in approvazione"
enterEmoji: "Inserisci emoji"
renote: "Rinota"
-unrenote: "Annulla rinota"
+unrenote: "Elimina la Rinota"
renoted: "Rinotato!"
cantRenote: "È impossibile rinotare questa nota."
cantReRenote: "È impossibile rinotare una Rinota."
-quote: "Cita"
+quote: "Citazione"
inChannelRenote: "Rinota nel canale"
inChannelQuote: "Cita nel canale"
-pinnedNote: "Nota fissata"
+pinnedNote: "Nota in primo piano"
pinned: "Fissa sul profilo"
you: "Tu"
-clickToShow: "Clicca per visualizzare"
-sensitive: "Contenuto sensibile"
+clickToShow: "Contenuto occultato, cliccare solo se si intende vedere"
+sensitive: "Allegato esplicito"
add: "Aggiungi"
reaction: "Reazioni"
reactions: "Reazioni"
@@ -124,20 +125,20 @@ reactionSetting: "Reazioni visualizzate sul pannello"
reactionSettingDescription2: "Trascina per riorganizzare, clicca per cancellare, usa il pulsante \"+\" per aggiungere."
rememberNoteVisibility: "Ricordare le impostazioni di visibilità delle note"
attachCancel: "Rimuovi allegato"
-markAsSensitive: "Segna come sensibile"
-unmarkAsSensitive: "Segna come non sensibile"
+markAsSensitive: "Segna come esplicito"
+unmarkAsSensitive: "Non segnare come esplicito "
enterFileName: "Nome del file"
mute: "Silenzia"
unmute: "Riattiva l'audio"
-renoteMute: "Silenzia i Rinota"
-renoteUnmute: "Non silenziare i Rinota"
+renoteMute: "Silenzia le Rinota"
+renoteUnmute: "Non silenziare le Rinota"
block: "Blocca"
unblock: "Sblocca"
-suspend: "Sospendi"
+suspend: "Sospensione"
unsuspend: "Revoca la sospensione"
blockConfirm: "Vuoi davvero bloccare il profilo?"
unblockConfirm: "Vuoi davvero sbloccare il profilo?"
-suspendConfirm: "Vuoi sospendere questo profilo?"
+suspendConfirm: "Vuoi davvero sospendere questo profilo?"
unsuspendConfirm: "Vuoi revocare la sospensione si questo profilo?"
selectList: "Seleziona una lista"
editList: "Modifica Lista"
@@ -147,7 +148,7 @@ editAntenna: "Modifica Antenna"
selectWidget: "Seleziona il riquadro"
editWidgets: "Modifica i riquadri"
editWidgetsExit: "Conferma le modifiche"
-customEmojis: "Emoji personalizzati"
+customEmojis: "Emoji personalizzate"
emoji: "Emoji"
emojis: "Emoji"
emojiName: "Nome dell'emoji"
@@ -156,15 +157,16 @@ addEmoji: "Aggiungi un emoji"
settingGuide: "Configurazione suggerita"
cacheRemoteFiles: "Memorizza i file remoti nella cache"
cacheRemoteFilesDescription: "Disabilitando questa opzione, i file remoti verranno linkati direttamente senza essere memorizzati nella cache. Sarà possibile risparmiare spazio di archiviazione sul server, ma il traffico aumenterà in quanto non verranno generate anteprime."
-cacheRemoteSensitiveFiles: "Memorizza nella cache i file sensibili remoti"
-cacheRemoteSensitiveFilesDescription: "Disattivando questa opzione, i file sensibili verranno caricati direttamente dall'istanza remota senza essere salvati dal server."
+youCanCleanRemoteFilesCache: "Puoi svuotare tutta la cache cliccando il bottone 🗑️ nella gestione file"
+cacheRemoteSensitiveFiles: "Copia nella cache locale i file espliciti remoti"
+cacheRemoteSensitiveFilesDescription: "Disattivando questa opzione, i file espliciti verranno richiesti direttamente all'istanza remota senza essere salvati nel server locale."
flagAsBot: "Io sono un robot"
flagAsBotDescription: "Attiva questo campo se il profilo esegue principalmente operazioni automatiche. L'attivazione segnala agli altri sviluppatori come comportarsi per evitare catene d’interazione infinite con altri bot. I sistemi interni di Misskey si adegueranno al fine di trattare questo profilo come bot."
-flagAsCat: "Sono un gatto"
-flagAsCatDescription: "La modalità \"sono un gatto\" aggiunge le orecchie al tuo profilo"
+flagAsCat: "MIIaaaoo!!! (Io sono un gatto è un romanzo del 1905, il primo dello scrittore giapponese Natsume Sōseki)"
+flagAsCatDescription: "Miaoo mia miao mi miao?"
flagShowTimelineReplies: "Mostra le risposte alle note sulla timeline."
flagShowTimelineRepliesDescription: "Attivando, la timeline mostra le Note del profilo ed anche le risposte ad altre Note"
-autoAcceptFollowed: "Accetta automaticamente le richieste di follow da utenti che già segui"
+autoAcceptFollowed: "Accetta automaticamente le richieste di follow da profili che già segui"
addAccount: "Aggiungi profilo"
reloadAccountsList: "Ricarica l'elenco dei profili"
loginFailed: "Accesso non riuscito"
@@ -178,13 +180,13 @@ youHaveNoLists: "Non hai ancora creato nessuna lista"
followConfirm: "Vuoi seguire {name}?"
proxyAccount: "Profilo proxy"
proxyAccountDescription: "Un profilo proxy funziona come follower per i profili remoti, sotto certe condizioni. Ad esempio, quando un profilo locale ne inserisce uno remoto in una lista (senza seguirlo), se nessun altro segue quel profilo remoto, le attività non possono essere distribuite. Dunque, il profilo proxy le seguirà per tutti."
-host: "Server remoto"
+host: "Host"
selectUser: "Seleziona profilo"
recipient: "Destinatario"
annotation: "Annotazione preventiva"
federation: "Federazione"
instances: "Istanza"
-registeredAt: "Registrato presso"
+registeredAt: "Prima federazione"
latestRequestReceivedAt: "Ultima richiesta ricevuta"
latestStatus: "Ultimo stato"
storageUsage: "Capienza dei dischi"
@@ -193,6 +195,7 @@ perHour: "orario"
perDay: "giornaliero"
stopActivityDelivery: "Interrompi la distribuzione di attività"
blockThisInstance: "Blocca questa istanza"
+silenceThisInstance: "Silenzia l'istanza"
operations: "Operazioni"
software: "Software"
version: "Versione"
@@ -212,6 +215,8 @@ clearCachedFiles: "Svuota cache"
clearCachedFilesConfirm: "Vuoi davvero svuotare la cache da tutti i file remoti?"
blockedInstances: "Istanze bloccate"
blockedInstancesDescription: "Elenca le istanze che vuoi bloccare, una per riga. Esse non potranno più interagire con la tua istanza."
+silencedInstances: "Istanze silenziate"
+silencedInstancesDescription: "Elenca i nomi host delle istanze che vuoi silenziare. Tutti i profili nelle istanze silenziate vengono trattati come tali. Possono solo inviare richieste di follow e menzionare soltanto i profili locali che seguono. Le istanze bloccate non sono interessate."
muteAndBlock: "Silenziati / Bloccati"
mutedUsers: "Profili silenziati"
blockedUsers: "Profili bloccati"
@@ -236,7 +241,7 @@ publishing: "Pubblicazione"
notResponding: "Nessuna risposta"
instanceFollowing: "Seguiti dall'istanza"
instanceFollowers: "Follower dell'istanza"
-instanceUsers: "Utenti dell'istanza"
+instanceUsers: "Profili nell'istanza"
changePassword: "Aggiorna Password"
security: "Sicurezza"
retypedNotMatch: "Le password non corrispondono."
@@ -245,7 +250,7 @@ newPassword: "Nuova Password"
newPasswordRetype: "Conferma password"
attachFile: "Allega file"
more: "Di più!"
-featured: "Tendenze"
+featured: "In evidenza"
usernameOrUserId: "Nome utente o ID"
noSuchUser: "Profilo non trovato"
lookup: "Ricerca remota"
@@ -254,7 +259,7 @@ imageUrl: "URL dell'immagine"
remove: "Elimina"
removed: "Eliminato con successo"
removeAreYouSure: "Vuoi davvero eliminare \"{x}\"?"
-deleteAreYouSure: "Eliminare \"{x}\"?"
+deleteAreYouSure: "Vuoi davvero eliminare \"{x}\"?"
resetAreYouSure: "Ripristinare?"
saved: "Salvato"
messaging: "Messaggi"
@@ -273,10 +278,10 @@ noMoreHistory: "Non c'è più cronologia da visualizzare"
startMessaging: "Nuovo messaggio"
nUsersRead: "Letto da {n} persone"
agreeTo: "Sono d'accordo con {0}"
-agree: "D'accordo"
+agree: "Accetto"
agreeBelow: "Accetto quanto riportato sotto"
basicNotesBeforeCreateAccount: "Note importanti"
-termsOfService: "Informativa Privacy"
+termsOfService: "Condizioni d'uso del servizio"
start: "Inizia!"
home: "Home"
remoteUserCaution: "Le informazioni potrebbero essere incomplete poiché questo profilo remoto potrebbe non essere completamente federato."
@@ -285,7 +290,7 @@ images: "Immagini"
image: "Immagini"
birthday: "Compleanno"
yearsOld: "{age} anni"
-registeredDate: "Iscrizione a.."
+registeredDate: "Data iscrizione"
location: "Posizione"
theme: "Tema"
themeForLightMode: "Tema da utilizzare per il modo chiaro"
@@ -319,11 +324,11 @@ copyUrl: "Copia URL"
rename: "Modifica nome"
avatar: "Foto del profilo"
banner: "Intestazione"
-displayOfSensitiveMedia: "Visibilità dei media sensibili"
+displayOfSensitiveMedia: "Visibilità dei media espliciti"
whenServerDisconnected: "Quando la connessione col server è persa"
-disconnectedFromServer: "Il server si è disconnesso"
+disconnectedFromServer: "Connessione persa"
reload: "Ricarica"
-doNothing: "Nessun'azione"
+doNothing: "Ignora"
reloadConfirm: "Vuoi ricaricare?"
watch: "Osserva"
unwatch: "Smetti di Osserva"
@@ -334,7 +339,7 @@ instanceName: "Nome dell'istanza"
instanceDescription: "Descrizione dell'istanza"
maintainerName: "Nome dell'amministratore"
maintainerEmail: "Indirizzo e-mail dell'amministratore"
-tosUrl: "URL dei termini del servizio e della privacy"
+tosUrl: "URL delle condizioni d'uso"
thisYear: "Anno"
thisMonth: "Mese"
today: "Oggi"
@@ -354,16 +359,15 @@ invite: "Invita"
driveCapacityPerLocalAccount: "Capienza del Drive per profilo locale"
driveCapacityPerRemoteAccount: "Capienza del Drive per profilo remoto"
inMb: "in Megabytes"
-iconUrl: "URL di icona (favicon, ecc.)"
bannerUrl: "URL dell'immagine d'intestazione"
backgroundImageUrl: "URL dello sfondo"
basicInfo: "Informazioni fondamentali"
-pinnedUsers: "Utenti in evidenza"
-pinnedUsersDescription: "Elenca gli/le utenti che vuoi fissare in cima alla pagina \"Esplora\", un@ per riga."
+pinnedUsers: "Profili in evidenza"
+pinnedUsersDescription: "Elenca i profili delle persone che vuoi fissare nella pagina \"Esplora\"."
pinnedPages: "Pagine in evidenza"
pinnedPagesDescription: "Specifica il percorso delle pagine che vuoi fissare in cima alla pagina dell'istanza. Una pagina per riga."
pinnedClipId: "ID della Clip in evidenza"
-pinnedNotes: "Nota fissata"
+pinnedNotes: "Note in primo piano"
hcaptcha: "hCaptcha"
enableHcaptcha: "Abilita hCaptcha"
hcaptchaSiteKey: "Chiave del sito"
@@ -383,25 +387,25 @@ name: "Nome"
antennaSource: "Fonte dell'antenna"
antennaKeywords: "Parole chiavi da ricevere"
antennaExcludeKeywords: "Parole chiavi da escludere"
-antennaKeywordsDescription: "Separare con uno spazio indica la condizione \"E\". Separare con un'interruzzione riga indica la condizione \"O\"."
+antennaKeywordsDescription: "Sparando con uno spazio indichi la condizione E (and). Separando con un a capo, indichi la condizione O (or)."
notifyAntenna: "Invia notifiche delle nuove note"
withFileAntenna: "Solo note con file in allegato"
enableServiceworker: "Abilita ServiceWorker"
-antennaUsersDescription: "Inserisci solo un nome utente per riga"
+antennaUsersDescription: "Elenca un nome utente per riga"
caseSensitive: "Sensibile alla distinzione tra maiuscole e minuscole"
withReplies: "Includere le risposte"
connectedTo: "Connessione ai seguenti profili:"
notesAndReplies: "Note e risposte"
-withFiles: "Con file in allegato"
+withFiles: "Con allegati"
silence: "Silenzia"
-silenceConfirm: "Vuoi davvero silenziare l'utente?"
+silenceConfirm: "Vuoi davvero silenziare questo profilo?"
unsilence: "Riattiva"
-unsilenceConfirm: "Vuoi davvero riattivare l'utente?"
-popularUsers: "Utenti popolari"
+unsilenceConfirm: "Vuoi davvero riattivare questo profilo?"
+popularUsers: "Profili popolari"
recentlyUpdatedUsers: "Utenti attivi di recente"
-recentlyRegisteredUsers: "Utenti registrati di recente"
-recentlyDiscoveredUsers: "Utenti scoperti di recente"
-exploreUsersCount: "Ci sono {count} utenti"
+recentlyRegisteredUsers: "Profili iscritti di recente"
+recentlyDiscoveredUsers: "Profili scoperti di recente"
+exploreUsersCount: "Ci sono {count} profili"
exploreFediverse: "Esplora il Fediverso"
popularTags: "Tag di tendenza"
userList: "Liste"
@@ -410,10 +414,14 @@ aboutMisskey: "Informazioni di Misskey"
administrator: "Amministratore"
token: "Token"
2fa: "Autenticazione a due fattori"
-totp: "App di autenticazione"
-totpDescription: "Inserisci un codice OTP tramite un'app di autenticazione"
+setupOf2fa: "Impostare l'autenticazione a due fattori"
+totp: "App di autenticazione a due fattori (2FA/MFA)"
+totpDescription: "Puoi autenticarti inserendo un codice OTP tramite la tua App di autenticazione a due fattori (2FA/MFA)"
moderator: "Moderatore"
moderation: "moderazione"
+moderationNote: "Promemoria di moderazione"
+addModerationNote: "Aggiungi promemoria di moderazione"
+moderationLogs: "Cronologia di moderazione"
nUsersMentioned: "{n} profili menzionati"
securityKeyAndPasskey: "Chiave di sicurezza e accesso"
securityKey: "Chiave di sicurezza"
@@ -455,8 +463,8 @@ invitations: "Invita"
invitationCode: "Codice di invito"
checking: "Confermando"
available: "Disponibile"
-unavailable: "Il nome utente è già in uso"
-usernameInvalidFormat: "Il nome utente può contenere solo lettere, numeri e '_'"
+unavailable: "Non puoi usarlo"
+usernameInvalidFormat: "Il nome utente deve avere solo caratteri alfanumerici e trattino basso '_'"
tooShort: "Troppo breve"
tooLong: "Troppo lungo"
weakPassword: "Password debole"
@@ -492,7 +500,7 @@ noFollowRequests: "Non hai alcuna richiesta di follow"
openImageInNewTab: "Apri le immagini in un nuovo tab"
dashboard: "Pannello di controllo"
local: "Locale"
-remote: "Remoto"
+remote: "Remota"
total: "Totale"
weekOverWeekChanges: "Settimanale"
dayOverDayChanges: "Giornaliero"
@@ -526,9 +534,10 @@ serverLogs: "Log del server"
deleteAll: "Cancella cronologia"
showFixedPostForm: "Visualizzare la finestra di pubblicazione in cima alla timeline"
showFixedPostFormInChannel: "Per i canali, mostra il modulo di pubblicazione in cima alla timeline"
-newNoteRecived: "Vedi le nuove note"
+withRepliesByDefaultForNewlyFollowed: "Quando segui nuovi profili, includi le risposte in TL come impostazione predefinita"
+newNoteRecived: "Nuove note da leggere"
sounds: "Impostazioni suoni"
-sound: "Impostazioni suoni"
+sound: "Suono"
listen: "Ascolta"
none: "Nessuno"
showInPage: "Visualizza in pagina"
@@ -547,20 +556,20 @@ installedDate: "Data installazione"
lastUsedDate: "Data di ultimo uso"
state: "Stato"
sort: "Ordina per"
-ascendingOrder: "Ascendente"
-descendingOrder: "Discendente"
+ascendingOrder: "Aumenta"
+descendingOrder: "Diminuisce"
scratchpad: "ScratchPad"
scratchpadDescription: "Lo Scratchpad offre un ambiente per esperimenti di AiScript. È possibile scrivere, eseguire e confermare i risultati dell'interazione del codice con Misskey."
output: "Uscita"
script: "Script"
disablePagesScript: "Disabilita AiScript nelle pagine"
-updateRemoteUser: "Aggiornare le informazioni di utente remot@"
+updateRemoteUser: "Aggiorna le informazioni dal profilo remoto"
deleteAllFiles: "Elimina tutti i file"
deleteAllFilesConfirm: "Vuoi davvero eliminare tutti i file?"
-removeAllFollowing: "Cancella tutti i follows"
+removeAllFollowing: "Annulla tutti i follow"
removeAllFollowingDescription: "Cancella tutti i follows del server {host}. Per favore, esegui se, ad esempio, l'istanza non esiste più."
userSuspended: "L'utente è in sospensione"
-userSilenced: "L'utente è silenziat@."
+userSilenced: "Profilo silente."
yourAccountSuspendedTitle: "Questo profilo è sospeso"
yourAccountSuspendedDescription: "Questo profilo è stato sospeso a causa di una violazione del regolamento. Per informazioni, contattare l'amministrazione. Si prega di non creare un nuovo account."
tokenRevoked: "Il token non è valido"
@@ -581,7 +590,7 @@ invisibleNote: "Nota invisibile"
enableInfiniteScroll: "Abilita scorrimento infinito"
visibility: "Visibilità"
poll: "Sondaggio"
-useCw: "Content Warning"
+useCw: "Contenuto esplicito"
enablePlayer: "Visualizza"
disablePlayer: "Chiudi"
expandTweet: "Espandi tweet"
@@ -617,7 +626,7 @@ emailConfigInfo: "Utilizzato per verificare il tuo indirizzo di posta elettronic
email: "Email"
emailAddress: "Indirizzo di posta elettronica"
smtpConfig: "Impostazioni del server SMTP"
-smtpHost: "Server remoto"
+smtpHost: "Host SMTP"
smtpPort: "Porta"
smtpUser: "Nome utente"
smtpPass: "Password"
@@ -644,7 +653,7 @@ notificationSetting: "Impostazioni notifiche"
notificationSettingDesc: "Seleziona il tipo di notifiche da visualizzare."
useGlobalSetting: "Usa impostazioni generali"
useGlobalSettingDesc: "Quando attiva, verranno utilizzate le impostazioni notifiche del profilo. Altrimenti si possono segliere impostazioni personalizzate."
-other: "Avanzate"
+other: "Ulteriori"
regenerateLoginToken: "Genera di nuovo un token di connessione"
regenerateLoginTokenDescription: "Genera un nuovo token di autenticazione. Solitamente questa operazione non è necessaria: quando si genera un nuovo token, tutti i dispositivi vanno disconnessi."
setMultipleBySeparatingWithSpace: "È possibile creare multiple voci separate da spazi."
@@ -652,9 +661,10 @@ fileIdOrUrl: "ID o URL del file"
behavior: "Comportamento"
sample: "Esempio"
abuseReports: "Segnalazioni"
-reportAbuse: "Segnalazioni"
+reportAbuse: "Segnala"
+reportAbuseRenote: "Segnala la Rinota"
reportAbuseOf: "Segnala {name}"
-fillAbuseReportDescription: "Si prega di spiegare il motivo della segnalazione. Se riguarda una nota precisa, si prega di collegare anche l'URL della nota."
+fillAbuseReportDescription: "Per favore, spiegaci il motivo della segnalazione. Se riguarda una Nota precisa, indica anche l'indirizzo URL."
abuseReported: "La segnalazione è stata inviata. Grazie."
reporter: "il corrispondente"
reporteeOrigin: "Origine del segnalato"
@@ -680,6 +690,7 @@ createNewClip: "Crea una Clip"
unclip: "Togli Nota dalla Clip"
confirmToUnclipAlreadyClippedNote: "Questa nota è già inclusa in \"{name}\". Si desidera escludere la nota?"
public: "Pubblica"
+private: "Privato"
i18nInfo: "Misskey è tradotto in diverse lingue da volontari. Anche tu puoi contribuire su {link}."
manageAccessTokens: "Gestisci token di accesso"
accountInfo: "Informazioni profilo"
@@ -701,9 +712,10 @@ driveUsage: "Utilizzazione del Drive"
noCrawle: "Rifiuta l'indicizzazione dai robot."
noCrawleDescription: "Richiedi che i motori di ricerca non indicizzino la tua pagina di profilo, le tue note, pagine, ecc."
lockedAccountInfo: "A meno che non imposti la visibilità delle tue note su \"Solo ai follower\", le tue note sono visibili da tutti, anche se hai configurato l'account per confermare manualmente le richieste di follow."
-alwaysMarkSensitive: "Segnare i media come sensibili per impostazione predefinita"
+alwaysMarkSensitive: "Segnare gli allegati come espliciti come opzione predefinita"
loadRawImages: "Visualizza le intere immagini allegate invece delle miniature."
disableShowingAnimatedImages: "Disabilita le immagini animate"
+highlightSensitiveMedia: "Evidenzia i media espliciti"
verificationEmailSent: "Una mail di verifica è stata inviata. Si prega di accedere al collegamento per compiere la verifica."
notSet: "Non impostato"
emailVerified: "Il tuo indirizzo email è stato verificato"
@@ -729,8 +741,8 @@ reloadToApplySetting: "Le tue preferenze verranno impostate dopo il ricaricament
needReloadToApply: "È necessario riavviare per rendere effettive le modifiche."
showTitlebar: "Visualizza la barra del titolo"
clearCache: "Svuota la cache"
-onlineUsersCount: "{n} utenti online"
-nUsers: "{n} utenti"
+onlineUsersCount: "{n} persone online"
+nUsers: "{n} profili"
nNotes: "{n}Note"
sendErrorReports: "Invia segnalazioni di errori"
sendErrorReportsDescription: "Quando abilitato, se si verifica un problema, informazioni dettagliate sugli errori verranno condivise con Misskey in modo da aiutare a migliorare la qualità del software.\nCiò include informazioni come la versione del sistema operativo, il tipo di navigatore web che usi, la cronologia delle attività, ecc."
@@ -760,7 +772,7 @@ editCode: "Modifica codice"
apply: "Applica"
receiveAnnouncementFromInstance: "Ricevi i messaggi informativi dall'istanza"
emailNotification: "Eventi per notifiche via mail"
-publish: "Pubblico"
+publish: "Pubblicare"
inChannelSearch: "Cerca in canale"
useReactionPickerForContextMenu: "Cliccare sul tasto destro per aprire il pannello di reazioni"
typingUsers: "{users} sta(nno) scrivendo"
@@ -776,7 +788,7 @@ addDescription: "Aggiungi descrizione"
userPagePinTip: "Qui puoi appuntare note, premendo \"Fissa sul profilo\" nel menù delle singole note."
notSpecifiedMentionWarning: "Sono stati menzionati profili non inclusi fra i destinatari"
info: "Informazioni"
-userInfo: "Informazioni utente"
+userInfo: "Informazioni sul profilo"
unknown: "Sconosciuto"
onlineStatus: "Stato di connessione"
hideOnlineStatus: "Modalità invisibile"
@@ -796,8 +808,8 @@ user: "Profilo"
administration: "Gestione"
accounts: "Profilo"
switch: "Cambia"
-noMaintainerInformationWarning: "Le informazioni amministratore non sono impostate."
-noBotProtectionWarning: "Nessuna protezione impostata contro i bot."
+noMaintainerInformationWarning: "Mancano le informazioni sull'amministratore."
+noBotProtectionWarning: "Non è stata impostata alcuna protezione dai Bot"
configure: "Imposta"
postToGallery: "Pubblicare nella galleria"
postToHashtag: "Pubblica a questo hashtag"
@@ -819,8 +831,8 @@ previewNoteText: "Anteprima del testo"
customCss: "CSS personalizzato"
customCssWarn: "Questa impostazione deve essere eseguita da una persona esperta. Una configurazione errata può impedire al client di utilizzare correttamente il sistema."
global: "Federata"
-squareAvatars: "Mostra l'immagine del profilo come quadrato"
-sent: "Inviare"
+squareAvatars: "Foto profilo squadrate"
+sent: "Inviato"
received: "Ricevuto"
searchResult: "Risultati della Ricerca"
hashtags: "Hashtag"
@@ -829,19 +841,19 @@ useBlurEffect: "Utilizza effetto sfocatura"
learnMore: "Più dettagli"
misskeyUpdated: "Misskey è stato aggiornato!"
whatIsNew: "Visualizza le informazioni sull'aggiornamento"
-translate: "Traduzione"
-translatedFrom: "Tradotto da {x}"
+translate: "Traduci"
+translatedFrom: "Traduzione da {x}"
accountDeletionInProgress: "È in corso l'eliminazione del profilo"
usernameInfo: "Un nome per identificare univocamente il tuo profilo sull'istanza. Puoi utilizzare caratteri alfanumerici maiuscoli, minuscoli e il trattino basso (_). Non potrai cambiare nome utente in seguito."
aiChanMode: "Modalità Ai"
devMode: "Modalità sviluppatori"
-keepCw: "Mantieni il Content Warning"
+keepCw: "Mostra i contenuti espliciti"
pubSub: "Publish/Subscribe del profilo"
lastCommunication: "La comunicazione più recente"
resolved: "Risolto"
unresolved: "Non risolto"
-breakFollow: "Non seguire"
-breakFollowConfirm: "Vuoi davvero togliere follower?"
+breakFollow: "Non farti più seguire"
+breakFollowConfirm: "Vuoi davvero che questo profilo smetta di seguirti?"
itsOn: "Abilitato"
itsOff: "Disabilitato"
on: "Acceso"
@@ -854,11 +866,11 @@ manageAccounts: "Gestisci i profili"
makeReactionsPublic: "Pubblicare la lista delle reazioni."
makeReactionsPublicDescription: "La lista delle reazioni che avete fatto è a disposizione di tutti."
classic: "Classico"
-muteThread: "Silenzia la conversazione"
+muteThread: "Silenzia conversazione"
unmuteThread: "Riattiva la conversazione"
ffVisibility: "Visibilità delle connessioni"
ffVisibilityDescription: "Puoi scegliere a chi mostrare le tue relazioni con altri profili nel fediverso."
-continueThread: "Altri thread."
+continueThread: "Altre conversazioni"
deleteAccountConfirm: "Così verrà eliminato il profilo. Vuoi procedere?"
incorrectPassword: "La password è errata."
voteConfirm: "Votare per「{choice}」?"
@@ -899,7 +911,7 @@ noEmailServerWarning: "Il server di posta non è configurato."
thereIsUnresolvedAbuseReportWarning: "Ci sono report non evasi."
recommended: "Consigliato"
check: "Verifica"
-driveCapOverrideLabel: "Modificare il limite di spazio per questo utente"
+driveCapOverrideLabel: "Modificare la capienza del Drive per questo profilo"
driveCapOverrideCaption: "Se viene specificato meno di 0, viene annullato."
requireAdminForView: "Per visualizzarli, è necessario aver effettuato l'accesso con un profilo amministratore."
isSystemAccount: "Questi profili vengono creati e gestiti automaticamente dal sistema"
@@ -920,7 +932,7 @@ type: "Tipo"
speed: "Velocità"
slow: "Lento"
fast: "Veloce"
-sensitiveMediaDetection: "Rilevamento dei contenuti sensibili."
+sensitiveMediaDetection: "Rilevamento dei contenuti espliciti"
localOnly: "Soltanto locale"
remoteOnly: "Solo remoto"
failedToUpload: "errore di caricamento"
@@ -967,6 +979,7 @@ assign: "Assegna"
unassign: "Disassegna"
color: "Colore"
manageCustomEmojis: "Gestisci le emoji personalizzate"
+manageAvatarDecorations: "Gestire le decorazioni di foto del profilo"
youCannotCreateAnymore: "Non puoi creare, hai raggiunto il limite."
cannotPerformTemporary: "Indisponibilità temporanea"
cannotPerformTemporaryDescription: "L'attività non può essere svolta, poiché si è raggiunto il limite di esecuzioni possibili. Per favore, riprova più tardi."
@@ -983,7 +996,7 @@ thisPostMayBeAnnoying: "Questa nota potrebbe essere offensiva"
thisPostMayBeAnnoyingHome: "Pubblica sulla timeline principale"
thisPostMayBeAnnoyingCancel: "Annulla"
thisPostMayBeAnnoyingIgnore: "Pubblica lo stesso"
-collapseRenotes: "Comprimi i Rinota già letti"
+collapseRenotes: "Comprimi le Rinota già viste"
internalServerError: "Errore interno del server"
internalServerErrorDescription: "Si è verificato un errore imprevisto all'interno del server"
copyErrorInfo: "Copia le informazioni sull'errore"
@@ -1000,11 +1013,11 @@ cannotBeChangedLater: "Non sarà più modificabile"
reactionAcceptance: "Reazioni consentite"
likeOnly: "Solo i Like"
likeOnlyForRemote: "Solo Like remoti"
-nonSensitiveOnly: "Solamente non sensibili"
-nonSensitiveOnlyForLocalLikeOnlyForRemote: "Solamente non sensibili (solo Mi piace remoti)"
+nonSensitiveOnly: "Soltanto non espliciti"
+nonSensitiveOnlyForLocalLikeOnlyForRemote: "Soltanto non espliciti (reazioni remote)"
rolesAssignedToMe: "I miei ruoli"
resetPasswordConfirm: "Vuoi davvero ripristinare la password?"
-sensitiveWords: "Parole sensibili"
+sensitiveWords: "Parole esplicite"
sensitiveWordsDescription: "Imposta automaticamente \"Home\" alla visibilità delle Note che contengono una qualsiasi parola tra queste configurate. Puoi separarle per riga."
sensitiveWordsDescription2: "Gli spazi creano la relazione \"E\" tra parole (questo E quello). Racchiudere una parola nelle slash \"/\" la trasforma in Espressione Regolare."
notesSearchNotAvailable: "Non è possibile cercare tra le Note."
@@ -1018,7 +1031,7 @@ retryAllQueuesConfirmText: "Potrebbe sovraccaricare il server temporaneamente."
enableChartsForRemoteUser: "Abilita i grafici per i profili remoti"
enableChartsForFederatedInstances: "Abilita i grafici per le istanze federate"
showClipButtonInNoteFooter: "Aggiungi il bottone Clip tra le azioni delle Note"
-largeNoteReactions: "Ingrandisci le reazioni"
+reactionsDisplaySize: "Grandezza delle reazioni"
noteIdOrUrl: "ID della Nota o URL"
video: "Video"
videos: "Video"
@@ -1030,8 +1043,8 @@ operationForbidden: "Operazione non consentita"
forceShowAds: "Mostra sempre i banner"
addMemo: "Aggiungi Memo"
editMemo: "Modifica Memo"
-reactionsList: "Elenco delle reazioni"
-renotesList: "Elenco di Rinota"
+reactionsList: "Chi ha reagito?"
+renotesList: "Chi ha Rinotato?"
notificationDisplay: "Stile delle notifiche"
leftTop: "In alto a sinistra"
rightTop: "In alto a destra"
@@ -1042,8 +1055,8 @@ vertical: "Verticale"
horizontal: "Laterale"
position: "Posizione"
serverRules: "Regolamento"
-pleaseConfirmBelowBeforeSignup: "Ai sensi del regolamento EU 679/2016 GDPR, autorizzo il trattamento dati personali come descritto nella informativa Privacy."
-pleaseAgreeAllToContinue: "Per continuare, occorre selezionare ed essere d'accordo su tutto."
+pleaseConfirmBelowBeforeSignup: "Per iscriversi, occorre essere d'accordo con le seguenti condizioni."
+pleaseAgreeAllToContinue: "Occorre accettare tutte le condizioni prima di continuare."
continue: "Continua"
preservedUsernames: "Nomi utente riservati"
preservedUsernamesDescription: "Elenca, uno per linea, i nomi utente che non possono essere registrati durante la creazione del profilo. La restrizione non si applica agli amministratori. Inoltre, i profili già registrati sono esenti."
@@ -1082,7 +1095,7 @@ inviteLimitExceeded: "Hai raggiunto il numero massimo di codici invito generabil
createLimitRemaining: "Inviti generabili: {limit} rimanenti"
inviteLimitResetCycle: "Alle {time}, il limite verrà ripristinato a {limit}"
expirationDate: "Scadenza"
-noExpirationDate: "Perpetuo"
+noExpirationDate: "Senza scadenza"
inviteCodeUsedAt: "Codice di invito usato alle"
registeredUserUsingInviteCode: "Codice di invito usato da"
waitingForMailAuth: "In attesa della verifica email"
@@ -1091,9 +1104,73 @@ usedAt: "Usato alle"
unused: "Inutilizzato"
used: "Utilizzato"
expired: "Scaduto"
-doYouAgree: "Sei d'accordo?"
+doYouAgree: "Accetti le condizioni?"
beSureToReadThisAsItIsImportant: "Si prega di leggere attentamente perché è importante."
-iHaveReadXCarefullyAndAgree: "Ho letto accuratamente \"{x}\" e sono d'accordo."
+iHaveReadXCarefullyAndAgree: "Dichiaro di aver letto attentamente \"{x}\" e accettarne le condizioni."
+dialog: "Dialogo"
+icon: "Ritratto"
+forYou: "Per te"
+currentAnnouncements: "Annunci attuali"
+pastAnnouncements: "Annunci precedenti"
+youHaveUnreadAnnouncements: "Ci sono Annunci non letti"
+useSecurityKey: "Per utilizzare la chiave di sicurezza o la passkey, segui le indicazioni del dispositivo"
+replies: "Rispondi"
+renotes: "Rinota"
+loadReplies: "Leggi le risposte"
+loadConversation: "Leggi la conversazione"
+pinnedList: "Elenco in primo piano"
+keepScreenOn: "Mantieni lo schermo acceso"
+verifiedLink: "Abbiamo confermato la validità di questo collegamento"
+notifyNotes: "Notifica nuove Note"
+unnotifyNotes: "Interrompi le notifiche di nuove Note"
+authentication: "Autenticazione"
+authenticationRequiredToContinue: "Per procedere, è richiesta l'autenticazione"
+dateAndTime: "Data e Ora"
+showRenotes: "Includi le Rinota"
+edited: "Modificato"
+notificationRecieveConfig: "Preferenze di notifica"
+mutualFollow: "Follow reciproco"
+fileAttachedOnly: "Solo con allegati"
+showRepliesToOthersInTimeline: "Risposte altrui nella TL"
+hideRepliesToOthersInTimeline: "Nascondi Riposte altrui nella TL"
+showRepliesToOthersInTimelineAll: "Mostra le risposte dei tuoi follow nella TL"
+hideRepliesToOthersInTimelineAll: "Nascondi le risposte dei tuoi follow nella TL"
+confirmShowRepliesAll: "Questa è una attività irreversibile. Vuoi davvero includere tutte le risposte dei following in TL?"
+confirmHideRepliesAll: "Questa è una attività irreversibile. Vuoi davvero escludere tutte le risposte dei following in TL?"
+externalServices: "Servizi esterni"
+impressum: "Dichiarazione di proprietà"
+impressumUrl: "URL della dichiarazione di proprietà"
+impressumDescription: "La dichiarazione di proprietà, è obbligatoria in alcuni paesi come la Germania (Impressum)."
+privacyPolicy: "Informativa ai sensi del Reg. UE 2016/679 (GDPR)"
+privacyPolicyUrl: "URL della informativa privacy"
+tosAndPrivacyPolicy: "Condizioni d'uso e informativa privacy"
+avatarDecorations: "Decorazioni foto profilo"
+attach: "Applica"
+detach: "Rimuovi"
+angle: "Angolo"
+flip: "Inverti"
+showAvatarDecorations: "Mostra decorazione della foto profilo"
+releaseToRefresh: "Rilascia per aggiornare"
+refreshing: "Aggiornamento..."
+pullDownToRefresh: "Trascina per aggiornare"
+disableStreamingTimeline: "Disabilitare gli aggiornamenti della TL in tempo reale"
+useGroupedNotifications: "Mostra le notifiche raggruppate"
+signupPendingError: "Si è verificato un problema durante la verifica del tuo indirizzo email. Potrebbe essere scaduto il collegamento temporaneo."
+cwNotationRequired: "Devi indicare perché il contenuto è indicato come esplicito."
+doReaction: "Reagisci"
+_announcement:
+ forExistingUsers: "Solo ai profili attuali"
+ forExistingUsersDescription: "L'annuncio sarà visibile solo ai profili esistenti in questo momento. Se disabilitato, sarà visibile anche ai profili che verranno creati dopo la pubblicazione di questo annuncio."
+ needConfirmationToRead: "Richiede la conferma di lettura"
+ needConfirmationToReadDescription: "Sarà visualizzata una finestra di dialogo che richiede la conferma di lettura. Inoltre, non è soggetto a conferme di lettura massicce."
+ end: "Archivia l'annuncio"
+ tooManyActiveAnnouncementDescription: "L'esperienza delle persone può peggiorare se ci sono troppi annunci attivi. Considera anche l'archiviazione degli annunci conclusi."
+ readConfirmTitle: "Segnare come già letto?"
+ readConfirmText: "Hai già letto \"{title}˝?"
+ shouldNotBeUsedToPresentPermanentInfo: "Ti consigliamo di utilizzare gli annunci per pubblicare informazioni tempestive e limitate nel tempo, anziché informazioni importanti a lungo andare nel tempo, poiché potrebbero risultare difficili da ritrovare e peggiorare la fruibilità del servizio, specialmente alle nuove persone iscritte."
+ dialogAnnouncementUxWarn: "Ti consigliamo di usarli con cautela, poiché è molto probabile che avere più di un annuncio in stile \"finestra di dialogo\" peggiori sensibilmente la fruibilità del servizio, specialmente alle nuove persone iscritte."
+ silence: "Silenzia gli annunci"
+ silenceDescription: "Se attivi questa opzione, non riceverai notifiche sugli annunci, evitando di contrassegnarle come già lette."
_initialAccountSetting:
accountCreated: "Il tuo profilo è stato creato!"
letsStartAccountSetup: "Per iniziare, impostiamo il tuo profilo."
@@ -1106,11 +1183,89 @@ _initialAccountSetting:
pushNotificationDescription: "Attivare le notifiche push ti permettera di ricevere informazioni sulla attività di {name} direttamente sul tuo dispositivo."
initialAccountSettingCompleted: "Hai completato la configurazione iniziale!"
haveFun: "Divertiti con {name}!"
- ifYouNeedLearnMore: "Per saperne di più su come usare {name} (Misskey), visita la pagina {link}"
+ youCanContinueTutorial: "Puoi continuare con l'esercitazione su come usare {name} (Misskey), oppure interrompere, iniziando subito a usarlo."
+ startTutorial: "Avvia l'esercitazione"
skipAreYouSure: "Vuoi davvero saltare la configurazione iniziale?"
laterAreYouSure: "Vuoi davvero rimandare la configurazione iniziale?"
+_initialTutorial:
+ launchTutorial: "Guarda il tutorial"
+ title: "Tutorial"
+ wellDone: "Ottimo lavoro!"
+ skipAreYouSure: "Vuoi davvero interrompere il tutorial?"
+ _landing:
+ title: "Eccoci nel tutorial"
+ description: "Qui puoi verificare l'uso delle funzionalità base di Misskey."
+ _note:
+ title: "Cosa sono le Note?"
+ description: "Gli status su Misskey sono chiamati \"Note\". Le Note sono elencate in ordine cronologico nelle timeline e vengono aggiornate in tempo reale."
+ reply: "Puoi rispondere alle Note. Puoi anche rispondere alle risposte e continuare i dialoghi come un conversazioni."
+ renote: "Puoi ri-condividere le Note, facendole rifluire sulla Timeline. Puoi anche aggiungere testo e citare altri profili."
+ reaction: "Puoi aggiungere una reazione. Nella pagina successiva spiegheremo i dettagli."
+ menu: "Puoi svolgere varie attività, come visualizzare i dettagli delle Note o copiare i collegamenti."
+ _reaction:
+ title: "Cosa sono le Reazioni?"
+ description: "Puoi reagire alle Note. Le sensazioni che non si riescono a trasmettere con i \"Mi piace\" si possono esprimere facilmente inviando una reazione."
+ letsTryReacting: "Puoi aggiungere una Reazione cliccando il bottone \"+\" (più) della relativa Nota. Prova ad aggiungerne una a questa Nota di esempio!"
+ reactToContinue: "Aggiungere la Reazione ti consentirà di procedere col tutorial."
+ reactNotification: "Quando qualcuno reagisce alle tue Note, ricevi una notifica in tempo reale."
+ reactDone: "Puoi annullare la tua Reazione premendo il bottone \"ー\" (meno)"
+ _timeline:
+ title: "Come funziona la Timeline"
+ description1: "Misskey fornisce alcune Timeline (sequenze cronologiche di Note). Una di queste potrebbe essere stata disattivata dagli amministratori."
+ home: "Puoi vedere le Note provenienti dai profili che segui (follow)."
+ local: "Puoi vedere tutte le Note pubblicate dai profili di questa istanza."
+ social: "Puoi vedere sia le Note della Timeline Home che quelle della Timeline Locale, insieme!"
+ global: "Puoi vedere le Note da pubblicate da tutte le altre istanze federate con la nostra."
+ description2: "Nella parte superiore dello schermo, puoi scegliere una Timeline o l'altra in qualsiasi momento."
+ description3: "Ci sono anche sequenze temporali di elenchi, sequenze temporali di canali, ecc. Per ulteriori dettagli, consultare il {link}.\nPuoi vedere anche Timeline delle liste di profili (se ne hai create), canali, ecc... Per i dettagli, visita {link}."
+ _postNote:
+ title: "La Nota e le sue impostazioni"
+ description1: "Quando scrivi una Nota su Misskey, hai a disposizione varie opzioni. Il modulo di invio è simile a questo."
+ _visibility:
+ description: "Puoi limitare chi può vedere la tua Nota."
+ public: "Visibile a tutti."
+ home: "Pubblicato solo sulla Timeline Home (personale). Visibile anche da profili remoti follower, visitatori del tuo profilo e tramite i Rinota dei follower."
+ followers: "Visibile solo ai profili tuoi follower (locali o remoti). Nessun altro oltre a te può \"Rinotare\"."
+ direct: "Visibile solo ai profili specificati, i quali riceveranno una notifica. Puoi usarlo come se fossero messaggi diretti."
+ doNotSendConfidencialOnDirect1: "Attenzione, quando si inviano informazioni confidenziali."
+ doNotSendConfidencialOnDirect2: "Poiché le Note non sono crittografate, l'amministratore del server di destinazione potrebbe leggere cosa è stato scritto, quindi se spedisci una Nota diretta a un profilo che risiede su un server non attendibile, evita di scrivere informazioni riservate."
+ localOnly: "Indipendentemente dalla visualizzazione sopra indicata, i profili su altri server non saranno in grado di visualizzare la Nota, se questa impostazione è attivata. Non non verrà comunicata ad altri server."
+ _cw:
+ title: "Nascondere il contenuto esplicito"
+ description: "Verrà visualizzato il testo scritto nel campo \"Annotazione preventiva\" al posto del testo principale della Nota. Premere il bottone \"Continua la lettura\" se si intende davvero leggere il testo."
+ _exampleNote:
+ cw: "Attenzione: contiene zuccheri"
+ note: "Ho appena mangiato una ciambella ricoperta di cioccolato 🍩😋"
+ useCases: "Utilizzalo per chiarire il contenuto della Nota, prima che sia letta. Come richiesto dal regolamento del server o per autoregolamentare spoiler e testi troppo espliciti."
+ _howToMakeAttachmentsSensitive:
+ title: "Come indicare che gli allegati sono espliciti?"
+ description: "Contrassegnare gli allegati come espliciti, va fatto quando è richiesto dal regolamento del server o quando gli allegati non devono essere immediatamente visibili."
+ tryThisFile: "Prova a rendere esplicite le immagini allegate a questo modulo!"
+ _exampleNote:
+ note: "Ho fatto un errore aprendo il coperchio del natto... (fagioli di soia fermentati, particolarmente appiccicosi)"
+ method: "Per indicare che un allegato è esplicito, tocca il file per aprirne il menu e scegliere la voce \"Segna come esplicito\"."
+ sensitiveSucceeded: "Quando alleghi file, assicurati di indicare se è materiale esplicito, in modo appropriato, in base al regolamento del tuo server."
+ doItToContinue: "Impostando l'immagine come esplicita, potrai procedere col tutorial."
+ _done:
+ title: "Il tutorial è finito! 🎉"
+ description: "Queste sono solamente alcune delle funzionalità principali di Misskey. Per ulteriori informazioni, {link}."
+_timelineDescription:
+ home: "Nella Timeline Home, la tua cronologia principale, puoi vedere le Note provenienti dai profili che segui (follow)."
+ local: "La Timeline Locale, è una cronologia di Note pubblicate da tutti i profili iscritti su questo server."
+ social: "La Timeline Sociale, unisce in ordine cronologico l'elenco di Note presenti nella Timeline Home e quella Locale."
+ global: "La Timeline Federata ti consente di vedere le Note pubblicate dai profili di tutti gli altri server federati a questo."
_serverRules:
description: "In Europa è necessario mostrare l'informativa sul trattamento dei dati personali, prima della registrazione al servizio."
+_serverSettings:
+ iconUrl: "URL dell'icona"
+ appIconDescription: "Indicare l'icona da usare quando {host} viene salvata come App."
+ appIconUsageExample: "Ad esempio quando si aggiunge il segnalibro alla PWA (Progressive Web App), oppure alla schermata iniziale del dispositivo mobile "
+ appIconStyleRecommendation: "Poiché l'icona potrebbe essere ritagliata in un quadrato o in un cerchio, si raccomanda che abbia un margine colorato."
+ appIconResolutionMustBe: "La risoluzione minima è {resolution}"
+ manifestJsonOverride: "Sostituire il file manifest.json"
+ shortName: "Abbreviazione"
+ shortNameDescription: "Un'abbreviazione o un nome comune che può essere visualizzato al posto del nome ufficiale lungo del server."
+ fanoutTimelineDescription: "Attivando questa funzionalità migliori notevolmente la capacità delle Timeline di collezionare Note, riducendo il carico sul database. Tuttavia, aumenterà l'impiego di memoria RAM per Redis. Disattiva se il tuo server ha poca RAM o la funzionalità è irregolare."
_accountMigration:
moveFrom: "Migra un altro profilo dentro a questo"
moveFromSub: "Crea un alias verso un altro profilo remoto"
@@ -1365,6 +1520,12 @@ _achievements:
title: "Brain Diver"
description: "Pubblica un link a Brain Diver"
flavor: "Sulle note di Brain Diver"
+ _smashTestNotificationButton:
+ title: "Prove eccessive"
+ description: "Hai provato le notifiche consecutivamente in un periodo di tempo molto breve"
+ _tutorialCompleted:
+ title: "Attestato di partecipazione al corso per principianti di Misskey"
+ description: "Ha completato il tutorial"
_role:
new: "Nuovo ruolo"
edit: "Modifica ruolo"
@@ -1402,14 +1563,15 @@ _role:
_options:
gtlAvailable: "Disponibilità della Timeline Federata"
ltlAvailable: "Disponibilità della Timeline Locale"
- canPublicNote: "Può scrivere Note con Visibilità Pubblica"
- canInvite: "Genera codici di invito all'istanza"
+ canPublicNote: "Scrivere Note con Visibilità Pubblica"
+ canInvite: "Generare codici di invito all'istanza"
inviteLimit: "Limite di codici invito"
inviteLimitCycle: "Intervallo di emissione del codice di invito"
inviteExpirationTime: "Scadenza del codice di invito"
canManageCustomEmojis: "Gestire le emoji personalizzate"
+ canManageAvatarDecorations: "Gestisce le decorazioni di immagini del profilo"
driveCapacity: "Capienza del Drive"
- alwaysMarkNsfw: "Imposta sempre come NSFW"
+ alwaysMarkNsfw: "Impostare sempre come esplicito (NSFW)"
pinMax: "Quantità massima di Note in primo piano"
antennaMax: "Quantità massima di Antenne"
wordMuteMax: "Lunghezza massima del filtro parole"
@@ -1420,15 +1582,16 @@ _role:
userEachUserListsMax: "Quantità massima di profili per lista"
rateLimitFactor: "Limite del rapporto"
descriptionOfRateLimitFactor: "I rapporti più bassi sono meno restrittivi, quelli più alti lo sono di più."
- canHideAds: "Può nascondere i banner"
+ canHideAds: "Nascondere i banner"
canSearchNotes: "Ricercare nelle Note"
+ canUseTranslator: "Tradurre le Note"
_condition:
isLocal: "Profilo locale"
isRemote: "Profilo remoto"
- createdLessThan: "Creato meno di"
- createdMoreThan: "Creato più di"
- followersLessThanOrEq: "Ha meno di N follower"
- followersMoreThanOrEq: "Ha più di N follower"
+ createdLessThan: "Profilo creato da meno di N"
+ createdMoreThan: "Profilo creato da più di N"
+ followersLessThanOrEq: "Profilo con N follower o meno"
+ followersMoreThanOrEq: "Profilo con N follower o più"
followingLessThanOrEq: "Segue N profili o meno"
followingMoreThanOrEq: "Segue N profili o più"
notesLessThanOrEq: "Conteggio Note inferiore o uguale a"
@@ -1437,9 +1600,9 @@ _role:
or: "O"
not: "NON"
_sensitiveMediaDetection:
- description: "L'apprendimento automatico può essere utilizzato per individuare automaticamente i media sensibili da moderare. Il carico del server aumenta leggermente."
- sensitivity: "Sensibilità di rilevamento"
- sensitivityDescription: "Una minore sensibilità riduce i falsi positivi (false positività). Una maggiore sensibilità riduce le omissioni (falsi negativi)."
+ description: "Utilizzare l'apprendimento automatico (machine learning) per riconoscere media espliciti e sottoporli alla moderazione. Aumenterà lievemente il carico del server."
+ sensitivity: "Sensibilità del rilevamento"
+ sensitivityDescription: "Abbassando la sensibilità si riducono i falsi positivi (rilevazioni errate). Aumentando la sensibilità si riduce il numero di rilevazioni mancate. (rilevazioni ignorate)."
setSensitiveFlagAutomatically: "Impostare il flag NSFW."
setSensitiveFlagAutomaticallyDescription: "Anche se questa impostazione è disattivata, il risultato della decisione viene conservato internamente."
analyzeVideos: "Abilitazione dell'analisi video."
@@ -1451,7 +1614,7 @@ _emailUnavailable:
mx: "Server email non corretto"
smtp: "Il server email non risponde"
_ffVisibility:
- public: "Pubblico"
+ public: "Pubblica"
followers: "Mostra solo ai follower"
private: "Invisibile"
_signup:
@@ -1470,6 +1633,10 @@ _ad:
reduceFrequencyOfThisAd: "Visualizza questa pubblicità meno spesso"
hide: "Nascondi"
timezoneinfo: "Il giorno della settimana è determinato in base al fuso orario del server."
+ adsSettings: "Impostazioni banner"
+ notesPerOneAd: "Quantità di Note tra i banner"
+ setZeroToDisable: "Imposta 0 (zero) per disattivare la distribuzione dei banner durante gli aggiornamenti in tempo reale"
+ adsTooClose: "Attenzione, l'intervallo di pubblicazione dei banner è molto breve, potrebbe infastidire significativamente la fruizione"
_forgotPassword:
enterEmail: "Inserisci l'indirizzo di posta elettronica che hai registrato nel tuo profilo. Il collegamento necessario per ripristinare la password verrà inviato a questo indirizzo."
ifNoEmail: "Se il tuo indirizzo email non risulta registrato, contatta l'amministrazione dell'istanza."
@@ -1481,19 +1648,20 @@ _gallery:
unlike: "Non mi piace più"
_email:
_follow:
- title: "Ha iniziato a seguirti"
+ title: "Adesso ti segue"
_receiveFollowRequest:
title: "Hai ricevuto una richiesta di follow"
_plugin:
install: "Installa estensioni"
installWarn: "Si prega di installare soltanto estensioni che provengono da fonti affidabili."
manage: "Gestisci estensioni"
+ viewSource: "Visualizza sorgente"
_preferencesBackups:
- list: "I backup creati."
+ list: "Elenco di impostazioni salvate in precedenza"
saveNew: "Nuovo salvataggio"
- loadFile: "Importa file"
+ loadFile: "Carica da file"
apply: "Applicabile a questo dispositivo"
- save: "Sovrascrivi il file di salvataggio"
+ save: "Sovrascrivi il backup"
inputName: "Inserire il nome del backup."
cannotSave: "Impossibile salvare."
nameAlreadyExists: "Il nome del backup \"{name}\" esiste già. Si prega di specificare un nome diverso."
@@ -1521,9 +1689,10 @@ _aboutMisskey:
donate: "Sostieni Misskey"
morePatrons: "Apprezziamo sinceramente il supporto di tante altre persone. Grazie mille! 🥰"
patrons: "Sostenitori"
+ projectMembers: "Partecipanti al progetto"
_displayOfSensitiveMedia:
- respect: "Nascondere i media sensibili"
- ignore: "Non nascondere i media sensibili"
+ respect: "Nascondere i media espliciti"
+ ignore: "Non nascondere i media espliciti"
force: "Nascondi tutti i media"
_instanceTicker:
none: "Nascondi"
@@ -1538,13 +1707,14 @@ _channel:
edit: "Gerisci canale"
setBanner: "Scegli intestazione"
removeBanner: "Rimuovi intestazione"
- featured: "Tendenze"
+ featured: "Di tendenza"
owned: "I miei canali"
following: "Seguiti"
usersCount: "{n} partecipanti"
notesCount: "{n} note"
nameAndDescription: "Nome e descrizione"
nameOnly: "Solo il nome"
+ allowRenoteToExternal: "Consenti i Rinota e le citazioni all'esterno del canale"
_menuDisplay:
sideFull: "Laterale"
sideIcon: "Laterale (solo icone)"
@@ -1552,13 +1722,8 @@ _menuDisplay:
hide: "Nascondere"
_wordMute:
muteWords: "Parole da filtrare"
- muteWordsDescription: "Separare con uno spazio indica la condizione \"E\". Separare con una interruzione di riga, indica la condizione \"O\""
+ muteWordsDescription: "Sparando con uno spazio indichi la condizione E (and). Separando con un a capo, indichi la condizione O (or)."
muteWordsDescription2: "Se vuoi indicare delle Espressioni Regolari (regexp), metti la condizione all'interno di due slash (/)"
- softDescription: "Verranno nascoste da tutte le Timeline quelle Note che soddisfano le seguenti condizioni"
- hardDescription: "Impedisci alla istanza di caricare Note che soddisfano le seguenti condizioni. Le Note già filtrate sono già scomparse in modo irreversibile, fino al cambiamento delle condizioni. Dopo di che scompariranno quelle che soddisfano le nuove condizioni."
- soft: "Leggero"
- hard: "Pesante"
- mutedNotes: "Note filtrate"
_instanceMute:
instanceMuteDescription: "Disattiva tutte le note, le note di rinvio (condivisione) dell'istanza configurata, comprese le risposte agli utenti dell'istanza."
instanceMuteDescription2: "Impostazione separata da una nuova riga"
@@ -1567,7 +1732,7 @@ _instanceMute:
_theme:
explore: "Esplora temi"
install: "Installa un tema"
- manage: "Gerisci temi"
+ manage: "Gestione temi"
code: "Codice tema"
description: "Descrizione"
installed: "{name} è installato"
@@ -1622,9 +1787,6 @@ _theme:
infoFg: "Testo di informazioni"
infoWarnBg: "Sfondo degli avvisi"
infoWarnFg: "Testo di avviso"
- cwBg: "Sfondo del CW"
- cwFg: "Testo del pulsante CW"
- cwHoverBg: "Sfondo del pulsante CW (sorvolato)"
toastBg: "Sfondo di notifica a comparsa"
toastFg: "Testo di notifica a comparsa"
buttonBg: "Sfondo del pulsante"
@@ -1642,8 +1804,6 @@ _sfx:
note: "Nota"
noteMy: "Mia nota"
notification: "Notifiche"
- chat: "Messaggi"
- chatBg: "Chat (sfondo)"
antenna: "Ricezione dell'antenna"
channel: "Notifiche di canale"
_ago:
@@ -1662,27 +1822,17 @@ _time:
minute: "min"
hour: "ore"
day: "giorni"
-_timelineTutorial:
- title: "Come usare Misskey"
- step1_1: "Questa è la \"Timeline\". tutte le \"Note\" pubblicate su {name} vengono elencate in ordine cronologico."
- step1_2: "Le Timeline sono diverse, ad esempio, la \"Home\" elenca le Note dei profili che segui. Quella \"Locale\" elenca quelle di tutti i profili attivi su {name}."
- step2_1: "Prova a pubblicare una Nota. Semplicemente premendo il bottone con l'icona di una matita."
- step2_2: "Potresti scrivere la tua presentazione, oppure semplicemente \"Ciao da {name}!\""
- step3_1: "Hai pubblicato qualcosa?"
- step3_2: "In tal caso, dovrebbe comparire subito nella tua \"Home\""
- step4_1: "Puoi reagire con un emoji alle Note."
- step4_2: "To attach a reaction, press the \"+\" mark on a note and choose an emoji you'd like to react with.\nPer reagire con una emoji, premi il bottone \"+\" (più) visibile vicino ad ogni Nota e scegli dall'elenco la emoji che rappresenta la tua reazione."
_2fa:
alreadyRegistered: "La configurazione è stata già completata."
- registerTOTP: "Registra un'app di autenticazione"
- passwordToTOTP: "Inserire la password"
- step1: "Innanzitutto, installare sul dispositivo un'applicazione di autenticazione come {a} o {b}."
- step2: "Quindi, scansionare il codice QR visualizzato con l'app."
+ registerTOTP: "Registra una App di autenticazione a due fattori (2FA/MFA)"
+ step1: "Innanzitutto, installa sul dispositivo un'App di autenticazione come {a} o {b}."
+ step2: "Quindi, tramite la App installata, scansiona questo codice QR."
step2Click: "Cliccando sul codice QR, puoi registrarlo con l'app di autenticazione o il portachiavi installato sul tuo dispositivo."
- step2Url: "Nell'applicazione desktop inserire il seguente URL: "
+ step2Uri: "Inserisci il seguente URL se desideri utilizzare una App per PC"
step3Title: "Inserisci il codice di verifica"
step3: "Inserite il token visualizzato nell'app e il gioco è fatto."
- step4: "D'ora in poi, quando si accede, si inserisce il token nello stesso modo.\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}"
+ setupCompleted: "Impostazione completata! 🎉"
+ step4: "D'ora in poi, quando si accede, si inserisce il token nello stesso modo."
securityKeyNotSupported: "Il tuo browser non supporta le chiavi di sicurezza."
registerTOTPBeforeKey: "Ti occorre un'app di autenticazione con OTP, prima di registrare la chiave di sicurezza."
securityKeyInfo: "È possibile impostare il dispositivo per accedere utilizzando una chiave di sicurezza hardware che supporta FIDO2 o un'impronta digitale o un PIN sul dispositivo."
@@ -1696,6 +1846,11 @@ _2fa:
renewTOTPConfirm: "I codici di verifica nelle app di autenticazione esistenti smetteranno di funzionare"
renewTOTPOk: "Ripristina"
renewTOTPCancel: "No grazie"
+ checkBackupCodesBeforeCloseThisWizard: "Prima di chiudere questa procedura guidata, salva i tuoi codici usa-e-getta in un posto sicuro."
+ backupCodes: "Codici usa-e-getta"
+ backupCodesDescription: "Puoi usare questi codici usa-e-getta per ottenere l'accesso al tuo profilo in caso sia impossibile usare l'App col codice OTP. Salvali in un posto sicuro."
+ backupCodeUsedWarning: "È stato usato un codice usa-e-getta. Per favore, riconfigura l'autenticazione a due fattori il prima possibile, nel caso la configurazione precedente abbia smesso di funzionare."
+ backupCodesExhaustedWarning: "Hai esaurito i codici usa-e-getta. Se l'App che genera il codice OTP non è più disponibile, non potrai più accedere al tuo profilo. Ripeti la configurazione per l'autenticazione a due fattori."
_permissions:
"read:account": "Visualizza le informazioni sul profilo"
"write:account": "Modifica le informazioni sul profilo"
@@ -1721,14 +1876,18 @@ _permissions:
"write:pages": "Gestire pagine"
"read:page-likes": "Visualizzare i \"Mi piace\" di pagine"
"write:page-likes": "Gestire i \"Mi piace\" di pagine"
- "read:user-groups": "Vedi gruppi di utenti"
- "write:user-groups": "Gestisci gruppi di utenti"
+ "read:user-groups": "Vedere i gruppi di utenti"
+ "write:user-groups": "Gestire i gruppi di utenti"
"read:channels": "Visualizza canali"
"write:channels": "Gerisci canali"
"read:gallery": "Visualizza la galleria."
"write:gallery": "Gestione della galleria"
"read:gallery-likes": "Visualizza i contenuti della galleria."
"write:gallery-likes": "Manipolazione dei \"Mi piace\" della galleria."
+ "read:flash": "Visualizza Play"
+ "write:flash": "Modifica Play"
+ "read:flash-likes": "Visualizza lista di Play piaciuti"
+ "write:flash-likes": "Modifica lista di Play piaciuti"
_auth:
shareAccessTitle: "Permessi dell'applicazione"
shareAccess: "Vuoi autorizzare {name} ad accedere al tuo profilo?"
@@ -1744,6 +1903,7 @@ _antennaSources:
homeTimeline: "Note dagli utenti che segui"
users: "Note dagli utenti selezionati"
userList: "Note dagli utenti della lista selezionata"
+ userBlacklist: "Tutte le Note tranne quelle di uno o più profili specificati"
_weekday:
sunday: "Domenica"
monday: "Lunedì"
@@ -1759,20 +1919,20 @@ _widgets:
notifications: "Notifiche"
timeline: "Timeline"
calendar: "Calendario"
- trends: "Tendenze"
+ trends: "Di tendenza"
clock: "Orologio"
- rss: "Aggregatore rss"
- rssTicker: "Ticker RSS"
+ rss: "Lettura RSS"
+ rssTicker: "Nastro RSS"
activity: "Attività"
photos: "Foto"
digitalClock: "Orologio digitale"
unixClock: "Orologio UNIX"
federation: "Federazione"
- instanceCloud: "Istanza Cloud"
+ instanceCloud: "Nuvola di federazione"
postForm: "Finestra di pubblicazione"
slideshow: "Diapositive"
button: "Pulsante"
- onlineUsers: "Utenti online"
+ onlineUsers: "Persone online"
jobQueue: "Coda di lavoro"
serverMetric: "Statistiche server"
aiscript: "Console AiScript"
@@ -1784,7 +1944,7 @@ _widgets:
clicker: "Cliccaggio"
_cw:
hide: "Nascondere"
- show: "Apri..."
+ show: "Continua la lettura..."
chars: "{count} caratteri"
files: "{count} file"
_poll:
@@ -1811,14 +1971,14 @@ _poll:
remainingSeconds: "Rimangono {s} secondi"
_visibility:
public: "Pubblica"
- publicDescription: "Visibile per tutti sul Fediverso"
+ publicDescription: "Visibilità pubblica"
home: "Home"
- homeDescription: "Visibile solo sulla timeline locale"
+ homeDescription: "Visibile solo nella Home"
followers: "Follower"
followersDescription: "Visibile solo ai tuoi follower"
specified: "Nota diretta"
specifiedDescription: "Visibile solo ai profili menzionati"
- disableFederation: "Non federare"
+ disableFederation: "Senza federazione"
disableFederationDescription: "Non spedire attività alle altre istanze remote"
_postForm:
replyPlaceholder: "Rispondi a questa nota..."
@@ -1843,6 +2003,7 @@ _profile:
metadataContent: "Contenuto"
changeAvatar: "Modifica immagine profilo"
changeBanner: "Cambia intestazione"
+ verifiedLinkDescription: "Puoi verificare il tuo profilo mostrando una icona. Devi inserire la URL alla pagina che contiene un link al tuo profilo."
_exportOrImport:
allNotes: "Tutte le note"
favoritedNotes: "Note preferite"
@@ -1852,6 +2013,7 @@ _exportOrImport:
userLists: "Liste"
excludeMutingUsers: "Escludere gli utenti silenziati"
excludeInactiveUsers: "Escludere i profili inutilizzati"
+ withReplies: "Includere le risposte da profili importati nella Timeline"
_charts:
federation: "Federazione"
apRequest: "Richieste"
@@ -1868,7 +2030,7 @@ _charts:
storageUsageTotal: "Utilizzo totale dell'immagazzinamento"
_instanceCharts:
requests: "Richieste"
- users: "Variazione del numero di utenti"
+ users: "Variazione del numero di profili"
usersTotal: "Totale cumulativo di utenti"
notes: "Variazione del numero di note"
notesTotal: "Totale cumulato di note"
@@ -1929,8 +2091,8 @@ _pages:
font: "Tipo di carattere"
fontSerif: "Serif"
fontSansSerif: "Sans serif"
- eyeCatchingImageSet: "Imposta un'immagine attrattiva"
- eyeCatchingImageRemove: "Elimina l'anteprima immagine"
+ eyeCatchingImageSet: "Imposta un'immagine attraente"
+ eyeCatchingImageRemove: "Elimina immagine attraente"
chooseBlock: "Aggiungi blocco"
selectType: "Seleziona tipo"
contentBlocks: "Contenuto"
@@ -1957,16 +2119,25 @@ _notification:
youGotReply: "{name} ti ha risposto"
youGotQuote: "{name} ha citato la tua Nota e ha detto"
youRenoted: "{name} ha rinotato"
- youWereFollowed: "Ha iniziato a seguirti"
+ youWereFollowed: "Adesso ti segue"
youReceivedFollowRequest: "Hai ricevuto una richiesta di follow"
yourFollowRequestAccepted: "La tua richiesta di follow è stata accettata"
pollEnded: "Risultati del sondaggio."
+ newNote: "Nuove Note"
unreadAntennaNote: "Antenna {name}"
emptyPushNotificationMessage: "Le notifiche push sono state aggiornate."
achievementEarned: "Obiettivo raggiunto"
+ testNotification: "Prova la notifica"
+ checkNotificationBehavior: "Prova il comportamento della notifica"
+ sendTestNotification: "Spedisci una notifica di prova"
+ notificationWillBeDisplayedLikeThis: "La notifica apparirà così"
+ reactedBySomeUsers: "{n} reazioni"
+ renotedBySomeUsers: "{n} Rinota"
+ followedBySomeUsers: "{n} nuovi follower"
_types:
all: "Tutto"
- follow: "Novità follower"
+ note: "Nuove Note"
+ follow: "Nuovi profili follower"
mention: "Menzioni"
reply: "Risposte"
renote: "Rinota"
@@ -1985,7 +2156,7 @@ _deck:
alwaysShowMainColumn: "Mostra sempre la colonna principale"
columnAlign: "Allineare colonne"
addColumn: "Aggiungi colonna"
- configureColumn: "Impostazioni della colonna."
+ configureColumn: "Impostazioni colonna"
swapLeft: "Sposta a sinistra"
swapRight: "Sposta a destra"
swapUp: "Sposta in alto"
@@ -1999,6 +2170,8 @@ _deck:
introduction2: "È possibile aggiungere colonne in qualsiasi momento premendo + sulla destra dello schermo."
widgetsIntroduction: "Dal menu della colonna, selezionare \"Modifica i riquadri\" per aggiungere un un riquadro con funzionalità"
useSimpleUiForNonRootPages: "Visualizza sotto pagine con interfaccia web semplice"
+ usedAsMinWidthWhenFlexible: "Se \"larghezza flessibile\" è abilitato, questa diventa la larghezza minima"
+ flexible: "Larghezza flessibile"
_columns:
main: "Principale"
widgets: "Riquadri"
@@ -2008,7 +2181,7 @@ _deck:
list: "Liste"
channel: "Canale"
mentions: "Menzioni"
- direct: "Diretta"
+ direct: "Note Dirette"
roleTimeline: "Timeline Ruolo"
_dialog:
charactersExceeded: "Hai superato il limite di {max} caratteri! ({corrente})"
@@ -2033,3 +2206,86 @@ _webhookSettings:
renote: "Quando la Nota è Rinotata"
reaction: "Quando ricevo una reazione"
mention: "Quando mi menzionano"
+_moderationLogTypes:
+ createRole: "Ruolo creato"
+ deleteRole: "Ruolo eliminato"
+ updateRole: "Ruolo aggiornato"
+ assignRole: "Ruolo assegnato"
+ unassignRole: "Ruolo disassegnato"
+ suspend: "Sospensione"
+ unsuspend: "Sospensione rimossa"
+ addCustomEmoji: "Emoji personalizzata aggiunta"
+ updateCustomEmoji: "Emoji personalizzata aggiornata"
+ deleteCustomEmoji: "Emoji personalizzata eliminata"
+ updateServerSettings: "Impostazioni del server aggiornate"
+ updateUserNote: "Promemoria di moderazione aggiornato"
+ deleteDriveFile: "File da Drive eliminato"
+ deleteNote: "Nota eliminata"
+ createGlobalAnnouncement: "Annuncio globale creato"
+ createUserAnnouncement: "Annuncio ai profili iscritti creato"
+ updateGlobalAnnouncement: "Annuncio globale aggiornato"
+ updateUserAnnouncement: "Annuncio ai profili iscritti aggiornato"
+ deleteGlobalAnnouncement: "Annuncio globale eliminato"
+ deleteUserAnnouncement: "Annuncio ai profili iscritti eliminato"
+ resetPassword: "Password azzerata"
+ suspendRemoteInstance: "Istanza remota sospesa"
+ unsuspendRemoteInstance: "Istanza remota riattivata"
+ markSensitiveDriveFile: "File nel Drive segnato come esplicito"
+ unmarkSensitiveDriveFile: "File nel Drive segnato come non esplicito"
+ resolveAbuseReport: "Segnalazione risolta"
+ createInvitation: "Genera codice di invito"
+ createAd: "Banner creato"
+ deleteAd: "Banner eliminato"
+ updateAd: "Banner aggiornato"
+ createAvatarDecoration: "Creazione decorazione della foto profilo"
+ updateAvatarDecoration: "Aggiornamento decorazione foto profilo"
+ deleteAvatarDecoration: "Eliminazione decorazione della foto profilo"
+_fileViewer:
+ title: "Dettagli del file"
+ type: "Tipo di file"
+ size: "Dimensioni file"
+ url: "URL"
+ uploadedAt: "Caricato il"
+ attachedNotes: "Note a cui è allegato"
+ thisPageCanBeSeenFromTheAuthor: "Questa pagina può essere vista solo da chi ha caricato il file."
+_externalResourceInstaller:
+ title: "Installa da sito esterno"
+ checkVendorBeforeInstall: "Prima di installare, assicurati che la fonte sia affidabile."
+ _plugin:
+ title: "Vuoi davvero installare questo componente aggiuntivo?"
+ metaTitle: "Informazioni sul componente aggiuntivo"
+ _theme:
+ title: "Vuoi davvero installare questa variazione grafica?"
+ metaTitle: "Informazioni sulla variazione grafica"
+ _meta:
+ base: "Combinazione base di colori"
+ _vendorInfo:
+ title: "Informazioni sulla fonte"
+ endpoint: "Punto di riferimento della fonte"
+ hashVerify: "Codice di verifica della fonte"
+ _errors:
+ _invalidParams:
+ title: "Parametri non validi"
+ description: "Mancano alcuni parametri per il caricamento, per favore, verifica la URL."
+ _resourceTypeNotSupported:
+ title: "Questa risorsa esterna non è supportata"
+ description: "Il tipo di risorsa ottenuta da questo sito esterno non è supportato. Si prega di contattare la fonte di distribuizone."
+ _failedToFetch:
+ title: "Impossibile ottenere i dati"
+ fetchErrorDescription: "Si è verificato un errore di comunicazione con la fonte. Se riprovare di nuovo non aiuta, contattare la fonte di distribuzione."
+ parseErrorDescription: "Si è verificato un errore elaborando i dati ottenuti dalla fonte. Per favore contattare il distributore."
+ _hashUnmatched:
+ title: "Dati non verificabili, diversi da quelli della fonte"
+ description: "Si è verificato un errore durante la verifica di integrità dei dati ottenuti. Per sicurezza, l'installazione è stata interrotta. Contattare la fonte di distribuzione."
+ _pluginParseFailed:
+ title: "Errore AiScript"
+ description: "Sebbene i dati ottenuti siano validi, non è stato possibile interpretarli, perché si è verificato un errore durante l'analisi di AiScript. Si prega di contattare gli autori del componente aggiuntivo. Potresti controllare la console di Javascript per ottenere dettagli aggiuntivi."
+ _pluginInstallFailed:
+ title: "Impossibile installare il componente aggiuntivo"
+ description: "Si è verificato un impedimento durante l'installazione del componente aggiuntivo. Per favore riprova e consulta la console di Javascript per ottenere dettagli aggiuntivi."
+ _themeParseFailed:
+ title: "Impossibile interpretare la variazione grafica"
+ description: "Sebbene i dati siano stati ottenuti, non è stato possibile interpretarli, si è verificato un errore durante l'analisi della variazione grafica. Si prega di contattare gli autori. Potresti anche controllare la console di Javascript per ottenere dettagli aggiuntivi."
+ _themeInstallFailed:
+ title: "Impossibile installare la variazione grafica"
+ description: "Si è verificato un impedimento durante l'installazione della variazione grafica. Per favore riprova e consulta la console di Javascript per ottenere dettagli aggiuntivi."
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index c13102b379..4d9be1ef33 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -15,7 +15,7 @@ gotIt: "わかった"
cancel: "キャンセル"
noThankYou: "やめておく"
enterUsername: "ユーザー名を入力"
-renotedBy: "{user}がRenote"
+renotedBy: "{user}がリノート"
noNotes: "ノートはありません"
noNotifications: "通知はありません"
instance: "サーバー"
@@ -45,9 +45,10 @@ pin: "ピン留め"
unpin: "ピン留め解除"
copyContent: "内容をコピー"
copyLink: "リンクをコピー"
+copyLinkRenote: "リノートのリンクをコピー"
delete: "削除"
deleteAndEdit: "削除して編集"
-deleteAndEditConfirm: "このノートを削除してもう一度編集しますか?このノートへのリアクション、Renote、返信も全て削除されます。"
+deleteAndEditConfirm: "このノートを削除してもう一度編集しますか?このノートへのリアクション、リノート、返信も全て削除されます。"
addToList: "リストに追加"
addToAntenna: "アンテナに追加"
sendMessage: "メッセージを送信"
@@ -74,7 +75,7 @@ import: "インポート"
export: "エクスポート"
files: "ファイル"
download: "ダウンロード"
-driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?このファイルを使用した全てのコンテンツからも削除されます。"
+driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?このファイルを使用した一部のコンテンツも削除されます。"
unfollowConfirm: "{name}のフォローを解除しますか?"
exportRequested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。"
importRequested: "インポートをリクエストしました。これには時間がかかる場合があります。"
@@ -104,13 +105,13 @@ followRequests: "フォロー申請"
unfollow: "フォロー解除"
followRequestPending: "フォロー許可待ち"
enterEmoji: "絵文字を入力"
-renote: "Renote"
-unrenote: "Renote解除"
-renoted: "Renoteしました。"
-cantRenote: "この投稿はRenoteできません。"
-cantReRenote: "RenoteをRenoteすることはできません。"
+renote: "リノート"
+unrenote: "リノート解除"
+renoted: "リノートしました。"
+cantRenote: "この投稿はリノートできません。"
+cantReRenote: "リノートをリノートすることはできません。"
quote: "引用"
-inChannelRenote: "チャンネル内Renote"
+inChannelRenote: "チャンネル内リノート"
inChannelQuote: "チャンネル内引用"
pinnedNote: "ピン留めされたノート"
pinned: "ピン留め"
@@ -155,7 +156,8 @@ emojiUrl: "絵文字画像URL"
addEmoji: "絵文字を追加"
settingGuide: "おすすめ設定"
cacheRemoteFiles: "リモートのファイルをキャッシュする"
-cacheRemoteFilesDescription: "この設定を無効にすると、リモートファイルをキャッシュせず直リンクするようになります。サーバーのストレージを節約できますが、サムネイルが生成されないので通信量が増加します。"
+cacheRemoteFilesDescription: "この設定を有効にすると、リモートファイルをこのサーバーのストレージにキャッシュするようになります。画像の表示が高速になりますが、サーバーのストレージを多く消費します。リモートユーザーがどれほどキャッシュを保持するかは、ロールによるドライブ容量制限によって決定されます。この制限を超えた場合、古いファイルからキャッシュが削除されリンクになります。この設定が無効の場合、リモートのファイルを最初からリンクとして保持しますが、画像のサムネイル生成やユーザーのプライバシー保護のために、default.ymlでproxyRemoteFilesをtrueにすることをお勧めします。"
+youCanCleanRemoteFilesCache: "ファイル管理の🗑️ボタンで全てのキャッシュを削除できます。"
cacheRemoteSensitiveFiles: "リモートのセンシティブなファイルをキャッシュする"
cacheRemoteSensitiveFilesDescription: "この設定を無効にすると、リモートのセンシティブなファイルはキャッシュせず直リンクするようになります。"
flagAsBot: "Botとして設定"
@@ -193,6 +195,7 @@ perHour: "1時間ごと"
perDay: "1日ごと"
stopActivityDelivery: "アクティビティの配送を停止"
blockThisInstance: "このサーバーをブロック"
+silenceThisInstance: "サーバーをサイレンス"
operations: "操作"
software: "ソフトウェア"
version: "バージョン"
@@ -211,7 +214,9 @@ clearQueueConfirmText: "未配達の投稿は配送されなくなります。
clearCachedFiles: "キャッシュをクリア"
clearCachedFilesConfirm: "キャッシュされたリモートファイルをすべて削除しますか?"
blockedInstances: "ブロックしたサーバー"
-blockedInstancesDescription: "ブロックしたいサーバーのホストを改行で区切って設定します。ブロックされたサーバーは、このサーバーとやり取りできなくなります。サブドメインもブロックされます。"
+blockedInstancesDescription: "ブロックしたいサーバーのホストを改行で区切って設定します。ブロックされたサーバーは、このインスタンスとやり取りできなくなります。"
+silencedInstances: "サイレンスしたサーバー"
+silencedInstancesDescription: "サイレンスしたいサーバーのホストを改行で区切って設定します。サイレンスされたサーバーに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになり、フォロワーでないローカルアカウントにはメンションできなくなります。ブロックしたインスタンスには影響しません。"
muteAndBlock: "ミュートとブロック"
mutedUsers: "ミュートしたユーザー"
blockedUsers: "ブロックしたユーザー"
@@ -356,7 +361,6 @@ invite: "招待"
driveCapacityPerLocalAccount: "ローカルユーザーひとりあたりのドライブ容量"
driveCapacityPerRemoteAccount: "リモートユーザーひとりあたりのドライブ容量"
inMb: "メガバイト単位"
-iconUrl: "アイコン画像のURL (faviconなど)"
bannerUrl: "バナー画像のURL"
backgroundImageUrl: "背景画像のURL"
basicInfo: "基本情報"
@@ -412,11 +416,14 @@ aboutMisskey: "Misskeyについて"
administrator: "管理者"
token: "確認コード"
2fa: "二要素認証"
+setupOf2fa: "二要素認証のセットアップ"
totp: "認証アプリ"
totpDescription: "認証アプリを使ってワンタイムパスワードを入力"
-useSecurityKey: "ブラウザまたはデバイスの指示に従って、セキュリティキーまたはパスキーを使用してください。"
moderator: "モデレーター"
moderation: "モデレーション"
+moderationNote: "モデレーションノート"
+addModerationNote: "モデレーションノートを追加する"
+moderationLogs: "モデログ"
nUsersMentioned: "{n}人が投稿"
securityKeyAndPasskey: "セキュリティキー・パスキー"
securityKey: "セキュリティキー"
@@ -529,6 +536,7 @@ serverLogs: "サーバーログ"
deleteAll: "全て削除"
showFixedPostForm: "タイムライン上部に投稿フォームを表示する"
showFixedPostFormInChannel: "タイムライン上部に投稿フォームを表示する(チャンネル)"
+withRepliesByDefaultForNewlyFollowed: "フォローする際、デフォルトで返信をTLに含むようにする"
newNoteRecived: "新しいノートがあります"
sounds: "サウンド"
sound: "サウンド"
@@ -591,7 +599,7 @@ poll: "アンケート"
useCw: "内容を隠す"
enablePlayer: "プレイヤーを開く"
disablePlayer: "プレイヤーを閉じる"
-expandTweet: "ツイートを展開する"
+expandTweet: "ポストを展開する"
themeEditor: "テーマエディター"
description: "説明"
describeFile: "キャプションを付ける"
@@ -660,6 +668,7 @@ behavior: "動作"
sample: "サンプル"
abuseReports: "通報"
reportAbuse: "通報"
+reportAbuseRenote: "リノートを通報"
reportAbuseOf: "{name}を通報する"
fillAbuseReportDescription: "通報理由の詳細を記入してください。対象のノートがある場合はそのURLも記入してください。"
abuseReported: "内容が送信されました。ご報告ありがとうございました。"
@@ -687,14 +696,15 @@ createNewClip: "新しいクリップを作成"
unclip: "クリップ解除"
confirmToUnclipAlreadyClippedNote: "このノートはすでにクリップ「{name}」に含まれています。ノートをこのクリップから除外しますか?"
public: "パブリック"
+private: "非公開"
i18nInfo: "Misskeyは有志によって様々な言語に翻訳されています。{link}で翻訳に協力できます。"
manageAccessTokens: "アクセストークンの管理"
accountInfo: "アカウント情報"
notesCount: "ノートの数"
repliesCount: "返信した数"
-renotesCount: "Renoteした数"
+renotesCount: "リノートした数"
repliedCount: "返信された数"
-renotedCount: "Renoteされた数"
+renotedCount: "リノートされた数"
followingCount: "フォロー数"
followersCount: "フォロワー数"
sentReactionsCount: "リアクションした数"
@@ -711,6 +721,7 @@ lockedAccountInfo: "フォローを承認制にしても、ノートの公開範
alwaysMarkSensitive: "デフォルトでメディアをセンシティブ設定にする"
loadRawImages: "添付画像のサムネイルをオリジナル画質にする"
disableShowingAnimatedImages: "アニメーション画像を再生しない"
+highlightSensitiveMedia: "メディアがセンシティブであることを分かりやすく表示"
verificationEmailSent: "確認のメールを送信しました。メールに記載されたリンクにアクセスして、設定を完了してください。"
notSet: "未設定"
emailVerified: "メールアドレスが確認されました"
@@ -793,7 +804,7 @@ active: "アクティブ"
offline: "オフライン"
notRecommended: "非推奨"
botProtection: "Botプロテクション"
-instanceBlocking: "サーバーブロック"
+instanceBlocking: "サーバーブロック・サイレンス"
selectAccount: "アカウントを選択"
switchAccount: "アカウントを切り替え"
enabled: "有効"
@@ -974,6 +985,7 @@ assign: "アサイン"
unassign: "アサインを解除"
color: "色"
manageCustomEmojis: "カスタム絵文字の管理"
+manageAvatarDecorations: "アバターデコレーションの管理"
youCannotCreateAnymore: "これ以上作成することはできません。"
cannotPerformTemporary: "一時的に利用できません"
cannotPerformTemporaryDescription: "操作回数が制限を超過するため一時的に利用できません。しばらく時間を置いてから再度お試しください。"
@@ -990,7 +1002,7 @@ thisPostMayBeAnnoying: "この投稿は迷惑になる可能性があります
thisPostMayBeAnnoyingHome: "ホームに投稿"
thisPostMayBeAnnoyingCancel: "やめる"
thisPostMayBeAnnoyingIgnore: "このまま投稿"
-collapseRenotes: "見たことのあるRenoteを省略して表示"
+collapseRenotes: "見たことのあるリノートを省略して表示"
internalServerError: "サーバー内部エラー"
internalServerErrorDescription: "サーバー内部で予期しないエラーが発生しました。"
copyErrorInfo: "エラー情報をコピー"
@@ -1025,7 +1037,7 @@ retryAllQueuesConfirmText: "一時的にサーバーの負荷が増大するこ
enableChartsForRemoteUser: "リモートユーザーのチャートを生成"
enableChartsForFederatedInstances: "リモートサーバーのチャートを生成"
showClipButtonInNoteFooter: "ノートのアクションにクリップを追加"
-largeNoteReactions: "ノートのリアクションを大きく表示"
+reactionsDisplaySize: "リアクションの表示サイズ"
noteIdOrUrl: "ノートIDまたはURL"
video: "動画"
videos: "動画"
@@ -1038,7 +1050,7 @@ forceShowAds: "常に広告を表示する"
addMemo: "メモを追加"
editMemo: "メモを編集"
reactionsList: "リアクション一覧"
-renotesList: "Renote一覧"
+renotesList: "リノート一覧"
notificationDisplay: "通知の表示"
leftTop: "左上"
rightTop: "右上"
@@ -1109,11 +1121,51 @@ forYou: "あなたへ"
currentAnnouncements: "現在のお知らせ"
pastAnnouncements: "過去のお知らせ"
youHaveUnreadAnnouncements: "未読のお知らせがあります。"
+useSecurityKey: "ブラウザまたはデバイスの指示に従って、セキュリティキーまたはパスキーを使用してください。"
+replies: "返信"
+renotes: "リノート"
+loadReplies: "返信を見る"
+loadConversation: "会話を見る"
+pinnedList: "ピン留めされたリスト"
+keepScreenOn: "デバイスの画面を常にオンにする"
+verifiedLink: "このリンク先の所有者であることが確認されました"
+notifyNotes: "投稿を通知"
+unnotifyNotes: "投稿の通知を解除"
+authentication: "認証"
+authenticationRequiredToContinue: "続けるには認証を行ってください"
+dateAndTime: "日時"
+showRenotes: "リノートを表示"
+edited: "編集済み"
+notificationRecieveConfig: "通知の受信設定"
+mutualFollow: "相互フォロー"
+fileAttachedOnly: "ファイル付きのみ"
+showRepliesToOthersInTimeline: "TLに他の人への返信を含める"
+hideRepliesToOthersInTimeline: "TLに他の人への返信を含めない"
+showRepliesToOthersInTimelineAll: "TLに現在フォロー中の人全員の返信を含めるようにする"
+hideRepliesToOthersInTimelineAll: "TLに現在フォロー中の人全員の返信を含めないようにする"
+confirmShowRepliesAll: "この操作は元に戻せません。本当にTLに現在フォロー中の人全員の返信を含めるようにしますか?"
+confirmHideRepliesAll: "この操作は元に戻せません。本当にTLに現在フォロー中の人全員の返信を含めないようにしますか?"
externalServices: "外部サービス"
+impressum: "運営者情報"
+impressumUrl: "運営者情報URL"
+impressumDescription: "ドイツなどの一部の国と地域では表示が義務付けられています(Impressum)。"
+privacyPolicy: "プライバシーポリシー"
+privacyPolicyUrl: "プライバシーポリシーURL"
+tosAndPrivacyPolicy: "利用規約・プライバシーポリシー"
+avatarDecorations: "アイコンデコレーション"
+attach: "付ける"
+detach: "外す"
+angle: "角度"
+flip: "反転"
+showAvatarDecorations: "アイコンのデコレーションを表示"
releaseToRefresh: "離してリロード"
refreshing: "リロード中"
pullDownToRefresh: "引っ張ってリロード"
disableStreamingTimeline: "タイムラインのリアルタイム更新を無効にする"
+useGroupedNotifications: "通知をグルーピングして表示する"
+signupPendingError: "メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。"
+cwNotationRequired: "「内容を隠す」がオンの場合は注釈の記述が必要です。"
+doReaction: "リアクションする"
urlPreviewDenyList: "サムネイルの表示を制限するURL"
urlPreviewDenyListDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。スラッシュで囲むと正規表現になります。一致した場合、サムネイルがぼかされて表示されます。"
@@ -1126,10 +1178,14 @@ _announcement:
tooManyActiveAnnouncementDescription: "アクティブなお知らせが多いため、UXが低下する可能性があります。終了したお知らせはアーカイブすることを検討してください。"
readConfirmTitle: "既読にしますか?"
readConfirmText: "「{title}」の内容を読み、既読にします。"
+ shouldNotBeUsedToPresentPermanentInfo: "特に新規ユーザーのUXを損ねる可能性が高いため、ストック情報ではなくフロー情報の掲示にお知らせを使用することを推奨します。"
+ dialogAnnouncementUxWarn: "ダイアログ形式のお知らせが同時に2つ以上ある場合、UXに悪影響を及ぼす可能性が非常に高いため、使用は慎重に行うことを推奨します。"
+ silence: "非通知"
+ silenceDescription: "オンにすると、このお知らせは通知されず、既読にする必要もなくなります。"
_initialAccountSetting:
accountCreated: "アカウントの作成が完了しました!"
- letsStartAccountSetup: "アカウントの初期設定を行いましょう。"
+ letsStartAccountSetup: "さっそくアカウントの初期設定を行いましょう。"
letsFillYourProfile: "まずはあなたのプロフィールを設定しましょう。"
profileSetting: "プロフィール設定"
privacySetting: "プライバシー設定"
@@ -1139,13 +1195,96 @@ _initialAccountSetting:
pushNotificationDescription: "プッシュ通知を有効にすると{name}の通知をお使いのデバイスで受け取ることができます。"
initialAccountSettingCompleted: "初期設定が完了しました!"
haveFun: "{name}をお楽しみください!"
- ifYouNeedLearnMore: "{name}(Misskey)の使い方などを詳しく知るには{link}をご覧ください。"
+ youCanContinueTutorial: "このまま{name}(Misskey)の使い方についてのチュートリアルに進むこともできますが、ここで中断してすぐに使い始めることもできます。"
+ startTutorial: "チュートリアルを開始"
skipAreYouSure: "初期設定をスキップしますか?"
laterAreYouSure: "初期設定をあとでやり直しますか?"
+_initialTutorial:
+ launchTutorial: "チュートリアルを見る"
+ title: "チュートリアル"
+ wellDone: "よくできました"
+ skipAreYouSure: "チュートリアルを終了しますか?"
+ _landing:
+ title: "チュートリアルへようこそ"
+ description: "ここでは、Misskeyの基本的な使い方や機能を確認できます。"
+ _note:
+ title: "ノートって何?"
+ description: "Misskeyでの投稿は「ノート」と呼びます。ノートはタイムラインに時系列で並んでいて、リアルタイムで更新されていきます。"
+ reply: "返信することができます。返信に対しての返信も可能で、スレッドのように会話を続けることもできます。"
+ renote: "そのノートを自分のタイムラインに流して共有することができます。テキストを追加して引用することも可能です。"
+ reaction: "リアクションをつけることができます。詳しくは次のページで解説します。"
+ menu: "ノートの詳細を表示したり、リンクをコピーしたりなどの様々な操作が行えます。"
+ _reaction:
+ title: "リアクションって何?"
+ description: "ノートには「リアクション」をつけることができます。「いいね」では伝わらないニュアンスも、リアクションで簡単・気軽に表現できます。"
+ letsTryReacting: "リアクションは、ノートの「+」ボタンをクリックするとつけられます。試しにこのサンプルのノートにリアクションをつけてみてください!"
+ reactToContinue: "リアクションをつけると先に進めるようになります。"
+ reactNotification: "あなたのノートが誰かにリアクションされると、リアルタイムで通知を受け取ります。"
+ reactDone: "「ー」ボタンを押すとリアクションを取り消すことができます。"
+ _timeline:
+ title: "タイムラインのしくみ"
+ description1: "Misskeyには、使い方に応じて複数のタイムラインが用意されています(サーバーによってはいずれかが無効になっていることがあります)。"
+ home: "あなたがフォローしているアカウントの投稿を見られます。"
+ local: "このサーバーにいるユーザー全員の投稿を見られます。"
+ social: "ホームタイムラインとローカルタイムラインの投稿が両方表示されます。"
+ global: "接続している他のすべてのサーバーからの投稿を見られます。"
+ description2: "それぞれのタイムラインは、画面上部でいつでも切り替えられます。"
+ description3: "その他にも、リストタイムラインやチャンネルタイムラインなどがあります。詳しくは{link}をご覧ください。"
+ _postNote:
+ title: "ノートの投稿設定"
+ description1: "Misskeyにノートを投稿する際には、様々なオプションの設定が可能です。投稿フォームはこのようになっています。"
+ _visibility:
+ description: "ノートを表示できる相手を制限できます。"
+ public: "すべてのユーザーに公開。"
+ home: "ホームタイムラインのみに公開。フォロワー・プロフィールを見に来た人・リノートから、他のユーザーも見ることができます。"
+ followers: "フォロワーにのみ公開。本人以外がリノートすることはできず、またフォロワー以外は閲覧できません。"
+ direct: "指定したユーザーにのみ公開され、また相手に通知が入ります。ダイレクトメッセージのかわりにお使いいただけます。"
+ doNotSendConfidencialOnDirect1: "機密情報は送信する際は注意してください。"
+ doNotSendConfidencialOnDirect2: "送信先のサーバーの管理者は投稿内容を見ることが可能なので、信頼できないサーバーのユーザーにダイレクト投稿を送信する場合は、機密情報の扱いに注意が必要です。"
+ localOnly: "他のサーバーに投稿を連合しません。上記の公開範囲に関わらず、他のサーバーのユーザーは、この設定がついたノートを直接閲覧することができなくなります。"
+ _cw:
+ title: "内容を隠す(CW)"
+ description: "本文のかわりに「注釈」に書いた内容が表示されます。「もっと見る」を押すと本文が表示されます。"
+ _exampleNote:
+ cw: "飯テロ注意"
+ note: "チョコのかかったドーナツを食べました🍩😋"
+ useCases: "サーバーのガイドラインにより必要とされるノートに指定したり、ネタバレ投稿やセンシティブな文章を自主規制したりするときに使います。"
+ _howToMakeAttachmentsSensitive:
+ title: "添付ファイルをセンシティブにするには?"
+ description: "サーバーのガイドラインにより必要とされる際や、そのまま見れる状態にしておくべきではない添付ファイルには、「センシティブ」設定を付けます。"
+ tryThisFile: "試しに、このフォームに添付された画像をセンシティブにしてみてください!"
+ _exampleNote:
+ note: "納豆のフタ開けるのミスったわね…"
+ method: "添付ファイルをセンシティブにする際は、そのファイルをクリックしてメニューを開き、「センシティブとして設定」をクリックします。"
+ sensitiveSucceeded: "ファイルを添付する際は、サーバーのガイドラインに従ってセンシティブを適切に設定してください。"
+ doItToContinue: "画像をセンシティブに設定すると先に進めるようになります。"
+ _done:
+ title: "チュートリアルは終了です🎉"
+ description: "ここで紹介した機能はほんの一部にすぎません。Misskeyの使い方をより詳しく知るには、{link}をご覧ください。"
+
+_timelineDescription:
+ home: "ホームタイムラインでは、あなたがフォローしているアカウントの投稿を見られます。"
+ local: "ローカルタイムラインでは、このサーバーにいるユーザー全員の投稿を見られます。"
+ social: "ソーシャルタイムラインには、ホームタイムラインとローカルタイムラインの投稿が両方表示されます。"
+ global: "グローバルタイムラインでは、接続している他のすべてのサーバーからの投稿を見られます。"
+
_serverRules:
description: "新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。"
+_serverSettings:
+ iconUrl: "アイコン画像のURL"
+ appIconDescription: "{host}がアプリとして表示される際のアイコンを指定します。"
+ appIconUsageExample: "例: PWAや、スマートフォンのホーム画面にブックマークとして追加された時など"
+ appIconStyleRecommendation: "円形もしくは角丸にクロップされる場合があるため、塗り潰された余白のある背景を持つことが推奨されます。"
+ appIconResolutionMustBe: "解像度は必ず{resolution}である必要があります。"
+ manifestJsonOverride: "manifest.jsonのオーバーライド"
+ shortName: "略称"
+ shortNameDescription: "サーバーの正式名称が長い場合に、代わりに表示することのできる略称や通称。"
+ fanoutTimelineDescription: "有効にすると、各種タイムラインを取得する際のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。サーバーのメモリ容量が少ない場合、または動作が不安定な場合は無効にすることができます。"
+ fanoutTimelineDbFallback: "データベースへのフォールバック"
+ fanoutTimelineDbFallbackDescription: "有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。"
+
_accountMigration:
moveFrom: "別のアカウントからこのアカウントに移行"
moveFromSub: "別のアカウントへエイリアスを作成"
@@ -1401,6 +1540,12 @@ _achievements:
title: "Brain Diver"
description: "Brain Diverへのリンクを投稿した"
flavor: "Misskey-Misskey La-Tu-Ma"
+ _smashTestNotificationButton:
+ title: "テスト過剰"
+ description: "通知のテストをごく短時間のうちに連続して行った"
+ _tutorialCompleted:
+ title: "Misskey初心者講座 修了証"
+ description: "チュートリアルを完了した"
_role:
new: "ロールの作成"
@@ -1448,6 +1593,7 @@ _role:
inviteLimitCycle: "招待コードの発行間隔"
inviteExpirationTime: "招待コードの有効期限"
canManageCustomEmojis: "カスタム絵文字の管理"
+ canManageAvatarDecorations: "アバターデコレーションの管理"
driveCapacity: "ドライブ容量"
alwaysMarkNsfw: "ファイルにNSFWを常に付与"
pinMax: "ノートのピン留めの最大数"
@@ -1461,7 +1607,8 @@ _role:
rateLimitFactor: "レートリミット"
descriptionOfRateLimitFactor: "小さいほど制限が緩和され、大きいほど制限が強化されます。"
canHideAds: "広告の非表示"
- canSearchNotes: "ノート検索の利用可否"
+ canSearchNotes: "ノート検索の利用"
+ canUseTranslator: "翻訳機能の利用"
_condition:
isLocal: "ローカルユーザー"
isRemote: "リモートユーザー"
@@ -1501,7 +1648,7 @@ _ffVisibility:
_signup:
almostThere: "ほとんど完了です"
emailAddressInfo: "あなたが使っているメールアドレスを入力してください。メールアドレスが公開されることはありません。"
- emailSent: "入力されたメールアドレス({email})宛に確認のメールが送信されました。メールに記載されたリンクにアクセスすると、アカウントの作成が完了します。"
+ emailSent: "入力されたメールアドレス({email})宛に確認のメールが送信されました。メールに記載されたリンクにアクセスすると、アカウントの作成が完了します。メールに記載されているリンクの有効期限は30分です。"
_accountDelete:
accountDelete: "アカウントの削除"
@@ -1516,6 +1663,10 @@ _ad:
reduceFrequencyOfThisAd: "この広告の表示頻度を下げる"
hide: "表示しない"
timezoneinfo: "曜日はサーバーのタイムゾーンを元に指定されます。"
+ adsSettings: "広告配信設定"
+ notesPerOneAd: "リアルタイム更新中に広告を配信する間隔(ノートの個数)"
+ setZeroToDisable: "0でリアルタイム更新時の広告配信を無効"
+ adsTooClose: "広告の配信間隔が極めて短いため、ユーザー体験が著しく損われる可能性があります。"
_forgotPassword:
enterEmail: "アカウントに登録したメールアドレスを入力してください。そのアドレス宛てに、パスワードリセット用のリンクが送信されます。"
@@ -1538,6 +1689,7 @@ _plugin:
install: "プラグインのインストール"
installWarn: "信頼できないプラグインはインストールしないでください。"
manage: "プラグインの管理"
+ viewSource: "ソースを表示"
_preferencesBackups:
list: "作成したバックアップ"
@@ -1567,13 +1719,14 @@ _registry:
_aboutMisskey:
about: "Misskeyはsyuiloによって2014年から開発されている、オープンソースのソフトウェアです。"
- contributors: "主なコントリビューター"
+ contributors: "コントリビューター"
allContributors: "全てのコントリビューター"
source: "ソースコード"
translation: "Misskeyを翻訳"
donate: "Misskeyに寄付"
morePatrons: "他にも多くの方が支援してくれています。ありがとうございます🥰"
patrons: "支援者"
+ projectMembers: "プロジェクトメンバー"
_displayOfSensitiveMedia:
respect: "センシティブ設定されたメディアを隠す"
@@ -1602,6 +1755,7 @@ _channel:
notesCount: "{n}投稿があります"
nameAndDescription: "名前と説明"
nameOnly: "名前のみ"
+ allowRenoteToExternal: "チャンネル外へのリノートと引用リノートを許可する"
_menuDisplay:
sideFull: "横"
@@ -1613,11 +1767,6 @@ _wordMute:
muteWords: "ミュートするワード"
muteWordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。"
muteWordsDescription2: "キーワードをスラッシュで囲むと正規表現になります。"
- softDescription: "指定した条件のノートをタイムラインから隠します。"
- hardDescription: "指定した条件のノートをタイムラインに追加しないようにします。追加されなかったノートは、条件を変更しても除外されたままになります。"
- soft: "ソフト"
- hard: "ハード"
- mutedNotes: "ミュートされたノート"
_instanceMute:
instanceMuteDescription: "ミュートしたサーバーのユーザーへの返信を含めて、設定したサーバーの全てのノートとRenoteをミュートします。"
@@ -1684,9 +1833,6 @@ _theme:
infoFg: "情報の文字"
infoWarnBg: "警告の背景"
infoWarnFg: "警告の文字"
- cwBg: "CW ボタンの背景"
- cwFg: "CW ボタンの文字"
- cwHoverBg: "CW ボタンの背景 (ホバー)"
toastBg: "通知トーストの背景"
toastFg: "通知トーストの文字"
buttonBg: "ボタンの背景"
@@ -1705,8 +1851,6 @@ _sfx:
note: "ノート"
noteMy: "ノート(自分)"
notification: "通知"
- chat: "チャット"
- chatBg: "チャット(バックグラウンド)"
antenna: "アンテナ受信"
channel: "チャンネル通知"
@@ -1722,36 +1866,32 @@ _ago:
yearsAgo: "{n}年前"
invalid: "ありません"
+_timeIn:
+ seconds: "{n}秒後"
+ minutes: "{n}分後"
+ hours: "{n}時間後"
+ days: "{n}日後"
+ weeks: "{n}週間後"
+ months: "{n}ヶ月後"
+ years: "{n}年後"
+
_time:
second: "秒"
minute: "分"
hour: "時間"
day: "日"
-_timelineTutorial:
- title: "Misskeyの使い方"
- step1_1: "この画面は「タイムライン」です。{name}に投稿された「ノート」が時系列で表示されます。"
- step1_2: "タイムラインにはいくつか種類があり、例えば「ホームタイムライン」にはあなたがフォローしている人のノートが流れ、「ローカルタイムライン」には{name}全体のノートが流れます。"
- step2_1: "試しに、何かノートを投稿してみましょう。画面上にある鉛筆マークのボタンを押すとフォームが開きます。"
- step2_2: "初めてのノートの内容は、あなたの自己紹介や「{name}始めました」などがおすすめです。"
- step3_1: "投稿できましたか?"
- step3_2: "あなたのノートがタイムラインに表示されていれば成功です。"
- step4_1: "ノートには、「リアクション」を付けることができます。"
- step4_2: "リアクションを付けるには、ノートの「+」マークをクリックして、好きな絵文字を選択します。"
-
_2fa:
alreadyRegistered: "既に設定は完了しています。"
registerTOTP: "認証アプリの設定を開始"
- passwordToTOTP: "パスワードを入力してください"
step1: "まず、{a}や{b}などの認証アプリをお使いのデバイスにインストールします。"
step2: "次に、表示されているQRコードをアプリでスキャンします。"
step2Click: "QRコードをクリックすると、お使いの端末にインストールされている認証アプリやキーリングに登録できます。"
- step2Url: "デスクトップアプリでは次のURIを入力します:"
+ step2Uri: "デスクトップアプリを使用する場合は次のURIを入力します"
step3Title: "確認コードを入力"
- step3: "アプリに表示されている確認コード(トークン)を入力して完了です。"
- step4: "これからログインするときも、同じように確認コードを入力します。\n\n認証アプリが使えなくなった時、アカウントへの緊急アクセスのためのバックアップコードを作成しました。\n紛失防止のため、必ず安全な場所に保管してください。\nこれらのコードはそれぞれ1回だけ使用することができます。\nもし全てのコードを使い切ってしまうとアカウントにアクセスすることができなくなってしまうので、出来るだけ早く認証アプリを再設定してください。\n\nバックアップコード:\n\n{codes}"
- twoFactorBackupSecretWarning: "バックアップコードが使用されました。認証アプリが使えなくなっている場合、なるべく早く認証アプリを再設定してください。"
- twoFactorBackupSecretExhausted: "バックアップコードが全て使用されました。認証アプリを利用できない場合、これ以上アカウントにアクセスできなくなります。できるだけ早く認証アプリを再登録してください。"
+ step3: "アプリに表示されている確認コード(トークン)を入力します。"
+ setupCompleted: "設定が完了しました"
+ step4: "これからログインするときも、同じように確認コードを入力します。"
securityKeyNotSupported: "お使いのブラウザはセキュリティキーに対応していません。"
registerTOTPBeforeKey: "セキュリティキー・パスキーを登録するには、まず認証アプリの設定を行なってください。"
securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキー、端末の生体認証やPINロック、パスキーといった、WebAuthn由来の鍵を登録します。"
@@ -1762,9 +1902,14 @@ _2fa:
removeKeyConfirm: "{name}を削除しますか?"
whyTOTPOnlyRenew: "セキュリティキーが登録されている場合、認証アプリの設定は解除できません。"
renewTOTP: "認証アプリを再設定"
- renewTOTPConfirm: "今までの認証アプリの確認コードとバックアップコードは使用できなくなります"
+ renewTOTPConfirm: "今までの認証アプリの確認コードおよびバックアップコードは使用できなくなります"
renewTOTPOk: "再設定する"
renewTOTPCancel: "やめておく"
+ checkBackupCodesBeforeCloseThisWizard: "このウィザードを閉じる前に、以下のバックアップコードを確認してください。"
+ backupCodes: "バックアップコード"
+ backupCodesDescription: "認証アプリが使用できなくなった場合、以下のバックアップコードを使ってアカウントにアクセスできます。これらのコードは必ず安全な場所に保管してください。各コードは一回だけ使用できます。"
+ backupCodeUsedWarning: "バックアップコードが使用されました。認証アプリが使えなくなっている場合、なるべく早く認証アプリを再設定してください。"
+ backupCodesExhaustedWarning: "バックアップコードが全て使用されました。認証アプリを利用できない場合、これ以上アカウントにアクセスできなくなります。認証アプリを再登録してください。"
_permissions:
"read:account": "アカウントの情報を見る"
@@ -1799,6 +1944,10 @@ _permissions:
"write:gallery": "ギャラリーを操作する"
"read:gallery-likes": "ギャラリーのいいねを見る"
"write:gallery-likes": "ギャラリーのいいねを操作する"
+ "read:flash": "Playを見る"
+ "write:flash": "Playを操作する"
+ "read:flash-likes": "Playのいいねを見る"
+ "write:flash-likes": "Playのいいねを操作する"
_auth:
shareAccessTitle: "アプリへのアクセス許可"
@@ -1816,6 +1965,7 @@ _antennaSources:
homeTimeline: "フォローしているユーザーのノート"
users: "指定した一人または複数のユーザーのノート"
userList: "指定したリストのユーザーのノート"
+ userBlacklist: "指定した一人または複数のユーザーを除いた全てのノート"
_weekday:
sunday: "日曜日"
@@ -1923,6 +2073,7 @@ _profile:
metadataContent: "内容"
changeAvatar: "アイコン画像を変更"
changeBanner: "バナー画像を変更"
+ verifiedLinkDescription: "内容にURLを設定すると、リンク先のWebサイトに自分のプロフィールへのリンクが含まれている場合に所有者確認済みアイコンを表示させることができます。"
_exportOrImport:
allNotes: "全てのノート"
@@ -1933,6 +2084,7 @@ _exportOrImport:
userLists: "リスト"
excludeMutingUsers: "ミュートしているユーザーを除外"
excludeInactiveUsers: "使われていないアカウントを除外"
+ withReplies: "インポートした人による返信をTLに含むようにする"
_charts:
federation: "連合"
@@ -2051,12 +2203,21 @@ _notification:
youReceivedFollowRequest: "フォローリクエストが来ました"
yourFollowRequestAccepted: "フォローリクエストが承認されました"
pollEnded: "アンケートの結果が出ました"
+ newNote: "新しい投稿"
unreadAntennaNote: "アンテナ {name}"
emptyPushNotificationMessage: "プッシュ通知の更新をしました"
achievementEarned: "実績を獲得"
+ testNotification: "通知テスト"
+ checkNotificationBehavior: "通知の表示を確かめる"
+ sendTestNotification: "テスト通知を送信する"
+ notificationWillBeDisplayedLikeThis: "通知はこのように表示されます"
+ reactedBySomeUsers: "{n}人がリアクションしました"
+ renotedBySomeUsers: "{n}人がリノートしました"
+ followedBySomeUsers: "{n}人にフォローされました"
_types:
all: "すべて"
+ note: "ユーザーの新規投稿"
follow: "フォロー"
mention: "メンション"
reply: "リプライ"
@@ -2092,6 +2253,8 @@ _deck:
introduction2: "画面の右にある + を押して、いつでもカラムを追加できます。"
widgetsIntroduction: "カラムのメニューから、「ウィジェットの編集」を選択してウィジェットを追加してください"
useSimpleUiForNonRootPages: "非ルートページは簡易UIで表示"
+ usedAsMinWidthWhenFlexible: "「幅を自動調整」が有効の場合、これが幅の最小値となります"
+ flexible: "幅を自動調整"
_columns:
main: "メイン"
@@ -2149,3 +2312,89 @@ _abuse:
reportContentPattern: "通報内容のパターン"
list: "一覧"
resolver: "リソルバー"
+
+_moderationLogTypes:
+ createRole: "ロールを作成"
+ deleteRole: "ロールを削除"
+ updateRole: "ロールを更新"
+ assignRole: "ロールへアサイン"
+ unassignRole: "ロールのアサイン解除"
+ suspend: "凍結"
+ unsuspend: "凍結解除"
+ addCustomEmoji: "カスタム絵文字追加"
+ updateCustomEmoji: "カスタム絵文字更新"
+ deleteCustomEmoji: "カスタム絵文字削除"
+ updateServerSettings: "サーバー設定更新"
+ updateUserNote: "モデレーションノート更新"
+ deleteDriveFile: "ファイルを削除"
+ deleteNote: "ノートを削除"
+ createGlobalAnnouncement: "全体のお知らせを作成"
+ createUserAnnouncement: "ユーザーへお知らせを作成"
+ updateGlobalAnnouncement: "全体のお知らせを更新"
+ updateUserAnnouncement: "ユーザーのお知らせを更新"
+ deleteGlobalAnnouncement: "全体のお知らせを削除"
+ deleteUserAnnouncement: "ユーザーのお知らせを削除"
+ resetPassword: "パスワードをリセット"
+ suspendRemoteInstance: "リモートサーバーを停止"
+ unsuspendRemoteInstance: "リモートサーバーを再開"
+ markSensitiveDriveFile: "ファイルをセンシティブ付与"
+ unmarkSensitiveDriveFile: "ファイルをセンシティブ解除"
+ resolveAbuseReport: "通報を解決"
+ createInvitation: "招待コードを作成"
+ createAd: "広告を作成"
+ deleteAd: "広告を削除"
+ updateAd: "広告を更新"
+ createAvatarDecoration: "アイコンデコレーションを作成"
+ updateAvatarDecoration: "アイコンデコレーションを更新"
+ deleteAvatarDecoration: "アイコンデコレーションを削除"
+
+_fileViewer:
+ title: "ファイルの詳細"
+ type: "ファイルタイプ"
+ size: "ファイルサイズ"
+ url: "URL"
+ uploadedAt: "追加日"
+ attachedNotes: "添付されているノート"
+ thisPageCanBeSeenFromTheAuthor: "このページは、このファイルをアップロードしたユーザーしか閲覧できません。"
+
+_externalResourceInstaller:
+ title: "外部サイトからインストール"
+ checkVendorBeforeInstall: "配布元が信頼できるかを確認した上でインストールしてください。"
+ _plugin:
+ title: "このプラグインをインストールしますか?"
+ metaTitle: "プラグイン情報"
+ _theme:
+ title: "このテーマをインストールしますか?"
+ metaTitle: "テーマ情報"
+ _meta:
+ base: "基本のカラースキーム"
+ _vendorInfo:
+ title: "配布元情報"
+ endpoint: "参照したエンドポイント"
+ hashVerify: "ファイル整合性の確認"
+ _errors:
+ _invalidParams:
+ title: "パラメータが不足しています"
+ description: "外部サイトからデータを取得するために必要な情報が不足しています。URLをお確かめください。"
+ _resourceTypeNotSupported:
+ title: "この外部リソースには対応していません"
+ description: "この外部サイトから取得したリソースの種別には対応していません。サイト管理者にお問い合わせください。"
+ _failedToFetch:
+ title: "データの取得に失敗しました"
+ fetchErrorDescription: "外部サイトとの通信に失敗しました。もう一度試しても改善しない場合、サイト管理者にお問い合わせください。"
+ parseErrorDescription: "外部サイトから取得したデータが読み取れませんでした。サイト管理者にお問い合わせください。"
+ _hashUnmatched:
+ title: "正しいデータが取得できませんでした"
+ description: "提供されたデータの整合性の確認に失敗しました。セキュリティ上、インストールは続行できません。サイト管理者にお問い合わせください。"
+ _pluginParseFailed:
+ title: "AiScript エラー"
+ description: "データは取得できたものの、AiScriptの解析時にエラーがあったため読み込めませんでした。プラグインの作者にお問い合わせください。エラーの詳細はJavascriptコンソールをご確認ください。"
+ _pluginInstallFailed:
+ title: "プラグインのインストールに失敗しました"
+ description: "プラグインのインストール中に問題が発生しました。もう一度お試しください。エラーの詳細はJavascriptコンソールをご覧ください。"
+ _themeParseFailed:
+ title: "テーマ解析エラー"
+ description: "データは取得できたものの、テーマファイルの解析時にエラーがあったため読み込めませんでした。テーマの作者にお問い合わせください。エラーの詳細はJavascriptコンソールをご確認ください。"
+ _themeInstallFailed:
+ title: "テーマのインストールに失敗しました"
+ description: "テーマのインストール中に問題が発生しました。もう一度お試しください。エラーの詳細はJavascriptコンソールをご覧ください。"
diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml
index 638cb507d1..aa68def1da 100644
--- a/locales/ja-KS.yml
+++ b/locales/ja-KS.yml
@@ -45,6 +45,7 @@ pin: "ピン留めしとく"
unpin: "やっぱピン留めせん"
copyContent: "内容をコピー"
copyLink: "リンクをコピー"
+copyLinkRenote: "リノートのリンクをコピーするで?"
delete: "ほかす"
deleteAndEdit: "ほかして直す"
deleteAndEditConfirm: "このノートをほかしてもっかい直す?このノートへのツッコミ、Renote、返信も全部消えるんやけどそれでもええん?"
@@ -76,7 +77,7 @@ files: "ファイル"
download: "ダウンロード"
driveFileDeleteConfirm: "ファイル「{name}」をほかしてええか?このファイルを添付したノートも消えてまうで。"
unfollowConfirm: "{name}のフォローを解除してもええんか?"
-exportRequested: "エクスポートしてな、ってリクエストしたけど、これ多分めっちゃ時間かかるで。エクスポート終わったら「ドライブ」に突っ込んどくで。"
+exportRequested: "エクスポートしてな、って言うたけど、これ多分めっちゃ時間かかるで。エクスポート終わったら「ドライブ」に突っ込んどくで。"
importRequested: "インポートしてな、ってリクエストしたけど、これ多分めっちゃ時間かかるで。"
lists: "リスト"
noLists: "リストなんてあらへんで"
@@ -156,6 +157,7 @@ addEmoji: "絵文字を追加"
settingGuide: "ええ感じの設定"
cacheRemoteFiles: "リモートのファイルをキャッシュする"
cacheRemoteFilesDescription: "この設定を切っとったら、リモートファイルをキャッシュせんと直リンクするようになるで。サーバーの容量は節約できるけど、サムネイルを作らんなるから通信量が増えるで。"
+youCanCleanRemoteFilesCache: "ファイル管理にある🗑️ボタンでキャッシュ全部ほかすで。"
cacheRemoteSensitiveFiles: "リモートのセンシティブなファイルをキャッシュする"
cacheRemoteSensitiveFilesDescription: "この設定を無効にすると、リモートのセンシティブなファイルはキャッシュせず直リンクするようになるで。"
flagAsBot: "Botにするで"
@@ -193,6 +195,7 @@ perHour: "1時間ごと"
perDay: "1日ごと"
stopActivityDelivery: "アクティビティの配送をやめる"
blockThisInstance: "このサーバーをブロックすんで"
+silenceThisInstance: "サーバーサイレンスすんで?"
operations: "操作"
software: "ソフトウェア"
version: "バージョン"
@@ -212,6 +215,8 @@ clearCachedFiles: "キャッシュをほかす"
clearCachedFilesConfirm: "キャッシュされとるリモートファイルをみんなほかしてええか?"
blockedInstances: "ブロックしたサーバー"
blockedInstancesDescription: "ブロックしたいサーバーのホストを改行で区切って設定してな。ブロックされてもうたサーバーとはもう金輪際やり取りできひんくなるで。ついでにそのサブドメインもブロックするで。"
+silencedInstances: "サーバーサイレンスされてんねん"
+silencedInstancesDescription: "サイレンスしたいサーバーのホストを改行で区切って設定すんで。サイレンスされたサーバーに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになり、フォロワーでないローカルアカウントにはメンションできなくなんねん。ブロックしたインスタンスには影響せーへんで。"
muteAndBlock: "ミュートとブロック"
mutedUsers: "ミュートしたユーザー"
blockedUsers: "ブロックしたユーザー"
@@ -354,7 +359,6 @@ invite: "来てや"
driveCapacityPerLocalAccount: "ローカルユーザーはんひとりあたりのドライブ容量"
driveCapacityPerRemoteAccount: "リモートユーザーはんひとりあたりのドライブ容量"
inMb: "メガバイト単位"
-iconUrl: "アイコン画像のURL"
bannerUrl: "バナー画像のURL"
backgroundImageUrl: "背景画像のURL"
basicInfo: "基本情報"
@@ -410,10 +414,14 @@ aboutMisskey: "Misskeyってなんや?"
administrator: "管理者"
token: "トークン"
2fa: "二要素認証"
+setupOf2fa: "二要素認証のセットアップ"
totp: "認証アプリ"
totpDescription: "認証アプリ使うてワンタイムパスワードを入れる"
moderator: "モデレーター"
moderation: "モデレーション"
+moderationNote: "モデレーションノート"
+addModerationNote: "モデレーションノートを追加するで"
+moderationLogs: "モデログ"
nUsersMentioned: "{n}人が投稿"
securityKeyAndPasskey: "セキュリティキー・パスキー"
securityKey: "セキュリティキー"
@@ -526,6 +534,7 @@ serverLogs: "サーバーログ"
deleteAll: "全部ほかす"
showFixedPostForm: "タイムラインの上の方で投稿できるようにやってくれへん?"
showFixedPostFormInChannel: "タイムラインの上の方で投稿できるようにするわ(チャンネル)"
+withRepliesByDefaultForNewlyFollowed: "フォローする時、デフォルトで返信をタイムラインに含むようにしよか"
newNoteRecived: "新しいノートがあるで"
sounds: "サウンド"
sound: "サウンド"
@@ -584,7 +593,7 @@ poll: "アンケート"
useCw: "内容を隠す"
enablePlayer: "プレイヤーを開く"
disablePlayer: "プレイヤーを閉じる"
-expandTweet: "ツイートを展開する"
+expandTweet: "ポストを展開する"
themeEditor: "テーマエディター"
description: "説明"
describeFile: "キャプションを付ける"
@@ -653,6 +662,7 @@ behavior: "動作"
sample: "サンプル"
abuseReports: "通報"
reportAbuse: "通報"
+reportAbuseRenote: "リノート苦情だすで?"
reportAbuseOf: "{name}を通報する"
fillAbuseReportDescription: "細かい通報理由を書いてなー。対象ノートがある時はそのURLも書いといてなー。"
abuseReported: "無事内容が送信されたみたいやで。おおきに〜。"
@@ -680,6 +690,7 @@ createNewClip: "新しいクリップを作るで"
unclip: "クリップ解除するで"
confirmToUnclipAlreadyClippedNote: "このノートはすでにクリップ「{name}」に含まれとるで。ノートをこのクリップから除外しよか?"
public: "パブリック"
+private: "非公開"
i18nInfo: "Misskeyは有志によっていろんな言語に翻訳されとるで。{link}で翻訳に協力したってやー。"
manageAccessTokens: "アクセストークンの管理"
accountInfo: "アカウント情報"
@@ -699,11 +710,12 @@ no: "あかん"
driveFilesCount: "ドライブのファイル数"
driveUsage: "ドライブ使用量やで"
noCrawle: "クローラーによるインデックスを拒否するで"
-noCrawleDescription: "検索エンジンにあんたのユーザーページ、ノート、Pagesとかのコンテンツを登録(インデックス)せぇへんように頼むで。"
+noCrawleDescription: "検索エンジンにあんたのユーザーページ、ノート、Pagesとかのコンテンツを登録(インデックス)せんように頼むで。邪魔すんねんやったら帰って〜。"
lockedAccountInfo: "フォローを承認制にしとっても、ノートの公開範囲を「フォロワー」にせぇへん限り、誰でもあんたのノートを見れるで。"
alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にするで"
loadRawImages: "添付画像のサムネイルをオリジナル画質にするで"
disableShowingAnimatedImages: "アニメーション画像を再生せんとくで"
+highlightSensitiveMedia: "メディアがセンシティブなことをめっっちゃわかりやすく表紙"
verificationEmailSent: "無事確認のメールを送れたで。メールに書いてあるリンクにアクセスして、設定を完了してなー。"
notSet: "未設定"
emailVerified: "メールアドレスは確認されたで"
@@ -745,7 +757,7 @@ value: "値"
createdAt: "作成した日"
updatedAt: "更新日時"
saveConfirm: "保存するで?"
-deleteConfirm: "ホンマに削除するで?"
+deleteConfirm: "ホンマにほかすで?"
invalidValue: "有効な値じゃないみたいやで。"
registry: "レジストリ"
closeAccount: "アカウントを閉鎖する"
@@ -967,6 +979,7 @@ assign: "アサイン"
unassign: "アサインを解除"
color: "色"
manageCustomEmojis: "カスタム絵文字の管理"
+manageAvatarDecorations: "アバターを飾るモンの管理"
youCannotCreateAnymore: "これ以上作れなさそうやわ"
cannotPerformTemporary: "一時的に利用できへんで"
cannotPerformTemporaryDescription: "操作回数が制限を超えたから一時的に利用できへんくなったで。ちょっと時間置いてからもう一回やってやー。"
@@ -1018,7 +1031,7 @@ retryAllQueuesConfirmText: "一時的にサーバー重なるかもしれへん
enableChartsForRemoteUser: "リモートユーザーのチャートを作る"
enableChartsForFederatedInstances: "リモートサーバーのチャートを作る"
showClipButtonInNoteFooter: "ノートのアクションにクリップを追加"
-largeNoteReactions: "ノートのツッコミを大きする"
+reactionsDisplaySize: "リアクションの表示のでかさ"
noteIdOrUrl: "ノートIDかURL"
video: "動画"
videos: "動画"
@@ -1070,7 +1083,7 @@ later: "あとで"
goToMisskey: "Misskeyへ"
additionalEmojiDictionary: "絵文字の追加辞書"
installed: "インストール済み"
-branding: "あ"
+branding: "ブランディング"
enableServerMachineStats: "サーバーのマシン情報見せびらかすで"
enableIdenticonGeneration: "ユーザーごとのIdenticon生成を有効にする"
turnOffToImprovePerformance: "オフにしたらえらい軽うなるで。"
@@ -1094,6 +1107,70 @@ expired: "期限切れ"
doYouAgree: "同意するんか?"
beSureToReadThisAsItIsImportant: "重要やから絶対読んでや。"
iHaveReadXCarefullyAndAgree: "「{x}」の内容をよう読んで、同意するで。"
+dialog: "ダイアログ"
+icon: "アイコン"
+forYou: "あんたへ"
+currentAnnouncements: "現在のお知らせやで"
+pastAnnouncements: "過去のお知らせやで"
+youHaveUnreadAnnouncements: "あんたまだこのお知らせ読んどらんやろ。"
+useSecurityKey: "ブラウザまたはデバイスの言う通りに、セキュリティキーまたはパスキーを使ってや。"
+replies: "返事"
+renotes: "Renote"
+loadReplies: "返信を見るで"
+loadConversation: "会話を見るで"
+pinnedList: "ピン留めしはったリスト"
+keepScreenOn: "デバイスの画面を常にオンにすんで"
+verifiedLink: "このリンク先の所有者であることが確認されたで。"
+notifyNotes: "投稿を通知"
+unnotifyNotes: "投稿の通知を解除すんで"
+authentication: "認証"
+authenticationRequiredToContinue: "続けるには認証をやってや。"
+dateAndTime: "日時"
+showRenotes: "リノートを表示"
+edited: "編集し終わってる"
+notificationRecieveConfig: "通知を受け取るかの設定"
+mutualFollow: "お互いフォローしてんで"
+fileAttachedOnly: "ファイル付きのみ"
+showRepliesToOthersInTimeline: "タイムラインに他の人への返信とかも含めんで"
+hideRepliesToOthersInTimeline: "タイムラインに他の人への返信とかは見ーへんで"
+showRepliesToOthersInTimelineAll: ""
+hideRepliesToOthersInTimelineAll: ""
+confirmShowRepliesAll: ""
+confirmHideRepliesAll: ""
+externalServices: "他のサイトのサービス"
+impressum: "運営者の情報"
+impressumUrl: "運営者の情報URL"
+impressumDescription: "ドイツなどのほんま1部の国と地域ではな、表示が義務付けられててん。(Impressum)"
+privacyPolicy: "プライバシーポリシー"
+privacyPolicyUrl: "プライバシーポリシーURL"
+tosAndPrivacyPolicy: "利用規約・プライバシーポリシー"
+avatarDecorations: "アイコンデコレーション"
+attach: ""
+detach: ""
+angle: ""
+flip: "反転"
+showAvatarDecorations: ""
+releaseToRefresh: "離してリロード"
+refreshing: "リロード中"
+pullDownToRefresh: "引っ張ってリロードするで"
+disableStreamingTimeline: "タイムラインのリアルタイム更新をやめるで"
+useGroupedNotifications: "通知をグルーピングしてだすで"
+signupPendingError: "メールアドレスの確認中に問題が起こってえらいこっちゃ。リンクの有効期限が切れてるかもやで"
+cwNotationRequired: "「内容を隠す」がオンの場合は注釈の記述が必要やで。"
+doReaction: "ツッコミすんで"
+_announcement:
+ forExistingUsers: "もうおるユーザーのみ"
+ forExistingUsersDescription: "有効にすると、このお知らせ作成時点でおるユーザーにのみお知らせが表示されます。無効にすると、このお知らせ作成後にアカウントを作成したユーザーにもお知らせが表示されます。"
+ needConfirmationToRead: "既読にするのに確認が必要やで"
+ needConfirmationToReadDescription: "有効にすると、このお知らせを既読にする際に確認ダイアログが表示されます。また、一括既読操作の対象にもなりません。"
+ end: "お知らせを終了"
+ tooManyActiveAnnouncementDescription: "アクティブなお知らせが多いため、UXが低下する可能性があります。終了したお知らせはアーカイブすることを検討した方がええよ。"
+ readConfirmTitle: "既読にしてええんやな?"
+ readConfirmText: "「{title}」の内容を読み、既読にします。"
+ shouldNotBeUsedToPresentPermanentInfo: "新規ユーザーのUXを損ねやすいから、お知らせはストック情報やのうてフロー情報の掲示に使った方がええで。"
+ dialogAnnouncementUxWarn: "ダイアログ形式のお知らせが同時に2つ以上ある場合、UXに悪影響を及ぼす可能性が高くなるから、使用は慎重にすんのがおすすめやで。"
+ silence: "通知せんで"
+ silenceDescription: "オンにすると、このお知らせは通知されないで、既読にする必要もなくなるで。"
_initialAccountSetting:
accountCreated: "アカウント作り終わったで。"
letsStartAccountSetup: "アカウントの初期設定をしよか。"
@@ -1106,11 +1183,36 @@ _initialAccountSetting:
pushNotificationDescription: "プッシュ通知を有効にすると{name}の通知をあんたのデバイスで受け取れるで。"
initialAccountSettingCompleted: "初期設定が終わったで。"
haveFun: "{name}、楽しんでな~"
- ifYouNeedLearnMore: "{name}(Misskey)の使い方とかをよー知りたいんやったら{link}をみてな。"
+ youCanContinueTutorial: "このまま{name}(Misskey)の使い方のチュートリアルに進めるけど、ここで中断してすぐに使い始めることもできるで。"
+ startTutorial: "チュートリアルを開始するで"
skipAreYouSure: "初期設定飛ばすか?"
laterAreYouSure: "初期設定あとでやり直すん?"
+_initialTutorial:
+ launchTutorial: "チュートリアルを見るで"
+ title: "チュートリアルやで"
+ wellDone: "やるやん"
+ skipAreYouSure: "チュートリアルをやめるか?"
+ _landing:
+ title: "チュートリアルによう来たな"
+ description: "ここでは、Misskeyの基本的な使い方や機能を確認できるで。"
+ _note:
+ title: "ノートってなんや?"
+ description: "Misskeyでの投稿は「ノート」って呼ばれてるで。ノートはタイムラインに時系列で並んでいて、リアルタイムで更新されてるで。"
+ reply: "返信することもできるで。返信に対しての返信も可能で、スレッドのように会話を続けることもできるで。"
+ renote: "そのノートを自分のタイムラインに流して共有することもできるで。テキストを追加して引用することもできるで。"
+ reaction: "ツッコミをつけることもできるで。細かいことは次のページで解説するで。"
_serverRules:
description: "新規登録前に見せる、サーバーの簡潔なルールを設定すんで。内容は使うための決め事の要約とすることを推奨するわ。"
+_serverSettings:
+ iconUrl: "アイコン画像のURL"
+ appIconDescription: "{host}がアプリとして表示してるんやつをアイコンを指定すんで。"
+ appIconUsageExample: "PWAや、スマートフォンのホーム画面にブックマークとして追加された時など"
+ appIconStyleRecommendation: "円形もしくは角丸にクロップされる場合があるさかいに、塗り潰された余白のある背景があるものが推奨されるで。"
+ appIconResolutionMustBe: "解像度は必ず{resolution}である必要があるで。"
+ manifestJsonOverride: "manifest.jsonのオーバーライド"
+ shortName: "略称"
+ shortNameDescription: "サーバーの名前が長い時に、代わりに表示することのできるあだ名。"
+ fanoutTimelineDescription: ""
_accountMigration:
moveFrom: "別のアカウントからこのアカウントに引っ越す"
moveFromSub: "別のアカウントへエイリアスを作る"
@@ -1365,6 +1467,9 @@ _achievements:
title: "Brain Diver"
description: "Brain Diverへのリンクを投稿したった"
flavor: "Misskey-Misskey La-Tu-Ma"
+ _smashTestNotificationButton:
+ title: "テスト過剰"
+ description: "通知テストをごく短時間のうちに連続して行ったねん"
_role:
new: "ロールの作成"
edit: "ロールの編集"
@@ -1408,6 +1513,7 @@ _role:
inviteLimitCycle: "招待コードの発行間隔"
inviteExpirationTime: "招待コードの有効期限"
canManageCustomEmojis: "カスタム絵文字の管理"
+ canManageAvatarDecorations: "アバターを飾るモンの管理"
driveCapacity: "ドライブ容量"
alwaysMarkNsfw: "勝手にファイルにNSFWをくっつける"
pinMax: "ノートのピン留めの最大数"
@@ -1422,6 +1528,7 @@ _role:
descriptionOfRateLimitFactor: "ちっちゃいほど制限が緩なって、大きいほど制限されるで。"
canHideAds: "広告を表示させへん"
canSearchNotes: "ノート検索を使わすかどうか"
+ canUseTranslator: "翻訳機能の利用"
_condition:
isLocal: "ローカルユーザー"
isRemote: "リモートユーザー"
@@ -1470,6 +1577,10 @@ _ad:
reduceFrequencyOfThisAd: "この広告の表示頻度を下げるで"
hide: "表示せん"
timezoneinfo: "曜日はサーバーのタイムゾーンを元に指定されるで。"
+ adsSettings: "広告配信設定"
+ notesPerOneAd: "リアタイ更新中に広告を出す間隔(ノートの個数な)"
+ setZeroToDisable: "0でリアタイ更新時の広告配信を無効にすんで"
+ adsTooClose: "広告を出す間隔がめっちゃ短いから、ユーザー体験が著しく損なわれる可能性があんで。"
_forgotPassword:
enterEmail: "アカウントに登録したメールアドレスをここに入力してや。そのアドレス宛に、パスワードリセット用のリンクが送られるから待っててな~。"
ifNoEmail: "メールアドレスを登録してへんのやったら、管理者まで教えてな~。"
@@ -1488,6 +1599,7 @@ _plugin:
install: "プラグインのインストール"
installWarn: "信頼できへんプラグインはインストールせんとってな"
manage: "プラグインの管理"
+ viewSource: "ソースを表示"
_preferencesBackups:
list: "作ったバックアップ"
saveNew: "新しく保存"
@@ -1521,6 +1633,7 @@ _aboutMisskey:
donate: "Misskeyに寄付"
morePatrons: "他にもぎょうさんの人からサポートしてもろてんねん。ほんまおおきに🥰"
patrons: "支援者"
+ projectMembers: ""
_displayOfSensitiveMedia:
respect: "きわどいのは見とうない"
ignore: "きわどいのも見たい"
@@ -1554,11 +1667,6 @@ _wordMute:
muteWords: "ミュートするワード"
muteWordsDescription: "スペースで区切るとAND指定になって、改行で区切るとOR指定になるで。"
muteWordsDescription2: "キーワードをスラッシュで囲むと正規表現になるで。"
- softDescription: "指定した条件のノートをタイムラインから隠すで。"
- hardDescription: "指定した条件のノートをタイムラインに追加しないようにするで。追加せーへんかったかったノートは、条件を変えても除外されたままになるで。"
- soft: "ソフト"
- hard: "ハード"
- mutedNotes: "ミュートされたノート"
_instanceMute:
instanceMuteDescription: "ミュートしたサーバーのユーザーへの返信を含めて、設定したインスタンスの全てのノートとRenoteをミュートにするで。"
instanceMuteDescription2: "改行で区切って設定するんやで"
@@ -1622,9 +1730,6 @@ _theme:
infoFg: "情報の文字"
infoWarnBg: "警告の背景"
infoWarnFg: "警告の文字"
- cwBg: "CW ボタンの背景"
- cwFg: "CW ボタンの文字"
- cwHoverBg: "CW ボタンの背景 (ホバー)"
toastBg: "通知トーストの背景"
toastFg: "通知トーストの文字"
buttonBg: "ボタンの背景"
@@ -1642,8 +1747,6 @@ _sfx:
note: "ノート"
noteMy: "ノート(自分)"
notification: "通知"
- chat: "チャット"
- chatBg: "チャット(バックグラウンド)"
antenna: "アンテナ受信"
channel: "チャンネル通知"
_ago:
@@ -1662,27 +1765,17 @@ _time:
minute: "分"
hour: "時間"
day: "日"
-_timelineTutorial:
- title: "Misskeyってなんや?"
- step1_1: "これは「タイムライン」や。{name}に投稿された「ノート」が順番に表示されるで。"
- step1_2: "タイムラインには何個か種類があってな、例えば「ホームタイムライン」だったらあんたのフォローしてる人のノート、「ローカルタイムライン」には{name}全部のノートが流れてくるで。"
- step2_1: "試しに、何かノートを投稿してみ。画面の鉛筆マークのボタンでフォームが開くで。"
- step2_2: "最初のノートは、自己紹介とか「{name}始めてみたんや」とかがええと思うで。"
- step3_1: "投稿できた?"
- step3_2: "あんたのノートがタイムラインに出てきたら成功や。"
- step4_1: "ノートには、「ツッコミ」を付けれるで。"
- step4_2: "ツッコむんやったら、ノートの「+」マークを押して、好きな絵文字を選ぶんやで。"
_2fa:
alreadyRegistered: "もう設定終わっとるわ。"
registerTOTP: "認証アプリの設定はじめる"
- passwordToTOTP: "パスワードを入れてーや"
step1: "ほんなら、{a}や{b}とかの認証アプリを使っとるデバイスにインストールしてな。"
step2: "次に、ここにあるQRコードをアプリでスキャンしてな~。"
step2Click: "QRコードをクリックすると、今使とる端末に入っとる認証アプリとかキーリングに登録できるで。"
- step2Url: "デスクトップアプリやったら次のURLを入力してや:"
+ step2Uri: "デスクトップアプリを使う時は次のURIを入れるで"
step3Title: "確認コードを入れてーや"
step3: "アプリに表示されているトークンを入力して終わりや。"
- step4: "これからログインするときも、同じようにトークンを入力するんやで\n\n認証アプリが使えなくなった時、アカウントへの緊急アクセスのためのバックアップコードを作成しました。\n紛失防止のため、必ず安全な場所に保管してください。\nこれらのコードはそれぞれ1回だけ使用することができます。\nもし全てのコードを使い切ってしまうとアカウントにアクセスすることができなくなってしまうので、出来るだけ早く認証アプリを再設定してください。\n\nバックアップコード:\n\n{codes}"
+ setupCompleted: "設定が完了したで。"
+ step4: "これからログインするときも、同じようにトークンを入力するんやで"
securityKeyNotSupported: "今使とるブラウザはセキュリティキーに対応してへんのやってさ。"
registerTOTPBeforeKey: "セキュリティキー・パスキーを登録するんやったら、まず認証アプリを設定してーな。"
securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキーか端末の指紋認証やPINを使ってログインするように設定できるで。"
@@ -1693,9 +1786,14 @@ _2fa:
removeKeyConfirm: "{name}を消すん?"
whyTOTPOnlyRenew: "セキュリティキーが登録されとったら、認証アプリの設定は解除できへんで。"
renewTOTP: "認証アプリをもっかい設定"
- renewTOTPConfirm: "今までの人称アプリの確認コードとバックアップコードは使えんくなるけどええか?"
+ renewTOTPConfirm: "今までの認証アプリの確認コードは使えんくなるけどええか?"
renewTOTPOk: "もっかい設定する"
renewTOTPCancel: "やめとく"
+ checkBackupCodesBeforeCloseThisWizard: "このウィザードを閉じる前に、したのバックアップコードを確認しいや。"
+ backupCodes: "バックアップコード"
+ backupCodesDescription: "認証アプリが使用できんなった場合、以下のバックアップコードを使ってアカウントにアクセスできるで。これらのコードは必ず安全な場所に置いときや。各コードは一回だけ使用できるで。"
+ backupCodeUsedWarning: "バックアップコードが使用されたで。認証アプリが使えなくなってるん場合、なるべく早く認証アプリを再設定しや。"
+ backupCodesExhaustedWarning: "バックアップコードが全て使用されたで。認証アプリを利用できん場合、これ以上アカウントにアクセスできなくなるで。認証アプリを再登録しや。"
_permissions:
"read:account": "アカウントの情報を見るで"
"write:account": "アカウントの情報を変更するで"
@@ -1729,6 +1827,10 @@ _permissions:
"write:gallery": "ギャラリーを操作するで"
"read:gallery-likes": "ギャラリーのいいねを見るで"
"write:gallery-likes": "ギャラリーのいいねを操作するで"
+ "read:flash": "Playを見る"
+ "write:flash": "Playを操作する"
+ "read:flash-likes": "Playのええやん!を見る"
+ "write:flash-likes": "Playのええやん!を見る"
_auth:
shareAccessTitle: "アプリへのアクセス許してやったらどうや"
shareAccess: "「{name}」がアカウントにアクセスすることを許可してええか?"
@@ -1744,6 +1846,7 @@ _antennaSources:
homeTimeline: "フォローしとるユーザーのノート"
users: "選らんだ一人か複数のユーザーのノート"
userList: "選んだリストのユーザーのノート"
+ userBlacklist: "選んだ1人か複数のユーザーのノート"
_weekday:
sunday: "日曜日"
monday: "月曜日"
@@ -1843,6 +1946,7 @@ _profile:
metadataContent: "内容"
changeAvatar: "アバター画像を変更するで"
changeBanner: "バナー画像を変更するで"
+ verifiedLinkDescription: "内容をURLに設定すると、リンク先のwebサイトに自分のプロフのリンクが含まれてる場合に所有者確認済みアイコンを表示させることができるで。"
_exportOrImport:
allNotes: "全てのノート"
favoritedNotes: "お気に入りにしたノート"
@@ -1852,6 +1956,7 @@ _exportOrImport:
userLists: "リスト"
excludeMutingUsers: "ミュートしてるユーザーは入れんとくわ"
excludeInactiveUsers: "使われてなさそうなアカウントは入れんとくわ"
+ withReplies: "インポートした人による返信をTLに含むようにすんで。"
_charts:
federation: "連合"
apRequest: "リクエスト"
@@ -1961,11 +2066,17 @@ _notification:
youReceivedFollowRequest: "フォロー許可してほしいみたいやな"
yourFollowRequestAccepted: "フォローさせてもろたで"
pollEnded: "アンケートの結果が出たみたいや"
+ newNote: "さらの投稿"
unreadAntennaNote: "アンテナ {name}"
emptyPushNotificationMessage: "プッシュ通知の更新をしといたで"
achievementEarned: "実績を獲得しとるで"
+ testNotification: "通知テスト"
+ checkNotificationBehavior: "通知の表示を確かめるで"
+ sendTestNotification: "テスト通知を送信するで"
+ notificationWillBeDisplayedLikeThis: "通知はこのように表示されるで"
_types:
all: "すべて"
+ note: "あんたらの新規投稿"
follow: "フォロー"
mention: "メンション"
reply: "リプライ"
@@ -1999,6 +2110,8 @@ _deck:
introduction2: "画面の右にある + を押して、いつでもカラムを追加できるで。"
widgetsIntroduction: "カラムのメニューから、「ウィジェットの編集」を選んでウィジェットを追加してなー"
useSimpleUiForNonRootPages: "非ルートページは簡易UIで表示"
+ usedAsMinWidthWhenFlexible: "「幅を自動調整」が有効の場合、これが幅の最小値となるで"
+ flexible: "幅を自動調整"
_columns:
main: "メイン"
widgets: "ウィジェット"
@@ -2033,3 +2146,81 @@ _webhookSettings:
renote: "Renoteされるとき~!"
reaction: "ツッコミがあるとき~!"
mention: "メンションがあるとき~!"
+_moderationLogTypes:
+ createRole: "ロールを追加すんで"
+ deleteRole: "ロールほかす"
+ updateRole: "ロールの更新すんで"
+ assignRole: "ロールへアサイン"
+ unassignRole: "ロールのアサインほかす"
+ suspend: "凍結"
+ unsuspend: "凍結解除"
+ addCustomEmoji: "自由な絵文字追加されたで"
+ updateCustomEmoji: "自由な絵文字更新されたで"
+ deleteCustomEmoji: "自由な絵文字消されたで"
+ updateServerSettings: "サーバー設定更新すんねん"
+ updateUserNote: "モデレーションノート更新"
+ deleteDriveFile: "ファイルをほかす"
+ deleteNote: "ノートを削除"
+ createGlobalAnnouncement: "みんなへの通告を作成したで"
+ createUserAnnouncement: "あんたらへの通告を作成したで"
+ updateGlobalAnnouncement: "みんなへの通告更新したったで"
+ updateUserAnnouncement: "あんたらへの通告更新したったで"
+ deleteGlobalAnnouncement: "みんなへの通告消したったで"
+ deleteUserAnnouncement: "あんたらへのお知らせを削除"
+ resetPassword: "パスワードをリセット"
+ suspendRemoteInstance: "リモートサーバーを止めんで"
+ unsuspendRemoteInstance: "リモートサーバーを再開すんで"
+ markSensitiveDriveFile: "ファイルをセンシティブ付与"
+ unmarkSensitiveDriveFile: "ファイルをセンシティブ解除"
+ resolveAbuseReport: "苦情を解決"
+ createInvitation: "招待コードを作成"
+ createAd: "広告を作んで"
+ deleteAd: "広告ほかす"
+ updateAd: "広告を更新"
+ createAvatarDecoration: "アイコンデコレーションを作成"
+ updateAvatarDecoration: "アイコンデコレーションを更新"
+ deleteAvatarDecoration: "アイコンデコレーションを削除"
+_fileViewer:
+ title: "ファイルの詳しい情報"
+ type: "ファイルの種類"
+ size: "ファイルのでかさ"
+ url: "URL"
+ uploadedAt: "追加した日"
+ attachedNotes: "ファイルがついてきてるノート"
+ thisPageCanBeSeenFromTheAuthor: "このページはこのファイルをアップした人しか見れへんねん。"
+_externalResourceInstaller:
+ title: "ほかのサイトからインストール"
+ checkVendorBeforeInstall: "配ってるとこが信頼できるか確認した上でインストールしてな。"
+ _plugin:
+ title: "このプラグイン、インストールする?"
+ metaTitle: "プラグイン情報"
+ _theme:
+ title: "このテーマインストールする?"
+ metaTitle: "テーマ情報"
+ _meta:
+ base: ""
+ _vendorInfo:
+ title: ""
+ endpoint: ""
+ hashVerify: ""
+ _errors:
+ _invalidParams:
+ title: ""
+ description: ""
+ _resourceTypeNotSupported:
+ title: ""
+ description: ""
+ _failedToFetch:
+ title: ""
+ _pluginParseFailed:
+ title: "AiScriptエラー起こしてもうたねん"
+ description: "データは取得できたものの、AiScript解析時にエラーがあったから読み込めへんかってん。すまんが、プラグインを作った人に問い合わせてくれへん?ごめんな。エラーの詳細はJavaScriptコンソール読んでな。"
+ _pluginInstallFailed:
+ title: "プラグインのインストール失敗してもた"
+ description: "プラグインのインストール中に問題発生してもた、もう1度試してな。エラーの詳細はJavaScriptのコンソール見てや。"
+ _themeParseFailed:
+ title: "テーマ解析エラー"
+ description: "データは取得できたものの、テーマファイル解析時にエラーがあったから読み込めへんかってん。すまんが、テーマ作った人に問い合わせてくれへん?ごめんな。エラーの詳細はJavaScriptコンソール読んでな。"
+ _themeInstallFailed:
+ title: "テーマインストールに失敗してもた"
+ description: "テーマのインストール中に問題発生してもた、もう1度試してな。エラーの詳細はJavaScriptのコンソール見てや。"
diff --git a/locales/jbo-EN.yml b/locales/jbo-EN.yml
index ed97d539c0..d4fea291d7 100644
--- a/locales/jbo-EN.yml
+++ b/locales/jbo-EN.yml
@@ -1 +1,3 @@
---
+_lang_: "la .lojban."
+headlineMisskey: "lo se tcana noi jorne fi loi notci"
diff --git a/locales/kab-KAB.yml b/locales/kab-KAB.yml
index 18fd8f5a58..22e24d3baa 100644
--- a/locales/kab-KAB.yml
+++ b/locales/kab-KAB.yml
@@ -56,6 +56,7 @@ accounts: "Imiḍan"
searchByGoogle: "Nadi"
file: "Ifuyla"
account: "Imiḍan"
+replies: "Err"
_email:
_follow:
title: "Yeṭṭafaṛ-ik·em-id"
diff --git a/locales/kn-IN.yml b/locales/kn-IN.yml
index ef66f3fbd2..b3ad46f2b1 100644
--- a/locales/kn-IN.yml
+++ b/locales/kn-IN.yml
@@ -61,6 +61,7 @@ smtpPass: "ಗುಪ್ತಪದ"
user: "ಬಳಕೆದಾರ"
searchByGoogle: "ಹುಡುಕು"
file: "ಕಡತಗಳು"
+replies: "ಉತ್ತರಿಸು"
_email:
_follow:
title: "ಹಿಂಬಾಲಿಸಿದರು"
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index 2a4011300f..cfe315e1dd 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.yml
@@ -2,20 +2,20 @@
_lang_: "한국어"
headlineMisskey: "노트로 연결되는 네트워크"
introMisskey: "환영합니다! Misskey는 오픈 소스 분산형 마이크로 블로그 서비스입니다.\n'노트'를 작성해서 지금 일어나고 있는 일을 공유하거나, 당신만의 이야기를 모두에게 발신하세요📡\n'리액션' 기능으로 친구의 노트에 총알같이 반응을 추가할 수도 있습니다👍\n새로운 세계를 탐험해 보세요🚀"
-poweredByMisskeyDescription: "{name}은(는) 오픈소스 플랫폼 Misskey를 사용한 서버 가운데 하나입니다."
+poweredByMisskeyDescription: "{name}은(는) 오픈소스 플랫폼Misskey를 사용한 서비스(Misskey 인스턴스라고 불립니다) 중 하나입니다."
monthAndDay: "{month}월 {day}일"
search: "검색"
notifications: "알림"
username: "유저명"
password: "비밀번호"
forgotPassword: "비밀번호 재설정"
-fetchingAsApObject: "연합에서 조회 중"
+fetchingAsApObject: "연합에 조회 중"
ok: "확인"
gotIt: "알겠어요"
cancel: "취소"
noThankYou: "나중에"
enterUsername: "유저명 입력"
-renotedBy: "{user}님의 리노트"
+renotedBy: "{user}님이 리노트"
noNotes: "노트가 없습니다"
noNotifications: "표시할 알림이 없습니다"
instance: "서버"
@@ -45,6 +45,7 @@ pin: "프로필에 고정"
unpin: "프로필에서 고정 해제"
copyContent: "내용 복사"
copyLink: "링크 복사"
+copyLinkRenote: "Renote 링크 복사"
delete: "삭제"
deleteAndEdit: "삭제 후 편집"
deleteAndEditConfirm: "이 노트를 삭제한 뒤 다시 편집하시겠습니까? 이 노트에 대한 리액션, 리노트, 답글 또한 모두 삭제됩니다."
@@ -112,7 +113,7 @@ cantReRenote: "리노트를 리노트할 수 없습니다."
quote: "인용"
inChannelRenote: "채널 내 리노트"
inChannelQuote: "채널 내 인용"
-pinnedNote: "고정해놓은 노트"
+pinnedNote: "고정된 노트"
pinned: "프로필에 고정"
you: "당신"
clickToShow: "클릭하여 보기"
@@ -155,13 +156,14 @@ emojiUrl: "이모지 URL"
addEmoji: "이모지 추가"
settingGuide: "추천 설정"
cacheRemoteFiles: "리모트 파일을 캐시"
-cacheRemoteFilesDescription: "이 설정을 해지하면 리모트 파일을 캐시하지 않고 해당 파일을 직접 링크하게 됩니다. 그에 따라 서버의 저장 공간을 절약할 수 있지만, 썸네일이 생성되지 않기 때문에 통신량이 증가합니다."
+cacheRemoteFilesDescription: "이 설정을 활성화하면 리모트 파일을 이 서버의 스토리지에 캐시합니다. 미디어의 표시가 빨라지지만, 서버의 저장 용량을 크게 소모합니다. 리모트 유저의 미디어를 얼마나 보관할 지는 역할의 드라이브 용량 제한에 따라 결정되며, 정해진 용량을 넘길 경우 오래된 파일부터 차례대로 삭제한 뒤 링크로 전환합니다. \n비활성화하면 리모트 파일을 직접 링크하며, 이 경우 이미지 썸네일 생성 및 유저 프라이버시 보호를 위해 default.yml에서 proxyRemoteFiles를 true로 설정하는 것을 권장합니다."
+youCanCleanRemoteFilesCache: "파일 관리 화면의 🗑️ 버튼을 눌러 모든 캐시를 삭제할 수 있습니다."
cacheRemoteSensitiveFiles: "리모트의 민감한 파일을 캐시"
cacheRemoteSensitiveFilesDescription: "이 설정을 비활성화하면 리모트의 민감한 파일은 캐시하지 않고 리모트에서 직접 가져오도록 합니다."
flagAsBot: "나는 봇입니다"
flagAsBotDescription: "이 계정을 자동화된 수단으로 운용할 경우에 활성화해 주세요. 이 플래그를 활성화하면, 다른 봇이 이를 참고하여 봇 끼리의 무한 연쇄 반응을 회피하거나, 이 계정의 시스템 상에서의 취급이 Bot 운영에 최적화되는 등의 변화가 생깁니다."
-flagAsCat: "나는 고양이다냥"
-flagAsCatDescription: "이 계정이 고양이라면 활성화해 주세요."
+flagAsCat: "미야아아아오오오오오오오오오옹!!!!!!!"
+flagAsCatDescription: "야옹?"
flagShowTimelineReplies: "타임라인에 노트의 답글을 표시하기"
flagShowTimelineRepliesDescription: "이 설정을 활성화하면 타임라인에 다른 유저 간의 답글을 표시합니다."
autoAcceptFollowed: "팔로우 중인 유저로부터의 팔로우 요청을 자동 수락"
@@ -193,6 +195,7 @@ perHour: "1시간마다"
perDay: "1일마다"
stopActivityDelivery: "액티비티 보내지 않기"
blockThisInstance: "이 서버를 차단"
+silenceThisInstance: "서버를 사일런스"
operations: "작업"
software: "소프트웨어"
version: "버전"
@@ -212,6 +215,8 @@ clearCachedFiles: "캐시 비우기"
clearCachedFilesConfirm: "캐시된 리모트 파일을 모두 삭제하시겠습니까?"
blockedInstances: "차단된 서버"
blockedInstancesDescription: "차단하려는 서버의 호스트 이름을 줄바꿈으로 구분하여 설정합니다. 차단된 인스턴스는 이 인스턴스와 통신할 수 없게 됩니다."
+silencedInstances: "사일런스한 서버"
+silencedInstancesDescription: "사일런스하려는 서버의 호스트명을 한 줄에 하나씩 입력합니다. 사일런스된 서버에 소속된 유저는 모두 '사일런스'된 상태로 취급되며, 이 서버로부터의 팔로우가 프로필 설정과 무관하게 승인제로 변경되고, 팔로워가 아닌 로컬 유저에게는 멘션할 수 없게 됩니다. 정지된 서버에는 적용되지 않습니다."
muteAndBlock: "뮤트 및 차단"
mutedUsers: "뮤트한 유저"
blockedUsers: "차단한 유저"
@@ -329,7 +334,7 @@ watch: "지켜보기"
unwatch: "지켜보기 해제"
accept: "허가"
reject: "거부"
-normal: "정상"
+normal: "일반"
instanceName: "서버 이름"
instanceDescription: "서버 소개"
maintainerName: "관리자 이름"
@@ -354,7 +359,6 @@ invite: "초대"
driveCapacityPerLocalAccount: "로컬 유저 한 명당 드라이브 용량"
driveCapacityPerRemoteAccount: "리모트 유저 한 명당 드라이브 용량"
inMb: "메가바이트 단위"
-iconUrl: "아이콘 URL"
bannerUrl: "배너 이미지 URL"
backgroundImageUrl: "배경 이미지 URL"
basicInfo: "기본 정보"
@@ -363,7 +367,7 @@ pinnedUsersDescription: "\"발견하기\" 페이지 등에 고정하고 싶은
pinnedPages: "고정한 페이지"
pinnedPagesDescription: "서버의 대문에 고정하고 싶은 페이지의 경로를 한 줄에 하나씩 적습니다."
pinnedClipId: "고정할 클립의 ID"
-pinnedNotes: "고정해놓은 노트"
+pinnedNotes: "고정된 노트"
hcaptcha: "hCaptcha"
enableHcaptcha: "hCaptcha 활성화"
hcaptchaSiteKey: "사이트 키"
@@ -410,11 +414,14 @@ aboutMisskey: "Misskey에 대하여"
administrator: "관리자"
token: "토큰"
2fa: "2단계 인증"
+setupOf2fa: "2단계 인증 설정"
totp: "인증 앱"
totpDescription: "인증 앱을 사용하여 일회성 비밀번호 입력"
-useSecurityKey: "브라우저 또는 장치의 안내에 따라 보안 키 또는 패스키를 사용해 주세요."
moderator: "모더레이터"
moderation: "모더레이션"
+moderationNote: "모더레이션 노트"
+addModerationNote: "모더레이션 노트 추가하기"
+moderationLogs: "모더레이션 로그"
nUsersMentioned: "{n}명이 언급함"
securityKeyAndPasskey: "보안 키 또는 패스 키"
securityKey: "보안 키"
@@ -527,6 +534,7 @@ serverLogs: "서버 로그"
deleteAll: "모두 삭제"
showFixedPostForm: "타임라인 상단에 글 작성란을 표시"
showFixedPostFormInChannel: "채널 타임라인 상단에 글 작성란을 표시"
+withRepliesByDefaultForNewlyFollowed: "팔로우 할 때 기본적으로 답글을 타임라인에 나오게 하기"
newNoteRecived: "새 노트가 있습니다"
sounds: "소리"
sound: "소리"
@@ -585,7 +593,7 @@ poll: "투표"
useCw: "내용 숨기기"
enablePlayer: "플레이어 열기"
disablePlayer: "플레이어 닫기"
-expandTweet: "트윗 확장하기"
+expandTweet: "게시물 확장하기"
themeEditor: "테마 에디터"
description: "설명"
describeFile: "캡션 추가"
@@ -654,6 +662,7 @@ behavior: "동작"
sample: "예시"
abuseReports: "신고"
reportAbuse: "신고"
+reportAbuseRenote: "Renote를 신고"
reportAbuseOf: "{name}을 신고하기"
fillAbuseReportDescription: "신고하려는 이유를 자세히 알려주세요. 특정 게시물을 신고할 때에는 게시물의 URL도 포함해 주세요."
abuseReported: "신고를 보냈습니다. 신고해 주셔서 감사합니다."
@@ -681,6 +690,7 @@ createNewClip: "새 클립 만들기"
unclip: "클립 해제"
confirmToUnclipAlreadyClippedNote: "이 노트는 이미 \"{name}\" 클립에 포함되어 있습니다. 클립을 해제하시겠습니까?"
public: "공개"
+private: "비공개"
i18nInfo: "Misskey는 자원봉사자들에 의해 다양한 언어로 번역되고 있습니다. {link}에서 번역에 참가할 수 있습니다."
manageAccessTokens: "액세스 토큰 관리"
accountInfo: "계정 정보"
@@ -705,6 +715,7 @@ lockedAccountInfo: "팔로우를 승인으로 승인받더라도 노트의 공
alwaysMarkSensitive: "미디어를 항상 열람 주의로 설정"
loadRawImages: "첨부한 이미지의 썸네일을 원본화질로 표시"
disableShowingAnimatedImages: "움직이는 이미지를 자동으로 재생하지 않음"
+highlightSensitiveMedia: "미디어가 민감한 내용이라는 것을 알기 쉽게 표시"
verificationEmailSent: "확인 메일을 발송하였습니다. 설정을 완료하려면 메일에 첨부된 링크를 확인해 주세요."
notSet: "설정되지 않음"
emailVerified: "메일 주소가 확인되었습니다."
@@ -877,7 +888,7 @@ numberOfColumn: "한 줄에 보일 리액션의 수"
searchByGoogle: "검색"
instanceDefaultLightTheme: "서버 기본 라이트 테마"
instanceDefaultDarkTheme: "서버 기본 다크 테마"
-instanceDefaultThemeDescription: "객체 형식의 테마 코드를 입력해 주세요."
+instanceDefaultThemeDescription: "객체 형식({}로 감싼 형태)의 테마 코드를 입력해 주세요."
mutePeriod: "뮤트할 기간"
period: "기간"
indefinitely: "무기한"
@@ -968,6 +979,7 @@ assign: "할당"
unassign: "할당 취소"
color: "색"
manageCustomEmojis: "커스텀 이모지 관리"
+manageAvatarDecorations: "아바타 꾸미기 관리"
youCannotCreateAnymore: "더 이상 생성할 수 없습니다."
cannotPerformTemporary: "일시적으로 사용할 수 없음"
cannotPerformTemporaryDescription: "조작 횟수 제한을 초과하여 일시적으로 사용이 불가합니다. 잠시 후 다시 시도해 주세요."
@@ -1019,7 +1031,7 @@ retryAllQueuesConfirmText: "일시적으로 서버의 부하가 증가할 수
enableChartsForRemoteUser: "리모트 유저의 차트를 생성"
enableChartsForFederatedInstances: "리모트 서버의 차트를 생성"
showClipButtonInNoteFooter: "노트 동작에 클립을 추가"
-largeNoteReactions: "노트의 리액션을 크게 표시"
+reactionsDisplaySize: "리액션 표시 크기"
noteIdOrUrl: "노트 ID 및 URL"
video: "동영상"
videos: "동영상"
@@ -1092,6 +1104,73 @@ usedAt: "사용 시각"
unused: "사용되지 않음"
used: "사용됨"
expired: "만료됨"
+doYouAgree: "동의하십니까?"
+beSureToReadThisAsItIsImportant: "중요하므로 반드시 읽어주십시오."
+iHaveReadXCarefullyAndAgree: "\"{x}\"의 내용을 읽고 동의합니다."
+dialog: "다이얼로그"
+icon: "아바타"
+forYou: "당신에게"
+currentAnnouncements: "현재 공지사항"
+pastAnnouncements: "과거 공지사항"
+youHaveUnreadAnnouncements: "읽지 않은 공지사항이 있습니다."
+useSecurityKey: "브라우저 또는 기기의 안내에 따라 보안 키 또는 패스키를 사용해 주십시오."
+replies: "답글"
+renotes: "리노트"
+loadReplies: "답글 보기"
+loadConversation: "대화 보기"
+pinnedList: "고정된 리스트"
+keepScreenOn: "기기 화면을 항상 켜기"
+verifiedLink: "이 링크의 소유자임이 확인되었습니다."
+notifyNotes: "새 노트 알림 켜기"
+unnotifyNotes: "새 노트 알림 끄기"
+authentication: "인증"
+authenticationRequiredToContinue: "계속하려면 인증하십시오"
+dateAndTime: "일시"
+showRenotes: "리노트 표시"
+edited: "수정됨"
+notificationRecieveConfig: "알림 설정"
+mutualFollow: "맞팔로우"
+fileAttachedOnly: "미디어를 포함한 노트만"
+showRepliesToOthersInTimeline: "타임라인에 다른 사람에게 보내는 답글을 포함"
+hideRepliesToOthersInTimeline: "타임라인에 다른 사람에게 보내는 답글을 포함하지 않음"
+showRepliesToOthersInTimelineAll: "타임라인에 현재 팔로우 중인 사람 전원의 답글을 포함하게 하기"
+hideRepliesToOthersInTimelineAll: "타임라인에 현재 팔로우 중인 사람 전원의 답글이 나오지 않게 하기"
+confirmShowRepliesAll: "이 조작은 되돌릴 수 없습니다. 정말로 타임라인에 현재 팔로우 중인 사람 전원의 답글이 나오지 않게 하시겠습니까?"
+confirmHideRepliesAll: "이 조작은 되돌릴 수 없습니다. 정말로 타임라인에 현재 팔로우 중인 사람 전원의 답글이 나오지 않게 하시겠습니까?"
+externalServices: "외부 서비스"
+impressum: "운영자 정보"
+impressumUrl: "운영자 정보 URL"
+impressumDescription: "독일 등의 일부 나라와 지역에서는 꼭 표시해야 합니다(Impressum)."
+privacyPolicy: "개인정보 보호 정책"
+privacyPolicyUrl: "개인정보 보호 정책 URL"
+tosAndPrivacyPolicy: "약관 및 개인정보 보호 정책"
+avatarDecorations: "아이콘 장식"
+attach: "붙이기"
+detach: "떼기"
+angle: "각도"
+flip: "플립"
+showAvatarDecorations: "아이콘 장식을 표시"
+releaseToRefresh: "놓아서 새로고침"
+refreshing: "새로고침 중"
+pullDownToRefresh: "아래로 내려서 새로고침"
+disableStreamingTimeline: "타임라인의 실시간 갱신을 무효화하기"
+useGroupedNotifications: "알림을 그룹화하고 표시"
+signupPendingError: "메일 주소 확인중에 문제가 발생했습니다. 링크의 유효기간이 지났을 가능성이 있습니다."
+cwNotationRequired: "'내용을 숨기기'를 체크한 경우 주석을 써야 합니다."
+doReaction: "리액션 추가"
+_announcement:
+ forExistingUsers: "기존 유저에게만 알림"
+ forExistingUsersDescription: "활성화하면 이 공지사항을 게시한 시점에서 이미 가입한 유저에게만 표시합니다. 비활성화하면 게시 후에 가입한 유저에게도 표시합니다."
+ needConfirmationToRead: "읽음으로 표시하기 전에 확인하기"
+ needConfirmationToReadDescription: "활성화하면 이 공지사항을 읽음으로 표시하기 전에 확인 알림창을 띄웁니다. '모두 읽음'의 대상에서도 제외됩니다."
+ end: "공지에서 내리기"
+ tooManyActiveAnnouncementDescription: "공지사항이 너무 많을 경우, 사용자 경험에 영향을 끼칠 가능성이 있습니다. 오래된 공지사항은 아카이브하시는 것을 권장드립니다."
+ readConfirmTitle: "읽음으로 표시합니까?"
+ readConfirmText: "\"{title}\"을(를) 읽음으로 표시합니다."
+ shouldNotBeUsedToPresentPermanentInfo: "신규 유저의 이용 경험에 악영향을 끼칠 수 있으므로, 일시적인 알림 수단으로만 사용하고 고정된 정보에는 사용을 지양하는 것을 추천합니다."
+ dialogAnnouncementUxWarn: "다이얼로그 형태의 알림이 동시에 2개 이상 존재하는 경우, 사용자 경험에 악영향을 끼칠 수 있으므로 신중히 결정하십시오."
+ silence: "조용히 알림"
+ silenceDescription: "활성화하면 공지사항에 대한 알림이 가지 않게 되며, 확인 버튼을 누를 필요가 없게 됩니다."
_initialAccountSetting:
accountCreated: "계정 생성이 완료되었습니다!"
letsStartAccountSetup: "계정의 초기 설정을 진행합니다."
@@ -1104,11 +1183,89 @@ _initialAccountSetting:
pushNotificationDescription: "푸시 알림을 활성화하면 {name}의 알림을 나의 기기에서 받아볼 수 있게 됩니다."
initialAccountSettingCompleted: "초기 설정을 모두 마쳤습니다!"
haveFun: "{name}와 함께 즐거운 시간 보내세요!"
- ifYouNeedLearnMore: "{name}(Misskey)의 사용 방법에 대해 자세히 알아보려면 {link}를 참고해 주세요."
+ youCanContinueTutorial: "이대로 {name}(Misskey)의 사용법에 대해 튜토리얼을 진행할 수도 있지만, 여기서 중단하고 바로 시작할 수도 있습니다."
+ startTutorial: "튜토리얼 시작"
skipAreYouSure: "초기 설정을 중단하시겠습니까?"
laterAreYouSure: "초기 설정을 나중에 진행하시겠습니까?"
+_initialTutorial:
+ launchTutorial: "튜토리얼 보기"
+ title: "튜토리얼"
+ wellDone: "잘 하셨습니다"
+ skipAreYouSure: "튜토리얼을 종료하시겠습니까?"
+ _landing:
+ title: "튜토리얼에 오신 걸 환영합니다"
+ description: "여기서는 미스키의 기본적인 사용법이나 기능을 확인할 수 있습니다."
+ _note:
+ title: "'노트'가 무엇인가요?"
+ description: "미스키에서는 게시물을 '노트'라고 합니다. 노트는 타임라인에 시간순으로 정렬되어 있고, 실시간으로 갱신됩니다."
+ reply: "답글을 다는 것이 가능합니다. 답글에 답글을 다는 것도 가능하며 스레드처럼 대화를 계속하는 것도 가능합니다."
+ renote: "그 노트를 자기 타임라인에 가져와서 공유하는 것이 가능합니다. 글을 추가해서 인용하는 것도 가능합니다."
+ reaction: "리액션을 다는 것이 가능합니다. 다음 페이지에서 자세한 설명을 볼 수 있습니다."
+ menu: "노트의 상세 정보를 표시하거나, 링크를 복사하는 등의 다양한 조작을 할 수 있습니다."
+ _reaction:
+ title: "'리액션'이 무엇인가요?"
+ description: "노트에 '리액션'을 보낼 수 있습니다. '좋아요'만으로는 충분히 전해지지 않는 감정을, 이모지에 실어서 가볍게 보낼 수 있습니다."
+ letsTryReacting: "리액션은 노트의 '+' 버튼을 클릭하여 붙일 수 있습니다. 지금 표시되는 샘플 노트에 리액션을 달아 보세요!"
+ reactToContinue: "다음으로 진행하려면 리액션을 보내세요."
+ reactNotification: "누군가가 나의 노트에 리액션을 보내면 실시간으로 알림을 받게 됩니다."
+ reactDone: "'-' 버튼을 눌러서 리액션을 취소할 수 있습니다."
+ _timeline:
+ title: "타임라인에 대하여"
+ description1: "Misskey에는 종류에 따라 여러 가지의 타임라인으로 구성되어 있습니다. (서버에 따라서는 일부 타임라인을 사용할 수 없는 경우가 있습니다)"
+ home: "내가 팔로우 중인 계정의 노트를 볼 수 있습니다."
+ local: "이 서버에 있는 모든 유저의 게시물을 볼 수 있습니다."
+ social: "홈 타임라인과 로컬 타임라인의 게시물을 모두 볼 수 있습니다."
+ global: "연결되어 있는 모든 서버의 게시물을 볼 수 있습니다."
+ description2: "각각의 타임라인은 화면 상단에서 언제든지 변경할 수 있습니다."
+ description3: "이 외에도, '리스트 타임라인'이나 '채널 타임라인' 등이 있습니다. 자세한 사항은 {link}에서 확인하실 수 있습니다."
+ _postNote:
+ title: "노트 게시 설정"
+ description1: "Misskey에 노트를 쓸 때에는 다양한 옵션을 설정할 수 있습니다. 노트를 작성하는 화면은 이렇게 생겼습니다."
+ _visibility:
+ description: "노트를 볼 수 있는 사람을 제한할 수 있습니다."
+ public: "모든 유저에게 공개합니다."
+ home: "홈 타임라인에만 공개합니다. 팔로워, 프로필 화면, 리노트를 통해서 다른 유저가 볼 수 있습니다."
+ followers: "팔로워에게만 공개. 자기 자신을 제외하고는 리노트가 불가능하며, 팔로워 외에는 열람할 수 없습니다."
+ direct: "지정한 유저에게만 공개되며, 상대방에게 알림이 갑니다. 다이렉트 메시지(DM) 대용으로써 사용하실 수 있습니다."
+ doNotSendConfidencialOnDirect1: "민감한 정보를 보낼 때에는 주의하십시오."
+ doNotSendConfidencialOnDirect2: "서버 관리자는 기술적으로 게시물 내용을 열람할 수 있습니다. 신뢰할 수 없는 서버의 유저에게 다이렉트 메시지를 보내는 경우, 민감한 정보가 포함되어 있는 지 확인하십시오."
+ localOnly: "다른 서버에 게시물을 보내지 않습니다. 앞서 설정한 공개 범위와 상관 없이, 다른 서버의 유저는 이 게시물을 직접 열람할 수 없게 됩니다."
+ _cw:
+ title: "내용 가리기 (CW)"
+ description: "본문 대신에 '내용에 대한 주석'에 입력한 텍스트가 먼저 표시됩니다. '더 보기' 버튼을 누르면 본문이 표시됩니다."
+ _exampleNote:
+ cw: "배고픈 사람 주의"
+ note: "방금 초코도넛을 먹었어요 🍩😋"
+ useCases: "서버의 가이드라인에 따라 특정 주제를 다룰 때에 사용하거나, 스포일러 및 민감한 화제를 다룰 때에 자율적으로 사용하기도 합니다."
+ _howToMakeAttachmentsSensitive:
+ title: "첨부 파일을 열람주의로 설정하려면?"
+ description: "서버의 가이드라인에 따라 필요한 이미지, 또는 그대로 노출되기에 부적절한 미디어는 '열람 주의'를 설정해 주세요."
+ tryThisFile: "이 작성 창에 첨부된 이미지를 열람 주의로 설정해 보세요!"
+ _exampleNote:
+ note: "낫또 뚜껑 뜯다가 실수했다…"
+ method: "첨부 파일을 열람 주의로 설정하려면, 해당 파일을 클릭하여 메뉴를 열고, '열람주의로 설정'을 클릭합니다."
+ sensitiveSucceeded: "파일을 첨부할 때에는 서버의 가이드라인에 따라 적절히 열람주의를 설정해 주시기 바랍니다."
+ doItToContinue: "이미지를 열람 주의로 설정하면 다음으로 넘어갈 수 있게 됩니다."
+ _done:
+ title: "튜토리얼이 끝났습니다! 🎉"
+ description: "여기에서 소개한 기능은 극히 일부에 지나지 않습니다. Misskey의 사용 방법을 더 자세히 알아보려면 {link}를 확인해 주세요!"
+_timelineDescription:
+ home: "홈 타임라인에서는, 내가 팔로우한 계정의 게시물을 볼 수 있습니다."
+ local: "로컬 타임라인에서는, 이 서버의 모든 유저의 게시물을 볼 수 있습니다."
+ social: "소셜 타임라인에서는, 홈 타임라인과 로컬 타임라인의 게시물을 모두 볼 수 있습니다."
+ global: "글로벌 타임라인에서는, 여기와 연결된 다른 모든 서버의 게시물을 볼 수 있습니다."
_serverRules:
description: "회원 가입 이전에 간단하게 표시할 서버 규칙입니다. 이용 약관의 요약으로 구성하는 것을 추천합니다."
+_serverSettings:
+ iconUrl: "아이콘 URL"
+ appIconDescription: "{host}이 앱으로 표시될 때의 아이콘을 지정합니다."
+ appIconUsageExample: "예를 들어, PWA나 스마트폰 홈 화면에 북마크로 추가되었을 때 등"
+ appIconStyleRecommendation: "아이콘이 원형 또는 둥근 사각형으로 잘리는 경우가 있으므로, 가장자리 여백이 충분한 사진을 사용하는 것을 추천합니다."
+ appIconResolutionMustBe: "해상도는 반드시 {resolution} 이어야 합니다."
+ manifestJsonOverride: "manifest.json 오버라이드"
+ shortName: "약칭"
+ shortNameDescription: "서버의 정식 명칭이 긴 경우에, 대신에 표시할 수 있는 약칭이나 통칭."
+ fanoutTimelineDescription: "활성화하면 각종 타임라인을 가져올 때의 성능을 대폭 향상하며, 데이터베이스의 부하를 줄일 수 있습니다. 단, Redis의 메모리 사용량이 증가합니다. 서버의 메모리 용량이 작거나, 서비스가 불안정해지는 경우 비활성화할 수 있습니다."
_accountMigration:
moveFrom: "다른 계정에서 이 계정으로 이사"
moveFromSub: "다른 계정에 대한 별칭을 생성"
@@ -1363,6 +1520,12 @@ _achievements:
title: "Brain Diver"
description: "Brain Diver로의 링크를 첨부했습니다"
flavor: "Misskey-Misskey La-Tu-Ma"
+ _smashTestNotificationButton:
+ title: "테스트 과잉"
+ description: "매우 짧은 시간 안에 알림 테스트를 여러 번 수행했습니다"
+ _tutorialCompleted:
+ title: "Misskey 입문자 과정 수료증"
+ description: "튜토리얼을 완료했습니다"
_role:
new: "새 역할 생성"
edit: "역할 수정"
@@ -1406,6 +1569,7 @@ _role:
inviteLimitCycle: "초대 발급 간격"
inviteExpirationTime: "초대 만료 기간"
canManageCustomEmojis: "커스텀 이모지 관리"
+ canManageAvatarDecorations: "아바타 꾸미기 관리"
driveCapacity: "드라이브 용량"
alwaysMarkNsfw: "파일을 항상 NSFW로 지정"
pinMax: "고정할 수 있는 노트 수"
@@ -1420,6 +1584,7 @@ _role:
descriptionOfRateLimitFactor: "작을수록 제한이 완화되고, 클수록 제한이 강화됩니다."
canHideAds: "광고 숨기기"
canSearchNotes: "노트 검색 이용 가능 여부"
+ canUseTranslator: "번역 기능의 사용"
_condition:
isLocal: "로컬 사용자"
isRemote: "리모트 사용자"
@@ -1468,6 +1633,10 @@ _ad:
reduceFrequencyOfThisAd: "이 광고의 표시 빈도 낮추기"
hide: "보이지 않음"
timezoneinfo: "요일은 서버의 표준 시간대에 따라 결정됩니다."
+ adsSettings: "광고 표시 설정"
+ notesPerOneAd: "실시간으로 갱신되는 타임라인에서 광고를 노출시키는 간격 (노트 당)"
+ setZeroToDisable: "0으로 지정하면 실시간 타임라인에서의 광고를 비활성화합니다"
+ adsTooClose: "광고의 표시 간격이 매우 작아, 사용자 경험에 부정적인 영향을 미칠 수 있습니다."
_forgotPassword:
enterEmail: "여기에 계정에 등록한 메일 주소를 입력해 주세요. 입력한 메일 주소로 비밀번호 재설정 링크를 발송합니다."
ifNoEmail: "메일 주소를 등록하지 않은 경우, 관리자에 문의해 주십시오."
@@ -1486,6 +1655,7 @@ _plugin:
install: "플러그인 설치"
installWarn: "신뢰할 수 없는 플러그인은 설치하지 않는 것이 좋습니다."
manage: "플러그인 관리"
+ viewSource: "소스 보기"
_preferencesBackups:
list: "생성한 백업"
saveNew: "새 백업 만들기"
@@ -1519,6 +1689,7 @@ _aboutMisskey:
donate: "Misskey에 기부하기"
morePatrons: "이 외에도 다른 많은 분들이 도움을 주시고 계십니다. 감사합니다🥰"
patrons: "후원자"
+ projectMembers: "프로젝트 구성원"
_displayOfSensitiveMedia:
respect: "민감한 콘텐츠로 표시된 미디어 숨기기"
ignore: "민감한 콘텐츠로 표시된 미디어 보이기"
@@ -1543,6 +1714,7 @@ _channel:
notesCount: "{n}노트"
nameAndDescription: "이름과 설명"
nameOnly: "이름만"
+ allowRenoteToExternal: "채널 외부로의 리노트와 인용 리노트를 허가"
_menuDisplay:
sideFull: "가로"
sideIcon: "가로(아이콘)"
@@ -1552,11 +1724,6 @@ _wordMute:
muteWords: "뮤트할 단어"
muteWordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다."
muteWordsDescription2: "정규 표현식을 사용하려면 키워드를 빗금표(/)로 감싸 주세요."
- softDescription: "지정한 조건의 노트를 타임라인에서 숨깁니다."
- hardDescription: "지정한 조건의 노트를 타임라인에 추가하지 않습니다. 타임라인에 추가되지 않은 노트는 조건을 변경해도 표시되지 않습니다."
- soft: "보통"
- hard: "보다 높은 수준"
- mutedNotes: "뮤트된 노트"
_instanceMute:
instanceMuteDescription: "뮤트한 서버에서 오는 답글을 포함한 모든 노트와 Renote를 뮤트합니다."
instanceMuteDescription2: "한 줄에 하나씩 입력해 주세요"
@@ -1620,9 +1787,6 @@ _theme:
infoFg: "정보창 텍스트"
infoWarnBg: "경고창 배경"
infoWarnFg: "경고창 텍스트"
- cwBg: "CW 버튼 배경"
- cwFg: "CW 버튼 텍스트"
- cwHoverBg: "CW 버튼 배경 (호버)"
toastBg: "알림창 배경"
toastFg: "알림창 텍스트"
buttonBg: "버튼 배경"
@@ -1640,8 +1804,6 @@ _sfx:
note: "새 노트"
noteMy: "내 노트"
notification: "알림"
- chat: "대화"
- chatBg: "대화 (백그라운드)"
antenna: "안테나 수신"
channel: "채널 알림"
_ago:
@@ -1660,29 +1822,17 @@ _time:
minute: "분"
hour: "시간"
day: "일"
-_timelineTutorial:
- title: "Misskey의 사용 방법"
- step1_1: "이것은 '타임라인'입니다. {name}에 게시된 '노트'가 시간 순서대로 표시됩니다."
- step1_2: "타임라인은 몇 가지 종류로 나뉩니다. 그 중에 '홈 타임라인'은 내가 팔로우한 사람의 노트가 표시되며, '로컬 타임라인'에는 {name} 의 모든 노트가 표시됩니다."
- step2_1: "그럼 시험삼아 노트를 작성해 봅시다. 화면에 있는 연필 버튼을 눌러 보세요."
- step2_2: "첫 노트이니까 자기소개, 혹은 가볍게 \"안녕 {name}\"라고 올려 보는 건 어떨까요?"
- step3_1: "노트 작성을 끝내셨나요?"
- step3_2: "당신의 노트가 타임라인에 표시되어 있다면 성공입니다."
- step4_1: "노트에는 '리액션'을 붙일 수 있습니다."
- step4_2: "리액션을 붙이려면, 노트의 \"+\" 버튼을 클릭하고 원하는 이모지를 선택합니다."
_2fa:
alreadyRegistered: "이미 설정이 완료되었습니다."
registerTOTP: "인증 앱 설정 시작"
- passwordToTOTP: "비밀번호를 입력하세요."
step1: "먼저, {a}나 {b}등의 인증 앱을 사용 중인 디바이스에 설치합니다."
step2: "그 후, 표시되어 있는 QR코드를 앱으로 스캔합니다."
step2Click: "QR 코드를 클릭하면 기기에 설치된 인증 앱에 등록할 수 있습니다."
- step2Url: "데스크톱 앱에서는 다음 URL을 입력하세요:"
+ step2Uri: "데스크톱 앱을 사용하려면 다음 URI를 입력하십시오"
step3Title: "인증 코드 입력"
step3: "앱에 표시된 토큰을 입력하시면 완료됩니다."
- step4: "다음 로그인부터는 토큰을 입력해야 합니다.\n\n인증 앱이 사용할 수 없게 되었을 때, 계정에 응급 접근할 수 있는 백업 코드를 생성했습니다.\n코드를 잃어버리지 않도록 안전한 곳에 보관해 두세요.\n코드는 각각 1번씩만 사용할 수 있습니다.\n만일 모든 코드를 사용해버렸다면 더이상 계정에 접근할 수 없게 되므로, 가능한 한 빨리 인증 앱을 다시 등록해 주세요.\n\n백업 코드:\n\n{code}"
- twoFactorBackupSecretWarning: "백업 코드가 사용되었습니다. 인증 앱을 사용할 수 없는 경우, 가능한 한 빨리 인증 앱을 다시 등록해 주세요."
- twoFactorBackupSecretExhausted: "백업 코드가 모두 사용되었습니다. 인증 앱을 사용할 수 없는 경우, 더이상 계정에 접근할 수 없게 됩니다. 가능한 한 빨리 인증 앱을 다시 등록해 주세요."
+ setupCompleted: "설정 완료했습니다"
+ step4: "다음 로그인부터는 토큰을 입력해야 합니다."
securityKeyNotSupported: "이 브라우저는 보안 키를 지원하지 않습니다."
registerTOTPBeforeKey: "보안 키 또는 패스키를 등록하려면 인증 앱을 등록하십시오."
securityKeyInfo: "FIDO2를 지원하는 하드웨어 보안 키 혹은 디바이스의 지문인식이나 화면잠금 PIN을 이용해서 로그인하도록 설정할 수 있습니다."
@@ -1696,6 +1846,11 @@ _2fa:
renewTOTPConfirm: "기존에 등록되어 있던 인증 키와 백업 코드는 사용하지 못하게 됩니다."
renewTOTPOk: "재설정"
renewTOTPCancel: "취소"
+ checkBackupCodesBeforeCloseThisWizard: "이 위자드를 닫기 전에 아래 백업 코드를 확인하십시오"
+ backupCodes: "백업 코드"
+ backupCodesDescription: "인증 앱을 사용할 수 없게 된 경우 아래 백업 코드를 사용하여 계정에 액세스 할 수 있습니다.이 코드들은 반드시 안전한 장소에 보관하십시오.각 코드는 한 번만 사용할 수 있습니다."
+ backupCodeUsedWarning: "백업 코드가 사용되었습니다.인증 앱을 사용할 수 없게 된 경우, 조속히 인증 앱을 다시 설정해 주십시오."
+ backupCodesExhaustedWarning: "백업 코드가 모두 사용되었습니다.인증 앱을 사용할 수 없는 경우 더 이상 계정에 액세스하는 것이 불가능합니다.인증 앱을 다시 등록해 주세요."
_permissions:
"read:account": "계정의 정보를 봅니다"
"write:account": "계정의 정보를 변경합니다"
@@ -1729,6 +1884,10 @@ _permissions:
"write:gallery": "갤러리를 추가하거나 삭제합니다"
"read:gallery-likes": "갤러리의 좋아요를 확인합니다"
"write:gallery-likes": "갤러리에 좋아요를 추가하거나 취소합니다"
+ "read:flash": "Play를 봅니다"
+ "write:flash": "Play를 조작합니다"
+ "read:flash-likes": "Play의 좋아요를 봅니다"
+ "write:flash-likes": "Play의 좋아요를 조작합니다"
_auth:
shareAccessTitle: "어플리케이션의 접근 허가"
shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?"
@@ -1742,8 +1901,9 @@ _auth:
_antennaSources:
all: "모든 노트"
homeTimeline: "팔로우중인 유저의 노트"
- users: "지정한 한 명 혹은 여러 명의 유저의 노트"
+ users: "지정한 유저의 노트"
userList: "지정한 리스트에 속한 유저의 노트"
+ userBlacklist: "지정한 유저를 제외한 모든 노트"
_weekday:
sunday: "일요일"
monday: "월요일"
@@ -1843,6 +2003,7 @@ _profile:
metadataContent: "내용"
changeAvatar: "아바타 이미지 변경"
changeBanner: "배너 이미지 변경"
+ verifiedLinkDescription: "내용에 자신의 프로필로 향하는 링크가 포함된 페이지의 URL을 삽입하면 소유자 인증 마크가 표시됩니다."
_exportOrImport:
allNotes: "모든 노트"
favoritedNotes: "즐겨찾기한 노트"
@@ -1852,6 +2013,7 @@ _exportOrImport:
userLists: "리스트"
excludeMutingUsers: "뮤트한 유저 제외하기"
excludeInactiveUsers: "휴면 중인 계정 제외하기"
+ withReplies: "가져오기한 유저에 의한 답글을 타임라인에 포함"
_charts:
federation: "연합"
apRequest: "요청"
@@ -1925,7 +2087,7 @@ _pages:
url: "페이지 URL"
summary: "페이지 요약"
alignCenter: "가운데 정렬"
- hideTitleWhenPinned: "프로필에 고정해놓은 경우 타이틀을 표시하지 않음"
+ hideTitleWhenPinned: "프로필에 고정한 경우 타이틀을 표시하지 않음"
font: "폰트"
fontSerif: "명조체"
fontSansSerif: "고딕체"
@@ -1961,11 +2123,20 @@ _notification:
youReceivedFollowRequest: "새로운 팔로우 요청이 있습니다"
yourFollowRequestAccepted: "팔로우 요청이 수락되었습니다"
pollEnded: "투표 결과가 발표되었습니다"
+ newNote: "새 게시물"
unreadAntennaNote: "안테나 {name}"
emptyPushNotificationMessage: "푸시 알림이 갱신되었습니다"
achievementEarned: "도전 과제를 달성했습니다"
+ testNotification: "알림 테스트"
+ checkNotificationBehavior: "알림 표시를 체크하기"
+ sendTestNotification: "테스트 알림 보내기"
+ notificationWillBeDisplayedLikeThis: "알림이 이렇게 표시됩니다"
+ reactedBySomeUsers: "{n}명이 반응했습니다"
+ renotedBySomeUsers: "{n}명이 리노트했습니다"
+ followedBySomeUsers: "{n}명에게 팔로우됨"
_types:
all: "전부"
+ note: "유저의 새 게시물"
follow: "팔로잉"
mention: "멘션"
reply: "답글"
@@ -1999,6 +2170,8 @@ _deck:
introduction2: "나중에라도 화면 우측의 + 버튼을 눌러 새 칼럼을 추가할 수 있습니다."
widgetsIntroduction: "칼럼 메뉴의 \"위젯 편집\"에서 위젯을 추가해 주세요"
useSimpleUiForNonRootPages: "루트 이외의 페이지로 접속한 경우 UI 간략화하기"
+ usedAsMinWidthWhenFlexible: "'폭 자동 조정'이 활성화된 경우 최소 폭으로 사용됩니다"
+ flexible: "폭 자동 조정"
_columns:
main: "메인"
widgets: "위젯"
@@ -2033,3 +2206,86 @@ _webhookSettings:
renote: "누군가 내 글을 Renote했을 때"
reaction: "누군가 내 노트에 리액션했을 때"
mention: "누군가 나를 멘션했을 때"
+_moderationLogTypes:
+ createRole: "역할 생성"
+ deleteRole: "역할 삭제"
+ updateRole: "역할 수정"
+ assignRole: "역할 할당"
+ unassignRole: "역할 해제"
+ suspend: "정지"
+ unsuspend: "정지 해제"
+ addCustomEmoji: "커스텀 이모지 추가"
+ updateCustomEmoji: "커스텀 이모지 수정"
+ deleteCustomEmoji: "커스텀 이모지 삭제"
+ updateServerSettings: "서버 설정 갱신"
+ updateUserNote: "모더레이션 노트 갱신"
+ deleteDriveFile: "파일 삭제"
+ deleteNote: "노트 삭제"
+ createGlobalAnnouncement: "전역 공지사항 생성"
+ createUserAnnouncement: "유저 공지사항 생성"
+ updateGlobalAnnouncement: "전역 공지사항 수정"
+ updateUserAnnouncement: "유저 공지사항 수정"
+ deleteGlobalAnnouncement: "전역 공지사항 삭제"
+ deleteUserAnnouncement: "유저 공지사항 삭제"
+ resetPassword: "비밀번호 재설정"
+ suspendRemoteInstance: "리모트 서버를 정지"
+ unsuspendRemoteInstance: "리모트 서버의 정지를 해제"
+ markSensitiveDriveFile: "파일에 열람주의를 설정"
+ unmarkSensitiveDriveFile: "파일에 열람주의를 해제"
+ resolveAbuseReport: "신고 해결"
+ createInvitation: "초대 코드 생성"
+ createAd: "광고 생성"
+ deleteAd: "광고 삭제"
+ updateAd: "광고 수정"
+ createAvatarDecoration: "아이콘 장식 추가"
+ updateAvatarDecoration: "아이콘 장식 수정"
+ deleteAvatarDecoration: "아이콘 장식 삭제"
+_fileViewer:
+ title: "파일 상세"
+ type: "파일 유형"
+ size: "파일 크기"
+ url: "URL"
+ uploadedAt: "업로드 날짜"
+ attachedNotes: "첨부된 노트"
+ thisPageCanBeSeenFromTheAuthor: "이 페이지는 파일 소유자만 열람할 수 있습니다"
+_externalResourceInstaller:
+ title: "외부 사이트로부터 설치"
+ checkVendorBeforeInstall: "제공자를 신뢰할 수 있는 경우에만 설치하십시오."
+ _plugin:
+ title: "이 플러그인을 설치하시겠습니까?"
+ metaTitle: "플러그인 정보"
+ _theme:
+ title: "이 테마를 설치하시겠습니까?"
+ metaTitle: "테마 정보"
+ _meta:
+ base: "기본 컬러 스키마"
+ _vendorInfo:
+ title: "제공자 정보"
+ endpoint: "참조한 엔드포인트"
+ hashVerify: "파일 무결성 확인"
+ _errors:
+ _invalidParams:
+ title: "파라미터가 부족합니다"
+ description: "외부 사이트로부터 데이터를 불러오기 위해 필요한 정보가 부족합니다. URL을 다시 한 번 확인하십시오."
+ _resourceTypeNotSupported:
+ title: "해당하는 외부 리소스는 지원되지 않습니다."
+ description: "외부 사이트의 해당 리소스는 지원되지 않습니다. 사이트 관리자에게 문의하십시오."
+ _failedToFetch:
+ title: "데이터를 불러올 수 없습니다"
+ fetchErrorDescription: "외부 사이트와의 통신에 실패하였습니다. 여러 번 시도해도 동일한 오류가 표시되는 경우 사이트 관리자에게 문의하십시오."
+ parseErrorDescription: "외부 사이트에서 불러온 데이터를 읽어들일 수 없습니다. 사이트 관리자에게 문의하십시오."
+ _hashUnmatched:
+ title: "데이터가 올바르지 않습니다."
+ description: "데이터의 무결성 확인에 실패하여, 보안을 위해 설치가 중단되었습니다. 사이트 관리자에게 문의하십시오."
+ _pluginParseFailed:
+ title: "AiScript 오류"
+ description: "데이터를 성공적으로 불러왔으나, AiScript 분석 과정에서 오류가 발생하여 읽어들일 수 없습니다. 플러그인 작성자에게 문의하십시오. 자세한 사항은 브라우저에 내장된 개발자 도구의 Javascript 콘솔에서 확인하실 수 있습니다."
+ _pluginInstallFailed:
+ title: "플러그인 설치에 실패했습니다"
+ description: "플러그인을 설치하는 도중 문제가 발생하였습니다. 다시 한 번 시도하십시오. 자세한 사항은 브라우저에 내장된 개발자 도구의 Javascript 콘솔에서 확인하실 수 있습니다."
+ _themeParseFailed:
+ title: "테마 코드 분석 오류"
+ description: "데이터를 성공적으로 불러왔으나, 테마 코드 분석 과정에서 오류가 발생하여 읽어들일 수 없습니다. 테마 작성자에게 문의하십시오. 자세한 사항은 브라우저에 내장된 개발자 도구의 Javascript 콘솔에서 확인하실 수 있습니다."
+ _themeInstallFailed:
+ title: "테마를 설치하지 못했습니다"
+ description: "테마를 설치하는 도중 문제가 발생하였습니다. 다시 한 번 시도하십시오. 자세한 사항은 브라우저에 내장된 개발자 도구의 Javascript 콘솔에서 확인하실 수 있습니다."
diff --git a/locales/lo-LA.yml b/locales/lo-LA.yml
index 4f73b70550..b22e047cfa 100644
--- a/locales/lo-LA.yml
+++ b/locales/lo-LA.yml
@@ -107,7 +107,11 @@ enterEmoji: "ປ້ອນອີໂມຈິ"
renote: "Renote"
unrenote: "ເລີກ Renote"
renoted: "ເກັບບັນທຶກໄວ້"
+cantRenote: "ໂພສນີ້ບໍ່ສາມາດຖືກບັນທຶກໄວ້ຄືນໃໝ່ໄດ້"
+cantReRenote: "ບໍ່ສາມາດບັນທຶກຄືນໃໝ່ໄດ້"
quote: "ລວມຂໍ້ຄວາມອ້າງອີງ"
+inChannelRenote: "ຊ່ອງພຽງແຕ່ Renote"
+inChannelQuote: "ຊ່ອງເທົ່ານັ້ນ Quote"
pinnedNote: "ບັນທຶກທີ່ປັກໝຸດໄວ້"
pinned: "ປັກໝຸດໄປຫາໂປຣໄຟລ໌"
you: "ເຈົ້າ"
@@ -226,9 +230,12 @@ fromUrl: "ຈາກ URL"
uploadFromUrl: "ອັບໂຫຼດຈາກ URL"
uploadFromUrlDescription: "URL ຂອງໄຟລ໌ທີ່ທ່ານຕ້ອງການອັບໂຫລດ"
uploadFromUrlRequested: "ຮ້ອງຂໍການອັບໂຫລດ"
+explore: "ສຳຫຼວດ"
messageRead: "ອ່ານແລ້ວ"
startMessaging: "ເລີ່ມການສົນທະນາໃໝ່"
nUsersRead: "ອ່ານໂດຍ {n}"
+agree: "ຍອມຮັບ"
+termsOfService: "ເງື່ອນໄຂການບໍລິການ"
start: "ເລີ່ມຕົ້ນນຳໃຊ້ເລີຍ"
home: "ໜ້າຫຼັກ"
activity: "ກິດຈະກຳ"
@@ -266,6 +273,7 @@ inputNewDescription: "ໃສ່ຄຳບັນຍາຍໃໝ່"
inputNewFolderName: "ໃສ່ຊື່ໂຟນເດີໃໝ່"
circularReferenceFolder: "ໂຟນເດີປາຍທາງແມ່ນໂຟນເດີຍ່ອຍຂອງໂຟນເດີທີ່ທ່ານຕ້ອງການຍ້າຍ"
rename: "ປ່ຽນຊື່"
+doNothing: "ບໍ່ສົນໃຈ"
watch: "ເບິ່ງ"
unwatch: "ຢຸດເບິ່ງ"
accept: "ອະນຸຍາດ"
@@ -294,7 +302,14 @@ enableRegistration: "ເປີດໃຊ້ການລົງທະບຽນຜ
invite: "ເຊີນ"
driveCapacityPerLocalAccount: "ຄວາມອາດສາມາດຂັບຕໍ່ຜູ້ໃຊ້ທ້ອງຖິ່ນ"
driveCapacityPerRemoteAccount: "ໄດຣຟ໌ຄວາມອາດສາມາດຕໍ່ຜູ້ໃຊ້ທາງໄກ"
+basicInfo: "ຂໍ້ມຸນເບື້ອງຕົ້ນ"
pinnedNotes: "ບັນທຶກທີ່ປັກໝຸດໄວ້"
+hcaptchaSiteKey: "ກະແຈໄຊທ໌"
+hcaptchaSecretKey: "ກະແຈລັບ"
+recaptcha: "reCAPTCHA"
+enableRecaptcha: "ເປີດໃຊ້ງານລີແຄ໋ບຈາ"
+recaptchaSiteKey: "ກະແຈໄຊທ໌"
+recaptchaSecretKey: "ກະແຈລັບ"
turnstileSiteKey: "ກະແຈໄຊທ໌"
turnstileSecretKey: "ກະແຈລັບ"
name: "ຊື່"
@@ -302,24 +317,41 @@ userList: "ລາຍການ"
about: "ກ່ຽວກັບ"
aboutMisskey: "ກ່ຽວກັບ Misskey"
administrator: "ຜູ້ບໍລິຫານ"
+token: "ໂທເຄັນ"
share: "ແບ່ງປັນ"
notFound: "ບໍ່ພົບ"
cacheClear: "ລຶບລ້າງແຄສ"
+help: "ຊ່ວຍເຫຼືອ"
+close: "ປິດ"
invites: "ເຊີນ"
+members: "ສະມາຊິກ"
+transfer: "ໂອນຍ້າຍ"
title: "ຫົວຂໍ້"
text: "ຂໍ້ຄວາມ"
enable: "ເປີດໃຊ້"
next: "ຕໍ່ໄປ"
+retype: "ເຂົ້າໄປອີກຄັ້ງ"
+quoteAttached: "ວົງຢືມ"
invitations: "ເຊີນ"
+unavailable: "ບໍ່ສາມາດໃຊ້ໄດ້"
language: "ພາສາ"
+aboutX: "ກ່ຽວກັບ {x}"
+emojiStyle: "ຮູບແບບອີໂມຈິ"
native: "ພາສາແມ່"
+noHistory: "ບໍ່ມີລາຍການຢູ່ບ່ອນນີ້"
+doing: "ກຳລັງປະມວນຜົນ..."
category: "ຫມວດຫມູ່"
tags: "ແທ໋ກ"
createAccount: "ສ້າງບັນຊີ"
existingAccount: "ທີ່ມີຢູ່"
dashboard: "ໜ້າປັດ"
local: "ທ້ອງຖິ່ນ"
+numberOfDays: "ຈຳນວນມື້"
+objectStorageBucket: "Bucket"
+objectStoragePrefix: "Prefix"
+objectStorageEndpoint: "Endpoint"
objectStorageRegion: "ພາກພື້ນ"
+deleteAll: "ລຶບທັງໝົດ"
sounds: "ສຽງ"
sound: "ສຽງ"
none: "ບໍ່ມີ"
@@ -333,25 +365,48 @@ ascendingOrder: "ນ້ອຍໄປຫາໃຫຍ່"
descendingOrder: "ໃຫຍ່ຫານ້ອຍ"
output: "ຜົນຜະລິດ"
script: "ບົດຄວາມ"
+menu: "ເມນູ"
+rearrange: "ຈັດລຽງຄືນ"
+poll: "ການພູນ"
+description: "ລາຍລະອຽດ"
+author: "ຜູ້ຂຽນ"
+manage: "ການຈັດການ"
+plugins: "ປລັ໋ກອີນ"
+width: "ກວ້າງ"
+height: "ຄວາມສູງ"
+large: "ໃຫຍ່."
+medium: "ປານກາງ"
+small: "ເລັກ"
+permission: "ການອະນຸຍາດ"
+notificationType: "ປະເພດການແຈ້ງເຕືອນ"
+edit: "ແກ້ໄຂ"
+email: "ອີເມວ"
smtpHost: "ໂຮດສ"
smtpUser: "ຊື່ຜູ້ໃຊ້"
smtpPass: "ລະຫັດຜ່ານ"
clearCache: "ລຶບລ້າງແຄສ"
info: "ກ່ຽວກັບ"
user: "ຜູ້ໃຊ້ຕ່າງໆ"
+administration: "ການຈັດການ"
+middle: "ປານກາງ"
searchByGoogle: "ຄົ້ນຫາ"
file: "ໄຟລ໌"
+replies: "ຕອບໄປທີ"
+renotes: "Renote"
+_role:
+ _priority:
+ middle: "ປານກາງ"
_email:
_follow:
title: "ໄດ້ຕິດຕາມທ່ານ"
_theme:
+ description: "ລາຍລະອຽດ"
keys:
mention: "ໄດ້ກ່າວມາ"
renote: "Renote"
_sfx:
note: "ບັນທຶກ"
notification: "ການແຈ້ງເຕືອນ"
- chat: "ແຊ໋ດ"
_2fa:
renewTOTPCancel: "ບໍ່ແມ່ນຕອນນີ້"
_widgets:
@@ -383,6 +438,7 @@ _timelines:
home: "ໜ້າຫຼັກ"
_play:
script: "ບົດຄວາມ"
+ summary: "ລາຍລະອຽດ"
_pages:
blocks:
image: "ຮູບພາບ"
@@ -406,3 +462,5 @@ _deck:
mentions: "ກ່າວເຖິງ"
_webhookSettings:
name: "ຊື່"
+_moderationLogTypes:
+ suspend: "ລະງັບ"
diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml
index ae2881a7d4..1c207d89c8 100644
--- a/locales/nl-NL.yml
+++ b/locales/nl-NL.yml
@@ -45,6 +45,7 @@ pin: "Vastmaken aan profielpagina"
unpin: "Losmaken van profielpagina"
copyContent: "Kopiëren inhoud"
copyLink: "Kopiëren link"
+copyLinkRenote: ""
delete: "Verwijderen"
deleteAndEdit: "Verwijderen en bewerken"
deleteAndEditConfirm: "Weet je zeker dat je deze notitie wilt verwijderen en dan bewerken? Je verliest alle reacties, herdelingen en antwoorden erop."
@@ -338,7 +339,6 @@ invite: "Uitnodigen"
driveCapacityPerLocalAccount: "Opslagruimte per lokale gebruiker"
driveCapacityPerRemoteAccount: "Opslagruimte per externe gebruiker"
inMb: "in megabytes"
-iconUrl: "Pictogram URL"
bannerUrl: "Banner URL"
backgroundImageUrl: "URL afbeelding"
basicInfo: "Basisinformatie"
@@ -426,6 +426,9 @@ pushNotificationAlreadySubscribed: "Pushberichtrn al ingeschakeld"
windowMaximize: "Maximaliseren"
windowRestore: "Herstellen"
loggedInAsBot: "Momenteel als bot ingelogd"
+icon: "Avatar"
+replies: "Antwoord"
+renotes: "Herdelen"
_email:
_follow:
title: "volgde jou"
@@ -436,7 +439,6 @@ _theme:
_sfx:
note: "Notities"
notification: "Meldingen"
- chat: "Chat"
_2fa:
renewTOTPCancel: "Nee, bedankt"
_widgets:
@@ -492,3 +494,6 @@ _deck:
mentions: "Vermeldingen"
_webhookSettings:
name: "Naam"
+_moderationLogTypes:
+ suspend: "Opschorten"
+ resetPassword: "Wachtwoord terugzetten"
diff --git a/locales/no-NO.yml b/locales/no-NO.yml
index ec2900527b..44944f8465 100644
--- a/locales/no-NO.yml
+++ b/locales/no-NO.yml
@@ -461,6 +461,9 @@ videos: "Videoer"
continue: "Fortsett"
youFollowing: "Følger"
options: "Alternativ"
+icon: "Avatar"
+replies: "Svar"
+renotes: "Renote"
_initialAccountSetting:
theseSettingsCanEditLater: "Du kan endre disse innstillingene senere."
_achievements:
@@ -572,9 +575,6 @@ _channel:
nameAndDescription: "Navn og beskrivelse"
_menuDisplay:
hide: "Skjul"
-_wordMute:
- soft: "Myk"
- hard: "Hard"
_theme:
description: "Beskrivelse"
color: "Farge"
@@ -601,9 +601,6 @@ _time:
minute: "Minutter"
hour: "Timer"
day: "Dager"
-_timelineTutorial:
- title: "Hvordan bruke Misskey"
- step2_2: "Hva med å skrive en selvpresentasjon, eller bare \"Hei {name}!\" hvis du ikke har lyst?"
_2fa:
renewTOTPCancel: "Avbryt"
_weekday:
@@ -722,3 +719,5 @@ _deck:
direct: "Direkte"
_webhookSettings:
name: "Navn"
+_moderationLogTypes:
+ suspend: "Suspender"
diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml
index d902d609be..1a15532c08 100644
--- a/locales/pl-PL.yml
+++ b/locales/pl-PL.yml
@@ -333,7 +333,6 @@ invite: "Zaproś"
driveCapacityPerLocalAccount: "Powierzchnia dyskowa na lokalnego użytkownika"
driveCapacityPerRemoteAccount: "Powierzchnia dyskowa na zdalnego użytkownika"
inMb: "W megabajtach"
-iconUrl: "Adres URL ikony"
bannerUrl: "Adres URL banera"
backgroundImageUrl: "Adres URL tła"
basicInfo: "Podstawowe informacje"
@@ -644,6 +643,7 @@ createNewClip: "Utwórz nowy klip"
unclip: "Odczep"
confirmToUnclipAlreadyClippedNote: "Ten wpis jest już częścią klipu \"{name}\". Czy chcesz ją usunąć z tego klipu?"
public: "Publiczny"
+private: "Prywatne"
i18nInfo: "Misskey jest tłumaczone na wiele języków przez wolontariuszy. Możesz pomóc na {link}."
manageAccessTokens: "Zarządzaj tokenami dostępu"
accountInfo: "Informacje o koncie"
@@ -870,6 +870,10 @@ like: "Polub"
show: "Wyświetlanie"
color: "Kolor"
youFollowing: "Śledzeni"
+icon: "Awatar"
+replies: "Odpowiedz"
+renotes: "Udostępnij"
+flip: "Odwróć"
_role:
priority: "Priorytet"
_priority:
@@ -922,6 +926,7 @@ _plugin:
install: "Zainstaluj wtyczki"
installWarn: "Nie instaluj niezaufanych wtyczek."
manage: "Zarządzanie wtyczkami"
+ viewSource: "Zobacz źródło"
_preferencesBackups:
list: "Utworzone kopie zapasowe"
saveNew: "Zapisz nową kopię zapasową"
@@ -978,9 +983,6 @@ _menuDisplay:
_wordMute:
muteWords: "Słowo do wyciszenia"
muteWordsDescription2: "Otocz słowa kluczowe ukośnikami, aby używać wyrażeń regularnych."
- soft: "Łagodny"
- hard: "Twardy"
- mutedNotes: "Wyciszone wpisy"
_instanceMute:
title: "Ukrywa wpisy z wymienionych instancji."
heading: "Lista instancji do wyciszenia"
@@ -1042,9 +1044,6 @@ _theme:
infoFg: "Tekst informacji"
infoWarnBg: "Tło ostrzeżenia"
infoWarnFg: "Tekst ostrzeżenia"
- cwBg: "Tło CW"
- cwFg: "Tekst CW"
- cwHoverBg: "Tło CW (po najechaniu)"
toastBg: "Tło powiadomień"
toastFg: "Tekst powiadomień"
buttonBg: "Tło przycisku"
@@ -1062,8 +1061,6 @@ _sfx:
note: "Wpisy"
noteMy: "Mój wpis"
notification: "Powiadomienia"
- chat: "Wiadomości"
- chatBg: "Rozmowy (tło)"
antenna: "Anteny"
channel: "Powiadomienia kanału"
_ago:
@@ -1087,8 +1084,10 @@ _2fa:
step1: "Najpierw, zainstaluj aplikację uwierzytelniającą (taką jak {a} lub {b}) na swoim urządzeniu."
step2: "Następnie, zeskanuje kod QR z ekranu."
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.\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}"
+ step4: "Od teraz, przy każdej próbie logowania otrzymasz prośbę o token logowania."
removeKeyConfirm: "Usunąć kopię zapasową {name}?"
+ renewTOTPConfirm: "Spowoduje to, że kody weryfikacyjne z poprzedniej aplikacji przestaną działać"
+ renewTOTPOk: "Rekonfiguruj"
renewTOTPCancel: "Nie teraz"
_permissions:
"read:account": "Wyświetl informacje o swoim koncie"
@@ -1102,8 +1101,10 @@ _permissions:
"read:following": "Wyświetlanie informacji o obserwowanych"
"write:following": "Obserwowanie lub cofanie obserwacji innych kont"
"read:messaging": "Zobacz swoje czaty"
+ "write:messaging": "Tworzenie lub usuwanie wiadomości czatu"
"read:mutes": "Wyświetlanie listy osób, które wyciszyłeś(-aś)"
"write:mutes": "Edycja listy osób, które wyciszyłeś(-aś)"
+ "write:notes": "Tworzenie lub usuwanie wpisów"
"read:notifications": "Wyświetlanie powiadomień"
"write:notifications": "Działanie na powiadomieniach"
"read:reactions": "Wyświetlanie reakcji"
@@ -1119,9 +1120,23 @@ _permissions:
"write:channels": "Edytuj swoje kanały"
"read:gallery": "Zobacz swoją galerię"
"write:gallery": "Edytuj swoją galerię"
+ "read:gallery-likes": "Wyświetlanie listy polubionych postów w galerii"
+ "write:gallery-likes": "Edytowanie listy polubionych postów w galerii"
_auth:
+ shareAccessTitle: "Przyznawanie uprawnień aplikacji"
shareAccess: "Czy chcesz autoryzować „{name}” do dostępu do tego konta?"
+ shareAccessAsk: "Czy na pewno chcesz zezwolić tej aplikacji na dostęp do Twojego konta?"
+ permission: "{name} żąda następujących uprawnień"
permissionAsk: "Ta aplikacja wymaga następujących uprawnień:"
+ pleaseGoBack: "Proszę, wróć do aplikacji"
+ callback: "Powracanie do aplikacji"
+ denied: "Odmowa dostępu"
+ pleaseLogin: "Zaloguj się, aby autoryzować aplikacje."
+_antennaSources:
+ all: "Wszystkie wpisy"
+ homeTimeline: "Wpisy obserwowanych użytkowników"
+ users: "Wpisy określonych użytkowników"
+ userList: "Wpisy z określonej listy użytkowników"
_weekday:
sunday: "Niedziela"
monday: "Poniedziałek"
@@ -1154,8 +1169,10 @@ _widgets:
serverMetric: "Metryka serwera"
aiscript: "Konsola AiScript"
aichan: "Ai"
+ userList: "Lista użytkowników"
_userList:
chooseList: "Wybierz listę"
+ clicker: "Clicker"
_cw:
hide: "Ukryj"
show: "Załaduj więcej"
@@ -1187,10 +1204,16 @@ _visibility:
public: "Publiczny"
publicDescription: "Twój wpis pojawi się w publicznych osiach czasu"
home: "Strona główna"
+ homeDescription: "Publikuj tylko na głównej osi czasu"
followers: "Obserwujący"
+ followersDescription: "Widoczne tylko dla obserwujących"
specified: "Bezpośredni"
specifiedDescription: "Napisz tylko określonym użytkownikom"
+ disableFederationDescription: "Nie przesyłaj do innych instancji"
_postForm:
+ replyPlaceholder: "Odpowiedz na ten wpis..."
+ quotePlaceholder: "Zacytuj ten wpis…"
+ channelPlaceholder: "Publikuj na kanale..."
_placeholders:
a: "Co się dzieje?"
b: "Co się wydarzyło?"
@@ -1212,17 +1235,29 @@ _profile:
changeBanner: "Zmień baner"
_exportOrImport:
allNotes: "Wszystkie wpisy"
+ favoritedNotes: "Ulubione wpisy"
followingList: "Obserwowani"
muteList: "Wycisz"
blockingList: "Zablokuj"
userLists: "Listy"
+ excludeMutingUsers: "Wyklucz wyciszonych użytkowników"
+ excludeInactiveUsers: "Wyklucz nieaktywnych użytkowników"
_charts:
federation: "Federacja"
apRequest: "Żądania"
+ usersIncDec: "Różnica w liczbie użytkowników"
usersTotal: "Łącznie # użytkowników"
activeUsers: "Aktywni użytkownicy"
+ notesIncDec: "Różnica w liczbie wpisów"
+ notesTotal: "Całkowita liczba wpisów"
+ filesIncDec: "Różnica w liczbie plików"
+ filesTotal: "Całkowita liczba plików"
+ storageUsageIncDec: "Różnica w wykorzystaniu pamięci"
+ storageUsageTotal: "Całkowite wykorzystanie pamięci"
_instanceCharts:
requests: "Żądania"
+ users: "Różnica w liczbie użytkowników"
+ notes: "Różnica w liczbie wpisów"
notesTotal: "Łącznie # wpisów"
ff: "Różnica w # obserwujących"
ffTotal: "Łączna liczba # obserwujących"
@@ -1347,5 +1382,19 @@ _deck:
mentions: "Wspomnienia"
direct: "Bezpośredni"
_webhookSettings:
+ createWebhook: "Stwórz Webhook"
name: "Nazwa"
+ secret: "Sekret"
+ events: "Uruchomienie Webhooka"
active: "Właczono"
+ _events:
+ follow: "Po zaobserwowaniu użytkownika"
+ followed: "Po zostaniu zaobserwowanym"
+ note: "Po opublikowaniu wpisu"
+ reply: "Po otrzymaniu odpowiedzi"
+ renote: "Po udostępnieniu wpisu"
+ reaction: "Po otrzymaniu reakcji"
+ mention: "Po zostaniu wspomnianym"
+_moderationLogTypes:
+ suspend: "Zawieś"
+ resetPassword: "Zresetuj hasło"
diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml
index 18fe955eae..32740175e3 100644
--- a/locales/pt-PT.yml
+++ b/locales/pt-PT.yml
@@ -4,7 +4,7 @@ headlineMisskey: "Uma rede ligada por notas"
introMisskey: "Bem-vindo! O Misskey é um serviço de microblog descentralizado de código aberto.\nCrie \"notas\" para compartilhar o que está acontecendo agora ou para se expressar com todos à sua volta 📡\nVocê também pode adicionar rapidamente reações às notas de outras pessoas usando a função \"Reações\" 👍\nVamos explorar um novo mundo 🚀"
poweredByMisskeyDescription: "{name} é um dos servidores da plataforma de código aberto Misskey."
monthAndDay: "{day}/{month}"
-search: "Buscar"
+search: "Pesquisar"
notifications: "Notificações"
username: "Nome de usuário"
password: "Senha"
@@ -25,36 +25,37 @@ basicSettings: "Configurações básicas"
otherSettings: "Outras configurações"
openInWindow: "Abrir em um janela"
profile: "Perfil"
-timeline: "Timeline"
+timeline: "Linha do tempo"
noAccountDescription: "Este usuário não tem uma descrição."
login: "Iniciar sessão"
loggingIn: "Iniciando sessão…"
logout: "Sair"
signup: "Registrar-se"
uploading: "Enviando…"
-save: "Guardar"
+save: "Salvar"
users: "Usuários"
addUser: "Adicionar usuário"
favorite: "Adicionar aos favoritos"
-favorites: "Adicionar aos favoritos"
+favorites: "Favoritos"
unfavorite: "Remover dos favoritos"
favorited: "Adicionado aos favoritos."
alreadyFavorited: "Já adicionado aos favoritos."
cantFavorite: "Não foi possível adicionar aos favoritos."
-pin: "Afixar no perfil"
+pin: "Fixar no perfil"
unpin: "Desafixar do perfil"
copyContent: "Copiar conteúdos"
copyLink: "Copiar link"
+copyLinkRenote: "Copiar o link da repostagem"
delete: "Excluir"
deleteAndEdit: "Excluir e editar"
deleteAndEditConfirm: "Deseja excluir esta nota e editá-la novamente? Todas as reações, compartilhamentos e respostas a esta nota também serão excluídas."
addToList: "Adicionar a lista"
addToAntenna: "Adicionar à antena"
-sendMessage: "Enviar uma mensagem"
+sendMessage: "Enviar mensagem"
copyRSS: "Copiar RSS"
copyUsername: "Copiar nome de utilizador"
-copyUserId: "Copiar o ID do utilizador"
-copyNoteId: "Copiar o ID da publicação"
+copyUserId: "Copiar ID do utilizador"
+copyNoteId: "Copiar ID da publicação"
copyFileId: "Copiar o ID do arquivo"
copyFolderId: "Copiar o ID da pasta"
copyProfileUrl: "Copiar a URL do perfil"
@@ -89,7 +90,7 @@ createList: "Criar lista"
manageLists: "Gerenciar listas"
error: "Erro"
somethingHappened: "Ocorreu um erro"
-retry: "Tentar novamente"
+retry: "Tente novamente"
pageLoadError: "Ocorreu um erro ao carregar a página."
pageLoadErrorDescription: "Isso geralmente acontece devido ao cache do navegador ou da rede. Tente limpar o cache ou aguarde um pouco antes de tentar novamente."
serverIsDead: "Não há resposta do servidor. Aguarde um momento e tente novamente."
@@ -156,6 +157,7 @@ addEmoji: "Adicionar um Emoji"
settingGuide: "Guia de configuração"
cacheRemoteFiles: "Cache de arquivos remotos"
cacheRemoteFilesDescription: "Ao desativar esta configuração, os arquivos remotos não serão mais armazenados em cache e serão vinculados diretamente. Isso economizará espaço de armazenamento no servidor, mas os thumbnails não serão gerados, o que pode aumentar o tráfego de dados."
+youCanCleanRemoteFilesCache: "Pode excluir todos os caches com o botão 🗑️ de gestão de arquivos."
cacheRemoteSensitiveFiles: "Fazer cache de arquivos remotos sensíveis"
cacheRemoteSensitiveFilesDescription: "Desativar essa configuração faz com que arquivos remotos sensíveis sejam vinculados diretamente em vez de armazenados em cache."
flagAsBot: "Marcar conta como robô"
@@ -231,7 +233,7 @@ federating: "Federando"
blocked: "Bloqueado"
suspended: "Suspenso"
all: "Todos"
-subscribing: "Subscrito"
+subscribing: "Inscrito"
publishing: "Publicando"
notResponding: "Sem resposta"
instanceFollowing: "Seguir a instância"
@@ -278,15 +280,15 @@ agreeBelow: "Eu concordo com o seguinte"
basicNotesBeforeCreateAccount: "Observações importantes"
termsOfService: "Termos de Uso"
start: "começar"
-home: "casa"
+home: "Início"
remoteUserCaution: "As informações estão incompletas porque é um utilizador remoto."
activity: "atividade"
images: "imagem"
image: "imagem"
-birthday: "aniversário"
+birthday: "Aniversário"
yearsOld: "{age} anos"
registeredDate: "Data de registro"
-location: "Lugar, colocar"
+location: "Localização"
theme: "tema"
themeForLightMode: "Temas usados no modo de luz"
themeForDarkMode: "Temas usados no modo escuro"
@@ -295,7 +297,7 @@ dark: "Escuro"
lightThemes: "Tema claro"
darkThemes: "Tema escuro"
syncDeviceDarkMode: "Sincronize com o modo escuro do dispositivo"
-drive: "Unidades"
+drive: "Drive"
fileName: "Nome do Ficheiro"
selectFile: "Selecione os arquivos"
selectFiles: "Selecione os arquivos"
@@ -354,7 +356,6 @@ invite: "Convidar"
driveCapacityPerLocalAccount: "Capacidade do drive por usuário local"
driveCapacityPerRemoteAccount: "Capacidade do drive por usuário remoto"
inMb: "Em ‘megabytes’"
-iconUrl: "URL da imagem do ícone (favicon, etc.)"
bannerUrl: "URL da imagem do ‘banner’"
backgroundImageUrl: "URL da imagem de fundo"
basicInfo: "Informações básicas"
@@ -410,6 +411,7 @@ aboutMisskey: "Sobre Misskey"
administrator: "Administrador"
token: "Símbolo"
2fa: "Autenticação de dois fatores"
+setupOf2fa: "Configuração de autenticação de dois fatores"
totp: "Aplicativo Autenticador"
totpDescription: "Digite a senha de uso único informado pelo aplicativo autenticador"
moderator: "Moderador"
@@ -428,7 +430,7 @@ reduceUiAnimation: "Reduzir a animação da ‘interface’ do utilizador"
share: "Compartilhar"
notFound: "Não encontrado"
notFoundDescription: "Não havia página correspondente ao URL especificado."
-uploadFolder: "Destino de ‘upload’ padrão"
+uploadFolder: "Destino de upload padrão"
cacheClear: "Excluir memória transitória"
markAsReadAllNotifications: "Marcar todas as notificações como lidas"
markAsReadAllUnreadNotes: "Marcar todas as postagens como lidas"
@@ -489,7 +491,7 @@ fontSize: "Tamanho do texto"
mediaListWithOneImageAppearance: "Altura da lista de mídias com apenas uma imagem"
limitTo: "Até {x}"
noFollowRequests: "Não há pedidos de seguidor pendentes"
-openImageInNewTab: "Abrir a imagem numa nova aba"
+openImageInNewTab: "Abrir a imagem em uma nova aba"
dashboard: "Painel de controle"
local: "Local"
remote: "Remoto"
@@ -601,7 +603,7 @@ useFullReactionPicker: "Usar o seletor de reações completo"
width: "Largura"
height: "Altura"
large: "Grande"
-medium: "Média"
+medium: "Médio"
small: "Pequeno"
generateAccessToken: "Gerar token de acesso"
permission: "Permissões"
@@ -653,6 +655,7 @@ behavior: "Comportamento"
sample: "Exemplo"
abuseReports: "Denúncias"
reportAbuse: "Denúncias"
+reportAbuseRenote: "Reportar repostagem"
reportAbuseOf: "Denunciar {name}"
fillAbuseReportDescription: "Por favor, forneça detalhes sobre o motivo da denúncia. Se houver uma nota específica envolvida, inclua também a URL dela."
abuseReported: "Denúncia enviada. Obrigado por sua ajuda."
@@ -680,6 +683,7 @@ createNewClip: "Criar novo clipe"
unclip: "Remover do clipe"
confirmToUnclipAlreadyClippedNote: "Esta nota já está incluída no clipe \"{name}\". Você deseja remover a nota deste clipe?"
public: "Público"
+private: "Privado"
i18nInfo: "Misskey é traduzido para várias línguas por voluntários. Você pode ajudar com as traduções em {link}."
manageAccessTokens: "Gerenciar tokens de acesso"
accountInfo: "Informações da conta"
@@ -715,63 +719,264 @@ useSystemFont: "Utilizar a fonte padrão do sistema"
clips: "Clipe"
experimentalFeatures: "Funcionalidades Experimentais"
experimental: "Experimental"
+thisIsExperimentalFeature: "Este é um recurso experimental. As funções podem mudar ou pode não funcionar corretamente."
+developer: "Programador"
+makeExplorable: "Deixe a sua conta mais fácil de encontrar."
+makeExplorableDescription: "Se você desativá-lo, outros usuários não poderão encontrar a sua conta na aba Descoberta."
+showGapBetweenNotesInTimeline: "Mostrar um espaço entre as notas na linha de tempo"
duplicate: "Duplicar"
left: "Esquerda"
+center: "Centralizar"
wide: "Largo"
narrow: "Estreito"
+reloadToApplySetting: "As configurações serão refletidas após recarregar a página. Deseja recarregar agora?"
+needReloadToApply: "É necessário recarregar a página para refletir as alterações."
showTitlebar: "Exibir barra de título"
clearCache: "Limpar o cache"
+onlineUsersCount: "Pessoas Online"
+nUsers: "Usuários"
+nNotes: "Notas"
+sendErrorReports: "Enviar relatórios de erro"
sendErrorReportsDescription: "Ao ativar essa opção, informações detalhadas de erro serão compartilhadas com o Misskey em caso de problemas, o que pode ajudar a melhorar a qualidade do software. As informações de erro podem incluir a versão do sistema operacional, o tipo de navegador e o sua atividade no Misskey."
+myTheme: "Meu tema"
backgroundColor: "Cor de fundo"
+accentColor: "Cor de destaque"
+textColor: "Cor do texto"
+saveAs: "Salvar como"
advanced: "Avançado"
+advancedSettings: "Configurações avançadas"
value: "Valor"
+createdAt: "Data de criação"
+updatedAt: "Última atualização"
+saveConfirm: "Deseja salvá-lo?"
deleteConfirm: "Confirma a exclusão?"
+invalidValue: "Valor inválido"
+registry: "Registo"
+closeAccount: "Encerrar conta"
+currentVersion: "Versão Atual"
+latestVersion: "Última versão"
+youAreRunningUpToDateClient: "Você está usando a última versão do cliente"
+newVersionOfClientAvailable: "Nova versão do cliente disponível"
+usageAmount: "Quantidade utilizada"
capacity: "Capacidade"
+inUse: "Em uso"
+editCode: "Editar código"
apply: "Aplicar"
+receiveAnnouncementFromInstance: "Receba as notificações da instância"
emailNotification: "Notificações por e-mail"
publish: "Publicar"
+inChannelSearch: "Pesquisar no canal"
+useReactionPickerForContextMenu: "Clique com o botão direito do mouse para abrir o seletor de reações."
+typingUsers: "digitando"
+jumpToSpecifiedDate: "Pular para uma data específica"
+showingPastTimeline: "Visualizar linha de tempo anterior"
+clear: "Limpar"
+markAllAsRead: "Marcar todas como lidas"
goBack: "Voltar"
unlikeConfirm: "Deseja realmente deixar de curtir?"
+fullView: "Visão completa"
+quitFullView: "Sair da visualização completa"
+addDescription: "Adicionar descrição"
+userPagePinTip: "Notas podem ser mostradas aqui ao clicar em \"Fixar no Perfil\" no menu de notas individuais."
+notSpecifiedMentionWarning: "Esta nota menciona usuários que não foram incluídos como recipientes."
info: "Informações"
+userInfo: "Informações do Usuário"
unknown: "Desconhecido"
+onlineStatus: "On-line"
+hideOnlineStatus: "Ocultar o status on-line."
+hideOnlineStatusDescription: "Esconder que está Ativo reduzirá a utilidade de certas funções (como, por exemplo, a Pesquisa)."
+online: "Online"
+active: "Ativo"
+offline: "Inativo"
+notRecommended: "Não recomendado"
+botProtection: "Proteção contra Bot"
+instanceBlocking: "Instâncias bloqueadas"
+selectAccount: "Selecionar conta"
+switchAccount: "Trocar conta"
enabled: "Ativado"
disabled: "Desativado"
+quickAction: "Ações rápidas"
user: "Usuários"
administration: "Administrar"
+accounts: "Contas"
+switch: "Trocar"
+noMaintainerInformationWarning: "A informação de administrador não foi configurada."
+noBotProtectionWarning: "A proteção contra bots não foi configurada."
+configure: "Configurar"
+postToGallery: "Criar publicação em galeria"
+postToHashtag: "Publicar nesta Hashtag"
+gallery: "Galeria"
+recentPosts: "Notas recentes"
+popularPosts: "Notas populares"
+shareWithNote: "Compartilhar em Notas"
ads: "Anúncios"
+expiration: "Data limite"
+startingperiod: "Data de início"
+memo: "Nota"
+priority: "Prioridade"
+high: "Alto"
middle: "Meio"
+low: "Baixo"
emailNotConfiguredWarning: "Endereço de e-mail não configurado. "
+ratio: "Ratio"
+previewNoteText: "Visualizar Nota"
+customCss: "CSS Personalizado"
+customCssWarn: "Esta configuração só deve ser usada se souber o que está fazendo. Valores impróprios podem causar erros no funcionamento do cliente."
+global: "Global"
+squareAvatars: "Exibir ícones quadrados"
sent: "Enviar"
+received: "Recebido"
+searchResult: "Pesquisar"
+hashtags: "Hashtags"
+troubleshooting: "Resolução de problemas"
+useBlurEffect: "Usar efeito de desfoque na UI"
+learnMore: "Saiba mais"
+misskeyUpdated: "Misskey foi atualizado!"
+whatIsNew: "Ver atualizações"
translate: "Traduzir"
+translatedFrom: "Traduzido de"
+accountDeletionInProgress: "Encerramento de conta em andamento"
usernameInfo: "O nome para identificar exclusivamente a sua conta no servidor. Pode conter letras (az, AZ), números (0~9) e sublinhados (_). O nome de usuário não pode ser alterado posteriormente."
+aiChanMode: "Modo AI-chan"
+devMode: "Modo de Desenvolvedor"
+keepCw: "Manter aviso de conteúdo"
+pubSub: "Publicar/Inscrever no perfil"
+lastCommunication: "Ultima atualização"
+resolved: "Resolvido"
+unresolved: "Não resolvido"
breakFollow: "Remover seguidor"
breakFollowConfirm: "Deseja realmente deixar de seguir?"
+itsOn: "Ativado"
+itsOff: "Desativado"
on: "Ligado"
off: "Desligado"
emailRequiredForSignup: "Tornar o endereço de e-mail obrigatório durante o cadastro"
unread: "Não lido"
+filter: "Filtrar"
+controlPanel: "Painel de controle"
+manageAccounts: "Gerenciar contas"
+makeReactionsPublic: "Deixar o histórico de reações em Público"
+makeReactionsPublicDescription: "Isto vai deixar o histórico de todas as suas reações visíveis para qualquer um ver."
+classic: "Clássico"
+muteThread: "Silenciar esta conversa"
+unmuteThread: "Desativar silêncio desta conversa"
+ffVisibility: "Visibilidade de Seguidos/Seguidores"
+ffVisibilityDescription: "Permite configurar quem pode ver quem lhe segue e quem você está seguindo."
+continueThread: "Ver mais desta conversa"
deleteAccountConfirm: "Deseja realmente excluir a conta?"
+incorrectPassword: "Senha inválida."
+voteConfirm: "Deseja confirmar o seu voto em \"{choice}\"?"
hide: "Ocultar"
useDrawerReactionPickerForMobile: "Mostrar em formato de gaveta"
+welcomeBackWithName: "Bem-vindo de volta, {name}"
clickToFinishEmailVerification: "Clique em [{ok}] para completar a validação do endereço de e-mail."
+overridedDeviceKind: "Sobrepor dispositivo"
+smartphone: "Celular"
+tablet: "Tablet"
auto: "Automático"
-searchByGoogle: "Buscar"
+themeColor: "Cor do tema"
+size: "Tamanho"
+numberOfColumn: "Número da coluna"
+searchByGoogle: "Pesquisar"
+instanceDefaultLightTheme: "Tema diurno padrão para toda a instância"
+instanceDefaultDarkTheme: "Tema noturno para toda a instância"
+instanceDefaultThemeDescription: "Insira o código do tema em formato de objeto."
+mutePeriod: "Duração de silenciamento"
+period: "Data limite"
+indefinitely: "Indefinitivamente"
+tenMinutes: "10 minutos"
+oneHour: "1 hora"
+oneDay: "1 dia"
+oneWeek: "1 semana"
+oneMonth: "1 mês"
+reflectMayTakeTime: "As mudanças podem demorar a aparecer."
+failedToFetchAccountInformation: "Não foi possível obter informações da conta"
+rateLimitExceeded: "Taxa limite excedido"
+cropImage: "Recortar imagem"
+cropImageAsk: "Deseja recortar esta imagem?"
+cropYes: "Recortar"
+cropNo: "Manter deste jeito"
file: "Ficheiros"
+recentNHours: "Últimas {n} horas"
+recentNDays: "Últimos {n} dias"
noEmailServerWarning: "Servidor de e-mail não configurado."
+thereIsUnresolvedAbuseReportWarning: "Existem denúncias não resolvidas."
+recommended: "Recomendado"
+check: "Verificar"
driveCapOverrideLabel: "Altere a capacidade do drive para este usuário"
driveCapOverrideCaption: "Altere a capacidade para o valor padrão informando o valor 0 ou inferior."
+requireAdminForView: "Para visualizar, é necessário acessar com uma conta de administrador."
+isSystemAccount: "É uma conta criada e gerenciada automaticamente pelo sistema."
+typeToConfirm: "Para realizar essa operação, digite {x}."
deleteAccount: "Excluir conta"
+document: "Documentação"
+numberOfPageCache: "Número de cache de página"
+numberOfPageCacheDescription: "Aumentar isso melhora a conveniência, mas também resulta em maior carga e uso de memória."
+logoutConfirm: "Gostaria de encerrar a sessão?"
+lastActiveDate: "Última data de uso"
+statusbar: "Barra de status"
+pleaseSelect: "Por favor, selecione."
+reverse: "Inversão"
+colored: "Colorido"
+refreshInterval: "Intervalo de atualização"
+label: "Etiqueta"
+type: "Tipo"
+speed: "Velocidade"
+slow: "Lento"
+fast: "Rápido"
+sensitiveMediaDetection: "Detecção de conteúdo sensível"
+localOnly: "Apenas local"
+remoteOnly: "Apenas remoto"
+cannotUploadBecauseExceedsFileSizeLimit: "Não é possível realizar o upload deste arquivo porque ele excede o tamanho máximo permitido."
+beta: "Beta"
+enableAutoSensitive: "Marcar automaticamente como conteúdo sensível"
enableAutoSensitiveDescription: "Quando disponível, a marcação de mídia sensível será automaticamente atribuído ao conteúdo de mídia usando aprendizado de máquina. Mesmo que você desative essa função, em alguns servidores, isso pode ser configurado automaticamente."
activeEmailValidationDescription: "A validação do endereço de e-mail do usuário será realizada de forma mais rigorosa, considerando se é um endereço descartável ou se é possível realizar comunicação efetiva. Se desativado, apenas a validade do formato do endereço será verificada como uma sequência de caracteres."
+shuffle: "Aleatório"
+account: "Contas"
+move: "Mover"
+pushNotification: "Notificações Push"
+subscribePushNotification: "Ativar notificações push"
+unsubscribePushNotification: "Desativar notificações push"
+windowMinimize: "Minimizar"
+windowRestore: "Restaurar"
+caption: "legenda"
+tools: "Ferramentas"
like: "Curtir"
unlike: "Remover curtida"
numberOfLikes: "Número de curtidas"
show: "Visualizar"
+neverShow: "Não exibir novamente"
+remindMeLater: "Lembrar mais tarde"
didYouLikeMisskey: "Você gostou do Misskey?"
pleaseDonate: "O Misskey é um software gratuito utilizado por {host}. Para que possamos continuar o desenvolvimento, pedimos que considerem fazer doações. A sua contribuição é muito importante!"
roles: "Cargos"
role: "Cargo"
noRole: "Nenhum cargo"
+normalUser: "Usuários padrão"
+undefined: "Indefinido"
+assign: "Atribuir"
+unassign: "Remover"
+color: "Cor"
+manageCustomEmojis: "Gerenciar Emojis customizados"
+youCannotCreateAnymore: "Você atingiu o limite de criação."
+cannotPerformTemporary: "Ação temporariamente indisponível"
+cannotPerformTemporaryDescription: "Esta ação não pôde ser concluída devido ao excesso de pedidos em sucessão. Tente novamente em alguns momentos."
+invalidParamError: "Parâmetros inválidos"
+invalidParamErrorDescription: "Parâmetros requisitados inválidos. Isto normalmente acontece devido a um erro, mas também pode ocorrer devido à entrada de valores além do limite ou algo semelhante."
+permissionDeniedError: "Operação recusada"
+permissionDeniedErrorDescription: "Esta conta não tem permissão para executar esta ação."
+preset: "Predefinições"
+selectFromPresets: "Escolher de predefinições"
+achievements: "Conquistas"
+gotInvalidResponseError: "Resposta do servidor inválida"
+gotInvalidResponseErrorDescription: "Servidor fora do ar ou em manutenção. Favor tentar mais tarde."
+thisPostMayBeAnnoying: "Esta nota pode incomodar outras pessoas."
+thisPostMayBeAnnoyingHome: "Postar na linha do tempo inicial"
+thisPostMayBeAnnoyingCancel: "Cancelar"
+thisPostMayBeAnnoyingIgnore: "Postar mesmo assim"
+collapseRenotes: "Ocultar repostagens já visualizadas"
+internalServerError: "Erro interno de servidor"
emailNotSupported: "O envio de e-mails não é suportado nesta instância"
likeOnly: "Apenas curtidas"
likeOnlyForRemote: "Tudo (somente curtidas remotas)"
@@ -780,7 +985,20 @@ rolesAssignedToMe: "Cargos atribuídos a mim"
unfavoriteConfirm: "Deseja realmente remover dos favoritos?"
drivecleaner: "Limpeza do drive"
retryAllQueuesConfirmTitle: "Gostaria de tentar novamente agora?"
+reactionsDisplaySize: "Tamanho de exibição das reações"
+reactionsList: "Reações"
+renotesList: "Repostagens"
+leftTop: "Superior esquerdo"
+rightTop: "Superior direito"
+leftBottom: "Inferior esquerdo"
+rightBottom: "Inferior direito"
+vertical: "Vertical"
+horizontal: "Exibir painel lateral inteiro"
+position: "Posição"
+serverRules: "Regras do servidor"
+continue: "Continuar"
preservedUsernamesDescription: "Liste os nomes de usuário que deseja reservar, separando-os por quebras de linha. Os nomes de usuário especificados aqui não poderão ser utilizados durante a criação de contas. No entanto, esta restrição não se aplica quando a conta é criada por um administrador. Além disso, as contas que já existem não serão afetadas."
+archive: "Arquivo"
channelArchiveConfirmTitle: "Deseja realmente arquivar {name}?"
youFollowing: "Seguindo"
preventAiLearningDescription: "Solicita-se que o conteúdo de notas e imagens enviadas não seja usado como objeto de aprendizado por sistemas externos de geração de texto ou imagens. Isso é alcançado incluindo a flag 'noai' na resposta HTML. No entanto, o cumprimento dessa solicitação depende do próprio sistema de IA, portanto, não é garantia total de prevenção de aprendizado."
@@ -789,8 +1007,15 @@ rolesThatCanBeUsedThisEmojiAsReaction: "Cargos que podem utilizar este emoji com
rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "Se nenhum cargo for especificado, qualquer pessoa pode usar este emoji como reação."
rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Estes cargos devem ser públicos."
waitingForMailAuth: "Verificação de e-mail pendente "
+icon: "Avatar"
+replies: "Responder"
+renotes: "Repostar"
+keepScreenOn: "Manter a tela do dispositivo sempre ligada"
+flip: "Inversão"
_initialAccountSetting:
followUsers: "Siga usuários que lhe interessam para criar a sua linha do tempo."
+_serverSettings:
+ iconUrl: "URL do ícone"
_accountMigration:
moveFromDescription: "Se você deseja migrar de outra conta para esta, é necessário criar um alias aqui. Por favor, insira a conta de origem da migração no seguinte formato: @username@server.example.com. Para excluir o alias, deixe o campo em branco e clique em salvar (não recomendado)."
moveAccountDescription: "Você está migrando para uma nova conta.\n ・Seus seguidores irão automaticamente seguir a nova conta.\n ・Todas as suas conexões de seguidores nesta conta serão removidas.\n ・Você não poderá mais criar novas notas nesta conta.\n\nA migração dos seguidores é automática, mas a migração das pessoas que você segue deve ser feita manualmente. Antes de migrar, exporte quem você está seguindo nesta conta e, assim que migrar, importe essa lista na nova conta.\nO mesmo se aplica para listas, silenciamentos e bloqueios, que também devem ser migrados manualmente.\n\n(Esta descrição se refere ao comportamento do servidor Misskey v13.12.0 ou posterior. Outros softwares ActivityPub, como Mastodon, podem ter comportamentos diferentes.)"
@@ -989,7 +1214,7 @@ _role:
priority: "Prioridade"
_priority:
low: "Baixa"
- middle: "Média"
+ middle: "Médio"
high: "Alta"
_options:
gtlAvailable: "Visualizar Linha do Tempo Global"
@@ -1038,9 +1263,11 @@ _emailUnavailable:
mx: "O servidor de informado é inválido"
smtp: "O servidor de e-mail não está respondendo"
_ffVisibility:
- public: "Publicar"
+ public: "Público"
followers: "Visível apenas para seguidores"
+ private: "Privado"
_signup:
+ almostThere: "Quase pronto"
emailAddressInfo: "Por favor, insira o seu endereço de e-mail. Ele não será divulgado."
emailSent: "Um e-mail de confirmação foi enviado para o endereço de e-mail fornecido ({email}). Acesse o link fornecido no e-mail para concluir a criação de sua conta."
_accountDelete:
@@ -1052,6 +1279,8 @@ _accountDelete:
inProgress: "A exclusão está em andamento"
_ad:
back: "Voltar"
+ reduceFrequencyOfThisAd: "Diminuir frequência deste anúncio"
+ hide: "Não exibir anúncios"
_forgotPassword:
enterEmail: "Por favor, insira o endereço de e-mail usado no cadastro de sua conta. Um link para redefinição de senha será enviado para esse endereço."
ifNoEmail: "Caso você não tenha registrado um endereço de e-mail, por favor, entre em contato com o administrador."
@@ -1072,8 +1301,18 @@ _preferencesBackups:
_channel:
featured: "Destaques"
following: "Seguindo"
+ usersCount: "usuários ativos"
+ notesCount: "notas"
+ nameAndDescription: "Nome e descrição"
+_menuDisplay:
+ sideFull: "Exibir painel lateral inteiro"
+ top: "Exibir barra superior"
+ hide: "Ocultar"
+_instanceMute:
+ instanceMuteDescription: "Todas as notas e repostagens do servidor configurado serão silenciados, incluindo respostas aos usuários do servidor mutado."
_theme:
description: "Descrição"
+ alpha: "Opacidade"
deleteConstantConfirm: "Confirma a exclusão da constante {const}?"
keys:
mention: "Menção"
@@ -1082,11 +1321,8 @@ _theme:
_sfx:
note: "Posts"
notification: "Notificações"
- chat: "Chat"
_ago:
invalid: "Não há nada aqui"
-_timelineTutorial:
- step1_2: "Existem vários tipos de linhas do tempo, por exemplo, na 'Linha do Tempo Principal', você verá as notas das pessoas que está seguindo, e na 'Linha do Tempo Local', verá todas as notas de {name}."
_2fa:
securityKeyInfo: "Além da autenticação por impressão digital ou PIN, você também pode configurar a autenticação por chaves de segurança de hardware compatível com FIDO2 para proteger ainda mais a sua conta."
removeKeyConfirm: "Deseja excluir {name}?"
@@ -1160,7 +1396,7 @@ _poll:
canMultipleVote: "Permitir múltipla seleção"
vote: "Votar em enquetes"
_visibility:
- home: "casa"
+ home: "Início"
followers: "Seguidores"
followersDescription: "Tornar visível apenas para os meus seguidores"
_profile:
@@ -1175,7 +1411,7 @@ _exportOrImport:
_charts:
federation: "União"
_timelines:
- home: "casa"
+ home: "Início"
_play:
new: "Criar Play"
edit: "Editar Play"
@@ -1205,13 +1441,14 @@ _notification:
youGotMention: "{name} te mencionou"
youGotReply: "{name} te respondeu"
youGotQuote: "{name} te citou"
+ youRenoted: "Repostagens de {name}"
youWereFollowed: "Você tem um novo seguidor"
youReceivedFollowRequest: "Você recebeu um pedido de seguidor"
yourFollowRequestAccepted: "Seu pedido de seguidor foi aceito"
pollEnded: "Os resultados da enquete agora estão disponíveis"
emptyPushNotificationMessage: "As notificações de alerta foram atualizadas"
_types:
- all: "Todos"
+ all: "Todas"
follow: "Seguindo"
mention: "Menção"
reply: "Respostas"
@@ -1257,3 +1494,7 @@ _webhookSettings:
_events:
follow: "Quando seguindo um usuário"
followed: "Quando sendo seguido"
+ renote: "Quando repostado"
+_moderationLogTypes:
+ suspend: "Suspender"
+ resetPassword: "Redefinir senha"
diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml
index 2d3099858d..77bccb7e6b 100644
--- a/locales/ro-RO.yml
+++ b/locales/ro-RO.yml
@@ -328,7 +328,6 @@ invite: "Invită"
driveCapacityPerLocalAccount: "Capacitatea Drive-ului per utilizator local"
driveCapacityPerRemoteAccount: "Capacitatea Drive-ului per utilizator extern"
inMb: "În megabytes"
-iconUrl: "URL-ul iconiței"
bannerUrl: "URL-ul imaginii de banner"
backgroundImageUrl: "URL-ul imaginii de fundal"
basicInfo: "Informații de bază"
@@ -630,6 +629,9 @@ sent: "Trimite"
searchByGoogle: "Caută"
file: "Fișiere"
show: "Arată"
+icon: "Avatar"
+replies: "Răspunde"
+renotes: "Re-notează"
_role:
_priority:
middle: "Mediu"
@@ -645,7 +647,6 @@ _theme:
_sfx:
note: "Note"
notification: "Notificări"
- chat: "Chat"
_ago:
invalid: "Nu e nimic de văzut aici"
_widgets:
@@ -702,3 +703,6 @@ _deck:
mentions: "Mențiuni"
_webhookSettings:
name: "Nume"
+_moderationLogTypes:
+ suspend: "Suspendă"
+ resetPassword: "Resetează parola"
diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml
index 7d511f889b..d8f7fe5193 100644
--- a/locales/ru-RU.yml
+++ b/locales/ru-RU.yml
@@ -59,7 +59,7 @@ copyFileId: "Скопировать ID файла"
copyFolderId: "Скопировать ID папки"
copyProfileUrl: "Скопировать URL профиля "
searchUser: "Поиск людей"
-reply: "Ответить"
+reply: "Ответ"
loadMore: "Показать еще"
showMore: "Показать еще"
showLess: "Закрыть"
@@ -354,7 +354,6 @@ invite: "Пригласить"
driveCapacityPerLocalAccount: "Объём диска на одного локального пользователя"
driveCapacityPerRemoteAccount: "Объём диска на одного пользователя с другого сайта"
inMb: "В мегабайтах"
-iconUrl: "Ссылка на аватар"
bannerUrl: "Ссылка на изображение в шапке"
backgroundImageUrl: "Ссылка на фоновое изображение"
basicInfo: "Общая информация"
@@ -410,6 +409,7 @@ aboutMisskey: "О Misskey"
administrator: "Администратор"
token: "Токен"
2fa: "2-х факторная аутентификация"
+setupOf2fa: "Настроить двухфакторную аутентификацию"
totp: "Приложение-аутентификатор"
totpDescription: "Описание приложения-аутентификатора"
moderator: "Модератор"
@@ -653,6 +653,7 @@ behavior: "Поведение"
sample: "Пример"
abuseReports: "Жалобы"
reportAbuse: "Жалоба"
+reportAbuseRenote: "Пожаловаться на репост"
reportAbuseOf: "Пожаловаться на пользователя {name}"
fillAbuseReportDescription: "Опишите, пожалуйста, причину жалобы подробнее. Если речь о конкретной заметке, будьте добры приложить ссылку на неё."
abuseReported: "Жалоба отправлена. Большое спасибо за информацию."
@@ -680,6 +681,7 @@ createNewClip: "Новая подборка"
unclip: "Убрать из подборки"
confirmToUnclipAlreadyClippedNote: "Эта заметка уже есть в подборке «{name}». Удалить из этой подборки?"
public: "Общедоступно"
+private: "Показываются только вам"
i18nInfo: "Misskey переводят на разные языки добровольцы со всего света. Ваша помощь тоже пригодится здесь: {link}."
manageAccessTokens: "Управление токенами доступа"
accountInfo: "Сведения об учётной записи"
@@ -1016,23 +1018,72 @@ retryAllQueuesConfirmTitle: "Хотите попробовать ещё раз?"
retryAllQueuesConfirmText: "Нагрузка на сервер может увеличиться"
enableChartsForRemoteUser: "Создание диаграмм для удалённых пользователей"
enableChartsForFederatedInstances: "Создание диаграмм для удалённых серверов"
-largeNoteReactions: "Показывать большие реакции на заметки"
noteIdOrUrl: "ID или ссылка на заметку"
video: "Видео"
videos: "Видео"
dataSaver: "Экономия трафика"
accountMigration: "Перенести учётную запись"
accountMoved: "Учетная запись перенесена"
+accountMovedShort: "Эта учётная запись перемещена"
operationForbidden: "Эта операция невозможна."
+forceShowAds: "Всегда отображать рекламу"
addMemo: "Добавить заметку"
editMemo: "Редактировать заметку"
reactionsList: "Реакции"
renotesList: "Репосты"
notificationDisplay: "Отображение уведомления"
leftTop: "Верхний левый угол"
+rightTop: "Сверху справа"
+leftBottom: "Снизу слева"
+rightBottom: "Снизу справа"
+vertical: "Вертикальная"
horizontal: "Сбоку"
+position: "Позиция"
+serverRules: "Правила сервера"
+pleaseConfirmBelowBeforeSignup: "Для регистрации на данном сервере, необходимо согласится с нижеследующими положениями."
+pleaseAgreeAllToContinue: "Чтобы продолжить, необходимо поставить отметки во всех полях \"согласен\"."
+continue: "Продолжить"
+preservedUsernames: "Зарезервированные имена пользователей"
+preservedUsernamesDescription: "Перечислите зарезервированные имена пользователей, отделяя их строками. Они станут недоступны при создании учётной записи. Это ограничение не применяется при создании учётной записи администраторами. Также, уже существующие учётные записи останутся без изменений."
+createNoteFromTheFile: "Создать заметку из этого файла"
+archive: "Архив"
+channelArchiveConfirmTitle: "Переместить {name} в архив?"
+channelArchiveConfirmDescription: "Архивированные каналы перестанут отображаться в списке каналов или результатах поиска. В них также нельзя будет добавлять новые записи."
+displayOfNote: "Отображение заметок"
+initialAccountSetting: "Настройка профиля"
youFollowing: "Подписки"
+preventAiLearning: "Отказаться от использования в машинном обучении (Генеративный ИИ)"
options: "Настройки ролей"
+specifyUser: "Указанный пользователь"
+failedToPreviewUrl: "Предварительный просмотр недоступен"
+update: "Обновить"
+later: "Позже"
+goToMisskey: "К Misskey"
+additionalEmojiDictionary: "Дополнительные словари эмодзи"
+installed: "Установлено"
+branding: "Бренд"
+enableIdenticonGeneration: "Включить генерацию иконки пользователя"
+turnOffToImprovePerformance: "Отключение этого параметра может повысить производительность."
+expirationDate: "Дата истечения"
+unused: "Неиспользуемый"
+expired: "Срок действия приглашения истёк"
+doYouAgree: "Согласны?"
+icon: "Аватар"
+replies: "Ответы"
+renotes: "Репост"
+flip: "Переворот"
+_initialAccountSetting:
+ accountCreated: "Аккаунт успешно создан!"
+ letsStartAccountSetup: "Давайте настроим вашу учётную запись."
+ profileSetting: "Настройки профиля"
+ privacySetting: "Настройки конфиденциальности"
+ initialAccountSettingCompleted: "Первоначальная настройка успешно завершена!"
+ skipAreYouSure: "Пропустить настройку?"
+_initialTutorial:
+ _note:
+ description: "Посты в Misskey называются 'Заметками.' Заметки отсортированы в хронологическом порядке в ленте и обновляются в режиме реального времени."
+_serverSettings:
+ iconUrl: "Адрес на иконку роли"
_achievements:
earnedAt: "Разблокировано в"
_types:
@@ -1384,6 +1435,7 @@ _plugin:
install: "Установка расширений"
installWarn: "Пожалуйста, не устанавливайте расширения, которым не доверяете."
manage: "Управление расширениями"
+ viewSource: "Просмотр исходника"
_preferencesBackups:
list: "Существующие резервные копии"
saveNew: "Создать резервную копию"
@@ -1444,11 +1496,6 @@ _wordMute:
muteWords: "Скрыть слово"
muteWordsDescription: "Пишите слова через пробел в одной строке, чтобы фильтровать их появление вместе; а если хотите фильтровать любое из них, пишите в отдельных строках."
muteWordsDescription2: "Здесь можно использовать регулярные выражения — просто заключите их между двумя дробными чертами (/)."
- softDescription: "Соответствующие условиям заметки будут спрятаны из вашей ленты."
- hardDescription: "Соответстующие условиям заметки вообще не будут попадать в вашу ленту. Даже если вы поменяете условия, отсеенные таким образом заметки уже не появятся."
- soft: "Мягко"
- hard: "Жёстко"
- mutedNotes: "Скрытые заметки"
_instanceMute:
instanceMuteDescription: "Заметки и репосты с указанных здесь инстансов, а также ответы пользователям оттуда же не будут отображаться."
instanceMuteDescription2: "Пишите каждый инстанс на отдельной строке"
@@ -1512,9 +1559,6 @@ _theme:
infoFg: "Текст сообщения"
infoWarnBg: "Фон предупреждения"
infoWarnFg: "Текст предупреждения"
- cwBg: "Фон предупреждения о содержимом"
- cwFg: "Текст предупреждения о содержимом"
- cwHoverBg: "Фон предупреждения о содержимом (под указателем)"
toastBg: "Фон оповещения"
toastFg: "Текст оповещения"
buttonBg: "Фон кнопки"
@@ -1532,8 +1576,6 @@ _sfx:
note: "Заметки"
noteMy: "Собственные заметки"
notification: "Уведомления"
- chat: "Сообщения"
- chatBg: "Сообщения (фон)"
antenna: "Антенна"
channel: "Канал"
_ago:
@@ -1552,27 +1594,15 @@ _time:
minute: "мин"
hour: "ч"
day: "сут"
-_timelineTutorial:
- title: "Как пользоваться Misskey"
- step1_1: "Это лицо Misskey, так называемая лента. Ваш инстанс, {name}, покажет тут все опубликованные на нём заметки в хронологическом порядке."
- step1_2: "Здесь есть несколько лент. К примеру «персональная» лента отображает заметки тех, на кого вы подписаны. А «местная» — заметки тех, кого приютил {name}."
- step2_1: "Что ж, теперь самое время опубликовать заметку. Если нажать вверху страницы на изображение карандаша, появится форма для текста."
- step2_2: "Почему бы не написать немного о себе? Ну, или хотя бы «Привет, {name}»?"
- step3_1: "Справились с первой заметкой?"
- step3_2: "Отлично, теперь она должна появиться в вашей ленте."
- step4_1: "А ещё здесь можно делиться своими реакциями на заметки."
- step4_2: "Отмечайте реакции, нажимая на символ «+» под заметкой и выбирая значок по душе."
_2fa:
alreadyRegistered: "Двухфакторная аутентификация уже настроена."
registerTOTP: "Начните настраивать приложение-аутентификатор"
- passwordToTOTP: "Пожалуйста, введите свой пароль"
step1: "Прежде всего, установите на устройство приложение для аутентификации, например, {a} или {b}."
step2: "Далее отсканируйте отображаемый QR-код при помощи приложения."
step2Click: "Нажав на QR-код, вы можете зарегистрироваться с помощью приложения для аутентификации или брелка для ключей, установленного на вашем устройстве."
- step2Url: "Если пользуетесь приложением на компьютере, можете ввести в него эту строку (URL):"
step3Title: "Введите проверочный код"
step3: "И наконец, введите код, который покажет приложение."
- step4: "Теперь при каждом входе на сайт вам нужно будет вводить код из приложения аналогичным образом.\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}"
+ step4: "Теперь при каждом входе на сайт вам нужно будет вводить код из приложения аналогичным образом."
securityKeyNotSupported: "Ваш браузер не поддерживает ключи безопасности."
registerTOTPBeforeKey: "Чтобы зарегистрировать ключ безопасности и пароль, сначала настройте приложение аутентификации."
securityKeyInfo: "Вы можете настроить вход с помощью аппаратного ключа безопасности, поддерживающего FIDO2, или отпечатка пальца или PIN-кода на устройстве."
@@ -1869,7 +1899,7 @@ _notification:
app: "Уведомления из приложений"
_actions:
followBack: "отвечает взаимной подпиской"
- reply: "Ответить"
+ reply: "Ответ"
renote: "Репост"
_deck:
alwaysShowMainColumn: "Всегда показывать главную колонку"
@@ -1898,6 +1928,7 @@ _deck:
channel: "Каналы"
mentions: "Упоминания"
direct: "Личное"
+ roleTimeline: "История Ролей"
_dialog:
charactersExceeded: "Превышено максимальное количество символов! У вас {current} / из {max}"
charactersBelow: "Это ниже минимального количества символов! У вас {current} / из {min}"
@@ -1905,5 +1936,9 @@ _disabledTimeline:
title: "Лента отключена"
description: "Ваша текущая роль не позволяет пользоваться этой лентой."
_webhookSettings:
+ createWebhook: "Создать вебхук"
name: "Название"
active: "Вкл."
+_moderationLogTypes:
+ suspend: "Заморозить"
+ resetPassword: "Сброс пароля:"
diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml
index 2da7e4255e..903891fdb9 100644
--- a/locales/sk-SK.yml
+++ b/locales/sk-SK.yml
@@ -337,7 +337,6 @@ invite: "Pozvať"
driveCapacityPerLocalAccount: "Kapacita disku pre používateľa"
driveCapacityPerRemoteAccount: "Kapacita disku pre vzdialeného používateľa"
inMb: "V megabajtoch"
-iconUrl: "Favicon URL"
bannerUrl: "URL obrázku bannera"
backgroundImageUrl: "URL obrázku pozadia"
basicInfo: "Základné informácie"
@@ -654,6 +653,7 @@ createNewClip: "Vytvoriť nový klip"
unclip: "Odopnúť"
confirmToUnclipAlreadyClippedNote: "Táto poznámka je už pripnutá ako \"{name}\". Naozaj ju chcete odopnúť?"
public: "Verejné"
+private: "Súkromné"
i18nInfo: "Misskey je prekladaný do rôznych jazykov dobrovoľníkmi. Pomôcť môžete na {link}."
manageAccessTokens: "Spravovať prístupové tokeny"
accountInfo: "Informácie o účte"
@@ -918,6 +918,10 @@ pleaseDonate: "Misskey je bezplatný softvér, ktorý používa {host}. Prosím,
color: "Farba"
horizontal: "Strana"
youFollowing: "Sledované"
+icon: "Avatar"
+replies: "Odpovedať"
+renotes: "Preposlať"
+flip: "Preklopiť"
_role:
priority: "Priorita"
_priority:
@@ -975,6 +979,7 @@ _plugin:
install: "Inštalova pluginy"
installWarn: "Prosím neinštalujte nedôveryhodné pluginy."
manage: "Spravovanie pluginov"
+ viewSource: "Ukázať zdroj"
_preferencesBackups:
list: "Vytvorené zálohy"
saveNew: "Uložiť novú"
@@ -1035,11 +1040,6 @@ _wordMute:
muteWords: "Umlčané slová"
muteWordsDescription: "Medzerami oddeľte pre podmienku AND a novými riadkami pre podmienku OR."
muteWordsDescription2: "Regulárne výrazy sa použijú keď použijete okolo lomítka."
- softDescription: "Skryje poznámky z časovej osi, ktoré spĺňajú podmienky."
- hardDescription: "Zabráni poznámky spĺňajúce množinu podmienok, aby boli pridané do časovej osi. Navyše tieto poznámky nepribudnú v časovej osi ani keď sa podmienky zmenia."
- soft: "Mäkké"
- hard: "Tvrdé"
- mutedNotes: "Umlčané poznámky"
_instanceMute:
instanceMuteDescription: "Toto umlčí všetky poznámky/preposlania zo zoznamu serverov, vrátane tých, na ktoré používatelia odpovedajú z umlčaného servera."
instanceMuteDescription2: "Oddeľte novými riadkami"
@@ -1103,9 +1103,6 @@ _theme:
infoFg: "Informačný text"
infoWarnBg: "Pozadie varovania"
infoWarnFg: "Text varovania"
- cwBg: "CW pozadie tlačidla"
- cwFg: "CW text tlačidla"
- cwHoverBg: "CW pozadie tlačidla (pod kurzorom)"
toastBg: "Pozadie upozornenia"
toastFg: "Text upozornenia"
buttonBg: "Pozadie tlačidla"
@@ -1123,8 +1120,6 @@ _sfx:
note: "Poznámky"
noteMy: "Vlastná poznámka"
notification: "Oznámenia"
- chat: "Chat"
- chatBg: "Chat (pozadie)"
antenna: "Antény"
channel: "Upozornenia kanála"
_ago:
@@ -1147,9 +1142,8 @@ _2fa:
alreadyRegistered: "Už ste zaregistrovali 2-faktorové autentifikačné zariadenie."
step1: "Najprv si nainštalujte autentifikačnú aplikáciu (napríklad {a} alebo {b}) na svoje zariadenie."
step2: "Potom, naskenujte QR kód zobrazený na obrazovke."
- step2Url: "Do aplikácie zadajte nasledujúcu URL adresu:"
step3: "Nastavenie dokončíte zadaním tokenu z vašej aplikácie."
- step4: "Od teraz, všetky ďalšie prihlásenia budú vyžadovať prihlasovací token.\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}"
+ step4: "Od teraz, všetky ďalšie prihlásenia budú vyžadovať prihlasovací token."
securityKeyInfo: "Okrem odtlačku prsta alebo PIN autentifikácie si môžete nastaviť autentifikáciu cez hardvérový bezpečnostný kľúč podporujúci FIDO2 a tak ešte viac zabezpečiť svoj účet."
removeKeyConfirm: "Naozaj chcete odstrániť \"{name}\"?"
renewTOTPCancel: "Nie, ďakujem"
@@ -1448,3 +1442,6 @@ _deck:
_webhookSettings:
name: "Názov"
active: "Zapnuté"
+_moderationLogTypes:
+ suspend: "Zmraziť"
+ resetPassword: "Resetovať heslo"
diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml
index 07f43afe2e..92678afef8 100644
--- a/locales/sv-SE.yml
+++ b/locales/sv-SE.yml
@@ -337,7 +337,6 @@ registration: "Registrera"
enableRegistration: "Aktivera registrering av nya användare"
invite: "Inbjudan"
inMb: "I megabyte"
-iconUrl: "URL till profilbilden"
bannerUrl: "URL till banner-bilden"
basicInfo: "Grundläggande info"
pinnedUsers: "Fästa användare"
@@ -485,6 +484,9 @@ windowRestore: "Återställ"
pleaseDonate: "Misskey är en gratis programvara som används på {host}. Donera gärna för att göra utvecklingen ständigt, tack!"
resetPasswordConfirm: "Återställ verkligen ditt lösenord?"
dataSaver: "Databesparing"
+icon: "Profilbild"
+replies: "Svara"
+renotes: "Omnotera"
_achievements:
_types:
_open3windows:
@@ -505,10 +507,8 @@ _theme:
_sfx:
note: "Noter"
notification: "Notifikationer"
- chat: "Chatt"
antenna: "Antenner"
_2fa:
- passwordToTOTP: "Skriv in ditt lösenord"
renewTOTPCancel: "Nej tack"
_antennaSources:
all: "Alla noter"
@@ -572,3 +572,6 @@ _deck:
_webhookSettings:
name: "Namn"
active: "Aktiverad"
+_moderationLogTypes:
+ suspend: "Suspendera"
+ resetPassword: "Återställ Lösenord"
diff --git a/locales/th-TH.yml b/locales/th-TH.yml
index 943d878e04..8df36a6829 100644
--- a/locales/th-TH.yml
+++ b/locales/th-TH.yml
@@ -45,6 +45,7 @@ pin: "ปักหมุดไปยังโปรไฟล์"
unpin: "เลิกปักหมุดจากโปรไฟล์"
copyContent: "คัดลอกเนื้อหา"
copyLink: "คัดลอกลิงก์"
+copyLinkRenote: "คัดลอกลิงก์รีโน้ต"
delete: "ลบ"
deleteAndEdit: "ลบและแก้ไข"
deleteAndEditConfirm: "นายแน่ใจแล้วเหรอ? ว่าต้องการลบโน้ตนี้และแก้ไข คุณอาจจะสูญเสียการโต้ตอบ, โน้ต, และการตอบกลับทั้งหมดได้นะ"
@@ -74,7 +75,7 @@ import: "นำเข้า"
export: "นำออก"
files: "ไฟล์"
download: "ดาวน์โหลด"
-driveFileDeleteConfirm: "นายแน่ใจแล้วหรอ? ว่าต้องการลบไฟล์ \"{name}\" โน้ตย่อที่แนบมากับไฟล์นี้ก็จะถูกลบด้วยนะ"
+driveFileDeleteConfirm: "คุณต้องการลบไฟล์ \"{name}\" ใช่หรือไม่? โน้ตย่อที่แนบมากับไฟล์นี้ก็จะถูกลบไปด้วย"
unfollowConfirm: "นายแน่ใจแล้วหรอว่าต้องการเลิกติดตาม {name}?"
exportRequested: "เมื่อคุณได้ร้องขอการส่งออก อาจจะต้องใช้เวลาสักครู่ และจะถูกเพิ่มในไดรฟ์ของคุณเมื่อเสร็จสิ้นแล้ว"
importRequested: "เมื่อคุณได้ร้องขอการนำเข้า อาจจะต้องใช้เวลาสักครู่นะ"
@@ -92,8 +93,8 @@ somethingHappened: "อุ๊ย ! มีอะไรบางอย่างผ
retry: "ลองใหม่อีกครั้ง"
pageLoadError: "เกิดข้อผิดพลาดในการโหลดหน้านี้"
pageLoadErrorDescription: "โดยปกติแล้วมักจะเกิดจากข้อผิดพลาดของเครือข่ายหรือแคชของเบราว์เซอร์ ลองล้างแคชแล้วลองใหม่อีกครั้งหลังจากรอสักครู่ "
-serverIsDead: "เซิร์ฟเวอร์นี้ไม่มีการตอบสนอง ได้โปรดกรุณารอสักครู่แล้วลองใหม่อีกครั้งนะ"
-youShouldUpgradeClient: "หากต้องการดูหน้านี้ได้โปรดกรุณา รีเซ็ตเพื่ออัปเดตไคลเอ็นต์ของคุณนะ"
+serverIsDead: "เซิร์ฟเวอร์นี้ไม่มีการตอบสนอง โปรดกรุณารอสักครู่แล้วลองใหม่อีกครั้ง"
+youShouldUpgradeClient: "หากต้องการดูหน้านี้ กรุณาโหลดหน้าใหม่เพื่ออัปเดตไคลเอ็นต์ของคุณ"
enterListName: "ใส่ชื่อสำหรับรายการลิสต์"
privacy: "ความเป็นส่วนตัว"
makeFollowManuallyApprove: "ติดตามคำขอที่ต้องได้รับการอนุมัติ"
@@ -109,7 +110,7 @@ unrenote: "เลิกรีโน้ต"
renoted: "รีโน้ตแล้ว"
cantRenote: "โพสต์นี้ไม่สามารถรีโน้ตไว้ใหม่ได้นะ"
cantReRenote: "ไม่สามารถรีโน้ตเอาไว้ใหม่ได้นะ"
-quote: "อ้างคำพูด"
+quote: "อ้างอิง"
inChannelRenote: "รีโน้ตช่องแชลแนลเท่านั้น"
inChannelQuote: "อ้างช่องเท่านั้น"
pinnedNote: "โน้ตที่ปักหมุดเอาไว้"
@@ -137,7 +138,7 @@ suspend: "ถูกระงับ"
unsuspend: "ยกเลิกระงับ"
blockConfirm: "คุณแน่ใจแล้วเหรอ? ว่าต้องการบล็อกบัญชีนี้"
unblockConfirm: "คุณแน่ใจแล้วเหรอ? ว่าต้องการปลดบล็อคบัญชีนี้"
-suspendConfirm: "นายแน่ใจแล้วเหรอว่าต้องการระงับบัญชีนี้อ่ะ?"
+suspendConfirm: "แน่ใจว่าคุณต้องการระงับบัญชีนี้?"
unsuspendConfirm: "นายแน่ใจแล้วหรอ? ว่าต้องการยกเลิกการระงับบัญชีนี้"
selectList: "เลือกรายการ"
editList: "แก้ไขรายการ"
@@ -156,6 +157,7 @@ addEmoji: "แทรกอีโมจิ"
settingGuide: "การตั้งค่าที่แนะนำ"
cacheRemoteFiles: "แคชไฟล์ระยะไกล"
cacheRemoteFilesDescription: "เมื่อปิดใช้งานการตั้งค่านี้ ไฟล์ระยะไกลนั้นจะถูกโหลดโดยตรงจากอินสแตนซ์ระยะไกล แต่กรณีการปิดใช้งานนี้จะช่วยลดปริมาณการใช้พื้นที่จัดเก็บข้อมูล แต่เพิ่มปริมาณการใช้งาน เพราะเนื่องจากจะไม่มีการสร้างภาพขนาดย่อ"
+youCanCleanRemoteFilesCache: "คุณสามารถล้างแคชได้โดยคลิกที่ปุ่ม 🗑️ ในมุมมองการจัดการไฟล์"
cacheRemoteSensitiveFiles: "ไฟล์ระยะไกลที่มีความละเอียดอ่อนแคช"
cacheRemoteSensitiveFilesDescription: "เมื่อปิดการใช้งานแล้วการตั้งค่านี้ ไฟล์รีโมตที่มีความละเอียดอ่อนนั้นจะถูกโหลดโดยตรงจากอินสแตนซ์ระยะไกลโดยที่ไม่มีการแคช"
flagAsBot: "ทำเครื่องหมายบอกว่าบัญชีนี้เป็นบอท"
@@ -254,7 +256,7 @@ imageUrl: "url รูปภาพ"
remove: "ลบ"
removed: "ถูกลบไปแล้ว"
removeAreYouSure: "นายแน่ใจจริงหรอว่าต้องการที่จะลบออก \"{x}\""
-deleteAreYouSure: "นายแน่ใจจริงหรอว่าต้องการที่จะลบออก \"{x}\""
+deleteAreYouSure: "ต้องการลบ {x} หรือไม่คะ?"
resetAreYouSure: "รีเซ็ตเลยไหม"
saved: "บันทึกแล้ว"
messaging: "แชท"
@@ -308,8 +310,8 @@ renameFolder: "เปลี่ยนชื่อโฟลเดอร์"
deleteFolder: "ลบโฟลเดอร์"
addFile: "เพิ่มไฟล์"
emptyDrive: "ไดรฟ์ของคุณว่างเปล่านะ"
-emptyFolder: "โฟลเดอร์นี้น่าจะว่างเปล่านะ"
-unableToDelete: "ไม่สามารถลบออกได้นะ"
+emptyFolder: "โฟลเดอร์นี้ว่างเปล่า"
+unableToDelete: "ไม่สามารถลบออกได้"
inputNewFileName: "ป้อนชื่อไฟล์ใหม่นะ"
inputNewDescription: "กรุณาใส่แคปชั่นใหม่"
inputNewFolderName: "กรุณาใส่ชื่อโฟลเดอร์ใหม่นะ\n"
@@ -354,7 +356,6 @@ invite: "เชิญชวน"
driveCapacityPerLocalAccount: "ความจุของไดรฟ์ต่อผู้ใช้ภายในเครื่อง"
driveCapacityPerRemoteAccount: "ความจุของไดรฟ์ต่อผู้ใช้ระยะไกล"
inMb: "เป็นเมกะไบต์"
-iconUrl: "ไอคอน URL"
bannerUrl: "URL รูปภาพแบนเนอร์"
backgroundImageUrl: "URL ภาพพื้นหลัง"
basicInfo: "ข้อมูลเบื้องต้น"
@@ -363,7 +364,7 @@ pinnedUsersDescription: "ลิสต์ชื่อผู้ใช้โดย
pinnedPages: "หน้าที่ปักหมุด"
pinnedPagesDescription: "ป้อนเส้นทางของหน้าที่คุณต้องการตรึงไว้ที่หน้าแรกของอินสแตนซ์นี้ โดยคั่นด้วยตัวแบ่งบรรทัด"
pinnedClipId: "ID ของคลิปที่จะปักหมุด"
-pinnedNotes: "โน้ตที่ปักหมุดเอาไว้"
+pinnedNotes: "โน้ตที่ปักหมุดไว้"
hcaptcha: "hCaptcha"
enableHcaptcha: "เปิดใช้ hCaptcha"
hcaptchaSiteKey: "คีย์ไซต์"
@@ -404,16 +405,20 @@ recentlyDiscoveredUsers: "ผู้ใช้ที่เพิ่งค้นพ
exploreUsersCount: "มีผู้ใช้ {จำนวน} ราย"
exploreFediverse: "สำรวจเฟดดิเวิร์ส"
popularTags: "แท็กยอดนิยม"
-userList: "รายการ"
+userList: "ลิสต์"
about: "เกี่ยวกับ"
aboutMisskey: "เกี่ยวกับ Misskey"
administrator: "ผู้ดูแลระบบ"
token: "โทเค็น"
2fa: "การยืนยันตัวตนแบบสองชั้น"
+setupOf2fa: "ตั้งค่าการยืนยันตัวตนแบบสองชั้น"
totp: "แอป Authenticator"
totpDescription: "ใช้แอปยืนยันตัวตนเพื่อป้อนรหัสผ่านแบบใช้ครั้งเดียว"
moderator: "ผู้ควบคุม"
moderation: "การกลั่นกรอง"
+moderationNote: "โน้ตการกลั่นกรอง"
+addModerationNote: "เพิ่มโน้ตการกลั่นกรอง"
+moderationLogs: "บันทึกการกลั่นกรอง"
nUsersMentioned: "กล่าวถึงโดยผู้ใช้ {n} รายนี้"
securityKeyAndPasskey: "ความปลอดภัยและรหัสผ่าน"
securityKey: "กุญแจความปลอดภัย"
@@ -444,7 +449,7 @@ text: "ข้อความ"
enable: "เปิดใช้งาน"
next: "ถัดไป"
retype: "พิมพ์รหัสอีกครั้ง"
-noteOf: "โน้ต โดย {ผู้ใช้งาน}"
+noteOf: "โน้ต โดย {user}"
quoteAttached: "อ้างอิง"
quoteQuestion: "นายต้องการที่จะอ้างอิงหรอ?"
noMessagesYet: "ยังไม่มีข้อความนะ"
@@ -653,6 +658,7 @@ behavior: "พฤติกรรม"
sample: "ตัวอย่าง"
abuseReports: "รายงาน"
reportAbuse: "รายงาน"
+reportAbuseRenote: "รายงานรีโน้ต"
reportAbuseOf: "รายงาน {ชื่อ}"
fillAbuseReportDescription: "กรุณากรอกรายละเอียดเกี่ยวกับรายงานนี้ หากเป็นเรื่องเกี่ยวกับโน้ตโดยเฉพาะ ได้โปรดระบุ URL"
abuseReported: "เราได้ส่งรายงานของคุณไปแล้ว ขอบคุณมากๆนะ"
@@ -680,6 +686,7 @@ createNewClip: "สร้างคลิปใหม่"
unclip: "ลบคลิป"
confirmToUnclipAlreadyClippedNote: "โน้ตนี้เป็นส่วนหนึ่งของคลิป \"{name}\" แล้ว คุณต้องการลบออกจากคลิปนี้แทนอย่างงั้นหรอ?"
public: "สาธารณะ"
+private: "ส่วนตัว"
i18nInfo: "Misskey กำลังได้รับการแปลเป็นภาษาต่างๆ โดยอาสาสมัคร คุณสามารถช่วยเหลือได้ที่ {link}"
manageAccessTokens: "การจัดการโทเค็นการเข้าถึง"
accountInfo: "ข้อมูลบัญชี"
@@ -704,6 +711,7 @@ lockedAccountInfo: "เว้นแต่ว่าคุณจะต้องต
alwaysMarkSensitive: "ทำเครื่องหมายเป็น NSFW เป็นค่าเริ่มต้น"
loadRawImages: "โหลดภาพต้นฉบับแทนการแสดงภาพขนาดย่อ"
disableShowingAnimatedImages: "ไม่ต้องเล่นภาพเคลื่อนไหว"
+highlightSensitiveMedia: "ไฮไลท์สื่อที่ละเอียดอ่อน"
verificationEmailSent: "ส่งอีเมลยืนยันแล้วนะ ได้โปรดกรุณาไปที่ลิงก์ที่รวมไว้เพื่อทำการตรวจสอบให้เสร็จสิ้น"
notSet: "ไม่ได้ตั้งค่า"
emailVerified: "อีเมลได้รับการยืนยันแล้ว"
@@ -957,7 +965,7 @@ show: "แสดงผล"
neverShow: "ไม่ต้องแสดงข้อความนี้อีก"
remindMeLater: "ไว้ครั้งหน้าแล้วกัน"
didYouLikeMisskey: "คุณเคยชอบ Misskey ไหม?"
-pleaseDonate: "{host} ใช้ซอฟต์แวร์ฟรี Misskey เราขอขอบคุณการบริจาคของคุณอย่างสูงเพื่อให้การพัฒนา Misskey สามารถดำเนินต่อไปได้นะ!"
+pleaseDonate: "Misskey เป็นซอฟต์แวร์ฟรีที่ใช้งานโดย {host} เราขอขอบคุณการสนับสนุนของคุณอย่างสูงเพื่อให้การพัฒนา Misskey สามารถดำเนินต่อไปได้!"
roles: "บทบาท"
role: "บทบาท"
noRole: "ไม่พบบทบาท"
@@ -1018,7 +1026,7 @@ retryAllQueuesConfirmText: "สิ่งนี้จะเพิ่มการ
enableChartsForRemoteUser: "สร้างแผนภูมิข้อมูลผู้ใช้ระยะไกล"
enableChartsForFederatedInstances: "สร้างแผนภูมิข้อมูลอินสแตนซ์ระยะไกล"
showClipButtonInNoteFooter: "เพิ่ม \"คลิป\" เพื่อบันทึกเมนูการทำงาน"
-largeNoteReactions: "ขยายรีแอคชั่นการแสดงผล"
+reactionsDisplaySize: "รีแอคชั่นแสดงผลขนาด"
noteIdOrUrl: "โน้ต ID หรือ URL"
video: "วีดีโอ"
videos: "วีดีโอ"
@@ -1094,6 +1102,46 @@ expired: "หมดอายุแล้ว"
doYouAgree: "ยอมรับมั้ย?"
beSureToReadThisAsItIsImportant: "กรุณาอ่านข้อมูลที่สำคัญอันนี้"
iHaveReadXCarefullyAndAgree: "ฉันได้อ่านข้อความ \"{x}\" และยินยอม"
+dialog: "ไดอะล็อก"
+icon: "ไอคอน"
+forYou: "สำหรับคุณ"
+currentAnnouncements: "ประกาศในปัจจุบัน"
+pastAnnouncements: "ประกาศที่ผ่านมา"
+youHaveUnreadAnnouncements: "มีการประกาศที่ยังไม่ได้อ่าน"
+replies: "ตอบกลับ"
+renotes: "รีโน้ต"
+loadReplies: "แสดงการตอบกลับ"
+loadConversation: "แสดงบทสนทนา"
+pinnedList: "รายการที่ปักหมุดไว้แล้ว"
+keepScreenOn: "เปิดหน้าจอไว้"
+notifyNotes: "แจ้งเตือนเกี่ยวกับโพสต์ใหม่"
+unnotifyNotes: "หยุดการแจ้งเตือนเกี่ยวกับโน้ตใหม่"
+authentication: "การตรวจสอบสิทธิ์"
+authenticationRequiredToContinue: "กรุณาตรวจสอบการรับรองความถูกต้องเพื่อดำเนินการต่อ"
+dateAndTime: "เวลาประทับ"
+showRenotes: "แสดงรีโน้ต"
+edited: "แก้ไขแล้ว"
+notificationRecieveConfig: "การตั้งค่าการแจ้งเตือน"
+mutualFollow: "ติดตามซึ่งกันและกัน"
+fileAttachedOnly: "เฉพาะโน้ตที่มีไฟล์เท่านั้น"
+showRepliesToOthersInTimeline: "แสดงการตอบกลับไปยังอื่นๆในไทม์ไลน์"
+hideRepliesToOthersInTimeline: "ซ่อนการตอบกลับไปยังอื่นๆจากไทม์ไลน์"
+externalServices: "บริการภายนอก"
+impressum: "อิมเพรสชั่น"
+impressumUrl: "URL อิมเพรสชั่น"
+privacyPolicy: "นโยบายความเป็นส่วนตัว"
+privacyPolicyUrl: "URL นโยบายความเป็นส่วนตัว"
+tosAndPrivacyPolicy: "เงื่อนไขในการให้บริการและนโยบายความเป็นส่วนตัว"
+flip: "ย้อนกลับ"
+_announcement:
+ forExistingUsers: "ผู้ใช้งานที่มีอยู่เท่านั้น"
+ forExistingUsersDescription: "การประกาศนี้จะแสดงต่อผู้ใช้ที่มีอยู่ ณ จุดที่เผยแพร่นั้นๆถ้าหากเปิดใช้งาน ถ้าหากปิดใช้งานผู้ที่กำลังสมัครใหม่หลังจากโพสต์แล้วนั้นก็จะเห็นเช่นกัน"
+ needConfirmationToRead: "จำเป็นต้องยืนยันเพื่อทำเครื่องหมายบอกว่าอ่านแล้ว"
+ needConfirmationToReadDescription: "ข้อความแจ้งแยก ถ้าหากต้องการเพื่อยืนยันว่ากำลังทำเครื่องหมายประกาศนี้ว่าอ่านแล้วจะแสดงขึ้นถ้าหากเปิดใช้งาน การประกาศนั้นจะไม่รวมอยู่ในฟังก์ชั่นว่า \"ทำเครื่องหมายทั้งหมดว่าอ่านแล้ว\""
+ end: "ประกาศเก็บถาวร"
+ tooManyActiveAnnouncementDescription: "การมีประกาศที่ใช้งานมากเกินไปนั้นอาจจะทำให้ประสบการณ์ของผู้ใช้งานนั้นดูแย่ลง โปรดกรุณาพิจารณาการเก็บประกาศที่ล้าสมัยด้วยนะค่ะ"
+ readConfirmTitle: "ทำเครื่องหมายบอกว่าอ่านแล้วเลยมั้ย?"
+ readConfirmText: "การดำเนินการนี้จะทำเครื่องหมายเนื้อหาของ \"{title}\" บอกว่าอ่านแล้วนะ"
_initialAccountSetting:
accountCreated: "คุณได้สร้างบัญชีของคุณสำเร็จเรียบร้อยแล้ว!"
letsStartAccountSetup: "สำหรับผู้เริ่มต้นมาตั้งค่าโปรไฟล์ของคุณกันเถอะ"
@@ -1105,12 +1153,17 @@ _initialAccountSetting:
followUsers: "ลองติดตามผู้ใช้บางคนที่คุณอาจจะสนใจเพื่อสร้างไทม์ไลน์ของคุณสิ !"
pushNotificationDescription: "กำลังเปิดใช้งานการแจ้งเตือนแบบพุชจะช่วยให้คุณได้รับการแจ้งเตือนจาก {name} โดยตรงบนอุปกรณ์ของคุณนะ"
initialAccountSettingCompleted: "ตั้งค่าโปรไฟล์เสร็จสมบูรณ์แล้ว!"
- haveFun: "สนุกกับ {name}!"
- ifYouNeedLearnMore: "ถ้าหากคุณต้องการเรียนรู้เพิ่มเติมเกี่ยวกับวิธีใช้ {ชื่อ} (Misskey) กรุณาไปที่ {link}"
+ haveFun: "ขอให้สนุก {name}!"
skipAreYouSure: "ต้องการข้ามการตั้งค่าโปรไฟล์จริงๆแบบนั้นหรอ?"
laterAreYouSure: "ต้องการตั้งค่าโปรไฟล์ในภายหลังจริงๆอย่างงั้นหรอ?"
_serverRules:
description: "ชุดของกฎที่จะแสดงก่อนการลงทะเบียนเราขอแนะนำให้ตั้งค่าสรุปข้อกำหนดในการให้บริการ"
+_serverSettings:
+ iconUrl: "ไอคอน URL"
+ appIconUsageExample: "E.g. เป็น PWA หรือเมื่อแสดงผลเป็นบุ๊กมาร์กหน้าจอหลักบนโทรศัพท์"
+ appIconResolutionMustBe: "ความละเอียดขั้นต่ำไว้คือ {resolution}."
+ manifestJsonOverride: "manifest.json โอเวอร์ลาย"
+ shortName: "ชื่อย่อ"
_accountMigration:
moveFrom: "ย้ายข้อมูลบัญชีอื่นไปยังอีกบัญชีนี้หนึ่ง"
moveFromSub: "สร้างนามแฝงไปยังบัญชีอื่น"
@@ -1130,8 +1183,8 @@ _achievements:
earnedAt: "ได้รับเมื่อ"
_types:
_notes1:
- title: "เพียงแค่ตั้งค่า msky ของฉัน"
- description: "โพสต์โน้ตครั้งแรกของคุณ"
+ title: "just setting up my msky"
+ description: "โพสต์โน้ตแรกของคุณ"
flavor: "ขอให้มีช่วงเวลาที่ดีกับ Misskey นะคะ!"
_notes10:
title: "โน้ตบางอย่าง"
@@ -1290,7 +1343,7 @@ _achievements:
_iLoveMisskey:
title: "ฉันรัก Misskey"
description: "โพสต์ \"I ❤ #Misskey\""
- flavor: "ทีมผู้พัฒนา Misskey ได้ขอบคุณสำหรับการสนับสนุนของคุณ!"
+ flavor: "ขอบคุณที่ใช้ Misskey! by ทีมผู้พัฒนา"
_foundTreasure:
title: "ล่าสมบัติ"
description: "คุณพบสมบัติที่ซ่อนอยู่"
@@ -1298,7 +1351,7 @@ _achievements:
title: "พักผ่อนสักหน่อย"
description: "ใช้เวลา 30 นาทีบน Misskey"
_client60min:
- title: "ไม่พบ \"Miss\" ใน Misskey "
+ title: "ไม่มี \"Miss\" ใน Misskey "
description: "เปิด Misskey ค้างไว้แล้วอย่างน้อย 60 นาที"
_noteDeletedWithin1min:
title: "ไม่เป็นไร"
@@ -1365,6 +1418,9 @@ _achievements:
title: "Brain Diver"
description: "โพสต์ลิงก์ไปยัง Brain Diver"
flavor: "Misskey-Misskey La-Tu-Ma"
+ _smashTestNotificationButton:
+ title: "ทดสอบโอเวอร์โฟลว์"
+ description: "ทดสอบการแจ้งเตือนทริกเกอร์ซ้ำๆ ภายในระยะเวลาอันสั้นๆ"
_role:
new: "บทบาทใหม่"
edit: "แก้ไขบทบาท"
@@ -1422,6 +1478,7 @@ _role:
descriptionOfRateLimitFactor: "ขีดจํากัดอัตราที่ต่ำกว่ามีข้อจํากัดน้อยกว่าข้อจํากัดที่สูงกว่า"
canHideAds: "ซ่อนโฆษณา"
canSearchNotes: "การใช้การค้นหาโน้ต"
+ canUseTranslator: "การใช้งานแปล"
_condition:
isLocal: "ผู้ใช้ภายใน"
isRemote: "ผู้ใช้ระยะไกล"
@@ -1447,7 +1504,7 @@ _sensitiveMediaDetection:
_emailUnavailable:
used: "ที่อยู่อีเมลนี้ได้ถูกใช้ไปแล้ว"
format: "รูปแบบของที่อยู่อีเมลนี้ไม่ถูกต้อง"
- disposable: "ที่อยู่อีเมลที่ใช้แล้วทิ้งนั้นไม่สามารถใช้ได้"
+ disposable: "ไม่สามารถใช้อีเมลชั่วคราวได้"
mx: "เซิร์ฟเวอร์อีเมลนี้ไม่ถูกต้อง"
smtp: "เซิร์ฟเวอร์อีเมลนี้ไม่มีการตอบสนอง"
_ffVisibility:
@@ -1470,6 +1527,8 @@ _ad:
reduceFrequencyOfThisAd: "แสดงโฆษณานี้ให้น้อยลง"
hide: "ไม่ต้องแสดง"
timezoneinfo: "วันในสัปดาห์นี้จะถูกกำหนดจากโซนเวลาของเซิร์ฟเวอร์"
+ adsSettings: "ตั้งค่าการโฆษณา"
+ setZeroToDisable: "ตั้งค่านี้ให้เป็น 0 เพื่อปิดใช้งานโฆษณาอัปเดตแบบเรียลไทม์"
_forgotPassword:
enterEmail: "ป้อนที่อยู่อีเมลที่คุณเคยใช้ในการลงทะเบียนไว้ ลิงก์ที่คุณสามารถรีเซ็ตรหัสผ่านได้นั้นจะถูกส่งไปนะ"
ifNoEmail: "ถ้าหากคุณไม่ได้ใช้อีเมลระหว่างการลงทะเบียน กรุณาติดต่อผู้ดูแลระบบอินสแตนซ์แทนนะ"
@@ -1488,6 +1547,7 @@ _plugin:
install: "ติดตั้งปลั๊กอิน"
installWarn: "กรุณาอย่าติดตั้งปลั๊กอินที่ไม่น่าเชื่อถือนะคะ"
manage: "จัดการปลั๊กอิน"
+ viewSource: "ดูต้นฉบับ"
_preferencesBackups:
list: "สร้างการสำรองข้อมูล"
saveNew: "บันทึกใหม่"
@@ -1517,9 +1577,9 @@ _aboutMisskey:
contributors: "ผู้สนับสนุนหลัก"
allContributors: "ผู้มีส่วนร่วมทั้งหมด"
source: "ซอร์สโค้ด"
- translation: "รับแปลภาษา Misskey"
+ translation: "แปลภาษา Misskey"
donate: "บริจาคให้กับ Misskey"
- morePatrons: "เราขอขอบคุณสำหรับความช่วยเหลือจากผู้ช่วยอื่นๆ ที่ไม่ได้ระบุไว้ที่นี่นะ ขอขอบคุณ! 🥰"
+ morePatrons: " ขอบคุณทุกท่านที่ร่วมกันช่วยเหลือตลอดมานะคะ 🥰"
patrons: "สมาชิกพันธมิตร"
_displayOfSensitiveMedia:
respect: "ซ่อนสื่อทำเครื่องหมายบอกว่าละเอียดอ่อน"
@@ -1554,11 +1614,6 @@ _wordMute:
muteWords: "ปิดเสียงคำ"
muteWordsDescription: "คั่นด้วยช่องว่างสำหรับเงื่อนไข AND หรือด้วยการขึ้นบรรทัดใหม่สำหรับเงื่อนไข OR นะ"
muteWordsDescription2: "ล้อมรอบคีย์เวิร์ดด้วยเครื่องหมายทับเพื่อใช้นิพจน์ทั่วไป"
- softDescription: "ซ่อนโน้ตให้ตรงตามเงื่อนไขที่ตั้งไว้จากไทม์ไลน์"
- hardDescription: "ป้องกันไม่ให้โน้ตย่อที่ตรงตามเงื่อนไขที่ตั้งไว้ไม่ให้ถูกเพิ่มลงในไทม์ไลน์ นอกจากนี้ โน้ตเหล่านี้จะไม่ถูกเพิ่มลงในไทม์ไลน์แม้ว่าจะมีการเปลี่ยนแปลงเงื่อนไขยังไงก็ตาม"
- soft: "ซอฟ"
- hard: "ยาก"
- mutedNotes: "ปิดเสียงโน้ต"
_instanceMute:
instanceMuteDescription: "การดำเนินการนี้จะปิดเสียง\"โน้ต/รีโน้ต\"จากอินสแตนซ์ที่อยู่ในรายการ รวมถึงบันทึกของผู้ใช้ที่ตอบกลับผู้ใช้จากอินสแตนซ์ที่ปิดเสียง"
instanceMuteDescription2: "คั่นด้วยการขึ้นบรรทัดใหม่"
@@ -1622,9 +1677,6 @@ _theme:
infoFg: "ข้อความข้อมูล"
infoWarnBg: "คำเตือนพื้นหลัง"
infoWarnFg: "คำเตือนข้อความ"
- cwBg: "ปุ่ม CW พื้นหลัง"
- cwFg: "ปุ่ม CW ข้อความ"
- cwHoverBg: "ปุ่ม CW พื้นหลัง (โฮเวอร์)"
toastBg: "ประวัติการแจ้งเตือน"
toastFg: "ข้อความแจ้งเตือน"
buttonBg: "ปุ่มพื้นหลัง"
@@ -1642,8 +1694,6 @@ _sfx:
note: "หมายเหตุ"
noteMy: "โน้ตของตัวเอง"
notification: "การเเจ้งเตือน"
- chat: "แชท"
- chatBg: "แชท (พื้นหลัง)"
antenna: "เสาอากาศ"
channel: "การแจ้งเตือนช่อง"
_ago:
@@ -1662,27 +1712,17 @@ _time:
minute: "นาที"
hour: "ชั่วโมง"
day: "วัน"
-_timelineTutorial:
- title: "วิธีใช้งาน Misskey"
- step1_1: "นี่คือ \"ไทม์ไลน์\" \"โน้ต\" ทั้งหมดที่ส่งใน {name} จะแสดงรายการตามลำดับเวลาที่นี่นะ"
- step1_2: "อาจจะมีไทม์ไลน์ที่แตกต่างกันเล็กน้อยยกตัวอย่างเช่น \"ไทม์ไลน์หน้าแรก\" จะมีโน้ตของผู้ใช้ที่คุณติดตามและ \"ไทม์ไลน์ท้องถิ่น\" จะมีโน้ตจากผู้ใช้ทั้งหมดของ {name}"
- step2_1: "มาลองโพสต์โน้ตต่อไปกัน คุณสามารถทำได้โดยการกดปุ่มที่มีไอคอนดินสอ"
- step2_2: "ยังไงไหนลองเขียนแนะนำตัวเองหรือแค่ \"สวัสดี {name}!\" ถ้าคุณไม่รู้สึกเหมือนมัน?"
- step3_1: "เสร็จสิ้นการโพสต์โน้ตย่อแรกของคุณแล้วอย่างงั้นหรอ?"
- step3_2: "ไชโย! ตอนนี้โน้ตย่อแรกของคุณได้ปรากฏบนไทม์ไลน์ของคุณแล้วนะ"
- step4_1: "คุณสามารถเพิ่ม \"การตอบสนอง\" ในโน้ตได้"
- step4_2: "หากต้องการแนบการแสดงความรู้สึก ให้กดเครื่องหมาย \"+\" บนโน้ตแล้วเลือกอิโมจิที่คุณต้องการแสดงความรู้สึกที่ตนเองชอบได้เลย"
_2fa:
alreadyRegistered: "คุณได้ลงทะเบียนอุปกรณ์ยืนยันตัวตนแบบ 2 ชั้นแล้ว"
registerTOTP: "ลงทะเบียนแอพตัวตรวจสอบสิทธิ์"
- passwordToTOTP: "กรอกรหัสผ่าน"
step1: "ขั้นตอนแรก ติดตั้งแอปยืนยันตัวตน (เช่น {a} หรือ {b}) บนอุปกรณ์ของคุณ"
step2: "จากนั้นสแกนรหัส QR ที่แสดงบนหน้าจอนี้"
step2Click: "การคลิกที่รหัส QR นี้จะช่วยให้คุณนั้นสามารถลงทะเบียน 2FA กับคีย์ความปลอดภัยหรือแอปตรวจสอบความถูกต้องของโทรศัพท์ได้"
- step2Url: "คุณยังสามารถป้อนบน URL นี้หากคุณใช้โปรแกรมเดสก์ท็อป:"
+ step2Uri: "ป้อนใส่ URL ดังต่อไปนี้ถ้าหากคุณใช้โปรแกรมเดสก์ท็อป"
step3Title: "ป้อนรหัสยืนยัน"
step3: "ป้อนโทเค็นที่แอปของคุณให้มาเพื่อเสร็จสิ้นการตั้งค่า"
- step4: "นับจากนี้เป็นต้นไปการพยายามเข้าสู่ระบบในอนาคตนั้น อาจจะต้องขอโทเค็นในการเข้าสู่ระบบดังกล่าว\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}"
+ setupCompleted: "ตั้งค่าสำเร็จแล้ว"
+ step4: "นับจากนี้เป็นต้นไปการพยายามเข้าสู่ระบบในอนาคตนั้น อาจจะต้องขอโทเค็นในการเข้าสู่ระบบดังกล่าว"
securityKeyNotSupported: "เบราว์เซอร์ของคุณไม่รองรับคีย์ความปลอดภัยนะ"
registerTOTPBeforeKey: "กรุณาตั้งค่าแอปยืนยันตัวตนเพื่อลงทะเบียนรหัสความปลอดภัยหรือรหัสผ่าน"
securityKeyInfo: "นอกจากนี้การตรวจสอบความถูกต้องด้วยลายนิ้วมือหรือ PIN แล้ว คุณยังสามารถตั้งค่าการตรวจสอบสิทธิ์ผ่านคีย์ความปลอดภัยของฮาร์ดแวร์ที่รองรับ FIDO2 เพื่อเพิ่มความปลอดภัยให้กับบัญชีของคุณ"
@@ -1696,6 +1736,9 @@ _2fa:
renewTOTPConfirm: "วิธีการแบบนี้จะทําให้รหัสยืนยันจากแอพก่อนหน้าของคุณหยุดทํางานเลยนะ"
renewTOTPOk: "ตั้งค่าคอนฟิกใหม่"
renewTOTPCancel: "ไม่เป็นไร"
+ backupCodes: "รหัสสำรองข้อมูล"
+ backupCodeUsedWarning: "มีการใช้รหัสสำรองแล้ว โปรดกรุณากำหนดค่าการตรวจสอบสิทธิ์แบบสองปัจจัยโดยเร็วที่สุดถ้าหากคุณยังไม่สามารถใช้งานได้อีก"
+ backupCodesExhaustedWarning: "รหัสสำรองทั้งหมดถูกใช้แล้ว ถ้าหากคุณยังสูญเสียการเข้าถึงแอปการตรวจสอบสิทธิ์แบบสองปัจจัยคุณจะยังไม่สามารถเข้าถึงบัญชีนี้ได้ กรุณากำหนดค่าการรับรองความถูกต้องด้วยการยืนยันสองชั้น"
_permissions:
"read:account": "ดูข้อมูลบัญชีของคุณ"
"write:account": "แก้ไขข้อมูลบัญชีของคุณ"
@@ -1729,6 +1772,10 @@ _permissions:
"write:gallery": "แก้ไขแกลเลอรี่ของคุณ"
"read:gallery-likes": "ดูรายการโพสต์ในแกลเลอรีที่ชอบของคุณ"
"write:gallery-likes": "แก้ไขรายการโพสต์ในแกลเลอรีที่ชอบของคุณ"
+ "read:flash": "วิว เพลย์"
+ "write:flash": "แก้ไขเพลย์"
+ "read:flash-likes": "ดูรายชื่อของไลค์ เพลย์"
+ "write:flash-likes": "แก้ไขรายชื่อของไลค์ เพลย์"
_auth:
shareAccessTitle: "การให้สิทธิ์แอปพลิเคชัน"
shareAccess: "คุณต้องการอนุญาตให้ \"{name}\" เข้าถึงบัญชีนี้เลยมั้ย?"
@@ -1744,6 +1791,7 @@ _antennaSources:
homeTimeline: "โน้ตจากผู้ใช้ที่ติดตาม"
users: "โน้ตจากผู้ใช้ที่เฉพาะเจาะจง"
userList: "โน้ตจากรายชื่อผู้ใช้ที่ระบุ"
+ userBlacklist: "โน้ตทั้งหมดยกเว้นโน้ตของผู้ใช้ที่ต้องระบุเจาะจงตั้งแต่หนึ่งรายขึ้นไป"
_weekday:
sunday: "วันอาทิตย์"
monday: "วันจันทร์"
@@ -1767,7 +1815,7 @@ _widgets:
photos: "รูปภาพ"
digitalClock: "นาฬิกาดิจิตอล"
unixClock: "นาฬิกา UNIX"
- federation: "สหพันธ์"
+ federation: "Fediration"
instanceCloud: "อินสแตนซ์คลาวด์"
postForm: "แบบฟอร์มการโพสต์"
slideshow: "แสดงภาพนิ่ง"
@@ -1777,7 +1825,7 @@ _widgets:
serverMetric: "ตัวชี้วัดเซิร์ฟเวอร์"
aiscript: "AiScript คอนโซล"
aiscriptApp: "AiScript แอพ"
- aichan: "เอไอ"
+ aichan: "ไอ"
userList: "รายชื่อผู้ใช้"
_userList:
chooseList: "เลือกรายการ"
@@ -1843,6 +1891,7 @@ _profile:
metadataContent: "เนื้อหา"
changeAvatar: "เปลี่ยนอวาตาร์"
changeBanner: "เปลี่ยนแบนเนอร์"
+ verifiedLinkDescription: "โดยการป้อน URL ที่มีลิงก์ไปยังโปรไฟล์ของคุณตรงนี้ ส่วนไอคอนการยืนยันความเป็นเจ้าของนั้นก็สามารถแสดงถัดจากฟิลด์ได้นะ"
_exportOrImport:
allNotes: "โน้ตทั้งหมด"
favoritedNotes: "บันทึกที่ชื่นชอบ"
@@ -1852,6 +1901,7 @@ _exportOrImport:
userLists: "รายการ"
excludeMutingUsers: "ยกเว้นผู้ใช้ที่ปิดเสียง"
excludeInactiveUsers: "ยกเว้นผู้ใช้ที่ไม่ได้ใช้งาน"
+ withReplies: "รวมการตอบกลับจากผู้ใช้ที่นำเข้าไว้ในไทม์ไลน์"
_charts:
federation: "สหพันธ์"
apRequest: "คำขอ"
@@ -1961,11 +2011,17 @@ _notification:
youReceivedFollowRequest: "คุณมีคำขอติดตามใหม่น่ะ"
yourFollowRequestAccepted: "คำขอติดตามของคุณได้รับการยอมรับแล้วน่ะ"
pollEnded: "โพลสำรวจความคิดเห็นผลลัพธ์มีพร้อมใช้งาน"
+ newNote: "โพสต์ใหม่"
unreadAntennaNote: "เสาอากาศ {name}"
emptyPushNotificationMessage: "การแจ้งเตือนแบบพุชได้รับการอัพเดทแล้ว"
achievementEarned: "รับความสำเร็จ"
+ testNotification: "ทดสอบการแจ้งเตือน"
+ checkNotificationBehavior: "ตรวจสอบลักษณะที่ปรากฏการแจ้งเตือน"
+ sendTestNotification: "ส่งทดสอบการแจ้งเตือน"
+ notificationWillBeDisplayedLikeThis: "การแจ้งเตือนมีลักษณะแบบนี้"
_types:
all: "ทั้งหมด"
+ note: "โน้ตใหม่"
follow: "กำลังติดตาม"
mention: "กล่าวถึง"
reply: "ตอบกลับ"
@@ -1999,6 +2055,8 @@ _deck:
introduction2: "คลิกที่เครื่องหมาย + ทางขวาของหน้าจอเพื่อเพิ่มคอลัมน์ใหม่ทุกครั้งที่คุณต้องการ"
widgetsIntroduction: "กรุณาเลือก \"แก้ไขวิดเจ็ต\" ในเมนูคอลัมน์และเพิ่มวิดเจ็ต"
useSimpleUiForNonRootPages: "แสดง UI ของ Root Page อย่างง่าย "
+ usedAsMinWidthWhenFlexible: "ความกว้างขั้นต่ำนั้นจะถูกใช้งานสำหรับสิ่งนี้เมื่อเปิดใช้งานตัวเลือก \"ปรับความกว้างอัตโนมัติ\" หากเลือกเปิดใช้งานแล้ว"
+ flexible: "ปรับความกว้างอัตโนมัติ"
_columns:
main: "หลัก"
widgets: "วิดเจ็ต"
@@ -2033,3 +2091,42 @@ _webhookSettings:
renote: "รีโน้ตแล้วเมื่อ"
reaction: "เมื่อได้รับรีแอคชั่น"
mention: "เมื่อกำลังถูกกล่าวถึง"
+_moderationLogTypes:
+ createRole: "สร้างบทบาทแล้ว"
+ deleteRole: "ลบบทบาทแล้ว"
+ updateRole: "อัปเดตบทบาทแล้ว"
+ assignRole: "ได้รับมอบหมายบทบาท"
+ unassignRole: "ถอดออกจากบทบาทแล้ว"
+ suspend: "ถูกระงับ"
+ unsuspend: "เลิกถูกระงับ"
+ addCustomEmoji: "เพิ่มอีโมจิที่กำหนดเองแล้ว"
+ updateCustomEmoji: "อัปเดตอีโมจิที่กำหนดเองแล้ว"
+ deleteCustomEmoji: "ลบอีโมจิที่กำหนดเองออกแล้ว"
+ updateServerSettings: "อัปเดตการตั้งค่าเซิร์ฟเวอร์แล้ว"
+ updateUserNote: "อัปเดตโน้ตการกลั่นกรองแล้ว"
+ deleteDriveFile: "ลบไฟล์ออกแล้ว"
+ deleteNote: "ลบโน้ตออกแล้ว"
+ createGlobalAnnouncement: "สร้างประกาศทั่วโลกแล้ว"
+ createUserAnnouncement: "สร้างประกาศผู้ใช้แล้ว"
+ updateGlobalAnnouncement: "อัปเดตประกาศทั่วโลกแล้ว"
+ updateUserAnnouncement: "อัปเดตประกาศผู้ใช้แล้ว"
+ deleteGlobalAnnouncement: "ลบประกาศทั่วโลกออกแล้ว"
+ deleteUserAnnouncement: "ลบประกาศผู้ใช้ออกแล้ว"
+ resetPassword: "รีเซ็ตรหัสผ่าน"
+ suspendRemoteInstance: "อินสแตนซ์ระยะไกลถูกระงับ"
+ unsuspendRemoteInstance: "อินสแตนซ์ระยะไกลเลิกการระงับ"
+ markSensitiveDriveFile: "ทำเครื่องหมายไฟล์บอกว่าละเอียดอ่อน"
+ unmarkSensitiveDriveFile: "ยกเลิกทำเครื่องหมายไฟล์ว่าละเอียดอ่อน"
+ resolveAbuseReport: "รายงานได้รับการแก้ไขแล้ว"
+ createInvitation: "สร้างคำเชิญ"
+ createAd: "สร้างโฆษณาแล้ว"
+ deleteAd: "ลบโฆษณาออกแล้ว"
+ updateAd: "อัปเดตโฆษณาแล้ว"
+_fileViewer:
+ title: "รายละเอียดไฟล์"
+ type: "ประเภทไฟล์"
+ size: "ขนาดไฟล์"
+ url: "URL"
+ uploadedAt: "วันที่เข้าร่วม"
+ attachedNotes: "โน้ตที่แนบมาด้วย"
+ thisPageCanBeSeenFromTheAuthor: "หน้าเพจนี้จะสามารถปรากฏได้โดยผู้ใช้ที่อัปโหลดไฟล์นี้เท่านั้น"
diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml
index 42cc7da3f8..3dd7a5b797 100644
--- a/locales/tr-TR.yml
+++ b/locales/tr-TR.yml
@@ -9,6 +9,7 @@ notifications: "Bildirim"
username: "Kullanıcı Adı"
password: "Şifre"
forgotPassword: "şifremi unuttum"
+fetchingAsApObject: "從聯邦宇宙取得中..."
ok: "TAMAM"
gotIt: "Anladım"
cancel: "İptal"
@@ -44,6 +45,7 @@ pin: "Sabitlenmiş"
unpin: "Sabitlemeyi kaldır"
copyContent: "İçeriği kopyala"
copyLink: "Bağlantıyı Kopyala"
+copyLinkRenote: "Turkish"
delete: "Sil"
deleteAndEdit: "Sil ve yeniden düzenle"
deleteAndEditConfirm: "Bu notu silip yeniden düzenlemek istiyor musunuz? Bu nota ilişkin tüm Tepkiler, Yeniden Notlar ve Yanıtlar da silinecektir."
@@ -155,6 +157,7 @@ addEmoji: "Emoji ekle"
settingGuide: "Önerilen ayarlar"
cacheRemoteFiles: "Uzak dosyalar ön belleğe alınsın"
cacheRemoteFilesDescription: "Bu ayar açık olduğunda diğer sitelerin dosyaları doğrudan uzak sunucudan yüklenecektir. Bu ayarı kapatmak depolama kullanımını azaltacak ama küçük resimler oluşturulmadığından trafiği arttıracaktır."
+youCanCleanRemoteFilesCache: ""
cacheRemoteSensitiveFiles: "Hassas uzak dosyalar ön belleğe alınsın"
cacheRemoteSensitiveFilesDescription: "Bu ayar kapalı olduğunda hassas uzak dosyalar ön belleğe alınmadan doğrudan uzak sunucudan yüklenecektir."
flagAsBot: "Bot olarak işaretle"
@@ -192,6 +195,7 @@ perHour: "Saatlik"
perDay: "Günlük"
stopActivityDelivery: "Durum güncellemelerini gönderme"
blockThisInstance: "Bu sunucuyu engelle"
+silenceThisInstance: ""
operations: "İşlemler"
software: "Yazılımlar"
version: "Sürüm"
@@ -211,6 +215,8 @@ clearCachedFiles: "Ön belleği temizle"
clearCachedFilesConfirm: "Ön belleğe alınmış tüm uzak sunucu dosyaları silinsin mi?"
blockedInstances: "Engellenen sunucular"
blockedInstancesDescription: "Engellemek istediğiniz sunucuların alan adlarını satır sonlarıyla ayırarak yazın. Yazılan sunucular bu sunucuyla iletişime geçemeyecek."
+silencedInstances: "Turkısh"
+silencedInstancesDescription: ""
muteAndBlock: "Susturma ve Engelleme"
mutedUsers: "Susturulan kullanıcılar"
blockedUsers: "Engellenen kullanıcılar"
@@ -259,6 +265,7 @@ messaging: "Mesajlar"
upload: "Yükle"
keepOriginalUploading: "Orijinal görseli koru"
keepOriginalUploadingDescription: "Orijinal olarak yüklenen görüntüyü olduğu gibi kaydeder. Kapatılırsa, yükleme sırasında web'de görüntülenecek bir sürüm oluşturulur."
+fromDrive: "Drive Dosyasından"
fromUrl: "Bağlantıdan"
uploadFromUrl: "Bağlantıdan yükle"
uploadFromUrlDescription: "Yüklemek istediğiniz dosyanın bağlantısı"
@@ -305,12 +312,42 @@ renameFolder: "Klasörü Yeniden Adlandır"
deleteFolder: "Klasörü sil"
addFile: "Dosya ekle"
emptyDrive: "Sürücü boş"
+emptyFolder: "Bu klasör boş"
+unableToDelete: "Silme mümkün değil"
+inputNewFileName: "Yeni dosya ismini girin"
+inputNewDescription: "Yeni bir başlık gir"
+inputNewFolderName: "Yeni klasör ismini girin"
+circularReferenceFolder: "Hedef klasör taşınan klasörün bir alt klasörü."
hasChildFilesOrFolders: "Klasör boş olmadığından silinemiyor"
+copyUrl: "URL'yi kopyala"
+rename: "Yeniden adlandır"
+avatar: "Avatar"
+banner: "Banner"
+displayOfSensitiveMedia: "Hassas içerik gösterimi"
+whenServerDisconnected: "Sunucu bağlantısı kesildiğinde"
+disconnectedFromServer: "Sunucu bağlantısı koptu"
+reload: "Yenile"
doNothing: "Bir şey yapma"
reloadConfirm: "Zaman akışı yenilensin mi?"
+watch: "İzle"
+unwatch: "İzlemeyi bırak"
+accept: "Kabul et"
+reject: "Reddet"
+normal: "Normal"
+instanceName: "Sunucu ismi"
+instanceDescription: "Sunucu açıklaması"
maintainerName: "Yönetici ismi"
+maintainerEmail: "Yöneticinin e-postası"
+tosUrl: "Hizmet Koşulları Bağlantısı"
+thisYear: "Bu yıl"
+thisMonth: "Bu ay"
+today: "Bugün"
monthX: "{month} ay"
+pages: "Sayfalar"
+integration: "Entegrasyon"
enableRegistration: "Kayıtlara izin ver"
+basicInfo: "Temel bilgiler"
+pinnedUsers: "Sabitlenmiş kullanıcılar"
pinnedNotes: "Sabitlenen"
manageAntennas: "Anten ayarları"
userList: "Listeler"
@@ -322,6 +359,7 @@ smtpHost: "Sağlayıcı"
smtpUser: "Kullanıcı Adı"
smtpPass: "Şifre"
notificationSetting: "Bildirim ayarları"
+instanceTicker: "Notların sunucu bilgileri"
noCrawleDescription: "Arama motorlarından profilinde, notlarında, sayfalarında vb. dolaşılmamasını ve dizine eklememesini talep et."
clearCache: "Ön belleği temizle"
onlineUsersCount: "{n} kullanıcı çevrim içi"
@@ -338,6 +376,9 @@ pushNotificationNotSupported: "Push bildirimleri sunucu veya tarayıcı tarafın
noRole: "Rol bulunamadı"
color: "Renk"
addMemo: "Kısa not ekle"
+icon: "Avatar"
+replies: "yanıt"
+renotes: "vazgeçme"
_accountDelete:
started: "Silme işlemi başlatıldı"
_email:
@@ -351,7 +392,6 @@ _theme:
_sfx:
note: "notlar"
notification: "Bildirim"
- chat: "Mesajlar"
_2fa:
renewTOTPCancel: "Hayır, teşekkürler"
_permissions:
@@ -413,3 +453,6 @@ _deck:
tl: "Zaman çizelgesi"
list: "Listeler"
mentions: "Bahsetmeler"
+_moderationLogTypes:
+ suspend: "askıya al"
+ resetPassword: "Şifre sıfırlama"
diff --git a/locales/ug-CN.yml b/locales/ug-CN.yml
index 65ef841259..e48f64511c 100644
--- a/locales/ug-CN.yml
+++ b/locales/ug-CN.yml
@@ -1,4 +1,19 @@
---
_lang_: "ياپونچە"
+headlineMisskey: "خاتىرە ئارقىلىق ئۇلانغان تور"
+monthAndDay: "{day}-{month}"
search: "ئىزدەش"
+ok: "ماقۇل"
+noThankYou: "ئۇنى توختىتىڭ"
+profile: "profile"
+login: "كىرىش"
+loggingIn: "كىرىش"
+pin: "pinned"
+delete: "ئۆچۈرۈش"
+pinned: "pinned"
+remove: "ئۆچۈرۈش"
searchByGoogle: "ئىزدەش"
+_2fa:
+ renewTOTPCancel: "ئۇنى توختىتىڭ"
+_widgets:
+ profile: "profile"
diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml
index a57a463689..016f41a8d6 100644
--- a/locales/uk-UA.yml
+++ b/locales/uk-UA.yml
@@ -20,6 +20,7 @@ noNotes: "Немає нотаток"
noNotifications: "Немає сповіщень"
instance: "Інстанс"
settings: "Налаштування"
+notificationSettings: "Параметри сповіщень"
basicSettings: "Основні налаштування"
otherSettings: "Інші налаштування"
openInWindow: "Відкрити у вікні"
@@ -48,9 +49,12 @@ delete: "Видалити"
deleteAndEdit: "Видалити й редагувати"
deleteAndEditConfirm: "Ви впевнені, що хочете видалити цю нотатку та відредагувати її? Ви втратите всі реакції, поширення та відповіді на неї."
addToList: "Додати до списку"
+addToAntenna: "Додати в антени"
sendMessage: "Надіслати повідомлення"
copyRSS: "Скопіювати RSS"
copyUsername: "Скопіювати ім’я користувача"
+copyUserId: "Копіювати ID користувача"
+copyNoteId: "блокнот ID користувача"
searchUser: "Пошук користувачів"
reply: "Відповісти"
loadMore: "Показати більше"
@@ -334,7 +338,6 @@ invite: "Запросити"
driveCapacityPerLocalAccount: "Об'єм диска на одного локального користувача"
driveCapacityPerRemoteAccount: "Об'єм диска на одного віддаленого користувача"
inMb: "В мегабайтах"
-iconUrl: "URL аватара"
bannerUrl: "URL банера"
backgroundImageUrl: "URL-адреса фонового зображення"
basicInfo: "Основна інформація"
@@ -644,6 +647,7 @@ createNewClip: "Створити нотатку"
unclip: "Незакріплений"
confirmToUnclipAlreadyClippedNote: "Ця нотатка вже включена до кліпу \"{name}\". Ви хочете виключити нотатку з цього кліпу?"
public: "Публічний"
+private: "Приватне"
i18nInfo: "Misskey перекладається на різні мови волонтерами. Ви можете допомогти: {link}"
manageAccessTokens: "Керування токенами доступу"
accountInfo: "Інформація про акаунт"
@@ -900,6 +904,10 @@ exploreOtherServers: "Знайти інший сервер"
letsLookAtTimeline: "Перегляд історії"
horizontal: "Збоку"
youFollowing: "Підписки"
+icon: "Аватар"
+replies: "Відповісти"
+renotes: "Поширити"
+flip: "Перевернути"
_achievements:
earnedAt: "Відкрито"
_types:
@@ -1173,6 +1181,7 @@ _plugin:
install: "Встановити плагін"
installWarn: "Будь ласка, не встановлюйте плагінів, яким ви не довіряєте."
manage: "Керування плагінами"
+ viewSource: "Переглянути вихідний код"
_preferencesBackups:
list: "Створені бекапи"
saveNew: "Зберегти як новий"
@@ -1225,11 +1234,6 @@ _wordMute:
muteWords: "Заглушені слова"
muteWordsDescription: "Розділення ключових слів пробілами для \"І\" або з нової лінійки для \"АБО\""
muteWordsDescription2: "Для використання RegEx, ключові слова потрібно вписати поміж слешів \"/\"."
- softDescription: "Приховати записи які відповідають критеріям зі стрічки подій."
- hardDescription: "Приховати записи які відповідають критеріям зі стрічки подій. Також приховані записи не будуть додані до стрічки подій навіть якщо критерії буде змінено."
- soft: "М'яко"
- hard: "Жорстко"
- mutedNotes: "Заблоковані нотатки"
_instanceMute:
instanceMuteDescription2: "Розділяйте новими рядками"
title: "Приховує нотатки з перелічених інстансів."
@@ -1287,9 +1291,6 @@ _theme:
infoFg: "Текст інформації"
infoWarnBg: "Фон попередження"
infoWarnFg: "Текст попередження"
- cwBg: "Фон чутливого змісту"
- cwFg: "Текст чутливого змісту"
- cwHoverBg: "Фон чутливого змісту (при наведенні)"
toastBg: "Фон повідомлення"
toastFg: "Текст повідомлення"
buttonBg: "Фон кнопки"
@@ -1307,8 +1308,6 @@ _sfx:
note: "Нотатки"
noteMy: "Мої нотатки"
notification: "Сповіщення"
- chat: "Чати"
- chatBg: "Чати (фон)"
antenna: "Прийом антени"
channel: "Повідомлення каналу"
_ago:
@@ -1331,9 +1330,8 @@ _2fa:
alreadyRegistered: "Двофакторна автентифікація вже налаштована."
step1: "Спершу встановіть на свій пристрій програму автентифікації (наприклад {a} або {b})."
step2: "Потім відскануйте QR-код, який відображається на цьому екрані."
- step2Url: "Ви також можете ввести цю URL-адресу, якщо використовуєте програму для ПК:"
step3: "Щоб завершити налаштування, введіть токен, наданий вашою програмою."
- step4: "Відтепер будь-які майбутні спроби входу вимагатимуть такого токена.\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}"
+ step4: "Відтепер будь-які майбутні спроби входу вимагатимуть такого токена."
renewTOTPCancel: "Не зараз"
_permissions:
"read:account": "Переглядати дані профілю"
@@ -1613,3 +1611,6 @@ _deck:
_webhookSettings:
name: "Ім'я"
active: "Увімкнено"
+_moderationLogTypes:
+ suspend: "Призупинити"
+ resetPassword: "Скинути пароль"
diff --git a/locales/uz-UZ.yml b/locales/uz-UZ.yml
index 6eb5af78cf..e9b3915f07 100644
--- a/locales/uz-UZ.yml
+++ b/locales/uz-UZ.yml
@@ -43,8 +43,8 @@ alreadyFavorited: "allaqachon sevimlilar orasida"
cantFavorite: "sevimlilarga qo'shib bo'lmadi"
pin: "Profilga qadab qo'yish"
unpin: "Profildan olib tashlash"
-copyContent: "kontentni nusxalash"
-copyLink: "Havolani nuxalash"
+copyContent: "Tarkibini nusxalash"
+copyLink: "Havolani nusxalash"
delete: "O'chirib tashlash"
deleteAndEdit: "O'chirish va tahrirlash"
deleteAndEditConfirm: "O'chirib, tahrirlamoqchiligingizga ishonchingiz komilmi? Siz bu qaydga tegishli barcha reaktsiyalar va javoblarni yo'qotasiz."
@@ -156,6 +156,7 @@ addEmoji: "Emoji qo'shish"
settingGuide: "Tavsiya qilingan sozlamalar"
cacheRemoteFiles: "Tashqi fayllarni keshlash"
cacheRemoteFilesDescription: "Ushbu sozlama o'chirilgan bo'lsa tashqi fayllar bevosita tashqi serverdan yuklanadi. Buni o'chirish ombor ishlatilishini kamaytiradi, lekin traffikni ko'paytiradi, chunki eskizlar generatsiya qilinmaydi."
+youCanCleanRemoteFilesCache: "Fayl menejeridagi 🗑️ tugmasi yordamida barcha keshlarni oʻchirib tashlashingiz mumkin."
cacheRemoteSensitiveFiles: "Tashqi fayllarni keshlash"
cacheRemoteSensitiveFilesDescription: "Bu sozlama oʻchiq boʻlsa, \"barcha ko'rishi mumkin bo'lmagan\" fayllar keshlashsiz toʻgʻridan-toʻgʻri masofaviy serverdan yuklanadi."
flagAsBot: "Ushbu akkauntni bot sifatida belgilash"
@@ -246,6 +247,7 @@ newPasswordRetype: "Yangi parolni boshqatdan tering"
attachFile: "Fayl biriktirish"
more: "Ko'proq!"
featured: "ta'kidlash"
+usernameOrUserId: "Foydalanuvchi nomi yoki identifikatori"
noSuchUser: "Foydalanuvchi topilmadi"
lookup: "So'rov"
announcements: "Bildirishnomalar"
@@ -259,7 +261,10 @@ saved: "Saqlandi"
messaging: "Suhbat"
upload: "Yuklash"
keepOriginalUploading: "Asl rasmni saqlang"
+keepOriginalUploadingDescription: "Rasmlarni yuklashda asl nusxasini saqlaydi. Agar o'chirilgan bo'lsa, brauzer yuklangandan keyin nashr qilish uchun rasm yaratadi."
+fromDrive: "Drive orqali"
fromUrl: "URL dan"
+uploadFromUrl: "URL orqali yuklash"
uploadFromUrlDescription: "Yuklamoqchi bo'lgan faylingizga havola"
uploadFromUrlRequested: "yuklab olish so'ralgan"
uploadFromUrlMayTakeTime: "Yuklash tugallanishi uchun biroz vaqt ketishi mumkin."
@@ -275,6 +280,7 @@ basicNotesBeforeCreateAccount: "Muhim qaydlar"
termsOfService: "Foydalanish shartlari"
start: "Boshlash"
home: "Bosh sahifa"
+remoteUserCaution: "Bu foydalanuvchi uzoqda bo'lganligi sababli, ko'rsatilgan ma'lumotlar to'liq bo'lmasligi mumkin."
activity: "Faollik"
images: "Rasmlar"
image: "Rasm"
@@ -308,11 +314,13 @@ unableToDelete: "O'chirilmadi"
inputNewFileName: "Yangi fayl nomini kiriting"
inputNewDescription: "Iltimos, yangi sarlavha kiriting."
inputNewFolderName: "Yangi papka nomini kiriting"
+circularReferenceFolder: "Belgilangan papka siz ko'chirmoqchi bo'lgan jildning pastki jildidir."
hasChildFilesOrFolders: "Bu papka boʻsh emas va uni oʻchirib boʻlmaydi."
copyUrl: "Bog'lamadan nusxa olish"
rename: "Qayta nomlash"
avatar: "Avatar"
banner: "Banner"
+displayOfSensitiveMedia: "Nozik kontentni ko'rish"
whenServerDisconnected: "server bilan aloqa uzilganda"
disconnectedFromServer: "Server bilan ulanish uzulib qoldi"
reload: "Yangilash"
@@ -340,15 +348,20 @@ connectService: "Ulash"
disconnectService: "Uzish"
enableLocalTimeline: "Mahalliy vaqt mintaqasini yoqing"
enableGlobalTimeline: "Global vaqt mintaqasini yoqing"
+disablingTimelinesInfo: "Administratorlar va Moderatorlar har doim barcha vaqt jadvallariga kirish huquqiga ega bo'ladilar, hatto ular yoqilmagan bo'lsa ham."
registration: "Ro'yxatdan o'tish"
enableRegistration: "Ro'yxatdan o'tishni yoqing"
invite: "Taklif qilish"
+driveCapacityPerLocalAccount: "Har bir mahalliy foydalanuvchi uchun disk maydoni"
+driveCapacityPerRemoteAccount: "Har bir masofaviy foydalanuvchi uchun disk maydoni"
inMb: "Megabaytlarda"
-iconUrl: "Ikonkaning URL manzili (masalan: favicon)"
+bannerUrl: "Banner URLi"
backgroundImageUrl: "Fon rasmi URL manzili"
basicInfo: "Asosiy ma'lumot"
pinnedUsers: "Qadalgan foydalanuvchilar"
+pinnedUsersDescription: "Har bir qatorga bitta foydalanuvchi nomini kiriting. Bu yerda sanab oʻtilgan foydalanuvchilar “Oʻrganish” yorligʻiga bogʻlanadi."
pinnedPages: "Qadalgan Sahifalar"
+pinnedClipId: "Qadalgan xabar IDsi"
pinnedNotes: "Qadalgan qayd"
hcaptcha: "hCaptcha"
enableHcaptcha: "hCaptchani yoqish"
@@ -358,19 +371,37 @@ recaptcha: "reCAPTCHA"
enableRecaptcha: "reCAPTCHA ni yoqish"
recaptchaSiteKey: "Sayt kaliti"
recaptchaSecretKey: "Maxfiy kalit"
+turnstile: "Turniket"
+enableTurnstile: "Turniketni yoqish"
turnstileSiteKey: "Sayt kaliti"
turnstileSecretKey: "Maxfiy kalit"
+avoidMultiCaptchaConfirm: "\nBir nechta Captcha tizimlaridan foydalanish ular o'rtasida noqulaylik olib kelishi mumkin. Hozirda faol bo'lgan boshqa Captcha tizimlarini o'chirib qo'ymoqchimisiz? Agar siz ularning faol bo'lishini istasangiz, bekor qilish tugmasini bosing."
antennas: "Antennalar"
manageAntennas: "Antennalarni boshqarish"
name: "Ism"
antennaSource: "Antenna manbai"
antennaKeywords: "Kalit so'zni qabul qilish"
+antennaExcludeKeywords: "Istisno qilingan kalit so'zlar"
+antennaKeywordsDescription: "VA sharti uchun bo'shliqlar bilan yoki YOKI sharti uchun qator uzilishlari bilan ajrating."
notifyAntenna: "Yangi qaydlar haqida menga xabar bering"
+withFileAntenna: "Faqatgina fayli bor qaydlar"
+enableServiceworker: "Bildirish nomalarni olish"
+antennaUsersDescription: "Har bir foydalunvchi nomini alohida qatorga yozing"
+caseSensitive: "Katta-kichik harfni farqlash"
+withReplies: "Javob yo'llash"
connectedTo: "Quyidagi akkountlarga ulangan"
-silence: "Sukunat"
+notesAndReplies: "Qaydlar va javoblar"
+withFiles: "Fayllar"
+silence: "Jim qilish"
+silenceConfirm: "Rostdan ham ushbu foydalanuvchini jim qilmoqchimisiz?"
+unsilence: "Jim qilishni bekor qilish"
+unsilenceConfirm: "Rostdan ham ushbu foydalanuvchini ovozsiz \nqilmoqchimisiz?"
popularUsers: "Mashhur foydalanuvchilar."
+recentlyUpdatedUsers: "Yaqinda ro'yxatdan o'tgan foydalanuvchilar"
recentlyRegisteredUsers: "Yaqinda ro'yxatdan o'tgan foydalanuvchilar"
+recentlyDiscoveredUsers: "Yangi foydalanuvchilar"
exploreUsersCount: "{count} ta foydalanuvchi bor"
+exploreFediverse: "Fediversni ko'rib chiqing"
popularTags: "Ommabop teglar"
userList: "Ro'yxatlar"
about: "Haqida"
@@ -381,12 +412,25 @@ token: "Tasdiqlash"
totp: "Autentifikatsiya ilovasi"
totpDescription: "Bir martalik parollarni kiritish uchun autentifikatsiya ilovasidan foydalaning"
moderator: "Moderator"
+moderation: "Moderatsiya"
nUsersMentioned: "{n} tomonidan chop etilgan"
+securityKeyAndPasskey: "Xavfsizlik kaliti va maxfiy so'z"
+securityKey: "Xavfsizlik kaliti"
+lastUsed: "Oxirgi marta foydalanilgan"
+lastUsedAt: "Oxirgi marta {t} da foydalanilgan"
+unregister: "ro'yxatdan chiqarish"
+passwordLessLogin: "Parolsiz kirshni sozlash"
+passwordLessLoginDescription: "Parolsiz kirish"
resetPassword: "Parolni tiklash"
+newPasswordIs: "Yangi parolingiz {password}"
+reduceUiAnimation: "Interfeysdagi animatsiyani kamaytirish"
share: "Yuborish"
notFound: "Topilmadi"
+notFoundDescription: "Ushbu sahifa topilmadi"
uploadFolder: "Jildni yuklash"
cacheClear: "Keshni tozalash"
+markAsReadAllNotifications: "Bildirishnomalarni o'qilgan deb belgilash"
+markAsReadAllUnreadNotes: "Barch xabarlarni oq'ilgan deb belgilash"
markAsReadAllTalkMessages: "Barcha suhbatlarni o'qilgan deb belgilang"
help: "Yordam"
inputMessageHere: "Xabar kiriting"
@@ -399,6 +443,11 @@ text: "Matn"
enable: "Yoqish"
next: "Keyingisi"
retype: "Qayta kiriting"
+noteOf: "{user} tomonidan joylandi\n"
+quoteAttached: "Iqtibos"
+quoteQuestion: "Iqtibos sifatida qo'shilsinmi?"
+noMessagesYet: "Bu yerda xabarlar yo'q"
+newMessageExists: "Yangi xabarlar bor"
onlyOneFileCanBeAttached: "Faqat bitta faylni biriktirish mumkin"
signinRequired: "Davom etishdan oldin ro'yhatdan o'tishingiz yoki tizimga kirishingiz kerak"
invitations: "Taklif qilish"
@@ -418,10 +467,16 @@ signinWith: "{x} bilan tizimga kirish"
signinFailed: "Tizimga kirishda xatolik yuz berdi. Iltimos, foydalanuvchi nomingiz va parolingizni tekshiring."
or: "yoki"
language: "til"
+uiLanguage: "Interfeys tili"
aboutX: "{x} haqida"
+emojiStyle: "Emoji ko'rinishi"
+native: "Mahalliy"
+disableDrawer: "Slayd menyusidan foydalanmang"
showNoteActionsOnlyHover: "Eslatma amallarini faqat sichqonchani olib borganda ko‘rsatish"
noHistory: "Tarix yo'q"
signinHistory: "kirish tarixi"
+enableAdvancedMfm: "MFMni faollashtirish"
+doing: "Bajarilmoqda..."
category: "kategoriya"
tags: "teg"
docSource: "Ushbu hujjatning manbasi"
@@ -431,8 +486,10 @@ regenerate: "regeneratsiya"
fontSize: "shrift hajmi"
limitTo: "{x} gacha"
noFollowRequests: "obuna uchun so'rov yo'q"
+openImageInNewTab: "Rasmni boshqa oynada ochish"
dashboard: "Boshqaruv paneli"
local: "Mahalliy"
+remote: "masofaviy"
total: "Jami"
weekOverWeekChanges: "Oxirgi haftadagi o'zgarishlar"
dayOverDayChanges: "Kecha bo'lgan o'zgarishlar"
@@ -444,18 +501,56 @@ promote: "targ'ib qilish"
numberOfDays: "kunlar soni"
hideThisNote: "bu eslatmani yashiring"
showFeaturedNotesInTimeline: "Tanlangan qaydlarni Timelineda ko'rsatish"
+objectStorage: "ob'ektni saqlash"
+useObjectStorage: "Ob'ektni saqlashdan foydalaning"
objectStorageBaseUrl: "Asosiy URL"
+objectStorageBaseUrlDesc: "Malumot va foydalanish uchun URL. Agar siz CDN yoki proksi-serverdan foydalanayotgan bo'lsangiz, URL manzili, S3: 'https://.s3.amazonaws.com', GCS va boshqalar: 'https://storage.googleapis.com/'."
+objectStorageBucket: "Bucket"
+objectStorageBucketDesc: "Iltimos, foydalaniladigan xizmatning bucket nomini belgilang."
+objectStoragePrefix: "Prefix"
+objectStorageEndpoint: "Endpoint"
objectStorageRegion: "Mintaqa"
+objectStorageRegionDesc: "'xx-east-1' kabi mintaqani belgilang. Agar xizmatingizda mintaqa tushunchasi bo'lmasa, `us-east-1` dan foydalaning. AWS konfiguratsiya fayllari yoki muhit oʻzgaruvchilariga havola qilishda boʻsh qoldiring."
objectStorageUseSSL: "SSL dan foydalaning"
+objectStorageUseSSLDesc: "API ulanishlari uchun https dan foydalanmasangiz, belgini olib tashlang"
+objectStorageUseProxy: "Proksi-serverdan foydalaning"
+objectStorageUseProxyDesc: "Proksi-serverdan foydalanishni xohlamasangiz, uni o'chiring"
+objectStorageSetPublicRead: "Yuklashda \"public-read\" ni o'rnating"
+serverLogs: "Server protokoli"
+deleteAll: "Hammasini o'chirib tashlash"
+showFixedPostForm: "Taqdim etish shaklini vaqt jadvalining yuqori qismida ko'rsating"
+newNoteRecived: "Yangi qaydlar mavjud emas"
sounds: "Tovushlar"
sound: "ovoz"
+listen: "Eshitish"
none: "Hechnima"
+showInPage: "Sahifada ko'rsatish"
+popout: "Oching"
volume: "Ovoz balandligi"
details: "Batafsil"
+chooseEmoji: "Emojini tanlang"
+unableToProcess: "Opertsiya bajarilmadi"
+recentUsed: "Oxirgi ishlatilganlar"
+install: "O‘rnatish"
+uninstall: "O‘chirib tashlash"
+installedApps: "O'rnatilgan ilovalar"
+nothing: "Hech narsa yo'q"
+installedDate: "O'rnatish sanasi"
+lastUsedDate: "Oxirgi marta ishlatilgan sana"
+state: "Holat"
+sort: "saralamoq"
+ascendingOrder: "O'sish bo'yicha"
+descendingOrder: "Kamayish bo'yicha"
+scratchpad: "Qoralama"
output: "Chiqish"
+script: "Skript"
+disablePagesScript: "AiScriptni sahifalardan o'chirish"
+updateRemoteUser: "Masofaviy foydalanuvchi ma'lumotlarini yangilash"
deleteAllFiles: "barcha fayllarni o'chirish"
deleteAllFilesConfirm: "Barcha fayllar oʻchirilsinmi?"
+removeAllFollowing: "Barcha obunalarni o'chirish"
userSuspended: "Bu foydalanuvchi muzlatilgan."
+userSilenced: "Ushbu foydalanuvchi jim qilingan"
yourAccountSuspendedTitle: "akkaunt muzlatilgan"
yourAccountSuspendedDescription: "Ushbu akkaunt serverning xizmat ko'rsatish shartlarini buzish kabi sabablarga ko'ra to'xtatilgan. Tafsilotlar uchun administratoringizga murojaat qiling. Iltimos, yangi akkaunt yaratmang."
tokenRevoked: "token yaroqsiz"
@@ -465,51 +560,306 @@ accountDeletedDescription: "Bu akkaunt oʻchirildi."
menu: "Menyu"
divider: "Ajratrmoq"
addItem: "Element qo'shish"
+rearrange: "Qayta saralash"
+inboxUrl: "Qabul qilingan xabarlar URL manzili"
serviceworkerInfo: "bildirishnomalar uchun yoqilgan bo'lishi kerak."
deletedNote: "Oʻchirilgan post"
+visibility: "Ko'rinishi"
+poll: "So'ro'vnoma"
+useCw: "Kontentni yashirish"
+enablePlayer: "Video pleyerni ochish"
+disablePlayer: "Video pleyerni yopish"
+expandTweet: "Xabarni kengaytirish"
themeEditor: "Rang sxemasi muharriri"
+description: "tavsif"
describeFile: "sarlavha qo'shing"
enterFileDescription: "sarlavha kiriting"
author: "muallif"
leaveConfirm: "Sizda saqlanmagan oʻzgarishlar bor. Bekor qilinsinmi?"
+manage: "Administratsiya"
+plugins: "Kengaytmalar, plaginlar"
+preferencesBackups: "Sozlamalarni zahiralash"
useBlurEffectForModal: "Modal uchun xiralashtirish effektidan foydalaning"
+useFullReactionPicker: "Katta oynada reaksiya tanlash"
width: "kengligi"
height: "balandligi"
large: "Katta"
+medium: "O'rta"
small: "kichik"
+generateAccessToken: "Kirish tokenini yaratish"
+permission: "Ruxsatlar"
enableAll: "Yoqish"
disableAll: "hammasini o'chirib qo'ying"
+tokenRequested: "Hisobga kirish"
+pluginTokenRequestedDescription: "Bu plagin shu yerda belgilanganlarga qodir bo'ladi"
+notificationType: "Bildirishnoma turi"
edit: "Tahrirlash"
+emailServer: "Email server"
email: "Email"
+emailAddress: "E-pochtangiz:"
+smtpConfig: "SMTP server sozlamalari"
smtpHost: "Host"
+smtpPort: "Port"
smtpUser: "Foydalanuvchi nomi"
smtpPass: "Parol"
+testEmail: "Email jo'natmani testlash"
+userSaysSomething: "{name} nimadir dedi"
+makeActive: "Faol"
+display: "Displey"
copy: "Nusxa olish"
+metrics: "Metrikalar"
+overview: "Umumiy ma'lumot"
+logs: "Jurnallar"
+delayed: "Kechiktirildi"
+database: "Ma'lumotlar bazasi"
+channel: "Kanallar"
+create: "Yaratish"
notificationSetting: "Bildirishnoma sozlamalari"
+notificationSettingDesc: "Ko'rsatish uchun bildirishnoma turlarini tanlang."
+useGlobalSetting: "Global sozlamalardan foydalanish"
other: "Qo‘shimcha"
+regenerateLoginToken: "Kirish tokenini qayta yaratish"
+setMultipleBySeparatingWithSpace: "Bo'sh joy qoldirib, bir necha ma'lumot kiritish mumkin"
+fileIdOrUrl: "Fayl ID'si yoki URL havolasi"
behavior: "Hatti-harakatlar"
sample: "Namuna"
+abuseReports: "Shikoyatlar"
+reportAbuse: "Shikoyat qilish"
+reportAbuseOf: "{name} ustidan shikoyat qilish"
+abuseReported: "Shikoyatingiz yetkazildi. Ma'lumot uchun rahmat."
+reporter: "Shikoyat qiluvchi"
+reporteeOrigin: "Xabarning kelib chiqishi"
+reporterOrigin: "Xabarchining joylashuvi"
+forwardReport: "Xabarni masofadagi serverga yuborish"
+forwardReportIsAnonymous: "Sizning yuborayotgan xabaringiz o'z akkountingiz emas balki anonim tarzda qoladi"
+send: "Yuborish"
+abuseMarkAsResolved: "Yuborilgan xabarni hal qilingan deb belgilash"
+openInNewTab: "Yangi tab da ochish"
+openInSideView: "Yon panelda ochish"
+defaultNavigationBehaviour: "Standart navigatsiya harakati"
+editTheseSettingsMayBreakAccount: "Bu sozlamalarni o'zgartirish hisobingizga zarar yetkazishi mumkin."
+waitingFor: "{x}ni kutayapman"
+random: "Tasodifiy"
+system: "Tizim"
+switchUi: "Interfeysni almashtirish"
+desktop: "Brauzer rejimi"
+clip: "Klip"
+createNew: "Yangi yaratish"
+optional: "Ixtiyoriy"
+createNewClip: "Yangi klip yaratish"
+unclip: "qirqish\n"
+confirmToUnclipAlreadyClippedNote: "Ushbu xat allaqachon \"{name}\" klipga tegishli. Uni ushbu klipdan olib tashlashni xohlaysizmi?"
public: "Ommaviy"
+i18nInfo: "Misskey bir qancha volontyorlar yordamida bir qancha tillarga tarjima qilingan. Ushbu {link} orqali ularga yordam berishingiz mumkin."
+manageAccessTokens: "Kirish tokenlarini boshqarish"
+accountInfo: "Akkount haqida ma'lumot"
+notesCount: "Xatlar soni"
+repliesCount: "Yuborilgan javoblar soni"
+renotesCount: "Qayta yuborilgan xatlar soni"
+repliedCount: "Qabul qilingan javoblar soni"
+renotedCount: "Qayta yuborilgan xatlar soni"
+followingCount: "Obuna bo'lingan akkountlar soni"
+followersCount: "Obunachilar soni"
+sentReactionsCount: "Yuborilgan reaksiyalar soni"
+receivedReactionsCount: "Qabul qilingan reaksiyalar soni"
+pollVotesCount: "Berilgan ovozlar soni"
+pollVotedCount: "Qabul qilingan ovozlar soni"
+yes: "Ha"
+no: "Yo'q"
+driveFilesCount: "Diskdagi fayllar soni"
+driveUsage: "Ishlatilgan disk joyi"
+noCrawleDescription: "Qidiruv tizimlari sizning profilingiz, sahifalaringiz, xatlaringiz va hokazolarni belgilamasligi uchun so'rov yuborish"
+lockedAccountInfo: "Xatlaringizni faqatgina obunachilaringizga ko'rsatishni xohlasangiz unda \"Faqat Obunachilar uchun\" xususiyatini yoqishingiz lozim. Bo'lmasa sizning yozgan xatlaringiz hammaga ko'rinadi."
+alwaysMarkSensitive: "Avvaldan ta'sirchan kontent deb belgilash"
+loadRawImages: "Thumbnaillarsiz Original rasmni yuklash"
+disableShowingAnimatedImages: "Animatsiyali rasmlarni ko'rsatmaslik"
+verificationEmailSent: "Emailingizga tasdiqlash xabari yuborildi. Iltimos linkda ko'rsatilgan amallarga rioya qiling"
+notSet: "Sozlanmagan"
+emailVerified: "Elektron pochta manzili tasdiqlandi"
+pageLikesCount: "Sahifadagi like'lar soni"
+contact: "aloqa uchun manzil"
+useSystemFont: "Tizimdagi standart shriftidan foydalaning"
+clips: "Klip"
+experimentalFeatures: "eksperimental xususiyatlar"
+experimental: "eksperimental"
+developer: "Dasturchi"
+makeExplorable: "Akkauntingizni topishni osonlashtiring"
+duplicate: "Dublikat"
+left: "Chap(dagi)"
+center: "Markaz"
+wide: "Keng"
+narrow: "Tor"
+reloadToApplySetting: "Bu sozlamalar sahifa yangilangandagina kuchga kiradi. Hozir yangilashni istaysizmi?"
+needReloadToApply: "Sahifani yangilash talab etiladi."
clearCache: "Keshni tozalash"
onlineUsersCount: "Faol userlar"
+nUsers: "{n} ta foydalanuvchi"
myTheme: "Mening rang sxemam"
backgroundColor: "Fon"
accentColor: "Urg'u"
textColor: "Matn"
+saveAs: "Boshqacha saqlash"
+advanced: "Murakkab"
+advancedSettings: "Qo'shimcha sozlashlar"
+value: "Qiymati"
+createdAt: "Yaratilish vaqti"
+updatedAt: "yangilangan sana"
+saveConfirm: "O'zgartirishni saqlash?"
+deleteConfirm: "o'chirishni tasdiqlash"
+invalidValue: "noto'g'ri qiymat"
+registry: "ro'yhatga olish"
+closeAccount: "hisobni yopish / profilni yopish"
+currentVersion: "joriy versiya"
+latestVersion: "so'ngi versiya"
+youAreRunningUpToDateClient: "siz so'ngi versiyali ilovani ishlatyapsiz"
+newVersionOfClientAvailable: "Mijozning yangi versiyasi mavjud."
+usageAmount: "foydalanish miqdori"
+capacity: "sig'im"
+inUse: "allaqachon band"
+editCode: "kodni tahrirlash"
+apply: "Ilova"
+receiveAnnouncementFromInstance: "Serverdan bildirishnomalarni oling"
+emailNotification: "E-mail xabarlari"
+publish: "Chiqarish"
+inChannelSearch: "Kanal qidirish"
+useReactionPickerForContextMenu: "kontekst menyusi uchun reaktsiya tanlash vositasidan foydalaning"
+typingUsers: "{users} yozmoqda"
+jumpToSpecifiedDate: "Muayyan sanaga o'tish"
+showingPastTimeline: "O'tgan vaqt jadvallarini ko'rsatish"
+clear: "aniq"
+markAllAsRead: "hammasini o'qilgan deb belgilang"
+goBack: "qaytish"
+unlikeConfirm: "Un like qilishni xohlaysizmi?"
+fullView: "to'liq ko'rish"
+quitFullView: "Toʻliq koʻrishdan chiqish"
+addDescription: "Tavsif qo'shing"
info: "Haqida"
+userInfo: "Foydalanuvchi ma'lumotlari"
+unknown: "aniq emas"
+onlineStatus: "onlayn holat"
+hideOnlineStatus: "onlayn holatni yashirish"
+hideOnlineStatusDescription: "Onlayn statusingizni yashirish, qidiruv kabi baʼzi funksiyalardan foydalanish imkoniyatini kamaytirishi mumkin."
+online: "onlayn"
+active: "Aktiv"
+offline: "oflayn"
+notRecommended: "tavsiya etilmaydi"
+selectAccount: "Akkauntni tanlang"
+switchAccount: "akkauntni almashtirish"
+enabled: "yaroqli"
+disabled: "yaroqsiz"
+quickAction: "tezkor harakat"
user: "Foydalanuvchilar"
+administration: "Administratsiya"
+accounts: "akkaunt"
+switch: "almashtirish"
+noBotProtectionWarning: "Bot himoyasi sozlanmagan."
+configure: "sozlamoq"
+postToGallery: "Yangi galleriya posti"
+gallery: "Galereya"
+recentPosts: "So'nggi postlar"
+popularPosts: "Mashhur postlar"
+shareWithNote: "Eslatmani ulashish"
+ads: "Reklama"
+startingperiod: "Boshlanish davri"
+memo: "Eslatma"
+priority: "Ustuvorlik"
+high: "Yuqori"
+middle: "O'rta"
+low: "Quyi"
+ratio: "nisbat"
+previewNoteText: "Razm solish"
+customCss: "Maxsus CSS"
global: "Global"
squareAvatars: "Kvadrat avatarkalar"
+sent: "Yuborish"
+received: "Qabul qilingan"
+searchResult: "Qidiruv natijalari"
+hashtags: "Hashteglar"
+troubleshooting: "Muammolarni bartaraf qilish"
+useBlurEffect: "Interfeysda xiralashtiruvchi effektlardan foydalanish"
+learnMore: "Batafsilroq"
+misskeyUpdated: "Misskey yangilandi!"
+whatIsNew: "O'zgarishlarni ko'rish"
+translate: "Tarjima qilish"
+translatedFrom: "{x} tilidan tarjima qilindi"
+devMode: "Dasturchi rejimi"
+lastCommunication: "Oxirgi muloqot"
+resolved: "Hal qilingan"
+unresolved: "Hal qilinmagan"
+breakFollow: "Obunachini o'chirish"
+breakFollowConfirm: "Obunachini o'chirmoqchimisiz?"
+itsOn: "Yoqilgan"
+itsOff: "O'chirilgan"
+on: "Yoqish"
+off: "O'chirish"
+emailRequiredForSignup: "Ro'yxatdan o'tish uchun email talab qilish"
+unread: "Oʻqilmagan xabarlar"
+filter: "Filter"
+controlPanel: "Boshqaruv paneli"
+manageAccounts: "Hisobni boshqarish"
+classic: "Klassik"
+hide: "Yashirish"
searchByGoogle: "Izlash"
indefinitely: "Hech qachon"
file: "Fayllar"
+recommended: "Tavsiya qilingan"
+check: "Tekshirish"
+requireAdminForView: "Ko'rish uchun adminstrator hisobi bilan tizimga kirgan bo'lishingiz kerak."
+isSystemAccount: "Ushbu hisob tizim tomonidan avtomatik tarzda yaratilgan va boshqariladi."
+typeToConfirm: "Ushbu amalni bajarish uchun {x}ni kiriting"
+deleteAccount: "Hisobni o'chirish"
+document: "hujjat"
+numberOfPageCache: "Sahifa keshlar soni"
+logoutConfirm: "Chiqishni xohlaysizmi?"
+lastActiveDate: "oxirgi foydalanish sanasi"
+statusbar: "Holat paneli"
+pleaseSelect: "Iltimos tanlang"
+reverse: "Teskari"
+colored: "rangli"
+refreshInterval: "yangilash oralig'i"
label: "Yorliq"
+type: "turi"
+speed: "tezlik"
+slow: "Sekin"
+fast: "Tez"
+localOnly: "Faqat mahalliy"
+remoteOnly: "faqat masofadan turib"
+failedToUpload: "yuklanmadi"
+cannotUploadBecauseInappropriate: "Faylni yuklab bo'lmaydi, chunki unda nomaqbul kontent borligi aniqlangan."
+cannotUploadBecauseNoFreeSpace: "Yuklab bo'lmadi, chunki diskda bo'sh joy yo'q."
+cannotUploadBecauseExceedsFileSizeLimit: "Faylni yuklash mumkin emas, chunki u fayl hajmi chegarasidan oshib ketgan."
+beta: "beta"
+account: "akkaunt"
+show: "Displey"
color: "Rang"
+disableFederationConfirm: "Federatsiyani o'chirmoqchimisiz?"
+disableFederationOk: "O'chirish"
+emailNotSupported: "Bu server E-pochtalar yuborishni qo'llab-quvvatlamaydi"
+postToTheChannel: "Kanalga joylash"
+cannotBeChangedLater: "Buni keyinchalik o'zgartirishni iloji yo'q"
+likeOnly: "Faqat like'lar"
+nonSensitiveOnly: "Xavfsiz rejim"
+rolesAssignedToMe: "Mening rollarim"
+resetPasswordConfirm: "Qayta parol o'rnatmoqchimisiz?"
+sensitiveWords: "Ta'sirchan so'zlar"
+icon: "Avatar"
+replies: "Javob berish"
+renotes: "Qayta qayd etish"
+flip: "Teskari"
_achievements:
_types:
_viewInstanceChart:
title: "Tahlilchi"
+_role:
+ priority: "Ustuvorlik"
+ _priority:
+ low: "Quyi"
+ middle: "O'rta"
+ high: "Yuqori"
+_ffVisibility:
+ public: "Chiqarish"
_ad:
+ back: "qaytish"
hide: "Boshqa ko'rsatilmasin"
_email:
_follow:
@@ -561,11 +911,11 @@ _theme:
_sfx:
note: "Qaydlar"
notification: "Xabarnomalar"
- chat: "Suhbat"
_ago:
minutesAgo: "{n} daqiqa oldin"
hoursAgo: "{n} soat oldin"
daysAgo: "{n} kun oldin"
+ invalid: "Hech narsa yo'q"
_2fa:
renewTOTPCancel: "Hozir emas"
_permissions:
@@ -734,3 +1084,6 @@ _webhookSettings:
_events:
renote: "Qayta qayd qilinganda"
mention: "Eslanganda"
+_moderationLogTypes:
+ suspend: "To'xtatish"
+ resetPassword: "Parolni tiklash"
diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml
index 35081148a2..c816fc314b 100644
--- a/locales/vi-VN.yml
+++ b/locales/vi-VN.yml
@@ -1,5 +1,5 @@
---
-_lang_: "Tiếng Việt"
+_lang_: "Tiếng Nhật"
headlineMisskey: "Mạng xã hội liên hợp"
introMisskey: "Xin chào! Misskey là một nền tảng tiểu blog phi tập trung mã nguồn mở.\nViết \"tút\" để chia sẻ những suy nghĩ của bạn 📡\nBằng \"biểu cảm\", bạn có thể bày tỏ nhanh chóng cảm xúc của bạn với các tút 👍\nHãy khám phá một thế giới mới! 🚀"
poweredByMisskeyDescription: "{name} là một trong những chủ máy của Misskey là nền tảng mã nguồn mở"
@@ -20,6 +20,7 @@ noNotes: "Chưa có bài viết nào."
noNotifications: "Chưa có thông báo"
instance: "Máy chủ"
settings: "Cài đặt"
+notificationSettings: "Cài đặt thông báo"
basicSettings: "Thiết lập chung"
otherSettings: "Thiết lập khác"
openInWindow: "Mở trong cửa sổ mới"
@@ -44,13 +45,20 @@ pin: "Ghim"
unpin: "Bỏ ghim"
copyContent: "Chép nội dung"
copyLink: "Chép liên kết"
+copyLinkRenote: "Sao chép liên kết ghi chú"
delete: "Xóa"
deleteAndEdit: "Sửa"
deleteAndEditConfirm: "Bạn có chắc muốn sửa tút này? Những biểu cảm, lượt trả lời và đăng lại sẽ bị mất."
addToList: "Thêm vào danh sách"
+addToAntenna: "Thêm vào Ăngten"
sendMessage: "Gửi tin nhắn"
copyRSS: "Sao chép RSS"
copyUsername: "Chép tên người dùng"
+copyUserId: "Sao chép ID người dùng"
+copyNoteId: "Sao chép ID ghi chú"
+copyFileId: "Sao chép ID tập tin"
+copyFolderId: "Sao chép ID thư mục"
+copyProfileUrl: "Sao chép URL hồ sơ"
searchUser: "Tìm kiếm người dùng"
reply: "Trả lời"
loadMore: "Tải thêm"
@@ -122,6 +130,8 @@ unmarkAsSensitive: "Bỏ đánh dấu nhạy cảm"
enterFileName: "Nhập tên tập tin"
mute: "Ẩn"
unmute: "Bỏ ẩn"
+renoteMute: "Mute Renotes"
+renoteUnmute: "Unmute Renotes"
block: "Chặn"
unblock: "Bỏ chặn"
suspend: "Vô hiệu hóa"
@@ -131,8 +141,10 @@ unblockConfirm: "Bạn có chắc muốn bỏ chặn người này?"
suspendConfirm: "Bạn có chắc muốn vô hiệu hóa người này?"
unsuspendConfirm: "Bạn có chắc muốn bỏ vô hiệu hóa người này?"
selectList: "Chọn danh sách"
+editList: "Chỉnh sửa danh sách"
selectChannel: "Lựa chọn kênh"
selectAntenna: "Chọn một antenna"
+editAntenna: "Chỉnh sửa Ăngten"
selectWidget: "Chọn tiện ích"
editWidgets: "Sửa tiện ích"
editWidgetsExit: "Xong"
@@ -145,6 +157,9 @@ addEmoji: "Thêm emoji"
settingGuide: "Cài đặt đề xuất"
cacheRemoteFiles: "Tập tin cache từ xa"
cacheRemoteFilesDescription: "Khi tùy chọn này bị tắt, các tập tin từ xa sẽ được tải trực tiếp từ máy chủ khác. Điều này sẽ giúp giảm dung lượng lưu trữ nhưng lại tăng lưu lượng truy cập, vì hình thu nhỏ sẽ không được tạo."
+youCanCleanRemoteFilesCache: "Bạn có thể xoá bộ nhớ đệm bằng cách nhấn vào nút🗑️ở trong phần quản lý tệp."
+cacheRemoteSensitiveFiles: "Lưu các tập tin nhạy cảm vào bộ nhớ tạm từ xa"
+cacheRemoteSensitiveFilesDescription: "Khi bạn tắt tính năng này, các tệp nhạy cảm sẽ được tải trực tiếp từ máy chủ và không được lưu vào bộ nhớ tạm"
flagAsBot: "Đánh dấu đây là tài khoản bot"
flagAsBotDescription: "Bật tùy chọn này nếu tài khoản này được kiểm soát bởi một chương trình. Nếu được bật, nó sẽ được đánh dấu để các nhà phát triển khác ngăn chặn chuỗi tương tác vô tận với các bot khác và điều chỉnh hệ thống nội bộ của Misskey để coi tài khoản này như một bot."
flagAsCat: "Chế độ Mèeeeeeeeeeo!!"
@@ -153,6 +168,7 @@ flagShowTimelineReplies: "Hiện lượt trả lời trong bảng tin"
flagShowTimelineRepliesDescription: "Hiện lượt trả lời của người bạn theo dõi trên tút của những người khác."
autoAcceptFollowed: "Tự động phê duyệt theo dõi từ những người mà bạn đang theo dõi"
addAccount: "Thêm tài khoản"
+reloadAccountsList: "Cập nhật danh sách tài khoản"
loginFailed: "Đăng nhập không thành công"
showOnRemote: "Truy cập trang của người này"
general: "Tổng quan"
@@ -259,8 +275,10 @@ noMoreHistory: "Không còn gì để đọc"
startMessaging: "Bắt đầu trò chuyện"
nUsersRead: "đọc bởi {n}"
agreeTo: "Tôi đồng ý {0}"
+agree: "Đồng ý"
agreeBelow: "Đồng ý với nội dung dưới đây"
basicNotesBeforeCreateAccount: "Những điều cơ bản cần chú ý "
+termsOfService: "Điều khoản và Điều kiện"
start: "Bắt đầu"
home: "Trang chính"
remoteUserCaution: "Vì người dùng này ở máy chủ khác, thông tin hiển thị có thể không đầy đủ."
@@ -303,6 +321,7 @@ copyUrl: "Sao chép URL"
rename: "Đổi tên"
avatar: "Ảnh đại diện"
banner: "Ảnh bìa"
+displayOfSensitiveMedia: "Hiển thị nội dung nhạy cảm (NSFW)"
whenServerDisconnected: "Khi mất kết nối tới máy chủ"
disconnectedFromServer: "Mất kết nối tới máy chủ"
reload: "Tải lại"
@@ -337,7 +356,6 @@ invite: "Mời"
driveCapacityPerLocalAccount: "Dung lượng ổ đĩa tối đa cho mỗi người dùng"
driveCapacityPerRemoteAccount: "Dung lượng ổ đĩa tối đa cho mỗi người dùng từ xa"
inMb: "Tính bằng MB"
-iconUrl: "URL Icon"
bannerUrl: "URL Ảnh bìa"
backgroundImageUrl: "URL Ảnh nền"
basicInfo: "Thông tin cơ bản"
@@ -393,10 +411,13 @@ aboutMisskey: "Về Misskey"
administrator: "Quản trị viên"
token: "Token"
2fa: "Xác thực 2 yếu tố"
+setupOf2fa: "Thiết lập xác thực 2 yếu tố"
totp: "Ứng dụng xác thực"
totpDescription: "Nhắn mã OTP bằng ứng dụng xác thực"
moderator: "Kiểm duyệt viên"
moderation: "Kiểm duyệt"
+moderationNote: "Ghi chú kiểm duyệt"
+addModerationNote: "Thêm ghi chú kiểm duyệt"
nUsersMentioned: "Dùng bởi {n} người"
securityKeyAndPasskey: "Mã bảo mật・Passkey"
securityKey: "Khóa bảo mật"
@@ -456,6 +477,7 @@ aboutX: "Giới thiệu {x}"
emojiStyle: "Kiểu cách Emoji"
native: "Bản xứ"
disableDrawer: "Không dùng menu thanh bên"
+showNoteActionsOnlyHover: "Chỉ hiển thị các hành động ghi chú khi di chuột"
noHistory: "Không có dữ liệu"
signinHistory: "Lịch sử đăng nhập"
enableAdvancedMfm: "Xem bài MFM chất lượng cao."
@@ -468,6 +490,7 @@ createAccount: "Tạo tài khoản"
existingAccount: "Tài khoản hiện có"
regenerate: "Tạo lại"
fontSize: "Cỡ chữ"
+limitTo: "Giới hạn tỷ lệ {x}"
noFollowRequests: "Bạn không có yêu cầu theo dõi nào"
openImageInNewTab: "Mở ảnh trong tab mới"
dashboard: "Trang chính"
@@ -504,6 +527,7 @@ objectStorageSetPublicRead: "Đặt \"public-read\" khi tải lên"
serverLogs: "Nhật ký máy chủ"
deleteAll: "Xóa tất cả"
showFixedPostForm: "Hiện khung soạn tút ở phía trên bảng tin"
+showFixedPostFormInChannel: "Hiển thị mẫu bài đăng ở phía trên bản tin"
newNoteRecived: "Đã nhận tút mới"
sounds: "Âm thanh"
sound: "Âm thanh"
@@ -541,9 +565,14 @@ userSuspended: "Người này đã bị vô hiệu hóa."
userSilenced: "Người này đã bị ẩn"
yourAccountSuspendedTitle: "Tài khoản bị vô hiệu hóa"
yourAccountSuspendedDescription: "Tài khoản này đã bị vô hiệu hóa do vi phạm quy tắc máy chủ hoặc điều tương tự. Liên hệ với quản trị viên nếu bạn muốn biết lý do chi tiết hơn. Vui lòng không tạo tài khoản mới."
+tokenRevoked: "Token đã bị từ chối"
+tokenRevokedDescription: "Phiên đăng nhập đã hết hạn. Vui lòng đăng nhập lại."
+accountDeleted: "Tài khoản đã bị xóa"
+accountDeletedDescription: "Tài khoản này đã bị xóa."
menu: "Menu"
divider: "Phân chia"
addItem: "Thêm mục"
+rearrange: "Sắp xếp lại"
relays: "Chuyển tiếp"
addRelay: "Thêm chuyển tiếp"
inboxUrl: "URL Hộp thư đến"
@@ -653,6 +682,7 @@ createNewClip: "Tạo một ghim mới"
unclip: "Bỏ ghim"
confirmToUnclipAlreadyClippedNote: "Bài đăng này là một phần của \"{name}\" ghim. Bạn có muốn bỏ khỏi ghim?"
public: "Công khai"
+private: "Riêng tư"
i18nInfo: "Misskey đang được các tình nguyện viên dịch sang nhiều thứ tiếng khác nhau. Bạn có thể hỗ trợ tại {link}."
manageAccessTokens: "Tạo mã truy cập"
accountInfo: "Thông tin tài khoản"
@@ -687,6 +717,8 @@ contact: "Liên hệ"
useSystemFont: "Dùng phông chữ mặc định của hệ thống"
clips: "Lưu bài viết"
experimentalFeatures: "Tính năng thử nghiệm"
+experimental: "Thử nghiệm"
+thisIsExperimentalFeature: "Tính năng này đang trong quá trình thử nghiệm. Tính năng có thể không hoạt động, hoặc đặc tính kỹ thuật có thể bị thay đổi sau này."
developer: "Nhà phát triển"
makeExplorable: "Không hiện tôi trong \"Khám phá\""
makeExplorableDescription: "Nếu bạn tắt, tài khoản của bạn sẽ không hiện trong mục \"Khám phá\"."
@@ -771,6 +803,7 @@ noMaintainerInformationWarning: "Chưa thiết lập thông tin vận hành."
noBotProtectionWarning: "Bảo vệ Bot chưa thiết lập."
configure: "Thiết lập"
postToGallery: "Tạo tút có ảnh"
+postToHashtag: "Đăng bài với hashtag này"
gallery: "Thư viện ảnh"
recentPosts: "Tút gần đây"
popularPosts: "Tút được xem nhiều nhất"
@@ -804,6 +837,7 @@ translatedFrom: "Dịch từ {x}"
accountDeletionInProgress: "Đang xử lý việc xóa tài khoản"
usernameInfo: "Bạn có thể sử dụng chữ cái (a ~ z, A ~ Z), chữ số (0 ~ 9) hoặc dấu gạch dưới (_). Tên người dùng không thể thay đổi sau này."
aiChanMode: "Chế độ Ai"
+devMode: "Chế độ dành cho nhà phát triển"
keepCw: "Giữ cảnh báo nội dung"
pubSub: "Tài khoản Chính/Phụ"
lastCommunication: "Lần giao tiếp cuối"
@@ -813,6 +847,8 @@ breakFollow: "Xóa người theo dõi"
breakFollowConfirm: "Bạn bỏ theo dõi tài khoản này không?"
itsOn: "Đã bật"
itsOff: "Đã tắt"
+on: "Bật"
+off: "Tắt"
emailRequiredForSignup: "Yêu cầu địa chỉ email khi đăng ký"
unread: "Chưa đọc"
filter: "Bộ lọc"
@@ -857,6 +893,7 @@ failedToFetchAccountInformation: "Không thể lấy thông tin tài khoản"
rateLimitExceeded: "Giới hạn quá mức"
cropImage: "Cắt hình ảnh"
cropImageAsk: "Bạn có muốn cắt ảnh này?"
+cropYes: "Cắt"
cropNo: "Để nguyên"
file: "Tập tin"
recentNHours: "{n}h trước"
@@ -892,6 +929,7 @@ remoteOnly: "Chỉ máy chủ từ xa"
failedToUpload: "Tải lên thất bại"
cannotUploadBecauseInappropriate: "Không thể tải lên tập tin này vì các phần của tập tin đã được phát hiện có khả năng là NSFW."
cannotUploadBecauseNoFreeSpace: "Tải lên không thành công do thiếu dung lượng Drive."
+cannotUploadBecauseExceedsFileSizeLimit: "Không thể tải lên tập tin vì kích thước quá lớn."
beta: "Beta"
enableAutoSensitive: "Tự động đánh dấu NSFW"
enableAutoSensitiveDescription: "Cho phép tự động phát hiện và đánh dấu media NSFW thông qua học máy, nếu có thể. Ngay cả khi tùy chọn này bị tắt, nó vẫn có thể được bật trên toàn máy chủ."
@@ -904,9 +942,11 @@ pushNotification: "Thông báo đẩy"
subscribePushNotification: "Bật thông báo đẩy"
unsubscribePushNotification: "Tắt thông báo đẩy"
pushNotificationAlreadySubscribed: "Đang bật thông báo đẩy"
+pushNotificationNotSupported: "Trình duyệt của bạn không hỗ trợ thông báo đẩy."
sendPushNotificationReadMessage: "Xóa thông báo đẩy sau khi đọc thông báo hay tin nhắn"
sendPushNotificationReadMessageCaption: "Thông báo như {emptyPushNotificationMessage} sẽ hiển thị trong giây phút. Tiêu tốn pin của máy bạn có thể tăng lên hơn nữa."
windowMaximize: "Phóng to"
+windowMinimize: "Thu nhỏ tối đa"
windowRestore: "Khôi phục"
caption: "Mô tả"
loggedInAsBot: "Đang đăng nhập bằng tài khoản Bot"
@@ -923,12 +963,22 @@ didYouLikeMisskey: "Bạn có ưa thích Mískey không?"
pleaseDonate: "Misskey là phần mềm miễn phí mà {host} đang sử dụng. Xin mong bạn quyên góp cho chúng tôi để chúng tôi có thể tiếp tục phát triển dịch vụ này. Xin cảm ơn!!"
roles: "Vai trò"
role: "Vai trò"
+noRole: "Bạn chưa được cấp quyền."
normalUser: "Người dùng bình thường"
undefined: "Chưa xác định"
+assign: "Phân công"
+unassign: "Hủy phân công"
color: "Màu sắc"
manageCustomEmojis: "Quản lý CustomEmoji"
+youCannotCreateAnymore: "Bạn đã tới giới hạn tạo."
cannotPerformTemporary: "Tạm thời không sử dụng được"
cannotPerformTemporaryDescription: "Tạm thời không sử dụng được vì lần số điều kiện quá giới hạn. Thử lại sau mọt lát nữa."
+invalidParamError: "Lỗi tham số"
+invalidParamErrorDescription: "Có vấn đề với các tham số được request. Thông thường, đây là do bug, nhưng cũng có thể do bạn đã nhập vào quá nhiều ký tự."
+permissionDeniedError: "Thao tác bị từ chối"
+permissionDeniedErrorDescription: "Tài khoản này không có đủ quyền hạn để thực hiện thao tác này."
+preset: "Mẫu thiết lập"
+selectFromPresets: "Chọn từ mẫu"
achievements: "Thành tích"
gotInvalidResponseError: "Không nhận được trả lời chủ máy"
gotInvalidResponseErrorDescription: "Chủ máy có lẻ ngừng hoạt động hoặc bảo trí. Thử lại sau một lát nữa. "
@@ -943,8 +993,95 @@ copyErrorInfo: "Sao chép thông tin lỗi"
joinThisServer: "Đăng ký trên chủ máy này"
exploreOtherServers: "Tìm chủ máy khác"
letsLookAtTimeline: "Thử xem Timeline"
+emailNotSupported: "Máy chủ này không hỗ trợ gửi email"
+postToTheChannel: "Đăng lên kênh"
+cannotBeChangedLater: "Không thể thay đổi sau này."
+rolesAssignedToMe: "Vai trò được giao cho tôi"
+resetPasswordConfirm: "Bạn thực sự muốn đặt lại mật khẩu?"
+sensitiveWords: "Các từ nhạy cảm"
+license: "Giấy phép"
+unfavoriteConfirm: "Bạn thực sự muốn xoá khỏi mục yêu thích?"
+retryAllQueuesConfirmText: "Điều này sẽ tạm thời làm tăng mức độ tải của máy chủ."
+enableChartsForRemoteUser: "Tạo biểu đồ người dùng từ xa"
+video: "Video"
+videos: "Các video"
+dataSaver: "Tiết kiệm dung lượng"
+accountMigration: "Chuyển tài khoản"
+accountMoved: "Người dùng này đã chuyển sang một tài khoản mới:"
+accountMovedShort: "Tài khoản này đã được chuyển"
+operationForbidden: "Thao tác này không thể thực hiện"
+forceShowAds: "Luôn hiện quảng cáo"
+notificationDisplay: "Thông báo"
+leftTop: "Phía trên bên tráí"
+rightTop: "Phía trên bên phải"
+leftBottom: "Phía dưới bên trái"
+rightBottom: "Phía dưới bên phải"
+stackAxis: "Hướng chồng"
+vertical: "Dọc"
horizontal: "Thanh bên"
+position: "Vị trí"
+serverRules: "Luật của máy chủ"
youFollowing: "Đang theo dõi"
+later: "Để sau"
+goToMisskey: "Tới Misskey"
+installed: "Đã tải xuống"
+branding: "Thương hiệu"
+turnOffToImprovePerformance: "Tắt mục này có thể cải thiện hiệu năng."
+expirationDate: "Ngày hết hạn"
+noExpirationDate: "Vô thời hạn"
+waitingForMailAuth: "Đang chờ xác nhận email"
+unused: "Chưa được sử dụng"
+used: "Đã được sử dụng"
+expired: "Đã hết hạn"
+doYouAgree: "Đồng ý?"
+iHaveReadXCarefullyAndAgree: "Tôi đã đọc và đồng ý với \"x\"."
+dialog: "Hộp thoại"
+icon: "Ảnh đại diện"
+forYou: "Dành cho bạn"
+currentAnnouncements: "Thông báo hiện tại"
+pastAnnouncements: "Thông báo trước đó"
+youHaveUnreadAnnouncements: "Có thông báo chưa đọc."
+replies: "Trả lời"
+renotes: "Đăng lại"
+loadReplies: "Hiển thị các trả lời"
+pinnedList: "Các mục đã được ghim"
+keepScreenOn: "Giữ màn hình luôn bật"
+verifiedLink: "Chúng tôi đã xác nhận bạn là chủ sở hữu của đường dẫn này"
+flip: "Lật"
+_announcement:
+ forExistingUsers: "Chỉ những người dùng đã tồn tại"
+ forExistingUsersDescription: "Nếu được bật, thông báo này sẽ chỉ hiển thị với những người dùng đã tồn tại vào lúc thông báo được tạo. Nếu tắt đi, những tài khoản mới đăng ký sau khi thông báo được đăng lên cũng sẽ thấy nó."
+ end: "Lưu trữ thông báo"
+ tooManyActiveAnnouncementDescription: "Có quá nhiều thông báo sẽ làm trải nghiệm của người dùng tệ đi. Vui lòng lưu trữ những thông báo đã hết hiệu lực."
+ readConfirmTitle: "Đánh dấu là đã đọc?"
+ readConfirmText: "Điều này sẽ đánh dấu nội dung của \"{title}\" là đã đọc."
+_initialAccountSetting:
+ accountCreated: "Tài khoản của bạn đã được tạo thành công!"
+ letsStartAccountSetup: "Để bắt đầu, hãy cùng thiết lập tài khoản nhé."
+ letsFillYourProfile: "Đầu tiên, hãy thiết lập hồ sơ của bạn."
+ profileSetting: "Thiết lập hồ sơ"
+ privacySetting: "Cài đặt quyền riêng tư"
+ theseSettingsCanEditLater: "Bạn vẫn có thể thay đổi những cài đặt này."
+ youCanEditMoreSettingsInSettingsPageLater: "Còn rất nhiều những cài đặt khác bạn có thể thay đổi ở trang \"Cài đặt\". Hãy nhớ ghé thăm trong lần sau nhé."
+ followUsers: "Thử theo dõi một vài người mà bạn có thể thích để xây dựng dòng thời gian của mình."
+ pushNotificationDescription: "Bật thông báo đẩy sẽ cho phép bạn nhận thông báo từ {name} trực tiếp từ thiết bị của bạn."
+ initialAccountSettingCompleted: "Thiết lập tài khoản thành công!"
+ haveFun: "Hãy tận hưởng {name} nhé!"
+ skipAreYouSure: "Bạn thực sự muốn bỏ qua mục thiết lập tài khoản?"
+ laterAreYouSure: "Bạn thực sự muốn thiết lập tài khoản vào lúc khác?"
+_serverSettings:
+ iconUrl: "Biểu tượng URL"
+ appIconResolutionMustBe: "Độ phân giải tối thiểu là {resolution}."
+ manifestJsonOverride: "Ghi đè manifest.json"
+_accountMigration:
+ moveFrom: "Chuyển một tài khoản khác vào tài khoản này"
+ moveFromLabel: "Tài khoản gốc #{n}"
+ moveTo: "Chuyển tài khoản này vào một tài khoản khác"
+ moveCannotBeUndone: "Việc chuyển tài khoản không thể huỷ."
+ moveAccountDescription: "Điều này sẽ chuyển tài khoản này sang một tài khoản khác.\n ・Những người theo dõi sẽ tự động được chuyển sang tài khoản mới\n ・Tài khoản này sẽ tự bỏ theo dõi những người mà bạn đã theo dõi trước đây\n ・Bạn sẽ không thể đăng tút mới, v.v trên tài khoản này\n\nDù việc chuyển người theo dõi được diễn ra tự động, bạn vẫn phải tự chuẩn bị một vài bước để chuyển danh sách những người dùng bạn đang theo dõi. Để làm vậy, vui lòng thực hiện việc xuất dữ liệu những người dùng đã theo dõi mà sau này bạn sẽ dùng để nhập vào tài khoản mới ở menu Cài đặt. Hành động tương tự áp dụng với danh sách những người dùng bị chặn hoặc tắt tiếng.\n\n(Điều này áp dụng cho phiên bản Misskey v13.12.0 và sau này. Các phần mềm ActivityPub khác , ví dụ như Mastodon, sẽ có thể hoạt động khác đi.)"
+ startMigration: "Chuyển"
+ movedAndCannotBeUndone: "\nTài khoản này đã được chuyển đi.\nViệc di chuyển tài khoản không thể bị huỷ bỏ."
+ movedTo: "Tài khoản mới:"
_achievements:
earnedAt: "Ngày thu nhận"
_types:
@@ -983,6 +1120,8 @@ _achievements:
title: "Hàng tinh đăng bài"
description: "Đã đăng bài 50,000 lần rồi"
_notes100000:
+ title: "ALL YOUR NOTE ARE BELONG TO US"
+ description: "Đăng 100,000 tút"
flavor: "Liệu viết bài gì tầm này vậy? "
_login3:
title: "Sơ cấp I"
@@ -1014,6 +1153,15 @@ _achievements:
_login400:
title: "Khách hàng thường xuyên cấp III"
description: "Tổng số ngày đăng nhập đạt 400 ngày"
+ _login1000:
+ flavor: "Cảm ơn bạn đã sử dụng Misskey!"
+ _noteFavorited1:
+ title: "Nhà thiên văn học"
+ _myNoteFavorited1:
+ title: "Đi tìm những ngôi sao"
+ _profileFilled:
+ title: "Luôn sẵn sàng"
+ description: "Thiết lập tài khoản của bạn"
_markedAsCat:
title: "Tôi là một con mèo"
description: "Bật chế độ mèo"
@@ -1039,8 +1187,18 @@ _achievements:
_followers10:
title: "FOLLOW ME!!"
description: "Người theo dõi bạn vượt lên 10 người"
+ _followers50:
+ title: "Từng chút một"
+ description: "Đạt được 50 lượt theo dõi"
+ _followers100:
+ title: "Người nổi tiếng"
+ description: "Đạt được 100 lượt theo dõi"
+ _followers300:
+ title: "Vui lòng xếp thành hàng nào"
+ description: "Đạt được 300 lượt theo dõi"
_followers500:
title: "Trạm phát sóng"
+ description: "Đạt được 500 lượt theo dõi"
_followers1000:
title: "Người có tầm ảnh hưởng"
description: "Người theo dõi bạn vượt lên 1000 người"
@@ -1059,11 +1217,15 @@ _achievements:
description: "Tìm thấy được những kho báu cất giấu"
_client30min:
title: "Giải lao xỉu"
+ description: "Giữ Misskey mở trong ít nhất 30 phút"
+ _client60min:
+ description: "Giữ Misskey mở trong ít nhất 60 phút"
_noteDeletedWithin1min:
title: "Xem như không có gì đâu nha"
_postedAtLateNight:
title: "Loài ăn đêm"
description: "Đăng bài trong đêm khuya "
+ flavor: "Đến giờ đi ngủ rồi."
_postedAt0min0sec:
title: "Tín hiệu báo giờ"
description: "Đăng bài vào 0 phút 0 giây"
@@ -1094,6 +1256,8 @@ _achievements:
_setNameToSyuilo:
title: "Ngưỡng mộ với vị thần"
description: "Đạt tên là syuilo"
+ _passedSinceAccountCreated1:
+ title: "Kỷ niệm một năm"
_loggedInOnBirthday:
title: "Sinh nhật vủi vẻ"
description: "Đăng nhập vào ngày sinh"
@@ -1104,6 +1268,7 @@ _achievements:
_cookieClicked:
flavor: "Bạn nhầm phầm mềm chứ?"
_role:
+ assignTarget: "Phân công"
priority: "Ưu tiên"
_priority:
low: "Thấp"
@@ -1178,6 +1343,7 @@ _plugin:
install: "Cài đặt tiện ích"
installWarn: "Vui lòng không cài đặt những tiện ích đáng ngờ."
manage: "Quản lý plugin"
+ viewSource: "Xem mã nguồn"
_preferencesBackups:
list: "Tạo sao lưu"
saveNew: "Lưu bản sao lưu"
@@ -1238,11 +1404,6 @@ _wordMute:
muteWords: "Ẩn từ ngữ"
muteWordsDescription: "Separate with spaces for an AND condition or with line breaks for an OR condition."
muteWordsDescription2: "Bao quanh các từ khóa bằng dấu gạch chéo để sử dụng cụm từ thông dụng."
- softDescription: "Ẩn các tút phù hợp điều kiện đã đặt khỏi bảng tin."
- hardDescription: "Ngăn các tút đáp ứng các điều kiện đã đặt xuất hiện trên bảng tin. Lưu ý, những tút này sẽ không được thêm vào bảng tin ngay cả khi các điều kiện được thay đổi."
- soft: "Yếu"
- hard: "Mạnh"
- mutedNotes: "Những tút đã ẩn"
_instanceMute:
instanceMuteDescription: "Thao tác này sẽ ẩn mọi tút/lượt đăng lại từ các máy chủ được liệt kê, bao gồm cả những tút dạng trả lời từ máy chủ bị ẩn."
instanceMuteDescription2: "Tách bằng cách xuống dòng"
@@ -1306,9 +1467,6 @@ _theme:
infoFg: "Chữ thông tin"
infoWarnBg: "Nền cảnh báo"
infoWarnFg: "Chữ cảnh báo"
- cwBg: "Nền nút nội dung ẩn"
- cwFg: "Chữ nút nội dung ẩn"
- cwHoverBg: "Nền nút nội dung ẩn (Chạm)"
toastBg: "Nền thông báo"
toastFg: "Chữ thông báo"
buttonBg: "Nền nút"
@@ -1326,8 +1484,6 @@ _sfx:
note: "Tút"
noteMy: "Tút của tôi"
notification: "Thông báo"
- chat: "Trò chuyện"
- chatBg: "Chat (Nền)"
antenna: "Trạm phát sóng"
channel: "Kênh"
_ago:
@@ -1348,13 +1504,19 @@ _time:
day: "ngày"
_2fa:
alreadyRegistered: "Bạn đã đăng ký thiết bị xác minh 2 bước."
- passwordToTOTP: "Nhắn mật mã"
+ registerTOTP: "Đăng ký ứng dụng xác thực"
step1: "Trước tiên, hãy cài đặt một ứng dụng xác minh (chẳng hạn như {a} hoặc {b}) trên thiết bị của bạn."
step2: "Sau đó, quét mã QR hiển thị trên màn hình này."
- step2Url: "Bạn cũng có thể nhập URL này nếu sử dụng một chương trình máy tính:"
+ step2Click: "Quét mã QR trên ứng dụng xác thực (Authy, Google authenticator, v.v.)"
+ step3Title: "Nhập mã xác thực"
step3: "Nhập mã token do ứng dụng của bạn cung cấp để hoàn tất thiết lập."
- step4: "Kể từ bây giờ, những lần đăng nhập trong tương lai sẽ yêu cầu mã token đăng nhập đó.\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}"
+ step4: "Kể từ bây giờ, những lần đăng nhập trong tương lai sẽ yêu cầu mã token đăng nhập đó."
+ securityKeyNotSupported: "Trình duyệt của bạn không hỗ trợ khóa bảo mật"
+ registerTOTPBeforeKey: "Vui lòng thiết lập một ứng dụng xác thực để đăng ký khóa bảo mật hoặc mật khẩu."
securityKeyInfo: "Bên cạnh xác minh bằng vân tay hoặc mã PIN, bạn cũng có thể thiết lập xác minh thông qua khóa bảo mật phần cứng hỗ trợ FIDO2 để bảo mật hơn nữa cho tài khoản của mình."
+ registerSecurityKey: "Tạo khóa bảo mật hoặc mã bảo mật"
+ securityKeyName: "Nhập tên khóa bảo mật"
+ tapSecurityKey: "Vui lòng làm theo hướng dẫn của trình duyệt để đăng ký mã bảo mật hoặc mã khóa"
removeKey: "Xóa mã bảo mật"
removeKeyConfirm: "Xóa bản sao lưu {name}?"
renewTOTP: "Cài đặt lại ứng dụng xác thực"
@@ -1677,5 +1839,14 @@ _dialog:
charactersExceeded: "Bạn nhắn quá giới hạn ký tự!! Hiện nay {current} / giới hạn {max}"
charactersBelow: "Bạn nhắn quá ít tối thiểu ký tự!! Hiện nay {current} / Tối thiểu {min}"
_webhookSettings:
+ createWebhook: "Tạo Webhook"
name: "Tên"
+ secret: "Mã bí mật"
+ events: "Sự kiện Webhook"
active: "Đã bật"
+ _events:
+ reaction: "Khi nhận được sự kiện"
+ mention: "Khi có người nhắc tới bạn"
+_moderationLogTypes:
+ suspend: "Vô hiệu hóa"
+ resetPassword: "Đặt lại mật khẩu"
diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml
index 9599e3d51e..76bc5c3271 100644
--- a/locales/zh-CN.yml
+++ b/locales/zh-CN.yml
@@ -15,7 +15,7 @@ gotIt: "我明白了"
cancel: "取消"
noThankYou: "不用,谢谢"
enterUsername: "输入用户名"
-renotedBy: "由 {user} 转贴"
+renotedBy: "{user} 转发了"
noNotes: "没有帖文"
noNotifications: "无通知"
instance: "服务器"
@@ -45,6 +45,7 @@ pin: "置顶"
unpin: "取消置顶"
copyContent: "复制内容"
copyLink: "复制链接"
+copyLinkRenote: "复制转帖链接"
delete: "删除"
deleteAndEdit: "删除并编辑"
deleteAndEditConfirm: "要删除此帖并再次编辑吗?对此帖的所有回应、转发和回复也将被删除。"
@@ -155,13 +156,14 @@ emojiUrl: "emoji 地址"
addEmoji: "添加表情符号"
settingGuide: "推荐配置"
cacheRemoteFiles: "缓存远程文件"
-cacheRemoteFilesDescription: "当禁用此设定时远程文件将直接从远程服务器载入。禁用后会减小储存空间需求,但是会增加流量,因为缩略图不会被生成。"
+cacheRemoteFilesDescription: "启用此设定时,将在此服务器上缓存远程文件。虽然可以加快图片显示的速度,但是相对的会消耗大量的服务器存储空间。用户角色内的网盘容量决定了这个远程用户能在服务器上保留保留多少缓存。当超出了这个限制时,旧的文件将从缓存中被删除,成为链接。当禁用此设定时,则是从一开始就将远程文件保留为链接。此时推荐将 default.yml 的 proxyRemoteFiles 设置为 true 以优化缩略图生成及保护用户隐私。"
+youCanCleanRemoteFilesCache: "可以使用文件管理的🗑️按钮来删除所有的缓存。"
cacheRemoteSensitiveFiles: "缓存远程敏感媒体文件"
cacheRemoteSensitiveFilesDescription: "如果禁用这项设定,远程服务器的敏感媒体将不会被缓存,而是直接链接。"
flagAsBot: "这是一个机器人账号"
flagAsBotDescription: "如果此账户由程序控制,请启用此项。启用后,此标志可以帮助其他开发人员防止机器人之间产生无限互动的行为,并让 Misskey 的内部系统将此账户识别为机器人。"
-flagAsCat: "将这个账户设定为一只猫"
-flagAsCatDescription: "如果您想表明此帐户是一只猫,请打开此标志。\n开启后,会在您的头像上出现猫耳朵,并将你的帖子中的「na」替换为「nya」,日文同理。"
+flagAsCat: "喵!!!!!!!!!!!!"
+flagAsCatDescription: "喵喵喵??"
flagShowTimelineReplies: "在时间线上显示帖子的回复"
flagShowTimelineRepliesDescription: "启用时,时间线除了显示用户的帖子外,还会显示其他用户对帖子的回复。"
autoAcceptFollowed: "自动允许来自我关注的用户对我的关注请求"
@@ -177,7 +179,7 @@ searchWith: "搜索:{q}"
youHaveNoLists: "列表为空"
followConfirm: "你确定要关注 {name} 吗?"
proxyAccount: "代理账户"
-proxyAccountDescription: "代理账户是在某些情况下充当用户的远程关注者的账户。 例如,当一个用户列出一个远程用户时,如果没有人跟随该列出的用户,则该活动将不会传递到该服务器,因此将代之以代理账户。"
+proxyAccountDescription: "代理账户是在某些情况下替代用户进行远程关注用的账户。 例如说,当用户将一位远程用户放入一个列表中时,如果本地服务器上没有任何人关注这位远程用户,则这位远程用户的账户活动将不会被送到本地服务器上。作为替代,此时将使用代理账户进行关注。"
host: "主机名"
selectUser: "选择用户"
recipient: "收件人"
@@ -193,6 +195,7 @@ perHour: "每小时"
perDay: "每天"
stopActivityDelivery: "停止发送活动"
blockThisInstance: "阻止此服务器向本服务器推流"
+silenceThisInstance: "使服务器静音"
operations: "操作"
software: "软件"
version: "版本"
@@ -211,7 +214,9 @@ clearQueueConfirmText: "未送达的帖子将不会投递。 通常,您不需
clearCachedFiles: "清除缓存"
clearCachedFilesConfirm: "确定要清除缓存文件?"
blockedInstances: "被封锁的服务器"
-blockedInstancesDescription: "设定要封锁的服务器,以换行来进行分割。被封锁的服务器将无法与本服务器进行交换通讯。"
+blockedInstancesDescription: "设定要封锁的服务器,以换行来进行分割。被封锁的服务器将无法与本服务器进行交换通讯。子域名也同样会被封锁。"
+silencedInstances: "沉默的服务器"
+silencedInstancesDescription: "设置要静音的服务器的主机,以换行符分隔。属于静默服务器的所有帐户都将被视为“静默”,所有关注都将成为请求,并且您将无法提及非关注者的本地帐户。被阻止的实例不受影响。"
muteAndBlock: "屏蔽/拉黑"
mutedUsers: "已屏蔽用户"
blockedUsers: "已拉黑的用户"
@@ -354,7 +359,6 @@ invite: "邀请"
driveCapacityPerLocalAccount: "每个用户的网盘容量"
driveCapacityPerRemoteAccount: "每个远程用户的网盘容量"
inMb: "以兆字节(MegaByte)为单位"
-iconUrl: "图标 URL"
bannerUrl: "横幅 URL"
backgroundImageUrl: "背景图 URL"
basicInfo: "基本信息"
@@ -410,10 +414,14 @@ aboutMisskey: "关于 Misskey"
administrator: "管理员"
token: "Token (令牌)"
2fa: "双因素认证"
+setupOf2fa: "设置双因素认证"
totp: "身份验证应用"
totpDescription: "使用认证应用输入一次性密码。"
moderator: "监察员"
moderation: "管理"
+moderationNote: "管理笔记"
+addModerationNote: "添加管理笔记"
+moderationLogs: "管理日志"
nUsersMentioned: "{n} 被提到"
securityKeyAndPasskey: "安全密钥或 Passkey"
securityKey: "安全密钥"
@@ -507,13 +515,13 @@ showFeaturedNotesInTimeline: "在时间线上显示热门推荐"
objectStorage: "对象存储"
useObjectStorage: "使用对象存储"
objectStorageBaseUrl: "Base URL"
-objectStorageBaseUrlDesc: "这里是用于引用的 URL,如果您正在使用 CDN 或反向代理,请指定其 URL,例如S3:“https://.s3.amazonaws.com”,GCS:“https://storage.googleapis.com/”"
+objectStorageBaseUrlDesc: "这里是用于参考的 URL,如果您正在使用 CDN 或反向代理,请指定其 URL,例如 S3:“https://.s3.amazonaws.com”,GCS:“https://storage.googleapis.com/”"
objectStorageBucket: "存储桶"
objectStorageBucketDesc: "请指定使用的对象存储服务的存储桶名称。"
objectStoragePrefix: "前缀"
objectStoragePrefixDesc: "文件将存储在此前缀的目录下。"
-objectStorageEndpoint: "Endpoint"
-objectStorageEndpointDesc: "如果你使用 AWS S3 请留空。否则请根据你使用的服务商的说明来进行设置,指定 Endpoint 形式为“”或“:”。"
+objectStorageEndpoint: "端点"
+objectStorageEndpointDesc: "如果你使用 AWS S3 请留空。否则请根据你使用的服务商的说明来进行设置,指定端点形式为“”或“:”。"
objectStorageRegion: "可用区"
objectStorageRegionDesc: "指定一个可用区,例如“xx-east-1”。 如果您的对象存储服务没有可用区概念,请将其留空或填写“us-east-1”。如果引用 AWS 的配置文件或环境变量,则留空。"
objectStorageUseSSL: "使用 SSL"
@@ -653,6 +661,7 @@ behavior: "行为"
sample: "示例"
abuseReports: "举报"
reportAbuse: "举报"
+reportAbuseRenote: "举报转帖"
reportAbuseOf: "举报 {name}"
fillAbuseReportDescription: "请填写举报的详细原因。如果有对方发的帖子,请同时填写 URL 地址。"
abuseReported: "内容已发送。感谢您提交信息。"
@@ -680,6 +689,7 @@ createNewClip: "新建便签"
unclip: "移除便签"
confirmToUnclipAlreadyClippedNote: "本帖已包含在便签 \"{name}\" 里。您想要将本帖从该便签中移除吗?"
public: "公开"
+private: "私密"
i18nInfo: "Misskey 已经被志愿者们翻译成了各种语言。如果你也有兴趣,可以通过 {link} 帮助翻译。"
manageAccessTokens: "管理 Access Tokens"
accountInfo: "账户信息"
@@ -704,6 +714,7 @@ lockedAccountInfo: "即使启用该功能,只要您不将帖子可见范围设
alwaysMarkSensitive: "默认将媒体文件标记为敏感内容"
loadRawImages: "添加附件图像的缩略图时使用原始图像质量"
disableShowingAnimatedImages: "不播放动画"
+highlightSensitiveMedia: "高亮显示敏感媒体"
verificationEmailSent: "已发送确认电子邮件。请访问电子邮件中的链接以完成设置。"
notSet: "未设置"
emailVerified: "电子邮件地址已验证"
@@ -1018,7 +1029,7 @@ retryAllQueuesConfirmText: "可能会使服务器负荷在一定时间内增加"
enableChartsForRemoteUser: "生成远程用户的图表"
enableChartsForFederatedInstances: "生成远程服务器的图表"
showClipButtonInNoteFooter: "在贴文下方显示便签按钮"
-largeNoteReactions: "使用大图标来显示回应"
+reactionsDisplaySize: "回应显示大小"
noteIdOrUrl: "帖子 ID 或 URL"
video: "视频"
videos: "视频"
@@ -1094,6 +1105,42 @@ expired: "已过期"
doYouAgree: "你同意吗?"
beSureToReadThisAsItIsImportant: "请好好阅读,这真的很重要。"
iHaveReadXCarefullyAndAgree: "我已经仔细阅读并同意了「{x}」的内容。"
+dialog: "对话框"
+icon: "头像"
+forYou: "您的"
+currentAnnouncements: "现在的公告"
+pastAnnouncements: "过去的公告"
+youHaveUnreadAnnouncements: "您有未读的公告"
+useSecurityKey: "请根据浏览器或设备的提示,使用安全密钥或通行密钥。"
+replies: "回复"
+renotes: "转发"
+loadReplies: "查看回复"
+loadConversation: "查看对话"
+pinnedList: "已置顶的列表"
+keepScreenOn: "保持设备屏幕开启"
+verifiedLink: "已验证的链接"
+notifyNotes: "打开发帖通知"
+unnotifyNotes: "关闭发帖通知"
+authentication: "验证"
+authenticationRequiredToContinue: "要继续,请先进行验证"
+dateAndTime: "日期和时间"
+showRenotes: "显示转帖"
+edited: "已编辑"
+notificationRecieveConfig: "通知接收设置"
+mutualFollow: "互相关注"
+fileAttachedOnly: "仅限媒体"
+showRepliesToOthersInTimeline: "在时间线上显示给其他人的回复"
+hideRepliesToOthersInTimeline: "在时间线上隐藏给其他人的回复"
+flip: "翻转"
+_announcement:
+ forExistingUsers: "仅限现有用户"
+ forExistingUsersDescription: "若启用,该公告将仅对创建此公告时存在的用户可见。 如果禁用,则在创建此公告后注册的用户也可以看到该公告。"
+ needConfirmationToRead: "需要确认才能标记为已读"
+ needConfirmationToReadDescription: "若启用,则会在标记已读时会显示确认对话框。此外,它也会不受批量已读操作的影响。"
+ end: "结束公告"
+ tooManyActiveAnnouncementDescription: "若有大量活动公告,可能会造成用户体验可能下降。请考虑归档已完成的公告。"
+ readConfirmTitle: "标记为已读?"
+ readConfirmText: "阅读“{title}”的内容并将其标记为已读。"
_initialAccountSetting:
accountCreated: "账户创建完成了!"
letsStartAccountSetup: "来进行帐户的初始设置吧。"
@@ -1106,11 +1153,19 @@ _initialAccountSetting:
pushNotificationDescription: "启用推送通知的话,就可以在设备上接收来自 {name} 的通知了。"
initialAccountSettingCompleted: "初始设定已经完成了!"
haveFun: "希望 {name} 在这里玩得开心!"
- ifYouNeedLearnMore: "关于 {name}(Misskey) 的使用方法,详见 {link}。"
skipAreYouSure: "要跳过初始设置吗?"
laterAreYouSure: "要稍后再进行初始设定吗?"
_serverRules:
description: "在新用户注册前显示服务器的简单规则。推荐显示服务条款的主要内容。"
+_serverSettings:
+ iconUrl: "图标 URL"
+ appIconDescription: "指定当 {host} 显示为 app 时的图标。"
+ appIconUsageExample: "例如:作为书签添加到 PWA 或手机主屏幕的时候"
+ appIconStyleRecommendation: "因为有可能会被裁切为圆形或者圆角矩形,建议使用边缘带有留白背景的图标。"
+ appIconResolutionMustBe: "分辨率必须为 {resolution}。"
+ manifestJsonOverride: "覆盖 mainfest.json"
+ shortName: "简称"
+ shortNameDescription: "如果服务器的正式名称很长,可以用简称或者別名来替代。"
_accountMigration:
moveFrom: "从别的账号迁移到此账户"
moveFromSub: "为另一个账户建立别名"
@@ -1365,6 +1420,9 @@ _achievements:
title: "Brain Diver"
description: "发布了包含 Brain Diver 链接的帖子"
flavor: "Misskey-Misskey La-Tu-Ma"
+ _smashTestNotificationButton:
+ title: "过度测试"
+ description: "短时间内连续测试通知"
_role:
new: "创建角色"
edit: "编辑角色"
@@ -1488,6 +1546,7 @@ _plugin:
install: "安装插件"
installWarn: "请不要安装不可信的插件。"
manage: "管理插件..."
+ viewSource: "查看源代码"
_preferencesBackups:
list: "已创建的备份"
saveNew: "另存为"
@@ -1554,11 +1613,6 @@ _wordMute:
muteWords: "禁用词"
muteWordsDescription: "AND 条件用空格分隔,OR 条件用换行符分隔。"
muteWordsDescription2: "正则表达式用斜线包裹"
- softDescription: "隐藏时间线中指定条件的帖子。"
- hardDescription: "防止将具有指定条件的帖子添加到时间线。 即使您更改条件,未添加的帖文也会被排除在外。"
- soft: "软屏蔽"
- hard: "硬屏蔽"
- mutedNotes: "被屏蔽的帖子"
_instanceMute:
instanceMuteDescription: "屏蔽服务器中的所有帖子和转帖,包括这些服务器上的用户回复。"
instanceMuteDescription2: "一行一个"
@@ -1622,9 +1676,6 @@ _theme:
infoFg: "信息文本"
infoWarnBg: "警告背景"
infoWarnFg: "警告文本"
- cwBg: "隐藏内容按钮背景"
- cwFg: "隐藏内容按钮文本"
- cwHoverBg: "隐藏内容按钮背景(悬停)"
toastBg: "Toast 通知背景"
toastFg: "Toast 通知文本"
buttonBg: "按钮背景"
@@ -1642,8 +1693,6 @@ _sfx:
note: "帖子"
noteMy: "我的帖子"
notification: "通知"
- chat: "聊天"
- chatBg: "聊天背景"
antenna: "天线接收"
channel: "频道通知"
_ago:
@@ -1662,27 +1711,17 @@ _time:
minute: "分"
hour: "小时"
day: "日"
-_timelineTutorial:
- title: "Misskey 的使用方法"
- step1_1: "这个画面是「时间线」。{name}的投稿会按照帖子的发布时间顺序来显示。"
- step1_2: "时间线有许多种类,比如在「首页时间线」中展现的是你关注的人的贴文;而在「本地时间线」中展现的是{name}里全部用户的贴文。"
- step2_1: "那么接下来,试着写一些什么东西来发布吧!你可以通过点击屏幕上的铅笔图标来打开投稿页面。"
- step2_2: "第一次发布的帖子内容,建议包含自我介绍,以及「开始使用{name}了」。"
- step3_1: "将想说的话发出去了吗?"
- step3_2: "太棒了!现在你可以在你的时间线中看到刚刚发布的帖子了。"
- step4_1: "试着对帖子使用「回应」吧!"
- step4_2: "在他人的帖子上按下「+」图标,即可选择想要的表情来进行「回应」。"
_2fa:
alreadyRegistered: "此设备已被注册"
registerTOTP: "开始设置认证应用"
- passwordToTOTP: "请输入您的密码"
step1: "首先,在您的设备上安装验证应用,例如 {a} 或 {b}。"
step2: "然后,扫描屏幕上显示的二维码。"
step2Click: "通过点击二维码,您可以使用设备上安装的身份验证器应用程序或密钥环进行注册"
- step2Url: "在桌面应用程序中输入以下 URL:"
+ step2Uri: "如果使用桌面应用程序的话,请输入下面的 URI"
step3Title: "输入验证码"
step3: "输入您的应用提供的动态口令以完成设置。"
- step4: "从现在开始,任何登录操作都将要求您提供动态口令。\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}"
+ setupCompleted: "设置完成"
+ step4: "从现在开始,任何登录操作都将要求您提供动态口令。"
securityKeyNotSupported: "您的浏览器不支持安全密钥。"
registerTOTPBeforeKey: "要注册安全密钥或 Passkey,请先设置验证器应用程序。"
securityKeyInfo: "注册兼容 WebAuthn 的密钥,例如支持 FIDO2 的硬件安全密钥、设备上的生物识别功能、PIN 码以及 Passkey 等。"
@@ -1696,6 +1735,11 @@ _2fa:
renewTOTPConfirm: "当前验证器应用程序的验证码将不再有效"
renewTOTPOk: "重新配置"
renewTOTPCancel: "不用,谢谢"
+ checkBackupCodesBeforeCloseThisWizard: "在关闭此窗口前,请确认下面的备用代码"
+ backupCodes: "备用代码"
+ backupCodesDescription: "如果无法使用认证应用,可以使用以下的备用代码来访问账户。请务必将这些代码保存在安全的地方。每个代码仅可使用一次。"
+ backupCodeUsedWarning: "已使用备用代码。如果无法使用认证应用,请尽快重新设定。"
+ backupCodesExhaustedWarning: "已使用完所有的备用代码。如果无法使用认证应用,将无法再访问您的账户。请再次设定认证应用。"
_permissions:
"read:account": "查看账户信息"
"write:account": "更改帐户信息"
@@ -1729,6 +1773,10 @@ _permissions:
"write:gallery": "操作图库"
"read:gallery-likes": "读取喜欢的图片"
"write:gallery-likes": "操作喜欢的图片"
+ "read:flash": "查看 Play"
+ "write:flash": "编辑 Play"
+ "read:flash-likes": "查看 Play 的点赞"
+ "write:flash-likes": "编辑 Play 的点赞列表"
_auth:
shareAccessTitle: "应用程序授权许可"
shareAccess: "您要授权允许 “{name}” 访问您的帐户吗?"
@@ -1744,6 +1792,7 @@ _antennaSources:
homeTimeline: "已关注用户的帖子"
users: "来自指定用户的帖子"
userList: "来自指定列表中的帖子"
+ userBlacklist: "除掉已选择用户后所有的帖子"
_weekday:
sunday: "星期日"
monday: "星期一"
@@ -1843,6 +1892,7 @@ _profile:
metadataContent: "内容"
changeAvatar: "修改头像"
changeBanner: "修改横幅"
+ verifiedLinkDescription: "如果将内容设置为 URL,当链接所指向的网页内包含自己的个人资料链接时,可以显示一个已验证图标。"
_exportOrImport:
allNotes: "所有帖子"
favoritedNotes: "收藏的帖子"
@@ -1961,11 +2011,17 @@ _notification:
youReceivedFollowRequest: "您有新的关注请求"
yourFollowRequestAccepted: "您的关注请求已通过"
pollEnded: "问卷调查结果已生成。"
+ newNote: "新的帖子"
unreadAntennaNote: "天线 {name}"
emptyPushNotificationMessage: "推送通知已更新"
achievementEarned: "获得成就"
+ testNotification: "测试通知"
+ checkNotificationBehavior: "检查通知显示"
+ sendTestNotification: "发送测试通知"
+ notificationWillBeDisplayedLikeThis: "通知将会这样表示"
_types:
all: "全部"
+ note: "用户的新帖子"
follow: "关注中"
mention: "提及"
reply: "回复"
@@ -1999,6 +2055,8 @@ _deck:
introduction2: "您可以随时通过屏幕右侧的 + 来添加列"
widgetsIntroduction: "从列菜单中,选择“小工具编辑”来添加小工具"
useSimpleUiForNonRootPages: "用简易UI表示非根页面"
+ usedAsMinWidthWhenFlexible: "「自适应宽度」被启用的时候,这就是最小的宽度"
+ flexible: "自适应宽度"
_columns:
main: "主列"
widgets: "小工具"
@@ -2033,3 +2091,35 @@ _webhookSettings:
renote: "被转发时"
reaction: "被回应时"
mention: "被提及时"
+_moderationLogTypes:
+ createRole: "创建角色"
+ deleteRole: "删除角色"
+ updateRole: "更新角色"
+ assignRole: "分配角色"
+ unassignRole: "取消分配角色"
+ suspend: "冻结"
+ unsuspend: "解除冻结"
+ addCustomEmoji: "添加自定义表情符号"
+ updateCustomEmoji: "更新自定义表情符号"
+ deleteCustomEmoji: "删除自定义表情符号"
+ updateServerSettings: "更新服务器设置"
+ updateUserNote: "更新管理笔记"
+ deleteDriveFile: "删除文件"
+ deleteNote: "删除帖子"
+ createGlobalAnnouncement: "创建全体通知"
+ createUserAnnouncement: "创建用户通知"
+ updateGlobalAnnouncement: "更新全体通知"
+ updateUserAnnouncement: "更新用户通知"
+ deleteGlobalAnnouncement: "删除全体通知"
+ deleteUserAnnouncement: "删除用户通知"
+ resetPassword: "重置密码"
+ markSensitiveDriveFile: "标记网盘文件为敏感媒体"
+ unmarkSensitiveDriveFile: "取消标记网盘文件为敏感媒体"
+ resolveAbuseReport: "处理举报"
+ createInvitation: "发行邀请码"
+ createAd: "创建了广告"
+ deleteAd: "删除了广告"
+ updateAd: "更新了广告"
+_fileViewer:
+ url: "URL"
+ uploadedAt: "添加日期"
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index cbb2a1a34f..ad0741693f 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.yml
@@ -1,9 +1,9 @@
---
-_lang_: "繁體中文"
-headlineMisskey: "貼文連繫網絡"
-introMisskey: "歡迎!Misskey 是一個開源且去中心化的社群網路服務。\n發佈「貼文」向身邊的人分享您的想法!📡\n利用「反應」表達您對貼文的感覺!👍\n讓我們一起探索新的世界吧!🚀"
-poweredByMisskeyDescription: "{name}是使用開放原始碼平台Misskey的服務之一(稱為 Misskey 伺服器)。\n"
-monthAndDay: "{month}月 {day}日"
+_lang_: "繁體中文(台灣)"
+headlineMisskey: "貼文連繫網路"
+introMisskey: "歡迎!Misskey 是一個開放原始碼且去中心化的社群網路服務。\n發布「貼文」向身邊的人分享您的想法!📡\n利用「反應」表達您對貼文的感覺!👍\n讓我們一起探索新的世界吧!🚀"
+poweredByMisskeyDescription: "{name}是開放原始碼平臺 Misskey 的伺服器之一。"
+monthAndDay: "{month} 月 {day} 日"
search: "搜尋"
notifications: "通知"
username: "使用者名稱"
@@ -16,7 +16,7 @@ cancel: "取消"
noThankYou: "現在不要"
enterUsername: "輸入使用者名稱"
renotedBy: "{user} 轉發了"
-noNotes: "無貼文。"
+noNotes: "無貼文"
noNotifications: "沒有通知"
instance: "伺服器"
settings: "設定"
@@ -26,7 +26,7 @@ otherSettings: "其他設定"
openInWindow: "在新視窗開啟"
profile: "個人檔案"
timeline: "時間軸"
-noAccountDescription: "此用戶還沒有自我介紹"
+noAccountDescription: "此使用者尚未自我介紹"
login: "登入"
loggingIn: "登入中"
logout: "登出"
@@ -45,6 +45,7 @@ pin: "置頂"
unpin: "取消置頂"
copyContent: "複製內容"
copyLink: "複製連結"
+copyLinkRenote: "複製轉發的連結"
delete: "刪除"
deleteAndEdit: "刪除並編輯"
deleteAndEditConfirm: "要刪除並再次編輯嗎?此貼文的所有反應、轉發和回覆也將會消失。"
@@ -55,7 +56,7 @@ copyRSS: "複製RSS"
copyUsername: "複製使用者名稱"
copyUserId: "複製使用者 ID"
copyNoteId: "複製貼文 ID"
-copyFileId: "複製檔案ID"
+copyFileId: "複製檔案 ID"
copyFolderId: "複製資料夾ID"
copyProfileUrl: "複製個人資料網址"
searchUser: "搜尋使用者"
@@ -74,9 +75,9 @@ import: "匯入"
export: "匯出"
files: "檔案"
download: "下載"
-driveFileDeleteConfirm: "確定要刪除檔案「{name}」嗎?使用此附件的貼文也會跟著消失。\n"
+driveFileDeleteConfirm: "確定要刪除檔案「{name}」嗎?使用此檔案的貼文也會跟著被刪除。"
unfollowConfirm: "確定要取消追隨{name}嗎?"
-exportRequested: "已請求匯出。這可能會花一點時間。匯出的檔案將會被放到雲端裡。"
+exportRequested: "已請求匯出。這可能會花一點時間。匯出的檔案將會被放到雲端硬碟裡。"
importRequested: "已請求匯入。這可能會花一點時間。"
lists: "清單"
noLists: "你沒有任何清單"
@@ -106,7 +107,7 @@ followRequestPending: "追隨許可待批准"
enterEmoji: "輸入表情符號"
renote: "轉發"
unrenote: "取消轉發"
-renoted: "轉發成功"
+renoted: "轉發成功。"
cantRenote: "無法轉發此貼文。"
cantReRenote: "無法轉發之前已經轉發過的內容。"
quote: "引用"
@@ -135,10 +136,10 @@ block: "封鎖"
unblock: "解除封鎖"
suspend: "凍結"
unsuspend: "解除凍結"
-blockConfirm: "確定要封鎖此用戶?"
-unblockConfirm: "確定解除封鎖此用戶?"
-suspendConfirm: "確定凍結此帳戶?"
-unsuspendConfirm: "確定解凍此帳戶?"
+blockConfirm: "確定要封鎖此使用者嗎?"
+unblockConfirm: "確定要解除封鎖此使用者嗎?"
+suspendConfirm: "確定凍結此使用者?"
+unsuspendConfirm: "確定解凍此使用者?"
selectList: "選擇清單"
editList: "編輯清單"
selectChannel: "選擇頻道"
@@ -151,19 +152,20 @@ customEmojis: "自訂表情符號"
emoji: "表情符號"
emojis: "表情符號"
emojiName: "表情符號名稱"
-emojiUrl: "表情符號URL"
+emojiUrl: "表情符號 URL"
addEmoji: "新增表情符號"
settingGuide: "推薦設定"
cacheRemoteFiles: "快取遠端檔案"
-cacheRemoteFilesDescription: "禁用此設定會停止遠端檔案的緩存,從而節省儲存空間,但資料會因直接連線從而產生額外連接數據。"
+cacheRemoteFilesDescription: "啟用此設定後,遠端檔案會被快取在本伺服器的儲存空間中。雖然顯示圖片會變快,但會消耗較多伺服器的儲存空間。至於要快取遠端使用者到什麼程度,是依照角色的雲端硬碟容量而定。當超過這個限制時,從較舊的檔案開始自快取中刪除並改為連結。關閉這個設定時,遠端檔案從一開始就維持連結的方式,但建議將 default.yml 的 proxyRemoteFiles 設為 true,以便產生圖片的縮圖並保護使用者的隱私。"
+youCanCleanRemoteFilesCache: "按檔案管理的🗑️按鈕,可將快取全部刪除。"
cacheRemoteSensitiveFiles: "快取遠端的敏感檔案"
cacheRemoteSensitiveFilesDescription: "若停用這個設定,則不會快取遠端的敏感檔案,而是直接連結。"
flagAsBot: "此使用者是機器人"
-flagAsBotDescription: "如果本帳戶是由程式控制,請啟用此選項。啟用後,會作為標示幫助其他開發者防止機器人之間產生無限互動的行為,並會調整Misskey內部系統將本帳戶識別為機器人。"
+flagAsBotDescription: "如果本帳戶是由程式控制,請啟用此選項。啟用後,會作為標示幫助其他開發者防止機器人之間產生無限互動的行為,並會調整 Misskey 內部系統將本帳戶識別為機器人。"
flagAsCat: "此帳戶是一隻貓,喵~~~!!!"
flagAsCatDescription: "如果想將本帳戶標示為一隻貓,請開啟此標示"
flagShowTimelineReplies: "在時間軸上顯示貼文的回覆"
-flagShowTimelineRepliesDescription: "啟用時,時間線除了顯示用戶的貼文以外,還會顯示用戶對其他貼文的回覆。"
+flagShowTimelineRepliesDescription: "啟用後,時間軸除了顯示使用者的貼文以外,還會顯示使用者對其他貼文的回覆。"
autoAcceptFollowed: "自動允許來自追隨中使用者的追隨請求"
addAccount: "新增帳戶"
reloadAccountsList: "更新帳戶清單的資訊"
@@ -177,7 +179,7 @@ searchWith: "搜尋: {q}"
youHaveNoLists: "你沒有任何清單"
followConfirm: "你真的要追隨{name}嗎?"
proxyAccount: "代理帳戶"
-proxyAccountDescription: "代理帳戶是在某些情況下充當其他伺服器用戶的帳戶。例如,當使用者將一個來自其他伺服器的帳戶放在列表中時,由於沒有其他使用者追隨該帳戶,該指令不會傳送到該伺服器上,因此會由代理帳戶追隨。"
+proxyAccountDescription: "代理帳戶是在特定條件下充當遠端追隨者的帳戶。例如,當使用者新增遠端使用者至其列表時,若沒有本地使用者追隨該遠端使用者,則其活動將不會傳送至伺服器,此時便會由代理帳戶代為追隨以解決問題。"
host: "主機"
selectUser: "選取使用者"
recipient: "收件人"
@@ -193,14 +195,15 @@ perHour: "每小時"
perDay: "每日"
stopActivityDelivery: "停止發送活動"
blockThisInstance: "封鎖此伺服器"
+silenceThisInstance: "禁言此伺服器"
operations: "操作"
software: "軟體"
version: "版本"
metadata: "元資料"
-withNFiles: "{n}個檔案"
+withNFiles: "{n} 個檔案"
monitor: "監視器"
jobQueue: "佇列"
-cpuAndMemory: "CPU及記憶體用量"
+cpuAndMemory: "CPU 及記憶體"
network: "網路"
disk: "硬碟"
instanceInfo: "伺服器資訊"
@@ -212,9 +215,11 @@ clearCachedFiles: "清除快取資料"
clearCachedFilesConfirm: "確定要清除所有遠端暫存資料嗎?"
blockedInstances: "已封鎖的伺服器"
blockedInstancesDescription: "請逐行輸入需要封鎖的伺服器。已封鎖的伺服器將無法與本伺服器進行通訊。"
+silencedInstances: "被禁言的伺服器"
+silencedInstancesDescription: "設定要禁言的伺服器主機名稱,以換行分隔。隸屬於禁言伺服器的所有帳戶都將被視為「禁言帳戶」,只能發出「追隨請求」,而且無法提及未追隨的本地帳戶。這不會影響已封鎖的實例。"
muteAndBlock: "靜音和封鎖"
-mutedUsers: "已靜音用戶"
-blockedUsers: "已封鎖用戶"
+mutedUsers: "被靜音的使用者"
+blockedUsers: "被封鎖的使用者"
noUsers: "沒有任何使用者"
editProfile: "編輯個人檔案"
noteDeleteConfirm: "確定刪除此貼文嗎?"
@@ -236,7 +241,7 @@ publishing: "直播中"
notResponding: "沒有回應"
instanceFollowing: "追隨的伺服器"
instanceFollowers: "伺服器的追隨者"
-instanceUsers: "用戶"
+instanceUsers: "伺服器使用者"
changePassword: "修改密碼"
security: "安全性"
retypedNotMatch: "兩次輸入不一致。"
@@ -246,7 +251,7 @@ newPasswordRetype: "確認密碼"
attachFile: "上傳附件"
more: "更多!"
featured: "精選"
-usernameOrUserId: "使用者名稱或使用者ID"
+usernameOrUserId: "使用者名稱或使用者 ID"
noSuchUser: "使用者不存在"
lookup: "查詢"
announcements: "公告"
@@ -262,16 +267,16 @@ upload: "上傳"
keepOriginalUploading: "保留原圖"
keepOriginalUploadingDescription: "上傳圖片時保留原始圖片。關閉時,瀏覽器會在上傳時生成適用於網路傳送的版本。"
fromDrive: "從雲端空間"
-fromUrl: "從URL"
+fromUrl: "從 URL"
uploadFromUrl: "從網址上傳"
-uploadFromUrlDescription: "您要上傳的文件的URL"
+uploadFromUrlDescription: "您要上傳的檔案網址"
uploadFromUrlRequested: "已請求上傳"
uploadFromUrlMayTakeTime: "還需要一些時間才能完成上傳。"
explore: "探索"
messageRead: "已讀"
noMoreHistory: "沒有更多歷史紀錄"
startMessaging: "開始聊天"
-nUsersRead: "{n}人已讀"
+nUsersRead: "{n} 人已讀"
agreeTo: "我同意{0}"
agree: "同意"
agreeBelow: "同意以下內容"
@@ -284,7 +289,7 @@ activity: "動態"
images: "圖片"
image: "圖片"
birthday: "生日"
-yearsOld: "{age}歲"
+yearsOld: "{age} 歲"
registeredDate: "註冊日期"
location: "位置"
theme: "外觀主題"
@@ -292,9 +297,9 @@ themeForLightMode: "在淺色模式下使用的主題"
themeForDarkMode: "在深色模式下使用的主題"
light: "淺色"
dark: "深色"
-lightThemes: "明亮主題"
-darkThemes: "黑暗主題"
-syncDeviceDarkMode: "同步至此裝置的深色模式設定"
+lightThemes: "淺色主題"
+darkThemes: "深色主題"
+syncDeviceDarkMode: "與設備的深色模式同步"
drive: "雲端硬碟"
fileName: "檔案名稱"
selectFile: "選擇檔案"
@@ -319,7 +324,7 @@ copyUrl: "複製URL"
rename: "重新命名"
avatar: "大頭貼"
banner: "橫幅"
-displayOfSensitiveMedia: "顯示敏感媒體"
+displayOfSensitiveMedia: "敏感檔案的顯示"
whenServerDisconnected: "與伺服器的連接中斷時"
disconnectedFromServer: "與伺服器中斷連線"
reload: "重新整理"
@@ -334,31 +339,30 @@ instanceName: "伺服器名稱"
instanceDescription: "伺服器介紹"
maintainerName: "管理員名稱"
maintainerEmail: "管理員郵箱"
-tosUrl: "服務條款URL"
+tosUrl: "服務條款 URL"
thisYear: "本年"
thisMonth: "本月"
today: "本日"
-dayX: "{day}日"
-monthX: "{month}月"
-yearX: "{year}年"
+dayX: "{day} 日"
+monthX: "{month} 月"
+yearX: "{year} 年"
pages: "頁面"
integration: "整合"
connectService: "已連結"
disconnectService: "已斷開 "
enableLocalTimeline: "啟用本地時間軸"
enableGlobalTimeline: "啟用全域時間軸"
-disablingTimelinesInfo: "為了方便,即使您關閉了時間線功能,管理員和審查員仍可以繼續使用。"
+disablingTimelinesInfo: "為了方便,即使您關閉了時間軸功能,管理員和審查員仍可以繼續使用。"
registration: "註冊"
enableRegistration: "開放新使用者註冊"
invite: "邀請"
-driveCapacityPerLocalAccount: "每個本地用戶的雲端空間大小"
+driveCapacityPerLocalAccount: "每個本地使用者的雲端硬碟容量"
driveCapacityPerRemoteAccount: "每個非本地用戶的雲端空間大小"
inMb: "以Mbps為單位"
-iconUrl: "圖標 URL(例如 favicon)"
bannerUrl: "橫幅圖片URL"
backgroundImageUrl: "背景圖片的來源網址 "
basicInfo: "基本資訊"
-pinnedUsers: "置頂用戶"
+pinnedUsers: "置頂使用者"
pinnedUsersDescription: "在「探索」頁面中使用換行標記想要置頂的使用者。"
pinnedPages: "釘選頁面"
pinnedPagesDescription: "輸入要固定至實例首頁的頁面路徑,以換行符分隔。"
@@ -386,7 +390,7 @@ antennaExcludeKeywords: "排除關鍵字"
antennaKeywordsDescription: "空格代表「以及」(AND),換行代表「或者」(OR)"
notifyAntenna: "通知有新貼文"
withFileAntenna: "僅帶有附件的貼文"
-enableServiceworker: "啟用 ServiceWorker"
+enableServiceworker: "啟用瀏覽器的推播通知"
antennaUsersDescription: "填寫使用者名稱,以換行分隔"
caseSensitive: "區分大小寫"
withReplies: "包含回覆"
@@ -394,7 +398,7 @@ connectedTo: "您的帳戶已連接到以下社交帳戶"
notesAndReplies: "貼文與回覆"
withFiles: "附件"
silence: "禁言"
-silenceConfirm: "確定要禁言此帳戶嗎?"
+silenceConfirm: "確定要禁言此使用者嗎?"
unsilence: "解除禁言"
unsilenceConfirm: "確定要解除禁言嗎?"
popularUsers: "熱門使用者"
@@ -410,24 +414,28 @@ aboutMisskey: "關於 Misskey"
administrator: "管理員"
token: "權杖"
2fa: "雙重驗證"
+setupOf2fa: "設定雙重驗證"
totp: "驗證應用程式"
totpDescription: "以驗證應用程式輸入一次性密碼"
moderator: "審查員"
moderation: "審查"
-nUsersMentioned: "被提及到 {n} 次"
+moderationNote: "管理筆記"
+addModerationNote: "新增管理筆記"
+moderationLogs: "管理日誌"
+nUsersMentioned: "被 {n} 個人提及"
securityKeyAndPasskey: "安全金鑰、Passkey"
securityKey: "安全金鑰"
lastUsed: "上次使用"
lastUsedAt: "上次使用:{t}"
-unregister: "註銷帳戶"
+unregister: "註銷"
passwordLessLogin: "設置無密碼登入"
passwordLessLoginDescription: "不使用密碼,以安全金鑰或 Passkey 登入"
resetPassword: "重設密碼"
newPasswordIs: "新密碼為「{password}」"
reduceUiAnimation: "減少介面的動態視覺"
share: "分享"
-notFound: "找不到"
-notFoundDescription: "找不到該 URL 的頁面"
+notFound: "查無項目"
+notFoundDescription: "查無此頁"
uploadFolder: "預設上傳資料夾"
cacheClear: "清除快取"
markAsReadAllNotifications: "標記所有通知為已讀"
@@ -476,7 +484,7 @@ disableDrawer: "不顯示下拉式選單"
showNoteActionsOnlyHover: "僅在游標停留時顯示貼文的操作選項"
noHistory: "沒有歷史紀錄"
signinHistory: "登入歷史"
-enableAdvancedMfm: "啟用高級 MFM"
+enableAdvancedMfm: "啟用進階 MFM"
enableAnimatedMfm: "啟用 MFM 動畫"
doing: "正在進行"
category: "類別"
@@ -486,8 +494,8 @@ createAccount: "建立帳戶"
existingAccount: "現有帳戶"
regenerate: "再次生成"
fontSize: "字體大小"
-mediaListWithOneImageAppearance: "只有一張圖片時的媒體列表高度"
-limitTo: "上限為{x}"
+mediaListWithOneImageAppearance: "只有一張圖片時的檔案列表高度"
+limitTo: "上限為 {x}"
noFollowRequests: "沒有追隨您的請求"
openImageInNewTab: "於新分頁中開啟圖片"
dashboard: "儀表板"
@@ -504,10 +512,10 @@ promote: "推廣"
numberOfDays: "有效天數"
hideThisNote: "隱藏此貼文"
showFeaturedNotesInTimeline: "在時間軸上顯示熱門推薦"
-objectStorage: "Object Storage (物件儲存)"
-useObjectStorage: "使用Object Storage"
+objectStorage: "物件儲存"
+useObjectStorage: "使用物件儲存"
objectStorageBaseUrl: "Base URL"
-objectStorageBaseUrlDesc: "引用時的 URL。如果您使用的是 CDN 或反向代理,請指定其 URL,例如 S3(https://.s3.amazonaws.com)、GCS(https://storage.googleapis.com/)。"
+objectStorageBaseUrlDesc: "用於引用的 URL。如果您使用的是 CDN 或反向代理,請指定其 URL,例如 S3(https://.s3.amazonaws.com)、GCS(https://storage.googleapis.com/)。"
objectStorageBucket: "儲存空間(Bucket)"
objectStorageBucketDesc: "請填寫所用服務的儲存空間(Bucket)名稱。 "
objectStoragePrefix: "前綴"
@@ -526,13 +534,14 @@ serverLogs: "伺服器日誌"
deleteAll: "刪除所有記錄"
showFixedPostForm: "於時間軸頁頂顯示「發送貼文」方框"
showFixedPostFormInChannel: "於時間軸頁頂顯示「發送貼文」方框(頻道)"
+withRepliesByDefaultForNewlyFollowed: "在追隨其他人後,預設在時間軸納入回覆的貼文"
newNoteRecived: "發現新貼文"
sounds: "音效"
sound: "音效"
listen: "聆聽"
none: "無"
showInPage: "在頁面中顯示"
-popout: "彈出型窗口"
+popout: "彈出式視窗"
volume: "音量"
masterVolume: "主音量"
details: "詳細資訊"
@@ -542,7 +551,7 @@ recentUsed: "最近使用"
install: "安裝"
uninstall: "解除安裝"
installedApps: "已授權的應用程式"
-nothing: "未發現"
+nothing: "查無項目"
installedDate: "安裝時間"
lastUsedDate: "最後上線日期"
state: "狀態"
@@ -550,7 +559,7 @@ sort: "排序"
ascendingOrder: "昇冪"
descendingOrder: "降冪"
scratchpad: "暫存記憶體"
-scratchpadDescription: "AiScript 控制台為 AiScript 的實驗環境。您可以在此編寫、執行和確認程式碼與 Misskey 互動的结果。"
+scratchpadDescription: "AiScript 控制臺為 AiScript 的實驗環境。您可以在此編寫、執行和確認程式碼與 Misskey 互動的結果。"
output: "輸出"
script: "腳本"
disablePagesScript: "停用頁面的 AiScript 腳本"
@@ -559,8 +568,8 @@ deleteAllFiles: "刪除所有檔案"
deleteAllFilesConfirm: "要刪除所有檔案嗎?"
removeAllFollowing: "解除所有追隨"
removeAllFollowingDescription: "解除{host}所有的追隨。在伺服器不再存在時執行。"
-userSuspended: "該使用者已被停用"
-userSilenced: "該用戶已被禁言。"
+userSuspended: "該使用者已被停用。"
+userSilenced: "該使用者已被禁言。"
yourAccountSuspendedTitle: "帳戶已被凍結"
yourAccountSuspendedDescription: "該帳戶已因違反伺服器服務條款或其他原因而被凍結。您可以向管理員查詢更多資訊。請不要建立新帳戶。"
tokenRevoked: "權杖無效"
@@ -568,16 +577,16 @@ tokenRevokedDescription: "登入權杖失效,請重新登入。"
accountDeleted: "帳戶已被刪除"
accountDeletedDescription: "這個帳戶已被刪除。"
menu: "選單"
-divider: "分割線"
+divider: "分隔線"
addItem: "新增項目"
rearrange: "排序方式"
relays: "中繼"
addRelay: "新增中繼"
inboxUrl: "收件夾URL"
addedRelays: "已加入的中繼"
-serviceworkerInfo: "您需要啟用推送通知。"
+serviceworkerInfo: "如要使用推播通知,需要啟用此選項並設定金鑰。"
deletedNote: "已刪除的貼文"
-invisibleNote: "隱藏的貼文"
+invisibleNote: "私密的貼文"
enableInfiniteScroll: "啟用自動滾動頁面模式"
visibility: "可見性"
poll: "投票"
@@ -653,6 +662,7 @@ behavior: "行為"
sample: "範例"
abuseReports: "檢舉"
reportAbuse: "檢舉"
+reportAbuseRenote: "檢舉轉發貼文"
reportAbuseOf: "檢舉{name}"
fillAbuseReportDescription: "請填寫檢舉的詳細理由。如有需要,請附上相關 URL。"
abuseReported: "檢舉完成。感謝您的報告。"
@@ -680,7 +690,8 @@ createNewClip: "建立新摘錄"
unclip: "解除摘錄"
confirmToUnclipAlreadyClippedNote: "此貼文已包含在摘錄「{name}」中。 你想將貼文從這個摘錄中排除嗎?"
public: "公開"
-i18nInfo: "Misskey 已被志願者們翻譯成各種語言版本。您可以瀏覽{link}幫助翻譯。"
+private: "私密"
+i18nInfo: "Misskey 已被志願者們翻譯成各種語言版本。您可以瀏覽 {link} 幫助翻譯。"
manageAccessTokens: "管理存取權杖"
accountInfo: "帳戶資訊"
notesCount: "貼文數量"
@@ -688,7 +699,7 @@ repliesCount: "回覆數量"
renotesCount: "轉發數量"
repliedCount: "回覆數量"
renotedCount: "轉發次數"
-followingCount: "正在追隨的用戶數量"
+followingCount: "正在追隨的使用者數量"
followersCount: "追隨者數量"
sentReactionsCount: "反應發送次數"
receivedReactionsCount: "收到反應次數"
@@ -701,9 +712,10 @@ driveUsage: "雲端硬碟使用量"
noCrawle: "拒絕搜尋引擎索引"
noCrawleDescription: "要求網路搜尋引擎不要索引你的個人資料頁、貼文及頁面等。"
lockedAccountInfo: "即使你通過了追隨者請求,除非你將貼文的可見性設定為 「追隨者」,否則任何人都能看見你的貼文。"
-alwaysMarkSensitive: "預設將多媒體標記為敏感內容"
+alwaysMarkSensitive: "預設標記檔案為敏感內容"
loadRawImages: "以原始圖檔顯示附件圖檔的縮圖"
disableShowingAnimatedImages: "不播放動態圖檔"
+highlightSensitiveMedia: "強調敏感標記"
verificationEmailSent: "已發送驗證電子郵件。請點擊進入電子郵件中的鏈接完成驗證。"
notSet: "未設定"
emailVerified: "已成功驗證您的電郵"
@@ -719,7 +731,7 @@ thisIsExperimentalFeature: "這是實驗性的功能。可能會有變更規格
developer: "開發者"
makeExplorable: "使自己的帳戶能夠在「探索」頁面中顯示"
makeExplorableDescription: "如果關閉,帳戶將不會被顯示在「探索」頁面中。"
-showGapBetweenNotesInTimeline: "分開顯示時間線上的貼文。"
+showGapBetweenNotesInTimeline: "分開顯示時間軸上的貼文"
duplicate: "複製"
left: "左"
center: "置中"
@@ -729,11 +741,11 @@ reloadToApplySetting: "設定將會在頁面重新載入之後生效。要現在
needReloadToApply: "必須重新載入才會生效。"
showTitlebar: "顯示標題列"
clearCache: "清除快取資料"
-onlineUsersCount: "{n}人正在線上"
-nUsers: "{n}用戶"
-nNotes: "{n}貼文"
+onlineUsersCount: "{n} 人上線"
+nUsers: "{n} 使用者"
+nNotes: "{n} 貼文"
sendErrorReports: "傳送錯誤報告"
-sendErrorReportsDescription: "啟用後,問題報告將傳送至開發者以提升軟體品質。問題報告可能包括OS版本,瀏覽器類型,行為歷史記錄等。"
+sendErrorReportsDescription: "傳送問題報告至開發者以提升軟體品質。問題報告可能包括作業系統版本,瀏覽器類型,行為歷史記錄等。"
myTheme: "我的佈景主題"
backgroundColor: "背景"
accentColor: "重點色彩"
@@ -765,18 +777,18 @@ inChannelSearch: "頻道内搜尋"
useReactionPickerForContextMenu: "點擊右鍵開啟反應工具欄"
typingUsers: "{users}輸入中"
jumpToSpecifiedDate: "跳轉到特定日期"
-showingPastTimeline: "顯示過往的時間線"
+showingPastTimeline: "顯示過往的時間軸"
clear: "清除"
markAllAsRead: "全部標示為已讀"
goBack: "返回"
unlikeConfirm: "要取消按讚嗎?"
-fullView: "全熒幕顯示"
+fullView: "全螢幕顯示"
quitFullView: "退出全螢幕顯示"
addDescription: "新增描述"
-userPagePinTip: "在貼文的選單中選擇\"置頂\",即可置頂該貼文至您的個人檔案頁面。"
+userPagePinTip: "在貼文的選單中選擇「置頂」,即可置頂該貼文至您的個人檔案頁面。"
notSpecifiedMentionWarning: "此貼文有未指定的提及"
info: "資訊"
-userInfo: "用戶資料"
+userInfo: "使用者資訊"
unknown: "未知"
onlineStatus: "上線狀態"
hideOnlineStatus: "隱藏上線狀態"
@@ -835,7 +847,7 @@ accountDeletionInProgress: "正在刪除帳戶"
usernameInfo: "在伺服器上您的帳戶是唯一的識別名稱。您可以使用字母 (a ~ z, A ~ Z)、數字 (0 ~ 9) 和下底線 (_)。之後帳戶名是不能更改的。"
aiChanMode: "小藍模式"
devMode: "開發者模式"
-keepCw: "保持 CW"
+keepCw: "保持隱藏內容"
pubSub: "Pub/Sub 帳戶"
lastCommunication: "最近的通信"
resolved: "已解決"
@@ -849,7 +861,7 @@ off: "關閉"
emailRequiredForSignup: "註冊帳戶需要電子郵件地址"
unread: "未讀"
filter: "篩選"
-controlPanel: "控制台"
+controlPanel: "控制臺"
manageAccounts: "管理帳戶"
makeReactionsPublic: "將反應設為公開"
makeReactionsPublicDescription: "將您做過的反應設為公開可見。"
@@ -893,8 +905,8 @@ cropImageAsk: "要剪裁圖片嗎?"
cropYes: "裁剪"
cropNo: "使用原圖"
file: "檔案"
-recentNHours: "過去{n}小時"
-recentNDays: "過去{n}天"
+recentNHours: "過去 {n} 小時"
+recentNDays: "過去 {n} 天"
noEmailServerWarning: "尚未設定電子郵件伺服器。"
thereIsUnresolvedAbuseReportWarning: "有尚未處理的檢舉。"
recommended: "推薦"
@@ -920,7 +932,7 @@ type: "類型"
speed: "速度"
slow: "慢"
fast: "快"
-sensitiveMediaDetection: "敏感性媒體的檢測"
+sensitiveMediaDetection: "敏感檔案的檢測"
localOnly: "僅限本地"
remoteOnly: "僅限遠端"
failedToUpload: "上傳失敗"
@@ -929,8 +941,8 @@ cannotUploadBecauseNoFreeSpace: "由於雲端硬碟沒有可用空間,因此
cannotUploadBecauseExceedsFileSizeLimit: "由於超過了檔案大小的限制,無法上傳。"
beta: "測試版"
enableAutoSensitive: "自動 NSFW 判定"
-enableAutoSensitiveDescription: "如果可用,它將使用機器學習技術判斷多媒體內容是否需要標記 NSFW。即使關閉此功能,也可能會依實例規則而自動啟用。"
-activeEmailValidationDescription: "積極驗證帳戶的電郵地址,以判斷它是否可以通訊。關閉此選項代表只會檢查地址是否符合格式。"
+enableAutoSensitiveDescription: "如果可用,它將使用機器學習技術判斷檔案是否需要標記為敏感。即使關閉此功能,也可能會依實例規則而自動啟用。"
+activeEmailValidationDescription: "積極驗證使用者的電郵地址,以判斷它是否可以通訊。關閉此選項代表只會檢查地址是否符合格式。"
navbar: "導覽列"
shuffle: "隨機"
account: "帳戶"
@@ -967,6 +979,7 @@ assign: "指派"
unassign: "取消指派"
color: "顏色"
manageCustomEmojis: "管理自訂表情符號"
+manageAvatarDecorations: "管理頭像裝飾"
youCannotCreateAnymore: "您無法再建立更多了。"
cannotPerformTemporary: "暫時無法進行"
cannotPerformTemporaryDescription: "由於超過操作次數限制,因此暫時無法進行。請稍後再嘗試。"
@@ -1015,10 +1028,10 @@ drivecleaner: "雲端硬碟清掃器"
retryAllQueuesNow: "立刻重試所有佇列"
retryAllQueuesConfirmTitle: "要現在重試嗎?"
retryAllQueuesConfirmText: "伺服器的負荷可能會暫時增加。"
-enableChartsForRemoteUser: "生成遠端用戶的圖表"
+enableChartsForRemoteUser: "生成遠端使用者的圖表"
enableChartsForFederatedInstances: "生成遠端伺服器的圖表"
-showClipButtonInNoteFooter: "新增摘錄至貼文"
-largeNoteReactions: "放大顯示貼文反應"
+showClipButtonInNoteFooter: "新增摘錄按鈕至貼文"
+reactionsDisplaySize: "反應的顯示尺寸"
noteIdOrUrl: "貼文ID或URL"
video: "影片"
videos: "影片"
@@ -1094,6 +1107,70 @@ expired: "過期"
doYouAgree: "你同意嗎?"
beSureToReadThisAsItIsImportant: "重要,請務必閱讀。"
iHaveReadXCarefullyAndAgree: "我已仔細閱讀並同意「{x}」的内容。"
+dialog: "對話方塊"
+icon: "圖示"
+forYou: "給您"
+currentAnnouncements: "最新公告"
+pastAnnouncements: "歷史公告"
+youHaveUnreadAnnouncements: "有未讀的公告。"
+useSecurityKey: "請按照瀏覽器或設備上的說明使用安全金鑰或 Passkey。"
+replies: "回覆"
+renotes: "轉發"
+loadReplies: "閱覽回覆"
+loadConversation: "閱覽對話"
+pinnedList: "已置頂的清單"
+keepScreenOn: "保持設備螢幕開啟"
+verifiedLink: "已驗證連結"
+notifyNotes: "開啟貼文通知"
+unnotifyNotes: "關閉貼文通知"
+authentication: "驗證"
+authenticationRequiredToContinue: "請於繼續前完成驗證"
+dateAndTime: "日期與時間"
+showRenotes: "顯示其他人的轉發貼文"
+edited: "已編輯"
+notificationRecieveConfig: "接受通知的設定"
+mutualFollow: "互相追隨"
+fileAttachedOnly: "顯示包含附件的貼文"
+showRepliesToOthersInTimeline: "顯示給其他人的回覆"
+hideRepliesToOthersInTimeline: "在時間軸上隱藏給其他人的回覆"
+showRepliesToOthersInTimelineAll: "在時間軸包含追隨中所有人的回覆"
+hideRepliesToOthersInTimelineAll: "在時間軸不包含追隨中所有人的回覆"
+confirmShowRepliesAll: "進行此操作後無法復原。您真的希望時間軸「包含」您目前追隨的所有人的回覆嗎?"
+confirmHideRepliesAll: "進行此操作後無法復原。您真的希望時間軸「不包含」您目前追隨的所有人的回覆嗎?"
+externalServices: "外部服務"
+impressum: "營運者資訊"
+impressumUrl: "營運者資訊網址"
+impressumDescription: "在德國與部份地區必須要明確顯示營運者資訊。"
+privacyPolicy: "隱私政策"
+privacyPolicyUrl: "隱私政策網址"
+tosAndPrivacyPolicy: "服務條款和隱私政策"
+avatarDecorations: "頭像裝飾"
+attach: "裝上"
+detach: "取下"
+angle: "角度"
+flip: "翻轉"
+showAvatarDecorations: "顯示頭像裝飾"
+releaseToRefresh: "放開以更新內容"
+refreshing: "載入更新中"
+pullDownToRefresh: "往下拉來更新內容"
+disableStreamingTimeline: "停用時間軸的即時更新"
+useGroupedNotifications: "分組顯示通知訊息"
+signupPendingError: "驗證您的電子郵件地址時出現問題。連結可能已過期。"
+cwNotationRequired: "如果開啟「隱藏內容」,則需要註解說明。"
+doReaction: "做出反應"
+_announcement:
+ forExistingUsers: "僅限既有的使用者"
+ forExistingUsersDescription: "啟用代表僅向現存使用者顯示;停用代表張貼後註冊的新使用者也會看到。"
+ needConfirmationToRead: "必須確認才能標記為已讀"
+ needConfirmationToReadDescription: "啟用代表此公告將顯示對話方塊以確認是否標記為已讀,同時不會受「標記所有公告為已讀」功能影響。"
+ end: "結束公告"
+ tooManyActiveAnnouncementDescription: "有過多公告可能會影響使用者體驗。請考慮歸檔已結束的公告。"
+ readConfirmTitle: "標記為已讀嗎?"
+ readConfirmText: "閱讀「{title}」的內容並標記為已讀。"
+ shouldNotBeUsedToPresentPermanentInfo: "由於可能會破壞使用者體驗,尤其是對於新使用者而言,我們建議使用公告來發布有時效性的資訊而不是固定不變的資訊。"
+ dialogAnnouncementUxWarn: "如果同時有 2 個以上對話方塊形式的公告存在,對於使用者體驗很可能會有不良的影響,因此建議謹慎使用。"
+ silence: "不發送通知"
+ silenceDescription: "啟用此選項後,將不會發送此公告的通知,並且無需將其標記為已讀。"
_initialAccountSetting:
accountCreated: "帳戶已建立完成!"
letsStartAccountSetup: "來進行帳戶的初始設定吧。"
@@ -1106,11 +1183,91 @@ _initialAccountSetting:
pushNotificationDescription: "啟用推送通知,就可以在設備上接收{name}的通知。"
initialAccountSettingCompleted: "初始設定完成了!"
haveFun: "盡情享受{name}吧!"
- ifYouNeedLearnMore: "關於如何使用{name}(Misskey)的詳細資訊,請見{link}。"
+ youCanContinueTutorial: "您可以繼續學習如何使用{name}(Misskey),也可以就此打住,立即開始使用。"
+ startTutorial: "開始教學課程"
skipAreYouSure: "要略過初始設定嗎?"
laterAreYouSure: "稍後再重新進行初始設定嗎?"
+_initialTutorial:
+ launchTutorial: "觀看教學課程"
+ title: "新手教學"
+ wellDone: "做得好"
+ skipAreYouSure: "結束教學模式?"
+ _landing:
+ title: "歡迎使用本教學課程"
+ description: "在這裡您可以查看Misskey的基本使用方法和功能。"
+ _note:
+ title: "什麼是貼文?"
+ description: "在Misskey上發布的內容稱為「貼文」。貼文在時間軸上按時間順序排列,並即時更新。"
+ reply: "您可以回覆貼文,並像討論串一樣繼續對話。"
+ renote: "您可以將此貼文分享到自己的時間軸。您也可以在引用時添加文字。"
+ reaction: "您可以添加反應。詳細資訊將在下一頁進行說明。"
+ menu: "可執行各種操作,如查看貼文詳細資訊和複製連結。"
+ _reaction:
+ title: "什麼是反應?"
+ description: "您可以在貼文中添加「反應」。您可以使用反應輕鬆隨意地表達「最愛/大心」所無法傳達的細微差別。"
+ letsTryReacting: "可以透過點擊貼文上的「+」按鈕來添加反應。請嘗試在此範例貼文添加反應!"
+ reactToContinue: "添加反應以繼續教學課程。"
+ reactNotification: "當有人對您的貼文做出反應時會即時接收到通知。"
+ reactDone: "按下「-」按鈕可以取消反應。"
+ _timeline:
+ title: "時間軸如何運作"
+ description1: "Misskey根據使用方式提供了多個時間軸(伺服器可能會將部份時間軸停用)。"
+ home: "您可以查看您追隨的使用者的貼文。"
+ local: "您可以看到此伺服器上所有使用者的貼文。"
+ social: "來自首頁時間軸和本地時間軸的貼文都會顯示。"
+ global: "可以看到其他已連接伺服器的貼文。"
+ description2: "您可以隨時在螢幕上方切換對應的時間軸。"
+ description3: "除此之外還有清單時間軸、頻道時間軸等。請參閱{link}以了解更多詳情。"
+ _postNote:
+ title: "貼文的發布設定"
+ description1: "在Misskey上發布貼文時,可以設定各種選項。發布表單如下所示。"
+ _visibility:
+ description: "可以限制誰可以看到您的貼文。"
+ public: "所有人都可以看見。"
+ home: "僅在首頁時間軸上發布。其他使用者只在下列情況可看見該貼文:追隨者、觀看使用者的個人資料頁面,以及貼文被轉發時。"
+ followers: "僅追隨者可見。只有發文者本人可轉發,未追隨發文者的使用者無法看見。"
+ direct: "僅指定的使用者可見,對方也會收到通知。可代替直接訊息使用。"
+ doNotSendConfidencialOnDirect1: "發送機密訊息時請務必注意。"
+ doNotSendConfidencialOnDirect2: "目標伺服器的管理員可以看到發布的內容,因此如果您向不受信任的伺服器上的使用者發送直接訊息,必須小心處理機密訊息。"
+ localOnly: "不將貼文發布到聯邦上的其他伺服器。不論上述發布範圍,使用此設定後,其他伺服器上的使用者將無法直接查看此貼文。"
+ _cw:
+ title: "隱藏內容(CW)"
+ description: "將顯示「註釋」中寫入的內容而不是本文。按一下「顯示內容」以顯示本文。"
+ _exampleNote:
+ cw: "美食恐怖主義注意"
+ note: "我吃了一個巧克力甜甜圈🍩😋"
+ useCases: "伺服器的服務條款可能會規範特定的貼文需要使用隱藏內容,除此之外也會用在隱藏劇情洩漏與敏感內容的貼文。"
+ _howToMakeAttachmentsSensitive:
+ title: "如何標記上傳附件為敏感內容?"
+ description: "如果伺服器服務條款有規範,又或者不希望上傳附件直接被看見,可以設置為「敏感內容」"
+ tryThisFile: "試試看!把附加在發文表單的圖像檔案標記為敏感內容。"
+ _exampleNote:
+ note: "打開納豆的包裝失敗了…"
+ method: "若要使上傳附件標記為敏感內容,請按一下該檔案以開啟選單,然後點擊「標記為敏感內容」。"
+ sensitiveSucceeded: "上傳附件時,請務必根據伺服器的服務條款適當設定敏感內容。"
+ doItToContinue: "把圖像標記為敏感內容以繼續教學課程。"
+ _done:
+ title: "教學課程已結束"
+ description: "這裡介紹的功能只是其中的一小部分。要了解更多有關如何使用Misskey的資訊,請瀏覽{link}。"
+_timelineDescription:
+ home: "在首頁時間軸上,可以看到您追隨的使用者的貼文。"
+ local: "在本地時間軸上,可以看到此伺服器所有使用者的貼文。"
+ social: "在社交時間軸上,可以看到首頁與本地時間軸的貼文。"
+ global: "在公開時間軸上,可以看到其他已連接伺服器的貼文。\n"
_serverRules:
description: "設定在註冊頁面顯示的伺服器簡要規則。建議是服務條款的摘要。"
+_serverSettings:
+ iconUrl: "圖示的 URL"
+ appIconDescription: "指定顯示 {host} 為應用程式時的圖示。"
+ appIconUsageExample: "例如:漸進式網路應用程式(PWA)、於手機桌面新增書籤"
+ appIconStyleRecommendation: "因為可能會裁剪成圓形或圓角,所以建議用單色填滿邊框及背景。"
+ appIconResolutionMustBe: "解析度必須為 {resolution}。"
+ manifestJsonOverride: "覆寫 manifest.json"
+ shortName: "簡稱"
+ shortNameDescription: "如果伺服器的正式名稱很長,可用簡稱或通稱代替。"
+ fanoutTimelineDescription: "如果啟用的話,檢索各個時間軸的性能會顯著提昇,資料庫的負荷也會減少。不過,Redis 的記憶體使用量會增加。如果伺服器的記憶體容量比較少或者運行不穩定,可以停用。"
+ fanoutTimelineDbFallback: "資料庫的回退"
+ fanoutTimelineDbFallbackDescription: "若啟用,在時間軸沒有快取的情況下將執行回退處理以額外查詢資料庫。若停用,可以透過不執行回退處理來進一步減少伺服器的負荷,但會限制可取得的時間軸範圍。"
_accountMigration:
moveFrom: "從其他帳戶遷移到這個帳戶"
moveFromSub: "為另一個帳戶建立別名"
@@ -1119,7 +1276,7 @@ _accountMigration:
moveTo: "將這個帳戶遷移至新的帳戶"
moveToLabel: "要遷移到的帳戶:"
moveCannotBeUndone: "一旦遷移帳戶,就無法取消。"
- moveAccountDescription: "這個操作不可撤銷。首先,請確認已在要遷移到的帳戶中為這個帳戶建立了一個別名。建立別名之後,像這樣輸入你要遷移到的帳戶:@person@instance.com"
+ moveAccountDescription: "遷移至新帳戶。\n ・此帳戶的追隨者將自動追隨新帳戶;\n ・此帳戶的所有追隨者將被取消追隨;\n ・此帳戶不能再發文。\n\n雖然會自動遷移您的追隨者,但必須手動遷移您追隨的帳戶。請在遷移前匯出此帳戶的「追隨中」名單,並在遷移後自行匯入。\n列表名單、靜音名單及封鎖名單也必須如此處理。\n\n(此說明適用於本伺服器,以及運行 Misskey v13.12.0 或更新版本的其他伺服器;如 Mastodon 等使用 ActivityPub 協定的其他軟體或有不同的處理方式。)"
moveAccountHowTo: "要遷移帳戶,首先要在目標帳戶中為此帳戶建立一個別名。\n 建立別名後,像這樣輸入目標帳戶:@username@server.example.com"
startMigration: "遷移"
migrationConfirm: "確定要將這個帳戶遷移至 {account} 嗎?一旦遷移就無法撤銷,也就無法以原來的狀態使用這個帳戶。\n另外,請確認在要遷移到的帳戶已經建立了一個別名。"
@@ -1242,7 +1399,7 @@ _achievements:
title: "有備而來"
description: "設定了個人檔案"
_markedAsCat:
- title: "吾輩乃貓是也"
+ title: "我是貓"
description: "已將帳戶設定為貓"
flavor: "還沒有名字。"
_following1:
@@ -1264,7 +1421,7 @@ _achievements:
title: "第一個追隨者"
description: "第一次被追隨"
_followers10:
- title: "Follow me!"
+ title: "追隨我吧!"
description: "追隨者超過10人了"
_followers50:
title: "成群結隊"
@@ -1273,20 +1430,20 @@ _achievements:
title: "熱門人物"
description: "追隨者超過100人了"
_followers300:
- title: "請排成一排"
+ title: "請排隊"
description: "追隨者超過300人了"
_followers500:
- title: "基地台"
+ title: "基地臺"
description: "超過五百名追隨者了"
_followers1000:
- title: "影響者"
+ title: "星光熠熠"
description: "超過一千名追隨者了"
_collectAchievements30:
title: "成就收藏家"
description: "獲得三十個以上的成就"
_viewAchievements3min:
- title: "喜愛成就"
- description: "看成就列表要花三分鐘以上"
+ title: "成就發燒友"
+ description: "看著成就列表超過三分鐘"
_iLoveMisskey:
title: "I Love Misskey"
description: "發佈「I ❤ #Misskey」"
@@ -1298,34 +1455,34 @@ _achievements:
title: "休息一下"
description: "客戶端啟動已超過30分鐘"
_client60min:
- title: "Misskey看太多"
+ title: "Misskey 看太多"
description: "客戶端啟動已超過60分鐘"
_noteDeletedWithin1min:
- title: "現在沒有了"
- description: "發文後1分鐘內刪文"
+ title: "欲言又止"
+ description: "發文後一分鐘內刪文"
_postedAtLateNight:
- title: "夜行性"
+ title: "夜貓子"
description: "在深夜發佈貼文"
flavor: "該去睡覺了。"
_postedAt0min0sec:
title: "報時"
- description: "在0分0秒發佈貼文"
- flavor: "啵.啵.啵.嗶ー"
+ description: "在零分零秒發佈貼文"
+ flavor: "啵、啵、啵、嗶ーー"
_selfQuote:
title: "自我引用"
description: "引用了自己的貼文"
_htl20npm:
- title: "流動的TL"
- description: "在首頁時間軸的流速超過20npm"
+ title: "源源不絕"
+ description: "首頁時間軸在一分鐘內出現超過二十篇貼文"
_viewInstanceChart:
title: "分析師"
description: "顯示了實例的圖表"
_outputHelloWorldOnScratchpad:
- title: "Hello world!"
- description: "在暫存記憶體輸出了 hello world"
+ title: "Hello, world!"
+ description: "在 AiScript 控制臺輸出了「hello world」"
_open3windows:
title: "多重視窗"
- description: "開啟了3個以上的視窗"
+ description: "開啟過三個以上的視窗"
_driveFolderCircularReference:
title: "循環引用"
description: "試圖遞迴套入雲端硬碟資料夾"
@@ -1337,34 +1494,40 @@ _achievements:
description: "已點擊這裡了"
_justPlainLucky:
title: "只是運氣好"
- description: "每10秒有0.01%的機率獲得"
+ description: "每十秒有二萬分之一(0.005%)的機率獲得"
_setNameToSyuilo:
- title: "神的情結"
+ title: "神與您同在"
description: "將名稱設定為 syuilo"
_passedSinceAccountCreated1:
- title: "一周年"
- description: "自建立帳戶開始過了1年"
+ title: "一週年"
+ description: "帳戶加入時間已超過一年"
_passedSinceAccountCreated2:
- title: "二周年"
- description: "自建立帳戶開始過了2年"
+ title: "二週年"
+ description: "帳戶加入時間已超過兩年"
_passedSinceAccountCreated3:
- title: "三周年"
- description: "自建立帳戶開始過了3年"
+ title: "三週年"
+ description: "帳戶加入時間已超過三年"
_loggedInOnBirthday:
title: "生日快樂"
description: "在生日當天登入了"
_loggedInOnNewYearsDay:
title: "新年快樂"
description: "在元旦當天登入了"
- flavor: "今年也請對敝實例多多指教"
+ flavor: "今年也請您多多指教!"
_cookieClicked:
title: "點擊餅乾的遊戲"
description: "點擊了餅乾"
flavor: "是不是軟體有問題?"
_brainDiver:
title: "Brain Driver"
- description: "發佈了Brain Driver的連結"
+ description: "發佈一篇含歌曲《Brain Driver》連結的貼文"
flavor: "Misskey-Misskey La-Tu-Ma"
+ _smashTestNotificationButton:
+ title: "過度測試"
+ description: "極短時間內連續測試通知"
+ _tutorialCompleted:
+ title: "Misskey新手講座 結業證書"
+ description: "已完成教學課程"
_role:
new: "建立角色"
edit: "編輯角色"
@@ -1387,7 +1550,7 @@ _role:
chooseRoleToAssign: "選擇要指派的角色"
iconUrl: "圖示的URL"
asBadge: "顯示為徽章"
- descriptionOfAsBadge: "開啟的話,角色圖示會顯示在用戶名旁邊。"
+ descriptionOfAsBadge: "開啟的話,角色圖示會顯示在使用者名稱旁邊。"
isExplorable: "讓使用者更容易找到您"
descriptionOfIsExplorable: "若開啟則公開角色時間軸。若角色不是公開的,則無法公開時間軸。"
displayOrder: "顯示順序"
@@ -1408,6 +1571,7 @@ _role:
inviteLimitCycle: "邀請碼的發放間隔"
inviteExpirationTime: "邀請碼的有效日期"
canManageCustomEmojis: "管理自訂表情符號"
+ canManageAvatarDecorations: "管理頭像裝飾"
driveCapacity: "雲端硬碟容量"
alwaysMarkNsfw: "總是將檔案標記為NSFW"
pinMax: "置頂貼文的最大數量"
@@ -1422,11 +1586,12 @@ _role:
descriptionOfRateLimitFactor: "值越小限制越少,值越大限制越多。"
canHideAds: "不顯示廣告"
canSearchNotes: "可否搜尋貼文"
+ canUseTranslator: "使用翻譯功能"
_condition:
isLocal: "本地使用者"
isRemote: "遠端使用者"
- createdLessThan: "自建立帳戶開始~以內"
- createdMoreThan: "自建立帳戶開始~經過"
+ createdLessThan: "帳戶加入時間不超過"
+ createdMoreThan: "帳戶加入時間已超過"
followersLessThanOrEq: "追隨者人數在~以下"
followersMoreThanOrEq: "追隨者人數在~以上"
followingLessThanOrEq: "追隨人數在~以下"
@@ -1437,7 +1602,7 @@ _role:
or: "~或~"
not: "~否"
_sensitiveMediaDetection:
- description: "您可以使用機器學習自動檢測敏感媒體並將其用於審查。 伺服器的負荷會稍微增加。"
+ description: "您可以使用機器學習自動檢測敏感檔案以便審查。這會稍微增加伺服器負荷。"
sensitivity: "檢測敏感度"
sensitivityDescription: "敏感度低時,誤檢測(偽陽性)會減少。敏感度高時,漏檢(偽陰性)會減少。"
setSensitiveFlagAutomatically: "設定 NSFW 標籤"
@@ -1452,7 +1617,7 @@ _emailUnavailable:
smtp: "郵件伺服器沒有應答"
_ffVisibility:
public: "公開"
- followers: "只有關注你的用戶能看到"
+ followers: "只有關注您的使用者能看到"
private: "私密"
_signup:
almostThere: "即將完成"
@@ -1470,6 +1635,10 @@ _ad:
reduceFrequencyOfThisAd: "降低此廣告的頻率 "
hide: "隱藏"
timezoneinfo: "星期幾是由伺服器的時區指定的。"
+ adsSettings: "廣告投放設定"
+ notesPerOneAd: "即時更新中投放廣告的間隔(貼文數)"
+ setZeroToDisable: "設為 0 則在即時更新時不投放廣告"
+ adsTooClose: "由於廣告投放的間隔極短,可能會嚴重影響使用者體驗。"
_forgotPassword:
enterEmail: "請輸入您的帳戶註冊的電子郵件地址。 密碼重置連結將被發送到該電子郵件地址。"
ifNoEmail: "如果您還沒有註冊您的電子郵件地址,請聯繫管理員。 "
@@ -1488,6 +1657,7 @@ _plugin:
install: "安裝外掛組件"
installWarn: "請不要安裝來源不明的外掛。"
manage: "管理外掛"
+ viewSource: "檢視原始碼"
_preferencesBackups:
list: "已備份的設定檔"
saveNew: "另存新檔"
@@ -1513,7 +1683,7 @@ _registry:
domain: "域"
createKey: "新增機碼"
_aboutMisskey:
- about: "Misskey是由syuilo自2014年起開發的開源軟體。"
+ about: "Misskey 是由 syuilo 自 2014 年起開發的開放原始碼軟體。"
contributors: "主要貢獻者"
allContributors: "全體貢獻人員"
source: "原始碼"
@@ -1521,14 +1691,15 @@ _aboutMisskey:
donate: "贊助 Misskey"
morePatrons: "還有許許多多幫助我們的其他人,非常感謝你們。 🥰"
patrons: "贊助者"
+ projectMembers: "專案成員"
_displayOfSensitiveMedia:
- respect: "隱藏被標記為敏感的多媒體內容"
- ignore: "不隱藏被標記為敏感的多媒體內容"
- force: "隱藏所有多媒體內容"
+ respect: "隱藏敏感檔案"
+ ignore: "顯示敏感檔案"
+ force: "隱藏所有檔案"
_instanceTicker:
none: "隱藏"
- remote: "向遠端使用者顯示"
- always: "總是顯示"
+ remote: "只顯示遠端使用者"
+ always: "一律顯示"
_serverDisconnectedBehavior:
reload: "自動重載"
dialog: "彈出式警告"
@@ -1541,10 +1712,11 @@ _channel:
featured: "熱門貼文"
owned: "管理中"
following: "追隨中"
- usersCount: "有{n}人參與"
- notesCount: "有{n}個貼文"
+ usersCount: "有 {n} 人參與"
+ notesCount: "有 {n} 篇貼文"
nameAndDescription: "名稱與說明"
nameOnly: "僅名稱"
+ allowRenoteToExternal: "允許在頻道外轉發和引用"
_menuDisplay:
sideFull: "橫向"
sideIcon: "橫向(圖示)"
@@ -1554,14 +1726,9 @@ _wordMute:
muteWords: "加入靜音文字"
muteWordsDescription: "空格代表「以及」(AND),換行代表「或者」(OR)。"
muteWordsDescription2: "用斜線包圍關鍵字代表正規表達式。"
- softDescription: "隱藏時間軸中符合特定條件的貼文。"
- hardDescription: "符合特定條件的貼文將不會新增至時間軸。 即使您更改條件,未被新增的貼文也會被排除在外。"
- soft: "軟性靜音"
- hard: "硬性靜音"
- mutedNotes: "已靜音的貼文"
_instanceMute:
- instanceMuteDescription: "包括對被靜音實例上的用戶的回覆,被設定的實例上所有貼文及轉發都會被靜音。"
- instanceMuteDescription2: "換行以分隔"
+ instanceMuteDescription: "包括對被靜音伺服器上的使用者的回覆,被設定的伺服器上所有貼文及轉發都會被靜音。"
+ instanceMuteDescription2: "設定時以換行進行分隔"
title: "將隱藏被設定的實例貼文。"
heading: "將實例靜音"
_theme:
@@ -1614,7 +1781,7 @@ _theme:
mentionMe: "提到了我"
renote: "轉發貼文"
modalBg: "對話框背景"
- divider: "分割線"
+ divider: "分隔線"
scrollbarHandle: "捲動條"
scrollbarHandleHover: "捲動條(懸浮)"
dateLabelFg: "日期標籤文字"
@@ -1622,9 +1789,6 @@ _theme:
infoFg: "資訊內容"
infoWarnBg: "警告背景"
infoWarnFg: "警告文字"
- cwBg: "CW 按鈕背景"
- cwFg: "CW 按鈕文字"
- cwHoverBg: "CW 按鈕背景(懸浮)"
toastBg: "通知背景"
toastFg: "通知文本"
buttonBg: "按鈕背景"
@@ -1642,47 +1806,43 @@ _sfx:
note: "貼文"
noteMy: "我的貼文"
notification: "通知"
- chat: "聊天"
- chatBg: "聊天背景"
antenna: "天線接收"
channel: "頻道通知"
_ago:
future: "未來"
justNow: "剛剛"
- secondsAgo: "{n}秒前"
- minutesAgo: "{n}分鐘前"
- hoursAgo: "{n}小時前"
- daysAgo: "{n}天前"
- weeksAgo: "{n}週前"
- monthsAgo: "{n}個月前"
- yearsAgo: "{n}年前"
- invalid: "未發現"
+ secondsAgo: "{n} 秒前"
+ minutesAgo: "{n} 分鐘前 "
+ hoursAgo: "{n} 小時前"
+ daysAgo: "{n} 天前"
+ weeksAgo: "{n} 週前"
+ monthsAgo: "{n} 個月前"
+ yearsAgo: "{n} 年前"
+ invalid: "無"
+_timeIn:
+ seconds: "{n} 秒後"
+ minutes: "{n} 分後"
+ hours: "{n} 小時後"
+ days: "{n} 日後"
+ weeks: "{n} 週後"
+ months: "{n} 個月後"
+ years: "{n} 年後"
_time:
second: "秒"
minute: "分鐘"
hour: "小時"
day: "日"
-_timelineTutorial:
- title: "Misskey 的使用方法"
- step1_1: "這個畫面是「時間軸」。發佈到{name}的「貼文」會按照時間順序顯示。"
- step1_2: "時間軸有多種類型,例如「首頁時間軸」是您追蹤帳戶的貼文、「本地時間軸」是{name}內所有帳戶的貼文。"
- step2_1: "不如現在就嘗試發文吧!按鉛筆圖示的按鈕開啟發文頁面。"
- step2_2: "您可以在第一篇貼文裡寫自我介紹,或是「我來到 {name} 了」之類的話。"
- step3_1: "貼文發出去了嗎?"
- step3_2: "如果您的貼文出現在時間軸上,就代表發文成功。"
- step4_1: "可以對貼文標記「反應」。"
- step4_2: "點擊貼文的「+」圖示,即可選擇表情符號來反應。"
_2fa:
alreadyRegistered: "此裝置已被註冊過了"
registerTOTP: "開始設定驗證應用程式"
- passwordToTOTP: "請輸入密碼"
step1: "首先,在您的裝置上安裝驗證程式,例如 {a} 或 {b}。"
step2: "然後,掃描螢幕上的 QR 碼。"
step2Click: "您可以點擊 QR 碼,以使用裝置上的驗證應用程式或金鑰環註冊。"
- step2Url: "請在桌面版應用程式中輸入以下的 URL:"
+ step2Uri: "使用桌面版應用程式時,請輸入以下的 URI"
step3Title: "輸入驗證碼"
step3: "輸入應用程式所提供的權杖以完成設定。"
- step4: "從現在開始,任何登入操作都將要求您提供權杖。\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}"
+ setupCompleted: "設定完成"
+ step4: "從現在開始,任何登入操作都將要求您提供權杖。"
securityKeyNotSupported: "您的瀏覽器不支援安全金鑰。"
registerTOTPBeforeKey: "如要註冊安全金鑰或 Passkey,請先設定驗證應用程式。"
securityKeyInfo: "您可以設定使用支援 FIDO2 的硬體安全鎖、終端設備的指紋認證,或者 PIN 碼來登入。"
@@ -1696,16 +1856,21 @@ _2fa:
renewTOTPConfirm: "目前驗證應用程式的驗證碼將無法使用。"
renewTOTPOk: "重設"
renewTOTPCancel: "現在不要"
+ checkBackupCodesBeforeCloseThisWizard: "請先確認下列備用驗證碼,再關閉此精靈視窗。"
+ backupCodes: "備用驗證碼"
+ backupCodesDescription: "如果驗證應用程式不能用了,可以使用以下的備用驗證碼存取您的帳戶。請務必妥善保管這個驗證碼。每個驗證碼只能使用一次。"
+ backupCodeUsedWarning: "已使用備用驗證碼。如果無法使用驗證應用程式,請盡快重新設定。"
+ backupCodesExhaustedWarning: "已使用所有備用驗證碼。如果無法使用驗證應用程式,則將無法再存取您的帳戶。請重新設定您的驗證應用程式。"
_permissions:
"read:account": "查看我的帳戶資訊"
"write:account": "更改我的帳戶資訊"
- "read:blocks": "已封鎖用戶名單"
- "write:blocks": "編輯已封鎖用戶名單"
+ "read:blocks": "查看封鎖名單"
+ "write:blocks": "編輯封鎖名單"
"read:drive": "存取雲端硬碟"
"write:drive": "編輯雲端硬碟的檔案"
"read:favorites": "瀏覽我的最愛"
"write:favorites": "編輯我的最愛列表"
- "read:following": "查看追隨中的用戶資訊"
+ "read:following": "查看追隨中的使用者資訊"
"write:following": "追隨/解除追隨"
"read:messaging": "顯示訊息"
"write:messaging": "撰寫或刪除訊息"
@@ -1729,10 +1894,14 @@ _permissions:
"write:gallery": "操作圖庫"
"read:gallery-likes": "讀取喜歡的圖片"
"write:gallery-likes": "操作喜歡的圖片"
+ "read:flash": "檢視 Play"
+ "write:flash": "編輯 Play"
+ "read:flash-likes": "檢視 Play 的讚"
+ "write:flash-likes": "編輯 Play 的讚"
_auth:
shareAccessTitle: "應用程式的存取權限"
shareAccess: "要授權「“{name}”」存取您的帳戶嗎?"
- shareAccessAsk: "您確定要授權這個應用程式使用您的帳戶嗎?"
+ shareAccessAsk: "您確定要授權這個應用程式存取您的帳戶嗎?"
permission: "{name}要求以下的權限"
permissionAsk: "此應用程式需要以下權限"
pleaseGoBack: "請返回至應用程式"
@@ -1744,6 +1913,7 @@ _antennaSources:
homeTimeline: "來自已追隨使用者的貼文"
users: "來自特定使用者的貼文"
userList: "來自特定清單中的貼文"
+ userBlacklist: "除指定使用者外的所有貼文"
_weekday:
sunday: "週日"
monday: "週一"
@@ -1759,7 +1929,7 @@ _widgets:
notifications: "通知"
timeline: "時間軸"
calendar: "行事曆"
- trends: "發燒貼文"
+ trends: "熱門貼文"
clock: "時鐘"
rss: "RSS 閱讀器"
rssTicker: "RSS 跑馬燈"
@@ -1767,15 +1937,15 @@ _widgets:
photos: "照片"
digitalClock: "電子時鐘"
unixClock: "UNIX 時間"
- federation: "站台聯邦"
+ federation: "聯邦宇宙"
instanceCloud: "實例雲"
- postForm: "發佈窗口"
+ postForm: "發文視窗"
slideshow: "幻燈片"
button: "按鈕"
- onlineUsers: "線上的用戶"
+ onlineUsers: "上線使用者"
jobQueue: "佇列"
serverMetric: "伺服器指標 "
- aiscript: "AiScript 控制台"
+ aiscript: "AiScript 控制臺"
aiscriptApp: "AiScript App"
aichan: "小藍"
userList: "使用者列表"
@@ -1784,12 +1954,12 @@ _widgets:
clicker: "點擊器"
_cw:
hide: "隱藏"
- show: "瀏覽更多"
+ show: "顯示內容"
chars: "{count} 個字元"
files: "{count} 個檔案"
_poll:
- noOnlyOneChoice: "至少需要兩個選項。"
- choiceN: "選擇 {n}"
+ noOnlyOneChoice: "需要至少兩個選項。"
+ choiceN: "選項 {n}"
noMore: "沒辦法再添加選項了"
canMultipleVote: "可以多次投票"
expiration: "期限"
@@ -1811,7 +1981,7 @@ _poll:
remainingSeconds: "{s} 秒後截止"
_visibility:
public: "公開"
- publicDescription: "發佈給所有帳戶"
+ publicDescription: "發佈給所有使用者"
home: "首頁"
homeDescription: "僅發布至首頁的時間軸"
followers: "追隨者"
@@ -1836,13 +2006,14 @@ _profile:
username: "使用者名稱"
description: "關於我"
youCanIncludeHashtags: "你也可以在「關於我」中加上 #tag"
- metadata: "進階資訊"
- metadataEdit: "編輯進階資訊"
+ metadata: "附加資訊"
+ metadataEdit: "編輯附加資訊"
metadataDescription: "可以在個人資料中以表格形式顯示其他資訊。"
metadataLabel: "標籤"
metadataContent: "内容"
changeAvatar: "更換大頭貼"
changeBanner: "變更橫幅圖像"
+ verifiedLinkDescription: "如果輸入包含您個人資料的網站 URL,欄位旁邊將出現驗證圖示。"
_exportOrImport:
allNotes: "所有貼文"
favoritedNotes: "「我的最愛」貼文"
@@ -1850,10 +2021,11 @@ _exportOrImport:
muteList: "靜音"
blockingList: "封鎖"
userLists: "清單"
- excludeMutingUsers: "排除被靜音的用戶"
+ excludeMutingUsers: "排除被靜音的使用者"
excludeInactiveUsers: "排除不活躍帳戶"
+ withReplies: "將被匯入的追隨中清單的貼文回覆包含在時間軸"
_charts:
- federation: "站台聯邦"
+ federation: "聯邦宇宙"
apRequest: "請求"
usersIncDec: "使用者增減"
usersTotal: "使用者合共"
@@ -1894,7 +2066,7 @@ _play:
viewSource: "檢視原始碼"
my: "自己的 Play"
liked: "按讚的 Play"
- featured: "人氣"
+ featured: "熱門"
title: "標題"
script: "腳本"
summary: "描述"
@@ -1916,7 +2088,7 @@ _pages:
unlike: "收回讚好"
my: "我的頁面"
liked: "已讚好的頁面"
- featured: "人氣"
+ featured: "熱門"
inspector: "面板檢查"
contents: "內容"
content: "頁面方塊"
@@ -1961,11 +2133,20 @@ _notification:
youReceivedFollowRequest: "您有新的追隨請求"
yourFollowRequestAccepted: "您的追隨請求已通過"
pollEnded: "問卷調查已產生結果"
+ newNote: "新的貼文"
unreadAntennaNote: "天線 {name}"
emptyPushNotificationMessage: "推送通知已更新"
achievementEarned: "獲得成就"
+ testNotification: "通知測試"
+ checkNotificationBehavior: "確認通知的顯示行為"
+ sendTestNotification: "發送測試通知"
+ notificationWillBeDisplayedLikeThis: "通知會以這樣的方式顯示"
+ reactedBySomeUsers: "{n}人做出了反應"
+ renotedBySomeUsers: "{n}人做了轉發"
+ followedBySomeUsers: "被{n}人追隨了"
_types:
all: "全部 "
+ note: "使用者的最新貼文"
follow: "追隨中"
mention: "提及"
reply: "回覆"
@@ -1998,7 +2179,9 @@ _deck:
introduction: "組合多個欄位,製作屬於自己的介面吧!"
introduction2: "您可以隨時按畫面右方的「+」新增欄位。"
widgetsIntroduction: "請從欄位選單中選擇「編輯小工具」新增小工具。"
- useSimpleUiForNonRootPages: "用簡易 UI 顯示非根頁面"
+ useSimpleUiForNonRootPages: "用簡易介面顯示非根頁面"
+ usedAsMinWidthWhenFlexible: "如果啟用「自動調整寬度」,此為最小寬度"
+ flexible: "自動調整寬度"
_columns:
main: "主列"
widgets: "小工具"
@@ -2033,3 +2216,86 @@ _webhookSettings:
renote: "當被轉發時"
reaction: "當獲得反應時"
mention: "當被提到時"
+_moderationLogTypes:
+ createRole: "新增角色"
+ deleteRole: "刪除角色 "
+ updateRole: "更新角色設定"
+ assignRole: "指派角色"
+ unassignRole: "撤銷角色"
+ suspend: "凍結"
+ unsuspend: "解除凍結"
+ addCustomEmoji: "新增自訂表情符號"
+ updateCustomEmoji: "更新自訂表情符號"
+ deleteCustomEmoji: "刪除自訂表情符號"
+ updateServerSettings: "更新伺服器設定"
+ updateUserNote: "更新管理筆記"
+ deleteDriveFile: "刪除檔案"
+ deleteNote: "刪除貼文"
+ createGlobalAnnouncement: "建立全網通知"
+ createUserAnnouncement: "建立使用者通知"
+ updateGlobalAnnouncement: "更新全部的公告"
+ updateUserAnnouncement: "更新使用者的公告"
+ deleteGlobalAnnouncement: "刪除全部的公告"
+ deleteUserAnnouncement: "刪除使用者的公告"
+ resetPassword: "重設密碼"
+ suspendRemoteInstance: "封鎖遠端伺服器"
+ unsuspendRemoteInstance: "解除封鎖遠端伺服器"
+ markSensitiveDriveFile: "標記為敏感檔案"
+ unmarkSensitiveDriveFile: "撤銷標記為敏感檔案"
+ resolveAbuseReport: "解決檢舉"
+ createInvitation: "建立邀請碼"
+ createAd: "建立廣告"
+ deleteAd: "刪除廣告"
+ updateAd: "更新廣告"
+ createAvatarDecoration: "建立頭像裝飾"
+ updateAvatarDecoration: "更新頭像裝飾"
+ deleteAvatarDecoration: "刪除頭像裝飾"
+_fileViewer:
+ title: "檔案詳細資訊"
+ type: "檔案類型 "
+ size: "檔案大小"
+ url: "URL"
+ uploadedAt: "加入日期"
+ attachedNotes: "含有附件的貼文"
+ thisPageCanBeSeenFromTheAuthor: "本頁面僅限上傳了這個檔案的使用者可以檢視。"
+_externalResourceInstaller:
+ title: "從外部網站安裝"
+ checkVendorBeforeInstall: "安裝前請確認提供者是可信賴的。"
+ _plugin:
+ title: "要安裝此外掛嘛?"
+ metaTitle: "外掛資訊"
+ _theme:
+ title: "要安裝此外觀主題嘛?"
+ metaTitle: "外觀主題資訊"
+ _meta:
+ base: "基本配色方案"
+ _vendorInfo:
+ title: "提供者資訊"
+ endpoint: "引用端點"
+ hashVerify: "確認檔案的完整性"
+ _errors:
+ _invalidParams:
+ title: "缺少參數"
+ description: "缺少從外部網站取得資料的必要資訊。請檢查 URL 是否正確。"
+ _resourceTypeNotSupported:
+ title: "不支援此外部資源。"
+ description: "不支援從此外部網站取得的資源類型。請聯絡網站管理員。"
+ _failedToFetch:
+ title: "無法取得資料"
+ fetchErrorDescription: "與外部站點的通訊失敗。如果重試後問題仍然存在,請聯絡網站管理員。"
+ parseErrorDescription: "無法讀取從外部站點取得的資料。請聯絡網站管理員。"
+ _hashUnmatched:
+ title: "無法取得正確資料"
+ description: "所提供資料的完整性驗證失敗。出於安全原因,安裝無法繼續。請聯絡網站管理員。"
+ _pluginParseFailed:
+ title: "AiScript 錯誤"
+ description: "已取得資料但解析 AiScript 時發生錯誤,導致無法載入。請聯絡外掛作者。請檢查 Javascript 控制台以取得錯誤詳細資訊。"
+ _pluginInstallFailed:
+ title: "外掛安裝失敗"
+ description: "安裝插件時出現問題。請再試一次。請參閱 Javascript 控制台以取得錯誤詳細資訊。"
+ _themeParseFailed:
+ title: "外觀主題解析錯誤"
+ description: "已取得資料但解析外觀主題時發生錯誤,導致無法載入。請聯絡主題作者。請檢查 Javascript 控制台以取得錯誤詳細資訊。"
+ _themeInstallFailed:
+ title: "無法安裝外觀主題"
+ description: "安裝外觀主題時出現問題。請再試一次。請參閱 Javascript 控制台以取得錯誤詳細資訊。"
diff --git a/package.json b/package.json
index f7f9c10b4a..babcde9c36 100644
--- a/package.json
+++ b/package.json
@@ -1,12 +1,12 @@
{
"name": "misskey",
- "version": "13.14.2-io.15a",
+ "version": "2023.11.1-io",
"codename": "nasubi",
"repository": {
"type": "git",
"url": "https://github.com/misskey-dev/misskey.git"
},
- "packageManager": "pnpm@8.6.10",
+ "packageManager": "pnpm@8.10.5",
"workspaces": [
"packages/frontend",
"packages/backend",
@@ -15,17 +15,18 @@
"private": true,
"scripts": {
"build-pre": "node ./scripts/build-pre.js",
- "build": "pnpm build-pre && pnpm -r build && pnpm gulp",
+ "build-assets": "node ./scripts/build-assets.mjs",
+ "build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
"build-storybook": "pnpm --filter frontend build-storybook",
- "start": "pnpm check:connect && cd packages/backend && node ./built/boot/index.js",
- "start:docker": "pnpm check:connect && cd packages/backend && exec node ./built/boot/index.js",
- "start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/index.js",
+ "start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
+ "start:docker": "pnpm check:connect && cd packages/backend && exec node ./built/boot/entry.js",
+ "start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
"init": "pnpm migrate",
"migrate": "cd packages/backend && pnpm migrate",
+ "revert": "cd packages/backend && pnpm revert",
"check:connect": "cd packages/backend && pnpm check:connect",
"migrateandstart": "pnpm migrate && pnpm start",
"migrateandstart:docker": "pnpm migrate && exec pnpm start:docker",
- "gulp": "pnpm exec gulp build",
"watch": "pnpm dev",
"dev": "node ./scripts/dev.mjs",
"lint": "pnpm -r lint",
@@ -36,7 +37,6 @@
"jest-and-coverage": "cd packages/backend && pnpm jest-and-coverage",
"test": "pnpm -r test",
"test-and-coverage": "pnpm -r test-and-coverage",
- "format": "pnpm exec gulp format",
"clean": "node ./scripts/clean.js",
"clean-all": "node ./scripts/clean-all.js",
"cleanall": "pnpm clean-all"
@@ -46,24 +46,20 @@
"lodash": "4.17.21"
},
"dependencies": {
- "execa": "7.1.1",
- "gulp": "4.0.2",
- "gulp-cssnano": "2.1.3",
- "gulp-rename": "2.0.0",
- "gulp-replace": "1.1.4",
- "gulp-terser": "2.1.0",
+ "execa": "8.0.1",
+ "cssnano": "6.0.1",
"js-yaml": "4.1.0",
- "typescript": "5.1.6"
+ "postcss": "8.4.31",
+ "terser": "5.24.0",
+ "typescript": "5.2.2"
},
"devDependencies": {
- "@types/gulp": "4.0.10",
- "@types/gulp-rename": "2.0.1",
- "@typescript-eslint/eslint-plugin": "5.61.0",
- "@typescript-eslint/parser": "5.61.0",
+ "@typescript-eslint/eslint-plugin": "6.11.0",
+ "@typescript-eslint/parser": "6.11.0",
"cross-env": "7.0.3",
- "cypress": "12.17.1",
- "eslint": "8.45.0",
- "start-server-and-test": "2.0.0"
+ "cypress": "13.5.1",
+ "eslint": "8.53.0",
+ "start-server-and-test": "2.0.3"
},
"optionalDependencies": {
"@tensorflow/tfjs-core": "4.4.0"
diff --git a/packages/backend/.swcrc b/packages/backend/.swcrc
index 0504a2d389..d9f047b6ac 100644
--- a/packages/backend/.swcrc
+++ b/packages/backend/.swcrc
@@ -11,7 +11,7 @@
"decoratorMetadata": true
},
"experimental": {
- "keepImportAssertions": true
+ "keepImportAttributes": true
},
"baseUrl": "src",
"paths": {
diff --git a/packages/backend/assets/tabler-badges/bell.png b/packages/backend/assets/tabler-badges/bell.png
new file mode 100644
index 0000000000..ab3b2a110f
Binary files /dev/null and b/packages/backend/assets/tabler-badges/bell.png differ
diff --git a/packages/backend/jest.config.cjs b/packages/backend/jest.config.cjs
index 6b1afec734..97d777c862 100644
--- a/packages/backend/jest.config.cjs
+++ b/packages/backend/jest.config.cjs
@@ -216,4 +216,6 @@ module.exports = {
maxWorkers: 1, // Make it use worker (that can be killed and restarted)
logHeapUsage: true, // To debug when out-of-memory happens on CI
workerIdleMemoryLimit: '1GiB', // Limit the worker to 1GB (GitHub Workflows dies at 2GB)
+
+ maxConcurrency: 32,
};
diff --git a/packages/backend/migration/1665091090561-add-renote-muting.js b/packages/backend/migration/1665091090561-add-renote-muting.js
index 575a63703c..a22d7037f3 100644
--- a/packages/backend/migration/1665091090561-add-renote-muting.js
+++ b/packages/backend/migration/1665091090561-add-renote-muting.js
@@ -16,5 +16,9 @@ export class addRenoteMuting1665091090561 {
}
async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_renote_muting_muterId"`);
+ await queryRunner.query(`DROP INDEX "IDX_renote_muting_muteeId"`);
+ await queryRunner.query(`DROP INDEX "IDX_renote_muting_createdAt"`);
+ await queryRunner.query(`DROP TABLE "renote_muting"`);
}
}
diff --git a/packages/backend/migration/1689325027964-UserBlacklistAnntena.js b/packages/backend/migration/1689325027964-UserBlacklistAnntena.js
new file mode 100644
index 0000000000..ce246b20f8
--- /dev/null
+++ b/packages/backend/migration/1689325027964-UserBlacklistAnntena.js
@@ -0,0 +1,10 @@
+export class UserBlacklistAnntena1689325027964 {
+ name = 'UserBlacklistAnntena1689325027964'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TYPE "antenna_src_enum" ADD VALUE 'users_blacklist' AFTER 'list'`);
+ }
+
+ async down(queryRunner) {
+ }
+}
diff --git a/packages/backend/migration/1690417561185-fix-renote-muting.js b/packages/backend/migration/1690417561185-fix-renote-muting.js
new file mode 100644
index 0000000000..14150b0362
--- /dev/null
+++ b/packages/backend/migration/1690417561185-fix-renote-muting.js
@@ -0,0 +1,12 @@
+export class FixRenoteMuting1690417561185 {
+ name = 'FixRenoteMuting1690417561185'
+
+ async up(queryRunner) {
+ await queryRunner.query(`DELETE FROM "renote_muting" WHERE "muteeId" NOT IN (SELECT "id" FROM "user")`);
+ await queryRunner.query(`DELETE FROM "renote_muting" WHERE "muterId" NOT IN (SELECT "id" FROM "user")`);
+ }
+
+ async down(queryRunner) {
+
+ }
+}
diff --git a/packages/backend/migration/1690417561186-ChangeCacheRemoteFilesDefault.js b/packages/backend/migration/1690417561186-ChangeCacheRemoteFilesDefault.js
new file mode 100644
index 0000000000..7eda5debe5
--- /dev/null
+++ b/packages/backend/migration/1690417561186-ChangeCacheRemoteFilesDefault.js
@@ -0,0 +1,11 @@
+export class ChangeCacheRemoteFilesDefault1690417561186 {
+ name = 'ChangeCacheRemoteFilesDefault1690417561186'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "cacheRemoteFiles" SET DEFAULT false`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "cacheRemoteFiles" SET DEFAULT true`);
+ }
+}
diff --git a/packages/backend/migration/1690417561187-Fix.js b/packages/backend/migration/1690417561187-Fix.js
new file mode 100644
index 0000000000..e780e66d7b
--- /dev/null
+++ b/packages/backend/migration/1690417561187-Fix.js
@@ -0,0 +1,81 @@
+export class Fix1690417561187 {
+ name = 'Fix1690417561187'
+
+ async up(queryRunner) {
+ await queryRunner.query(`DROP INDEX "public"."IDX_2cd3b2a6b4cf0b910b260afe08"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_renote_muting_createdAt"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_renote_muting_muteeId"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_renote_muting_muterId"`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."isRoot" IS 'Whether the User is the root.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "ad"."startsAt" IS 'The expired date of the Ad.'`);
+ await queryRunner.query(`ALTER TABLE "antenna" ALTER COLUMN "lastUsedAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "mascotImageUrl" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "preservedUsernames" SET DEFAULT '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }'`);
+ await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."createdAt" IS 'The created date of the Muting.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."muteeId" IS 'The mutee user ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."muterId" IS 'The muter user ID.'`);
+ await queryRunner.query(`ALTER TABLE "poll" DROP CONSTRAINT "FK_da851e06d0dfe2ef397d8b1bf1b"`);
+ await queryRunner.query(`ALTER TABLE "poll" DROP CONSTRAINT "UQ_da851e06d0dfe2ef397d8b1bf1b"`);
+ await queryRunner.query(`ALTER TABLE "promo_note" DROP CONSTRAINT "FK_e263909ca4fe5d57f8d4230dd5c"`);
+ await queryRunner.query(`ALTER TABLE "promo_note" DROP CONSTRAINT "UQ_e263909ca4fe5d57f8d4230dd5c"`);
+ await queryRunner.query(`ALTER TABLE "user_keypair" DROP CONSTRAINT "FK_f4853eb41ab722fe05f81cedeb6"`);
+ await queryRunner.query(`ALTER TABLE "user_keypair" DROP CONSTRAINT "UQ_f4853eb41ab722fe05f81cedeb6"`);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT "FK_51cb79b5555effaf7d69ba1cff9"`);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT "UQ_51cb79b5555effaf7d69ba1cff9"`);
+ await queryRunner.query(`ALTER TABLE "user_publickey" DROP CONSTRAINT "FK_10c146e4b39b443ede016f6736d"`);
+ await queryRunner.query(`ALTER TABLE "user_publickey" DROP CONSTRAINT "UQ_10c146e4b39b443ede016f6736d"`);
+ await queryRunner.query(`CREATE INDEX "IDX_3fcc2c589eaefc205e0714b99c" ON "ad" ("startsAt") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_c71faf11f0a28a5c0bb506203c" ON "channel_favorite" ("userId", "channelId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f7b9d338207e40e768e4a5265a" ON "instance" ("firstRetrievedAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_d1259a2c2b7bb413ff449e8711" ON "renote_muting" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_7eac97594bcac5ffcf2068089b" ON "renote_muting" ("muteeId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_7aa72a5fe76019bfe8e5e0e8b7" ON "renote_muting" ("muterId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0d801c609cec4e9eb4b6b4490c" ON "renote_muting" ("muterId", "muteeId") `);
+ await queryRunner.query(`ALTER TABLE "renote_muting" ADD CONSTRAINT "FK_7eac97594bcac5ffcf2068089b6" FOREIGN KEY ("muteeId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "renote_muting" ADD CONSTRAINT "FK_7aa72a5fe76019bfe8e5e0e8b7d" FOREIGN KEY ("muterId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "poll" ADD CONSTRAINT "FK_da851e06d0dfe2ef397d8b1bf1b" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "promo_note" ADD CONSTRAINT "FK_e263909ca4fe5d57f8d4230dd5c" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_keypair" ADD CONSTRAINT "FK_f4853eb41ab722fe05f81cedeb6" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "FK_51cb79b5555effaf7d69ba1cff9" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_publickey" ADD CONSTRAINT "FK_10c146e4b39b443ede016f6736d" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_publickey" DROP CONSTRAINT "FK_10c146e4b39b443ede016f6736d"`);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT "FK_51cb79b5555effaf7d69ba1cff9"`);
+ await queryRunner.query(`ALTER TABLE "user_keypair" DROP CONSTRAINT "FK_f4853eb41ab722fe05f81cedeb6"`);
+ await queryRunner.query(`ALTER TABLE "promo_note" DROP CONSTRAINT "FK_e263909ca4fe5d57f8d4230dd5c"`);
+ await queryRunner.query(`ALTER TABLE "poll" DROP CONSTRAINT "FK_da851e06d0dfe2ef397d8b1bf1b"`);
+ await queryRunner.query(`ALTER TABLE "renote_muting" DROP CONSTRAINT "FK_7aa72a5fe76019bfe8e5e0e8b7d"`);
+ await queryRunner.query(`ALTER TABLE "renote_muting" DROP CONSTRAINT "FK_7eac97594bcac5ffcf2068089b6"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_0d801c609cec4e9eb4b6b4490c"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_7aa72a5fe76019bfe8e5e0e8b7"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_7eac97594bcac5ffcf2068089b"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_d1259a2c2b7bb413ff449e8711"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_f7b9d338207e40e768e4a5265a"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_c71faf11f0a28a5c0bb506203c"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_3fcc2c589eaefc205e0714b99c"`);
+ await queryRunner.query(`ALTER TABLE "user_publickey" ADD CONSTRAINT "UQ_10c146e4b39b443ede016f6736d" UNIQUE ("userId")`);
+ await queryRunner.query(`ALTER TABLE "user_publickey" ADD CONSTRAINT "FK_10c146e4b39b443ede016f6736d" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "UQ_51cb79b5555effaf7d69ba1cff9" UNIQUE ("userId")`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "FK_51cb79b5555effaf7d69ba1cff9" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_keypair" ADD CONSTRAINT "UQ_f4853eb41ab722fe05f81cedeb6" UNIQUE ("userId")`);
+ await queryRunner.query(`ALTER TABLE "user_keypair" ADD CONSTRAINT "FK_f4853eb41ab722fe05f81cedeb6" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "promo_note" ADD CONSTRAINT "UQ_e263909ca4fe5d57f8d4230dd5c" UNIQUE ("noteId")`);
+ await queryRunner.query(`ALTER TABLE "promo_note" ADD CONSTRAINT "FK_e263909ca4fe5d57f8d4230dd5c" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "poll" ADD CONSTRAINT "UQ_da851e06d0dfe2ef397d8b1bf1b" UNIQUE ("noteId")`);
+ await queryRunner.query(`ALTER TABLE "poll" ADD CONSTRAINT "FK_da851e06d0dfe2ef397d8b1bf1b" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."muterId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."muteeId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."createdAt" IS NULL`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "preservedUsernames" SET DEFAULT '{admin,administrator,root,system,maintainer,host,mod,moderator,owner,superuser,staff,auth,i,me,everyone,all,mention,mentions,example,user,users,account,accounts,official,help,helps,support,supports,info,information,informations,announce,announces,announcement,announcements,notice,notification,notifications,dev,developer,developers,tech,misskey}'`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "mascotImageUrl" SET DEFAULT '/assets/ai.png'`);
+ await queryRunner.query(`ALTER TABLE "antenna" ALTER COLUMN "lastUsedAt" SET DEFAULT '2023-04-25 06:51:20.985478+00'`);
+ await queryRunner.query(`COMMENT ON COLUMN "ad"."startsAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."isRoot" IS 'Whether the User is the admin.'`);
+ await queryRunner.query(`CREATE INDEX "IDX_renote_muting_muterId" ON "muting" ("muterId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_renote_muting_muteeId" ON "muting" ("muteeId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_renote_muting_createdAt" ON "muting" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_2cd3b2a6b4cf0b910b260afe08" ON "instance" ("firstRetrievedAt") `);
+ }
+}
diff --git a/packages/backend/migration/1690782653311-SensitiveChannel.js b/packages/backend/migration/1690782653311-SensitiveChannel.js
new file mode 100644
index 0000000000..e76dda5180
--- /dev/null
+++ b/packages/backend/migration/1690782653311-SensitiveChannel.js
@@ -0,0 +1,17 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class SensitiveChannel1690782653311 {
+ name = 'SensitiveChannel1690782653311'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "channel"
+ ADD "isSensitive" boolean NOT NULL DEFAULT false`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "channel" DROP COLUMN "isSensitive"`);
+ }
+}
diff --git a/packages/backend/migration/1690796169261-play-visibility.js b/packages/backend/migration/1690796169261-play-visibility.js
new file mode 100644
index 0000000000..c57fa7a109
--- /dev/null
+++ b/packages/backend/migration/1690796169261-play-visibility.js
@@ -0,0 +1,15 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class PlayVisibility1689102832143 {
+ name = 'PlayVisibility1690796169261'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "public"."flash" ADD "visibility" character varying(512) DEFAULT 'public'`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "public"."flash" DROP COLUMN "visibility"`, undefined);
+ }
+}
diff --git a/packages/backend/migration/1691959191872-passkey-support.js b/packages/backend/migration/1691959191872-passkey-support.js
index d9e808ba5f..55b571d60d 100644
--- a/packages/backend/migration/1691959191872-passkey-support.js
+++ b/packages/backend/migration/1691959191872-passkey-support.js
@@ -4,46 +4,46 @@
*/
export class PasskeySupport1691959191872 {
- name = 'PasskeySupport1691959191872'
+ name = 'PasskeySupport1691959191872'
- async up(queryRunner) {
- await queryRunner.query(`ALTER TABLE "user_security_key" ADD "counter" bigint NOT NULL DEFAULT '0'`);
- await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."counter" IS 'The number of times the UserSecurityKey was validated.'`);
- await queryRunner.query(`ALTER TABLE "user_security_key" ADD "credentialDeviceType" character varying(32)`);
- await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."credentialDeviceType" IS 'The type of Backup Eligibility in authenticator data'`);
- await queryRunner.query(`ALTER TABLE "user_security_key" ADD "credentialBackedUp" boolean`);
- await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."credentialBackedUp" IS 'Whether or not the credential has been backed up'`);
- await queryRunner.query(`ALTER TABLE "user_security_key" ADD "transports" character varying(32) array`);
- await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."transports" IS 'The type of the credential returned by the browser'`);
- await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."publicKey" IS 'The public key of the UserSecurityKey, hex-encoded.'`);
- await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."lastUsed" IS 'Timestamp of the last time the UserSecurityKey was used.'`);
- await queryRunner.query(`ALTER TABLE "user_security_key" ALTER COLUMN "lastUsed" SET DEFAULT now()`);
- await queryRunner.query(`UPDATE "user_security_key" SET "id" = REPLACE(REPLACE(REPLACE(REPLACE(ENCODE(DECODE("id", 'hex'), 'base64'), E'\\n', ''), '+', '-'), '/', '_'), '=', ''), "publicKey" = REPLACE(REPLACE(REPLACE(REPLACE(ENCODE(DECODE("publicKey", 'hex'), 'base64'), E'\\n', ''), '+', '-'), '/', '_'), '=', '')`);
- await queryRunner.query(`ALTER TABLE "attestation_challenge" DROP CONSTRAINT "FK_f1a461a618fa1755692d0e0d592"`);
- await queryRunner.query(`DROP INDEX "IDX_47efb914aed1f72dd39a306c7b"`);
- await queryRunner.query(`DROP INDEX "IDX_f1a461a618fa1755692d0e0d59"`);
- await queryRunner.query(`DROP TABLE "attestation_challenge"`);
- }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_security_key" ADD "counter" bigint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."counter" IS 'The number of times the UserSecurityKey was validated.'`);
+ await queryRunner.query(`ALTER TABLE "user_security_key" ADD "credentialDeviceType" character varying(32)`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."credentialDeviceType" IS 'The type of Backup Eligibility in authenticator data'`);
+ await queryRunner.query(`ALTER TABLE "user_security_key" ADD "credentialBackedUp" boolean`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."credentialBackedUp" IS 'Whether or not the credential has been backed up'`);
+ await queryRunner.query(`ALTER TABLE "user_security_key" ADD "transports" character varying(32) array`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."transports" IS 'The type of the credential returned by the browser'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."publicKey" IS 'The public key of the UserSecurityKey, hex-encoded.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."lastUsed" IS 'Timestamp of the last time the UserSecurityKey was used.'`);
+ await queryRunner.query(`ALTER TABLE "user_security_key" ALTER COLUMN "lastUsed" SET DEFAULT now()`);
+ await queryRunner.query(`UPDATE "user_security_key" SET "id" = REPLACE(REPLACE(REPLACE(REPLACE(ENCODE(DECODE("id", 'hex'), 'base64'), E'\\n', ''), '+', '-'), '/', '_'), '=', ''), "publicKey" = REPLACE(REPLACE(REPLACE(REPLACE(ENCODE(DECODE("publicKey", 'hex'), 'base64'), E'\\n', ''), '+', '-'), '/', '_'), '=', '')`);
+ await queryRunner.query(`ALTER TABLE "attestation_challenge" DROP CONSTRAINT "FK_f1a461a618fa1755692d0e0d592"`);
+ await queryRunner.query(`DROP INDEX "IDX_47efb914aed1f72dd39a306c7b"`);
+ await queryRunner.query(`DROP INDEX "IDX_f1a461a618fa1755692d0e0d59"`);
+ await queryRunner.query(`DROP TABLE "attestation_challenge"`);
+ }
- async down(queryRunner) {
- await queryRunner.query(`CREATE TABLE "attestation_challenge" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "challenge" character varying(64) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "registrationChallenge" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_d0ba6786e093f1bcb497572a6b5" PRIMARY KEY ("id", "userId"))`);
- await queryRunner.query(`CREATE INDEX "IDX_f1a461a618fa1755692d0e0d59" ON "attestation_challenge" ("userId") `);
- await queryRunner.query(`CREATE INDEX "IDX_47efb914aed1f72dd39a306c7b" ON "attestation_challenge" ("challenge") `);
- await queryRunner.query(`ALTER TABLE "attestation_challenge" ADD CONSTRAINT "FK_f1a461a618fa1755692d0e0d592" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
- await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."challenge" IS 'Hex-encoded sha256 hash of the challenge.'`);
- await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."createdAt" IS 'The date challenge was created for expiry purposes.'`);
- await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."registrationChallenge" IS 'Indicates that the challenge is only for registration purposes if true to prevent the challenge for being used as authentication.'`);
- await queryRunner.query(`UPDATE "user_security_key" SET "id" = ENCODE(DECODE(REPLACE(REPLACE("id" || CASE WHEN LENGTH("id") % 4 = 2 THEN '==' WHEN LENGTH("id") % 4 = 3 THEN '=' ELSE '' END, '-', '+'), '_', '/'), 'base64'), 'hex'), "publicKey" = ENCODE(DECODE(REPLACE(REPLACE("publicKey" || CASE WHEN LENGTH("publicKey") % 4 = 2 THEN '==' WHEN LENGTH("publicKey") % 4 = 3 THEN '=' ELSE '' END, '-', '+'), '_', '/'), 'base64'), 'hex')`);
- await queryRunner.query(`ALTER TABLE "user_security_key" ALTER COLUMN "lastUsed" DROP DEFAULT`);
- await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."lastUsed" IS 'The date of the last time the UserSecurityKey was successfully validated.'`);
- await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."publicKey" IS 'Variable-length public key used to verify attestations (hex-encoded).'`);
- await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."transports" IS 'The type of the credential returned by the browser'`);
- await queryRunner.query(`ALTER TABLE "user_security_key" DROP COLUMN "transports"`);
- await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."credentialBackedUp" IS 'Whether or not the credential has been backed up'`);
- await queryRunner.query(`ALTER TABLE "user_security_key" DROP COLUMN "credentialBackedUp"`);
- await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."credentialDeviceType" IS 'The type of Backup Eligibility in authenticator data'`);
- await queryRunner.query(`ALTER TABLE "user_security_key" DROP COLUMN "credentialDeviceType"`);
- await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."counter" IS 'The number of times the UserSecurityKey was validated.'`);
- await queryRunner.query(`ALTER TABLE "user_security_key" DROP COLUMN "counter"`);
- }
+ async down(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "attestation_challenge" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "challenge" character varying(64) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "registrationChallenge" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_d0ba6786e093f1bcb497572a6b5" PRIMARY KEY ("id", "userId"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_f1a461a618fa1755692d0e0d59" ON "attestation_challenge" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_47efb914aed1f72dd39a306c7b" ON "attestation_challenge" ("challenge") `);
+ await queryRunner.query(`ALTER TABLE "attestation_challenge" ADD CONSTRAINT "FK_f1a461a618fa1755692d0e0d592" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."challenge" IS 'Hex-encoded sha256 hash of the challenge.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."createdAt" IS 'The date challenge was created for expiry purposes.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."registrationChallenge" IS 'Indicates that the challenge is only for registration purposes if true to prevent the challenge for being used as authentication.'`);
+ await queryRunner.query(`UPDATE "user_security_key" SET "id" = ENCODE(DECODE(REPLACE(REPLACE("id" || CASE WHEN LENGTH("id") % 4 = 2 THEN '==' WHEN LENGTH("id") % 4 = 3 THEN '=' ELSE '' END, '-', '+'), '_', '/'), 'base64'), 'hex'), "publicKey" = ENCODE(DECODE(REPLACE(REPLACE("publicKey" || CASE WHEN LENGTH("publicKey") % 4 = 2 THEN '==' WHEN LENGTH("publicKey") % 4 = 3 THEN '=' ELSE '' END, '-', '+'), '_', '/'), 'base64'), 'hex')`);
+ await queryRunner.query(`ALTER TABLE "user_security_key" ALTER COLUMN "lastUsed" DROP DEFAULT`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."lastUsed" IS 'The date of the last time the UserSecurityKey was successfully validated.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."publicKey" IS 'Variable-length public key used to verify attestations (hex-encoded).'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."transports" IS 'The type of the credential returned by the browser'`);
+ await queryRunner.query(`ALTER TABLE "user_security_key" DROP COLUMN "transports"`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."credentialBackedUp" IS 'Whether or not the credential has been backed up'`);
+ await queryRunner.query(`ALTER TABLE "user_security_key" DROP COLUMN "credentialBackedUp"`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."credentialDeviceType" IS 'The type of Backup Eligibility in authenticator data'`);
+ await queryRunner.query(`ALTER TABLE "user_security_key" DROP COLUMN "credentialDeviceType"`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."counter" IS 'The number of times the UserSecurityKey was validated.'`);
+ await queryRunner.query(`ALTER TABLE "user_security_key" DROP COLUMN "counter"`);
+ }
}
diff --git a/packages/backend/migration/1694850832075-server-icons-and-manifest.js b/packages/backend/migration/1694850832075-server-icons-and-manifest.js
new file mode 100644
index 0000000000..1bd8979d9b
--- /dev/null
+++ b/packages/backend/migration/1694850832075-server-icons-and-manifest.js
@@ -0,0 +1,20 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class ServerIconsAndManifest1694850832075 {
+ name = 'ServerIconsAndManifest1694850832075'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "app192IconUrl" character varying(1024)`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "app512IconUrl" character varying(1024)`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "manifestJsonOverride" character varying(8192) NOT NULL DEFAULT '{}'`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "manifestJsonOverride"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "app512IconUrl"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "app192IconUrl"`);
+ }
+}
diff --git a/packages/backend/migration/1694915420864-clipped-count.js b/packages/backend/migration/1694915420864-clipped-count.js
new file mode 100644
index 0000000000..1ad8e04ce0
--- /dev/null
+++ b/packages/backend/migration/1694915420864-clipped-count.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class ClippedCount1694915420864 {
+ name = 'ClippedCount1694915420864'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "note" ADD "clippedCount" smallint NOT NULL DEFAULT '0'`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "clippedCount"`);
+ }
+}
diff --git a/packages/backend/migration/1695260774117-verified-links.js b/packages/backend/migration/1695260774117-verified-links.js
new file mode 100644
index 0000000000..18e0571d81
--- /dev/null
+++ b/packages/backend/migration/1695260774117-verified-links.js
@@ -0,0 +1,11 @@
+export class VerifiedLinks1695260774117 {
+ name = 'VerifiedLinks1695260774117'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "verifiedLinks" character varying array NOT NULL DEFAULT '{}'`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "verifiedLinks"`);
+ }
+}
diff --git a/packages/backend/migration/1695288787870-following-notify.js b/packages/backend/migration/1695288787870-following-notify.js
new file mode 100644
index 0000000000..e7e2194b15
--- /dev/null
+++ b/packages/backend/migration/1695288787870-following-notify.js
@@ -0,0 +1,13 @@
+export class FollowingNotify1695288787870 {
+ name = 'FollowingNotify1695288787870'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "following" ADD "notify" character varying(32)`);
+ await queryRunner.query(`CREATE INDEX "IDX_5108098457488634a4768e1d12" ON "following" ("notify") `);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "public"."IDX_5108098457488634a4768e1d12"`);
+ await queryRunner.query(`ALTER TABLE "following" DROP COLUMN "notify"`);
+ }
+}
diff --git a/packages/backend/migration/1695440131671-short-name.js b/packages/backend/migration/1695440131671-short-name.js
new file mode 100644
index 0000000000..2c37297fc1
--- /dev/null
+++ b/packages/backend/migration/1695440131671-short-name.js
@@ -0,0 +1,11 @@
+export class ShortName1695440131671 {
+ name = 'ShortName1695440131671'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "shortName" character varying(64)`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "shortName"`);
+ }
+}
diff --git a/packages/backend/migration/1695605508898-mutingNotificationTypes.js b/packages/backend/migration/1695605508898-mutingNotificationTypes.js
new file mode 100644
index 0000000000..8c0e52a2f0
--- /dev/null
+++ b/packages/backend/migration/1695605508898-mutingNotificationTypes.js
@@ -0,0 +1,21 @@
+export class MutingNotificationTypes1695605508898 {
+ name = 'MutingNotificationTypes1695605508898'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TYPE "public"."user_profile_mutingnotificationtypes_enum" RENAME TO "user_profile_mutingnotificationtypes_enum_old"`);
+ await queryRunner.query(`CREATE TYPE "public"."user_profile_mutingnotificationtypes_enum" AS ENUM('note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app', 'test', 'pollVote', 'groupInvited')`);
+ 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"[] USING "mutingNotificationTypes"::"text"::"public"."user_profile_mutingnotificationtypes_enum"[]`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" SET DEFAULT '{}'`);
+ await queryRunner.query(`DROP TYPE "public"."user_profile_mutingnotificationtypes_enum_old"`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`CREATE TYPE "public"."user_profile_mutingnotificationtypes_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'achievementEarned', 'app')`);
+ 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"`);
+ }
+}
diff --git a/packages/backend/migration/1695901659683-note-updated-at.js b/packages/backend/migration/1695901659683-note-updated-at.js
new file mode 100644
index 0000000000..d8a151a1f7
--- /dev/null
+++ b/packages/backend/migration/1695901659683-note-updated-at.js
@@ -0,0 +1,11 @@
+export class NoteUpdatedAt1695901659683 {
+ name = 'NoteUpdatedAt1695901659683'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "note" ADD "updatedAt" TIMESTAMP WITH TIME ZONE`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "updatedAt"`);
+ }
+}
diff --git a/packages/backend/migration/1695944637565-notificationRecieveConfig.js b/packages/backend/migration/1695944637565-notificationRecieveConfig.js
new file mode 100644
index 0000000000..42d3dce5d6
--- /dev/null
+++ b/packages/backend/migration/1695944637565-notificationRecieveConfig.js
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class NotificationRecieveConfig1695944637565 {
+ name = 'NotificationRecieveConfig1695944637565'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "mutingNotificationTypes"`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "notificationRecieveConfig" jsonb NOT NULL DEFAULT '{}'`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "notificationRecieveConfig"`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "mutingNotificationTypes" "public"."user_profile_notificationrecieveconfig_enum" array NOT NULL DEFAULT '{}'`);
+ }
+}
diff --git a/packages/backend/migration/1696003580220-AddSomeUrls.js b/packages/backend/migration/1696003580220-AddSomeUrls.js
new file mode 100644
index 0000000000..683aa5eeed
--- /dev/null
+++ b/packages/backend/migration/1696003580220-AddSomeUrls.js
@@ -0,0 +1,17 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class AddSomeUrls1696003580220 {
+ name = 'AddSomeUrls1696003580220'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "impressumUrl" character varying(1024)`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "privacyPolicyUrl" character varying(1024)`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "impressumUrl"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "privacyPolicyUrl"`);
+ }
+}
diff --git a/packages/backend/migration/1696222183852-withReplies.js b/packages/backend/migration/1696222183852-withReplies.js
new file mode 100644
index 0000000000..9f65d5f6a1
--- /dev/null
+++ b/packages/backend/migration/1696222183852-withReplies.js
@@ -0,0 +1,20 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class WithReplies1696222183852 {
+ name = 'WithReplies1696222183852'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "following" ADD "withReplies" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "user_list_joining" ADD "withReplies" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`CREATE INDEX "IDX_d74d8ab5efa7e3bb82825c0fa2" ON "following" ("followeeId", "followerHost") `);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "public"."IDX_d74d8ab5efa7e3bb82825c0fa2"`);
+ await queryRunner.query(`ALTER TABLE "user_list_joining" DROP COLUMN "withReplies"`);
+ await queryRunner.query(`ALTER TABLE "following" DROP COLUMN "withReplies"`);
+ }
+}
diff --git a/packages/backend/migration/1696331570827-hibernation.js b/packages/backend/migration/1696331570827-hibernation.js
new file mode 100644
index 0000000000..119d35913f
--- /dev/null
+++ b/packages/backend/migration/1696331570827-hibernation.js
@@ -0,0 +1,17 @@
+export class Hibernation1696331570827 {
+ name = 'Hibernation1696331570827'
+
+ async up(queryRunner) {
+ await queryRunner.query(`DROP INDEX "public"."IDX_d74d8ab5efa7e3bb82825c0fa2"`);
+ await queryRunner.query(`ALTER TABLE "user" ADD "isHibernated" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "following" ADD "isFollowerHibernated" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`CREATE INDEX "IDX_ce62b50d882d4e9dee10ad0d2f" ON "following" ("followeeId", "followerHost", "isFollowerHibernated") `);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "public"."IDX_ce62b50d882d4e9dee10ad0d2f"`);
+ await queryRunner.query(`ALTER TABLE "following" DROP COLUMN "isFollowerHibernated"`);
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isHibernated"`);
+ await queryRunner.query(`CREATE INDEX "IDX_d74d8ab5efa7e3bb82825c0fa2" ON "following" ("followeeId", "followerHost") `);
+ }
+}
diff --git a/packages/backend/migration/1696332072038-clean.js b/packages/backend/migration/1696332072038-clean.js
new file mode 100644
index 0000000000..97dba655f4
--- /dev/null
+++ b/packages/backend/migration/1696332072038-clean.js
@@ -0,0 +1,33 @@
+export class Clean1696332072038 {
+ name = 'Clean1696332072038'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_list_membership" DROP CONSTRAINT "FK_d844bfc6f3f523a05189076efaa"`);
+ await queryRunner.query(`ALTER TABLE "user_list_membership" DROP CONSTRAINT "FK_605472305f26818cc93d1baaa74"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_d844bfc6f3f523a05189076efa"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_605472305f26818cc93d1baaa7"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_90f7da835e4c10aca6853621e1"`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "preservedUsernames" SET DEFAULT '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_list_membership"."createdAt" IS 'The created date of the UserListMembership.'`);
+ await queryRunner.query(`CREATE INDEX "IDX_021015e6683570ae9f6b0c62be" ON "user_list_membership" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_cddcaf418dc4d392ecfcca842a" ON "user_list_membership" ("userListId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_e4f3094c43f2d665e6030b0337" ON "user_list_membership" ("userId", "userListId") `);
+ await queryRunner.query(`ALTER TABLE "user_list_membership" ADD CONSTRAINT "FK_021015e6683570ae9f6b0c62bee" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_list_membership" ADD CONSTRAINT "FK_cddcaf418dc4d392ecfcca842a7" FOREIGN KEY ("userListId") REFERENCES "user_list"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_list_membership" DROP CONSTRAINT "FK_cddcaf418dc4d392ecfcca842a7"`);
+ await queryRunner.query(`ALTER TABLE "user_list_membership" DROP CONSTRAINT "FK_021015e6683570ae9f6b0c62bee"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_e4f3094c43f2d665e6030b0337"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_cddcaf418dc4d392ecfcca842a"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_021015e6683570ae9f6b0c62be"`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_list_membership"."createdAt" IS 'The created date of the UserListJoining.'`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "preservedUsernames" SET DEFAULT '{admin,administrator,root,system,maintainer,host,mod,moderator,owner,superuser,staff,auth,i,me,everyone,all,mention,mentions,example,user,users,account,accounts,official,help,helps,support,supports,info,information,informations,announce,announces,announcement,announcements,notice,notification,notifications,dev,developer,developers,tech,misskey}'`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_90f7da835e4c10aca6853621e1" ON "user_list_membership" ("userId", "userListId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_605472305f26818cc93d1baaa7" ON "user_list_membership" ("userListId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_d844bfc6f3f523a05189076efa" ON "user_list_membership" ("userId") `);
+ await queryRunner.query(`ALTER TABLE "user_list_membership" ADD CONSTRAINT "FK_605472305f26818cc93d1baaa74" FOREIGN KEY ("userListId") REFERENCES "user_list"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_list_membership" ADD CONSTRAINT "FK_d844bfc6f3f523a05189076efaa" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+}
diff --git a/packages/backend/migration/1696388600237-revert-note-edit.js b/packages/backend/migration/1696388600237-revert-note-edit.js
new file mode 100644
index 0000000000..83bc552c35
--- /dev/null
+++ b/packages/backend/migration/1696388600237-revert-note-edit.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class RevertNoteEdit1696388600237 {
+ name = 'RevertNoteEdit1696388600237'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "updatedAt"`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "note" ADD "updatedAt" TIMESTAMP WITH TIME ZONE`);
+ }
+}
diff --git a/packages/backend/migration/1696405744672-clean-up.js b/packages/backend/migration/1696405744672-clean-up.js
new file mode 100644
index 0000000000..5ec89b08f4
--- /dev/null
+++ b/packages/backend/migration/1696405744672-clean-up.js
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class CleanUp1696405744672 {
+ name = 'CleanUp1696405744672'
+
+ async up(queryRunner) {
+ await queryRunner.query(`DROP INDEX "public"."IDX_e7c0567f5261063592f022e9b5"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_25dfc71b0369b003a4cd434d0b"`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`CREATE INDEX "IDX_25dfc71b0369b003a4cd434d0b" ON "note" ("attachedFileTypes") `);
+ await queryRunner.query(`CREATE INDEX "IDX_e7c0567f5261063592f022e9b5" ON "note" ("createdAt") `);
+ }
+}
diff --git a/packages/backend/migration/1696569742153-clean-up.js b/packages/backend/migration/1696569742153-clean-up.js
new file mode 100644
index 0000000000..de48fab5aa
--- /dev/null
+++ b/packages/backend/migration/1696569742153-clean-up.js
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class CleanUp1696569742153 {
+ name = 'CleanUp1696569742153'
+
+ async up(queryRunner) {
+ await queryRunner.query(`DROP INDEX "public"."IDX_01f4581f114e0ebd2bbb876f0b"`);
+ await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "score"`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "note" ADD "score" integer NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`CREATE INDEX "IDX_01f4581f114e0ebd2bbb876f0b" ON "note_reaction" ("createdAt") `);
+ }
+}
diff --git a/packages/backend/migration/1696581429196-clean-up.js b/packages/backend/migration/1696581429196-clean-up.js
new file mode 100644
index 0000000000..da69b4e9de
--- /dev/null
+++ b/packages/backend/migration/1696581429196-clean-up.js
@@ -0,0 +1,15 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class CleanUp1696581429196 {
+ name = 'CleanUp1696581429196'
+
+ async up(queryRunner) {
+ await queryRunner.query(`DROP TABLE IF EXISTS "muted_note"`);
+ }
+
+ async down(queryRunner) {
+ }
+}
diff --git a/packages/backend/migration/1696743032098-AdsOnStream.js b/packages/backend/migration/1696743032098-AdsOnStream.js
new file mode 100644
index 0000000000..c86ee84883
--- /dev/null
+++ b/packages/backend/migration/1696743032098-AdsOnStream.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class AdsOnStream1696743032098 {
+ name = 'AdsOnStream1696743032098'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "notesPerOneAd" integer NOT NULL DEFAULT '0'`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "notesPerOneAd"`);
+ }
+}
diff --git a/packages/backend/migration/1697247230117-InstanceSilence.js b/packages/backend/migration/1697247230117-InstanceSilence.js
new file mode 100644
index 0000000000..5fdbca3b27
--- /dev/null
+++ b/packages/backend/migration/1697247230117-InstanceSilence.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class InstanceSilence1697247230117 {
+ name = 'InstanceSilence1697247230117'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "silencedHosts" character varying(1024) array NOT NULL DEFAULT '{}'`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "silencedHosts"`);
+ }
+}
diff --git a/packages/backend/migration/1697436246389-antenna-localOnly.js b/packages/backend/migration/1697436246389-antenna-localOnly.js
new file mode 100644
index 0000000000..0228673291
--- /dev/null
+++ b/packages/backend/migration/1697436246389-antenna-localOnly.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class AntennaLocalOnly1697436246389 {
+ name = 'AntennaLocalOnly1697436246389'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "antenna" ADD "localOnly" boolean NOT NULL DEFAULT false`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "localOnly"`);
+ }
+}
diff --git a/packages/backend/migration/1697441463087-FollowRequestWithReplies.js b/packages/backend/migration/1697441463087-FollowRequestWithReplies.js
new file mode 100644
index 0000000000..214c6f6680
--- /dev/null
+++ b/packages/backend/migration/1697441463087-FollowRequestWithReplies.js
@@ -0,0 +1,17 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+
+export class FollowRequestWithReplies1697441463087 {
+ name = 'FollowRequestWithReplies1697441463087'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "follow_request" ADD "withReplies" boolean NOT NULL DEFAULT false`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "follow_request" DROP COLUMN "withReplies"`);
+ }
+}
diff --git a/packages/backend/migration/1697673894459-note-reactionAndUserPairCache.js b/packages/backend/migration/1697673894459-note-reactionAndUserPairCache.js
new file mode 100644
index 0000000000..fe0ea282d2
--- /dev/null
+++ b/packages/backend/migration/1697673894459-note-reactionAndUserPairCache.js
@@ -0,0 +1,17 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+
+export class NoteReactionAndUserPairCache1697673894459 {
+ name = 'NoteReactionAndUserPairCache1697673894459'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "note" ADD "reactionAndUserPairCache" character varying(1024) array NOT NULL DEFAULT '{}'`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "reactionAndUserPairCache"`);
+ }
+}
diff --git a/packages/backend/migration/1697847397844-avatar-decoration.js b/packages/backend/migration/1697847397844-avatar-decoration.js
new file mode 100644
index 0000000000..1f22139746
--- /dev/null
+++ b/packages/backend/migration/1697847397844-avatar-decoration.js
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class AvatarDecoration1697847397844 {
+ name = 'AvatarDecoration1697847397844'
+
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "avatar_decoration" ("id" character varying(32) NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE, "url" character varying(1024) NOT NULL, "name" character varying(256) NOT NULL, "description" character varying(2048) NOT NULL, "roleIdsThatCanBeUsedThisDecoration" character varying(128) array NOT NULL DEFAULT '{}', CONSTRAINT "PK_b6de9296f6097078e1dc53f7603" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`ALTER TABLE "user" ADD "avatarDecorations" character varying(512) array NOT NULL DEFAULT '{}'`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "avatarDecorations"`);
+ await queryRunner.query(`DROP TABLE "avatar_decoration"`);
+ }
+}
diff --git a/packages/backend/migration/1697941908548-avatar-decoration2.js b/packages/backend/migration/1697941908548-avatar-decoration2.js
new file mode 100644
index 0000000000..9d15c1c3d0
--- /dev/null
+++ b/packages/backend/migration/1697941908548-avatar-decoration2.js
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class AvatarDecoration21697941908548 {
+ name = 'AvatarDecoration21697941908548'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "avatarDecorations"`);
+ await queryRunner.query(`ALTER TABLE "user" ADD "avatarDecorations" jsonb NOT NULL DEFAULT '[]'`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "avatarDecorations"`);
+ await queryRunner.query(`ALTER TABLE "user" ADD "avatarDecorations" character varying(512) array NOT NULL DEFAULT '{}'`);
+ }
+}
diff --git a/packages/backend/migration/1698041201306-enable-ftt.js b/packages/backend/migration/1698041201306-enable-ftt.js
new file mode 100644
index 0000000000..6769ed53b5
--- /dev/null
+++ b/packages/backend/migration/1698041201306-enable-ftt.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class EnableFtt1698041201306 {
+ name = 'EnableFtt1698041201306'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "enableFanoutTimeline" boolean NOT NULL DEFAULT true`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableFanoutTimeline"`);
+ }
+}
diff --git a/packages/backend/migration/1698840138000-add-allow-renote-to-external.js b/packages/backend/migration/1698840138000-add-allow-renote-to-external.js
new file mode 100644
index 0000000000..0edf298841
--- /dev/null
+++ b/packages/backend/migration/1698840138000-add-allow-renote-to-external.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class AddAllowRenoteToExternal1698840138000 {
+ name = 'AddAllowRenoteToExternal1698840138000'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "channel" ADD "allowRenoteToExternal" boolean NOT NULL DEFAULT true`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "channel" DROP COLUMN "allowRenoteToExternal"`);
+ }
+}
diff --git a/packages/backend/migration/1699141698112-announcement-silence.js b/packages/backend/migration/1699141698112-announcement-silence.js
new file mode 100644
index 0000000000..eef9b076fc
--- /dev/null
+++ b/packages/backend/migration/1699141698112-announcement-silence.js
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class AnnouncementSilence1699141698112 {
+ name = 'AnnouncementSilence1699141698112'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "announcement" ADD "silence" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`CREATE INDEX "IDX_7b8d9225168e962f94ea517e00" ON "announcement" ("silence") `);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "public"."IDX_7b8d9225168e962f94ea517e00"`);
+ await queryRunner.query(`ALTER TABLE "announcement" DROP COLUMN "silence"`);
+ }
+}
diff --git a/packages/backend/migration/1700096812223-enableFanoutTimelineDbFallback.js b/packages/backend/migration/1700096812223-enableFanoutTimelineDbFallback.js
new file mode 100644
index 0000000000..94fa588985
--- /dev/null
+++ b/packages/backend/migration/1700096812223-enableFanoutTimelineDbFallback.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class EnableFanoutTimelineDbFallback1700096812223 {
+ name = 'EnableFanoutTimelineDbFallback1700096812223'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "enableFanoutTimelineDbFallback" boolean NOT NULL DEFAULT true`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableFanoutTimelineDbFallback"`);
+ }
+}
diff --git a/packages/backend/migration/1700415938358-createdAt-default.js b/packages/backend/migration/1700415938358-createdAt-default.js
new file mode 100644
index 0000000000..deaa7927ff
--- /dev/null
+++ b/packages/backend/migration/1700415938358-createdAt-default.js
@@ -0,0 +1,224 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class CreatedAtDefault1700415938358 {
+ name = 'CreatedAtDefault1700415938358'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "avatar_decoration" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()`);
+ await queryRunner.query(`COMMENT ON COLUMN "avatar_decoration"."createdAt" IS 'The created date of the AvatarDecoration.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "abuse_report_resolver"."createdAt" IS 'The created date of the AbuseReportResolver.'`);
+ await queryRunner.query(`ALTER TABLE "abuse_report_resolver" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "drive_folder" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "drive_file" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "user" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "app" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "ad" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "announcement" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "announcement_read" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "user_list" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "antenna" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "auth_session" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "blocking" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "channel" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "channel_following" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "channel_favorite" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "clip" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "note" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`COMMENT ON COLUMN "clip_favorite"."createdAt" IS 'The created date of the ClipFavorite.'`);
+ await queryRunner.query(`ALTER TABLE "clip_favorite" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "following" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "follow_request" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "gallery_post" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`COMMENT ON COLUMN "gallery_like"."createdAt" IS 'The created date of the GalleryLike.'`);
+ await queryRunner.query(`ALTER TABLE "gallery_like" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "preservedUsernames" SET DEFAULT '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }'`);
+ await queryRunner.query(`ALTER TABLE "moderation_log" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "muting" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."createdAt" IS 'The created date of the RenoteMuting.'`);
+ await queryRunner.query(`ALTER TABLE "renote_muting" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "note_favorite" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "note_reaction" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`COMMENT ON COLUMN "note_thread_muting"."createdAt" IS 'The created date of the NoteThreadMuting.'`);
+ await queryRunner.query(`ALTER TABLE "note_thread_muting" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "page" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`COMMENT ON COLUMN "page_like"."createdAt" IS 'The created date of the PageLike.'`);
+ await queryRunner.query(`ALTER TABLE "page_like" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`COMMENT ON COLUMN "password_reset_request"."createdAt" IS 'The created date of the PasswordResetRequest.'`);
+ await queryRunner.query(`ALTER TABLE "password_reset_request" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "poll_vote" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "promo_read" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`COMMENT ON COLUMN "registration_ticket"."createdAt" IS 'The created date of the RegistrationTicket.'`);
+ await queryRunner.query(`ALTER TABLE "registration_ticket" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "registry_item" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "signin" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`COMMENT ON COLUMN "sw_subscription"."createdAt" IS 'The created date of the SwSubscriptipnpon.'`);
+ await queryRunner.query(`ALTER TABLE "sw_subscription" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`COMMENT ON COLUMN "used_username"."createdAt" IS 'The created date of the UsedUsername.'`);
+ await queryRunner.query(`ALTER TABLE "used_username" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_ip"."createdAt" IS 'The created date of the UserIp.'`);
+ await queryRunner.query(`ALTER TABLE "user_ip" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_list_favorite"."createdAt" IS 'The created date of the UserListFavorite.'`);
+ await queryRunner.query(`ALTER TABLE "user_list_favorite" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "user_list_membership" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_note_pining"."createdAt" IS 'The created date of the UserNotePining.'`);
+ await queryRunner.query(`ALTER TABLE "user_note_pining" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_pending"."createdAt" IS 'The created date of the UserPending.'`);
+ await queryRunner.query(`ALTER TABLE "user_pending" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`COMMENT ON COLUMN "webhook"."createdAt" IS 'The created date of the Webhook.'`);
+ await queryRunner.query(`ALTER TABLE "webhook" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`COMMENT ON COLUMN "retention_aggregation"."createdAt" IS 'The created date of the GalleryPost.'`);
+ await queryRunner.query(`ALTER TABLE "retention_aggregation" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "role" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "role_assignment" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "flash" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`ALTER TABLE "flash" ALTER COLUMN "visibility" SET NOT NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "flash_like"."createdAt" IS 'The created date of the FlashLike.'`);
+ await queryRunner.query(`ALTER TABLE "flash_like" ALTER COLUMN "createdAt" SET DEFAULT now()`);
+ await queryRunner.query(`CREATE INDEX "IDX_f9b40730606162a441c7acb3e5" ON "access_token" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_cbca0122587e5a757ea0e584f0" ON "announcement_read" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_1383c050b99ba7deb995207afe" ON "user_list" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_9425d976c9cf6d47d2b9956344" ON "antenna" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_a3aca00bb7f8d79408edfefe67" ON "avatar_decoration" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_407e3e07747e5cebb916e77914" ON "auth_session" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_823073a0f1f5d44ef83917e0c4" ON "clip" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_e7c0567f5261063592f022e9b5" ON "note" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_94af6cc88a484caf0cd53bfec9" ON "clip_favorite" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_9a20428737dfc7c515fc31c9bc" ON "follow_request" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_3712d1129515e88dedc7c0ca9b" ON "gallery_like" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_1c32fad73f120e11702982f713" ON "moderation_log" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_b7a97c1435dfa03ab42ab7ec92" ON "note_favorite" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_01f4581f114e0ebd2bbb876f0b" ON "note_reaction" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_51fe96e68f335de120a5f8974b" ON "note_thread_muting" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_b72859eb6173fd2e176aad3fbc" ON "page_like" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_0123b5cc155383f3d380170774" ON "password_reset_request" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_65a0babf63cec88aaa804332a0" ON "promo_read" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_0bf1bd10114284dc984d900c8b" ON "registration_ticket" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_0ff7393a15d37079be4e1f2bd5" ON "registry_item" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_68e9b8637a5b186f242d81e41a" ON "signin" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_8781b31c9b1e5c6c0b1cf904c0" ON "sw_subscription" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_4ac8a879384f3fc210bbaa21bc" ON "used_username" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_e15e78ed889553e314336e4952" ON "user_ip" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_970ffee983708c114a0c289903" ON "user_list_favorite" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_d6d398ea7c0d187aa9a91c4ad0" ON "user_list_membership" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_61347f72791a48bfaa9244eb05" ON "user_note_pining" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_e9181436b1294069148b5ba491" ON "user_pending" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_7ad27f46c9449fe9d6fbb4c79c" ON "webhook" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_3c39bd046f5e69d37f0e4fe768" ON "role" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_fe3eb6be723a95c6b7ce539a4f" ON "role_assignment" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_89523d5c47dc3fcc0bd6793f18" ON "flash_like" ("createdAt") `);
+ await queryRunner.query(`ALTER TABLE "announcement" ADD CONSTRAINT "FK_fd25dfe3da37df1715f11ba6ec8" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "announcement" DROP CONSTRAINT "FK_fd25dfe3da37df1715f11ba6ec8"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_89523d5c47dc3fcc0bd6793f18"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_fe3eb6be723a95c6b7ce539a4f"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_3c39bd046f5e69d37f0e4fe768"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_7ad27f46c9449fe9d6fbb4c79c"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_e9181436b1294069148b5ba491"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_61347f72791a48bfaa9244eb05"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_d6d398ea7c0d187aa9a91c4ad0"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_970ffee983708c114a0c289903"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_e15e78ed889553e314336e4952"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_4ac8a879384f3fc210bbaa21bc"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_8781b31c9b1e5c6c0b1cf904c0"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_68e9b8637a5b186f242d81e41a"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_0ff7393a15d37079be4e1f2bd5"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_0bf1bd10114284dc984d900c8b"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_65a0babf63cec88aaa804332a0"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_0123b5cc155383f3d380170774"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_b72859eb6173fd2e176aad3fbc"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_51fe96e68f335de120a5f8974b"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_01f4581f114e0ebd2bbb876f0b"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_b7a97c1435dfa03ab42ab7ec92"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_1c32fad73f120e11702982f713"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_3712d1129515e88dedc7c0ca9b"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_9a20428737dfc7c515fc31c9bc"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_94af6cc88a484caf0cd53bfec9"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_e7c0567f5261063592f022e9b5"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_823073a0f1f5d44ef83917e0c4"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_407e3e07747e5cebb916e77914"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_a3aca00bb7f8d79408edfefe67"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_9425d976c9cf6d47d2b9956344"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_1383c050b99ba7deb995207afe"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_cbca0122587e5a757ea0e584f0"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_f9b40730606162a441c7acb3e5"`);
+ await queryRunner.query(`ALTER TABLE "flash_like" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`COMMENT ON COLUMN "flash_like"."createdAt" IS NULL`);
+ await queryRunner.query(`ALTER TABLE "flash" ALTER COLUMN "visibility" DROP NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "flash" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "role_assignment" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "role" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "retention_aggregation" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`COMMENT ON COLUMN "retention_aggregation"."createdAt" IS 'The created date of the Note.'`);
+ await queryRunner.query(`ALTER TABLE "webhook" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`COMMENT ON COLUMN "webhook"."createdAt" IS 'The created date of the Antenna.'`);
+ await queryRunner.query(`ALTER TABLE "user_pending" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_pending"."createdAt" IS NULL`);
+ await queryRunner.query(`ALTER TABLE "user_note_pining" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_note_pining"."createdAt" IS 'The created date of the UserNotePinings.'`);
+ await queryRunner.query(`ALTER TABLE "user_list_membership" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "user_list_favorite" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_list_favorite"."createdAt" IS NULL`);
+ await queryRunner.query(`ALTER TABLE "user_ip" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_ip"."createdAt" IS NULL`);
+ await queryRunner.query(`ALTER TABLE "used_username" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`COMMENT ON COLUMN "used_username"."createdAt" IS NULL`);
+ await queryRunner.query(`ALTER TABLE "sw_subscription" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`COMMENT ON COLUMN "sw_subscription"."createdAt" IS NULL`);
+ await queryRunner.query(`ALTER TABLE "signin" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "registry_item" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "registration_ticket" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`COMMENT ON COLUMN "registration_ticket"."createdAt" IS NULL`);
+ await queryRunner.query(`ALTER TABLE "promo_read" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "poll_vote" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "password_reset_request" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`COMMENT ON COLUMN "password_reset_request"."createdAt" IS NULL`);
+ await queryRunner.query(`ALTER TABLE "page_like" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`COMMENT ON COLUMN "page_like"."createdAt" IS NULL`);
+ await queryRunner.query(`ALTER TABLE "page" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "note_thread_muting" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`COMMENT ON COLUMN "note_thread_muting"."createdAt" IS NULL`);
+ await queryRunner.query(`ALTER TABLE "note_reaction" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "note_favorite" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "renote_muting" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."createdAt" IS 'The created date of the Muting.'`);
+ await queryRunner.query(`ALTER TABLE "muting" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "moderation_log" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "preservedUsernames" SET DEFAULT '{admin,administrator,root,system,maintainer,host,mod,moderator,owner,superuser,staff,auth,i,me,everyone,all,mention,mentions,example,user,users,account,accounts,official,help,helps,support,supports,info,information,informations,announce,announces,announcement,announcements,notice,notification,notifications,dev,developer,developers,tech,misskey}'`);
+ await queryRunner.query(`ALTER TABLE "gallery_like" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`COMMENT ON COLUMN "gallery_like"."createdAt" IS NULL`);
+ await queryRunner.query(`ALTER TABLE "gallery_post" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "follow_request" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "following" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "clip_favorite" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`COMMENT ON COLUMN "clip_favorite"."createdAt" IS NULL`);
+ await queryRunner.query(`ALTER TABLE "note" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "clip" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "channel_favorite" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "channel_following" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "channel" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "blocking" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "auth_session" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "antenna" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "user_list" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "announcement_read" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "announcement" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "ad" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "app" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "user" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "drive_file" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "drive_folder" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "abuse_report_resolver" ALTER COLUMN "createdAt" DROP DEFAULT`);
+ await queryRunner.query(`COMMENT ON COLUMN "abuse_report_resolver"."createdAt" IS 'The created date of AbuseReportResolver'`);
+ await queryRunner.query(`COMMENT ON COLUMN "avatar_decoration"."createdAt" IS 'The created date of the AvatarDecoration.'`);
+ await queryRunner.query(`ALTER TABLE "avatar_decoration" DROP COLUMN "createdAt"`);
+ }
+}
diff --git a/packages/backend/package.json b/packages/backend/package.json
index c816ccbfdf..a4856709c3 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -10,6 +10,7 @@
"start": "node ./built/index.js",
"start:test": "NODE_ENV=test node ./built/index.js",
"migrate": "pnpm typeorm migration:run -d ormconfig.js",
+ "revert": "pnpm typeorm migration:revert -d ormconfig.js",
"check:connect": "node ./check_connect.js",
"build": "swc src -d built -D",
"watch:swc": "swc src -d built -D -w",
@@ -39,7 +40,7 @@
"@swc/core-win32-x64-msvc": "1.3.56",
"@tensorflow/tfjs": "4.4.0",
"@tensorflow/tfjs-node": "4.4.0",
- "bufferutil": "^4.0.7",
+ "bufferutil": "4.0.7",
"slacc-android-arm-eabi": "0.0.10",
"slacc-android-arm64": "0.0.10",
"slacc-darwin-arm64": "0.0.10",
@@ -53,40 +54,42 @@
"slacc-linux-x64-musl": "0.0.10",
"slacc-win32-arm64-msvc": "0.0.10",
"slacc-win32-x64-msvc": "0.0.10",
- "utf-8-validate": "^6.0.3"
+ "utf-8-validate": "6.0.3"
},
"dependencies": {
- "@aws-sdk/client-s3": "3.367.0",
- "@aws-sdk/lib-storage": "3.367.0",
- "@aws-sdk/node-http-handler": "3.360.0",
- "@bull-board/api": "5.6.1",
- "@bull-board/fastify": "5.6.1",
- "@bull-board/ui": "5.6.1",
+ "@aws-sdk/client-s3": "3.412.0",
+ "@aws-sdk/lib-storage": "3.412.0",
+ "@bull-board/api": "5.9.1",
+ "@bull-board/fastify": "5.9.1",
+ "@bull-board/ui": "5.9.1",
"@discordapp/twemoji": "14.1.2",
"@fastify/accepts": "4.2.0",
- "@fastify/cookie": "8.3.0",
- "@fastify/cors": "8.3.0",
- "@fastify/http-proxy": "9.2.1",
- "@fastify/multipart": "7.7.1",
- "@fastify/static": "6.10.2",
- "@fastify/view": "8.0.0",
- "@nestjs/common": "10.1.0",
- "@nestjs/core": "10.1.0",
- "@nestjs/testing": "10.1.0",
+ "@fastify/cookie": "9.2.0",
+ "@fastify/cors": "8.4.1",
+ "@fastify/express": "2.3.0",
+ "@fastify/http-proxy": "9.3.0",
+ "@fastify/multipart": "8.0.0",
+ "@fastify/static": "6.12.0",
+ "@fastify/view": "8.2.0",
+ "@nestjs/common": "10.2.8",
+ "@nestjs/core": "10.2.8",
+ "@nestjs/testing": "10.2.8",
"@peertube/http-signature": "1.7.0",
- "@simplewebauthn/server": "^8.3.5",
- "@sinonjs/fake-timers": "10.3.0",
- "@swc/cli": "0.1.62",
- "@swc/core": "1.3.70",
+ "@simplewebauthn/server": "8.3.5",
+ "@sinonjs/fake-timers": "11.2.2",
+ "@smithy/node-http-handler": "2.1.5",
+ "@swc/cli": "0.1.63",
+ "@swc/core": "1.3.96",
"accepts": "1.3.8",
"ajv": "8.12.0",
- "archiver": "5.3.1",
- "async-mutex": "^0.4.0",
+ "archiver": "6.0.1",
+ "async-mutex": "0.4.0",
"bcryptjs": "2.4.3",
"blurhash": "2.0.5",
- "bullmq": "4.4.0",
+ "body-parser": "1.20.2",
+ "bullmq": "4.13.3",
"cacheable-lookup": "7.0.0",
- "cbor": "9.0.0",
+ "cbor": "9.0.1",
"chalk": "5.3.0",
"chalk-template": "1.1.0",
"chokidar": "3.5.3",
@@ -95,14 +98,16 @@
"content-disposition": "0.5.4",
"date-fns": "2.30.0",
"deep-email-validator": "0.1.21",
- "fastify": "4.20.0",
+ "fastify": "4.24.3",
+ "fastify-raw-body": "4.3.0",
"feed": "4.2.2",
- "file-type": "18.5.0",
+ "file-type": "18.7.0",
"fluent-ffmpeg": "2.1.2",
"form-data": "4.0.0",
"got": "13.0.0",
"happy-dom": "10.0.3",
"hpagent": "1.2.0",
+ "http-link-header": "1.1.1",
"ioredis": "5.3.2",
"ip-cidr": "3.1.0",
"ipaddr.js": "2.1.0",
@@ -110,104 +115,115 @@
"js-yaml": "4.1.0",
"jsdom": "22.1.0",
"json5": "2.2.3",
- "jsonld": "8.2.0",
+ "jsonld": "8.3.1",
"jsrsasign": "10.8.6",
- "meilisearch": "0.33.0",
+ "meilisearch": "0.35.0",
"mfm-js": "0.23.3",
+ "microformats-parser": "1.5.2",
"mime-types": "2.1.35",
"misskey-js": "workspace:*",
"ms": "3.0.0-canary.1",
+ "nanoid": "5.0.3",
"nested-property": "4.0.0",
- "node-fetch": "3.3.1",
- "nodemailer": "6.9.3",
+ "node-fetch": "3.3.2",
+ "nodemailer": "6.9.7",
"nsfwjs": "2.4.2",
"oauth": "0.10.0",
+ "oauth2orize": "1.12.0",
+ "oauth2orize-pkce": "0.1.2",
"os-utils": "0.0.14",
- "otpauth": "9.1.3",
+ "otpauth": "9.2.0",
"parse5": "7.1.2",
- "pg": "8.11.1",
+ "pg": "8.11.3",
+ "pkce-challenge": "4.0.1",
"probe-image-size": "7.2.3",
"promise-limit": "2.7.0",
"pug": "3.0.2",
- "punycode": "2.3.0",
+ "punycode": "2.3.1",
"pureimage": "0.3.17",
"qrcode": "1.5.3",
"random-seed": "0.3.0",
"ratelimiter": "3.4.1",
- "re2": "1.19.1",
+ "re2": "1.20.8",
"redis-lock": "0.1.4",
"reflect-metadata": "0.1.13",
"rename": "1.0.4",
"rss-parser": "3.13.0",
"rxjs": "7.8.1",
"sanitize-html": "2.11.0",
- "semver": "7.5.4",
+ "secure-json-parse": "2.7.0",
"sharp": "0.32.6",
"sharp-read-bmp": "github:misskey-dev/sharp-read-bmp",
"slacc": "0.0.10",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
"summaly": "github:misskey-dev/summaly",
- "systeminformation": "5.18.7",
+ "systeminformation": "5.21.17",
"tinycolor2": "1.6.0",
"tmp": "0.2.1",
- "tsc-alias": "1.8.7",
+ "tsc-alias": "1.8.8",
"tsconfig-paths": "4.2.0",
"twemoji-parser": "14.0.0",
"typeorm": "0.3.17",
- "typescript": "5.1.6",
+ "typescript": "5.2.2",
"ulid": "2.3.0",
"vary": "1.1.2",
- "web-push": "3.6.3",
- "ws": "8.13.0",
+ "web-push": "3.6.6",
+ "ws": "8.14.2",
"xev": "3.0.2"
},
"devDependencies": {
- "@jest/globals": "29.6.1",
- "@simplewebauthn/typescript-types": "^8.3.4",
- "@swc/jest": "0.2.26",
- "@types/accepts": "1.3.5",
- "@types/archiver": "5.3.2",
- "@types/bcryptjs": "2.4.2",
+ "@jest/globals": "29.7.0",
+ "@simplewebauthn/typescript-types": "8.3.4",
+ "@swc/jest": "0.2.29",
+ "@types/accepts": "1.3.7",
+ "@types/archiver": "6.0.1",
+ "@types/bcryptjs": "2.4.6",
+ "@types/body-parser": "1.19.5",
"@types/cbor": "6.0.0",
- "@types/color-convert": "2.0.0",
- "@types/content-disposition": "0.5.5",
- "@types/fluent-ffmpeg": "2.1.21",
- "@types/jest": "29.5.3",
- "@types/js-yaml": "4.0.5",
- "@types/jsdom": "21.1.1",
- "@types/jsonld": "1.5.9",
- "@types/jsrsasign": "10.5.8",
- "@types/mime-types": "2.1.1",
- "@types/ms": "^0.7.31",
- "@types/node": "20.4.2",
+ "@types/color-convert": "2.0.3",
+ "@types/content-disposition": "0.5.8",
+ "@types/fluent-ffmpeg": "2.1.24",
+ "@types/http-link-header": "1.0.5",
+ "@types/jest": "29.5.8",
+ "@types/js-yaml": "4.0.9",
+ "@types/jsdom": "21.1.5",
+ "@types/jsonld": "1.5.12",
+ "@types/jsrsasign": "10.5.12",
+ "@types/mime-types": "2.1.4",
+ "@types/ms": "0.7.34",
+ "@types/node": "20.9.1",
"@types/node-fetch": "3.0.3",
- "@types/nodemailer": "6.4.8",
- "@types/oauth": "0.9.1",
- "@types/pg": "8.10.2",
- "@types/pug": "2.0.6",
- "@types/punycode": "2.1.0",
- "@types/qrcode": "1.5.1",
- "@types/random-seed": "0.3.3",
- "@types/ratelimiter": "3.4.4",
- "@types/rename": "1.0.4",
- "@types/sanitize-html": "2.9.0",
- "@types/semver": "7.5.0",
+ "@types/nodemailer": "6.4.14",
+ "@types/oauth": "0.9.4",
+ "@types/oauth2orize": "1.11.3",
+ "@types/oauth2orize-pkce": "0.1.2",
+ "@types/pg": "8.10.9",
+ "@types/pug": "2.0.9",
+ "@types/punycode": "2.1.2",
+ "@types/qrcode": "1.5.5",
+ "@types/random-seed": "0.3.5",
+ "@types/ratelimiter": "3.4.6",
+ "@types/rename": "1.0.7",
+ "@types/sanitize-html": "2.9.4",
+ "@types/semver": "7.5.5",
"@types/sharp": "0.32.0",
- "@types/sinonjs__fake-timers": "8.1.2",
- "@types/tinycolor2": "1.4.3",
- "@types/tmp": "0.2.3",
- "@types/vary": "1.1.0",
- "@types/web-push": "3.3.2",
- "@types/ws": "8.5.5",
- "@typescript-eslint/eslint-plugin": "5.61.0",
- "@typescript-eslint/parser": "5.61.0",
+ "@types/simple-oauth2": "5.0.7",
+ "@types/sinonjs__fake-timers": "8.1.5",
+ "@types/tinycolor2": "1.4.6",
+ "@types/tmp": "0.2.6",
+ "@types/vary": "1.1.3",
+ "@types/web-push": "3.6.3",
+ "@types/ws": "8.5.9",
+ "@typescript-eslint/eslint-plugin": "6.11.0",
+ "@typescript-eslint/parser": "6.11.0",
"aws-sdk-client-mock": "3.0.0",
"cross-env": "7.0.3",
- "eslint": "8.45.0",
- "eslint-plugin-import": "2.27.5",
- "execa": "7.1.1",
- "jest": "29.6.1",
- "jest-mock": "29.6.1"
+ "eslint": "8.53.0",
+ "eslint-plugin-import": "2.29.0",
+ "execa": "8.0.1",
+ "jest": "29.7.0",
+ "jest-mock": "29.7.0",
+ "simple-oauth2": "5.0.0"
}
}
diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts
index f6dfe53942..d648febb86 100644
--- a/packages/backend/src/GlobalModule.ts
+++ b/packages/backend/src/GlobalModule.ts
@@ -78,6 +78,7 @@ const $redisForTimelines: Provider = {
},
inject: [DI.config],
};
+
@Global()
@Module({
imports: [RepositoryModule],
diff --git a/packages/backend/src/boot/index.ts b/packages/backend/src/boot/entry.ts
similarity index 100%
rename from packages/backend/src/boot/index.ts
rename to packages/backend/src/boot/entry.ts
diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts
index 5dd7d7baf1..623cc964ac 100644
--- a/packages/backend/src/boot/master.ts
+++ b/packages/backend/src/boot/master.ts
@@ -10,7 +10,6 @@ import * as os from 'node:os';
import cluster from 'node:cluster';
import chalk from 'chalk';
import chalkTemplate from 'chalk-template';
-import semver from 'semver';
import Logger from '@/logger.js';
import { loadConfig } from '@/config.js';
import type { Config } from '@/config.js';
@@ -64,26 +63,40 @@ export async function masterMain() {
showNodejsVersion();
config = loadConfigBoot();
//await connectDb();
+ if (config.pidFile) fs.writeFileSync(config.pidFile, process.pid.toString());
} catch (e) {
bootLogger.error('Fatal error occurred during initialization', null, true);
process.exit(1);
}
- if (envOption.onlyServer) {
- await server();
- } else if (envOption.onlyQueue) {
- await jobQueue();
- } else {
- await server();
- }
-
bootLogger.succ('Misskey initialized');
- if (!envOption.disableClustering) {
+ if (envOption.disableClustering) {
+ if (envOption.onlyServer) {
+ await server();
+ } else if (envOption.onlyQueue) {
+ await jobQueue();
+ } else {
+ await server();
+ await jobQueue();
+ }
+ } else {
+ if (envOption.onlyServer) {
+ // nop
+ } else if (envOption.onlyQueue) {
+ // nop
+ } else {
+ await server();
+ }
+
await spawnWorkers(config.clusterLimit);
}
- bootLogger.succ(config.socket ? `Now listening on socket ${config.socket} on ${config.url}` : `Now listening on port ${config.port} on ${config.url}`, null, true);
+ if (envOption.onlyQueue) {
+ bootLogger.succ('Queue started', null, true);
+ } else {
+ bootLogger.succ(config.socket ? `Now listening on socket ${config.socket} on ${config.url}` : `Now listening on port ${config.port} on ${config.url}`, null, true);
+ }
}
function showEnvironment(): void {
diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts
index 5ef8209405..d8a422d404 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -3,10 +3,6 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-/**
- * Config loader
- */
-
import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';
@@ -23,11 +19,9 @@ type RedisOptionsSource = Partial & {
};
/**
- * ユーザーが設定する必要のある情報
+ * 設定ファイルの型
*/
-export type Source = {
- repository_url?: string;
- feedback_url?: string;
+type Source = {
url: string;
port?: number;
socket?: string;
@@ -73,12 +67,11 @@ export type Source = {
maxFileSize?: number;
- accesslog?: string;
-
clusterLimit?: number;
id: string;
+ outgoingAddress?: string;
outgoingAddressFamily?: 'ipv4' | 'ipv6' | 'dual';
deliverJobConcurrency?: number;
@@ -99,12 +92,61 @@ export type Source = {
perChannelMaxNoteCacheCount?: number;
perUserNotificationsMaxCount?: number;
deactivateAntennaThreshold?: number;
+ pidFile: string;
};
-/**
- * Misskeyが自動的に(ユーザーが設定した情報から推論して)設定する情報
- */
-export type Mixin = {
+export type Config = {
+ url: string;
+ port: number;
+ socket: string | undefined;
+ chmodSocket: string | undefined;
+ disableHsts: boolean | undefined;
+ db: {
+ host: string;
+ port: number;
+ db: string;
+ user: string;
+ pass: string;
+ disableCache?: boolean;
+ extra?: { [x: string]: string };
+ };
+ dbReplications: boolean | undefined;
+ dbSlaves: {
+ host: string;
+ port: number;
+ db: string;
+ user: string;
+ pass: string;
+ }[] | undefined;
+ meilisearch: {
+ host: string;
+ port: string;
+ apiKey: string;
+ ssl?: boolean;
+ index: string;
+ scope?: 'local' | 'global' | string[];
+ } | undefined;
+ proxy: string | undefined;
+ proxySmtp: string | undefined;
+ proxyBypassHosts: string[] | undefined;
+ allowedPrivateNetworks: string[] | undefined;
+ contentSecurityPolicy: string | undefined;
+ maxFileSize: number | undefined;
+ clusterLimit: number | undefined;
+ id: string;
+ outgoingAddress: string | undefined;
+ outgoingAddressFamily: 'ipv4' | 'ipv6' | 'dual' | undefined;
+ deliverJobConcurrency: number | undefined;
+ inboxJobConcurrency: number | undefined;
+ relashionshipJobConcurrency: number | undefined;
+ deliverJobPerSec: number | undefined;
+ inboxJobPerSec: number | undefined;
+ relashionshipJobPerSec: number | undefined;
+ deliverJobMaxAttempts: number | undefined;
+ inboxJobMaxAttempts: number | undefined;
+ proxyRemoteFiles: boolean | undefined;
+ signToActivityPubGet: boolean | undefined;
+
version: string;
host: string;
hostname: string;
@@ -127,10 +169,9 @@ export type Mixin = {
perChannelMaxNoteCacheCount: number;
perUserNotificationsMaxCount: number;
deactivateAntennaThreshold: number;
+ pidFile: string;
};
-export type Config = Source & Mixin;
-
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
@@ -148,7 +189,7 @@ const path = process.env.MISSKEY_CONFIG_YML
? resolve(dir, 'test.yml')
: resolve(dir, 'default.yml');
-export function loadConfig() {
+export function loadConfig(): Config {
const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../built/meta.json`, 'utf-8'));
const clientManifestExists = fs.existsSync(_dirname + '/../../../built/_vite_/manifest.json');
const clientManifest = clientManifestExists ?
@@ -156,47 +197,75 @@ export function loadConfig() {
: { 'src/_boot_.ts': { file: 'src/_boot_.ts' } };
const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source;
- const mixin = {} as Mixin;
-
const url = tryCreateUrl(config.url);
-
- config.url = url.origin;
-
- config.port = config.port ?? parseInt(process.env.PORT ?? '', 10);
-
- mixin.version = meta.version;
- mixin.host = url.host;
- mixin.hostname = url.hostname;
- mixin.scheme = url.protocol.replace(/:$/, '');
- mixin.wsScheme = mixin.scheme.replace('http', 'ws');
- mixin.wsUrl = `${mixin.wsScheme}://${mixin.host}`;
- mixin.apiUrl = `${mixin.scheme}://${mixin.host}/api`;
- mixin.authUrl = `${mixin.scheme}://${mixin.host}/auth`;
- mixin.driveUrl = `${mixin.scheme}://${mixin.host}/files`;
- mixin.userAgent = `Misskey/${meta.version} (${config.url})`;
- mixin.clientEntry = clientManifest['src/_boot_.ts'];
- mixin.clientManifestExists = clientManifestExists;
+ const version = meta.version;
+ const host = url.host;
+ const hostname = url.hostname;
+ const scheme = url.protocol.replace(/:$/, '');
+ const wsScheme = scheme.replace('http', 'ws');
const externalMediaProxy = config.mediaProxy ?
config.mediaProxy.endsWith('/') ? config.mediaProxy.substring(0, config.mediaProxy.length - 1) : config.mediaProxy
: null;
- const internalMediaProxy = `${mixin.scheme}://${mixin.host}/proxy`;
- mixin.mediaProxy = externalMediaProxy ?? internalMediaProxy;
- mixin.externalMediaProxyEnabled = externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy;
+ const internalMediaProxy = `${scheme}://${host}/proxy`;
+ const redis = convertRedisOptions(config.redis, host);
- mixin.videoThumbnailGenerator = config.videoThumbnailGenerator ?
- config.videoThumbnailGenerator.endsWith('/') ? config.videoThumbnailGenerator.substring(0, config.videoThumbnailGenerator.length - 1) : config.videoThumbnailGenerator
- : null;
-
- mixin.redis = convertRedisOptions(config.redis, mixin.host);
- mixin.redisForPubsub = config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, mixin.host) : mixin.redis;
- mixin.redisForJobQueue = config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, mixin.host) : mixin.redis;
- mixin.redisForTimelines = config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, mixin.host) : mixin.redis;
- mixin.perChannelMaxNoteCacheCount = config.perChannelMaxNoteCacheCount ?? 1000;
- mixin.perUserNotificationsMaxCount = config.perUserNotificationsMaxCount ?? 300;
- mixin.deactivateAntennaThreshold = config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7);
-
- return Object.assign(config, mixin);
+ return {
+ version,
+ url: url.origin,
+ port: config.port ?? parseInt(process.env.PORT ?? '', 10),
+ socket: config.socket,
+ chmodSocket: config.chmodSocket,
+ disableHsts: config.disableHsts,
+ host,
+ hostname,
+ scheme,
+ wsScheme,
+ wsUrl: `${wsScheme}://${host}`,
+ apiUrl: `${scheme}://${host}/api`,
+ authUrl: `${scheme}://${host}/auth`,
+ driveUrl: `${scheme}://${host}/files`,
+ db: config.db,
+ dbReplications: config.dbReplications,
+ dbSlaves: config.dbSlaves,
+ meilisearch: config.meilisearch,
+ redis,
+ redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis,
+ redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis,
+ redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis,
+ id: config.id,
+ proxy: config.proxy,
+ proxySmtp: config.proxySmtp,
+ proxyBypassHosts: config.proxyBypassHosts,
+ allowedPrivateNetworks: config.allowedPrivateNetworks,
+ contentSecurityPolicy: config.contentSecurityPolicy,
+ maxFileSize: config.maxFileSize,
+ clusterLimit: config.clusterLimit,
+ outgoingAddress: config.outgoingAddress,
+ outgoingAddressFamily: config.outgoingAddressFamily,
+ deliverJobConcurrency: config.deliverJobConcurrency,
+ inboxJobConcurrency: config.inboxJobConcurrency,
+ relashionshipJobConcurrency: config.relashionshipJobConcurrency,
+ deliverJobPerSec: config.deliverJobPerSec,
+ inboxJobPerSec: config.inboxJobPerSec,
+ relashionshipJobPerSec: config.relashionshipJobPerSec,
+ deliverJobMaxAttempts: config.deliverJobMaxAttempts,
+ inboxJobMaxAttempts: config.inboxJobMaxAttempts,
+ proxyRemoteFiles: config.proxyRemoteFiles,
+ signToActivityPubGet: config.signToActivityPubGet,
+ mediaProxy: externalMediaProxy ?? internalMediaProxy,
+ externalMediaProxyEnabled: externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy,
+ videoThumbnailGenerator: config.videoThumbnailGenerator ?
+ config.videoThumbnailGenerator.endsWith('/') ? config.videoThumbnailGenerator.substring(0, config.videoThumbnailGenerator.length - 1) : config.videoThumbnailGenerator
+ : null,
+ userAgent: `Misskey/${version} (${config.url})`,
+ clientEntry: clientManifest['src/_boot_.ts'],
+ clientManifestExists: clientManifestExists,
+ perChannelMaxNoteCacheCount: config.perChannelMaxNoteCacheCount ?? 1000,
+ perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 500,
+ deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7),
+ pidFile: config.pidFile,
+ };
}
function tryCreateUrl(url: string) {
@@ -212,7 +281,7 @@ function convertRedisOptions(options: RedisOptionsSource, host: string): RedisOp
...options,
password: options.pass,
prefix: options.prefix ?? host,
- family: options.family == null ? 0 : options.family,
+ family: options.family ?? 0,
keyPrefix: `${options.prefix ?? host}:`,
db: options.db ?? 0,
};
diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts
index f0a3b78946..d92bc23ea6 100644
--- a/packages/backend/src/core/AccountMoveService.ts
+++ b/packages/backend/src/core/AccountMoveService.ts
@@ -9,8 +9,8 @@ import { IsNull, In, MoreThan, Not } from 'typeorm';
import { bindThis } from '@/decorators.js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
-import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/entities/User.js';
-import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MiMuting, MutingsRepository, UserListJoiningsRepository, UsersRepository } from '@/models/index.js';
+import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/User.js';
+import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, UserListMembershipsRepository, UsersRepository } from '@/models/_.js';
import type { RelationshipJobData, ThinUser } from '@/queue/types.js';
import { IdService } from '@/core/IdService.js';
@@ -31,9 +31,6 @@ import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
@Injectable()
export class AccountMoveService {
constructor(
- @Inject(DI.config)
- private config: Config,
-
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@@ -46,8 +43,8 @@ export class AccountMoveService {
@Inject(DI.mutingsRepository)
private mutingsRepository: MutingsRepository,
- @Inject(DI.userListJoiningsRepository)
- private userListJoiningsRepository: UserListJoiningsRepository,
+ @Inject(DI.userListMembershipsRepository)
+ private userListMembershipsRepository: UserListMembershipsRepository,
@Inject(DI.instancesRepository)
private instancesRepository: InstancesRepository,
@@ -184,13 +181,13 @@ export class AccountMoveService {
{ muteeId: dst.id, expiresAt: IsNull() },
).then(mutings => mutings.map(muting => muting.muterId));
- const newMutings: Map = new Map();
+ const newMutings: Map = new Map();
// 重複しないようにIDを生成
const genId = (): string => {
let id: string;
do {
- id = this.idService.genId();
+ id = this.idService.gen();
} while (newMutings.has(id));
return id;
};
@@ -198,7 +195,6 @@ export class AccountMoveService {
if (existingMutingsMuterUserIds.includes(muting.muterId)) continue; // skip if already muted indefinitely
newMutings.set(genId(), {
...muting,
- createdAt: new Date(),
muteeId: dst.id,
});
}
@@ -219,41 +215,40 @@ export class AccountMoveService {
@bindThis
public async updateLists(src: ThinUser, dst: MiUser): Promise {
// Return if there is no list to be updated.
- const oldJoinings = await this.userListJoiningsRepository.find({
+ const oldMemberships = await this.userListMembershipsRepository.find({
where: {
userId: src.id,
},
});
- if (oldJoinings.length === 0) return;
+ if (oldMemberships.length === 0) return;
- const existingUserListIds = await this.userListJoiningsRepository.find({
+ const existingUserListIds = await this.userListMembershipsRepository.find({
where: {
userId: dst.id,
},
- }).then(joinings => joinings.map(joining => joining.userListId));
+ }).then(memberships => memberships.map(membership => membership.userListId));
- const newJoinings: Map = new Map();
+ const newMemberships: Map = new Map();
// 重複しないようにIDを生成
const genId = (): string => {
let id: string;
do {
- id = this.idService.genId();
- } while (newJoinings.has(id));
+ id = this.idService.gen();
+ } while (newMemberships.has(id));
return id;
};
- for (const joining of oldJoinings) {
- if (existingUserListIds.includes(joining.userListId)) continue; // skip if dst exists in this user's list
- newJoinings.set(genId(), {
- createdAt: new Date(),
+ for (const membership of oldMemberships) {
+ if (existingUserListIds.includes(membership.userListId)) continue; // skip if dst exists in this user's list
+ newMemberships.set(genId(), {
userId: dst.id,
- userListId: joining.userListId,
- userListUserId: joining.userListUserId,
+ userListId: membership.userListId,
+ userListUserId: membership.userListUserId,
});
}
- const arrayToInsert = Array.from(newJoinings.entries()).map(entry => ({ ...entry[1], id: entry[0] }));
- await this.userListJoiningsRepository.insert(arrayToInsert);
+ const arrayToInsert = Array.from(newMemberships.entries()).map(entry => ({ ...entry[1], id: entry[0] }));
+ await this.userListMembershipsRepository.insert(arrayToInsert);
// Have the proxy account follow the new account in the same way as UserListService.push
if (this.userEntityService.isRemoteUser(dst)) {
diff --git a/packages/backend/src/core/AccountUpdateService.ts b/packages/backend/src/core/AccountUpdateService.ts
index 5cd455fa77..664700ea6b 100644
--- a/packages/backend/src/core/AccountUpdateService.ts
+++ b/packages/backend/src/core/AccountUpdateService.ts
@@ -5,9 +5,8 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { UsersRepository } from '@/models/index.js';
-import type { Config } from '@/config.js';
-import type { MiUser } from '@/models/entities/User.js';
+import type { UsersRepository } from '@/models/_.js';
+import type { MiUser } from '@/models/User.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { RelayService } from '@/core/RelayService.js';
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
@@ -17,9 +16,6 @@ import { bindThis } from '@/decorators.js';
@Injectable()
export class AccountUpdateService {
constructor(
- @Inject(DI.config)
- private config: Config,
-
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
diff --git a/packages/backend/src/core/AchievementService.ts b/packages/backend/src/core/AchievementService.ts
index 8526e3f98b..88fc033859 100644
--- a/packages/backend/src/core/AchievementService.ts
+++ b/packages/backend/src/core/AchievementService.ts
@@ -4,8 +4,8 @@
*/
import { Inject, Injectable } from '@nestjs/common';
-import type { UserProfilesRepository, UsersRepository } from '@/models/index.js';
-import type { MiUser } from '@/models/entities/User.js';
+import type { UserProfilesRepository } from '@/models/_.js';
+import type { MiUser } from '@/models/User.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { NotificationService } from '@/core/NotificationService.js';
@@ -85,14 +85,13 @@ export const ACHIEVEMENT_TYPES = [
'setNameToSyuilo',
'cookieClicked',
'brainDiver',
+ 'smashTestNotificationButton',
+ 'tutorialCompleted',
] as const;
@Injectable()
export class AchievementService {
constructor(
- @Inject(DI.usersRepository)
- private usersRepository: UsersRepository,
-
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
diff --git a/packages/backend/src/core/AiService.ts b/packages/backend/src/core/AiService.ts
index fc7b0226c0..4e876495a6 100644
--- a/packages/backend/src/core/AiService.ts
+++ b/packages/backend/src/core/AiService.ts
@@ -6,12 +6,10 @@
import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
-import { Inject, Injectable } from '@nestjs/common';
+import { Injectable } from '@nestjs/common';
import * as nsfw from 'nsfwjs';
import si from 'systeminformation';
import { Mutex } from 'async-mutex';
-import type { Config } from '@/config.js';
-import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
const _filename = fileURLToPath(import.meta.url);
@@ -26,8 +24,6 @@ export class AiService {
private modelLoadMutex: Mutex = new Mutex();
constructor(
- @Inject(DI.config)
- private config: Config,
) {
}
diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts
index 49913be111..58b07bef9a 100644
--- a/packages/backend/src/core/AnnouncementService.ts
+++ b/packages/backend/src/core/AnnouncementService.ts
@@ -5,16 +5,17 @@
import { Inject, Injectable } from '@nestjs/common';
import { Brackets, In } from 'typeorm';
-import type { AnnouncementReadsRepository, AnnouncementsRepository, UsersRepository } from '@/models/index.js';
-import type { MiUser } from '@/models/entities/User.js';
-import { MiAnnouncement, MiAnnouncementRead } from '@/models/index.js';
-import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntityService.js';
-import { bindThis } from '@/decorators.js';
import { DI } from '@/di-symbols.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
-import { IdService } from '@/core/IdService.js';
+import type { MiUser } from '@/models/User.js';
+import type { AnnouncementReadsRepository, AnnouncementsRepository, MiAnnouncement, UsersRepository } from '@/models/_.js';
+import { MiAnnouncementRead } from '@/models/_.js';
+import { bindThis } from '@/decorators.js';
import { Packed } from '@/misc/json-schema.js';
+import { IdService } from '@/core/IdService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntityService.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
@Injectable()
export class AnnouncementService {
@@ -32,49 +33,97 @@ export class AnnouncementService {
private userEntityService: UserEntityService,
private announcementEntityService: AnnouncementEntityService,
private globalEventService: GlobalEventService,
- ) {}
+ private moderationLogService: ModerationLogService,
+ ) {
+ }
@bindThis
- public async create(
- values: Partial,
- ): Promise<{ raw: MiAnnouncement; packed: Packed<'Announcement'> }> {
- const announcement = await this.announcementsRepository
- .insert({
- id: this.idService.genId(),
- createdAt: new Date(),
- updatedAt: null,
- title: values.title,
- text: values.text,
- imageUrl: values.imageUrl,
- icon: values.icon,
- display: values.display,
- forExistingUsers: values.forExistingUsers,
- needConfirmationToRead: values.needConfirmationToRead,
- closeDuration: values.closeDuration,
- displayOrder: values.displayOrder,
- userId: values.userId,
- })
- .then((x) =>
- this.announcementsRepository.findOneByOrFail(x.identifiers[0]),
- );
+ public async getReads(userId: MiUser['id']): Promise {
+ return this.announcementReadsRepository.findBy({
+ userId: userId,
+ });
+ }
- const packed = await this.announcementEntityService.pack(
- announcement,
- null,
+ @bindThis
+ public async getUnreadAnnouncements(user: MiUser): Promise[]> {
+ const q = this.announcementsRepository.createQueryBuilder('announcement');
+ q.leftJoinAndSelect(
+ MiAnnouncementRead,
+ 'read',
+ 'read."announcementId" = announcement.id AND read."userId" = :userId',
+ { userId: user.id },
);
+ q
+ .where('read.id IS NULL')
+ .andWhere('announcement.isActive = true')
+ .andWhere('announcement.silence = false')
+ .andWhere(new Brackets(qb => {
+ qb.orWhere('announcement.userId = :userId', { userId: user.id });
+ qb.orWhere('announcement.userId IS NULL');
+ }))
+ .andWhere(new Brackets(qb => {
+ qb.orWhere('announcement.forExistingUsers = false');
+ qb.orWhere('announcement.id > :userId', { userId: user.id });
+ }));
+
+ q.orderBy({
+ 'announcement."displayOrder"': 'DESC',
+ 'announcement.id': 'DESC',
+ });
+
+ return this.announcementEntityService.packMany(
+ await q.getMany(),
+ user,
+ );
+ }
+
+ @bindThis
+ public async create(values: Partial, moderator?: MiUser): Promise<{ raw: MiAnnouncement; packed: Packed<'Announcement'> }> {
+ const announcement = await this.announcementsRepository.insert({
+ id: this.idService.gen(),
+ updatedAt: null,
+ title: values.title,
+ text: values.text,
+ imageUrl: values.imageUrl,
+ icon: values.icon,
+ display: values.display,
+ forExistingUsers: values.forExistingUsers,
+ silence: values.silence,
+ needConfirmationToRead: values.needConfirmationToRead,
+ closeDuration: values.closeDuration,
+ displayOrder: values.displayOrder,
+ userId: values.userId,
+ }).then(x => this.announcementsRepository.findOneByOrFail(x.identifiers[0]));
+
+ const packed = (await this.announcementEntityService.packMany([announcement], null))[0];
+
if (values.userId) {
- this.globalEventService.publishMainStream(
- values.userId,
- 'announcementCreated',
- {
- announcement: packed,
- },
- );
+ this.globalEventService.publishMainStream(values.userId, 'announcementCreated', {
+ announcement: packed,
+ });
+
+ if (moderator) {
+ const user = await this.usersRepository.findOneByOrFail({ id: values.userId });
+ this.moderationLogService.log(moderator, 'createUserAnnouncement', {
+ announcementId: announcement.id,
+ announcement: announcement,
+ userId: values.userId,
+ userUsername: user.username,
+ userHost: user.host,
+ });
+ }
} else {
this.globalEventService.publishBroadcastStream('announcementCreated', {
announcement: packed,
});
+
+ if (moderator) {
+ this.moderationLogService.log(moderator, 'createGlobalAnnouncement', {
+ announcementId: announcement.id,
+ announcement: announcement,
+ });
+ }
}
return {
@@ -89,7 +138,7 @@ export class AnnouncementService {
limit: number,
offset: number,
moderator: MiUser,
- ): Promise<(MiAnnouncement & { userInfo: Packed<'UserLite'> | null, readCount: number })[]> {
+ ): Promise<(MiAnnouncement & { userInfo: Packed<'UserLite'> | null, reads: number })[]> {
const query = this.announcementsRepository.createQueryBuilder('announcement');
if (userId) {
query.andWhere('announcement."userId" = :userId', { userId: userId });
@@ -100,7 +149,7 @@ export class AnnouncementService {
query.orderBy({
'announcement."isActive"': 'DESC',
'announcement."displayOrder"': 'DESC',
- 'announcement."createdAt"': 'DESC',
+ 'announcement.id': 'DESC',
});
const announcements = await query
@@ -126,81 +175,83 @@ export class AnnouncementService {
return announcements.map(announcement => ({
...announcement,
userInfo: packedUsers.find(u => u.id === announcement.userId) ?? null,
- readCount: reads.get(announcement) ?? 0,
+ reads: reads.get(announcement) ?? 0,
}));
}
@bindThis
- public async update(
- announcementId: MiAnnouncement['id'],
- values: Partial,
- ): Promise<{ raw: MiAnnouncement; packed: Packed<'Announcement'> }> {
- const oldAnnouncement = await this.announcementsRepository.findOneByOrFail({
- id: announcementId,
- });
-
- if (oldAnnouncement.userId && oldAnnouncement.userId !== values.userId) {
+ public async update(announcement: MiAnnouncement, values: Partial, moderator?: MiUser): Promise {
+ if (announcement.userId && announcement.userId !== values.userId) {
await this.announcementReadsRepository.delete({
- announcementId: announcementId,
- userId: oldAnnouncement.userId,
+ announcementId: announcement.id,
+ userId: announcement.userId,
});
}
- const announcement = await this.announcementsRepository
- .update(announcementId, {
- updatedAt: new Date(),
- isActive: values.isActive,
- title: values.title,
- text: values.text,
- imageUrl: values.imageUrl !== '' ? values.imageUrl : null,
- icon: values.icon,
- display: values.display,
- forExistingUsers: values.forExistingUsers,
- needConfirmationToRead: values.needConfirmationToRead,
- closeDuration: values.closeDuration,
- displayOrder: values.displayOrder,
- userId: values.userId,
- })
- .then(() =>
- this.announcementsRepository.findOneByOrFail({ id: announcementId }),
- );
+ await this.announcementsRepository.update(announcement.id, {
+ updatedAt: new Date(),
+ isActive: values.isActive,
+ title: values.title,
+ text: values.text,
+ /* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 空の文字列の場合、nullを渡すようにするため */
+ imageUrl: values.imageUrl || null,
+ display: values.display,
+ icon: values.icon,
+ forExistingUsers: values.forExistingUsers,
+ needConfirmationToRead: values.needConfirmationToRead,
+ closeDuration: values.closeDuration,
+ displayOrder: values.displayOrder,
+ silence: values.silence,
+ userId: values.userId,
+ });
- const packed = await this.announcementEntityService.pack(
- announcement,
- announcement.userId ? { id: announcement.userId } : null,
- );
+ const after = await this.announcementsRepository.findOneByOrFail({ id: announcement.id });
- if (announcement.isActive) {
+ if (moderator) {
if (announcement.userId) {
- this.globalEventService.publishMainStream(
- announcement.userId,
- 'announcementCreated',
- {
- announcement: packed,
- },
- );
+ const user = await this.usersRepository.findOneByOrFail({ id: announcement.userId });
+ this.moderationLogService.log(moderator, 'updateUserAnnouncement', {
+ announcementId: announcement.id,
+ before: announcement,
+ after: after,
+ userId: announcement.userId,
+ userUsername: user.username,
+ userHost: user.host,
+ });
} else {
- this.globalEventService.publishBroadcastStream(
- 'announcementCreated',
- {
- announcement: packed,
- },
- );
+ this.moderationLogService.log(moderator, 'updateGlobalAnnouncement', {
+ announcementId: announcement.id,
+ before: announcement,
+ after: after,
+ });
}
}
-
- return {
- raw: announcement,
- packed: packed,
- };
}
@bindThis
- public async delete(announcementId: MiAnnouncement['id']): Promise {
+ public async delete(announcement: MiAnnouncement, moderator?: MiUser): Promise {
await this.announcementReadsRepository.delete({
- announcementId: announcementId,
+ announcementId: announcement.id,
});
- await this.announcementsRepository.delete({ id: announcementId });
+ await this.announcementsRepository.delete(announcement.id);
+
+ if (moderator) {
+ if (announcement.userId) {
+ const user = await this.usersRepository.findOneByOrFail({ id: announcement.userId });
+ this.moderationLogService.log(moderator, 'deleteUserAnnouncement', {
+ announcementId: announcement.id,
+ announcement: announcement,
+ userId: announcement.userId,
+ userUsername: user.username,
+ userHost: user.host,
+ });
+ } else {
+ this.moderationLogService.log(moderator, 'deleteGlobalAnnouncement', {
+ announcementId: announcement.id,
+ announcement: announcement,
+ });
+ }
+ }
}
@bindThis
@@ -220,7 +271,7 @@ export class AnnouncementService {
);
query.select([
'announcement.*',
- 'CASE WHEN read.id IS NULL THEN FALSE ELSE TRUE END as "isRead"',
+ 'read.id IS NOT NULL as "isRead"',
]);
query
.andWhere(
@@ -232,9 +283,7 @@ export class AnnouncementService {
.andWhere(
new Brackets((qb) => {
qb.orWhere('announcement."forExistingUsers" = false');
- qb.orWhere('announcement."createdAt" > :createdAt', {
- createdAt: me.createdAt,
- });
+ qb.orWhere('announcement.id > :userId', { userId: me.id });
}),
);
} else {
@@ -255,7 +304,7 @@ export class AnnouncementService {
query.orderBy({
'"isRead"': 'ASC',
'announcement."displayOrder"': 'DESC',
- 'announcement."createdAt"': 'DESC',
+ 'announcement.id': 'DESC',
});
return this.announcementEntityService.packMany(
@@ -268,93 +317,45 @@ export class AnnouncementService {
}
@bindThis
- public async getUnreadAnnouncements(me: MiUser): Promise[]> {
- const query = this.announcementsRepository.createQueryBuilder('announcement');
- query.leftJoinAndSelect(
+ public async countUnreadAnnouncements(user: MiUser): Promise {
+ const q = this.announcementsRepository.createQueryBuilder('announcement');
+ q.leftJoinAndSelect(
MiAnnouncementRead,
'read',
'read."announcementId" = announcement.id AND read."userId" = :userId',
- { userId: me.id },
+ { userId: user.id },
);
- query.andWhere('read.id IS NULL');
- query.andWhere('announcement."isActive" = true');
- query
- .andWhere(
- new Brackets((qb) => {
- qb.orWhere('announcement."userId" = :userId', { userId: me.id });
- qb.orWhere('announcement."userId" IS NULL');
- }),
- )
- .andWhere(
- new Brackets((qb) => {
- qb.orWhere('announcement."forExistingUsers" = false');
- qb.orWhere('announcement."createdAt" > :createdAt', {
- createdAt: me.createdAt,
- });
- }),
- );
+ q
+ .where('read.id IS NULL')
+ .andWhere('announcement.isActive = true')
+ .andWhere('announcement.silence = false')
+ .andWhere(new Brackets(qb => {
+ qb.orWhere('announcement.userId = :userId', { userId: user.id });
+ qb.orWhere('announcement.userId IS NULL');
+ }))
+ .andWhere(new Brackets(qb => {
+ qb.orWhere('announcement.forExistingUsers = false');
+ qb.orWhere('announcement.id > :userId', { userId: user.id });
+ }));
- query.orderBy({
- 'announcement."displayOrder"': 'DESC',
- 'announcement."createdAt"': 'DESC',
- });
-
- return this.announcementEntityService.packMany(
- await query.getMany(),
- me,
- );
+ return q.getCount();
}
@bindThis
- public async countUnreadAnnouncements(me: MiUser): Promise {
- const query = this.announcementsRepository.createQueryBuilder('announcement');
- query.leftJoinAndSelect(
- MiAnnouncementRead,
- 'read',
- 'read."announcementId" = announcement.id AND read."userId" = :userId',
- { userId: me.id },
- );
- query.andWhere('read.id IS NULL');
- query.andWhere('announcement."isActive" = true');
-
- query
- .andWhere(
- new Brackets((qb) => {
- qb.orWhere('announcement."userId" = :userId', { userId: me.id });
- qb.orWhere('announcement."userId" IS NULL');
- }),
- )
- .andWhere(
- new Brackets((qb) => {
- qb.orWhere('announcement."forExistingUsers" = false');
- qb.orWhere('announcement."createdAt" > :createdAt', {
- createdAt: me.createdAt,
- });
- }),
- );
-
- return query.getCount();
- }
-
- @bindThis
- public async markAsRead(
- me: MiUser,
- announcementId: MiAnnouncement['id'],
- ): Promise {
+ public async read(user: MiUser, announcementId: MiAnnouncement['id']): Promise {
try {
await this.announcementReadsRepository.insert({
- id: this.idService.genId(),
- createdAt: new Date(),
+ id: this.idService.gen(),
announcementId: announcementId,
- userId: me.id,
+ userId: user.id,
});
} catch (e) {
return;
}
- if ((await this.countUnreadAnnouncements(me)) === 0) {
- this.globalEventService.publishMainStream(me.id, 'readAllAnnouncements');
+ if ((await this.countUnreadAnnouncements(user)) === 0) {
+ this.globalEventService.publishMainStream(user.id, 'readAllAnnouncements');
}
}
}
diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts
index 769ab97694..cd1f32cc00 100644
--- a/packages/backend/src/core/AntennaService.ts
+++ b/packages/backend/src/core/AntennaService.ts
@@ -5,22 +5,18 @@
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
-import type { MiAntenna } from '@/models/entities/Antenna.js';
-import type { MiNote } from '@/models/entities/Note.js';
-import type { MiUser } from '@/models/entities/User.js';
-import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
-import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js';
-import { IdService } from '@/core/IdService.js';
-import { isUserRelated } from '@/misc/is-user-related.js';
+import type { MiAntenna } from '@/models/Antenna.js';
+import type { MiNote } from '@/models/Note.js';
+import type { MiUser } from '@/models/User.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
-import { PushNotificationService } from '@/core/PushNotificationService.js';
import * as Acct from '@/misc/acct.js';
import type { Packed } from '@/misc/json-schema.js';
import { DI } from '@/di-symbols.js';
-import type { MutingsRepository, NotesRepository, AntennasRepository, UserListJoiningsRepository } from '@/models/index.js';
+import type { AntennasRepository, UserListMembershipsRepository } from '@/models/_.js';
import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
-import { StreamMessages } from '@/server/api/stream/types.js';
+import type { GlobalEvents } from '@/core/GlobalEventService.js';
+import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
import type { OnApplicationShutdown } from '@nestjs/common';
@Injectable()
@@ -29,30 +25,21 @@ export class AntennaService implements OnApplicationShutdown {
private antennas: MiAntenna[];
constructor(
- @Inject(DI.redis)
- private redisClient: Redis.Redis,
+ @Inject(DI.redisForTimelines)
+ private redisForTimelines: Redis.Redis,
@Inject(DI.redisForSub)
private redisForSub: Redis.Redis,
- @Inject(DI.mutingsRepository)
- private mutingsRepository: MutingsRepository,
-
- @Inject(DI.notesRepository)
- private notesRepository: NotesRepository,
-
@Inject(DI.antennasRepository)
private antennasRepository: AntennasRepository,
- @Inject(DI.userListJoiningsRepository)
- private userListJoiningsRepository: UserListJoiningsRepository,
+ @Inject(DI.userListMembershipsRepository)
+ private userListMembershipsRepository: UserListMembershipsRepository,
private utilityService: UtilityService,
- private idService: IdService,
private globalEventService: GlobalEventService,
- private pushNotificationService: PushNotificationService,
- private noteEntityService: NoteEntityService,
- private antennaEntityService: AntennaEntityService,
+ private funoutTimelineService: FunoutTimelineService,
) {
this.antennasFetched = false;
this.antennas = [];
@@ -65,7 +52,7 @@ export class AntennaService implements OnApplicationShutdown {
const obj = JSON.parse(data);
if (obj.channel === 'internal') {
- const { type, body } = obj.message as StreamMessages['internal']['payload'];
+ const { type, body } = obj.message as GlobalEvents['internal']['payload'];
switch (type) {
case 'antennaCreated':
this.antennas.push({
@@ -96,15 +83,10 @@ export class AntennaService implements OnApplicationShutdown {
const antennasWithMatchResult = await Promise.all(antennas.map(antenna => this.checkHitAntenna(antenna, note, noteUser).then(hit => [antenna, hit] as const)));
const matchedAntennas = antennasWithMatchResult.filter(([, hit]) => hit).map(([antenna]) => antenna);
- const redisPipeline = this.redisClient.pipeline();
+ const redisPipeline = this.redisForTimelines.pipeline();
for (const antenna of matchedAntennas) {
- redisPipeline.xadd(
- `antennaTimeline:${antenna.id}`,
- 'MAXLEN', '~', '200',
- '*',
- 'note', note.id);
-
+ this.funoutTimelineService.push(`antennaTimeline:${antenna.id}`, note.id, 200, redisPipeline);
this.globalEventService.publishAntennaStream(antenna.id, 'note', note);
}
@@ -118,12 +100,14 @@ export class AntennaService implements OnApplicationShutdown {
if (note.visibility === 'specified') return false;
if (note.visibility === 'followers') return false;
+ if (antenna.localOnly && noteUser.host != null) return false;
+
if (!antenna.withReplies && note.replyId != null) return false;
if (antenna.src === 'home') {
// TODO
} else if (antenna.src === 'list') {
- const listUsers = (await this.userListJoiningsRepository.findBy({
+ const listUsers = (await this.userListMembershipsRepository.findBy({
userListId: antenna.userListId!,
})).map(x => x.userId);
@@ -134,6 +118,12 @@ export class AntennaService implements OnApplicationShutdown {
return this.utilityService.getFullApAccount(username, host).toLowerCase();
});
if (!accts.includes(this.utilityService.getFullApAccount(noteUser.username, noteUser.host).toLowerCase())) return false;
+ } else if (antenna.src === 'users_blacklist') {
+ const accts = antenna.users.map(x => {
+ const { username, host } = Acct.parse(x);
+ return this.utilityService.getFullApAccount(username, host).toLowerCase();
+ });
+ if (accts.includes(this.utilityService.getFullApAccount(noteUser.username, noteUser.host).toLowerCase())) return false;
}
const keywords = antenna.keywords
diff --git a/packages/backend/src/core/AvatarDecorationService.ts b/packages/backend/src/core/AvatarDecorationService.ts
new file mode 100644
index 0000000000..e97946f9dc
--- /dev/null
+++ b/packages/backend/src/core/AvatarDecorationService.ts
@@ -0,0 +1,129 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
+import * as Redis from 'ioredis';
+import type { AvatarDecorationsRepository, MiAvatarDecoration, MiUser } from '@/models/_.js';
+import { IdService } from '@/core/IdService.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { DI } from '@/di-symbols.js';
+import { bindThis } from '@/decorators.js';
+import { MemorySingleCache } from '@/misc/cache.js';
+import type { GlobalEvents } from '@/core/GlobalEventService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
+
+@Injectable()
+export class AvatarDecorationService implements OnApplicationShutdown {
+ public cache: MemorySingleCache;
+
+ constructor(
+ @Inject(DI.redisForSub)
+ private redisForSub: Redis.Redis,
+
+ @Inject(DI.avatarDecorationsRepository)
+ private avatarDecorationsRepository: AvatarDecorationsRepository,
+
+ private idService: IdService,
+ private moderationLogService: ModerationLogService,
+ private globalEventService: GlobalEventService,
+ ) {
+ this.cache = new MemorySingleCache(1000 * 60 * 30);
+
+ this.redisForSub.on('message', this.onMessage);
+ }
+
+ @bindThis
+ private async onMessage(_: string, data: string): Promise {
+ const obj = JSON.parse(data);
+
+ if (obj.channel === 'internal') {
+ const { type, body } = obj.message as GlobalEvents['internal']['payload'];
+ switch (type) {
+ case 'avatarDecorationCreated':
+ case 'avatarDecorationUpdated':
+ case 'avatarDecorationDeleted': {
+ this.cache.delete();
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+
+ @bindThis
+ public async create(options: Partial, moderator?: MiUser): Promise {
+ const created = await this.avatarDecorationsRepository.insert({
+ id: this.idService.gen(),
+ ...options,
+ }).then(x => this.avatarDecorationsRepository.findOneByOrFail(x.identifiers[0]));
+
+ this.globalEventService.publishInternalEvent('avatarDecorationCreated', created);
+
+ if (moderator) {
+ this.moderationLogService.log(moderator, 'createAvatarDecoration', {
+ avatarDecorationId: created.id,
+ avatarDecoration: created,
+ });
+ }
+
+ return created;
+ }
+
+ @bindThis
+ public async update(id: MiAvatarDecoration['id'], params: Partial, moderator?: MiUser): Promise {
+ const avatarDecoration = await this.avatarDecorationsRepository.findOneByOrFail({ id });
+
+ const date = new Date();
+ await this.avatarDecorationsRepository.update(avatarDecoration.id, {
+ updatedAt: date,
+ ...params,
+ });
+
+ const updated = await this.avatarDecorationsRepository.findOneByOrFail({ id: avatarDecoration.id });
+ this.globalEventService.publishInternalEvent('avatarDecorationUpdated', updated);
+
+ if (moderator) {
+ this.moderationLogService.log(moderator, 'updateAvatarDecoration', {
+ avatarDecorationId: avatarDecoration.id,
+ before: avatarDecoration,
+ after: updated,
+ });
+ }
+ }
+
+ @bindThis
+ public async delete(id: MiAvatarDecoration['id'], moderator?: MiUser): Promise {
+ const avatarDecoration = await this.avatarDecorationsRepository.findOneByOrFail({ id });
+
+ await this.avatarDecorationsRepository.delete({ id: avatarDecoration.id });
+ this.globalEventService.publishInternalEvent('avatarDecorationDeleted', avatarDecoration);
+
+ if (moderator) {
+ this.moderationLogService.log(moderator, 'deleteAvatarDecoration', {
+ avatarDecorationId: avatarDecoration.id,
+ avatarDecoration: avatarDecoration,
+ });
+ }
+ }
+
+ @bindThis
+ public async getAll(noCache = false): Promise {
+ if (noCache) {
+ this.cache.delete();
+ }
+ return this.cache.fetch(() => this.avatarDecorationsRepository.find());
+ }
+
+ @bindThis
+ public dispose(): void {
+ this.redisForSub.off('message', this.onMessage);
+ }
+
+ @bindThis
+ public onApplicationShutdown(signal?: string | undefined): void {
+ this.dispose();
+ }
+}
diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts
index 15be5f398c..e1413342b1 100644
--- a/packages/backend/src/core/CacheService.ts
+++ b/packages/backend/src/core/CacheService.ts
@@ -5,13 +5,13 @@
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
-import type { BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository } from '@/models/index.js';
+import type { BlockingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository, MiFollowing } from '@/models/_.js';
import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
-import type { MiLocalUser, MiUser } from '@/models/entities/User.js';
+import type { MiLocalUser, MiUser } from '@/models/User.js';
import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
-import { StreamMessages } from '@/server/api/stream/types.js';
+import type { GlobalEvents } from '@/core/GlobalEventService.js';
import type { OnApplicationShutdown } from '@nestjs/common';
@Injectable()
@@ -25,8 +25,7 @@ export class CacheService implements OnApplicationShutdown {
public userBlockingCache: RedisKVCache>;
public userBlockedCache: RedisKVCache>; // NOTE: 「被」Blockキャッシュ
public renoteMutingsCache: RedisKVCache>;
- public userFollowingsCache: RedisKVCache>;
- public userFollowingChannelsCache: RedisKVCache>;
+ public userFollowingsCache: RedisKVCache | undefined>>;
constructor(
@Inject(DI.redis)
@@ -53,9 +52,6 @@ export class CacheService implements OnApplicationShutdown {
@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,
- @Inject(DI.channelFollowingsRepository)
- private channelFollowingsRepository: ChannelFollowingsRepository,
-
private userEntityService: UserEntityService,
) {
//this.onMessage = this.onMessage.bind(this);
@@ -136,21 +132,21 @@ export class CacheService implements OnApplicationShutdown {
fromRedisConverter: (value) => new Set(JSON.parse(value)),
});
- this.userFollowingsCache = new RedisKVCache>(this.redisClient, 'userFollowings', {
+ this.userFollowingsCache = new RedisKVCache | undefined>>(this.redisClient, 'userFollowings', {
lifetime: 1000 * 60 * 30, // 30m
memoryCacheLifetime: 1000 * 60, // 1m
- fetcher: (key) => this.followingsRepository.find({ where: { followerId: key }, select: ['followeeId'] }).then(xs => new Set(xs.map(x => x.followeeId))),
- toRedisConverter: (value) => JSON.stringify(Array.from(value)),
- fromRedisConverter: (value) => new Set(JSON.parse(value)),
+ fetcher: (key) => this.followingsRepository.find({ where: { followerId: key }, select: ['followeeId', 'withReplies'] }).then(xs => {
+ const obj: Record | undefined> = {};
+ for (const x of xs) {
+ obj[x.followeeId] = { withReplies: x.withReplies };
+ }
+ return obj;
+ }),
+ toRedisConverter: (value) => JSON.stringify(value),
+ fromRedisConverter: (value) => JSON.parse(value),
});
- this.userFollowingChannelsCache = new RedisKVCache>(this.redisClient, 'userFollowingChannels', {
- lifetime: 1000 * 60 * 30, // 30m
- memoryCacheLifetime: 1000 * 60, // 1m
- fetcher: (key) => this.channelFollowingsRepository.find({ where: { followerId: key }, select: ['followeeId'] }).then(xs => new Set(xs.map(x => x.followeeId))),
- toRedisConverter: (value) => JSON.stringify(Array.from(value)),
- fromRedisConverter: (value) => new Set(JSON.parse(value)),
- });
+ // NOTE: チャンネルのフォロー状況キャッシュはChannelFollowingServiceで行っている
this.redisForSub.on('message', this.onMessage);
}
@@ -160,7 +156,7 @@ export class CacheService implements OnApplicationShutdown {
const obj = JSON.parse(data);
if (obj.channel === 'internal') {
- const { type, body } = obj.message as StreamMessages['internal']['payload'];
+ const { type, body } = obj.message as GlobalEvents['internal']['payload'];
switch (type) {
case 'userChangeSuspendedState':
case 'remoteUserUpdated': {
@@ -188,6 +184,7 @@ export class CacheService implements OnApplicationShutdown {
if (follower) follower.followingCount++;
const followee = this.userByIdCache.get(body.followeeId);
if (followee) followee.followersCount++;
+ this.userFollowingsCache.delete(body.followerId);
break;
}
default:
@@ -214,7 +211,6 @@ export class CacheService implements OnApplicationShutdown {
this.userBlockedCache.dispose();
this.renoteMutingsCache.dispose();
this.userFollowingsCache.dispose();
- this.userFollowingChannelsCache.dispose();
}
@bindThis
diff --git a/packages/backend/src/core/ChannelFollowingService.ts b/packages/backend/src/core/ChannelFollowingService.ts
new file mode 100644
index 0000000000..75843b9773
--- /dev/null
+++ b/packages/backend/src/core/ChannelFollowingService.ts
@@ -0,0 +1,104 @@
+import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
+import Redis from 'ioredis';
+import { DI } from '@/di-symbols.js';
+import type { ChannelFollowingsRepository } from '@/models/_.js';
+import { MiChannel } from '@/models/_.js';
+import { IdService } from '@/core/IdService.js';
+import { GlobalEvents, GlobalEventService } from '@/core/GlobalEventService.js';
+import { bindThis } from '@/decorators.js';
+import type { MiLocalUser } from '@/models/User.js';
+import { RedisKVCache } from '@/misc/cache.js';
+
+@Injectable()
+export class ChannelFollowingService implements OnModuleInit {
+ public userFollowingChannelsCache: RedisKVCache>;
+
+ constructor(
+ @Inject(DI.redis)
+ private redisClient: Redis.Redis,
+ @Inject(DI.redisForSub)
+ private redisForSub: Redis.Redis,
+ @Inject(DI.channelFollowingsRepository)
+ private channelFollowingsRepository: ChannelFollowingsRepository,
+ private idService: IdService,
+ private globalEventService: GlobalEventService,
+ ) {
+ this.userFollowingChannelsCache = new RedisKVCache>(this.redisClient, 'userFollowingChannels', {
+ lifetime: 1000 * 60 * 30, // 30m
+ memoryCacheLifetime: 1000 * 60, // 1m
+ fetcher: (key) => this.channelFollowingsRepository.find({
+ where: { followerId: key },
+ select: ['followeeId'],
+ }).then(xs => new Set(xs.map(x => x.followeeId))),
+ toRedisConverter: (value) => JSON.stringify(Array.from(value)),
+ fromRedisConverter: (value) => new Set(JSON.parse(value)),
+ });
+
+ this.redisForSub.on('message', this.onMessage);
+ }
+
+ onModuleInit() {
+ }
+
+ @bindThis
+ public async follow(
+ requestUser: MiLocalUser,
+ targetChannel: MiChannel,
+ ): Promise {
+ await this.channelFollowingsRepository.insert({
+ id: this.idService.gen(),
+ followerId: requestUser.id,
+ followeeId: targetChannel.id,
+ });
+
+ this.globalEventService.publishInternalEvent('followChannel', {
+ userId: requestUser.id,
+ channelId: targetChannel.id,
+ });
+ }
+
+ @bindThis
+ public async unfollow(
+ requestUser: MiLocalUser,
+ targetChannel: MiChannel,
+ ): Promise {
+ await this.channelFollowingsRepository.delete({
+ followerId: requestUser.id,
+ followeeId: targetChannel.id,
+ });
+
+ this.globalEventService.publishInternalEvent('unfollowChannel', {
+ userId: requestUser.id,
+ channelId: targetChannel.id,
+ });
+ }
+
+ @bindThis
+ private async onMessage(_: string, data: string): Promise {
+ const obj = JSON.parse(data);
+
+ if (obj.channel === 'internal') {
+ const { type, body } = obj.message as GlobalEvents['internal']['payload'];
+ switch (type) {
+ case 'followChannel': {
+ this.userFollowingChannelsCache.refresh(body.userId);
+ break;
+ }
+ case 'unfollowChannel': {
+ this.userFollowingChannelsCache.delete(body.userId);
+ break;
+ }
+ }
+ }
+ }
+
+ @bindThis
+ public dispose(): void {
+ this.userFollowingChannelsCache.dispose();
+ }
+
+ @bindThis
+ public onApplicationShutdown(signal?: string | undefined): void {
+ this.dispose();
+ }
+}
diff --git a/packages/backend/src/core/ClipService.ts b/packages/backend/src/core/ClipService.ts
new file mode 100644
index 0000000000..e94f1eb531
--- /dev/null
+++ b/packages/backend/src/core/ClipService.ts
@@ -0,0 +1,158 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { QueryFailedError } from 'typeorm';
+import { DI } from '@/di-symbols.js';
+import type { ClipsRepository, MiNote, MiClip, ClipNotesRepository, NotesRepository } from '@/models/_.js';
+import { bindThis } from '@/decorators.js';
+import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
+import { RoleService } from '@/core/RoleService.js';
+import { IdService } from '@/core/IdService.js';
+import type { MiLocalUser } from '@/models/User.js';
+
+@Injectable()
+export class ClipService {
+ public static NoSuchNoteError = class extends Error {};
+ public static NoSuchClipError = class extends Error {};
+ public static AlreadyAddedError = class extends Error {};
+ public static TooManyClipNotesError = class extends Error {};
+ public static TooManyClipsError = class extends Error {};
+
+ constructor(
+ @Inject(DI.clipsRepository)
+ private clipsRepository: ClipsRepository,
+
+ @Inject(DI.clipNotesRepository)
+ private clipNotesRepository: ClipNotesRepository,
+
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
+
+ private roleService: RoleService,
+ private idService: IdService,
+ ) {
+ }
+
+ @bindThis
+ public async create(me: MiLocalUser, name: string, isPublic: boolean, description: string | null): Promise {
+ const currentCount = await this.clipsRepository.countBy({
+ userId: me.id,
+ });
+ if (currentCount > (await this.roleService.getUserPolicies(me.id)).clipLimit) {
+ throw new ClipService.TooManyClipsError();
+ }
+
+ const clip = await this.clipsRepository.insert({
+ id: this.idService.gen(),
+ userId: me.id,
+ name: name,
+ isPublic: isPublic,
+ description: description,
+ }).then(x => this.clipsRepository.findOneByOrFail(x.identifiers[0]));
+
+ return clip;
+ }
+
+ @bindThis
+ public async update(me: MiLocalUser, clipId: MiClip['id'], name: string | undefined, isPublic: boolean | undefined, description: string | null | undefined): Promise {
+ const clip = await this.clipsRepository.findOneBy({
+ id: clipId,
+ userId: me.id,
+ });
+
+ if (clip == null) {
+ throw new ClipService.NoSuchClipError();
+ }
+
+ await this.clipsRepository.update(clip.id, {
+ name: name,
+ description: description,
+ isPublic: isPublic,
+ });
+ }
+
+ @bindThis
+ public async delete(me: MiLocalUser, clipId: MiClip['id']): Promise {
+ const clip = await this.clipsRepository.findOneBy({
+ id: clipId,
+ userId: me.id,
+ });
+
+ if (clip == null) {
+ throw new ClipService.NoSuchClipError();
+ }
+
+ await this.clipsRepository.delete(clip.id);
+ }
+
+ @bindThis
+ public async addNote(me: MiLocalUser, clipId: MiClip['id'], noteId: MiNote['id']): Promise {
+ const clip = await this.clipsRepository.findOneBy({
+ id: clipId,
+ userId: me.id,
+ });
+
+ if (clip == null) {
+ throw new ClipService.NoSuchClipError();
+ }
+
+ const currentCount = await this.clipNotesRepository.countBy({
+ clipId: clip.id,
+ });
+ if (currentCount > (await this.roleService.getUserPolicies(me.id)).noteEachClipsLimit) {
+ throw new ClipService.TooManyClipNotesError();
+ }
+
+ try {
+ await this.clipNotesRepository.insert({
+ id: this.idService.gen(),
+ noteId: noteId,
+ clipId: clip.id,
+ });
+ } catch (e: unknown) {
+ if (e instanceof QueryFailedError) {
+ if (isDuplicateKeyValueError(e)) {
+ throw new ClipService.AlreadyAddedError();
+ } else if (e.driverError.detail.includes('is not present in table "note".')) {
+ throw new ClipService.NoSuchNoteError();
+ }
+ }
+
+ throw e;
+ }
+
+ this.clipsRepository.update(clip.id, {
+ lastClippedAt: new Date(),
+ });
+
+ this.notesRepository.increment({ id: noteId }, 'clippedCount', 1);
+ }
+
+ @bindThis
+ public async removeNote(me: MiLocalUser, clipId: MiClip['id'], noteId: MiNote['id']): Promise {
+ const clip = await this.clipsRepository.findOneBy({
+ id: clipId,
+ userId: me.id,
+ });
+
+ if (clip == null) {
+ throw new ClipService.NoSuchClipError();
+ }
+
+ const note = await this.notesRepository.findOneBy({ id: noteId });
+
+ if (note == null) {
+ throw new ClipService.NoSuchNoteError();
+ }
+
+ await this.clipNotesRepository.delete({
+ noteId: noteId,
+ clipId: clip.id,
+ });
+
+ this.notesRepository.decrement({ id: noteId }, 'clippedCount', 1);
+ }
+}
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index f816428b1e..ef224abccf 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -11,6 +11,7 @@ import { AnnouncementService } from './AnnouncementService.js';
import { AntennaService } from './AntennaService.js';
import { AppLockService } from './AppLockService.js';
import { AchievementService } from './AchievementService.js';
+import { AvatarDecorationService } from './AvatarDecorationService.js';
import { CaptchaService } from './CaptchaService.js';
import { CreateSystemUserService } from './CreateSystemUserService.js';
import { CustomEmojiService } from './CustomEmojiService.js';
@@ -43,22 +44,27 @@ import { RelayService } from './RelayService.js';
import { RoleService } from './RoleService.js';
import { S3Service } from './S3Service.js';
import { SignupService } from './SignupService.js';
+import { WebAuthnService } from './WebAuthnService.js';
import { UserBlockingService } from './UserBlockingService.js';
import { CacheService } from './CacheService.js';
+import { UserService } from './UserService.js';
import { UserFollowingService } from './UserFollowingService.js';
import { UserKeypairService } from './UserKeypairService.js';
import { UserListService } from './UserListService.js';
import { UserMutingService } from './UserMutingService.js';
import { UserSuspendService } from './UserSuspendService.js';
+import { UserAuthService } from './UserAuthService.js';
import { VideoProcessingService } from './VideoProcessingService.js';
-import { WebAuthnService } from './WebAuthnService.js';
import { WebhookService } from './WebhookService.js';
import { ProxyAccountService } from './ProxyAccountService.js';
import { UtilityService } from './UtilityService.js';
import { FileInfoService } from './FileInfoService.js';
import { SearchService } from './SearchService.js';
+import { ClipService } from './ClipService.js';
import { FeaturedService } from './FeaturedService.js';
-import { RedisTimelineService } from './RedisTimelineService.js';
+import { FunoutTimelineService } from './FunoutTimelineService.js';
+import { ChannelFollowingService } from './ChannelFollowingService.js';
+import { RegistryApiService } from './RegistryApiService.js';
import { ChartLoggerService } from './chart/ChartLoggerService.js';
import FederationChart from './chart/charts/federation.js';
import NotesChart from './chart/charts/notes.js';
@@ -138,6 +144,7 @@ const $AnnouncementService: Provider = { provide: 'AnnouncementService', useExis
const $AntennaService: Provider = { provide: 'AntennaService', useExisting: AntennaService };
const $AppLockService: Provider = { provide: 'AppLockService', useExisting: AppLockService };
const $AchievementService: Provider = { provide: 'AchievementService', useExisting: AchievementService };
+const $AvatarDecorationService: Provider = { provide: 'AvatarDecorationService', useExisting: AvatarDecorationService };
const $CaptchaService: Provider = { provide: 'CaptchaService', useExisting: CaptchaService };
const $CreateSystemUserService: Provider = { provide: 'CreateSystemUserService', useExisting: CreateSystemUserService };
const $CustomEmojiService: Provider = { provide: 'CustomEmojiService', useExisting: CustomEmojiService };
@@ -171,21 +178,26 @@ const $RelayService: Provider = { provide: 'RelayService', useExisting: RelaySer
const $RoleService: Provider = { provide: 'RoleService', useExisting: RoleService };
const $S3Service: Provider = { provide: 'S3Service', useExisting: S3Service };
const $SignupService: Provider = { provide: 'SignupService', useExisting: SignupService };
+const $WebAuthnService: Provider = { provide: 'WebAuthnService', useExisting: WebAuthnService };
const $UserBlockingService: Provider = { provide: 'UserBlockingService', useExisting: UserBlockingService };
const $CacheService: Provider = { provide: 'CacheService', useExisting: CacheService };
+const $UserService: Provider = { provide: 'UserService', useExisting: UserService };
const $UserFollowingService: Provider = { provide: 'UserFollowingService', useExisting: UserFollowingService };
const $UserKeypairService: Provider = { provide: 'UserKeypairService', useExisting: UserKeypairService };
const $UserListService: Provider = { provide: 'UserListService', useExisting: UserListService };
const $UserMutingService: Provider = { provide: 'UserMutingService', useExisting: UserMutingService };
const $UserSuspendService: Provider = { provide: 'UserSuspendService', useExisting: UserSuspendService };
+const $UserAuthService: Provider = { provide: 'UserAuthService', useExisting: UserAuthService };
const $VideoProcessingService: Provider = { provide: 'VideoProcessingService', useExisting: VideoProcessingService };
-const $WebAuthnService: Provider = { provide: 'WebAuthnService', useExisting: WebAuthnService };
const $WebhookService: Provider = { provide: 'WebhookService', useExisting: WebhookService };
const $UtilityService: Provider = { provide: 'UtilityService', useExisting: UtilityService };
const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: FileInfoService };
const $SearchService: Provider = { provide: 'SearchService', useExisting: SearchService };
+const $ClipService: Provider = { provide: 'ClipService', useExisting: ClipService };
const $FeaturedService: Provider = { provide: 'FeaturedService', useExisting: FeaturedService };
-const $RedisTimelineService: Provider = { provide: 'RedisTimelineService', useExisting: RedisTimelineService };
+const $FunoutTimelineService: Provider = { provide: 'FunoutTimelineService', useExisting: FunoutTimelineService };
+const $ChannelFollowingService: Provider = { provide: 'ChannelFollowingService', useExisting: ChannelFollowingService };
+const $RegistryApiService: Provider = { provide: 'RegistryApiService', useExisting: RegistryApiService };
const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService };
const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart };
@@ -269,6 +281,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
AntennaService,
AppLockService,
AchievementService,
+ AvatarDecorationService,
CaptchaService,
CreateSystemUserService,
CustomEmojiService,
@@ -302,21 +315,26 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
RoleService,
S3Service,
SignupService,
+ WebAuthnService,
UserBlockingService,
CacheService,
+ UserService,
UserFollowingService,
UserKeypairService,
UserListService,
UserMutingService,
UserSuspendService,
+ UserAuthService,
VideoProcessingService,
- WebAuthnService,
WebhookService,
UtilityService,
FileInfoService,
SearchService,
+ ClipService,
FeaturedService,
- RedisTimelineService,
+ FunoutTimelineService,
+ ChannelFollowingService,
+ RegistryApiService,
ChartLoggerService,
FederationChart,
NotesChart,
@@ -393,6 +411,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$AntennaService,
$AppLockService,
$AchievementService,
+ $AvatarDecorationService,
$CaptchaService,
$CreateSystemUserService,
$CustomEmojiService,
@@ -426,21 +445,26 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$RoleService,
$S3Service,
$SignupService,
+ $WebAuthnService,
$UserBlockingService,
$CacheService,
+ $UserService,
$UserFollowingService,
$UserKeypairService,
$UserListService,
$UserMutingService,
$UserSuspendService,
+ $UserAuthService,
$VideoProcessingService,
- $WebAuthnService,
$WebhookService,
$UtilityService,
$FileInfoService,
$SearchService,
+ $ClipService,
$FeaturedService,
- $RedisTimelineService,
+ $FunoutTimelineService,
+ $ChannelFollowingService,
+ $RegistryApiService,
$ChartLoggerService,
$FederationChart,
$NotesChart,
@@ -518,6 +542,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
AntennaService,
AppLockService,
AchievementService,
+ AvatarDecorationService,
CaptchaService,
CreateSystemUserService,
CustomEmojiService,
@@ -551,21 +576,26 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
RoleService,
S3Service,
SignupService,
+ WebAuthnService,
UserBlockingService,
CacheService,
+ UserService,
UserFollowingService,
UserKeypairService,
UserListService,
UserMutingService,
UserSuspendService,
+ UserAuthService,
VideoProcessingService,
- WebAuthnService,
WebhookService,
UtilityService,
FileInfoService,
SearchService,
+ ClipService,
FeaturedService,
- RedisTimelineService,
+ FunoutTimelineService,
+ ChannelFollowingService,
+ RegistryApiService,
FederationChart,
NotesChart,
UsersChart,
@@ -641,6 +671,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$AntennaService,
$AppLockService,
$AchievementService,
+ $AvatarDecorationService,
$CaptchaService,
$CreateSystemUserService,
$CustomEmojiService,
@@ -674,21 +705,26 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$RoleService,
$S3Service,
$SignupService,
+ $WebAuthnService,
$UserBlockingService,
$CacheService,
+ $UserService,
$UserFollowingService,
$UserKeypairService,
$UserListService,
$UserMutingService,
$UserSuspendService,
+ $UserAuthService,
$VideoProcessingService,
- $WebAuthnService,
$WebhookService,
$UtilityService,
$FileInfoService,
$SearchService,
+ $ClipService,
$FeaturedService,
- $RedisTimelineService,
+ $FunoutTimelineService,
+ $ChannelFollowingService,
+ $RegistryApiService,
$FederationChart,
$NotesChart,
$UsersChart,
diff --git a/packages/backend/src/core/CreateSystemUserService.ts b/packages/backend/src/core/CreateSystemUserService.ts
index 71c53735c1..5296b4f766 100644
--- a/packages/backend/src/core/CreateSystemUserService.ts
+++ b/packages/backend/src/core/CreateSystemUserService.ts
@@ -8,11 +8,11 @@ import { Inject, Injectable } from '@nestjs/common';
import bcrypt from 'bcryptjs';
import { IsNull, DataSource } from 'typeorm';
import { genRsaKeyPair } from '@/misc/gen-key-pair.js';
-import { MiUser } from '@/models/entities/User.js';
-import { MiUserProfile } from '@/models/entities/UserProfile.js';
+import { MiUser } from '@/models/User.js';
+import { MiUserProfile } from '@/models/UserProfile.js';
import { IdService } from '@/core/IdService.js';
-import { MiUserKeypair } from '@/models/entities/UserKeypair.js';
-import { MiUsedUsername } from '@/models/entities/UsedUsername.js';
+import { MiUserKeypair } from '@/models/UserKeypair.js';
+import { MiUsedUsername } from '@/models/UsedUsername.js';
import { DI } from '@/di-symbols.js';
import generateNativeUserToken from '@/misc/generate-native-user-token.js';
import { bindThis } from '@/decorators.js';
@@ -52,8 +52,7 @@ export class CreateSystemUserService {
if (exist) throw new Error('the user is already exists');
account = await transactionalEntityManager.insert(MiUser, {
- id: this.idService.genId(),
- createdAt: new Date(),
+ id: this.idService.gen(),
username: username,
usernameLower: username.toLowerCase(),
host: null,
diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts
index b3b08d7fac..69edbed79f 100644
--- a/packages/backend/src/core/CustomEmojiService.ts
+++ b/packages/backend/src/core/CustomEmojiService.ts
@@ -4,21 +4,21 @@
*/
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
-import { DataSource, In, IsNull } from 'typeorm';
+import { In, IsNull } from 'typeorm';
import * as Redis from 'ioredis';
import { DI } from '@/di-symbols.js';
import { IdService } from '@/core/IdService.js';
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
-import type { MiDriveFile } from '@/models/entities/DriveFile.js';
-import type { MiEmoji } from '@/models/entities/Emoji.js';
-import type { EmojisRepository, MiRole } from '@/models/index.js';
+import type { MiDriveFile } from '@/models/DriveFile.js';
+import type { MiEmoji } from '@/models/Emoji.js';
+import type { EmojisRepository, MiRole, MiUser } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
import { UtilityService } from '@/core/UtilityService.js';
-import type { Config } from '@/config.js';
import { query } from '@/misc/prelude/url.js';
-import type { Serialized } from '@/server/api/stream/types.js';
+import type { Serialized } from '@/types.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/;
@@ -31,18 +31,13 @@ export class CustomEmojiService implements OnApplicationShutdown {
@Inject(DI.redis)
private redisClient: Redis.Redis,
- @Inject(DI.config)
- private config: Config,
-
- @Inject(DI.db)
- private db: DataSource,
-
@Inject(DI.emojisRepository)
private emojisRepository: EmojisRepository,
private utilityService: UtilityService,
private idService: IdService,
private emojiEntityService: EmojiEntityService,
+ private moderationLogService: ModerationLogService,
private globalEventService: GlobalEventService,
) {
this.cache = new MemoryKVCache(1000 * 60 * 60 * 12);
@@ -53,7 +48,6 @@ export class CustomEmojiService implements OnApplicationShutdown {
fetcher: () => this.emojisRepository.find({ where: { host: IsNull() } }).then(emojis => new Map(emojis.map(emoji => [emoji.name, emoji]))),
toRedisConverter: (value) => JSON.stringify(Array.from(value.values())),
fromRedisConverter: (value) => {
- if (!Array.isArray(JSON.parse(value))) return undefined; // 古いバージョンの壊れたキャッシュが残っていることがある(そのうち消す)
return new Map(JSON.parse(value).map((x: Serialized) => [x.name, {
...x,
updatedAt: x.updatedAt ? new Date(x.updatedAt) : null,
@@ -74,9 +68,9 @@ export class CustomEmojiService implements OnApplicationShutdown {
localOnly: boolean;
roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][];
roleIdsThatCanNotBeUsedThisEmojiAsReaction: MiRole['id'][];
- }): Promise {
+ }, moderator?: MiUser): Promise {
const emoji = await this.emojisRepository.insert({
- id: this.idService.genId(),
+ id: this.idService.gen(),
updatedAt: new Date(),
name: data.name,
category: data.category,
@@ -98,6 +92,13 @@ export class CustomEmojiService implements OnApplicationShutdown {
this.globalEventService.publishBroadcastStream('emojiAdded', {
emoji: await this.emojiEntityService.packDetailed(emoji.id),
});
+
+ if (moderator) {
+ this.moderationLogService.log(moderator, 'addCustomEmoji', {
+ emojiId: emoji.id,
+ emoji: emoji,
+ });
+ }
}
return emoji;
@@ -114,7 +115,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
localOnly?: boolean;
roleIdsThatCanBeUsedThisEmojiAsReaction?: MiRole['id'][];
roleIdsThatCanNotBeUsedThisEmojiAsReaction?: MiRole['id'][];
- }): Promise {
+ }, moderator?: MiUser): Promise {
const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() });
if (sameNameEmoji != null && sameNameEmoji.id !== id) throw new Error('name already exists');
@@ -136,11 +137,11 @@ export class CustomEmojiService implements OnApplicationShutdown {
this.localEmojisCache.refresh();
- const updated = await this.emojiEntityService.packDetailed(emoji.id);
+ const packed = await this.emojiEntityService.packDetailed(emoji.id);
if (emoji.name === data.name) {
this.globalEventService.publishBroadcastStream('emojiUpdated', {
- emojis: [updated],
+ emojis: [packed],
});
} else {
this.globalEventService.publishBroadcastStream('emojiDeleted', {
@@ -148,7 +149,16 @@ export class CustomEmojiService implements OnApplicationShutdown {
});
this.globalEventService.publishBroadcastStream('emojiAdded', {
- emoji: updated,
+ emoji: packed,
+ });
+ }
+
+ if (moderator) {
+ const updated = await this.emojisRepository.findOneByOrFail({ id: id });
+ this.moderationLogService.log(moderator, 'updateCustomEmoji', {
+ emojiId: emoji.id,
+ before: emoji,
+ after: updated,
});
}
}
@@ -242,7 +252,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
}
@bindThis
- public async delete(id: MiEmoji['id']) {
+ public async delete(id: MiEmoji['id'], moderator?: MiUser) {
const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
await this.emojisRepository.delete(emoji.id);
@@ -252,16 +262,30 @@ export class CustomEmojiService implements OnApplicationShutdown {
this.globalEventService.publishBroadcastStream('emojiDeleted', {
emojis: [await this.emojiEntityService.packDetailed(emoji)],
});
+
+ if (moderator) {
+ this.moderationLogService.log(moderator, 'deleteCustomEmoji', {
+ emojiId: emoji.id,
+ emoji: emoji,
+ });
+ }
}
@bindThis
- public async deleteBulk(ids: MiEmoji['id'][]) {
+ public async deleteBulk(ids: MiEmoji['id'][], moderator?: MiUser) {
const emojis = await this.emojisRepository.findBy({
id: In(ids),
});
for (const emoji of emojis) {
await this.emojisRepository.delete(emoji.id);
+
+ if (moderator) {
+ this.moderationLogService.log(moderator, 'deleteCustomEmoji', {
+ emojiId: emoji.id,
+ emoji: emoji,
+ });
+ }
}
this.localEmojisCache.refresh();
@@ -311,7 +335,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
const queryOrNull = async () => (await this.emojisRepository.findOneBy({
name,
- host: host ?? IsNull(),
+ host,
})) ?? null;
const emoji = await this.cache.fetch(`${name} ${host}`, queryOrNull);
@@ -359,6 +383,20 @@ export class CustomEmojiService implements OnApplicationShutdown {
}
}
+ /**
+ * ローカル内の絵文字に重複がないかチェックします
+ * @param name 絵文字名
+ */
+ @bindThis
+ public checkDuplicate(name: string): Promise {
+ return this.emojisRepository.exist({ where: { name, host: IsNull() } });
+ }
+
+ @bindThis
+ public getEmojiById(id: string): Promise {
+ return this.emojisRepository.findOneBy({ id });
+ }
+
@bindThis
public dispose(): void {
this.cache.dispose();
diff --git a/packages/backend/src/core/DeleteAccountService.ts b/packages/backend/src/core/DeleteAccountService.ts
index 9ba260109f..570bd440e4 100644
--- a/packages/backend/src/core/DeleteAccountService.ts
+++ b/packages/backend/src/core/DeleteAccountService.ts
@@ -4,10 +4,9 @@
*/
import { Inject, Injectable } from '@nestjs/common';
-import type { UsersRepository } from '@/models/index.js';
+import type { UsersRepository } from '@/models/_.js';
import { QueueService } from '@/core/QueueService.js';
import { UserSuspendService } from '@/core/UserSuspendService.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
@@ -19,7 +18,6 @@ export class DeleteAccountService {
private userSuspendService: UserSuspendService,
private queueService: QueueService,
- private globalEventService: GlobalEventService,
) {
}
diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts
index fbf45fc950..ee026971ed 100644
--- a/packages/backend/src/core/DriveService.ts
+++ b/packages/backend/src/core/DriveService.ts
@@ -11,12 +11,12 @@ import { sharpBmp } from 'sharp-read-bmp';
import { IsNull } from 'typeorm';
import { DeleteObjectCommandInput, PutObjectCommandInput, NoSuchKey } from '@aws-sdk/client-s3';
import { DI } from '@/di-symbols.js';
-import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository } from '@/models/index.js';
+import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository } from '@/models/_.js';
import type { Config } from '@/config.js';
import Logger from '@/logger.js';
-import type { MiRemoteUser, MiUser } from '@/models/entities/User.js';
+import type { MiRemoteUser, MiUser } from '@/models/User.js';
import { MetaService } from '@/core/MetaService.js';
-import { MiDriveFile } from '@/models/entities/DriveFile.js';
+import { MiDriveFile } from '@/models/DriveFile.js';
import { IdService } from '@/core/IdService.js';
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
import { FILE_TYPE_BROWSERSAFE } from '@/const.js';
@@ -27,7 +27,7 @@ import { VideoProcessingService } from '@/core/VideoProcessingService.js';
import { ImageProcessingService } from '@/core/ImageProcessingService.js';
import type { IImage } from '@/core/ImageProcessingService.js';
import { QueueService } from '@/core/QueueService.js';
-import type { MiDriveFolder } from '@/models/entities/DriveFolder.js';
+import type { MiDriveFolder } from '@/models/DriveFolder.js';
import { createTemp } from '@/misc/create-temp.js';
import DriveChart from '@/core/chart/charts/drive.js';
import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js';
@@ -42,6 +42,7 @@ import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import { correctFilename } from '@/misc/correct-filename.js';
import { isMimeImage } from '@/misc/is-mime-image.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
type AddFileArgs = {
/** User who wish to add file */
@@ -86,6 +87,9 @@ type UploadFromUrlArgs = {
@Injectable()
export class DriveService {
+ public static NoSuchFolderError = class extends Error {};
+ public static InvalidFileNameError = class extends Error {};
+ public static CannotUnmarkSensitiveError = class extends Error {};
private registerLogger: Logger;
private downloaderLogger: Logger;
private deleteLogger: Logger;
@@ -119,6 +123,7 @@ export class DriveService {
private globalEventService: GlobalEventService,
private queueService: QueueService,
private roleService: RoleService,
+ private moderationLogService: ModerationLogService,
private driveChart: DriveChart,
private perUserDriveChart: PerUserDriveChart,
private instanceChart: InstanceChart,
@@ -332,7 +337,7 @@ export class DriveService {
this.registerLogger.debug('web image not created (not an required image)');
}
} catch (err) {
- this.registerLogger.warn('web image not created (an error occured)', err as Error);
+ this.registerLogger.warn('web image not created (an error occurred)', err as Error);
}
} else {
if (satisfyWebpublic) this.registerLogger.info('web image not created (original satisfies webpublic)');
@@ -351,7 +356,7 @@ export class DriveService {
thumbnail = await this.imageProcessingService.convertSharpToWebp(img, 498, 422);
}
} catch (err) {
- this.registerLogger.warn('thumbnail not created (an error occured)', err as Error);
+ this.registerLogger.warn('thumbnail not created (an error occurred)', err as Error);
}
// #endregion thumbnail
@@ -559,8 +564,7 @@ export class DriveService {
const folder = await fetchFolder();
let file = new MiDriveFile();
- file.id = this.idService.genId();
- file.createdAt = new Date();
+ file.id = this.idService.gen();
file.userId = user ? user.id : null;
file.userHost = user ? user.host : null;
file.folderId = folder !== null ? folder.id : null;
@@ -574,9 +578,7 @@ export class DriveService {
file.maybePorn = info.porn;
file.isSensitive = user
? this.userEntityService.isLocalUser(user) && profile!.alwaysMarkNsfw ? true :
- (sensitive !== null && sensitive !== undefined)
- ? sensitive
- : false
+ sensitive ?? false
: false;
if (info.sensitive && profile!.autoSensitive) file.isSensitive = true;
@@ -650,7 +652,63 @@ export class DriveService {
}
@bindThis
- public async deleteFile(file: MiDriveFile, isExpired = false) {
+ public async updateFile(file: MiDriveFile, values: Partial, updater: MiUser) {
+ const alwaysMarkNsfw = (await this.roleService.getUserPolicies(file.userId)).alwaysMarkNsfw;
+
+ if (values.name && !this.driveFileEntityService.validateFileName(file.name)) {
+ throw new DriveService.InvalidFileNameError();
+ }
+
+ if (values.isSensitive !== undefined && values.isSensitive !== file.isSensitive && alwaysMarkNsfw && !values.isSensitive) {
+ throw new DriveService.CannotUnmarkSensitiveError();
+ }
+
+ if (values.folderId != null) {
+ const folder = await this.driveFoldersRepository.findOneBy({
+ id: values.folderId,
+ userId: file.userId!,
+ });
+
+ if (folder == null) {
+ throw new DriveService.NoSuchFolderError();
+ }
+ }
+
+ await this.driveFilesRepository.update(file.id, values);
+
+ const fileObj = await this.driveFileEntityService.pack(file.id, updater, { self: true });
+
+ // Publish fileUpdated event
+ if (file.userId) {
+ this.globalEventService.publishDriveStream(file.userId, 'fileUpdated', fileObj);
+ }
+
+ if (await this.roleService.isModerator(updater) && (file.userId !== updater.id)) {
+ if (values.isSensitive !== undefined && values.isSensitive !== file.isSensitive) {
+ const user = file.userId ? await this.usersRepository.findOneByOrFail({ id: file.userId }) : null;
+ if (values.isSensitive) {
+ this.moderationLogService.log(updater, 'markSensitiveDriveFile', {
+ fileId: file.id,
+ fileUserId: file.userId,
+ fileUserUsername: user?.username ?? null,
+ fileUserHost: user?.host ?? null,
+ });
+ } else {
+ this.moderationLogService.log(updater, 'unmarkSensitiveDriveFile', {
+ fileId: file.id,
+ fileUserId: file.userId,
+ fileUserUsername: user?.username ?? null,
+ fileUserHost: user?.host ?? null,
+ });
+ }
+ }
+ }
+
+ return fileObj;
+ }
+
+ @bindThis
+ public async deleteFile(file: MiDriveFile, isExpired = false, deleter?: MiUser) {
if (file.storedInternal) {
this.internalStorageService.del(file.accessKey!);
@@ -673,11 +731,11 @@ export class DriveService {
}
}
- this.deletePostProcess(file, isExpired);
+ this.deletePostProcess(file, isExpired, deleter);
}
@bindThis
- public async deleteFileSync(file: MiDriveFile, isExpired = false) {
+ public async deleteFileSync(file: MiDriveFile, isExpired = false, deleter?: MiUser) {
if (file.storedInternal) {
this.internalStorageService.del(file.accessKey!);
@@ -704,11 +762,11 @@ export class DriveService {
await Promise.all(promises);
}
- this.deletePostProcess(file, isExpired);
+ this.deletePostProcess(file, isExpired, deleter);
}
@bindThis
- private async deletePostProcess(file: MiDriveFile, isExpired = false) {
+ private async deletePostProcess(file: MiDriveFile, isExpired = false, deleter?: MiUser) {
// リモートファイル期限切れ削除後は直リンクにする
if (isExpired && file.userHost !== null && file.uri != null) {
this.driveFilesRepository.update(file.id, {
@@ -735,6 +793,20 @@ export class DriveService {
this.instanceChart.updateDrive(file, false);
}
}
+
+ if (file.userId) {
+ this.globalEventService.publishDriveStream(file.userId, 'fileDeleted', file.id);
+ }
+
+ if (deleter && await this.roleService.isModerator(deleter) && (file.userId !== deleter.id)) {
+ const user = file.userId ? await this.usersRepository.findOneByOrFail({ id: file.userId }) : null;
+ this.moderationLogService.log(deleter, 'deleteDriveFile', {
+ fileId: file.id,
+ fileUserId: file.userId,
+ fileUserUsername: user?.username ?? null,
+ fileUserHost: user?.host ?? null,
+ });
+ }
}
@bindThis
diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts
index 162634593e..c9da3f77c0 100644
--- a/packages/backend/src/core/EmailService.ts
+++ b/packages/backend/src/core/EmailService.ts
@@ -10,7 +10,7 @@ import { MetaService } from '@/core/MetaService.js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import type Logger from '@/logger.js';
-import type { UserProfilesRepository } from '@/models/index.js';
+import type { UserProfilesRepository } from '@/models/_.js';
import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js';
diff --git a/packages/backend/src/core/FeaturedService.ts b/packages/backend/src/core/FeaturedService.ts
index 0772dc689f..078d2f5228 100644
--- a/packages/backend/src/core/FeaturedService.ts
+++ b/packages/backend/src/core/FeaturedService.ts
@@ -5,7 +5,7 @@
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
-import type { MiNote, MiUser } from '@/models/index.js';
+import type { MiNote, MiUser } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
@@ -52,7 +52,7 @@ export class FeaturedService {
`${name}:${currentWindow}`, 0, threshold, 'WITHSCORES');
redisPipeline.zrevrange(
`${name}:${previousWindow}`, 0, threshold, 'WITHSCORES');
- const [currentRankingResult, previousRankingResult] = await redisPipeline.exec().then(result => result ? result.map(r => r[1] as string[]) : [[], []]);
+ const [currentRankingResult, previousRankingResult] = await redisPipeline.exec().then(result => result ? result.map(r => (r[1] ?? []) as string[]) : [[], []]);
const ranking = new Map();
for (let i = 0; i < currentRankingResult.length; i += 2) {
diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts
index e7b85b750b..e41f010e48 100644
--- a/packages/backend/src/core/FederatedInstanceService.ts
+++ b/packages/backend/src/core/FederatedInstanceService.ts
@@ -5,8 +5,8 @@
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import * as Redis from 'ioredis';
-import type { InstancesRepository } from '@/models/index.js';
-import type { MiInstance } from '@/models/entities/Instance.js';
+import type { InstancesRepository } from '@/models/_.js';
+import type { MiInstance } from '@/models/Instance.js';
import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
import { IdService } from '@/core/IdService.js';
import { DI } from '@/di-symbols.js';
@@ -56,7 +56,7 @@ export class FederatedInstanceService implements OnApplicationShutdown {
if (index == null) {
const i = await this.instancesRepository.insert({
- id: this.idService.genId(),
+ id: this.idService.gen(),
host,
firstRetrievedAt: new Date(),
}).then(x => this.instancesRepository.findOneByOrFail(x.identifiers[0]));
diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts
index 3f55a12f6e..7abf8370d3 100644
--- a/packages/backend/src/core/FetchInstanceMetadataService.ts
+++ b/packages/backend/src/core/FetchInstanceMetadataService.ts
@@ -8,7 +8,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { JSDOM } from 'jsdom';
import tinycolor from 'tinycolor2';
import * as Redis from 'ioredis';
-import type { MiInstance } from '@/models/entities/Instance.js';
+import type { MiInstance } from '@/models/Instance.js';
import type Logger from '@/logger.js';
import { DI } from '@/di-symbols.js';
import { LoggerService } from '@/core/LoggerService.js';
@@ -108,7 +108,7 @@ export class FetchInstanceMetadataService {
if (name) updates.name = name;
if (description) updates.description = description;
- if (icon || favicon) updates.iconUrl = (icon && !icon.includes('data:image/png;base64')) ? icon : favicon;
+ if (icon ?? favicon) updates.iconUrl = (icon && !icon.includes('data:image/png;base64')) ? icon : favicon;
if (favicon) updates.faviconUrl = favicon;
if (themeColor) updates.themeColor = themeColor;
@@ -142,10 +142,10 @@ export class FetchInstanceMetadataService {
const links = wellknown.links as any[];
- const lnik1_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/1.0');
- const lnik2_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.0');
- const lnik2_1 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.1');
- const link = lnik2_1 ?? lnik2_0 ?? lnik1_0;
+ const link1_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/1.0');
+ const link2_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.0');
+ const link2_1 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.1');
+ const link = link2_1 ?? link2_0 ?? link1_0;
if (link == null) {
throw new Error('No nodeinfo link provided');
diff --git a/packages/backend/src/core/RedisTimelineService.ts b/packages/backend/src/core/FunoutTimelineService.ts
similarity index 95%
rename from packages/backend/src/core/RedisTimelineService.ts
rename to packages/backend/src/core/FunoutTimelineService.ts
index 94541759cc..c633c329e5 100644
--- a/packages/backend/src/core/RedisTimelineService.ts
+++ b/packages/backend/src/core/FunoutTimelineService.ts
@@ -10,7 +10,7 @@ import { bindThis } from '@/decorators.js';
import { IdService } from '@/core/IdService.js';
@Injectable()
-export class RedisTimelineService {
+export class FunoutTimelineService {
constructor(
@Inject(DI.redisForTimelines)
private redisForTimelines: Redis.Redis,
@@ -77,4 +77,9 @@ export class RedisTimelineService {
);
});
}
+
+ @bindThis
+ public purge(name: string) {
+ return this.redisForTimelines.del('list:' + name);
+ }
}
diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts
index 224eb3c599..d175f21f2f 100644
--- a/packages/backend/src/core/GlobalEventService.ts
+++ b/packages/backend/src/core/GlobalEventService.ts
@@ -5,27 +5,263 @@
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiNote } from '@/models/entities/Note.js';
-import type { MiUserList } from '@/models/entities/UserList.js';
-import type { MiAntenna } from '@/models/entities/Antenna.js';
-import type {
- StreamChannels,
- AdminStreamTypes,
- AntennaStreamTypes,
- BroadcastTypes,
- DriveStreamTypes,
- InternalStreamTypes,
- MainStreamTypes,
- NoteStreamTypes,
- UserListStreamTypes,
- RoleTimelineStreamTypes,
-} from '@/server/api/stream/types.js';
+import type { MiChannel } from '@/models/Channel.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiUserProfile } from '@/models/UserProfile.js';
+import type { MiNote } from '@/models/Note.js';
+import type { MiAntenna } from '@/models/Antenna.js';
+import type { MiDriveFile } from '@/models/DriveFile.js';
+import type { MiDriveFolder } from '@/models/DriveFolder.js';
+import type { MiUserList } from '@/models/UserList.js';
+import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
+import type { MiSignin } from '@/models/Signin.js';
+import type { MiPage } from '@/models/Page.js';
+import type { MiWebhook } from '@/models/Webhook.js';
+import type { MiMeta } from '@/models/Meta.js';
+import { MiAvatarDecoration, MiRole, MiRoleAssignment } from '@/models/_.js';
import type { Packed } from '@/misc/json-schema.js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { bindThis } from '@/decorators.js';
-import { MiRole } from '@/models/index.js';
+import { Serialized } from '@/types.js';
+import type Emitter from 'strict-event-emitter-types';
+import type { EventEmitter } from 'events';
+
+//#region Stream type-body definitions
+export interface BroadcastTypes {
+ emojiAdded: {
+ emoji: Packed<'EmojiDetailed'>;
+ };
+ emojiUpdated: {
+ emojis: Packed<'EmojiDetailed'>[];
+ };
+ emojiDeleted: {
+ emojis: {
+ id?: string;
+ name: string;
+ [other: string]: any;
+ }[];
+ };
+ announcementCreated: {
+ announcement: Packed<'Announcement'>;
+ };
+}
+
+export interface MainEventTypes {
+ notification: Packed<'Notification'>;
+ mention: Packed<'Note'>;
+ reply: Packed<'Note'>;
+ renote: Packed<'Note'>;
+ follow: Packed<'UserDetailedNotMe'>;
+ followed: Packed<'User'>;
+ unfollow: Packed<'User'>;
+ meUpdated: Packed<'User'>;
+ pageEvent: {
+ pageId: MiPage['id'];
+ event: string;
+ var: any;
+ userId: MiUser['id'];
+ user: Packed<'User'>;
+ };
+ urlUploadFinished: {
+ marker?: string | null;
+ file: Packed<'DriveFile'>;
+ };
+ readAllNotifications: undefined;
+ unreadNotification: Packed<'Notification'>;
+ unreadMention: MiNote['id'];
+ readAllUnreadMentions: undefined;
+ unreadSpecifiedNote: MiNote['id'];
+ readAllUnreadSpecifiedNotes: undefined;
+ readAllAntennas: undefined;
+ unreadAntenna: MiAntenna;
+ readAllAnnouncements: undefined;
+ myTokenRegenerated: undefined;
+ signin: {
+ id: MiSignin['id'];
+ createdAt: string;
+ ip: string;
+ headers: Record;
+ success: boolean;
+ };
+ registryUpdated: {
+ scope?: string[];
+ key: string;
+ value: any | null;
+ };
+ driveFileCreated: Packed<'DriveFile'>;
+ readAntenna: MiAntenna;
+ receiveFollowRequest: Packed<'User'>;
+ announcementCreated: {
+ announcement: Packed<'Announcement'>;
+ };
+}
+
+export interface DriveEventTypes {
+ fileCreated: Packed<'DriveFile'>;
+ fileDeleted: MiDriveFile['id'];
+ fileUpdated: Packed<'DriveFile'>;
+ folderCreated: Packed<'DriveFolder'>;
+ folderDeleted: MiDriveFolder['id'];
+ folderUpdated: Packed<'DriveFolder'>;
+}
+
+export interface NoteEventTypes {
+ pollVoted: {
+ choice: number;
+ userId: MiUser['id'];
+ };
+ deleted: {
+ deletedAt: Date;
+ };
+ updated: {
+ cw: string | null;
+ text: string;
+ };
+ reacted: {
+ reaction: string;
+ emoji?: {
+ name: string;
+ url: string;
+ } | null;
+ userId: MiUser['id'];
+ };
+ unreacted: {
+ reaction: string;
+ userId: MiUser['id'];
+ };
+}
+type NoteStreamEventTypes = {
+ [key in keyof NoteEventTypes]: {
+ id: MiNote['id'];
+ body: NoteEventTypes[key];
+ };
+};
+
+export interface UserListEventTypes {
+ userAdded: Packed<'User'>;
+ userRemoved: Packed<'User'>;
+}
+
+export interface AntennaEventTypes {
+ note: MiNote;
+}
+
+export interface RoleTimelineEventTypes {
+ note: Packed<'Note'>;
+}
+
+export interface AdminEventTypes {
+ newAbuseUserReport: {
+ id: MiAbuseUserReport['id'];
+ targetUserId: MiUser['id'],
+ reporterId: MiUser['id'],
+ comment: string;
+ };
+}
+//#endregion
+
+// 辞書(interface or type)から{ type, body }ユニオンを定義
+// https://stackoverflow.com/questions/49311989/can-i-infer-the-type-of-a-value-using-extends-keyof-type
+// VS Codeの展開を防止するためにEvents型を定義
+type Events = { [K in keyof T]: { type: K; body: T[K]; } };
+type EventUnionFromDictionary<
+ T extends object,
+ U = Events
+> = U[keyof U];
+
+type SerializedAll = {
+ [K in keyof T]: Serialized;
+};
+
+export interface InternalEventTypes {
+ userChangeSuspendedState: { id: MiUser['id']; isSuspended: MiUser['isSuspended']; };
+ userTokenRegenerated: { id: MiUser['id']; oldToken: string; newToken: string; };
+ remoteUserUpdated: { id: MiUser['id']; };
+ follow: { followerId: MiUser['id']; followeeId: MiUser['id']; };
+ unfollow: { followerId: MiUser['id']; followeeId: MiUser['id']; };
+ blockingCreated: { blockerId: MiUser['id']; blockeeId: MiUser['id']; };
+ blockingDeleted: { blockerId: MiUser['id']; blockeeId: MiUser['id']; };
+ policiesUpdated: MiRole['policies'];
+ roleCreated: MiRole;
+ roleDeleted: MiRole;
+ roleUpdated: MiRole;
+ userRoleAssigned: MiRoleAssignment;
+ userRoleUnassigned: MiRoleAssignment;
+ webhookCreated: MiWebhook;
+ webhookDeleted: MiWebhook;
+ webhookUpdated: MiWebhook;
+ antennaCreated: MiAntenna;
+ antennaDeleted: MiAntenna;
+ antennaUpdated: MiAntenna;
+ avatarDecorationCreated: MiAvatarDecoration;
+ avatarDecorationDeleted: MiAvatarDecoration;
+ avatarDecorationUpdated: MiAvatarDecoration;
+ metaUpdated: MiMeta;
+ followChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
+ unfollowChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
+ updateUserProfile: MiUserProfile;
+ mute: { muterId: MiUser['id']; muteeId: MiUser['id']; };
+ unmute: { muterId: MiUser['id']; muteeId: MiUser['id']; };
+ userListMemberAdded: { userListId: MiUserList['id']; memberId: MiUser['id']; };
+ userListMemberRemoved: { userListId: MiUserList['id']; memberId: MiUser['id']; };
+}
+
+// name/messages(spec) pairs dictionary
+export type GlobalEvents = {
+ internal: {
+ name: 'internal';
+ payload: EventUnionFromDictionary>;
+ };
+ broadcast: {
+ name: 'broadcast';
+ payload: EventUnionFromDictionary>;
+ };
+ main: {
+ name: `mainStream:${MiUser['id']}`;
+ payload: EventUnionFromDictionary>;
+ };
+ drive: {
+ name: `driveStream:${MiUser['id']}`;
+ payload: EventUnionFromDictionary>;
+ };
+ note: {
+ name: `noteStream:${MiNote['id']}`;
+ payload: EventUnionFromDictionary>;
+ };
+ userList: {
+ name: `userListStream:${MiUserList['id']}`;
+ payload: EventUnionFromDictionary>;
+ };
+ roleTimeline: {
+ name: `roleTimelineStream:${MiRole['id']}`;
+ payload: EventUnionFromDictionary>;
+ };
+ antenna: {
+ name: `antennaStream:${MiAntenna['id']}`;
+ payload: EventUnionFromDictionary>;
+ };
+ admin: {
+ name: `adminStream:${MiUser['id']}`;
+ payload: EventUnionFromDictionary>;
+ };
+ notes: {
+ name: 'notesStream';
+ payload: Serialized>;
+ };
+};
+
+// API event definitions
+// ストリームごとのEmitterの辞書を用意
+type EventEmitterDictionary = { [x in keyof GlobalEvents]: Emitter.default void }> };
+// 共用体型を交差型にする型 https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection
+type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
+// Emitter辞書から共用体型を作り、UnionToIntersectionで交差型にする
+export type StreamEventEmitter = UnionToIntersection;
+// { [y in name]: (e: spec) => void }をまとめてその交差型をEmitterにかけるとts(2590)にひっかかる
+
+// provide stream channels union
+export type StreamChannels = GlobalEvents[keyof GlobalEvents]['name'];
@Injectable()
export class GlobalEventService {
@@ -51,7 +287,7 @@ export class GlobalEventService {
}
@bindThis
- public publishInternalEvent(type: K, value?: InternalStreamTypes[K]): void {
+ public publishInternalEvent(type: K, value?: InternalEventTypes[K]): void {
this.publish('internal', type, typeof value === 'undefined' ? null : value);
}
@@ -61,17 +297,17 @@ export class GlobalEventService {
}
@bindThis
- public publishMainStream(userId: MiUser['id'], type: K, value?: MainStreamTypes[K]): void {
+ public publishMainStream(userId: MiUser['id'], type: K, value?: MainEventTypes[K]): void {
this.publish(`mainStream:${userId}`, type, typeof value === 'undefined' ? null : value);
}
@bindThis
- public publishDriveStream(userId: MiUser['id'], type: K, value?: DriveStreamTypes[K]): void {
+ public publishDriveStream(userId: MiUser['id'], type: K, value?: DriveEventTypes[K]): void {
this.publish(`driveStream:${userId}`, type, typeof value === 'undefined' ? null : value);
}
@bindThis
- public publishNoteStream(noteId: MiNote['id'], type: K, value?: NoteStreamTypes[K]): void {
+ public publishNoteStream(noteId: MiNote['id'], type: K, value?: NoteEventTypes[K]): void {
this.publish(`noteStream:${noteId}`, type, {
id: noteId,
body: value,
@@ -79,17 +315,17 @@ export class GlobalEventService {
}
@bindThis
- public publishUserListStream(listId: MiUserList['id'], type: K, value?: UserListStreamTypes[K]): void {
+ public publishUserListStream(listId: MiUserList['id'], type: K, value?: UserListEventTypes[K]): void {
this.publish(`userListStream:${listId}`, type, typeof value === 'undefined' ? null : value);
}
@bindThis
- public publishAntennaStream(antennaId: MiAntenna['id'], type: K, value?: AntennaStreamTypes[K]): void {
+ public publishAntennaStream(antennaId: MiAntenna['id'], type: K, value?: AntennaEventTypes[K]): void {
this.publish(`antennaStream:${antennaId}`, type, typeof value === 'undefined' ? null : value);
}
@bindThis
- public publishRoleTimelineStream(roleId: MiRole['id'], type: K, value?: RoleTimelineStreamTypes[K]): void {
+ public publishRoleTimelineStream(roleId: MiRole['id'], type: K, value?: RoleTimelineEventTypes[K]): void {
this.publish(`roleTimelineStream:${roleId}`, type, typeof value === 'undefined' ? null : value);
}
@@ -99,7 +335,7 @@ export class GlobalEventService {
}
@bindThis
- public publishAdminStream(userId: MiUser['id'], type: K, value?: AdminStreamTypes[K]): void {
+ public publishAdminStream(userId: MiUser['id'], type: K, value?: AdminEventTypes[K]): void {
this.publish(`adminStream:${userId}`, type, typeof value === 'undefined' ? null : value);
}
}
diff --git a/packages/backend/src/core/HashtagService.ts b/packages/backend/src/core/HashtagService.ts
index 24130b2ab7..d378999907 100644
--- a/packages/backend/src/core/HashtagService.ts
+++ b/packages/backend/src/core/HashtagService.ts
@@ -4,26 +4,31 @@
*/
import { Inject, Injectable } from '@nestjs/common';
+import * as Redis from 'ioredis';
import { DI } from '@/di-symbols.js';
-import type { MiUser } from '@/models/entities/User.js';
+import type { MiUser } from '@/models/User.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { IdService } from '@/core/IdService.js';
-import type { MiHashtag } from '@/models/entities/Hashtag.js';
-import type { HashtagsRepository, UsersRepository } from '@/models/index.js';
+import type { MiHashtag } from '@/models/Hashtag.js';
+import type { HashtagsRepository } from '@/models/_.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
+import { FeaturedService } from '@/core/FeaturedService.js';
+import { MetaService } from '@/core/MetaService.js';
@Injectable()
export class HashtagService {
constructor(
- @Inject(DI.usersRepository)
- private usersRepository: UsersRepository,
+ @Inject(DI.redis)
+ private redisClient: Redis.Redis, // TODO: 専用のRedisサーバーを設定できるようにする
@Inject(DI.hashtagsRepository)
private hashtagsRepository: HashtagsRepository,
private userEntityService: UserEntityService,
+ private featuredService: FeaturedService,
private idService: IdService,
+ private metaService: MetaService,
) {
}
@@ -40,7 +45,7 @@ export class HashtagService {
await this.updateHashtag(user, tag, true, true);
}
- for (const tag of (user.tags ?? []).filter(x => !tags.includes(x))) {
+ for (const tag of user.tags.filter(x => !tags.includes(x))) {
await this.updateHashtag(user, tag, true, false);
}
}
@@ -49,6 +54,9 @@ export class HashtagService {
public async updateHashtag(user: { id: MiUser['id']; host: MiUser['host']; }, tag: string, isUserAttached = false, inc = true) {
tag = normalizeForSearch(tag);
+ // TODO: サンプリング
+ this.updateHashtagsRanking(tag, user.id);
+
const index = await this.hashtagsRepository.findOneBy({ name: tag });
if (index == null && !inc) return;
@@ -88,7 +96,7 @@ export class HashtagService {
}
}
} else {
- // 自分が初めてこのタグを使ったなら
+ // 自分が初めてこのタグを使ったなら
if (!index.mentionedUserIds.some(id => id === user.id)) {
set.mentionedUserIds = () => `array_append("mentionedUserIds", '${user.id}')`;
set.mentionedUsersCount = () => '"mentionedUsersCount" + 1';
@@ -112,7 +120,7 @@ export class HashtagService {
} else {
if (isUserAttached) {
this.hashtagsRepository.insert({
- id: this.idService.genId(),
+ id: this.idService.gen(),
name: tag,
mentionedUserIds: [],
mentionedUsersCount: 0,
@@ -129,7 +137,7 @@ export class HashtagService {
} as MiHashtag);
} else {
this.hashtagsRepository.insert({
- id: this.idService.genId(),
+ id: this.idService.gen(),
name: tag,
mentionedUserIds: [user.id],
mentionedUsersCount: 1,
@@ -147,4 +155,94 @@ export class HashtagService {
}
}
}
+
+ @bindThis
+ public async updateHashtagsRanking(hashtag: string, userId: MiUser['id']): Promise {
+ const instance = await this.metaService.fetch();
+ const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t));
+ if (hiddenTags.includes(hashtag)) return;
+
+ // YYYYMMDDHHmm (10分間隔)
+ const now = new Date();
+ now.setMinutes(Math.floor(now.getMinutes() / 10) * 10, 0, 0);
+ const window = `${now.getUTCFullYear()}${(now.getUTCMonth() + 1).toString().padStart(2, '0')}${now.getUTCDate().toString().padStart(2, '0')}${now.getUTCHours().toString().padStart(2, '0')}${now.getUTCMinutes().toString().padStart(2, '0')}`;
+
+ const exist = await this.redisClient.sismember(`hashtagUsers:${hashtag}`, userId);
+ if (exist === 1) return;
+
+ this.featuredService.updateHashtagsRanking(hashtag, 1);
+
+ const redisPipeline = this.redisClient.pipeline();
+
+ // チャート用
+ redisPipeline.pfadd(`hashtagUsers:${hashtag}:${window}`, userId);
+ redisPipeline.expire(`hashtagUsers:${hashtag}:${window}`,
+ 60 * 60 * 24 * 3, // 3日間
+ 'NX', // "NX -- Set expiry only when the key has no expiry" = 有効期限がないときだけ設定
+ );
+
+ // ユニークカウント用
+ // TODO: Bloom Filter を使うようにしても良さそう
+ redisPipeline.sadd(`hashtagUsers:${hashtag}`, userId);
+ redisPipeline.expire(`hashtagUsers:${hashtag}`,
+ 60 * 60, // 1時間
+ 'NX', // "NX -- Set expiry only when the key has no expiry" = 有効期限がないときだけ設定
+ );
+
+ redisPipeline.exec();
+ }
+
+ @bindThis
+ public async getChart(hashtag: string, range: number): Promise {
+ const now = new Date();
+ now.setMinutes(Math.floor(now.getMinutes() / 10) * 10, 0, 0);
+
+ const redisPipeline = this.redisClient.pipeline();
+
+ for (let i = 0; i < range; i++) {
+ const window = `${now.getUTCFullYear()}${(now.getUTCMonth() + 1).toString().padStart(2, '0')}${now.getUTCDate().toString().padStart(2, '0')}${now.getUTCHours().toString().padStart(2, '0')}${now.getUTCMinutes().toString().padStart(2, '0')}`;
+ redisPipeline.pfcount(`hashtagUsers:${hashtag}:${window}`);
+ now.setMinutes(now.getMinutes() - (i * 10), 0, 0);
+ }
+
+ const result = await redisPipeline.exec();
+
+ if (result == null) return [];
+
+ return result.map(x => x[1]) as number[];
+ }
+
+ @bindThis
+ public async getCharts(hashtags: string[], range: number): Promise> {
+ const now = new Date();
+ now.setMinutes(Math.floor(now.getMinutes() / 10) * 10, 0, 0);
+
+ const redisPipeline = this.redisClient.pipeline();
+
+ for (let i = 0; i < range; i++) {
+ const window = `${now.getUTCFullYear()}${(now.getUTCMonth() + 1).toString().padStart(2, '0')}${now.getUTCDate().toString().padStart(2, '0')}${now.getUTCHours().toString().padStart(2, '0')}${now.getUTCMinutes().toString().padStart(2, '0')}`;
+ for (const hashtag of hashtags) {
+ redisPipeline.pfcount(`hashtagUsers:${hashtag}:${window}`);
+ }
+ now.setMinutes(now.getMinutes() - (i * 10), 0, 0);
+ }
+
+ const result = await redisPipeline.exec();
+
+ if (result == null) return {};
+
+ // key is hashtag
+ const charts = {} as Record;
+ for (const hashtag of hashtags) {
+ charts[hashtag] = [];
+ }
+
+ for (let i = 0; i < range; i++) {
+ for (let j = 0; j < hashtags.length; j++) {
+ charts[hashtags[j]].push(result[(i * hashtags.length) + j][1] as number);
+ }
+ }
+
+ return charts;
+ }
}
diff --git a/packages/backend/src/core/HttpRequestService.ts b/packages/backend/src/core/HttpRequestService.ts
index 47878ed694..73bb3dc7e9 100644
--- a/packages/backend/src/core/HttpRequestService.ts
+++ b/packages/backend/src/core/HttpRequestService.ts
@@ -53,12 +53,14 @@ export class HttpRequestService {
keepAlive: true,
keepAliveMsecs: 30 * 1000,
lookup: cache.lookup as unknown as net.LookupFunction,
+ localAddress: config.outgoingAddress,
});
this.https = new https.Agent({
keepAlive: true,
keepAliveMsecs: 30 * 1000,
lookup: cache.lookup as unknown as net.LookupFunction,
+ localAddress: config.outgoingAddress,
});
const maxSockets = Math.max(256, config.deliverJobConcurrency ?? 128);
@@ -71,6 +73,7 @@ export class HttpRequestService {
maxFreeSockets: 256,
scheduling: 'lifo',
proxy: config.proxy,
+ localAddress: config.outgoingAddress,
})
: this.http;
@@ -82,6 +85,7 @@ export class HttpRequestService {
maxFreeSockets: 256,
scheduling: 'lifo',
proxy: config.proxy,
+ localAddress: config.outgoingAddress,
})
: this.https;
}
@@ -93,7 +97,7 @@ export class HttpRequestService {
*/
@bindThis
public getAgentByUrl(url: URL, bypassProxy = false): http.Agent | https.Agent {
- if (bypassProxy || (this.config.proxyBypassHosts || []).includes(url.hostname)) {
+ if (bypassProxy || (this.config.proxyBypassHosts ?? []).includes(url.hostname)) {
return url.protocol === 'http:' ? this.http : this.https;
} else {
return url.protocol === 'http:' ? this.httpAgent : this.httpsAgent;
diff --git a/packages/backend/src/core/IdService.ts b/packages/backend/src/core/IdService.ts
index 186fd36b42..c98b8ea6fc 100644
--- a/packages/backend/src/core/IdService.ts
+++ b/packages/backend/src/core/IdService.ts
@@ -8,6 +8,7 @@ import { ulid } from 'ulid';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { genAid, parseAid } from '@/misc/id/aid.js';
+import { genAidx, parseAidx } from '@/misc/id/aidx.js';
import { genMeid, parseMeid } from '@/misc/id/meid.js';
import { genMeidg, parseMeidg } from '@/misc/id/meidg.js';
import { genObjectId, parseObjectId } from '@/misc/id/object-id.js';
@@ -25,16 +26,21 @@ export class IdService {
this.method = config.id.toLowerCase();
}
+ /**
+ * 時間を元にIDを生成します(省略時は現在日時)
+ * @param time 日時
+ */
@bindThis
- public genId(date?: Date): string {
- if (!date || (date > new Date())) date = new Date();
+ public gen(time?: number): string {
+ const t = (!time || (time > Date.now())) ? Date.now() : time;
switch (this.method) {
- case 'aid': return genAid(date);
- case 'meid': return genMeid(date);
- case 'meidg': return genMeidg(date);
- case 'ulid': return ulid(date.getTime());
- case 'objectid': return genObjectId(date);
+ case 'aid': return genAid(t);
+ case 'aidx': return genAidx(t);
+ case 'meid': return genMeid(t);
+ case 'meidg': return genMeidg(t);
+ case 'ulid': return ulid(t);
+ case 'objectid': return genObjectId(t);
default: throw new Error('unrecognized id generation method');
}
}
@@ -43,6 +49,7 @@ export class IdService {
public parse(id: string): { date: Date; } {
switch (this.method) {
case 'aid': return parseAid(id);
+ case 'aidx': return parseAidx(id);
case 'objectid': return parseObjectId(id);
case 'meid': return parseMeid(id);
case 'meidg': return parseMeidg(id);
diff --git a/packages/backend/src/core/ImageProcessingService.ts b/packages/backend/src/core/ImageProcessingService.ts
index bbed29b7d0..8e800eb8f5 100644
--- a/packages/backend/src/core/ImageProcessingService.ts
+++ b/packages/backend/src/core/ImageProcessingService.ts
@@ -3,10 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Inject, Injectable } from '@nestjs/common';
+import { Injectable } from '@nestjs/common';
import sharp from 'sharp';
-import { DI } from '@/di-symbols.js';
-import type { Config } from '@/config.js';
export type IImage = {
data: Buffer;
@@ -50,8 +48,6 @@ import { Readable } from 'node:stream';
@Injectable()
export class ImageProcessingService {
constructor(
- @Inject(DI.config)
- private config: Config,
) {
}
diff --git a/packages/backend/src/core/InstanceActorService.ts b/packages/backend/src/core/InstanceActorService.ts
index 8e576774d4..b40fd46291 100644
--- a/packages/backend/src/core/InstanceActorService.ts
+++ b/packages/backend/src/core/InstanceActorService.ts
@@ -5,8 +5,8 @@
import { Inject, Injectable } from '@nestjs/common';
import { IsNull } from 'typeorm';
-import type { MiLocalUser } from '@/models/entities/User.js';
-import type { UsersRepository } from '@/models/index.js';
+import type { MiLocalUser } from '@/models/User.js';
+import type { UsersRepository } from '@/models/_.js';
import { MemorySingleCache } from '@/misc/cache.js';
import { DI } from '@/di-symbols.js';
import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
diff --git a/packages/backend/src/core/LoggerService.ts b/packages/backend/src/core/LoggerService.ts
index 6d3ed10961..46b000ee63 100644
--- a/packages/backend/src/core/LoggerService.ts
+++ b/packages/backend/src/core/LoggerService.ts
@@ -3,9 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Inject, Injectable } from '@nestjs/common';
-import { DI } from '@/di-symbols.js';
-import type { Config } from '@/config.js';
+import { Injectable } from '@nestjs/common';
import Logger from '@/logger.js';
import { bindThis } from '@/decorators.js';
import type { KEYWORD } from 'color-convert/conversions.js';
@@ -13,8 +11,6 @@ import type { KEYWORD } from 'color-convert/conversions.js';
@Injectable()
export class LoggerService {
constructor(
- @Inject(DI.config)
- private config: Config,
) {
}
diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts
index f9619861bb..508544dc07 100644
--- a/packages/backend/src/core/MetaService.ts
+++ b/packages/backend/src/core/MetaService.ts
@@ -7,16 +7,16 @@ import { Inject, Injectable } from '@nestjs/common';
import { DataSource } from 'typeorm';
import * as Redis from 'ioredis';
import { DI } from '@/di-symbols.js';
-import { MiMeta } from '@/models/entities/Meta.js';
+import { MiMeta } from '@/models/Meta.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { bindThis } from '@/decorators.js';
-import { StreamMessages } from '@/server/api/stream/types.js';
+import type { GlobalEvents } from '@/core/GlobalEventService.js';
import type { OnApplicationShutdown } from '@nestjs/common';
@Injectable()
export class MetaService implements OnApplicationShutdown {
private cache: MiMeta | undefined;
- private intervalId: NodeJS.Timer;
+ private intervalId: NodeJS.Timeout;
constructor(
@Inject(DI.redisForSub)
@@ -46,7 +46,7 @@ export class MetaService implements OnApplicationShutdown {
const obj = JSON.parse(data);
if (obj.channel === 'internal') {
- const { type, body } = obj.message as StreamMessages['internal']['payload'];
+ const { type, body } = obj.message as GlobalEvents['internal']['payload'];
switch (type) {
case 'metaUpdated': {
this.cache = body;
diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts
index b6f84da475..af602168d4 100644
--- a/packages/backend/src/core/MfmService.ts
+++ b/packages/backend/src/core/MfmService.ts
@@ -10,7 +10,7 @@ import { Window } from 'happy-dom';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { intersperse } from '@/misc/prelude/array.js';
-import type { IMentionedRemoteUsers } from '@/models/entities/Note.js';
+import type { IMentionedRemoteUsers } from '@/models/Note.js';
import { bindThis } from '@/decorators.js';
import * as TreeAdapter from '../../node_modules/parse5/dist/tree-adapters/default.js';
import type * as mfm from 'mfm-js';
@@ -276,9 +276,18 @@ export class MfmService {
},
fn: (node) => {
- const el = doc.createElement('i');
- appendChildren(node.children, el);
- return el;
+ if (node.props.name === 'unixtime') {
+ const text = node.children[0]!.type === 'text' ? node.children[0].props.text : '';
+ const date = new Date(parseInt(text, 10) * 1000);
+ const el = doc.createElement('time');
+ el.setAttribute('datetime', date.toISOString());
+ el.textContent = date.toISOString();
+ return el;
+ } else {
+ const el = doc.createElement('i');
+ appendChildren(node.children, el);
+ return el;
+ }
},
blockCode: (node) => {
diff --git a/packages/backend/src/core/ModerationLogService.ts b/packages/backend/src/core/ModerationLogService.ts
index be01fbf9fa..8b78d02047 100644
--- a/packages/backend/src/core/ModerationLogService.ts
+++ b/packages/backend/src/core/ModerationLogService.ts
@@ -5,10 +5,11 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { ModerationLogsRepository } from '@/models/index.js';
-import type { MiUser } from '@/models/entities/User.js';
+import type { ModerationLogsRepository } from '@/models/_.js';
+import type { MiUser } from '@/models/User.js';
import { IdService } from '@/core/IdService.js';
import { bindThis } from '@/decorators.js';
+import { ModerationLogPayloads, moderationLogTypes } from '@/types.js';
@Injectable()
export class ModerationLogService {
@@ -21,13 +22,12 @@ export class ModerationLogService {
}
@bindThis
- public async insertModerationLog(moderator: { id: MiUser['id'] }, type: string, info?: Record) {
+ public async log(moderator: { id: MiUser['id'] }, type: T, info?: ModerationLogPayloads[T]) {
await this.moderationLogsRepository.insert({
- id: this.idService.genId(),
- createdAt: new Date(),
+ id: this.idService.gen(),
userId: moderator.id,
type: type,
- info: info ?? {},
+ info: (info as any) ?? {},
});
}
}
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index a12b964e11..b6f7ee745b 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -12,22 +12,22 @@ import RE2 from 're2';
import { extractMentions } from '@/misc/extract-mentions.js';
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
import { extractHashtags } from '@/misc/extract-hashtags.js';
-import type { IMentionedRemoteUsers } from '@/models/entities/Note.js';
-import { MiNote } from '@/models/entities/Note.js';
-import type { ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MutedNotesRepository, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListJoiningsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
-import type { MiDriveFile } from '@/models/entities/DriveFile.js';
-import type { MiApp } from '@/models/entities/App.js';
+import type { IMentionedRemoteUsers } from '@/models/Note.js';
+import { MiNote } from '@/models/Note.js';
+import type { ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import type { MiDriveFile } from '@/models/DriveFile.js';
+import type { MiApp } from '@/models/App.js';
import { concat } from '@/misc/prelude/array.js';
import { IdService } from '@/core/IdService.js';
-import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/entities/User.js';
-import type { IPoll } from '@/models/entities/Poll.js';
-import { MiPoll } from '@/models/entities/Poll.js';
+import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js';
+import type { IPoll } from '@/models/Poll.js';
+import { MiPoll } from '@/models/Poll.js';
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
import { checkWordMute } from '@/misc/check-word-mute.js';
-import type { MiChannel } from '@/models/entities/Channel.js';
+import type { MiChannel } from '@/models/Channel.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { MemorySingleCache } from '@/misc/cache.js';
-import type { MiUserProfile } from '@/models/entities/UserProfile.js';
+import type { MiUserProfile } from '@/models/UserProfile.js';
import { RelayService } from '@/core/RelayService.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { DI } from '@/di-symbols.js';
@@ -54,14 +54,14 @@ import { RoleService } from '@/core/RoleService.js';
import { MetaService } from '@/core/MetaService.js';
import { SearchService } from '@/core/SearchService.js';
import { FeaturedService } from '@/core/FeaturedService.js';
-import { RedisTimelineService } from '@/core/RedisTimelineService.js';
-
-const mutedWordsCache = new MemorySingleCache<{ userId: MiUserProfile['userId']; mutedWords: MiUserProfile['mutedWords']; }[]>(1000 * 60 * 5);
+import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
+import { UtilityService } from '@/core/UtilityService.js';
+import { UserBlockingService } from '@/core/UserBlockingService.js';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
class NotificationManager {
- private notifier: { id: MiUser['id'] };
+ private notifier: { id: MiUser['id']; };
private note: MiNote;
private queue: {
target: MiLocalUser['id'];
@@ -71,7 +71,7 @@ class NotificationManager {
constructor(
private mutingsRepository: MutingsRepository,
private notificationService: NotificationService,
- notifier: { id: MiUser['id'] },
+ notifier: { id: MiUser['id']; },
note: MiNote,
) {
this.notifier = notifier;
@@ -100,21 +100,17 @@ class NotificationManager {
}
@bindThis
- public async deliver() {
+ public async notify() {
for (const x of this.queue) {
- // ミュート情報を取得
- const mentioneeMutes = await this.mutingsRepository.findBy({
- muterId: x.target,
- });
-
- const mentioneesMutedUserIds = mentioneeMutes.map(m => m.muteeId);
-
- // 通知される側のユーザーが通知する側のユーザーをミュートしていない限りは通知する
- if (!mentioneesMutedUserIds.includes(this.notifier.id)) {
- this.notificationService.createNotification(x.target, x.reason, {
- notifierId: this.notifier.id,
+ if (x.reason === 'renote') {
+ this.notificationService.createNotification(x.target, 'renote', {
noteId: this.note.id,
- });
+ targetNoteId: this.note.renoteId!,
+ }, this.notifier.id);
+ } else {
+ this.notificationService.createNotification(x.target, x.reason, {
+ noteId: this.note.id,
+ }, this.notifier.id);
}
}
}
@@ -160,9 +156,6 @@ export class NoteCreateService implements OnApplicationShutdown {
@Inject(DI.db)
private db: DataSource,
- @Inject(DI.redis)
- private redisClient: Redis.Redis,
-
@Inject(DI.redisForTimelines)
private redisForTimelines: Redis.Redis,
@@ -181,11 +174,8 @@ export class NoteCreateService implements OnApplicationShutdown {
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
- @Inject(DI.mutedNotesRepository)
- private mutedNotesRepository: MutedNotesRepository,
-
- @Inject(DI.userListJoiningsRepository)
- private userListJoiningsRepository: UserListJoiningsRepository,
+ @Inject(DI.userListMembershipsRepository)
+ private userListMembershipsRepository: UserListMembershipsRepository,
@Inject(DI.channelsRepository)
private channelsRepository: ChannelsRepository,
@@ -204,7 +194,7 @@ export class NoteCreateService implements OnApplicationShutdown {
private idService: IdService,
private globalEventService: GlobalEventService,
private queueService: QueueService,
- private redisTimelineService: RedisTimelineService,
+ private funoutTimelineService: FunoutTimelineService,
private noteReadService: NoteReadService,
private notificationService: NotificationService,
private relayService: RelayService,
@@ -223,6 +213,8 @@ export class NoteCreateService implements OnApplicationShutdown {
private perUserNotesChart: PerUserNotesChart,
private activeUsersChart: ActiveUsersChart,
private instanceChart: InstanceChart,
+ private utilityService: UtilityService,
+ private userBlockingService: UserBlockingService,
) { }
@bindThis
@@ -230,8 +222,8 @@ export class NoteCreateService implements OnApplicationShutdown {
id: MiUser['id'];
username: MiUser['username'];
host: MiUser['host'];
- createdAt: MiUser['createdAt'];
isBot: MiUser['isBot'];
+ isCat: MiUser['isCat'];
}, data: Option, silent = false): Promise {
// チャンネル外にリプライしたら対象のスコープに合わせる
// (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで)
@@ -256,8 +248,10 @@ export class NoteCreateService implements OnApplicationShutdown {
if (data.channel != null) data.visibleUsers = [];
if (data.channel != null) data.localOnly = true;
+ const meta = await this.metaService.fetch();
+
if (data.visibility === 'public' && data.channel == null) {
- const sensitiveWords = (await this.metaService.fetch()).sensitiveWords;
+ const sensitiveWords = meta.sensitiveWords;
if (this.isSensitive(data, sensitiveWords)) {
data.visibility = 'home';
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
@@ -265,6 +259,12 @@ export class NoteCreateService implements OnApplicationShutdown {
}
}
+ const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host);
+
+ if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
+ data.visibility = 'home';
+ }
+
if (data.renote) {
switch (data.renote.visibility) {
case 'public':
@@ -291,6 +291,18 @@ export class NoteCreateService implements OnApplicationShutdown {
}
}
+ // Check blocking
+ if (data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length === 0)) {
+ if (data.renote.userHost === null) {
+ if (data.renote.userId !== user.id) {
+ const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id);
+ if (blocked) {
+ throw new Error('blocked');
+ }
+ }
+ }
+ }
+
// 返信対象がpublicではないならhomeにする
if (data.reply && data.reply.visibility !== 'public' && data.visibility === 'public') {
data.visibility = 'home';
@@ -321,7 +333,7 @@ export class NoteCreateService implements OnApplicationShutdown {
// Parse MFM if needed
if (!tags || !emojis || !mentionedUsers) {
- const tokens = data.text ? mfm.parse(data.text)! : [];
+ const tokens = (data.text ? mfm.parse(data.text)! : []);
const cwTokens = data.cw ? mfm.parse(data.cw)! : [];
const choiceTokens = data.poll && data.poll.choices
? concat(data.poll.choices.map(choice => mfm.parse(choice)!))
@@ -336,7 +348,7 @@ export class NoteCreateService implements OnApplicationShutdown {
mentionedUsers = data.apMentions ?? await this.extractMentionedUsers(user, combinedTokens);
}
- tags = tags.filter(tag => Array.from(tag ?? '').length <= 128).splice(0, 32);
+ tags = tags.filter(tag => Array.from(tag).length <= 128).splice(0, 32);
if (data.reply && (user.id !== data.reply.userId) && !mentionedUsers.some(u => u.id === data.reply!.userId)) {
mentionedUsers.push(await this.usersRepository.findOneByOrFail({ id: data.reply!.userId }));
@@ -358,14 +370,6 @@ export class NoteCreateService implements OnApplicationShutdown {
const note = await this.insertNote(user, data, tags, emojis, mentionedUsers);
- if (data.channel) {
- this.redisClient.xadd(
- `channelTimeline:${data.channel.id}`,
- 'MAXLEN', '~', '1000',
- '*',
- 'note', note.id);
- }
-
setImmediate('post created', { signal: this.#shutdownController.signal }).then(
() => this.postNoteCreated(note, user, data, silent, tags!, mentionedUsers!),
() => { /* aborted, ignore this */ },
@@ -377,7 +381,7 @@ export class NoteCreateService implements OnApplicationShutdown {
@bindThis
private async insertNote(user: { id: MiUser['id']; host: MiUser['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: MinimumUser[]) {
const insert = new MiNote({
- id: this.idService.genId(data.createdAt!),
+ id: this.idService.gen(data.createdAt?.getTime()),
createdAt: data.createdAt!,
fileIds: data.files ? data.files.map(file => file.id) : [],
replyId: data.reply ? data.reply.id : null,
@@ -477,7 +481,6 @@ export class NoteCreateService implements OnApplicationShutdown {
id: MiUser['id'];
username: MiUser['username'];
host: MiUser['host'];
- createdAt: MiUser['createdAt'];
isBot: MiUser['isBot'];
}, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) {
const meta = await this.metaService.fetch();
@@ -505,27 +508,6 @@ export class NoteCreateService implements OnApplicationShutdown {
// Increment notes count (user)
this.incNotesCountOfUser(user);
- // Word mute
- mutedWordsCache.fetch(() => this.userProfilesRepository.find({
- where: {
- enableWordMute: true,
- },
- select: ['userId', 'mutedWords'],
- })).then(us => {
- for (const u of us) {
- checkWordMute(note, { id: u.userId }, u.mutedWords).then(shouldMute => {
- if (shouldMute) {
- this.mutedNotesRepository.insert({
- id: this.idService.genId(),
- userId: u.userId,
- noteId: note.id,
- reason: 'word',
- });
- }
- });
- }
- });
-
this.pushToTl(note, user);
this.antennaService.addNoteToAntennas(note, user);
@@ -534,9 +516,25 @@ export class NoteCreateService implements OnApplicationShutdown {
this.saveReply(data.reply, note);
}
- // この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき
- if (data.renote && (await this.noteEntityService.countSameRenotes(user.id, data.renote.id, note.id) === 0)) {
- if (!user.isBot) this.incRenoteCount(data.renote);
+ if (data.reply == null) {
+ // TODO: キャッシュ
+ this.followingsRepository.findBy({
+ followeeId: user.id,
+ notify: 'normal',
+ }).then(followings => {
+ if (note.visibility !== 'specified') {
+ for (const following of followings) {
+ // TODO: ワードミュート考慮
+ this.notificationService.createNotification(following.followerId, 'note', {
+ noteId: note.id,
+ }, user.id);
+ }
+ }
+ });
+ }
+
+ if (data.renote && data.renote.userId !== user.id && !user.isBot) {
+ this.incRenoteCount(data.renote);
}
if (data.poll && data.poll.expiresAt) {
@@ -578,7 +576,7 @@ export class NoteCreateService implements OnApplicationShutdown {
}
// Pack the note
- const noteObj = await this.noteEntityService.pack(note, null);
+ const noteObj = await this.noteEntityService.pack(note, null, { skipHide: true, withReactionAndUserPairCache: true });
this.globalEventService.publishNotesStream(noteObj);
@@ -644,7 +642,7 @@ export class NoteCreateService implements OnApplicationShutdown {
}
}
- nm.deliver();
+ nm.notify();
//#region AP deliver
if (this.userEntityService.isLocalUser(user)) {
@@ -736,7 +734,6 @@ export class NoteCreateService implements OnApplicationShutdown {
this.notesRepository.createQueryBuilder().update()
.set({
renoteCount: () => '"renoteCount" + 1',
- score: () => '"score" + 1',
})
.where('id = :id', { id: renote.id })
.execute();
@@ -812,7 +809,7 @@ export class NoteCreateService implements OnApplicationShutdown {
}
@bindThis
- private incNotesCountOfUser(user: { id: MiUser['id'] }) {
+ private incNotesCountOfUser(user: { id: MiUser['id']; }) {
this.usersRepository.createQueryBuilder().update()
.set({
updatedAt: new Date(),
@@ -842,13 +839,14 @@ export class NoteCreateService implements OnApplicationShutdown {
@bindThis
private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) {
const meta = await this.metaService.fetch();
+ if (!meta.enableFanoutTimeline) return;
const r = this.redisForTimelines.pipeline();
if (note.channelId) {
- this.redisTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r);
+ this.funoutTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r);
- this.redisTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
+ this.funoutTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
const channelFollowings = await this.channelFollowingsRepository.find({
where: {
@@ -858,9 +856,9 @@ export class NoteCreateService implements OnApplicationShutdown {
});
for (const channelFollowing of channelFollowings) {
- this.redisTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r);
+ this.funoutTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r);
if (note.fileIds.length > 0) {
- this.redisTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
+ this.funoutTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
}
}
} else {
@@ -871,20 +869,21 @@ export class NoteCreateService implements OnApplicationShutdown {
where: {
followeeId: user.id,
followerHost: IsNull(),
+ isFollowerHibernated: false,
},
- select: ['followerId'],
+ select: ['followerId', 'withReplies'],
}),
- this.userListJoiningsRepository.find({
+ this.userListMembershipsRepository.find({
where: {
userId: user.id,
},
- select: ['userListId', 'userListUserId'],
+ select: ['userListId', 'userListUserId', 'withReplies'],
}),
]);
if (note.visibility === 'followers') {
// TODO: 重そうだから何とかしたい Set 使う?
- userListMemberships = (userListMemberships).filter(x => followings.some(f => f.followerId === x.userListUserId));
+ userListMemberships = userListMemberships.filter(x => x.userListUserId === user.id || followings.some(f => f.followerId === x.userListUserId));
}
// TODO: あまりにも数が多いと redisPipeline.exec に失敗する(理由は不明)ため、3万件程度を目安に分割して実行するようにする
@@ -894,12 +893,12 @@ export class NoteCreateService implements OnApplicationShutdown {
// 「自分自身への返信 or そのフォロワーへの返信」のどちらでもない場合
if (note.replyId && !(note.replyUserId === note.userId || note.replyUserId === following.followerId)) {
- continue;
+ if (!following.withReplies) continue;
}
- this.redisTimelineService.push(`homeTimeline:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r);
+ this.funoutTimelineService.push(`homeTimeline:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r);
if (note.fileIds.length > 0) {
- this.redisTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
+ this.funoutTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
}
}
@@ -912,47 +911,91 @@ export class NoteCreateService implements OnApplicationShutdown {
// 「自分自身への返信 or そのリストの作成者への返信」のどちらでもない場合
if (note.replyId && !(note.replyUserId === note.userId || note.replyUserId === userListMembership.userListUserId)) {
- continue;
+ if (!userListMembership.withReplies) continue;
}
- this.redisTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r);
+ this.funoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r);
if (note.fileIds.length > 0) {
- this.redisTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r);
+ this.funoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r);
}
}
if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) { // 自分自身のHTL
- this.redisTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r);
+ this.funoutTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r);
if (note.fileIds.length > 0) {
- this.redisTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
+ this.funoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
}
}
// 自分自身以外への返信
if (note.replyId && note.replyUserId !== note.userId) {
- this.redisTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
+ this.funoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
if (note.visibility === 'public' && note.userHost == null) {
- this.redisTimelineService.push('localTimelineWithReplies', note.id, 300, r);
+ this.funoutTimelineService.push('localTimelineWithReplies', note.id, 300, r);
}
} else {
- this.redisTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
+ this.funoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
if (note.fileIds.length > 0) {
- this.redisTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax / 2 : meta.perRemoteUserUserTimelineCacheMax / 2, r);
+ this.funoutTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax / 2 : meta.perRemoteUserUserTimelineCacheMax / 2, r);
}
if (note.visibility === 'public' && note.userHost == null) {
- this.redisTimelineService.push('localTimeline', note.id, 1000, r);
+ this.funoutTimelineService.push('localTimeline', note.id, 1000, r);
if (note.fileIds.length > 0) {
- this.redisTimelineService.push('localTimelineWithFiles', note.id, 500, r);
+ this.funoutTimelineService.push('localTimelineWithFiles', note.id, 500, r);
}
}
}
+
+ if (Math.random() < 0.1) {
+ process.nextTick(() => {
+ this.checkHibernation(followings);
+ });
+ }
}
r.exec();
}
+ @bindThis
+ public async checkHibernation(followings: MiFollowing[]) {
+ if (followings.length === 0) return;
+
+ const shuffle = (array: MiFollowing[]) => {
+ for (let i = array.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1));
+ [array[i], array[j]] = [array[j], array[i]];
+ }
+ return array;
+ };
+
+ // ランダムに最大1000件サンプリング
+ const samples = shuffle(followings).slice(0, Math.min(followings.length, 1000));
+
+ const hibernatedUsers = await this.usersRepository.find({
+ where: {
+ id: In(samples.map(x => x.followerId)),
+ lastActiveDate: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 50))),
+ },
+ select: ['id'],
+ });
+
+ if (hibernatedUsers.length > 0) {
+ this.usersRepository.update({
+ id: In(hibernatedUsers.map(x => x.id)),
+ }, {
+ isHibernated: true,
+ });
+
+ this.followingsRepository.update({
+ followerId: In(hibernatedUsers.map(x => x.id)),
+ }, {
+ isFollowerHibernated: true,
+ });
+ }
+ }
+
@bindThis
public dispose(): void {
this.#shutdownController.abort();
diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts
index 07708bd028..632daf991a 100644
--- a/packages/backend/src/core/NoteDeleteService.ts
+++ b/packages/backend/src/core/NoteDeleteService.ts
@@ -5,9 +5,9 @@
import { Brackets, In } from 'typeorm';
import { Injectable, Inject } from '@nestjs/common';
-import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/entities/User.js';
-import type { MiNote, IMentionedRemoteUsers } from '@/models/entities/Note.js';
-import type { InstancesRepository, NotesRepository, UsersRepository } from '@/models/index.js';
+import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js';
+import type { MiNote, IMentionedRemoteUsers } from '@/models/Note.js';
+import type { InstancesRepository, NotesRepository, UsersRepository } from '@/models/_.js';
import { RelayService } from '@/core/RelayService.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { DI } from '@/di-symbols.js';
@@ -23,6 +23,8 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import { MetaService } from '@/core/MetaService.js';
import { SearchService } from '@/core/SearchService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { isPureRenote } from '@/misc/is-pure-renote.js';
@Injectable()
export class NoteDeleteService {
@@ -48,6 +50,7 @@ export class NoteDeleteService {
private apDeliverManagerService: ApDeliverManagerService,
private metaService: MetaService,
private searchService: SearchService,
+ private moderationLogService: ModerationLogService,
private notesChart: NotesChart,
private perUserNotesChart: PerUserNotesChart,
private instanceChart: InstanceChart,
@@ -58,16 +61,10 @@ export class NoteDeleteService {
* @param user 投稿者
* @param note 投稿
*/
- async delete(user: { id: MiUser['id']; uri: MiUser['uri']; host: MiUser['host']; isBot: MiUser['isBot']; }, note: MiNote, quiet = false) {
+ async delete(user: { id: MiUser['id']; uri: MiUser['uri']; host: MiUser['host']; isBot: MiUser['isBot']; }, note: MiNote, quiet = false, deleter?: MiUser) {
const deletedAt = new Date();
const cascadingNotes = await this.findCascadingNotes(note);
- // この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき
- if (note.renoteId && (await this.noteEntityService.countSameRenotes(user.id, note.renoteId, note.id)) === 0) {
- this.notesRepository.decrement({ id: note.renoteId }, 'renoteCount', 1);
- if (!user.isBot) this.notesRepository.decrement({ id: note.renoteId }, 'score', 1);
- }
-
if (note.replyId) {
await this.notesRepository.decrement({ id: note.replyId }, 'repliesCount', 1);
}
@@ -81,8 +78,8 @@ export class NoteDeleteService {
if (this.userEntityService.isLocalUser(user) && !note.localOnly) {
let renote: MiNote | null = null;
- // if deletd note is renote
- if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) {
+ // if deleted note is renote
+ if (isPureRenote(note)) {
renote = await this.notesRepository.findOneBy({
id: note.renoteId,
});
@@ -131,6 +128,17 @@ export class NoteDeleteService {
id: note.id,
userId: user.id,
});
+
+ if (deleter && (note.userId !== deleter.id)) {
+ const user = await this.usersRepository.findOneByOrFail({ id: note.userId });
+ this.moderationLogService.log(deleter, 'deleteNote', {
+ noteId: note.id,
+ noteUserId: note.userId,
+ noteUserUsername: user.username,
+ noteUserHost: user.host,
+ note: note,
+ });
+ }
}
@bindThis
diff --git a/packages/backend/src/core/NotePiningService.ts b/packages/backend/src/core/NotePiningService.ts
index 7f1381a41a..52abb4c2a1 100644
--- a/packages/backend/src/core/NotePiningService.ts
+++ b/packages/backend/src/core/NotePiningService.ts
@@ -5,12 +5,12 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { NotesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/index.js';
+import type { NotesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/_.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiNote } from '@/models/entities/Note.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiNote } from '@/models/Note.js';
import { IdService } from '@/core/IdService.js';
-import type { MiUserNotePining } from '@/models/entities/UserNotePining.js';
+import type { MiUserNotePining } from '@/models/UserNotePining.js';
import { RelayService } from '@/core/RelayService.js';
import type { Config } from '@/config.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
@@ -71,8 +71,7 @@ export class NotePiningService {
}
await this.userNotePiningsRepository.insert({
- id: this.idService.genId(),
- createdAt: new Date(),
+ id: this.idService.gen(),
userId: user.id,
noteId: note.id,
} as MiUserNotePining);
diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts
index 0197486263..03c1735e04 100644
--- a/packages/backend/src/core/NoteReadService.ts
+++ b/packages/backend/src/core/NoteReadService.ts
@@ -7,12 +7,12 @@ import { setTimeout } from 'node:timers/promises';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
-import type { MiUser } from '@/models/entities/User.js';
+import type { MiUser } from '@/models/User.js';
import type { Packed } from '@/misc/json-schema.js';
-import type { MiNote } from '@/models/entities/Note.js';
+import type { MiNote } from '@/models/Note.js';
import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
-import type { NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository } from '@/models/index.js';
+import type { NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
@Injectable()
@@ -57,7 +57,7 @@ export class NoteReadService implements OnApplicationShutdown {
if (isThreadMuted) return;
const unread = {
- id: this.idService.genId(),
+ id: this.idService.gen(),
noteId: note.id,
userId: userId,
isSpecified: params.isSpecified,
diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts
index d9a0f9bf53..ad7be83e5b 100644
--- a/packages/backend/src/core/NotificationService.ts
+++ b/packages/backend/src/core/NotificationService.ts
@@ -8,40 +8,39 @@ import * as Redis from 'ioredis';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
-import type { MutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository } from '@/models/index.js';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiNotification } from '@/models/entities/Notification.js';
-import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import type { UsersRepository } from '@/models/_.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiNotification } from '@/models/Notification.js';
import { bindThis } from '@/decorators.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { PushNotificationService } from '@/core/PushNotificationService.js';
import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js';
import { IdService } from '@/core/IdService.js';
import { CacheService } from '@/core/CacheService.js';
+import type { Config } from '@/config.js';
+import { UserListService } from '@/core/UserListService.js';
+import type { FilterUnionByProperty } from '@/types.js';
@Injectable()
export class NotificationService implements OnApplicationShutdown {
#shutdownController = new AbortController();
constructor(
+ @Inject(DI.config)
+ private config: Config,
+
@Inject(DI.redis)
private redisClient: Redis.Redis,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
- @Inject(DI.userProfilesRepository)
- private userProfilesRepository: UserProfilesRepository,
-
- @Inject(DI.mutingsRepository)
- private mutingsRepository: MutingsRepository,
-
private notificationEntityService: NotificationEntityService,
- private userEntityService: UserEntityService,
private idService: IdService,
private globalEventService: GlobalEventService,
private pushNotificationService: PushNotificationService,
private cacheService: CacheService,
+ private userListService: UserListService,
) {
}
@@ -75,36 +74,70 @@ export class NotificationService implements OnApplicationShutdown {
}
@bindThis
- public async createNotification(
+ public async createNotification(
notifieeId: MiUser['id'],
- type: MiNotification['type'],
- data: Partial,
+ type: T,
+ data: Omit, 'type' | 'id' | 'createdAt' | 'notifierId'>,
+ notifierId?: MiUser['id'] | null,
): Promise {
const profile = await this.cacheService.userProfileCache.fetch(notifieeId);
- const isMuted = profile.mutingNotificationTypes.includes(type);
- if (isMuted) return null;
- if (data.notifierId) {
- if (notifieeId === data.notifierId) {
+ // 古いMisskeyバージョンのキャッシュが残っている可能性がある
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ const recieveConfig = (profile.notificationRecieveConfig ?? {})[type];
+ if (recieveConfig?.type === 'never') {
+ return null;
+ }
+
+ if (notifierId) {
+ if (notifieeId === notifierId) {
return null;
}
const mutings = await this.cacheService.userMutingsCache.fetch(notifieeId);
- if (mutings.has(data.notifierId)) {
+ if (mutings.has(notifierId)) {
return null;
}
+
+ if (recieveConfig?.type === 'following') {
+ const isFollowing = await this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => Object.hasOwn(followings, notifierId));
+ if (!isFollowing) {
+ return null;
+ }
+ } else if (recieveConfig?.type === 'follower') {
+ const isFollower = await this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => Object.hasOwn(followings, notifieeId));
+ if (!isFollower) {
+ return null;
+ }
+ } else if (recieveConfig?.type === 'mutualFollow') {
+ const [isFollowing, isFollower] = await Promise.all([
+ this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => Object.hasOwn(followings, notifierId)),
+ this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => Object.hasOwn(followings, notifieeId)),
+ ]);
+ if (!isFollowing && !isFollower) {
+ return null;
+ }
+ } else if (recieveConfig?.type === 'list') {
+ const isMember = await this.userListService.membersCache.fetch(recieveConfig.userListId).then(members => members.has(notifierId));
+ if (!isMember) {
+ return null;
+ }
+ }
}
const notification = {
- id: this.idService.genId(),
+ id: this.idService.gen(),
createdAt: new Date(),
type: type,
+ ...(notifierId ? {
+ notifierId,
+ } : {}),
...data,
- } as MiNotification;
+ } as any as FilterUnionByProperty;
const redisIdPromise = this.redisClient.xadd(
`notificationTimeline:${notifieeId}`,
- 'MAXLEN', '~', '300',
+ 'MAXLEN', '~', this.config.perUserNotificationsMaxCount.toString(),
'*',
'data', JSON.stringify(notification));
@@ -114,15 +147,17 @@ export class NotificationService implements OnApplicationShutdown {
this.globalEventService.publishMainStream(notifieeId, 'notification', packed);
// 2秒経っても(今回作成した)通知が既読にならなかったら「未読の通知がありますよ」イベントを発行する
- setTimeout(2000, 'unread notification', { signal: this.#shutdownController.signal }).then(async () => {
+ // テスト通知の場合は即時発行
+ const interval = notification.type === 'test' ? 0 : 2000;
+ setTimeout(interval, 'unread notification', { signal: this.#shutdownController.signal }).then(async () => {
const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${notifieeId}`);
if (latestReadNotificationId && (latestReadNotificationId >= (await redisIdPromise)!)) return;
this.globalEventService.publishMainStream(notifieeId, 'unreadNotification', packed);
this.pushNotificationService.pushNotification(notifieeId, 'notification', packed);
- if (type === 'follow') this.emailNotificationFollow(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! }));
- if (type === 'receiveFollowRequest') this.emailNotificationReceiveFollowRequest(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! }));
+ if (type === 'follow') this.emailNotificationFollow(notifieeId, await this.usersRepository.findOneByOrFail({ id: notifierId! }));
+ if (type === 'receiveFollowRequest') this.emailNotificationReceiveFollowRequest(notifieeId, await this.usersRepository.findOneByOrFail({ id: notifierId! }));
}, () => { /* aborted, ignore it */ });
return notification;
diff --git a/packages/backend/src/core/PollService.ts b/packages/backend/src/core/PollService.ts
index 2178bdb8e9..9e1b5ca78a 100644
--- a/packages/backend/src/core/PollService.ts
+++ b/packages/backend/src/core/PollService.ts
@@ -5,8 +5,8 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { NotesRepository, UsersRepository, PollsRepository, PollVotesRepository, MiUser } from '@/models/index.js';
-import type { MiNote } from '@/models/entities/Note.js';
+import type { NotesRepository, UsersRepository, PollsRepository, PollVotesRepository, MiUser } from '@/models/_.js';
+import type { MiNote } from '@/models/Note.js';
import { RelayService } from '@/core/RelayService.js';
import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
@@ -72,10 +72,8 @@ export class PollService {
throw new Error('already voted');
}
- // Create vote
await this.pollVotesRepository.insert({
- id: this.idService.genId(),
- createdAt: new Date(),
+ id: this.idService.gen(),
noteId: note.id,
userId: user.id,
choice: choice,
diff --git a/packages/backend/src/core/ProxyAccountService.ts b/packages/backend/src/core/ProxyAccountService.ts
index 9fb0a45df9..b1bc60701b 100644
--- a/packages/backend/src/core/ProxyAccountService.ts
+++ b/packages/backend/src/core/ProxyAccountService.ts
@@ -4,8 +4,8 @@
*/
import { Inject, Injectable } from '@nestjs/common';
-import type { UsersRepository } from '@/models/index.js';
-import type { MiLocalUser } from '@/models/entities/User.js';
+import type { UsersRepository } from '@/models/_.js';
+import type { MiLocalUser } from '@/models/User.js';
import { DI } from '@/di-symbols.js';
import { MetaService } from '@/core/MetaService.js';
import { bindThis } from '@/decorators.js';
diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts
index 93928e3cd7..40d1deceeb 100644
--- a/packages/backend/src/core/PushNotificationService.ts
+++ b/packages/backend/src/core/PushNotificationService.ts
@@ -10,7 +10,7 @@ import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import type { Packed } from '@/misc/json-schema.js';
import { getNoteSummary } from '@/misc/get-note-summary.js';
-import type { MiSwSubscription, SwSubscriptionsRepository } from '@/models/index.js';
+import type { MiSwSubscription, SwSubscriptionsRepository } from '@/models/_.js';
import { MetaService } from '@/core/MetaService.js';
import { bindThis } from '@/decorators.js';
import { RedisKVCache } from '@/misc/cache.js';
diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts
index 191a06b353..f85bed7295 100644
--- a/packages/backend/src/core/QueryService.ts
+++ b/packages/backend/src/core/QueryService.ts
@@ -6,9 +6,10 @@
import { Inject, Injectable } from '@nestjs/common';
import { Brackets, ObjectLiteral } from 'typeorm';
import { DI } from '@/di-symbols.js';
-import type { MiUser } from '@/models/entities/User.js';
-import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, MutedNotesRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository, RenoteMutingsRepository } from '@/models/index.js';
+import type { MiUser } from '@/models/User.js';
+import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository, RenoteMutingsRepository } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
import type { SelectQueryBuilder } from 'typeorm';
@Injectable()
@@ -23,9 +24,6 @@ export class QueryService {
@Inject(DI.channelFollowingsRepository)
private channelFollowingsRepository: ChannelFollowingsRepository,
- @Inject(DI.mutedNotesRepository)
- private mutedNotesRepository: MutedNotesRepository,
-
@Inject(DI.blockingsRepository)
private blockingsRepository: BlockingsRepository,
@@ -37,10 +35,12 @@ export class QueryService {
@Inject(DI.renoteMutingsRepository)
private renoteMutingsRepository: RenoteMutingsRepository,
+
+ private idService: IdService,
) {
}
- public makePaginationQuery(q: SelectQueryBuilder, sinceId?: string, untilId?: string, sinceDate?: number, untilDate?: number): SelectQueryBuilder {
+ public makePaginationQuery(q: SelectQueryBuilder, sinceId?: string | null, untilId?: string | null, sinceDate?: number | null, untilDate?: number | null): SelectQueryBuilder {
if (sinceId && untilId) {
q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: sinceId });
q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId });
@@ -52,15 +52,15 @@ export class QueryService {
q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId });
q.orderBy(`${q.alias}.id`, 'DESC');
} else if (sinceDate && untilDate) {
- q.andWhere(`${q.alias}.createdAt > :sinceDate`, { sinceDate: new Date(sinceDate) });
- q.andWhere(`${q.alias}.createdAt < :untilDate`, { untilDate: new Date(untilDate) });
- q.orderBy(`${q.alias}.createdAt`, 'DESC');
+ q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: this.idService.gen(sinceDate) });
+ q.andWhere(`${q.alias}.id < :untilId`, { untilId: this.idService.gen(untilDate) });
+ q.orderBy(`${q.alias}.id`, 'DESC');
} else if (sinceDate) {
- q.andWhere(`${q.alias}.createdAt > :sinceDate`, { sinceDate: new Date(sinceDate) });
- q.orderBy(`${q.alias}.createdAt`, 'ASC');
+ q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: this.idService.gen(sinceDate) });
+ q.orderBy(`${q.alias}.id`, 'ASC');
} else if (untilDate) {
- q.andWhere(`${q.alias}.createdAt < :untilDate`, { untilDate: new Date(untilDate) });
- q.orderBy(`${q.alias}.createdAt`, 'DESC');
+ q.andWhere(`${q.alias}.id < :untilId`, { untilId: this.idService.gen(untilDate) });
+ q.orderBy(`${q.alias}.id`, 'DESC');
} else {
q.orderBy(`${q.alias}.id`, 'DESC');
}
@@ -79,13 +79,15 @@ export class QueryService {
// 投稿の引用元の作者にブロックされていない
q
.andWhere(`note.userId NOT IN (${ blockingQuery.getQuery() })`)
- .andWhere(new Brackets(qb => { qb
- .where('note.replyUserId IS NULL')
- .orWhere(`note.replyUserId NOT IN (${ blockingQuery.getQuery() })`);
+ .andWhere(new Brackets(qb => {
+ qb
+ .where('note.replyUserId IS NULL')
+ .orWhere(`note.replyUserId NOT IN (${ blockingQuery.getQuery() })`);
}))
- .andWhere(new Brackets(qb => { qb
- .where('note.renoteUserId IS NULL')
- .orWhere(`note.renoteUserId NOT IN (${ blockingQuery.getQuery() })`);
+ .andWhere(new Brackets(qb => {
+ qb
+ .where('note.renoteUserId IS NULL')
+ .orWhere(`note.renoteUserId NOT IN (${ blockingQuery.getQuery() })`);
}));
q.setParameters(blockingQuery.getParameters());
@@ -108,39 +110,6 @@ export class QueryService {
q.setParameters(blockedQuery.getParameters());
}
- @bindThis
- public generateChannelQuery(q: SelectQueryBuilder, me: { id: MiUser['id'] } | null): void {
- if (me == null) {
- q.andWhere('note.channelId IS NULL');
- } else {
- q.leftJoinAndSelect('note.channel', 'channel');
-
- const channelFollowingQuery = this.channelFollowingsRepository.createQueryBuilder('channelFollowing')
- .select('channelFollowing.followeeId')
- .where('channelFollowing.followerId = :followerId', { followerId: me.id });
-
- q.andWhere(new Brackets(qb => { qb
- // チャンネルのノートではない
- .where('note.channelId IS NULL')
- // または自分がフォローしているチャンネルのノート
- .orWhere(`note.channelId IN (${ channelFollowingQuery.getQuery() })`);
- }));
-
- q.setParameters(channelFollowingQuery.getParameters());
- }
- }
-
- @bindThis
- public generateMutedNoteQuery(q: SelectQueryBuilder, me: { id: MiUser['id'] }): void {
- const mutedQuery = this.mutedNotesRepository.createQueryBuilder('muted')
- .select('muted.noteId')
- .where('muted.userId = :userId', { userId: me.id });
-
- q.andWhere(`note.id NOT IN (${ mutedQuery.getQuery() })`);
-
- q.setParameters(mutedQuery.getParameters());
- }
-
@bindThis
public generateMutedNoteThreadQuery(q: SelectQueryBuilder, me: { id: MiUser['id'] }): void {
const mutedQuery = this.noteThreadMutingsRepository.createQueryBuilder('threadMuted')
@@ -148,16 +117,17 @@ export class QueryService {
.where('threadMuted.userId = :userId', { userId: me.id });
q.andWhere(`note.id NOT IN (${ mutedQuery.getQuery() })`);
- q.andWhere(new Brackets(qb => { qb
- .where('note.threadId IS NULL')
- .orWhere(`note.threadId NOT IN (${ mutedQuery.getQuery() })`);
+ q.andWhere(new Brackets(qb => {
+ qb
+ .where('note.threadId IS NULL')
+ .orWhere(`note.threadId NOT IN (${ mutedQuery.getQuery() })`);
}));
q.setParameters(mutedQuery.getParameters());
}
@bindThis
- public generateMutedUserQuery(q: SelectQueryBuilder, me: { id: MiUser['id'] }, exclude?: MiUser): void {
+ public generateMutedUserQuery(q: SelectQueryBuilder, me: { id: MiUser['id'] }, exclude?: { id: MiUser['id'] }): void {
const mutingQuery = this.mutingsRepository.createQueryBuilder('muting')
.select('muting.muteeId')
.where('muting.muterId = :muterId', { muterId: me.id });
@@ -175,26 +145,31 @@ export class QueryService {
// 投稿の引用元の作者をミュートしていない
q
.andWhere(`note.userId NOT IN (${ mutingQuery.getQuery() })`)
- .andWhere(new Brackets(qb => { qb
- .where('note.replyUserId IS NULL')
- .orWhere(`note.replyUserId NOT IN (${ mutingQuery.getQuery() })`);
+ .andWhere(new Brackets(qb => {
+ qb
+ .where('note.replyUserId IS NULL')
+ .orWhere(`note.replyUserId NOT IN (${ mutingQuery.getQuery() })`);
}))
- .andWhere(new Brackets(qb => { qb
- .where('note.renoteUserId IS NULL')
- .orWhere(`note.renoteUserId NOT IN (${ mutingQuery.getQuery() })`);
+ .andWhere(new Brackets(qb => {
+ qb
+ .where('note.renoteUserId IS NULL')
+ .orWhere(`note.renoteUserId NOT IN (${ mutingQuery.getQuery() })`);
}))
// mute instances
- .andWhere(new Brackets(qb => { qb
- .andWhere('note.userHost IS NULL')
- .orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.userHost)`);
+ .andWhere(new Brackets(qb => {
+ qb
+ .andWhere('note.userHost IS NULL')
+ .orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.userHost)`);
}))
- .andWhere(new Brackets(qb => { qb
- .where('note.replyUserHost IS NULL')
- .orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.replyUserHost)`);
+ .andWhere(new Brackets(qb => {
+ qb
+ .where('note.replyUserHost IS NULL')
+ .orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.replyUserHost)`);
}))
- .andWhere(new Brackets(qb => { qb
- .where('note.renoteUserHost IS NULL')
- .orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.renoteUserHost)`);
+ .andWhere(new Brackets(qb => {
+ qb
+ .where('note.renoteUserHost IS NULL')
+ .orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.renoteUserHost)`);
}));
q.setParameters(mutingQuery.getParameters());
@@ -212,66 +187,45 @@ export class QueryService {
q.setParameters(mutingQuery.getParameters());
}
- @bindThis
- public generateRepliesQuery(q: SelectQueryBuilder, withReplies: boolean, me: Pick | null): void {
- if (me == null) {
- q.andWhere(new Brackets(qb => { qb
- .where('note.replyId IS NULL') // 返信ではない
- .orWhere(new Brackets(qb => { qb // 返信だけど投稿者自身への返信
- .where('note.replyId IS NOT NULL')
- .andWhere('note.replyUserId = note.userId');
- }));
- }));
- } else if (!withReplies) {
- q.andWhere(new Brackets(qb => { qb
- .where('note.replyId IS NULL') // 返信ではない
- .orWhere('note.replyUserId = :meId', { meId: me.id }) // 返信だけど自分のノートへの返信
- .orWhere(new Brackets(qb => { qb // 返信だけど自分の行った返信
- .where('note.replyId IS NOT NULL')
- .andWhere('note.userId = :meId', { meId: me.id });
- }))
- .orWhere(new Brackets(qb => { qb // 返信だけど投稿者自身への返信
- .where('note.replyId IS NOT NULL')
- .andWhere('note.replyUserId = note.userId');
- }));
- }));
- }
- }
-
@bindThis
public generateVisibilityQuery(q: SelectQueryBuilder, me: { id: MiUser['id'] } | null): void {
// This code must always be synchronized with the checks in Notes.isVisibleForMe.
if (me == null) {
- q.andWhere(new Brackets(qb => { qb
- .where('note.visibility = \'public\'')
- .orWhere('note.visibility = \'home\'');
+ q.andWhere(new Brackets(qb => {
+ qb
+ .where('note.visibility = \'public\'')
+ .orWhere('note.visibility = \'home\'');
}));
} else {
const followingQuery = this.followingsRepository.createQueryBuilder('following')
.select('following.followeeId')
.where('following.followerId = :meId');
- q.andWhere(new Brackets(qb => { qb
+ q.andWhere(new Brackets(qb => {
+ qb
// 公開投稿である
- .where(new Brackets(qb => { qb
- .where('note.visibility = \'public\'')
- .orWhere('note.visibility = \'home\'');
- }))
+ .where(new Brackets(qb => {
+ qb
+ .where('note.visibility = \'public\'')
+ .orWhere('note.visibility = \'home\'');
+ }))
// または 自分自身
- .orWhere('note.userId = :meId')
+ .orWhere('note.userId = :meId')
// または 自分宛て
- .orWhere(':meId = ANY(note.visibleUserIds)')
- .orWhere(':meId = ANY(note.mentions)')
- .orWhere(new Brackets(qb => { qb
- // または フォロワー宛ての投稿であり、
- .where('note.visibility = \'followers\'')
- .andWhere(new Brackets(qb => { qb
- // 自分がフォロワーである
- .where(`note.userId IN (${ followingQuery.getQuery() })`)
- // または 自分の投稿へのリプライ
- .orWhere('note.replyUserId = :meId');
+ .orWhere(':meId = ANY(note.visibleUserIds)')
+ .orWhere(':meId = ANY(note.mentions)')
+ .orWhere(new Brackets(qb => {
+ qb
+ // または フォロワー宛ての投稿であり、
+ .where('note.visibility = \'followers\'')
+ .andWhere(new Brackets(qb => {
+ qb
+ // 自分がフォロワーである
+ .where(`note.userId IN (${ followingQuery.getQuery() })`)
+ // または 自分の投稿へのリプライ
+ .orWhere('note.replyUserId = :meId');
+ }));
}));
- }));
}));
q.setParameters({ meId: me.id });
diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts
index 930ba0f94a..c999e73169 100644
--- a/packages/backend/src/core/QueueService.ts
+++ b/packages/backend/src/core/QueueService.ts
@@ -6,9 +6,9 @@
import { randomUUID } from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common';
import type { IActivity } from '@/core/activitypub/type.js';
-import type { MiDriveFile } from '@/models/entities/DriveFile.js';
-import type { MiAbuseUserReport } from '@/models/entities/AbuseUserReport.js';
-import type { MiWebhook, webhookEventTypes } from '@/models/entities/Webhook.js';
+import type { MiDriveFile } from '@/models/DriveFile.js';
+import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
+import type { MiWebhook, webhookEventTypes } from '@/models/Webhook.js';
import type { Config } from '@/config.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
@@ -238,10 +238,11 @@ export class QueueService {
}
@bindThis
- public createImportFollowingJob(user: ThinUser, fileId: MiDriveFile['id']) {
+ public createImportFollowingJob(user: ThinUser, fileId: MiDriveFile['id'], withReplies?: boolean) {
return this.dbQueue.add('importFollowing', {
user: { id: user.id },
fileId: fileId,
+ withReplies,
}, {
removeOnComplete: true,
removeOnFail: true,
@@ -249,8 +250,8 @@ export class QueueService {
}
@bindThis
- public createImportFollowingToDbJob(user: ThinUser, targets: string[]) {
- const jobs = targets.map(rel => this.generateToDbJobData('importFollowingToDb', { user, target: rel }));
+ public createImportFollowingToDbJob(user: ThinUser, targets: string[], withReplies?: boolean) {
+ const jobs = targets.map(rel => this.generateToDbJobData('importFollowingToDb', { user, target: rel, withReplies }));
return this.dbQueue.addBulk(jobs);
}
@@ -348,7 +349,7 @@ export class QueueService {
}
@bindThis
- public createFollowJob(followings: { from: ThinUser, to: ThinUser, requestId?: string, silent?: boolean }[]) {
+ public createFollowJob(followings: { from: ThinUser, to: ThinUser, requestId?: string, silent?: boolean, withReplies?: boolean }[]) {
const jobs = followings.map(rel => this.generateRelationshipJobData('follow', rel));
return this.relationshipQueue.addBulk(jobs);
}
@@ -390,6 +391,7 @@ export class QueueService {
to: { id: data.to.id },
silent: data.silent,
requestId: data.requestId,
+ withReplies: data.withReplies,
},
opts: {
removeOnComplete: true,
diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts
index 18a1cf8b47..d1560d2fb0 100644
--- a/packages/backend/src/core/ReactionService.ts
+++ b/packages/backend/src/core/ReactionService.ts
@@ -4,13 +4,14 @@
*/
import { Inject, Injectable } from '@nestjs/common';
+import * as Redis from 'ioredis';
import { DI } from '@/di-symbols.js';
-import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository } from '@/models/index.js';
+import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository } from '@/models/_.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
-import type { MiRemoteUser, MiUser } from '@/models/entities/User.js';
-import type { MiNote } from '@/models/entities/Note.js';
+import type { MiRemoteUser, MiUser } from '@/models/User.js';
+import type { MiNote } from '@/models/Note.js';
import { IdService } from '@/core/IdService.js';
-import type { MiNoteReaction } from '@/models/entities/NoteReaction.js';
+import type { MiNoteReaction } from '@/models/NoteReaction.js';
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { NotificationService } from '@/core/NotificationService.js';
@@ -29,6 +30,7 @@ import { RoleService } from '@/core/RoleService.js';
import { FeaturedService } from '@/core/FeaturedService.js';
const FALLBACK = '❤';
+const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
const legacies: Record = {
'like': '👍',
@@ -67,6 +69,9 @@ const decodeCustomEmojiRegexp = /^:([\w+-]+)(?:@([\w.-]+))?:$/;
@Injectable()
export class ReactionService {
constructor(
+ @Inject(DI.redis)
+ private redisClient: Redis.Redis,
+
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@@ -147,12 +152,12 @@ export class ReactionService {
reaction = FALLBACK;
}
} else {
- reaction = this.normalize(reaction ?? null);
+ reaction = this.normalize(reaction);
}
}
const record: MiNoteReaction = {
- id: this.idService.genId(),
+ id: this.idService.gen(),
createdAt: new Date(),
noteId: note.id,
userId: user.id,
@@ -187,7 +192,9 @@ export class ReactionService {
await this.notesRepository.createQueryBuilder().update()
.set({
reactions: () => sql,
- ... (!user.isBot ? { score: () => '"score" + 1' } : {}),
+ ...(note.reactionAndUserPairCache.length < PER_NOTE_REACTION_USER_PAIR_CACHE_MAX ? {
+ reactionAndUserPairCache: () => `array_append("reactionAndUserPairCache", '${user.id}/${reaction}')`,
+ } : {}),
})
.where('id = :id', { id: note.id })
.execute();
@@ -242,10 +249,9 @@ export class ReactionService {
// リアクションされたユーザーがローカルユーザーなら通知を作成
if (note.userHost === null) {
this.notificationService.createNotification(note.userId, 'reaction', {
- notifierId: user.id,
noteId: note.id,
reaction: reaction,
- });
+ }, user.id);
}
//#region 配信
@@ -295,12 +301,11 @@ export class ReactionService {
await this.notesRepository.createQueryBuilder().update()
.set({
reactions: () => sql,
+ reactionAndUserPairCache: () => `array_remove("reactionAndUserPairCache", '${user.id}/${exist.reaction}')`,
})
.where('id = :id', { id: note.id })
.execute();
- if (!user.isBot) this.notesRepository.decrement({ id: note.id }, 'score', 1);
-
this.globalEventService.publishNoteStream(note.id, 'unreacted', {
reaction: this.decodeReaction(exist.reaction).reaction,
userId: user.id,
diff --git a/packages/backend/src/core/RegistryApiService.ts b/packages/backend/src/core/RegistryApiService.ts
new file mode 100644
index 0000000000..d340c5e480
--- /dev/null
+++ b/packages/backend/src/core/RegistryApiService.ts
@@ -0,0 +1,147 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { DI } from '@/di-symbols.js';
+import type { MiRegistryItem, RegistryItemsRepository } from '@/models/_.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
+import type { MiUser } from '@/models/User.js';
+import { IdService } from '@/core/IdService.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { bindThis } from '@/decorators.js';
+
+@Injectable()
+export class RegistryApiService {
+ constructor(
+ @Inject(DI.registryItemsRepository)
+ private registryItemsRepository: RegistryItemsRepository,
+
+ private idService: IdService,
+ private globalEventService: GlobalEventService,
+ ) {
+ }
+
+ @bindThis
+ public async set(userId: MiUser['id'], domain: string | null, scope: string[], key: string, value: any) {
+ // TODO: 作成できるキーの数を制限する
+
+ const query = this.registryItemsRepository.createQueryBuilder('item');
+ if (domain) {
+ query.where('item.domain = :domain', { domain: domain });
+ } else {
+ query.where('item.domain IS NULL');
+ }
+ query.andWhere('item.userId = :userId', { userId: userId });
+ query.andWhere('item.key = :key', { key: key });
+ query.andWhere('item.scope = :scope', { scope: scope });
+
+ const existingItem = await query.getOne();
+
+ if (existingItem) {
+ await this.registryItemsRepository.update(existingItem.id, {
+ updatedAt: new Date(),
+ value: value,
+ });
+ } else {
+ await this.registryItemsRepository.insert({
+ id: this.idService.gen(),
+ updatedAt: new Date(),
+ userId: userId,
+ domain: domain,
+ scope: scope,
+ key: key,
+ value: value,
+ });
+ }
+
+ if (domain == null) {
+ // TODO: サードパーティアプリが傍受出来てしまうのでどうにかする
+ this.globalEventService.publishMainStream(userId, 'registryUpdated', {
+ scope: scope,
+ key: key,
+ value: value,
+ });
+ }
+ }
+
+ @bindThis
+ public async getItem(userId: MiUser['id'], domain: string | null, scope: string[], key: string): Promise {
+ const query = this.registryItemsRepository.createQueryBuilder('item')
+ .where(domain == null ? 'item.domain IS NULL' : 'item.domain = :domain', { domain: domain })
+ .andWhere('item.userId = :userId', { userId: userId })
+ .andWhere('item.key = :key', { key: key })
+ .andWhere('item.scope = :scope', { scope: scope });
+
+ const item = await query.getOne();
+
+ return item;
+ }
+
+ @bindThis
+ public async getAllItemsOfScope(userId: MiUser['id'], domain: string | null, scope: string[]): Promise {
+ const query = this.registryItemsRepository.createQueryBuilder('item');
+ query.where(domain == null ? 'item.domain IS NULL' : 'item.domain = :domain', { domain: domain });
+ query.andWhere('item.userId = :userId', { userId: userId });
+ query.andWhere('item.scope = :scope', { scope: scope });
+
+ const items = await query.getMany();
+
+ return items;
+ }
+
+ @bindThis
+ public async getAllKeysOfScope(userId: MiUser['id'], domain: string | null, scope: string[]): Promise {
+ const query = this.registryItemsRepository.createQueryBuilder('item');
+ query.select('item.key');
+ query.where(domain == null ? 'item.domain IS NULL' : 'item.domain = :domain', { domain: domain });
+ query.andWhere('item.userId = :userId', { userId: userId });
+ query.andWhere('item.scope = :scope', { scope: scope });
+
+ const items = await query.getMany();
+
+ return items.map(x => x.key);
+ }
+
+ @bindThis
+ public async getAllScopeAndDomains(userId: MiUser['id']): Promise<{ domain: string | null; scopes: string[][] }[]> {
+ const query = this.registryItemsRepository.createQueryBuilder('item')
+ .select(['item.scope', 'item.domain'])
+ .where('item.userId = :userId', { userId: userId });
+
+ const items = await query.getMany();
+
+ const res = [] as { domain: string | null; scopes: string[][] }[];
+
+ for (const item of items) {
+ const target = res.find(x => x.domain === item.domain);
+ if (target) {
+ if (target.scopes.some(scope => scope.join('.') === item.scope.join('.'))) continue;
+ target.scopes.push(item.scope);
+ } else {
+ res.push({
+ domain: item.domain,
+ scopes: [item.scope],
+ });
+ }
+ }
+
+ return res;
+ }
+
+ @bindThis
+ public async remove(userId: MiUser['id'], domain: string | null, scope: string[], key: string) {
+ const query = this.registryItemsRepository.createQueryBuilder().delete();
+ if (domain) {
+ query.where('domain = :domain', { domain: domain });
+ } else {
+ query.where('domain IS NULL');
+ }
+ query.andWhere('userId = :userId', { userId: userId });
+ query.andWhere('key = :key', { key: key });
+ query.andWhere('scope = :scope', { scope: scope });
+
+ await query.execute();
+ }
+}
diff --git a/packages/backend/src/core/RelayService.ts b/packages/backend/src/core/RelayService.ts
index 89f245e86d..d40cd080c7 100644
--- a/packages/backend/src/core/RelayService.ts
+++ b/packages/backend/src/core/RelayService.ts
@@ -5,11 +5,11 @@
import { Inject, Injectable } from '@nestjs/common';
import { IsNull } from 'typeorm';
-import type { MiLocalUser, MiUser } from '@/models/entities/User.js';
-import type { RelaysRepository, UsersRepository } from '@/models/index.js';
+import type { MiLocalUser, MiUser } from '@/models/User.js';
+import type { RelaysRepository, UsersRepository } from '@/models/_.js';
import { IdService } from '@/core/IdService.js';
import { MemorySingleCache } from '@/misc/cache.js';
-import type { MiRelay } from '@/models/entities/Relay.js';
+import type { MiRelay } from '@/models/Relay.js';
import { QueueService } from '@/core/QueueService.js';
import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
@@ -54,7 +54,7 @@ export class RelayService {
@bindThis
public async addRelay(inbox: string): Promise {
const relay = await this.relaysRepository.insert({
- id: this.idService.genId(),
+ id: this.idService.gen(),
inbox,
status: 'requesting',
}).then(x => this.relaysRepository.findOneByOrFail(x.identifiers[0]));
diff --git a/packages/backend/src/core/RemoteUserResolveService.ts b/packages/backend/src/core/RemoteUserResolveService.ts
index ca538ed6cd..75c5f14aa4 100644
--- a/packages/backend/src/core/RemoteUserResolveService.ts
+++ b/packages/backend/src/core/RemoteUserResolveService.ts
@@ -8,8 +8,8 @@ import { Inject, Injectable } from '@nestjs/common';
import chalk from 'chalk';
import { IsNull } from 'typeorm';
import { DI } from '@/di-symbols.js';
-import type { UsersRepository } from '@/models/index.js';
-import type { MiLocalUser, MiRemoteUser } from '@/models/entities/User.js';
+import type { UsersRepository } from '@/models/_.js';
+import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
import type { Config } from '@/config.js';
import type Logger from '@/logger.js';
import { UtilityService } from '@/core/UtilityService.js';
diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts
index 9713e6274c..5f6e8f6069 100644
--- a/packages/backend/src/core/RoleService.ts
+++ b/packages/backend/src/core/RoleService.ts
@@ -6,20 +6,22 @@
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import { In } from 'typeorm';
-import type { MiRole, MiRoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/index.js';
+import type { MiRole, MiRoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/_.js';
import { MemoryKVCache, MemorySingleCache } from '@/misc/cache.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
-import type { MiUser } from '@/models/entities/User.js';
+import type { MiUser } from '@/models/User.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { MetaService } from '@/core/MetaService.js';
import { CacheService } from '@/core/CacheService.js';
-import type { RoleCondFormulaValue } from '@/models/entities/Role.js';
+import type { RoleCondFormulaValue } from '@/models/Role.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import { StreamMessages } from '@/server/api/stream/types.js';
+import type { GlobalEvents } from '@/core/GlobalEventService.js';
import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
import type { Packed } from '@/misc/json-schema.js';
+import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
import type { OnApplicationShutdown } from '@nestjs/common';
export type RolePolicies = {
@@ -34,7 +36,9 @@ export type RolePolicies = {
inviteLimitCycle: number;
inviteExpirationTime: number;
canManageCustomEmojis: boolean;
+ canManageAvatarDecorations: boolean;
canSearchNotes: boolean;
+ canUseTranslator: boolean;
canHideAds: boolean;
driveCapacityMb: number;
alwaysMarkNsfw: boolean;
@@ -61,7 +65,9 @@ export const DEFAULT_POLICIES: RolePolicies = {
inviteLimitCycle: 60 * 24 * 7,
inviteExpirationTime: 0,
canManageCustomEmojis: false,
+ canManageAvatarDecorations: false,
canSearchNotes: false,
+ canUseTranslator: true,
canHideAds: false,
driveCapacityMb: 100,
alwaysMarkNsfw: false,
@@ -102,6 +108,8 @@ export class RoleService implements OnApplicationShutdown {
private userEntityService: UserEntityService,
private globalEventService: GlobalEventService,
private idService: IdService,
+ private moderationLogService: ModerationLogService,
+ private funoutTimelineService: FunoutTimelineService,
) {
//this.onMessage = this.onMessage.bind(this);
@@ -116,7 +124,7 @@ export class RoleService implements OnApplicationShutdown {
const obj = JSON.parse(data);
if (obj.channel === 'internal') {
- const { type, body } = obj.message as StreamMessages['internal']['payload'];
+ const { type, body } = obj.message as GlobalEvents['internal']['payload'];
switch (type) {
case 'roleCreated': {
const cached = this.rolesCache.get();
@@ -196,10 +204,10 @@ export class RoleService implements OnApplicationShutdown {
return this.userEntityService.isRemoteUser(user);
}
case 'createdLessThan': {
- return user.createdAt.getTime() > (Date.now() - (value.sec * 1000));
+ return this.idService.parse(user.id).date.getTime() > (Date.now() - (value.sec * 1000));
}
case 'createdMoreThan': {
- return user.createdAt.getTime() < (Date.now() - (value.sec * 1000));
+ return this.idService.parse(user.id).date.getTime() < (Date.now() - (value.sec * 1000));
}
case 'followersLessThanOrEq': {
return user.followersCount <= value.value;
@@ -228,6 +236,12 @@ export class RoleService implements OnApplicationShutdown {
}
}
+ @bindThis
+ public async getRoles() {
+ const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
+ return roles;
+ }
+
@bindThis
public async getUserAssigns(userId: MiUser['id']) {
const now = Date.now();
@@ -304,7 +318,9 @@ export class RoleService implements OnApplicationShutdown {
inviteLimitCycle: calc('inviteLimitCycle', vs => Math.max(...vs)),
inviteExpirationTime: calc('inviteExpirationTime', vs => Math.max(...vs)),
canManageCustomEmojis: calc('canManageCustomEmojis', vs => vs.some(v => v === true)),
+ canManageAvatarDecorations: calc('canManageAvatarDecorations', vs => vs.some(v => v === true)),
canSearchNotes: calc('canSearchNotes', vs => vs.some(v => v === true)),
+ canUseTranslator: calc('canUseTranslator', vs => vs.some(v => v === true)),
canHideAds: calc('canHideAds', vs => vs.some(v => v === true)),
driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)),
alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)),
@@ -381,11 +397,17 @@ export class RoleService implements OnApplicationShutdown {
}
@bindThis
- public async assign(userId: MiUser['id'], roleId: MiRole['id'], expiresAt: Date | null = null): Promise {
- const now = new Date();
+ public async assign(userId: MiUser['id'], roleId: MiRole['id'], expiresAt: Date | null = null, moderator?: MiUser): Promise {
+ const now = Date.now();
- let existing = await this.roleAssignmentsRepository.findOneBy({ roleId, userId });
- if (existing?.expiresAt && (existing.expiresAt.getTime() < now.getTime())) {
+ const role = await this.rolesRepository.findOneByOrFail({ id: roleId });
+
+ let existing = await this.roleAssignmentsRepository.findOneBy({
+ roleId: roleId,
+ userId: userId,
+ });
+
+ if (existing?.expiresAt && (existing.expiresAt.getTime() < now)) {
await this.roleAssignmentsRepository.delete({
roleId: roleId,
userId: userId,
@@ -395,8 +417,7 @@ export class RoleService implements OnApplicationShutdown {
if (!existing) {
const created = await this.roleAssignmentsRepository.insert({
- id: this.idService.genId(),
- createdAt: now,
+ id: this.idService.gen(now),
expiresAt: expiresAt,
roleId: roleId,
userId: userId,
@@ -414,10 +435,22 @@ export class RoleService implements OnApplicationShutdown {
this.rolesRepository.update(roleId, {
lastUsedAt: new Date(),
});
+
+ if (moderator) {
+ const user = await this.usersRepository.findOneByOrFail({ id: userId });
+ this.moderationLogService.log(moderator, 'assignRole', {
+ roleId: roleId,
+ roleName: role.name,
+ userId: userId,
+ userUsername: user.username,
+ userHost: user.host,
+ expiresAt: expiresAt ? expiresAt.toISOString() : null,
+ });
+ }
}
@bindThis
- public async unassign(userId: MiUser['id'], roleId: MiRole['id']): Promise {
+ public async unassign(userId: MiUser['id'], roleId: MiRole['id'], moderator?: MiUser): Promise {
const now = new Date();
let existing = await this.roleAssignmentsRepository.findOneBy({ roleId, userId });
@@ -440,6 +473,20 @@ export class RoleService implements OnApplicationShutdown {
});
this.globalEventService.publishInternalEvent('userRoleUnassigned', existing);
+
+ if (moderator) {
+ const [user, role] = await Promise.all([
+ this.usersRepository.findOneByOrFail({ id: userId }),
+ this.rolesRepository.findOneByOrFail({ id: roleId }),
+ ]);
+ this.moderationLogService.log(moderator, 'unassignRole', {
+ roleId: roleId,
+ roleName: role.name,
+ userId: userId,
+ userUsername: user.username,
+ userHost: user.host,
+ });
+ }
}
@bindThis
@@ -449,18 +496,81 @@ export class RoleService implements OnApplicationShutdown {
const redisPipeline = this.redisClient.pipeline();
for (const role of roles) {
- redisPipeline.xadd(
- `roleTimeline:${role.id}`,
- 'MAXLEN', '~', '1000',
- '*',
- 'note', note.id);
-
+ this.funoutTimelineService.push(`roleTimeline:${role.id}`, note.id, 1000, redisPipeline);
this.globalEventService.publishRoleTimelineStream(role.id, 'note', note);
}
redisPipeline.exec();
}
+ @bindThis
+ public async create(values: Partial, moderator?: MiUser): Promise {
+ const date = new Date();
+ const created = await this.rolesRepository.insert({
+ id: this.idService.gen(date.getTime()),
+ updatedAt: date,
+ lastUsedAt: date,
+ name: values.name,
+ description: values.description,
+ color: values.color,
+ iconUrl: values.iconUrl,
+ target: values.target,
+ condFormula: values.condFormula,
+ isPublic: values.isPublic,
+ isAdministrator: values.isAdministrator,
+ isModerator: values.isModerator,
+ isExplorable: values.isExplorable,
+ asBadge: values.asBadge,
+ canEditMembersByModerator: values.canEditMembersByModerator,
+ displayOrder: values.displayOrder,
+ policies: values.policies,
+ }).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0]));
+
+ this.globalEventService.publishInternalEvent('roleCreated', created);
+
+ if (moderator) {
+ this.moderationLogService.log(moderator, 'createRole', {
+ roleId: created.id,
+ role: created,
+ });
+ }
+
+ return created;
+ }
+
+ @bindThis
+ public async update(role: MiRole, params: Partial, moderator?: MiUser): Promise {
+ const date = new Date();
+ await this.rolesRepository.update(role.id, {
+ updatedAt: date,
+ ...params,
+ });
+
+ const updated = await this.rolesRepository.findOneByOrFail({ id: role.id });
+ this.globalEventService.publishInternalEvent('roleUpdated', updated);
+
+ if (moderator) {
+ this.moderationLogService.log(moderator, 'updateRole', {
+ roleId: role.id,
+ before: role,
+ after: updated,
+ });
+ }
+ }
+
+ @bindThis
+ public async delete(role: MiRole, moderator?: MiUser): Promise {
+ await this.rolesRepository.delete({ id: role.id });
+ this.globalEventService.publishInternalEvent('roleDeleted', role);
+
+ if (moderator) {
+ this.moderationLogService.log(moderator, 'deleteRole', {
+ roleId: role.id,
+ role: role,
+ });
+ }
+ }
+
@bindThis
public dispose(): void {
this.redisForSub.off('message', this.onMessage);
diff --git a/packages/backend/src/core/S3Service.ts b/packages/backend/src/core/S3Service.ts
index 9f54e885a2..df0991539d 100644
--- a/packages/backend/src/core/S3Service.ts
+++ b/packages/backend/src/core/S3Service.ts
@@ -6,13 +6,11 @@
import { URL } from 'node:url';
import * as http from 'node:http';
import * as https from 'node:https';
-import { Inject, Injectable } from '@nestjs/common';
+import { Injectable } from '@nestjs/common';
import { DeleteObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { Upload } from '@aws-sdk/lib-storage';
-import { NodeHttpHandler, NodeHttpHandlerOptions } from '@aws-sdk/node-http-handler';
-import { DI } from '@/di-symbols.js';
-import type { Config } from '@/config.js';
-import type { MiMeta } from '@/models/entities/Meta.js';
+import { NodeHttpHandler, NodeHttpHandlerOptions } from '@smithy/node-http-handler';
+import type { MiMeta } from '@/models/Meta.js';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import { bindThis } from '@/decorators.js';
import type { DeleteObjectCommandInput, PutObjectCommandInput } from '@aws-sdk/client-s3';
@@ -20,9 +18,6 @@ import type { DeleteObjectCommandInput, PutObjectCommandInput } from '@aws-sdk/c
@Injectable()
export class S3Service {
constructor(
- @Inject(DI.config)
- private config: Config,
-
private httpRequestService: HttpRequestService,
) {
}
diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts
index 73d8ec8ffb..934b579059 100644
--- a/packages/backend/src/core/SearchService.ts
+++ b/packages/backend/src/core/SearchService.ts
@@ -8,9 +8,9 @@ import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { bindThis } from '@/decorators.js';
-import { MiNote } from '@/models/entities/Note.js';
-import { MiUser } from '@/models/index.js';
-import type { NotesRepository } from '@/models/index.js';
+import { MiNote } from '@/models/Note.js';
+import { MiUser } from '@/models/_.js';
+import type { NotesRepository } from '@/models/_.js';
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
import { QueryService } from '@/core/QueryService.js';
import { IdService } from '@/core/IdService.js';
@@ -25,6 +25,8 @@ type Q =
{ op: '<', k: K, v: number } |
{ op: '>=', k: K, v: number } |
{ op: '<=', k: K, v: number } |
+ { op: 'is null', k: K} |
+ { op: 'is not null', k: K} |
{ op: 'and', qs: Q[] } |
{ op: 'or', qs: Q[] } |
{ op: 'not', q: Q };
@@ -50,6 +52,8 @@ function compileQuery(q: Q): string {
case '<=': return `(${q.k} <= ${compileValue(q.v)})`;
case 'and': return q.qs.length === 0 ? '' : `(${ q.qs.map(_q => compileQuery(_q)).join(' AND ') })`;
case 'or': return q.qs.length === 0 ? '' : `(${ q.qs.map(_q => compileQuery(_q)).join(' OR ') })`;
+ case 'is null': return `(${q.k} IS NULL)`;
+ case 'is not null': return `(${q.k} IS NOT NULL)`;
case 'not': return `(NOT ${compileQuery(q.q)})`;
default: throw new Error('unrecognized query operator');
}
@@ -127,7 +131,7 @@ export class SearchService {
await this.meilisearchNoteIndex?.addDocuments([{
id: note.id,
- createdAt: note.createdAt.getTime(),
+ createdAt: this.idService.parse(note.id).date.getTime(),
userId: note.userId,
userHost: note.userHost,
channelId: note.channelId,
@@ -170,7 +174,7 @@ export class SearchService {
if (opts.channelId) filter.qs.push({ op: '=', k: 'channelId', v: opts.channelId });
if (opts.host) {
if (opts.host === '.') {
- // TODO: Meilisearchが2023/05/07現在値がNULLかどうかのクエリが書けない
+ filter.qs.push({ op: 'is null', k: 'userHost' });
} else {
filter.qs.push({ op: '=', k: 'userHost', v: opts.host });
}
@@ -204,6 +208,14 @@ export class SearchService {
.leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('renote.user', 'renoteUser');
+ if (opts.host) {
+ if (opts.host === '.') {
+ query.andWhere('user.host IS NULL');
+ } else {
+ query.andWhere('user.host = :host', { host: opts.host });
+ }
+ }
+
this.queryService.generateVisibilityQuery(query, me);
if (me) this.queryService.generateMutedUserQuery(query, me);
if (me) this.queryService.generateBlockedUserQuery(query, me);
diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts
index d9c436744e..9b2fc03d21 100644
--- a/packages/backend/src/core/SignupService.ts
+++ b/packages/backend/src/core/SignupService.ts
@@ -8,13 +8,12 @@ import { Inject, Injectable } from '@nestjs/common';
import bcrypt from 'bcryptjs';
import { DataSource, IsNull } from 'typeorm';
import { DI } from '@/di-symbols.js';
-import type { UsedUsernamesRepository, UsersRepository } from '@/models/index.js';
-import type { Config } from '@/config.js';
-import { MiUser } from '@/models/entities/User.js';
-import { MiUserProfile } from '@/models/entities/UserProfile.js';
+import type { UsedUsernamesRepository, UsersRepository } from '@/models/_.js';
+import { MiUser } from '@/models/User.js';
+import { MiUserProfile } from '@/models/UserProfile.js';
import { IdService } from '@/core/IdService.js';
-import { MiUserKeypair } from '@/models/entities/UserKeypair.js';
-import { MiUsedUsername } from '@/models/entities/UsedUsername.js';
+import { MiUserKeypair } from '@/models/UserKeypair.js';
+import { MiUsedUsername } from '@/models/UsedUsername.js';
import generateUserToken from '@/misc/generate-native-user-token.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
@@ -28,9 +27,6 @@ export class SignupService {
@Inject(DI.db)
private db: DataSource,
- @Inject(DI.config)
- private config: Config,
-
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@@ -124,8 +120,7 @@ export class SignupService {
if (exist) throw new Error(' the username is already used');
account = await transactionalEntityManager.save(new MiUser({
- id: this.idService.genId(),
- createdAt: new Date(),
+ id: this.idService.gen(),
username: username,
usernameLower: username.toLowerCase(),
host: this.utilityService.toPunyNullable(host),
diff --git a/packages/backend/src/core/UserAuthService.ts b/packages/backend/src/core/UserAuthService.ts
new file mode 100644
index 0000000000..d5f93a46d5
--- /dev/null
+++ b/packages/backend/src/core/UserAuthService.ts
@@ -0,0 +1,43 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import * as OTPAuth from 'otpauth';
+import { DI } from '@/di-symbols.js';
+import type { MiUserProfile, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import { bindThis } from '@/decorators.js';
+import { IdentifiableError } from "@/misc/identifiable-error.js";
+
+@Injectable()
+export class UserAuthService {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
+
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
+ ) {
+ }
+
+ @bindThis
+ public async twoFactorAuthenticate(profile: MiUserProfile, token: string): Promise {
+ if (profile.twoFactorBackupSecret?.includes(token)) {
+ await this.userProfilesRepository.update({ userId: profile.userId }, {
+ twoFactorBackupSecret: profile.twoFactorBackupSecret.filter((secret) => secret !== token),
+ });
+ } else {
+ const delta = OTPAuth.TOTP.validate({
+ secret: OTPAuth.Secret.fromBase32(profile.twoFactorSecret!),
+ digits: 6,
+ token,
+ window: 5,
+ });
+
+ if (delta === null) {
+ throw new IdentifiableError('7d0a7d85-206c-4d16-8cf3-8af92249a082', 'authentication failed');
+ }
+ }
+ }
+}
diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts
index 44e2066a11..39b19325c3 100644
--- a/packages/backend/src/core/UserBlockingService.ts
+++ b/packages/backend/src/core/UserBlockingService.ts
@@ -6,12 +6,12 @@
import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { IdService } from '@/core/IdService.js';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiBlocking } from '@/models/entities/Blocking.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiBlocking } from '@/models/Blocking.js';
import { QueueService } from '@/core/QueueService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
-import type { FollowRequestsRepository, BlockingsRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/index.js';
+import type { FollowRequestsRepository, BlockingsRepository, UserListsRepository, UserListMembershipsRepository } from '@/models/_.js';
import Logger from '@/logger.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
@@ -38,8 +38,8 @@ export class UserBlockingService implements OnModuleInit {
@Inject(DI.userListsRepository)
private userListsRepository: UserListsRepository,
- @Inject(DI.userListJoiningsRepository)
- private userListJoiningsRepository: UserListJoiningsRepository,
+ @Inject(DI.userListMembershipsRepository)
+ private userListMembershipsRepository: UserListMembershipsRepository,
private cacheService: CacheService,
private userEntityService: UserEntityService,
@@ -68,8 +68,7 @@ export class UserBlockingService implements OnModuleInit {
]);
const blocking = {
- id: this.idService.genId(),
- createdAt: new Date(),
+ id: this.idService.gen(),
blocker,
blockerId: blocker.id,
blockee,
@@ -149,7 +148,7 @@ export class UserBlockingService implements OnModuleInit {
});
for (const userList of userLists) {
- await this.userListJoiningsRepository.delete({
+ await this.userListMembershipsRepository.delete({
userListId: userList.id,
userId: user.id,
});
diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts
index c99a3e6a11..bd7f298021 100644
--- a/packages/backend/src/core/UserFollowingService.ts
+++ b/packages/backend/src/core/UserFollowingService.ts
@@ -3,10 +3,10 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Inject, Injectable, OnModuleInit, forwardRef } from '@nestjs/common';
+import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { IsNull } from 'typeorm';
-import type { MiLocalUser, MiPartialLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/entities/User.js';
+import type { MiLocalUser, MiPartialLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { QueueService } from '@/core/QueueService.js';
import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
@@ -19,7 +19,7 @@ import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { WebhookService } from '@/core/WebhookService.js';
import { NotificationService } from '@/core/NotificationService.js';
import { DI } from '@/di-symbols.js';
-import type { FollowingsRepository, FollowRequestsRepository, InstancesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
+import type { FollowingsRepository, FollowRequestsRepository, InstancesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { bindThis } from '@/decorators.js';
@@ -28,6 +28,8 @@ import { MetaService } from '@/core/MetaService.js';
import { CacheService } from '@/core/CacheService.js';
import type { Config } from '@/config.js';
import { AccountMoveService } from '@/core/AccountMoveService.js';
+import { UtilityService } from '@/core/UtilityService.js';
+import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
import Logger from '../logger.js';
const logger = new Logger('following/create');
@@ -71,6 +73,7 @@ export class UserFollowingService implements OnModuleInit {
private instancesRepository: InstancesRepository,
private cacheService: CacheService,
+ private utilityService: UtilityService,
private userEntityService: UserEntityService,
private idService: IdService,
private queueService: QueueService,
@@ -81,6 +84,7 @@ export class UserFollowingService implements OnModuleInit {
private webhookService: WebhookService,
private apRendererService: ApRendererService,
private accountMoveService: AccountMoveService,
+ private funoutTimelineService: FunoutTimelineService,
private perUserFollowingChart: PerUserFollowingChart,
private instanceChart: InstanceChart,
) {
@@ -91,7 +95,15 @@ export class UserFollowingService implements OnModuleInit {
}
@bindThis
- public async follow(_follower: { id: MiUser['id'] }, _followee: { id: MiUser['id'] }, requestId?: string, silent = false): Promise {
+ public async follow(
+ _follower: { id: MiUser['id'] },
+ _followee: { id: MiUser['id'] },
+ { requestId, silent = false, withReplies }: {
+ requestId?: string,
+ silent?: boolean,
+ withReplies?: boolean,
+ } = {},
+ ): Promise {
const [follower, followee] = await Promise.all([
this.usersRepository.findOneByOrFail({ id: _follower.id }),
this.usersRepository.findOneByOrFail({ id: _followee.id }),
@@ -118,12 +130,17 @@ export class UserFollowingService implements OnModuleInit {
}
const followeeProfile = await this.userProfilesRepository.findOneByOrFail({ userId: followee.id });
-
// フォロー対象が鍵アカウントである or
// フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or
- // フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである
+ // フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである or
+ // フォロワーがローカルユーザーであり、フォロー対象がサイレンスされているサーバーである
// 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく
- if (followee.isLocked || (followeeProfile.carefulBot && follower.isBot) || (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee))) {
+ if (
+ followee.isLocked ||
+ (followeeProfile.carefulBot && follower.isBot) ||
+ (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee) && process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING !== 'true') ||
+ (this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower) && this.utilityService.isSilencedHost((await this.metaService.fetch()).silencedHosts, follower.host))
+ ) {
let autoAccept = false;
// 鍵アカウントであっても、既にフォローされていた場合はスルー
@@ -164,12 +181,12 @@ export class UserFollowingService implements OnModuleInit {
}
if (!autoAccept) {
- await this.createFollowRequest(follower, followee, requestId);
+ await this.createFollowRequest(follower, followee, requestId, withReplies);
return;
}
}
- await this.insertFollowingDoc(followee, follower, silent);
+ await this.insertFollowingDoc(followee, follower, silent, withReplies);
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, requestId), followee));
@@ -186,16 +203,17 @@ export class UserFollowingService implements OnModuleInit {
id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox']
},
silent = false,
+ withReplies?: boolean,
): Promise {
if (follower.id === followee.id) return;
let alreadyFollowed = false as boolean;
await this.followingsRepository.insert({
- id: this.idService.genId(),
- createdAt: new Date(),
+ id: this.idService.gen(),
followerId: follower.id,
followeeId: followee.id,
+ withReplies: withReplies,
// 非正規化
followerHost: follower.host,
@@ -230,8 +248,7 @@ export class UserFollowingService implements OnModuleInit {
// 通知を作成
this.notificationService.createNotification(follower.id, 'followRequestAccepted', {
- notifierId: followee.id,
- });
+ }, followee.id);
}
if (alreadyFollowed) return;
@@ -273,8 +290,8 @@ export class UserFollowingService implements OnModuleInit {
this.perUserFollowingChart.update(follower, followee, true);
}
- // Publish follow event
if (this.userEntityService.isLocalUser(follower) && !silent) {
+ // Publish follow event
this.userEntityService.pack(followee.id, follower, {
detail: true,
}).then(async packed => {
@@ -287,6 +304,8 @@ export class UserFollowingService implements OnModuleInit {
});
}
});
+
+ this.funoutTimelineService.purge(`homeTimeline:${follower.id}`);
}
// Publish followed event
@@ -304,8 +323,7 @@ export class UserFollowingService implements OnModuleInit {
// 通知を作成
this.notificationService.createNotification(followee.id, 'follow', {
- notifierId: follower.id,
- });
+ }, follower.id);
}
}
@@ -341,8 +359,8 @@ export class UserFollowingService implements OnModuleInit {
this.decrementFollowing(following.follower, following.followee);
- // Publish unfollow event
if (!silent && this.userEntityService.isLocalUser(follower)) {
+ // Publish unfollow event
this.userEntityService.pack(followee.id, follower, {
detail: true,
}).then(async packed => {
@@ -355,6 +373,8 @@ export class UserFollowingService implements OnModuleInit {
});
}
});
+
+ this.funoutTimelineService.purge(`homeTimeline:${follower.id}`);
}
if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
@@ -450,6 +470,7 @@ export class UserFollowingService implements OnModuleInit {
id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox'];
},
requestId?: string,
+ withReplies?: boolean,
): Promise {
if (follower.id === followee.id) return;
@@ -463,11 +484,11 @@ export class UserFollowingService implements OnModuleInit {
if (blocked) throw new Error('blocked');
const followRequest = await this.followRequestsRepository.insert({
- id: this.idService.genId(),
- createdAt: new Date(),
+ id: this.idService.gen(),
followerId: follower.id,
followeeId: followee.id,
requestId,
+ withReplies,
// 非正規化
followerHost: follower.host,
@@ -488,9 +509,7 @@ export class UserFollowingService implements OnModuleInit {
// 通知を作成
this.notificationService.createNotification(followee.id, 'receiveFollowRequest', {
- notifierId: follower.id,
- followRequestId: followRequest.id,
- });
+ }, follower.id);
}
if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
@@ -553,7 +572,7 @@ export class UserFollowingService implements OnModuleInit {
throw new IdentifiableError('8884c2dd-5795-4ac9-b27e-6a01d38190f9', 'No follow request.');
}
- await this.insertFollowingDoc(followee, follower);
+ await this.insertFollowingDoc(followee, follower, false, request.withReplies);
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee as MiPartialLocalUser, request.requestId!), followee));
@@ -693,4 +712,12 @@ export class UserFollowingService implements OnModuleInit {
});
}
}
+
+ @bindThis
+ public getFollowees(userId: MiUser['id']) {
+ return this.followingsRepository.createQueryBuilder('following')
+ .select('following.followeeId')
+ .where('following.followerId = :followerId', { followerId: userId })
+ .getMany();
+ }
}
diff --git a/packages/backend/src/core/UserKeypairService.ts b/packages/backend/src/core/UserKeypairService.ts
index f321727601..425a97f3f1 100644
--- a/packages/backend/src/core/UserKeypairService.ts
+++ b/packages/backend/src/core/UserKeypairService.ts
@@ -5,10 +5,10 @@
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import * as Redis from 'ioredis';
-import type { MiUser } from '@/models/entities/User.js';
-import type { UserKeypairsRepository } from '@/models/index.js';
+import type { MiUser } from '@/models/User.js';
+import type { UserKeypairsRepository } from '@/models/_.js';
import { RedisKVCache } from '@/misc/cache.js';
-import type { MiUserKeypair } from '@/models/entities/UserKeypair.js';
+import type { MiUserKeypair } from '@/models/UserKeypair.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts
index e10608d803..eae6b6fbc3 100644
--- a/packages/backend/src/core/UserListService.ts
+++ b/packages/backend/src/core/UserListService.ts
@@ -3,13 +3,13 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Inject, Injectable } from '@nestjs/common';
-import type { UserListJoiningsRepository, UsersRepository } from '@/models/index.js';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiUserList } from '@/models/entities/UserList.js';
-import type { MiUserListJoining } from '@/models/entities/UserListJoining.js';
+import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
+import * as Redis from 'ioredis';
+import type { UserListMembershipsRepository } from '@/models/_.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiUserList } from '@/models/UserList.js';
+import type { MiUserListMembership } from '@/models/UserListMembership.js';
import { IdService } from '@/core/IdService.js';
-import { UserFollowingService } from '@/core/UserFollowingService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
@@ -17,44 +17,87 @@ import { ProxyAccountService } from '@/core/ProxyAccountService.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import { QueueService } from '@/core/QueueService.js';
+import { RedisKVCache } from '@/misc/cache.js';
+import type { GlobalEvents } from '@/core/GlobalEventService.js';
@Injectable()
-export class UserListService {
+export class UserListService implements OnApplicationShutdown {
public static TooManyUsersError = class extends Error {};
- constructor(
- @Inject(DI.usersRepository)
- private usersRepository: UsersRepository,
+ public membersCache: RedisKVCache>;
- @Inject(DI.userListJoiningsRepository)
- private userListJoiningsRepository: UserListJoiningsRepository,
+ constructor(
+ @Inject(DI.redis)
+ private redisClient: Redis.Redis,
+
+ @Inject(DI.redisForSub)
+ private redisForSub: Redis.Redis,
+
+ @Inject(DI.userListMembershipsRepository)
+ private userListMembershipsRepository: UserListMembershipsRepository,
private userEntityService: UserEntityService,
private idService: IdService,
- private userFollowingService: UserFollowingService,
private roleService: RoleService,
private globalEventService: GlobalEventService,
private proxyAccountService: ProxyAccountService,
private queueService: QueueService,
) {
+ this.membersCache = new RedisKVCache>(this.redisClient, 'userListMembers', {
+ lifetime: 1000 * 60 * 30, // 30m
+ memoryCacheLifetime: 1000 * 60, // 1m
+ fetcher: (key) => this.userListMembershipsRepository.find({ where: { userListId: key }, select: ['userId'] }).then(xs => new Set(xs.map(x => x.userId))),
+ toRedisConverter: (value) => JSON.stringify(Array.from(value)),
+ fromRedisConverter: (value) => new Set(JSON.parse(value)),
+ });
+
+ this.redisForSub.on('message', this.onMessage);
}
@bindThis
- public async push(target: MiUser, list: MiUserList, me: MiUser) {
- const currentCount = await this.userListJoiningsRepository.countBy({
+ private async onMessage(_: string, data: string): Promise {
+ const obj = JSON.parse(data);
+
+ if (obj.channel === 'internal') {
+ const { type, body } = obj.message as GlobalEvents['internal']['payload'];
+ switch (type) {
+ case 'userListMemberAdded': {
+ const { userListId, memberId } = body;
+ const members = await this.membersCache.get(userListId);
+ if (members) {
+ members.add(memberId);
+ }
+ break;
+ }
+ case 'userListMemberRemoved': {
+ const { userListId, memberId } = body;
+ const members = await this.membersCache.get(userListId);
+ if (members) {
+ members.delete(memberId);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+
+ @bindThis
+ public async addMember(target: MiUser, list: MiUserList, me: MiUser) {
+ const currentCount = await this.userListMembershipsRepository.countBy({
userListId: list.id,
});
if (currentCount > (await this.roleService.getUserPolicies(me.id)).userEachUserListsLimit) {
throw new UserListService.TooManyUsersError();
}
- await this.userListJoiningsRepository.insert({
- id: this.idService.genId(),
- createdAt: new Date(),
+ await this.userListMembershipsRepository.insert({
+ id: this.idService.gen(),
userId: target.id,
userListId: list.id,
userListUserId: list.userId,
- } as MiUserListJoining);
+ } as MiUserListMembership);
this.globalEventService.publishUserListStream(list.id, 'userAdded', await this.userEntityService.pack(target, me));
@@ -66,4 +109,44 @@ export class UserListService {
}
}
}
+
+ @bindThis
+ public async removeMember(target: MiUser, list: MiUserList) {
+ await this.userListMembershipsRepository.delete({
+ userId: target.id,
+ userListId: list.id,
+ });
+
+ this.globalEventService.publishInternalEvent('userListMemberRemoved', { userListId: list.id, memberId: target.id });
+ this.globalEventService.publishUserListStream(list.id, 'userRemoved', await this.userEntityService.pack(target, null));
+ }
+
+ @bindThis
+ public async updateMembership(target: MiUser, list: MiUserList, options: { withReplies?: boolean }) {
+ const membership = await this.userListMembershipsRepository.findOneBy({
+ userId: target.id,
+ userListId: list.id,
+ });
+
+ if (membership == null) {
+ throw new Error('User is not a member of the list');
+ }
+
+ await this.userListMembershipsRepository.update({
+ id: membership.id,
+ }, {
+ withReplies: options.withReplies,
+ });
+ }
+
+ @bindThis
+ public dispose(): void {
+ this.redisForSub.off('message', this.onMessage);
+ this.membersCache.dispose();
+ }
+
+ @bindThis
+ public onApplicationShutdown(signal?: string | undefined): void {
+ this.dispose();
+ }
}
diff --git a/packages/backend/src/core/UserMutingService.ts b/packages/backend/src/core/UserMutingService.ts
index 1f0b2c215b..397e6bdd5d 100644
--- a/packages/backend/src/core/UserMutingService.ts
+++ b/packages/backend/src/core/UserMutingService.ts
@@ -5,9 +5,9 @@
import { Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
-import type { MutingsRepository, MiMuting } from '@/models/index.js';
+import type { MutingsRepository, MiMuting } from '@/models/_.js';
import { IdService } from '@/core/IdService.js';
-import type { MiUser } from '@/models/entities/User.js';
+import type { MiUser } from '@/models/User.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { CacheService } from '@/core/CacheService.js';
@@ -26,8 +26,7 @@ export class UserMutingService {
@bindThis
public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null): Promise {
await this.mutingsRepository.insert({
- id: this.idService.genId(),
- createdAt: new Date(),
+ id: this.idService.gen(),
expiresAt: expiresAt ?? null,
muterId: user.id,
muteeId: target.id,
diff --git a/packages/backend/src/core/UserService.ts b/packages/backend/src/core/UserService.ts
new file mode 100644
index 0000000000..d16e1be615
--- /dev/null
+++ b/packages/backend/src/core/UserService.ts
@@ -0,0 +1,53 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import type { FollowingsRepository, UsersRepository } from '@/models/_.js';
+import type { MiUser } from '@/models/User.js';
+import { DI } from '@/di-symbols.js';
+import { bindThis } from '@/decorators.js';
+
+@Injectable()
+export class UserService {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
+
+ @Inject(DI.followingsRepository)
+ private followingsRepository: FollowingsRepository,
+ ) {
+ }
+
+ @bindThis
+ public async updateLastActiveDate(user: MiUser): Promise {
+ if (user.isHibernated) {
+ const result = await this.usersRepository.createQueryBuilder().update()
+ .set({
+ lastActiveDate: new Date(),
+ })
+ .where('id = :id', { id: user.id })
+ .returning('*')
+ .execute()
+ .then((response) => {
+ return response.raw[0];
+ });
+ const wokeUp = result.isHibernated;
+ if (wokeUp) {
+ this.usersRepository.update(user.id, {
+ isHibernated: false,
+ });
+ this.followingsRepository.update({
+ followerId: user.id,
+ }, {
+ isFollowerHibernated: false,
+ });
+ }
+ } else {
+ this.usersRepository.update(user.id, {
+ lastActiveDate: new Date(),
+ });
+ }
+ }
+}
diff --git a/packages/backend/src/core/UserSuspendService.ts b/packages/backend/src/core/UserSuspendService.ts
index 645be624c4..8940a142d1 100644
--- a/packages/backend/src/core/UserSuspendService.ts
+++ b/packages/backend/src/core/UserSuspendService.ts
@@ -5,12 +5,11 @@
import { Inject, Injectable } from '@nestjs/common';
import { Not, IsNull } from 'typeorm';
-import type { FollowingsRepository, UsersRepository } from '@/models/index.js';
-import type { MiUser } from '@/models/entities/User.js';
+import type { FollowingsRepository } from '@/models/_.js';
+import type { MiUser } from '@/models/User.js';
import { QueueService } from '@/core/QueueService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
-import type { Config } from '@/config.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
@@ -18,12 +17,6 @@ import { bindThis } from '@/decorators.js';
@Injectable()
export class UserSuspendService {
constructor(
- @Inject(DI.config)
- private config: Config,
-
- @Inject(DI.usersRepository)
- private usersRepository: UsersRepository,
-
@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,
diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts
index d2d2776bd2..b95e41167b 100644
--- a/packages/backend/src/core/UtilityService.ts
+++ b/packages/backend/src/core/UtilityService.ts
@@ -35,6 +35,12 @@ export class UtilityService {
return blockedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`));
}
+ @bindThis
+ public isSilencedHost(silencedHosts: string[] | undefined, host: string | null): boolean {
+ if (!silencedHosts || host == null) return false;
+ return silencedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`));
+ }
+
@bindThis
public extractDbHost(uri: string): string {
const url = new URL(uri);
diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts
index 9f548dd07f..7b2cf8467a 100644
--- a/packages/backend/src/core/WebAuthnService.ts
+++ b/packages/backend/src/core/WebAuthnService.ts
@@ -13,11 +13,11 @@ import {
} from '@simplewebauthn/server';
import { AttestationFormat, isoCBOR } from '@simplewebauthn/server/helpers';
import { DI } from '@/di-symbols.js';
-import type { UserSecurityKeysRepository } from '@/models/index.js';
+import type { UserSecurityKeysRepository } from '@/models/_.js';
import type { Config } from '@/config.js';
import { bindThis } from '@/decorators.js';
import { MetaService } from '@/core/MetaService.js';
-import { MiUser } from '@/models/index.js';
+import { MiUser } from '@/models/_.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import type {
AuthenticationResponseJSON,
@@ -34,8 +34,10 @@ export class WebAuthnService {
constructor(
@Inject(DI.redis)
private redisClient: Redis.Redis,
+
@Inject(DI.config)
private config: Config,
+
@Inject(DI.userSecurityKeysRepository)
private userSecurityKeysRepository: UserSecurityKeysRepository,
diff --git a/packages/backend/src/core/WebfingerService.ts b/packages/backend/src/core/WebfingerService.ts
index e91ba29228..3d5747aebd 100644
--- a/packages/backend/src/core/WebfingerService.ts
+++ b/packages/backend/src/core/WebfingerService.ts
@@ -4,9 +4,7 @@
*/
import { URL } from 'node:url';
-import { Inject, Injectable } from '@nestjs/common';
-import { DI } from '@/di-symbols.js';
-import type { Config } from '@/config.js';
+import { Injectable } from '@nestjs/common';
import { query as urlQuery } from '@/misc/prelude/url.js';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import { bindThis } from '@/decorators.js';
@@ -27,9 +25,6 @@ const mRegex = /^([^@]+)@(.*)/;
@Injectable()
export class WebfingerService {
constructor(
- @Inject(DI.config)
- private config: Config,
-
private httpRequestService: HttpRequestService,
) {
}
diff --git a/packages/backend/src/core/WebhookService.ts b/packages/backend/src/core/WebhookService.ts
index 3b43c1224c..ff70f7bc0c 100644
--- a/packages/backend/src/core/WebhookService.ts
+++ b/packages/backend/src/core/WebhookService.ts
@@ -5,11 +5,11 @@
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
-import type { WebhooksRepository } from '@/models/index.js';
-import type { MiWebhook } from '@/models/entities/Webhook.js';
+import type { WebhooksRepository } from '@/models/_.js';
+import type { MiWebhook } from '@/models/Webhook.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
-import { StreamMessages } from '@/server/api/stream/types.js';
+import type { GlobalEvents } from '@/core/GlobalEventService.js';
import type { OnApplicationShutdown } from '@nestjs/common';
@Injectable()
@@ -45,7 +45,7 @@ export class WebhookService implements OnApplicationShutdown {
const obj = JSON.parse(data);
if (obj.channel === 'internal') {
- const { type, body } = obj.message as StreamMessages['internal']['payload'];
+ const { type, body } = obj.message as GlobalEvents['internal']['payload'];
switch (type) {
case 'webhookCreated':
if (body.active) {
diff --git a/packages/backend/src/core/activitypub/ApAudienceService.ts b/packages/backend/src/core/activitypub/ApAudienceService.ts
index f3432f8334..440852bdf3 100644
--- a/packages/backend/src/core/activitypub/ApAudienceService.ts
+++ b/packages/backend/src/core/activitypub/ApAudienceService.ts
@@ -5,7 +5,7 @@
import { Injectable } from '@nestjs/common';
import promiseLimit from 'promise-limit';
-import type { MiRemoteUser, MiUser } from '@/models/entities/User.js';
+import type { MiRemoteUser, MiUser } from '@/models/User.js';
import { concat, unique } from '@/misc/prelude/array.js';
import { bindThis } from '@/decorators.js';
import { getApIds } from './type.js';
diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts
index b691588320..995c5dcd5f 100644
--- a/packages/backend/src/core/activitypub/ApDbResolverService.ts
+++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts
@@ -5,14 +5,14 @@
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js';
+import type { NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/_.js';
import type { Config } from '@/config.js';
import { MemoryKVCache } from '@/misc/cache.js';
-import type { MiUserPublickey } from '@/models/entities/UserPublickey.js';
+import type { MiUserPublickey } from '@/models/UserPublickey.js';
import { CacheService } from '@/core/CacheService.js';
-import type { MiNote } from '@/models/entities/Note.js';
+import type { MiNote } from '@/models/Note.js';
import { bindThis } from '@/decorators.js';
-import { MiLocalUser, MiRemoteUser } from '@/models/entities/User.js';
+import { MiLocalUser, MiRemoteUser } from '@/models/User.js';
import { getApId } from './type.js';
import { ApPersonService } from './models/ApPersonService.js';
import type { IObject } from './type.js';
diff --git a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts
index 0da164a874..81003bcf1c 100644
--- a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts
+++ b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts
@@ -6,9 +6,8 @@
import { Inject, Injectable } from '@nestjs/common';
import { IsNull, Not } from 'typeorm';
import { DI } from '@/di-symbols.js';
-import type { FollowingsRepository, UsersRepository } from '@/models/index.js';
-import type { Config } from '@/config.js';
-import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/entities/User.js';
+import type { FollowingsRepository } from '@/models/_.js';
+import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/User.js';
import { QueueService } from '@/core/QueueService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
@@ -152,12 +151,6 @@ class DeliverManager {
@Injectable()
export class ApDeliverManagerService {
constructor(
- @Inject(DI.config)
- private config: Config,
-
- @Inject(DI.usersRepository)
- private usersRepository: UsersRepository,
-
@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,
diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts
index 76a00b8012..098907b93e 100644
--- a/packages/backend/src/core/activitypub/ApInboxService.ts
+++ b/packages/backend/src/core/activitypub/ApInboxService.ts
@@ -18,17 +18,15 @@ import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js';
import { AppLockService } from '@/core/AppLockService.js';
import type Logger from '@/logger.js';
import { MetaService } from '@/core/MetaService.js';
-import { AccountMoveService } from '@/core/AccountMoveService.js';
import { IdService } from '@/core/IdService.js';
import { StatusError } from '@/misc/status-error.js';
import { UtilityService } from '@/core/UtilityService.js';
-import { CacheService } from '@/core/CacheService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { QueueService } from '@/core/QueueService.js';
-import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/index.js';
+import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
-import type { MiRemoteUser } from '@/models/entities/User.js';
+import type { MiRemoteUser } from '@/models/User.js';
import { getApHrefNullable, getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
import { ApNoteService } from './models/ApNoteService.js';
import { ApLoggerService } from './ApLoggerService.js';
@@ -83,8 +81,6 @@ export class ApInboxService {
private apNoteService: ApNoteService,
private apPersonService: ApPersonService,
private apQuestionService: ApQuestionService,
- private accountMoveService: AccountMoveService,
- private cacheService: CacheService,
private queueService: QueueService,
) {
this.logger = this.apLoggerService.logger;
@@ -168,7 +164,7 @@ export class ApInboxService {
}
// don't queue because the sender may attempt again when timeout
- await this.userFollowingService.follow(actor, followee, activity.id);
+ await this.userFollowingService.follow(actor, followee, { requestId: activity.id });
return 'ok';
}
@@ -518,8 +514,7 @@ export class ApInboxService {
if (users.length < 1) return 'skip';
const report = await this.abuseUserReportsRepository.insert({
- id: this.idService.genId(),
- createdAt: new Date(),
+ id: this.idService.gen(),
targetUserId: users[0].id,
targetUserHost: users[0].host,
reporterId: actor.id,
diff --git a/packages/backend/src/core/activitypub/ApMfmService.ts b/packages/backend/src/core/activitypub/ApMfmService.ts
index fd83362243..60868627a2 100644
--- a/packages/backend/src/core/activitypub/ApMfmService.ts
+++ b/packages/backend/src/core/activitypub/ApMfmService.ts
@@ -3,12 +3,10 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Inject, Injectable } from '@nestjs/common';
+import { Injectable } from '@nestjs/common';
import * as mfm from 'mfm-js';
-import { DI } from '@/di-symbols.js';
-import type { Config } from '@/config.js';
import { MfmService } from '@/core/MfmService.js';
-import type { MiNote } from '@/models/entities/Note.js';
+import type { MiNote } from '@/models/Note.js';
import { bindThis } from '@/decorators.js';
import { extractApHashtagObjects } from './models/tag.js';
import type { IObject } from './type.js';
@@ -16,9 +14,6 @@ import type { IObject } from './type.js';
@Injectable()
export class ApMfmService {
constructor(
- @Inject(DI.config)
- private config: Config,
-
private mfmService: MfmService,
) {
}
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index 928144a12b..1891c341e4 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -9,24 +9,25 @@ import { In } from 'typeorm';
import * as mfm from 'mfm-js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
-import type { MiPartialLocalUser, MiLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/entities/User.js';
-import type { IMentionedRemoteUsers, MiNote } from '@/models/entities/Note.js';
-import type { MiBlocking } from '@/models/entities/Blocking.js';
-import type { MiRelay } from '@/models/entities/Relay.js';
-import type { MiDriveFile } from '@/models/entities/DriveFile.js';
-import type { MiNoteReaction } from '@/models/entities/NoteReaction.js';
-import type { MiEmoji } from '@/models/entities/Emoji.js';
-import type { MiPoll } from '@/models/entities/Poll.js';
-import type { MiPollVote } from '@/models/entities/PollVote.js';
+import type { MiPartialLocalUser, MiLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js';
+import type { IMentionedRemoteUsers, MiNote } from '@/models/Note.js';
+import type { MiBlocking } from '@/models/Blocking.js';
+import type { MiRelay } from '@/models/Relay.js';
+import type { MiDriveFile } from '@/models/DriveFile.js';
+import type { MiNoteReaction } from '@/models/NoteReaction.js';
+import type { MiEmoji } from '@/models/Emoji.js';
+import type { MiPoll } from '@/models/Poll.js';
+import type { MiPollVote } from '@/models/PollVote.js';
import { UserKeypairService } from '@/core/UserKeypairService.js';
import { MfmService } from '@/core/MfmService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
-import type { MiUserKeypair } from '@/models/entities/UserKeypair.js';
-import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, EmojisRepository, PollsRepository } from '@/models/index.js';
+import type { MiUserKeypair } from '@/models/UserKeypair.js';
+import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { isNotNull } from '@/misc/is-not-null.js';
+import { IdService } from '@/core/IdService.js';
import { LdSignatureService } from './LdSignatureService.js';
import { ApMfmService } from './ApMfmService.js';
import type { IAccept, IActivity, IAdd, IAnnounce, IApDocument, IApEmoji, IApHashtag, IApImage, IApMention, IBlock, ICreate, IDelete, IFlag, IFollow, IKey, ILike, IMove, IObject, IPost, IQuestion, IReject, IRemove, ITombstone, IUndo, IUpdate } from './type.js';
@@ -49,9 +50,6 @@ export class ApRendererService {
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
- @Inject(DI.emojisRepository)
- private emojisRepository: EmojisRepository,
-
@Inject(DI.pollsRepository)
private pollsRepository: PollsRepository,
@@ -62,6 +60,7 @@ export class ApRendererService {
private userKeypairService: UserKeypairService,
private apMfmService: ApMfmService,
private mfmService: MfmService,
+ private idService: IdService,
) {
}
@@ -108,7 +107,7 @@ export class ApRendererService {
id: `${this.config.url}/notes/${note.id}/activity`,
actor: this.userEntityService.genLocalUserUri(note.userId),
type: 'Announce',
- published: note.createdAt.toISOString(),
+ published: this.idService.parse(note.id).date.toISOString(),
to,
cc,
object,
@@ -140,7 +139,7 @@ export class ApRendererService {
id: `${this.config.url}/notes/${note.id}/activity`,
actor: this.userEntityService.genLocalUserUri(note.userId),
type: 'Create',
- published: note.createdAt.toISOString(),
+ published: this.idService.parse(note.id).date.toISOString(),
object,
};
@@ -440,7 +439,7 @@ export class ApRendererService {
},
_misskey_quote: quote,
quoteUrl: quote,
- published: note.createdAt.toISOString(),
+ published: this.idService.parse(note.id).date.toISOString(),
to,
cc,
inReplyTo,
@@ -465,7 +464,7 @@ export class ApRendererService {
const attachment = profile.fields.map(field => ({
type: 'PropertyValue',
name: field.name,
- value: /^https?:/.test(field.value)
+ value: (field.value.startsWith('http://') || field.value.startsWith('https://'))
? `${new URL(field.value).href}`
: field.value,
}));
diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts
index fb44a55de2..b59ce5241f 100644
--- a/packages/backend/src/core/activitypub/ApRequestService.ts
+++ b/packages/backend/src/core/activitypub/ApRequestService.ts
@@ -8,7 +8,7 @@ import { URL } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
-import type { MiUser } from '@/models/entities/User.js';
+import type { MiUser } from '@/models/User.js';
import { UserKeypairService } from '@/core/UserKeypairService.js';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import { LoggerService } from '@/core/LoggerService.js';
diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts
index 34913fabe8..9ca63c9ec5 100644
--- a/packages/backend/src/core/activitypub/ApResolverService.ts
+++ b/packages/backend/src/core/activitypub/ApResolverService.ts
@@ -4,9 +4,10 @@
*/
import { Inject, Injectable } from '@nestjs/common';
-import type { MiLocalUser, MiRemoteUser } from '@/models/entities/User.js';
+import { IsNull, Not } from 'typeorm';
+import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
import { InstanceActorService } from '@/core/InstanceActorService.js';
-import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/index.js';
+import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository, FollowRequestsRepository } from '@/models/_.js';
import type { Config } from '@/config.js';
import { MetaService } from '@/core/MetaService.js';
import { HttpRequestService } from '@/core/HttpRequestService.js';
@@ -32,6 +33,7 @@ export class Resolver {
private notesRepository: NotesRepository,
private pollsRepository: PollsRepository,
private noteReactionsRepository: NoteReactionsRepository,
+ private followRequestsRepository: FollowRequestsRepository,
private utilityService: UtilityService,
private instanceActorService: InstanceActorService,
private metaService: MetaService,
@@ -146,13 +148,24 @@ export class Resolver {
return this.noteReactionsRepository.findOneByOrFail({ id: parsed.id }).then(async reaction =>
this.apRendererService.addContext(await this.apRendererService.renderLike(reaction, { uri: null })));
case 'follows':
- // rest should be
- if (parsed.rest == null || !/^\w+$/.test(parsed.rest)) throw new Error('resolveLocal: invalid follow URI');
-
- return Promise.all(
- [parsed.id, parsed.rest].map(id => this.usersRepository.findOneByOrFail({ id })),
- )
- .then(([follower, followee]) => this.apRendererService.addContext(this.apRendererService.renderFollow(follower as MiLocalUser | MiRemoteUser, followee as MiLocalUser | MiRemoteUser, url)));
+ return this.followRequestsRepository.findOneBy({ id: parsed.id })
+ .then(async followRequest => {
+ if (followRequest == null) throw new Error('resolveLocal: invalid follow request ID');
+ const [follower, followee] = await Promise.all([
+ this.usersRepository.findOneBy({
+ id: followRequest.followerId,
+ host: IsNull(),
+ }),
+ this.usersRepository.findOneBy({
+ id: followRequest.followeeId,
+ host: Not(IsNull()),
+ }),
+ ]);
+ if (follower == null || followee == null) {
+ throw new Error('resolveLocal: follower or followee does not exist');
+ }
+ return this.apRendererService.addContext(this.apRendererService.renderFollow(follower as MiLocalUser | MiRemoteUser, followee as MiLocalUser | MiRemoteUser, url));
+ });
default:
throw new Error(`resolveLocal: type ${parsed.type} unhandled`);
}
@@ -177,6 +190,9 @@ export class ApResolverService {
@Inject(DI.noteReactionsRepository)
private noteReactionsRepository: NoteReactionsRepository,
+ @Inject(DI.followRequestsRepository)
+ private followRequestsRepository: FollowRequestsRepository,
+
private utilityService: UtilityService,
private instanceActorService: InstanceActorService,
private metaService: MetaService,
@@ -196,6 +212,7 @@ export class ApResolverService {
this.notesRepository,
this.pollsRepository,
this.noteReactionsRepository,
+ this.followRequestsRepository,
this.utilityService,
this.instanceActorService,
this.metaService,
diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts
index fa17a778bf..a4cd533892 100644
--- a/packages/backend/src/core/activitypub/models/ApImageService.ts
+++ b/packages/backend/src/core/activitypub/models/ApImageService.ts
@@ -5,9 +5,9 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { DriveFilesRepository } from '@/models/index.js';
-import type { MiRemoteUser } from '@/models/entities/User.js';
-import type { MiDriveFile } from '@/models/entities/DriveFile.js';
+import type { DriveFilesRepository } from '@/models/_.js';
+import type { MiRemoteUser } from '@/models/User.js';
+import type { MiDriveFile } from '@/models/DriveFile.js';
import { MetaService } from '@/core/MetaService.js';
import { truncate } from '@/misc/truncate.js';
import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js';
diff --git a/packages/backend/src/core/activitypub/models/ApMentionService.ts b/packages/backend/src/core/activitypub/models/ApMentionService.ts
index 443c21cdd1..9aa8ba5ede 100644
--- a/packages/backend/src/core/activitypub/models/ApMentionService.ts
+++ b/packages/backend/src/core/activitypub/models/ApMentionService.ts
@@ -3,25 +3,19 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Inject, Injectable } from '@nestjs/common';
+import { Injectable } from '@nestjs/common';
import promiseLimit from 'promise-limit';
-import { DI } from '@/di-symbols.js';
-import type { MiUser } from '@/models/index.js';
-import type { Config } from '@/config.js';
+import type { MiUser } from '@/models/_.js';
import { toArray, unique } from '@/misc/prelude/array.js';
import { bindThis } from '@/decorators.js';
import { isMention } from '../type.js';
-import { ApResolverService, Resolver } from '../ApResolverService.js';
+import { Resolver } from '../ApResolverService.js';
import { ApPersonService } from './ApPersonService.js';
import type { IObject, IApMention } from '../type.js';
@Injectable()
export class ApMentionService {
constructor(
- @Inject(DI.config)
- private config: Config,
-
- private apResolverService: ApResolverService,
private apPersonService: ApPersonService,
) {
}
diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts
index a5b300a1d2..1979cdda9c 100644
--- a/packages/backend/src/core/activitypub/models/ApNoteService.ts
+++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts
@@ -7,15 +7,15 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common';
import promiseLimit from 'promise-limit';
import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
-import type { PollsRepository, EmojisRepository } from '@/models/index.js';
+import type { PollsRepository, EmojisRepository } from '@/models/_.js';
import type { Config } from '@/config.js';
-import type { MiRemoteUser } from '@/models/entities/User.js';
-import type { MiNote } from '@/models/entities/Note.js';
+import type { MiRemoteUser } from '@/models/User.js';
+import type { MiNote } from '@/models/Note.js';
import { toArray, toSingle, unique } from '@/misc/prelude/array.js';
-import type { MiEmoji } from '@/models/entities/Emoji.js';
+import type { MiEmoji } from '@/models/Emoji.js';
import { MetaService } from '@/core/MetaService.js';
import { AppLockService } from '@/core/AppLockService.js';
-import type { MiDriveFile } from '@/models/entities/DriveFile.js';
+import type { MiDriveFile } from '@/models/DriveFile.js';
import { NoteCreateService } from '@/core/NoteCreateService.js';
import type Logger from '@/logger.js';
import { IdService } from '@/core/IdService.js';
@@ -131,13 +131,13 @@ export class ApNoteService {
this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`);
if (note.id && !checkHttps(note.id)) {
- throw new Error('unexpected shcema of note.id: ' + note.id);
+ throw new Error('unexpected schema of note.id: ' + note.id);
}
const url = getOneApHrefNullable(note.url);
if (url && !checkHttps(url)) {
- throw new Error('unexpected shcema of note url: ' + url);
+ throw new Error('unexpected schema of note url: ' + url);
}
this.logger.info(`Creating the Note: ${note.id}`);
@@ -200,7 +200,7 @@ export class ApNoteService {
// 引用
let quote: MiNote | undefined | null = null;
- if (note._misskey_quote || note.quoteUrl) {
+ if (note._misskey_quote ?? note.quoteUrl) {
const tryResolveNote = async (uri: string): Promise<
| { status: 'ok'; res: MiNote }
| { status: 'permerror' | 'temperror' }
@@ -271,24 +271,36 @@ export class ApNoteService {
const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined);
- return await this.noteCreateService.create(actor, {
- createdAt: note.published ? new Date(note.published) : null,
- files,
- reply,
- renote: quote,
- name: note.name,
- cw,
- text,
- localOnly: false,
- visibility,
- visibleUsers,
- apMentions,
- apHashtags,
- apEmojis,
- poll,
- uri: note.id,
- url: url,
- }, silent);
+ try {
+ return await this.noteCreateService.create(actor, {
+ createdAt: note.published ? new Date(note.published) : null,
+ files,
+ reply,
+ renote: quote,
+ name: note.name,
+ cw,
+ text,
+ localOnly: false,
+ visibility,
+ visibleUsers,
+ apMentions,
+ apHashtags,
+ apEmojis,
+ poll,
+ uri: note.id,
+ url: url,
+ }, silent);
+ } catch (err: any) {
+ if (err.name !== 'duplicated') {
+ throw err;
+ }
+ this.logger.info('The note is already inserted while creating itself, reading again');
+ const duplicate = await this.fetchNote(value);
+ if (!duplicate) {
+ throw new Error('The note creation failed with duplication error even when there is no duplication');
+ }
+ return duplicate;
+ }
}
/**
@@ -374,7 +386,7 @@ export class ApNoteService {
this.logger.info(`register emoji host=${host}, name=${name}`);
return await this.emojisRepository.insert({
- id: this.idService.genId(),
+ id: this.idService.gen(),
host,
name,
uri: tag.id,
diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts
index bc8c0a85ba..2c11f4c07f 100644
--- a/packages/backend/src/core/activitypub/models/ApPersonService.ts
+++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts
@@ -8,28 +8,28 @@ import promiseLimit from 'promise-limit';
import { DataSource } from 'typeorm';
import { ModuleRef } from '@nestjs/core';
import { DI } from '@/di-symbols.js';
-import type { FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js';
+import type { FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/_.js';
import type { Config } from '@/config.js';
-import type { MiLocalUser, MiRemoteUser } from '@/models/entities/User.js';
-import { MiUser } from '@/models/entities/User.js';
+import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
+import { MiUser } from '@/models/User.js';
import { truncate } from '@/misc/truncate.js';
import type { CacheService } from '@/core/CacheService.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
import type Logger from '@/logger.js';
-import type { MiNote } from '@/models/entities/Note.js';
+import type { MiNote } from '@/models/Note.js';
import type { IdService } from '@/core/IdService.js';
import type { MfmService } from '@/core/MfmService.js';
import { toArray } from '@/misc/prelude/array.js';
import type { GlobalEventService } from '@/core/GlobalEventService.js';
import type { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import type { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js';
-import { MiUserProfile } from '@/models/entities/UserProfile.js';
-import { MiUserPublickey } from '@/models/entities/UserPublickey.js';
+import { MiUserProfile } from '@/models/UserProfile.js';
+import { MiUserPublickey } from '@/models/UserPublickey.js';
import type UsersChart from '@/core/chart/charts/users.js';
import type InstanceChart from '@/core/chart/charts/instance.js';
import type { HashtagService } from '@/core/HashtagService.js';
-import { MiUserNotePining } from '@/models/entities/UserNotePining.js';
+import { MiUserNotePining } from '@/models/UserNotePining.js';
import { StatusError } from '@/misc/status-error.js';
import type { UtilityService } from '@/core/UtilityService.js';
import type { UserEntityService } from '@/core/entities/UserEntityService.js';
@@ -284,7 +284,7 @@ export class ApPersonService implements OnModuleInit {
const tags = extractApHashtags(person.tag).map(normalizeForSearch).splice(0, 32);
- const isBot = getApType(object) === 'Service';
+ const isBot = getApType(object) === 'Service' || getApType(object) === 'Application';
const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/);
@@ -301,7 +301,7 @@ export class ApPersonService implements OnModuleInit {
const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], host)
.then(_emojis => _emojis.map(emoji => emoji.name))
.catch(err => {
- this.logger.error('error occured while fetching user emojis', { stack: err });
+ this.logger.error('error occurred while fetching user emojis', { stack: err });
return [];
});
//#endregion
@@ -310,10 +310,9 @@ export class ApPersonService implements OnModuleInit {
// Start transaction
await this.db.transaction(async transactionalEntityManager => {
user = await transactionalEntityManager.save(new MiUser({
- id: this.idService.genId(),
+ id: this.idService.gen(),
avatarId: null,
bannerId: null,
- createdAt: new Date(),
lastFetchedAt: new Date(),
name: truncate(person.name, nameLength),
isLocked: person.manuallyApprovesFollowers,
@@ -403,7 +402,7 @@ export class ApPersonService implements OnModuleInit {
// Register to the cache
this.cacheService.uriPersonCache.set(user.uri, user);
} catch (err) {
- this.logger.error('error occured while fetching user avatar/banner', { stack: err });
+ this.logger.error('error occurred while fetching user avatar/banner', { stack: err });
}
//#endregion
@@ -472,7 +471,7 @@ export class ApPersonService implements OnModuleInit {
emojis: emojiNames,
name: truncate(person.name, nameLength),
tags,
- isBot: getApType(object) === 'Service',
+ isBot: getApType(object) === 'Service' || getApType(object) === 'Application',
isCat: (person as any).isCat === true,
isLocked: person.manuallyApprovesFollowers,
movedToUri: person.movedTo ?? null,
@@ -638,8 +637,7 @@ export class ApPersonService implements OnModuleInit {
for (const note of featuredNotes.filter((note): note is MiNote => note != null)) {
td -= 1000;
transactionalEntityManager.insert(MiUserNotePining, {
- id: this.idService.genId(new Date(Date.now() + td)),
- createdAt: new Date(),
+ id: this.idService.gen(Date.now() + td),
userId: user.id,
noteId: note.id,
});
diff --git a/packages/backend/src/core/activitypub/models/ApQuestionService.ts b/packages/backend/src/core/activitypub/models/ApQuestionService.ts
index 8d8c0a5811..27bd62268b 100644
--- a/packages/backend/src/core/activitypub/models/ApQuestionService.ts
+++ b/packages/backend/src/core/activitypub/models/ApQuestionService.ts
@@ -5,9 +5,9 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { NotesRepository, PollsRepository } from '@/models/index.js';
+import type { NotesRepository, PollsRepository } from '@/models/_.js';
import type { Config } from '@/config.js';
-import type { IPoll } from '@/models/entities/Poll.js';
+import type { IPoll } from '@/models/Poll.js';
import type Logger from '@/logger.js';
import { bindThis } from '@/decorators.js';
import { isQuestion } from '../type.js';
diff --git a/packages/backend/src/core/chart/ChartManagementService.ts b/packages/backend/src/core/chart/ChartManagementService.ts
index 6b626d3980..f751a68cb4 100644
--- a/packages/backend/src/core/chart/ChartManagementService.ts
+++ b/packages/backend/src/core/chart/ChartManagementService.ts
@@ -23,7 +23,7 @@ import type { OnApplicationShutdown } from '@nestjs/common';
@Injectable()
export class ChartManagementService implements OnApplicationShutdown {
private charts;
- private saveIntervalId: NodeJS.Timer;
+ private saveIntervalId: NodeJS.Timeout;
constructor(
private federationChart: FederationChart,
diff --git a/packages/backend/src/core/chart/charts/active-users.ts b/packages/backend/src/core/chart/charts/active-users.ts
index 1493727290..f0918e059c 100644
--- a/packages/backend/src/core/chart/charts/active-users.ts
+++ b/packages/backend/src/core/chart/charts/active-users.ts
@@ -6,9 +6,10 @@
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { AppLockService } from '@/core/AppLockService.js';
-import type { MiUser } from '@/models/entities/User.js';
+import type { MiUser } from '@/models/User.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
import Chart from '../core.js';
import { ChartLoggerService } from '../ChartLoggerService.js';
import { name, schema } from './entities/active-users.js';
@@ -21,15 +22,15 @@ const year = 1000 * 60 * 60 * 24 * 365;
/**
* アクティブユーザーに関するチャート
*/
-// eslint-disable-next-line import/no-default-export
@Injectable()
-export default class ActiveUsersChart extends Chart {
+export default class ActiveUsersChart extends Chart { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.db)
private db: DataSource,
private appLockService: AppLockService,
private chartLoggerService: ChartLoggerService,
+ private idService: IdService,
) {
super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema);
}
@@ -43,20 +44,21 @@ export default class ActiveUsersChart extends Chart {
}
@bindThis
- public async read(user: { id: MiUser['id'], host: null, createdAt: MiUser['createdAt'] }): Promise {
+ public async read(user: { id: MiUser['id'], host: null }): Promise {
+ const createdAt = this.idService.parse(user.id).date;
await this.commit({
'read': [user.id],
- 'registeredWithinWeek': (Date.now() - user.createdAt.getTime() < week) ? [user.id] : [],
- 'registeredWithinMonth': (Date.now() - user.createdAt.getTime() < month) ? [user.id] : [],
- 'registeredWithinYear': (Date.now() - user.createdAt.getTime() < year) ? [user.id] : [],
- 'registeredOutsideWeek': (Date.now() - user.createdAt.getTime() > week) ? [user.id] : [],
- 'registeredOutsideMonth': (Date.now() - user.createdAt.getTime() > month) ? [user.id] : [],
- 'registeredOutsideYear': (Date.now() - user.createdAt.getTime() > year) ? [user.id] : [],
+ 'registeredWithinWeek': (Date.now() - createdAt.getTime() < week) ? [user.id] : [],
+ 'registeredWithinMonth': (Date.now() - createdAt.getTime() < month) ? [user.id] : [],
+ 'registeredWithinYear': (Date.now() - createdAt.getTime() < year) ? [user.id] : [],
+ 'registeredOutsideWeek': (Date.now() - createdAt.getTime() > week) ? [user.id] : [],
+ 'registeredOutsideMonth': (Date.now() - createdAt.getTime() > month) ? [user.id] : [],
+ 'registeredOutsideYear': (Date.now() - createdAt.getTime() > year) ? [user.id] : [],
});
}
@bindThis
- public async write(user: { id: MiUser['id'], host: null, createdAt: MiUser['createdAt'] }): Promise {
+ public async write(user: { id: MiUser['id'], host: null }): Promise {
await this.commit({
'write': [user.id],
});
diff --git a/packages/backend/src/core/chart/charts/ap-request.ts b/packages/backend/src/core/chart/charts/ap-request.ts
index 8bcbd97b48..03c9b42be1 100644
--- a/packages/backend/src/core/chart/charts/ap-request.ts
+++ b/packages/backend/src/core/chart/charts/ap-request.ts
@@ -16,9 +16,8 @@ import type { KVs } from '../core.js';
/**
* Chart about ActivityPub requests
*/
-// eslint-disable-next-line import/no-default-export
@Injectable()
-export default class ApRequestChart extends Chart {
+export default class ApRequestChart extends Chart { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.db)
private db: DataSource,
diff --git a/packages/backend/src/core/chart/charts/drive.ts b/packages/backend/src/core/chart/charts/drive.ts
index 9db93e7e5c..bbcbf1a955 100644
--- a/packages/backend/src/core/chart/charts/drive.ts
+++ b/packages/backend/src/core/chart/charts/drive.ts
@@ -5,7 +5,7 @@
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
-import type { MiDriveFile } from '@/models/entities/DriveFile.js';
+import type { MiDriveFile } from '@/models/DriveFile.js';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
@@ -17,9 +17,8 @@ import type { KVs } from '../core.js';
/**
* ドライブに関するチャート
*/
-// eslint-disable-next-line import/no-default-export
@Injectable()
-export default class DriveChart extends Chart {
+export default class DriveChart extends Chart { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.db)
private db: DataSource,
diff --git a/packages/backend/src/core/chart/charts/federation.ts b/packages/backend/src/core/chart/charts/federation.ts
index 6d42dab9b4..fc474b002b 100644
--- a/packages/backend/src/core/chart/charts/federation.ts
+++ b/packages/backend/src/core/chart/charts/federation.ts
@@ -5,7 +5,7 @@
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
-import type { FollowingsRepository, InstancesRepository } from '@/models/index.js';
+import type { FollowingsRepository, InstancesRepository } from '@/models/_.js';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import { MetaService } from '@/core/MetaService.js';
@@ -18,9 +18,8 @@ import type { KVs } from '../core.js';
/**
* フェデレーションに関するチャート
*/
-// eslint-disable-next-line import/no-default-export
@Injectable()
-export default class FederationChart extends Chart {
+export default class FederationChart extends Chart { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.db)
private db: DataSource,
diff --git a/packages/backend/src/core/chart/charts/instance.ts b/packages/backend/src/core/chart/charts/instance.ts
index a780e59eb9..9df0afb02e 100644
--- a/packages/backend/src/core/chart/charts/instance.ts
+++ b/packages/backend/src/core/chart/charts/instance.ts
@@ -5,9 +5,9 @@
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
-import type { DriveFilesRepository, FollowingsRepository, UsersRepository, NotesRepository } from '@/models/index.js';
-import type { MiDriveFile } from '@/models/entities/DriveFile.js';
-import type { MiNote } from '@/models/entities/Note.js';
+import type { DriveFilesRepository, FollowingsRepository, UsersRepository, NotesRepository } from '@/models/_.js';
+import type { MiDriveFile } from '@/models/DriveFile.js';
+import type { MiNote } from '@/models/Note.js';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import { UtilityService } from '@/core/UtilityService.js';
@@ -20,9 +20,8 @@ import type { KVs } from '../core.js';
/**
* インスタンスごとのチャート
*/
-// eslint-disable-next-line import/no-default-export
@Injectable()
-export default class InstanceChart extends Chart {
+export default class InstanceChart extends Chart { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.db)
private db: DataSource,
diff --git a/packages/backend/src/core/chart/charts/notes.ts b/packages/backend/src/core/chart/charts/notes.ts
index 5a4226911c..df3295dbac 100644
--- a/packages/backend/src/core/chart/charts/notes.ts
+++ b/packages/backend/src/core/chart/charts/notes.ts
@@ -5,8 +5,8 @@
import { Injectable, Inject } from '@nestjs/common';
import { Not, IsNull, DataSource } from 'typeorm';
-import type { NotesRepository } from '@/models/index.js';
-import type { MiNote } from '@/models/entities/Note.js';
+import type { NotesRepository } from '@/models/_.js';
+import type { MiNote } from '@/models/Note.js';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
@@ -18,9 +18,8 @@ import type { KVs } from '../core.js';
/**
* ノートに関するチャート
*/
-// eslint-disable-next-line import/no-default-export
@Injectable()
-export default class NotesChart extends Chart {
+export default class NotesChart extends Chart { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.db)
private db: DataSource,
diff --git a/packages/backend/src/core/chart/charts/per-user-drive.ts b/packages/backend/src/core/chart/charts/per-user-drive.ts
index 89822479c6..18354359c8 100644
--- a/packages/backend/src/core/chart/charts/per-user-drive.ts
+++ b/packages/backend/src/core/chart/charts/per-user-drive.ts
@@ -5,8 +5,8 @@
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
-import type { DriveFilesRepository } from '@/models/index.js';
-import type { MiDriveFile } from '@/models/entities/DriveFile.js';
+import type { DriveFilesRepository } from '@/models/_.js';
+import type { MiDriveFile } from '@/models/DriveFile.js';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
@@ -19,9 +19,8 @@ import type { KVs } from '../core.js';
/**
* ユーザーごとのドライブに関するチャート
*/
-// eslint-disable-next-line import/no-default-export
@Injectable()
-export default class PerUserDriveChart extends Chart {
+export default class PerUserDriveChart extends Chart { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.db)
private db: DataSource,
diff --git a/packages/backend/src/core/chart/charts/per-user-following.ts b/packages/backend/src/core/chart/charts/per-user-following.ts
index f195585b3c..79bff2cb66 100644
--- a/packages/backend/src/core/chart/charts/per-user-following.ts
+++ b/packages/backend/src/core/chart/charts/per-user-following.ts
@@ -5,11 +5,11 @@
import { Injectable, Inject } from '@nestjs/common';
import { Not, IsNull, DataSource } from 'typeorm';
-import type { MiUser } from '@/models/entities/User.js';
+import type { MiUser } from '@/models/User.js';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import type { FollowingsRepository } from '@/models/index.js';
+import type { FollowingsRepository } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
import Chart from '../core.js';
import { ChartLoggerService } from '../ChartLoggerService.js';
@@ -19,9 +19,8 @@ import type { KVs } from '../core.js';
/**
* ユーザーごとのフォローに関するチャート
*/
-// eslint-disable-next-line import/no-default-export
@Injectable()
-export default class PerUserFollowingChart extends Chart {
+export default class PerUserFollowingChart extends Chart { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.db)
private db: DataSource,
diff --git a/packages/backend/src/core/chart/charts/per-user-notes.ts b/packages/backend/src/core/chart/charts/per-user-notes.ts
index 3ab72bc1d0..0db0e6f07f 100644
--- a/packages/backend/src/core/chart/charts/per-user-notes.ts
+++ b/packages/backend/src/core/chart/charts/per-user-notes.ts
@@ -5,11 +5,11 @@
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiNote } from '@/models/entities/Note.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiNote } from '@/models/Note.js';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
-import type { NotesRepository } from '@/models/index.js';
+import type { NotesRepository } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
import Chart from '../core.js';
import { ChartLoggerService } from '../ChartLoggerService.js';
@@ -19,9 +19,8 @@ import type { KVs } from '../core.js';
/**
* ユーザーごとのノートに関するチャート
*/
-// eslint-disable-next-line import/no-default-export
@Injectable()
-export default class PerUserNotesChart extends Chart {
+export default class PerUserNotesChart extends Chart { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.db)
private db: DataSource,
diff --git a/packages/backend/src/core/chart/charts/per-user-pv.ts b/packages/backend/src/core/chart/charts/per-user-pv.ts
index bc5cb2e888..cf1b4c71f6 100644
--- a/packages/backend/src/core/chart/charts/per-user-pv.ts
+++ b/packages/backend/src/core/chart/charts/per-user-pv.ts
@@ -5,7 +5,7 @@
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
-import type { MiUser } from '@/models/entities/User.js';
+import type { MiUser } from '@/models/User.js';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
@@ -17,9 +17,8 @@ import type { KVs } from '../core.js';
/**
* ユーザーごとのプロフィール被閲覧数に関するチャート
*/
-// eslint-disable-next-line import/no-default-export
@Injectable()
-export default class PerUserPvChart extends Chart {
+export default class PerUserPvChart extends Chart { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.db)
private db: DataSource,
diff --git a/packages/backend/src/core/chart/charts/per-user-reactions.ts b/packages/backend/src/core/chart/charts/per-user-reactions.ts
index 7f8ae66f55..9f4f6e9651 100644
--- a/packages/backend/src/core/chart/charts/per-user-reactions.ts
+++ b/packages/backend/src/core/chart/charts/per-user-reactions.ts
@@ -5,8 +5,8 @@
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiNote } from '@/models/entities/Note.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiNote } from '@/models/Note.js';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
@@ -19,9 +19,8 @@ import type { KVs } from '../core.js';
/**
* ユーザーごとのリアクションに関するチャート
*/
-// eslint-disable-next-line import/no-default-export
@Injectable()
-export default class PerUserReactionsChart extends Chart {
+export default class PerUserReactionsChart extends Chart { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.db)
private db: DataSource,
diff --git a/packages/backend/src/core/chart/charts/test-grouped.ts b/packages/backend/src/core/chart/charts/test-grouped.ts
index 452f54330e..00fb872237 100644
--- a/packages/backend/src/core/chart/charts/test-grouped.ts
+++ b/packages/backend/src/core/chart/charts/test-grouped.ts
@@ -16,9 +16,8 @@ import type { KVs } from '../core.js';
/**
* For testing
*/
-// eslint-disable-next-line import/no-default-export
@Injectable()
-export default class TestGroupedChart extends Chart {
+export default class TestGroupedChart extends Chart { // eslint-disable-line import/no-default-export
private total = {} as Record;
constructor(
diff --git a/packages/backend/src/core/chart/charts/test-intersection.ts b/packages/backend/src/core/chart/charts/test-intersection.ts
index 0898f28128..45a7e805c5 100644
--- a/packages/backend/src/core/chart/charts/test-intersection.ts
+++ b/packages/backend/src/core/chart/charts/test-intersection.ts
@@ -16,9 +16,8 @@ import type { KVs } from '../core.js';
/**
* For testing
*/
-// eslint-disable-next-line import/no-default-export
@Injectable()
-export default class TestIntersectionChart extends Chart {
+export default class TestIntersectionChart extends Chart { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.db)
private db: DataSource,
diff --git a/packages/backend/src/core/chart/charts/test-unique.ts b/packages/backend/src/core/chart/charts/test-unique.ts
index 96cc171945..e9d38eaf13 100644
--- a/packages/backend/src/core/chart/charts/test-unique.ts
+++ b/packages/backend/src/core/chart/charts/test-unique.ts
@@ -16,9 +16,8 @@ import type { KVs } from '../core.js';
/**
* For testing
*/
-// eslint-disable-next-line import/no-default-export
@Injectable()
-export default class TestUniqueChart extends Chart {
+export default class TestUniqueChart extends Chart { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.db)
private db: DataSource,
diff --git a/packages/backend/src/core/chart/charts/test.ts b/packages/backend/src/core/chart/charts/test.ts
index ce2eeb0d5a..4dd6063b5b 100644
--- a/packages/backend/src/core/chart/charts/test.ts
+++ b/packages/backend/src/core/chart/charts/test.ts
@@ -16,9 +16,8 @@ import type { KVs } from '../core.js';
/**
* For testing
*/
-// eslint-disable-next-line import/no-default-export
@Injectable()
-export default class TestChart extends Chart {
+export default class TestChart extends Chart { // eslint-disable-line import/no-default-export
public total = 0; // publicにするのはテストのため
constructor(
diff --git a/packages/backend/src/core/chart/charts/users.ts b/packages/backend/src/core/chart/charts/users.ts
index 86d530a7bb..c2026c2aea 100644
--- a/packages/backend/src/core/chart/charts/users.ts
+++ b/packages/backend/src/core/chart/charts/users.ts
@@ -5,11 +5,11 @@
import { Injectable, Inject } from '@nestjs/common';
import { Not, IsNull, DataSource } from 'typeorm';
-import type { MiUser } from '@/models/entities/User.js';
+import type { MiUser } from '@/models/User.js';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import type { UsersRepository } from '@/models/index.js';
+import type { UsersRepository } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
import Chart from '../core.js';
import { ChartLoggerService } from '../ChartLoggerService.js';
@@ -19,9 +19,8 @@ import type { KVs } from '../core.js';
/**
* ユーザー数に関するチャート
*/
-// eslint-disable-next-line import/no-default-export
@Injectable()
-export default class UsersChart extends Chart {
+export default class UsersChart extends Chart { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.db)
private db: DataSource,
diff --git a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts
index 312c583156..2d6cb1b077 100644
--- a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts
+++ b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts
@@ -5,12 +5,13 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { AbuseUserReportsRepository } from '@/models/index.js';
+import type { AbuseUserReportsRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
-import type { MiAbuseUserReport } from '@/models/entities/AbuseUserReport.js';
+import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
import { bindThis } from '@/decorators.js';
import { Packed } from '@/misc/json-schema.js';
-import type { MiUser } from '@/models/entities/User.js';
+import type { MiUser } from '@/models/User.js';
+import { IdService } from '@/core/IdService.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
@@ -20,6 +21,7 @@ export class AbuseUserReportEntityService {
private abuseUserReportsRepository: AbuseUserReportsRepository,
private userEntityService: UserEntityService,
+ private idService: IdService,
) {
}
@@ -32,7 +34,7 @@ export class AbuseUserReportEntityService {
return await awaitAll({
id: report.id,
- createdAt: report.createdAt.toISOString(),
+ createdAt: this.idService.parse(report.id).date.toISOString(),
comment: report.comment,
resolved: report.resolved,
reporterId: report.reporterId,
diff --git a/packages/backend/src/core/entities/AnnouncementEntityService.ts b/packages/backend/src/core/entities/AnnouncementEntityService.ts
index 559a48a58f..2dc2c2cc3c 100644
--- a/packages/backend/src/core/entities/AnnouncementEntityService.ts
+++ b/packages/backend/src/core/entities/AnnouncementEntityService.ts
@@ -5,13 +5,10 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type {
- AnnouncementReadsRepository,
- AnnouncementsRepository,
-} from '@/models/index.js';
+import type { AnnouncementsRepository, AnnouncementReadsRepository, MiAnnouncement, MiUser } from "@/models/_.js";
import type { Packed } from '@/misc/json-schema.js';
import { bindThis } from '@/decorators.js';
-import { MiAnnouncement, MiUser } from '@/models/index.js';
+import { IdService } from '@/core/IdService.js';
@Injectable()
export class AnnouncementEntityService {
@@ -21,6 +18,8 @@ export class AnnouncementEntityService {
@Inject(DI.announcementReadsRepository)
private announcementReadsRepository: AnnouncementReadsRepository,
+
+ private idService: IdService,
) {
}
@@ -36,15 +35,17 @@ export class AnnouncementEntityService {
}) as MiAnnouncement & { isRead?: boolean | null };
if (me && announcement.isRead === undefined) {
- announcement.isRead = await this.announcementReadsRepository.countBy({
- announcementId: announcement.id,
- userId: me.id,
- }).then(count => count > 0);
+ announcement.isRead = await this.announcementReadsRepository
+ .countBy({
+ announcementId: announcement.id,
+ userId: me.id,
+ })
+ .then((count: number) => count > 0);
}
return {
id: announcement.id,
- createdAt: announcement.createdAt.toISOString(),
+ createdAt: this.idService.parse(announcement.id).date.toISOString(),
updatedAt: announcement.updatedAt?.toISOString() ?? null,
title: announcement.title,
text: announcement.text,
@@ -55,6 +56,7 @@ export class AnnouncementEntityService {
needConfirmationToRead: announcement.needConfirmationToRead,
closeDuration: announcement.closeDuration,
displayOrder: announcement.displayOrder,
+ silence: announcement.silence,
isRead: announcement.isRead !== null ? announcement.isRead : undefined,
};
}
diff --git a/packages/backend/src/core/entities/AntennaEntityService.ts b/packages/backend/src/core/entities/AntennaEntityService.ts
index 7826ca5d70..265a61e8ad 100644
--- a/packages/backend/src/core/entities/AntennaEntityService.ts
+++ b/packages/backend/src/core/entities/AntennaEntityService.ts
@@ -5,16 +5,19 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { AntennasRepository } from '@/models/index.js';
+import type { AntennasRepository } from '@/models/_.js';
import type { Packed } from '@/misc/json-schema.js';
-import type { MiAntenna } from '@/models/entities/Antenna.js';
+import type { MiAntenna } from '@/models/Antenna.js';
import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
@Injectable()
export class AntennaEntityService {
constructor(
@Inject(DI.antennasRepository)
private antennasRepository: AntennasRepository,
+
+ private idService: IdService,
) {
}
@@ -26,7 +29,7 @@ export class AntennaEntityService {
return {
id: antenna.id,
- createdAt: antenna.createdAt.toISOString(),
+ createdAt: this.idService.parse(antenna.id).date.toISOString(),
name: antenna.name,
keywords: antenna.keywords,
excludeKeywords: antenna.excludeKeywords,
@@ -34,6 +37,7 @@ export class AntennaEntityService {
userListId: antenna.userListId,
users: antenna.users,
caseSensitive: antenna.caseSensitive,
+ localOnly: antenna.localOnly,
notify: antenna.notify,
withReplies: antenna.withReplies,
withFile: antenna.withFile,
diff --git a/packages/backend/src/core/entities/AppEntityService.ts b/packages/backend/src/core/entities/AppEntityService.ts
index 4ce47ce343..9d612d861a 100644
--- a/packages/backend/src/core/entities/AppEntityService.ts
+++ b/packages/backend/src/core/entities/AppEntityService.ts
@@ -5,10 +5,10 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { AccessTokensRepository, AppsRepository } from '@/models/index.js';
+import type { AccessTokensRepository, AppsRepository } from '@/models/_.js';
import type { Packed } from '@/misc/json-schema.js';
-import type { MiApp } from '@/models/entities/App.js';
-import type { MiUser } from '@/models/entities/User.js';
+import type { MiApp } from '@/models/App.js';
+import type { MiUser } from '@/models/User.js';
import { bindThis } from '@/decorators.js';
@Injectable()
@@ -32,11 +32,11 @@ export class AppEntityService {
includeProfileImageIds?: boolean
},
): Promise> {
- const opts = Object.assign({
+ const opts = {
detail: false,
includeSecret: false,
- includeProfileImageIds: false,
- }, options);
+ includeProfileImageIds: false, ...options
+ };
const app = typeof src === 'object' ? src : await this.appsRepository.findOneByOrFail({ id: src });
diff --git a/packages/backend/src/core/entities/AuthSessionEntityService.ts b/packages/backend/src/core/entities/AuthSessionEntityService.ts
index 709e8bce3f..ea35825cbf 100644
--- a/packages/backend/src/core/entities/AuthSessionEntityService.ts
+++ b/packages/backend/src/core/entities/AuthSessionEntityService.ts
@@ -5,10 +5,10 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { AuthSessionsRepository } from '@/models/index.js';
+import type { AuthSessionsRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
-import type { MiAuthSession } from '@/models/entities/AuthSession.js';
-import type { MiUser } from '@/models/entities/User.js';
+import type { MiAuthSession } from '@/models/AuthSession.js';
+import type { MiUser } from '@/models/User.js';
import { bindThis } from '@/decorators.js';
import { AppEntityService } from './AppEntityService.js';
diff --git a/packages/backend/src/core/entities/BlockingEntityService.ts b/packages/backend/src/core/entities/BlockingEntityService.ts
index a1516266fe..f5c49bb5bb 100644
--- a/packages/backend/src/core/entities/BlockingEntityService.ts
+++ b/packages/backend/src/core/entities/BlockingEntityService.ts
@@ -5,12 +5,13 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { BlockingsRepository } from '@/models/index.js';
+import type { BlockingsRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/json-schema.js';
-import type { MiBlocking } from '@/models/entities/Blocking.js';
-import type { MiUser } from '@/models/entities/User.js';
+import type { MiBlocking } from '@/models/Blocking.js';
+import type { MiUser } from '@/models/User.js';
import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
@@ -20,6 +21,7 @@ export class BlockingEntityService {
private blockingsRepository: BlockingsRepository,
private userEntityService: UserEntityService,
+ private idService: IdService,
) {
}
@@ -32,7 +34,7 @@ export class BlockingEntityService {
return await awaitAll({
id: blocking.id,
- createdAt: blocking.createdAt.toISOString(),
+ createdAt: this.idService.parse(blocking.id).date.toISOString(),
blockeeId: blocking.blockeeId,
blockee: this.userEntityService.pack(blocking.blockeeId, me, {
detail: true,
diff --git a/packages/backend/src/core/entities/ChannelEntityService.ts b/packages/backend/src/core/entities/ChannelEntityService.ts
index c49cfa6fca..fd3d53640a 100644
--- a/packages/backend/src/core/entities/ChannelEntityService.ts
+++ b/packages/backend/src/core/entities/ChannelEntityService.ts
@@ -6,18 +6,12 @@
import { Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
-import type {
- ChannelFavoritesRepository,
- ChannelFollowingsRepository,
- ChannelsRepository,
- DriveFilesRepository,
- NotesRepository,
- NoteUnreadsRepository,
-} from '@/models/index.js';
+import type { ChannelFavoritesRepository, ChannelFollowingsRepository, ChannelsRepository, DriveFilesRepository, NotesRepository } from '@/models/_.js';
import type { Packed } from '@/misc/json-schema.js';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiChannel } from '@/models/entities/Channel.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiChannel } from '@/models/Channel.js';
import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
import { DriveFileEntityService } from './DriveFileEntityService.js';
import { NoteEntityService } from './NoteEntityService.js';
@@ -36,14 +30,12 @@ export class ChannelEntityService {
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
- @Inject(DI.noteUnreadsRepository)
- private noteUnreadsRepository: NoteUnreadsRepository,
-
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
private noteEntityService: NoteEntityService,
private driveFileEntityService: DriveFileEntityService,
+ private idService: IdService,
) {
}
@@ -58,13 +50,6 @@ export class ChannelEntityService {
const banner = channel.bannerId ? await this.driveFilesRepository.findOneBy({ id: channel.bannerId }) : null;
- const hasUnreadNote = meId ? await this.noteUnreadsRepository.exist({
- where: {
- noteChannelId: channel.id,
- userId: meId,
- },
- }) : undefined;
-
const isFollowing = meId ? await this.channelFollowingsRepository.exist({
where: {
followerId: meId,
@@ -87,7 +72,7 @@ export class ChannelEntityService {
return {
id: channel.id,
- createdAt: channel.createdAt.toISOString(),
+ createdAt: this.idService.parse(channel.id).date.toISOString(),
lastNotedAt: channel.lastNotedAt ? channel.lastNotedAt.toISOString() : null,
name: channel.name,
description: channel.description,
@@ -98,11 +83,13 @@ export class ChannelEntityService {
isArchived: channel.isArchived,
usersCount: channel.usersCount,
notesCount: channel.notesCount,
+ isSensitive: channel.isSensitive,
+ allowRenoteToExternal: channel.allowRenoteToExternal,
...(me ? {
isFollowing,
isFavorited,
- hasUnreadNote,
+ hasUnreadNote: false, // 後方互換性のため
} : {}),
...(detailed ? {
diff --git a/packages/backend/src/core/entities/ClipEntityService.ts b/packages/backend/src/core/entities/ClipEntityService.ts
index 30327ee55d..7a74e06470 100644
--- a/packages/backend/src/core/entities/ClipEntityService.ts
+++ b/packages/backend/src/core/entities/ClipEntityService.ts
@@ -5,11 +5,12 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { ClipFavoritesRepository, ClipsRepository, MiUser } from '@/models/index.js';
+import type { ClipFavoritesRepository, ClipsRepository, MiUser } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/json-schema.js';
-import type { MiClip } from '@/models/entities/Clip.js';
+import type { MiClip } from '@/models/Clip.js';
import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
@@ -22,6 +23,7 @@ export class ClipEntityService {
private clipFavoritesRepository: ClipFavoritesRepository,
private userEntityService: UserEntityService,
+ private idService: IdService,
) {
}
@@ -35,7 +37,7 @@ export class ClipEntityService {
return await awaitAll({
id: clip.id,
- createdAt: clip.createdAt.toISOString(),
+ createdAt: this.idService.parse(clip.id).date.toISOString(),
lastClippedAt: clip.lastClippedAt ? clip.lastClippedAt.toISOString() : null,
userId: clip.userId,
user: this.userEntityService.pack(clip.user ?? clip.userId, me),
diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts
index 1506fce58d..0085c8a91f 100644
--- a/packages/backend/src/core/entities/DriveFileEntityService.ts
+++ b/packages/backend/src/core/entities/DriveFileEntityService.ts
@@ -4,19 +4,20 @@
*/
import { forwardRef, Inject, Injectable } from '@nestjs/common';
-import { DataSource, In } from 'typeorm';
+import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
-import type { DriveFilesRepository, NotesRepository } from '@/models/index.js';
+import type { DriveFilesRepository } from '@/models/_.js';
import type { Config } from '@/config.js';
import type { Packed } from '@/misc/json-schema.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiDriveFile } from '@/models/entities/DriveFile.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiDriveFile } from '@/models/DriveFile.js';
import { appendQuery, query } from '@/misc/prelude/url.js';
import { deepClone } from '@/misc/clone.js';
import { bindThis } from '@/decorators.js';
import { isMimeImage } from '@/misc/is-mime-image.js';
import { isNotNull } from '@/misc/is-not-null.js';
+import { IdService } from '@/core/IdService.js';
import { UtilityService } from '../UtilityService.js';
import { VideoProcessingService } from '../VideoProcessingService.js';
import { UserEntityService } from './UserEntityService.js';
@@ -34,12 +35,6 @@ export class DriveFileEntityService {
@Inject(DI.config)
private config: Config,
- @Inject(DI.db)
- private db: DataSource,
-
- @Inject(DI.notesRepository)
- private notesRepository: NotesRepository,
-
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
@@ -50,6 +45,7 @@ export class DriveFileEntityService {
private utilityService: UtilityService,
private driveFolderEntityService: DriveFolderEntityService,
private videoProcessingService: VideoProcessingService,
+ private idService: IdService,
) {
}
@@ -94,7 +90,7 @@ export class DriveFileEntityService {
if (file.type.startsWith('video')) {
if (file.thumbnailUrl) return file.thumbnailUrl;
- return this.videoProcessingService.getExternalVideoThumbnailUrl(file.webpublicUrl ?? file.url ?? file.uri);
+ return this.videoProcessingService.getExternalVideoThumbnailUrl(file.webpublicUrl ?? file.url);
} else if (file.uri != null && file.userHost != null && this.config.externalMediaProxyEnabled) {
// 動画ではなくリモートかつメディアプロキシ
return this.getProxiedUrl(file.uri, 'static');
@@ -149,7 +145,7 @@ export class DriveFileEntityService {
.select('SUM(file.size)', 'sum')
.getRawOne();
- return parseInt(sum, 10) ?? 0;
+ return parseInt(sum, 10) || 0;
}
@bindThis
@@ -161,7 +157,7 @@ export class DriveFileEntityService {
.select('SUM(file.size)', 'sum')
.getRawOne();
- return parseInt(sum, 10) ?? 0;
+ return parseInt(sum, 10) || 0;
}
@bindThis
@@ -173,7 +169,7 @@ export class DriveFileEntityService {
.select('SUM(file.size)', 'sum')
.getRawOne();
- return parseInt(sum, 10) ?? 0;
+ return parseInt(sum, 10) || 0;
}
@bindThis
@@ -185,7 +181,7 @@ export class DriveFileEntityService {
.select('SUM(file.size)', 'sum')
.getRawOne();
- return parseInt(sum, 10) ?? 0;
+ return parseInt(sum, 10) || 0;
}
@bindThis
@@ -203,7 +199,7 @@ export class DriveFileEntityService {
return await awaitAll>({
id: file.id,
- createdAt: file.createdAt.toISOString(),
+ createdAt: this.idService.parse(file.id).date.toISOString(),
name: file.name,
type: file.type,
md5: file.md5,
@@ -239,7 +235,7 @@ export class DriveFileEntityService {
return await awaitAll>({
id: file.id,
- createdAt: file.createdAt.toISOString(),
+ createdAt: this.idService.parse(file.id).date.toISOString(),
name: file.name,
type: file.type,
md5: file.md5,
diff --git a/packages/backend/src/core/entities/DriveFolderEntityService.ts b/packages/backend/src/core/entities/DriveFolderEntityService.ts
index a052d34ff6..6c64f03898 100644
--- a/packages/backend/src/core/entities/DriveFolderEntityService.ts
+++ b/packages/backend/src/core/entities/DriveFolderEntityService.ts
@@ -5,11 +5,12 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { DriveFilesRepository, DriveFoldersRepository } from '@/models/index.js';
+import type { DriveFilesRepository, DriveFoldersRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/json-schema.js';
-import type { MiDriveFolder } from '@/models/entities/DriveFolder.js';
+import type { MiDriveFolder } from '@/models/DriveFolder.js';
import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
@Injectable()
export class DriveFolderEntityService {
@@ -19,6 +20,8 @@ export class DriveFolderEntityService {
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
+
+ private idService: IdService,
) {
}
@@ -37,7 +40,7 @@ export class DriveFolderEntityService {
return await awaitAll({
id: folder.id,
- createdAt: folder.createdAt.toISOString(),
+ createdAt: this.idService.parse(folder.id).date.toISOString(),
name: folder.name,
parentId: folder.parentId,
diff --git a/packages/backend/src/core/entities/EmojiEntityService.ts b/packages/backend/src/core/entities/EmojiEntityService.ts
index 4e83970696..ea03a11afd 100644
--- a/packages/backend/src/core/entities/EmojiEntityService.ts
+++ b/packages/backend/src/core/entities/EmojiEntityService.ts
@@ -5,9 +5,9 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { EmojisRepository } from '@/models/index.js';
+import type { EmojisRepository } from '@/models/_.js';
import type { Packed } from '@/misc/json-schema.js';
-import type { MiEmoji } from '@/models/entities/Emoji.js';
+import type { MiEmoji } from '@/models/Emoji.js';
import { bindThis } from '@/decorators.js';
@Injectable()
diff --git a/packages/backend/src/core/entities/FlashEntityService.ts b/packages/backend/src/core/entities/FlashEntityService.ts
index 49483f6ded..5798201d66 100644
--- a/packages/backend/src/core/entities/FlashEntityService.ts
+++ b/packages/backend/src/core/entities/FlashEntityService.ts
@@ -5,12 +5,13 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { FlashLikesRepository, FlashsRepository } from '@/models/index.js';
+import type { FlashsRepository, FlashLikesRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/json-schema.js';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiFlash } from '@/models/entities/Flash.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiFlash } from '@/models/Flash.js';
import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
@@ -23,6 +24,7 @@ export class FlashEntityService {
private flashLikesRepository: FlashLikesRepository,
private userEntityService: UserEntityService,
+ private idService: IdService,
) {
}
@@ -36,7 +38,7 @@ export class FlashEntityService {
return await awaitAll({
id: flash.id,
- createdAt: flash.createdAt.toISOString(),
+ createdAt: this.idService.parse(flash.id).date.toISOString(),
updatedAt: flash.updatedAt.toISOString(),
userId: flash.userId,
user: this.userEntityService.pack(flash.user ?? flash.userId, me), // { detail: true } すると無限ループするので注意
@@ -50,7 +52,7 @@ export class FlashEntityService {
@bindThis
public async packMany(
- flashs: (MiFlash['id'] | MiFlash)[],
+ flashs: MiFlash[],
me: { id: MiUser['id'] } | null | undefined,
) : Promise[]> {
return (await Promise.allSettled(flashs.map(x => this.pack(x, me))))
diff --git a/packages/backend/src/core/entities/FlashLikeEntityService.ts b/packages/backend/src/core/entities/FlashLikeEntityService.ts
index 35c98904d5..1e44b123dd 100644
--- a/packages/backend/src/core/entities/FlashLikeEntityService.ts
+++ b/packages/backend/src/core/entities/FlashLikeEntityService.ts
@@ -5,9 +5,9 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { FlashLikesRepository } from '@/models/index.js';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiFlashLike } from '@/models/entities/FlashLike.js';
+import type { FlashLikesRepository } from '@/models/_.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiFlashLike } from '@/models/FlashLike.js';
import { bindThis } from '@/decorators.js';
import { Packed } from '@/misc/json-schema.js';
import { FlashEntityService } from './FlashEntityService.js';
@@ -38,7 +38,7 @@ export class FlashLikeEntityService {
@bindThis
public async packMany(
likes: (MiFlashLike['id'] | MiFlashLike)[],
- me: { id: MiUser['id'] } | null | undefined,
+ me: { id: MiUser['id'] },
) : Promise[]> {
return (await Promise.allSettled(likes.map(x => this.pack(x, me))))
.filter(result => result.status === 'fulfilled')
diff --git a/packages/backend/src/core/entities/FollowRequestEntityService.ts b/packages/backend/src/core/entities/FollowRequestEntityService.ts
index 5cee20fb41..d35c3d6523 100644
--- a/packages/backend/src/core/entities/FollowRequestEntityService.ts
+++ b/packages/backend/src/core/entities/FollowRequestEntityService.ts
@@ -5,9 +5,9 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { FollowRequestsRepository } from '@/models/index.js';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiFollowRequest } from '@/models/entities/FollowRequest.js';
+import type { FollowRequestsRepository } from '@/models/_.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiFollowRequest } from '@/models/FollowRequest.js';
import { bindThis } from '@/decorators.js';
import { Packed } from '@/misc/json-schema.js';
import { UserEntityService } from './UserEntityService.js';
diff --git a/packages/backend/src/core/entities/FollowingEntityService.ts b/packages/backend/src/core/entities/FollowingEntityService.ts
index d23af7ae05..ae3f3b4f10 100644
--- a/packages/backend/src/core/entities/FollowingEntityService.ts
+++ b/packages/backend/src/core/entities/FollowingEntityService.ts
@@ -5,12 +5,13 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { FollowingsRepository } from '@/models/index.js';
+import type { FollowingsRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/json-schema.js';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiFollowing } from '@/models/entities/Following.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiFollowing } from '@/models/Following.js';
import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
import { UserEntityService } from './UserEntityService.js';
type LocalFollowerFollowing = MiFollowing & {
@@ -44,6 +45,7 @@ export class FollowingEntityService {
private followingsRepository: FollowingsRepository,
private userEntityService: UserEntityService,
+ private idService: IdService,
) {
}
@@ -82,7 +84,7 @@ export class FollowingEntityService {
return await awaitAll({
id: following.id,
- createdAt: following.createdAt.toISOString(),
+ createdAt: this.idService.parse(following.id).date.toISOString(),
followeeId: following.followeeId,
followerId: following.followerId,
followee: opts.populateFollowee ? this.userEntityService.pack(following.followee ?? following.followeeId, me, {
diff --git a/packages/backend/src/core/entities/GalleryLikeEntityService.ts b/packages/backend/src/core/entities/GalleryLikeEntityService.ts
index 6b41d981b5..b3f3b3faeb 100644
--- a/packages/backend/src/core/entities/GalleryLikeEntityService.ts
+++ b/packages/backend/src/core/entities/GalleryLikeEntityService.ts
@@ -5,11 +5,11 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { GalleryLikesRepository } from '@/models/index.js';
-import type { MiGalleryLike } from '@/models/entities/GalleryLike.js';
+import type { GalleryLikesRepository } from '@/models/_.js';
+import type { MiGalleryLike } from '@/models/GalleryLike.js';
import { bindThis } from '@/decorators.js';
import { Packed } from '@/misc/json-schema.js';
-import type { MiUser } from '@/models/entities/User.js';
+import type { MiUser } from '@/models/User.js';
import { GalleryPostEntityService } from './GalleryPostEntityService.js';
@Injectable()
diff --git a/packages/backend/src/core/entities/GalleryPostEntityService.ts b/packages/backend/src/core/entities/GalleryPostEntityService.ts
index 21b03faf76..eb19ae2ef1 100644
--- a/packages/backend/src/core/entities/GalleryPostEntityService.ts
+++ b/packages/backend/src/core/entities/GalleryPostEntityService.ts
@@ -5,12 +5,13 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { GalleryLikesRepository, GalleryPostsRepository } from '@/models/index.js';
+import type { GalleryLikesRepository, GalleryPostsRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/json-schema.js';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiGalleryPost } from '@/models/entities/GalleryPost.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiGalleryPost } from '@/models/GalleryPost.js';
import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
import { UserEntityService } from './UserEntityService.js';
import { DriveFileEntityService } from './DriveFileEntityService.js';
@@ -25,6 +26,7 @@ export class GalleryPostEntityService {
private userEntityService: UserEntityService,
private driveFileEntityService: DriveFileEntityService,
+ private idService: IdService,
) {
}
@@ -38,7 +40,7 @@ export class GalleryPostEntityService {
return await awaitAll({
id: post.id,
- createdAt: post.createdAt.toISOString(),
+ createdAt: this.idService.parse(post.id).date.toISOString(),
updatedAt: post.updatedAt.toISOString(),
userId: post.userId,
user: this.userEntityService.pack(post.user ?? post.userId, me),
@@ -56,7 +58,7 @@ export class GalleryPostEntityService {
@bindThis
public async packMany(
- posts: (MiGalleryPost['id'] | MiGalleryPost)[],
+ posts: MiGalleryPost[],
me: { id: MiUser['id'] } | null | undefined,
) : Promise[]> {
return (await Promise.allSettled(posts.map(x => this.pack(x, me))))
diff --git a/packages/backend/src/core/entities/HashtagEntityService.ts b/packages/backend/src/core/entities/HashtagEntityService.ts
index 4bccec689f..a70865f810 100644
--- a/packages/backend/src/core/entities/HashtagEntityService.ts
+++ b/packages/backend/src/core/entities/HashtagEntityService.ts
@@ -3,21 +3,14 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Inject, Injectable } from '@nestjs/common';
-import { DI } from '@/di-symbols.js';
-import type { HashtagsRepository } from '@/models/index.js';
+import { Injectable } from '@nestjs/common';
import type { Packed } from '@/misc/json-schema.js';
-import type { MiHashtag } from '@/models/entities/Hashtag.js';
+import type { MiHashtag } from '@/models/Hashtag.js';
import { bindThis } from '@/decorators.js';
-import { UserEntityService } from './UserEntityService.js';
@Injectable()
export class HashtagEntityService {
constructor(
- @Inject(DI.hashtagsRepository)
- private hashtagsRepository: HashtagsRepository,
-
- private userEntityService: UserEntityService,
) {
}
diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts
index f52b81967e..f05a414d84 100644
--- a/packages/backend/src/core/entities/InstanceEntityService.ts
+++ b/packages/backend/src/core/entities/InstanceEntityService.ts
@@ -3,11 +3,9 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Inject, Injectable } from '@nestjs/common';
-import { DI } from '@/di-symbols.js';
-import type { InstancesRepository } from '@/models/index.js';
+import { Injectable } from '@nestjs/common';
import type { Packed } from '@/misc/json-schema.js';
-import type { MiInstance } from '@/models/entities/Instance.js';
+import type { MiInstance } from '@/models/Instance.js';
import { MetaService } from '@/core/MetaService.js';
import { bindThis } from '@/decorators.js';
import { UtilityService } from '../UtilityService.js';
@@ -15,9 +13,6 @@ import { UtilityService } from '../UtilityService.js';
@Injectable()
export class InstanceEntityService {
constructor(
- @Inject(DI.instancesRepository)
- private instancesRepository: InstancesRepository,
-
private metaService: MetaService,
private utilityService: UtilityService,
@@ -47,10 +42,12 @@ export class InstanceEntityService {
description: instance.description,
maintainerName: instance.maintainerName,
maintainerEmail: instance.maintainerEmail,
+ isSilenced: this.utilityService.isSilencedHost(meta.silencedHosts, instance.host),
iconUrl: instance.iconUrl,
faviconUrl: instance.faviconUrl,
themeColor: instance.themeColor,
infoUpdatedAt: instance.infoUpdatedAt ? instance.infoUpdatedAt.toISOString() : null,
+ latestRequestReceivedAt: instance.latestRequestReceivedAt ? instance.latestRequestReceivedAt.toISOString() : null,
};
}
diff --git a/packages/backend/src/core/entities/InviteCodeEntityService.ts b/packages/backend/src/core/entities/InviteCodeEntityService.ts
index 453ea0311b..b6fe576cb3 100644
--- a/packages/backend/src/core/entities/InviteCodeEntityService.ts
+++ b/packages/backend/src/core/entities/InviteCodeEntityService.ts
@@ -5,12 +5,13 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { RegistrationTicketsRepository } from '@/models/index.js';
+import type { RegistrationTicketsRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/json-schema.js';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiRegistrationTicket } from '@/models/entities/RegistrationTicket.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiRegistrationTicket } from '@/models/RegistrationTicket.js';
import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
@@ -20,6 +21,7 @@ export class InviteCodeEntityService {
private registrationTicketsRepository: RegistrationTicketsRepository,
private userEntityService: UserEntityService,
+ private idService: IdService,
) {
}
@@ -39,7 +41,7 @@ export class InviteCodeEntityService {
id: target.id,
code: target.code,
expiresAt: target.expiresAt ? target.expiresAt.toISOString() : null,
- createdAt: target.createdAt.toISOString(),
+ createdAt: this.idService.parse(target.id).date.toISOString(),
createdBy: target.createdBy ? await this.userEntityService.pack(target.createdBy, me) : null,
usedBy: target.usedBy ? await this.userEntityService.pack(target.usedBy, me) : null,
usedAt: target.usedAt ? target.usedAt.toISOString() : null,
@@ -50,7 +52,7 @@ export class InviteCodeEntityService {
@bindThis
public async packMany(
targets: (MiRegistrationTicket['id'] | MiRegistrationTicket)[],
- me: { id: MiUser['id'] } | null | undefined,
+ me: { id: MiUser['id'] },
) : Promise[]> {
return (await Promise.allSettled(targets.map(x => this.pack(x, me))))
.filter(result => result.status === 'fulfilled')
diff --git a/packages/backend/src/core/entities/ModerationLogEntityService.ts b/packages/backend/src/core/entities/ModerationLogEntityService.ts
index 9e2083ccce..8ca80d99e3 100644
--- a/packages/backend/src/core/entities/ModerationLogEntityService.ts
+++ b/packages/backend/src/core/entities/ModerationLogEntityService.ts
@@ -5,12 +5,13 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { ModerationLogsRepository } from '@/models/index.js';
+import type { ModerationLogsRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiModerationLog } from '@/models/entities/ModerationLog.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiModerationLog } from '@/models/ModerationLog.js';
import { bindThis } from '@/decorators.js';
import { Packed } from '@/misc/json-schema.js';
+import { IdService } from '@/core/IdService.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
@@ -20,6 +21,7 @@ export class ModerationLogEntityService {
private moderationLogsRepository: ModerationLogsRepository,
private userEntityService: UserEntityService,
+ private idService: IdService,
) {
}
@@ -32,7 +34,7 @@ export class ModerationLogEntityService {
return await awaitAll({
id: log.id,
- createdAt: log.createdAt.toISOString(),
+ createdAt: this.idService.parse(log.id).date.toISOString(),
type: log.type,
info: log.info,
userId: log.userId,
diff --git a/packages/backend/src/core/entities/MutingEntityService.ts b/packages/backend/src/core/entities/MutingEntityService.ts
index cd94523f4c..88660f4247 100644
--- a/packages/backend/src/core/entities/MutingEntityService.ts
+++ b/packages/backend/src/core/entities/MutingEntityService.ts
@@ -5,12 +5,13 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { MutingsRepository } from '@/models/index.js';
+import type { MutingsRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/json-schema.js';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiMuting } from '@/models/entities/Muting.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiMuting } from '@/models/Muting.js';
import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
@@ -20,6 +21,7 @@ export class MutingEntityService {
private mutingsRepository: MutingsRepository,
private userEntityService: UserEntityService,
+ private idService: IdService,
) {
}
@@ -32,7 +34,7 @@ export class MutingEntityService {
return await awaitAll({
id: muting.id,
- createdAt: muting.createdAt.toISOString(),
+ createdAt: this.idService.parse(muting.id).date.toISOString(),
expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null,
muteeId: muting.muteeId,
mutee: this.userEntityService.pack(muting.muteeId, me, {
@@ -44,7 +46,7 @@ export class MutingEntityService {
@bindThis
public async packMany(
mutings: (MiMuting['id'] | MiMuting)[],
- me: { id: MiUser['id'] } | null | undefined,
+ me: { id: MiUser['id'] },
) : Promise[]> {
return (await Promise.allSettled(mutings.map(x => this.pack(x, me))))
.filter(result => result.status === 'fulfilled')
diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts
index 3eaf671c68..f45261b7dc 100644
--- a/packages/backend/src/core/entities/NoteEntityService.ts
+++ b/packages/backend/src/core/entities/NoteEntityService.ts
@@ -4,29 +4,19 @@
*/
import { Inject, Injectable } from '@nestjs/common';
-import { DataSource, In } from 'typeorm';
-import * as mfm from 'mfm-js';
+import { In } from 'typeorm';
import { ModuleRef } from '@nestjs/core';
import { DI } from '@/di-symbols.js';
import type { Packed } from '@/misc/json-schema.js';
-import { nyaize } from '@/misc/nyaize.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiNote } from '@/models/entities/Note.js';
-import type { MiNoteReaction } from '@/models/entities/NoteReaction.js';
-import type {
- ChannelsRepository,
- DriveFilesRepository,
- FollowingsRepository,
- NoteReactionsRepository,
- NotesRepository,
- PollsRepository,
- PollVotesRepository,
- UsersRepository,
-} from '@/models/index.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiNote } from '@/models/Note.js';
+import type { MiNoteReaction } from '@/models/NoteReaction.js';
+import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
import { isNotNull } from '@/misc/is-not-null.js';
import { DebounceLoader } from '@/misc/loader.js';
+import { IdService } from '@/core/IdService.js';
import type { OnModuleInit } from '@nestjs/common';
import type { CustomEmojiService } from '../CustomEmojiService.js';
import type { ReactionService } from '../ReactionService.js';
@@ -39,14 +29,12 @@ export class NoteEntityService implements OnModuleInit {
private driveFileEntityService: DriveFileEntityService;
private customEmojiService: CustomEmojiService;
private reactionService: ReactionService;
+ private idService: IdService;
private noteLoader = new DebounceLoader(this.findNoteOrFail);
constructor(
private moduleRef: ModuleRef,
- @Inject(DI.db)
- private db: DataSource,
-
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@@ -68,9 +56,6 @@ export class NoteEntityService implements OnModuleInit {
@Inject(DI.channelsRepository)
private channelsRepository: ChannelsRepository,
- @Inject(DI.driveFilesRepository)
- private driveFilesRepository: DriveFilesRepository,
-
//private userEntityService: UserEntityService,
//private driveFileEntityService: DriveFileEntityService,
//private customEmojiService: CustomEmojiService,
@@ -83,11 +68,12 @@ export class NoteEntityService implements OnModuleInit {
this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService');
this.customEmojiService = this.moduleRef.get('CustomEmojiService');
this.reactionService = this.moduleRef.get('ReactionService');
+ this.idService = this.moduleRef.get('IdService');
}
@bindThis
private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null) {
- // TODO: isVisibleForMe を使うようにしても良さそう(型違うけど)
+ // TODO: isVisibleForMe を使うようにしても良さそう(型違うけど)
let hide = false;
// visibility が specified かつ自分が指定されていなかったら非表示
@@ -97,7 +83,7 @@ export class NoteEntityService implements OnModuleInit {
} else if (meId === packedNote.userId) {
hide = false;
} else {
- // 指定されているかどうか
+ // 指定されているかどうか
const specified = packedNote.visibleUserIds!.some((id: any) => meId === id);
if (specified) {
@@ -115,13 +101,13 @@ export class NoteEntityService implements OnModuleInit {
} else if (meId === packedNote.userId) {
hide = false;
} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
- // 自分の投稿に対するリプライ
+ // 自分の投稿に対するリプライ
hide = false;
} else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
- // 自分へのメンション
+ // 自分へのメンション
hide = false;
} else {
- // フォロワーかどうか
+ // フォロワーかどうか
const isFollowing = await this.followingsRepository.exist({
where: {
followeeId: packedNote.userId,
@@ -184,21 +170,31 @@ export class NoteEntityService implements OnModuleInit {
}
@bindThis
- private async populateMyReaction(note: MiNote, meId: MiUser['id'], _hint_?: {
- myReactions: Map;
+ public async populateMyReaction(note: { id: MiNote['id']; reactions: MiNote['reactions']; reactionAndUserPairCache?: MiNote['reactionAndUserPairCache']; }, meId: MiUser['id'], _hint_?: {
+ myReactions: Map;
}) {
if (_hint_?.myReactions) {
const reaction = _hint_.myReactions.get(note.id);
if (reaction) {
- return this.reactionService.convertLegacyReaction(reaction.reaction);
- } else if (reaction === null) {
+ return this.reactionService.convertLegacyReaction(reaction);
+ } else {
return undefined;
}
- // 実装上抜けがあるだけかもしれないので、「ヒントに含まれてなかったら(=undefinedなら)return」のようにはしない
}
- // パフォーマンスのためノートが作成されてから1秒以上経っていない場合はリアクションを取得しない
- if (note.createdAt.getTime() + 1000 > Date.now()) {
+ const reactionsCount = Object.values(note.reactions).reduce((a, b) => a + b, 0);
+ if (reactionsCount === 0) return undefined;
+ if (note.reactionAndUserPairCache && reactionsCount <= note.reactionAndUserPairCache.length) {
+ const pair = note.reactionAndUserPairCache.find(p => p.startsWith(meId));
+ if (pair) {
+ return this.reactionService.convertLegacyReaction(pair.split('/')[1]);
+ } else {
+ return undefined;
+ }
+ }
+
+ // パフォーマンスのためノートが作成されてから2秒以上経っていない場合はリアクションを取得しない
+ if (this.idService.parse(note.id).date.getTime() + 2000 > Date.now()) {
return undefined;
}
@@ -294,8 +290,9 @@ export class NoteEntityService implements OnModuleInit {
options?: {
detail?: boolean;
skipHide?: boolean;
+ withReactionAndUserPairCache?: boolean;
_hint_?: {
- myReactions: Map;
+ myReactions: Map;
packedFiles: Map | null>;
};
},
@@ -303,6 +300,7 @@ export class NoteEntityService implements OnModuleInit {
const opts = Object.assign({
detail: true,
skipHide: false,
+ withReactionAndUserPairCache: false,
}, options);
const meId = me ? me.id : null;
@@ -329,7 +327,7 @@ export class NoteEntityService implements OnModuleInit {
const packed: Packed<'Note'> = await awaitAll({
id: note.id,
- createdAt: note.createdAt.toISOString(),
+ createdAt: this.idService.parse(note.id).date.toISOString(),
userId: note.userId,
user: this.userEntityService.pack(note.user ?? note.userId, me, {
detail: false,
@@ -337,13 +335,14 @@ export class NoteEntityService implements OnModuleInit {
text: text,
cw: note.cw,
visibility: note.visibility,
- localOnly: note.localOnly ?? undefined,
+ localOnly: note.localOnly,
reactionAcceptance: note.reactionAcceptance,
visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined,
renoteCount: note.renoteCount,
repliesCount: note.repliesCount,
reactions: this.reactionService.convertLegacyReactions(note.reactions),
reactionEmojis: this.customEmojiService.populateEmojis(reactionEmojiNames, host),
+ reactionAndUserPairCache: opts.withReactionAndUserPairCache ? note.reactionAndUserPairCache : undefined,
emojis: host != null ? this.customEmojiService.populateEmojis(note.emojis, host) : undefined,
tags: note.tags.length > 0 ? note.tags : undefined,
fileIds: note.fileIds,
@@ -355,49 +354,38 @@ export class NoteEntityService implements OnModuleInit {
id: channel.id,
name: channel.name,
color: channel.color,
+ isSensitive: channel.isSensitive,
+ allowRenoteToExternal: channel.allowRenoteToExternal,
} : undefined,
mentions: note.mentions.length > 0 ? note.mentions : undefined,
uri: note.uri ?? undefined,
url: note.url ?? undefined,
...(opts.detail ? {
+ clippedCount: note.clippedCount,
+
reply: note.replyId ? this.pack(note.reply ?? note.replyId, me, {
detail: false,
+ skipHide: opts.skipHide,
+ withReactionAndUserPairCache: opts.withReactionAndUserPairCache,
_hint_: options?._hint_,
}) : undefined,
renote: note.renoteId ? this.pack(note.renote ?? note.renoteId, me, {
detail: true,
+ skipHide: opts.skipHide,
+ withReactionAndUserPairCache: opts.withReactionAndUserPairCache,
_hint_: options?._hint_,
}) : undefined,
poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,
- ...(meId ? {
+ ...(meId && Object.keys(note.reactions).length > 0 ? {
myReaction: this.populateMyReaction(note, meId, options?._hint_),
} : {}),
} : {}),
});
- if (packed.user.isCat && packed.text) {
- const tokens = packed.text ? mfm.parse(packed.text) : [];
- function nyaizeNode(node: mfm.MfmNode) {
- if (node.type === 'quote') return;
- if (node.type === 'text') {
- node.props.text = nyaize(node.props.text);
- }
- if (node.children) {
- for (const child of node.children) {
- nyaizeNode(child);
- }
- }
- }
- for (const node of tokens) {
- nyaizeNode(node);
- }
- packed.text = mfm.toString(tokens);
- }
-
if (!opts.skipHide) {
await this.hideNote(packed, meId);
}
@@ -417,18 +405,48 @@ export class NoteEntityService implements OnModuleInit {
if (notes.length === 0) return [];
const meId = me ? me.id : null;
- const myReactionsMap = new Map();
+ const myReactionsMap = new Map();
if (meId) {
- const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!);
- // パフォーマンスのためノートが作成されてから1秒以上経っていない場合はリアクションを取得しない
- const targets = [...notes.filter(n => n.createdAt.getTime() + 1000 < Date.now()).map(n => n.id), ...renoteIds];
- const myReactions = await this.noteReactionsRepository.findBy({
- userId: meId,
- noteId: In(targets),
- });
+ const idsNeedFetchMyReaction = new Set();
- for (const target of targets) {
- myReactionsMap.set(target, myReactions.find(reaction => reaction.noteId === target) ?? null);
+ // パフォーマンスのためノートが作成されてから2秒以上経っていない場合はリアクションを取得しない
+ const oldId = this.idService.gen(Date.now() - 2000);
+
+ for (const note of notes) {
+ if (note.renote && (note.text == null && note.fileIds.length === 0)) { // pure renote
+ const reactionsCount = Object.values(note.renote.reactions).reduce((a, b) => a + b, 0);
+ if (reactionsCount === 0) {
+ myReactionsMap.set(note.renote.id, null);
+ } else if (reactionsCount <= note.renote.reactionAndUserPairCache.length) {
+ const pair = note.renote.reactionAndUserPairCache.find(p => p.startsWith(meId));
+ myReactionsMap.set(note.renote.id, pair ? pair.split('/')[1] : null);
+ } else {
+ idsNeedFetchMyReaction.add(note.renote.id);
+ }
+ } else {
+ if (note.id < oldId) {
+ const reactionsCount = Object.values(note.reactions).reduce((a, b) => a + b, 0);
+ if (reactionsCount === 0) {
+ myReactionsMap.set(note.id, null);
+ } else if (reactionsCount <= note.reactionAndUserPairCache.length) {
+ const pair = note.reactionAndUserPairCache.find(p => p.startsWith(meId));
+ myReactionsMap.set(note.id, pair ? pair.split('/')[1] : null);
+ } else {
+ idsNeedFetchMyReaction.add(note.id);
+ }
+ } else {
+ myReactionsMap.set(note.id, null);
+ }
+ }
+ }
+
+ const myReactions = idsNeedFetchMyReaction.size > 0 ? await this.noteReactionsRepository.findBy({
+ userId: meId,
+ noteId: In(Array.from(idsNeedFetchMyReaction)),
+ }) : [];
+
+ for (const id of idsNeedFetchMyReaction) {
+ myReactionsMap.set(id, myReactions.find(reaction => reaction.noteId === id)?.reaction ?? null);
}
}
@@ -473,25 +491,10 @@ export class NoteEntityService implements OnModuleInit {
}
@bindThis
- public async countSameRenotes(userId: string, renoteId: string, excludeNoteId: string | undefined): Promise {
- // 指定したユーザーの指定したノートのリノートがいくつあるか数える
- const query = this.notesRepository.createQueryBuilder('note')
- .where('note.userId = :userId', { userId })
- .andWhere('note.renoteId = :renoteId', { renoteId });
-
- // 指定した投稿を除く
- if (excludeNoteId) {
- query.andWhere('note.id != :excludeNoteId', { excludeNoteId });
- }
-
- return await query.getCount();
- }
-
- @bindThis
- private async findNoteOrFail(id: string): Promise {
- return await this.notesRepository.findOneOrFail({
+ private findNoteOrFail(id: string): Promise {
+ return this.notesRepository.findOneOrFail({
where: { id },
- relations: ["user"],
+ relations: ['user'],
});
}
}
diff --git a/packages/backend/src/core/entities/NoteFavoriteEntityService.ts b/packages/backend/src/core/entities/NoteFavoriteEntityService.ts
index 51dd976ba0..936e734811 100644
--- a/packages/backend/src/core/entities/NoteFavoriteEntityService.ts
+++ b/packages/backend/src/core/entities/NoteFavoriteEntityService.ts
@@ -5,11 +5,12 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { NoteFavoritesRepository } from '@/models/index.js';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiNoteFavorite } from '@/models/entities/NoteFavorite.js';
+import type { NoteFavoritesRepository } from '@/models/_.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiNoteFavorite } from '@/models/NoteFavorite.js';
import { bindThis } from '@/decorators.js';
import { Packed } from '@/misc/json-schema.js';
+import { IdService } from '@/core/IdService.js';
import { NoteEntityService } from './NoteEntityService.js';
@Injectable()
@@ -19,6 +20,7 @@ export class NoteFavoriteEntityService {
private noteFavoritesRepository: NoteFavoritesRepository,
private noteEntityService: NoteEntityService,
+ private idService: IdService,
) {
}
@@ -31,7 +33,7 @@ export class NoteFavoriteEntityService {
return {
id: favorite.id,
- createdAt: favorite.createdAt.toISOString(),
+ createdAt: this.idService.parse(favorite.id).date.toISOString(),
noteId: favorite.noteId,
note: await this.noteEntityService.pack(favorite.note ?? favorite.noteId, me),
};
@@ -40,7 +42,7 @@ export class NoteFavoriteEntityService {
@bindThis
public async packMany(
favorites: (MiNoteFavorite['id'] | MiNoteFavorite)[],
- me: { id: MiUser['id'] } | null | undefined,
+ me: { id: MiUser['id'] },
) : Promise[]> {
return (await Promise.allSettled(favorites.map(x => this.pack(x, me))))
.filter(result => result.status === 'fulfilled')
diff --git a/packages/backend/src/core/entities/NoteReactionEntityService.ts b/packages/backend/src/core/entities/NoteReactionEntityService.ts
index 838910df3b..1f676f7823 100644
--- a/packages/backend/src/core/entities/NoteReactionEntityService.ts
+++ b/packages/backend/src/core/entities/NoteReactionEntityService.ts
@@ -6,12 +6,13 @@
import { Inject, Injectable } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { DI } from '@/di-symbols.js';
-import type { NoteReactionsRepository } from '@/models/index.js';
+import type { NoteReactionsRepository } from '@/models/_.js';
import type { Packed } from '@/misc/json-schema.js';
import { bindThis } from '@/decorators.js';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiNoteReaction } from '@/models/entities/NoteReaction.js';
+import { IdService } from '@/core/IdService.js';
import type { OnModuleInit } from '@nestjs/common';
+import type { MiUser } from '@/models/User.js';
+import type { MiNoteReaction } from '@/models/NoteReaction.js';
import type { ReactionService } from '../ReactionService.js';
import type { UserEntityService } from './UserEntityService.js';
import type { NoteEntityService } from './NoteEntityService.js';
@@ -21,6 +22,7 @@ export class NoteReactionEntityService implements OnModuleInit {
private userEntityService: UserEntityService;
private noteEntityService: NoteEntityService;
private reactionService: ReactionService;
+ private idService: IdService;
constructor(
private moduleRef: ModuleRef,
@@ -31,6 +33,7 @@ export class NoteReactionEntityService implements OnModuleInit {
//private userEntityService: UserEntityService,
//private noteEntityService: NoteEntityService,
//private reactionService: ReactionService,
+ //private idService: IdService,
) {
}
@@ -38,6 +41,7 @@ export class NoteReactionEntityService implements OnModuleInit {
this.userEntityService = this.moduleRef.get('UserEntityService');
this.noteEntityService = this.moduleRef.get('NoteEntityService');
this.reactionService = this.moduleRef.get('ReactionService');
+ this.idService = this.moduleRef.get('IdService');
}
@bindThis
@@ -56,7 +60,7 @@ export class NoteReactionEntityService implements OnModuleInit {
return {
id: reaction.id,
- createdAt: reaction.createdAt.toISOString(),
+ createdAt: this.idService.parse(reaction.id).date.toISOString(),
user: await this.userEntityService.pack(reaction.user ?? reaction.userId, me),
type: this.reactionService.convertLegacyReaction(reaction.reaction),
...(opts.withNote ? {
diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts
index 149d740ff5..b9e5e28297 100644
--- a/packages/backend/src/core/entities/NotificationEntityService.ts
+++ b/packages/backend/src/core/entities/NotificationEntityService.ts
@@ -7,27 +7,21 @@ import { Inject, Injectable } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
-import type {
- AccessTokensRepository,
- FollowRequestsRepository,
- NoteReactionsRepository,
- NotesRepository,
- UsersRepository,
-} from '@/models/index.js';
+import type { FollowRequestsRepository, NotesRepository, MiUser, UsersRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
-import type { MiNotification } from '@/models/entities/Notification.js';
-import type { MiNote } from '@/models/entities/Note.js';
+import type { MiGroupedNotification, MiNotification } from '@/models/Notification.js';
+import type { MiNote } from '@/models/Note.js';
import type { Packed } from '@/misc/json-schema.js';
import { bindThis } from '@/decorators.js';
import { isNotNull } from '@/misc/is-not-null.js';
-import { notificationTypes } from '@/types.js';
-import type { MiUser } from '@/models/entities/User.js';
+import { FilterUnionByProperty, notificationTypes } from '@/types.js';
import type { OnModuleInit } from '@nestjs/common';
import type { CustomEmojiService } from '../CustomEmojiService.js';
import type { UserEntityService } from './UserEntityService.js';
import type { NoteEntityService } from './NoteEntityService.js';
-const NOTE_REQUIRED_NOTIFICATION_TYPES = new Set(['mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded'] as (typeof notificationTypes[number])[]);
+const NOTE_REQUIRED_NOTIFICATION_TYPES = new Set(['note', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded'] as (typeof notificationTypes[number])[]);
+const NOTE_REQUIRED_GROUPED_NOTIFICATION_TYPES = new Set(['note', 'mention', 'reply', 'renote', 'renote:grouped', 'quote', 'reaction', 'reaction:grouped', 'pollEnded']);
@Injectable()
export class NotificationEntityService implements OnModuleInit {
@@ -44,15 +38,9 @@ export class NotificationEntityService implements OnModuleInit {
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
- @Inject(DI.noteReactionsRepository)
- private noteReactionsRepository: NoteReactionsRepository,
-
@Inject(DI.followRequestsRepository)
private followRequestsRepository: FollowRequestsRepository,
- @Inject(DI.accessTokensRepository)
- private accessTokensRepository: AccessTokensRepository,
-
//private userEntityService: UserEntityService,
//private noteEntityService: NoteEntityService,
//private customEmojiService: CustomEmojiService,
@@ -79,18 +67,17 @@ export class NotificationEntityService implements OnModuleInit {
},
): Promise> {
const notification = src;
- const token = notification.appAccessTokenId ? await this.accessTokensRepository.findOneByOrFail({ id: notification.appAccessTokenId }) : null;
- const noteIfNeed = NOTE_REQUIRED_NOTIFICATION_TYPES.has(notification.type) && notification.noteId != null ? (
+ const noteIfNeed = NOTE_REQUIRED_NOTIFICATION_TYPES.has(notification.type) && 'noteId' in notification ? (
hint?.packedNotes != null
? hint.packedNotes.get(notification.noteId)
- : this.noteEntityService.pack(notification.noteId!, { id: meId }, {
+ : this.noteEntityService.pack(notification.noteId, { id: meId }, {
detail: true,
})
) : undefined;
- const userIfNeed = notification.notifierId != null ? (
+ const userIfNeed = 'notifierId' in notification ? (
hint?.packedUsers != null
? hint.packedUsers.get(notification.notifierId)
- : this.userEntityService.pack(notification.notifierId!, { id: meId }, {
+ : this.userEntityService.pack(notification.notifierId, { id: meId }, {
detail: false,
})
) : undefined;
@@ -99,7 +86,7 @@ export class NotificationEntityService implements OnModuleInit {
id: notification.id,
createdAt: new Date(notification.createdAt).toISOString(),
type: notification.type,
- userId: notification.notifierId,
+ userId: 'notifierId' in notification ? notification.notifierId : undefined,
...(userIfNeed != null ? { user: userIfNeed } : {}),
...(noteIfNeed != null ? { note: noteIfNeed } : {}),
...(notification.type === 'reaction' ? {
@@ -110,8 +97,8 @@ export class NotificationEntityService implements OnModuleInit {
} : {}),
...(notification.type === 'app' ? {
body: notification.customBody,
- header: notification.customHeader ?? token?.name,
- icon: notification.customIcon ?? token?.iconUrl,
+ header: notification.customHeader,
+ icon: notification.customIcon,
} : {}),
});
}
@@ -125,7 +112,7 @@ export class NotificationEntityService implements OnModuleInit {
let validNotifications = notifications;
- const noteIds = validNotifications.map(x => x.noteId).filter(isNotNull);
+ const noteIds = validNotifications.map(x => 'noteId' in x ? x.noteId : null).filter(isNotNull);
const notes = noteIds.length > 0 ? await this.notesRepository.find({
where: { id: In(noteIds) },
relations: ['user', 'reply', 'reply.user', 'renote', 'renote.user'],
@@ -135,9 +122,9 @@ export class NotificationEntityService implements OnModuleInit {
});
const packedNotes = new Map(packedNotesArray.map(p => [p.id, p]));
- validNotifications = validNotifications.filter(x => x.noteId == null || packedNotes.has(x.noteId));
+ validNotifications = validNotifications.filter(x => !('noteId' in x) || packedNotes.has(x.noteId));
- const userIds = validNotifications.map(x => x.notifierId).filter(isNotNull);
+ const userIds = validNotifications.map(x => 'notifierId' in x ? x.notifierId : null).filter(isNotNull);
const users = userIds.length > 0 ? await this.usersRepository.find({
where: { id: In(userIds) },
}) : [];
@@ -147,10 +134,10 @@ export class NotificationEntityService implements OnModuleInit {
const packedUsers = new Map(packedUsersArray.map(p => [p.id, p]));
// 既に解決されたフォローリクエストの通知を除外
- const followRequestNotifications = validNotifications.filter(x => x.type === 'receiveFollowRequest');
+ const followRequestNotifications = validNotifications.filter((x): x is FilterUnionByProperty => x.type === 'receiveFollowRequest');
if (followRequestNotifications.length > 0) {
const reqs = await this.followRequestsRepository.find({
- where: { followerId: In(followRequestNotifications.map(x => x.notifierId!)) },
+ where: { followerId: In(followRequestNotifications.map(x => x.notifierId)) },
});
validNotifications = validNotifications.filter(x => (x.type !== 'receiveFollowRequest') || reqs.some(r => r.followerId === x.notifierId));
}
@@ -159,4 +146,141 @@ export class NotificationEntityService implements OnModuleInit {
.filter(result => result.status === 'fulfilled')
.map(result => (result as PromiseFulfilledResult>).value);
}
+
+ @bindThis
+ public async packGrouped(
+ src: MiGroupedNotification,
+ meId: MiUser['id'],
+ // eslint-disable-next-line @typescript-eslint/ban-types
+ options: {
+
+ },
+ hint?: {
+ packedNotes: Map>;
+ packedUsers: Map>;
+ },
+ ): Promise> {
+ const notification = src;
+ const noteIfNeed = NOTE_REQUIRED_GROUPED_NOTIFICATION_TYPES.has(notification.type) && 'noteId' in notification ? (
+ hint?.packedNotes != null
+ ? hint.packedNotes.get(notification.noteId)
+ : this.noteEntityService.pack(notification.noteId, { id: meId }, {
+ detail: true,
+ })
+ ) : undefined;
+ const userIfNeed = 'notifierId' in notification ? (
+ hint?.packedUsers != null
+ ? hint.packedUsers.get(notification.notifierId)
+ : this.userEntityService.pack(notification.notifierId, { id: meId }, {
+ detail: false,
+ })
+ ) : undefined;
+
+ if (notification.type === 'reaction:grouped') {
+ const reactions = await Promise.all(notification.reactions.map(async reaction => {
+ const user = hint?.packedUsers != null
+ ? hint.packedUsers.get(reaction.userId)!
+ : await this.userEntityService.pack(reaction.userId, { id: meId }, {
+ detail: false,
+ });
+ return {
+ user,
+ reaction: reaction.reaction,
+ };
+ }));
+ return await awaitAll({
+ id: notification.id,
+ createdAt: new Date(notification.createdAt).toISOString(),
+ type: notification.type,
+ note: noteIfNeed,
+ reactions,
+ });
+ } else if (notification.type === 'renote:grouped') {
+ const users = await Promise.all(notification.userIds.map(userId => {
+ const user = hint?.packedUsers != null
+ ? hint.packedUsers.get(userId)
+ : this.userEntityService.pack(userId!, { id: meId }, {
+ detail: false,
+ });
+ return user;
+ }));
+ return await awaitAll({
+ id: notification.id,
+ createdAt: new Date(notification.createdAt).toISOString(),
+ type: notification.type,
+ note: noteIfNeed,
+ users,
+ });
+ }
+
+ return await awaitAll({
+ id: notification.id,
+ createdAt: new Date(notification.createdAt).toISOString(),
+ type: notification.type,
+ userId: 'notifierId' in notification ? notification.notifierId : undefined,
+ ...(userIfNeed != null ? { user: userIfNeed } : {}),
+ ...(noteIfNeed != null ? { note: noteIfNeed } : {}),
+ ...(notification.type === 'reaction' ? {
+ reaction: notification.reaction,
+ } : {}),
+ ...(notification.type === 'achievementEarned' ? {
+ achievement: notification.achievement,
+ } : {}),
+ ...(notification.type === 'app' ? {
+ body: notification.customBody,
+ header: notification.customHeader,
+ icon: notification.customIcon,
+ } : {}),
+ });
+ }
+
+ @bindThis
+ public async packGroupedMany(
+ notifications: MiGroupedNotification[],
+ meId: MiUser['id'],
+ ) {
+ if (notifications.length === 0) return [];
+
+ let validNotifications = notifications;
+
+ const noteIds = validNotifications.map(x => 'noteId' in x ? x.noteId : null).filter(isNotNull);
+ const notes = noteIds.length > 0 ? await this.notesRepository.find({
+ where: { id: In(noteIds) },
+ relations: ['user', 'reply', 'reply.user', 'renote', 'renote.user'],
+ }) : [];
+ const packedNotesArray = await this.noteEntityService.packMany(notes, { id: meId }, {
+ detail: true,
+ });
+ const packedNotes = new Map(packedNotesArray.map(p => [p.id, p]));
+
+ validNotifications = validNotifications.filter(x => !('noteId' in x) || packedNotes.has(x.noteId));
+
+ const userIds = [];
+ for (const notification of validNotifications) {
+ if ('notifierId' in notification) userIds.push(notification.notifierId);
+ if (notification.type === 'reaction:grouped') userIds.push(...notification.reactions.map(x => x.userId));
+ if (notification.type === 'renote:grouped') userIds.push(...notification.userIds);
+ }
+ const users = userIds.length > 0 ? await this.usersRepository.find({
+ where: { id: In(userIds) },
+ }) : [];
+ const packedUsersArray = await this.userEntityService.packMany(users, { id: meId }, {
+ detail: false,
+ });
+ const packedUsers = new Map(packedUsersArray.map(p => [p.id, p]));
+
+ // 既に解決されたフォローリクエストの通知を除外
+ const followRequestNotifications = validNotifications.filter((x): x is FilterUnionByProperty => x.type === 'receiveFollowRequest');
+ if (followRequestNotifications.length > 0) {
+ const reqs = await this.followRequestsRepository.find({
+ where: { followerId: In(followRequestNotifications.map(x => x.notifierId)) },
+ });
+ validNotifications = validNotifications.filter(x => (x.type !== 'receiveFollowRequest') || reqs.some(r => r.followerId === x.notifierId));
+ }
+
+ return await Promise.all(validNotifications.map(x => this.packGrouped(x, meId, {}, {
+ packedNotes,
+ packedUsers,
+ })));
+ }
}
diff --git a/packages/backend/src/core/entities/PageEntityService.ts b/packages/backend/src/core/entities/PageEntityService.ts
index c92f0ec2ae..d387d068cd 100644
--- a/packages/backend/src/core/entities/PageEntityService.ts
+++ b/packages/backend/src/core/entities/PageEntityService.ts
@@ -5,13 +5,14 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { DriveFilesRepository, PageLikesRepository, PagesRepository } from '@/models/index.js';
+import type { DriveFilesRepository, PagesRepository, PageLikesRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/json-schema.js';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiPage } from '@/models/entities/Page.js';
-import type { MiDriveFile } from '@/models/entities/DriveFile.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiPage } from '@/models/Page.js';
+import type { MiDriveFile } from '@/models/DriveFile.js';
import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
import { UserEntityService } from './UserEntityService.js';
import { DriveFileEntityService } from './DriveFileEntityService.js';
@@ -29,6 +30,7 @@ export class PageEntityService {
private userEntityService: UserEntityService,
private driveFileEntityService: DriveFileEntityService,
+ private idService: IdService,
) {
}
@@ -84,7 +86,7 @@ export class PageEntityService {
return await awaitAll({
id: page.id,
- createdAt: page.createdAt.toISOString(),
+ createdAt: this.idService.parse(page.id).date.toISOString(),
updatedAt: page.updatedAt.toISOString(),
userId: page.userId,
user: this.userEntityService.pack(page.user ?? page.userId, me), // { detail: true } すると無限ループするので注意
diff --git a/packages/backend/src/core/entities/PageLikeEntityService.ts b/packages/backend/src/core/entities/PageLikeEntityService.ts
index fefaf387a8..6de5750f5e 100644
--- a/packages/backend/src/core/entities/PageLikeEntityService.ts
+++ b/packages/backend/src/core/entities/PageLikeEntityService.ts
@@ -5,9 +5,9 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { PageLikesRepository } from '@/models/index.js';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiPageLike } from '@/models/entities/PageLike.js';
+import type { PageLikesRepository } from '@/models/_.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiPageLike } from '@/models/PageLike.js';
import { bindThis } from '@/decorators.js';
import { Packed } from '@/misc/json-schema.js';
import { PageEntityService } from './PageEntityService.js';
@@ -38,7 +38,7 @@ export class PageLikeEntityService {
@bindThis
public async packMany(
likes: (MiPageLike['id'] | MiPageLike)[],
- me: { id: MiUser['id'] } | null | undefined,
+ me: { id: MiUser['id'] },
) : Promise[]> {
return (await Promise.allSettled(likes.map(x => this.pack(x, me))))
.filter(result => result.status === 'fulfilled')
diff --git a/packages/backend/src/core/entities/RenoteMutingEntityService.ts b/packages/backend/src/core/entities/RenoteMutingEntityService.ts
index 9e47f3a36e..b7440a68f2 100644
--- a/packages/backend/src/core/entities/RenoteMutingEntityService.ts
+++ b/packages/backend/src/core/entities/RenoteMutingEntityService.ts
@@ -5,12 +5,13 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { RenoteMutingsRepository } from '@/models/index.js';
+import type { RenoteMutingsRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/json-schema.js';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiRenoteMuting } from '@/models/entities/RenoteMuting.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiRenoteMuting } from '@/models/RenoteMuting.js';
import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
@@ -20,6 +21,7 @@ export class RenoteMutingEntityService {
private renoteMutingsRepository: RenoteMutingsRepository,
private userEntityService: UserEntityService,
+ private idService: IdService,
) {
}
@@ -32,7 +34,7 @@ export class RenoteMutingEntityService {
return await awaitAll({
id: muting.id,
- createdAt: muting.createdAt.toISOString(),
+ createdAt: this.idService.parse(muting.id).date.toISOString(),
muteeId: muting.muteeId,
mutee: this.userEntityService.pack(muting.muteeId, me, {
detail: true,
@@ -43,7 +45,7 @@ export class RenoteMutingEntityService {
@bindThis
public async packMany(
mutings: (MiRenoteMuting['id'] | MiRenoteMuting)[],
- me: { id: MiUser['id'] } | null | undefined,
+ me: { id: MiUser['id'] },
) : Promise[]> {
return (await Promise.allSettled(mutings.map(u => this.pack(u, me))))
.filter(result => result.status === 'fulfilled')
diff --git a/packages/backend/src/core/entities/RoleEntityService.ts b/packages/backend/src/core/entities/RoleEntityService.ts
index 10a02f9929..9d72e785c2 100644
--- a/packages/backend/src/core/entities/RoleEntityService.ts
+++ b/packages/backend/src/core/entities/RoleEntityService.ts
@@ -6,14 +6,14 @@
import { Inject, Injectable } from '@nestjs/common';
import { Brackets } from 'typeorm';
import { DI } from '@/di-symbols.js';
-import type { RoleAssignmentsRepository, RolesRepository } from '@/models/index.js';
+import type { RoleAssignmentsRepository, RolesRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
-import type { MiUser } from '@/models/entities/User.js';
-import type { MiRole } from '@/models/entities/Role.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiRole } from '@/models/Role.js';
import { bindThis } from '@/decorators.js';
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
import { Packed } from '@/misc/json-schema.js';
-import { UserEntityService } from './UserEntityService.js';
+import { IdService } from '@/core/IdService.js';
@Injectable()
export class RoleEntityService {
@@ -24,7 +24,7 @@ export class RoleEntityService {
@Inject(DI.roleAssignmentsRepository)
private roleAssignmentsRepository: RoleAssignmentsRepository,
- private userEntityService: UserEntityService,
+ private idService: IdService,
) {
}
@@ -37,9 +37,10 @@ export class RoleEntityService {
const assignedCount = await this.roleAssignmentsRepository.createQueryBuilder('assign')
.where('assign.roleId = :roleId', { roleId: role.id })
- .andWhere(new Brackets(qb => { qb
- .where('assign.expiresAt IS NULL')
- .orWhere('assign.expiresAt > :now', { now: new Date() });
+ .andWhere(new Brackets(qb => {
+ qb
+ .where('assign.expiresAt IS NULL')
+ .orWhere('assign.expiresAt > :now', { now: new Date() });
}))
.getCount();
@@ -54,7 +55,7 @@ export class RoleEntityService {
return await awaitAll({
id: role.id,
- createdAt: role.createdAt.toISOString(),
+ createdAt: this.idService.parse(role.id).date.toISOString(),
updatedAt: role.updatedAt.toISOString(),
name: role.name,
description: role.description,
@@ -77,7 +78,7 @@ export class RoleEntityService {
@bindThis
public async packMany(
roles: (MiRole['id'] | MiRole)[],
- me: { id: MiUser['id'] } | null | undefined,
+ me: { id: MiUser['id'] },
) : Promise[]> {
return (await Promise.allSettled(roles.map(x => this.pack(x, me))))
.filter(result => result.status === 'fulfilled')
diff --git a/packages/backend/src/core/entities/SigninEntityService.ts b/packages/backend/src/core/entities/SigninEntityService.ts
index 12562ff874..09daac8ae9 100644
--- a/packages/backend/src/core/entities/SigninEntityService.ts
+++ b/packages/backend/src/core/entities/SigninEntityService.ts
@@ -3,20 +3,15 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Inject, Injectable } from '@nestjs/common';
-import { DI } from '@/di-symbols.js';
-import type { SigninsRepository } from '@/models/index.js';
-import type { MiSignin } from '@/models/entities/Signin.js';
+import { Injectable } from '@nestjs/common';
+import type { MiSignin } from '@/models/Signin.js';
import { bindThis } from '@/decorators.js';
-import { UserEntityService } from './UserEntityService.js';
+import { IdService } from '@/core/IdService.js';
@Injectable()
export class SigninEntityService {
constructor(
- @Inject(DI.signinsRepository)
- private signinsRepository: SigninsRepository,
-
- private userEntityService: UserEntityService,
+ private idService: IdService,
) {
}
@@ -24,7 +19,13 @@ export class SigninEntityService {
public async pack(
src: MiSignin,
) {
- return src;
+ return {
+ id: src.id,
+ createdAt: this.idService.parse(src.id).date.toISOString(),
+ ip: src.ip,
+ headers: src.headers,
+ success: src.success,
+ };
}
}
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index dad17a830a..aa78995f72 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -13,43 +13,20 @@ import type { Packed } from '@/misc/json-schema.js';
import type { Promiseable } from '@/misc/prelude/await-all.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js';
-import type { MiLocalUser, MiPartialLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/entities/User.js';
-import {
- birthdaySchema,
- descriptionSchema,
- localUsernameSchema,
- locationSchema,
- nameSchema,
- passwordSchema,
-} from '@/models/entities/User.js';
-import type {
- AnnouncementReadsRepository,
- AnnouncementsRepository,
- BlockingsRepository,
- ChannelFollowingsRepository,
- DriveFilesRepository,
- FollowingsRepository,
- FollowRequestsRepository,
- InstancesRepository,
- MutingsRepository,
- NoteUnreadsRepository,
- PagesRepository,
- RenoteMutingsRepository,
- UserMemoRepository,
- UserNotePiningsRepository,
- MiUserProfile,
- UserProfilesRepository,
- UserSecurityKeysRepository,
- UsersRepository,
-} from '@/models/index.js';
+import type { MiLocalUser, MiPartialLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js';
+import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/User.js';
+import { MiNotification } from '@/models/Notification.js';
+import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, UserNotePiningsRepository, UserProfilesRepository, AnnouncementReadsRepository, AnnouncementsRepository, MiUserProfile, RenoteMutingsRepository, UserMemoRepository } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
+import { IdService } from '@/core/IdService.js';
+import type { AnnouncementService } from '@/core/AnnouncementService.js';
+import type { CustomEmojiService } from '@/core/CustomEmojiService.js';
+import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
import type { OnModuleInit } from '@nestjs/common';
-import type { AnnouncementService } from '../AnnouncementService.js';
-import type { CustomEmojiService } from '../CustomEmojiService.js';
import type { NoteEntityService } from './NoteEntityService.js';
import type { DriveFileEntityService } from './DriveFileEntityService.js';
import type { PageEntityService } from './PageEntityService.js';
@@ -87,6 +64,8 @@ export class UserEntityService implements OnModuleInit {
private announcementService: AnnouncementService;
private roleService: RoleService;
private federatedInstanceService: FederatedInstanceService;
+ private idService: IdService;
+ private avatarDecorationService: AvatarDecorationService;
constructor(
private moduleRef: ModuleRef,
@@ -124,36 +103,20 @@ export class UserEntityService implements OnModuleInit {
@Inject(DI.noteUnreadsRepository)
private noteUnreadsRepository: NoteUnreadsRepository,
- @Inject(DI.channelFollowingsRepository)
- private channelFollowingsRepository: ChannelFollowingsRepository,
-
@Inject(DI.userNotePiningsRepository)
private userNotePiningsRepository: UserNotePiningsRepository,
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
- @Inject(DI.instancesRepository)
- private instancesRepository: InstancesRepository,
-
@Inject(DI.announcementReadsRepository)
private announcementReadsRepository: AnnouncementReadsRepository,
@Inject(DI.announcementsRepository)
private announcementsRepository: AnnouncementsRepository,
- @Inject(DI.pagesRepository)
- private pagesRepository: PagesRepository,
-
@Inject(DI.userMemosRepository)
private userMemosRepository: UserMemoRepository,
-
- //private noteEntityService: NoteEntityService,
- //private driveFileEntityService: DriveFileEntityService,
- //private pageEntityService: PageEntityService,
- //private customEmojiService: CustomEmojiService,
- //private antennaService: AntennaService,
- //private roleService: RoleService,
) {
}
@@ -166,6 +129,8 @@ export class UserEntityService implements OnModuleInit {
this.announcementService = this.moduleRef.get('AnnouncementService');
this.roleService = this.moduleRef.get('RoleService');
this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService');
+ this.idService = this.moduleRef.get('IdService');
+ this.avatarDecorationService = this.moduleRef.get('AvatarDecorationService');
}
//#region Validators
@@ -182,65 +147,76 @@ export class UserEntityService implements OnModuleInit {
@bindThis
public async getRelation(me: MiUser['id'], target: MiUser['id']) {
- return awaitAll({
- id: target,
- isFollowing: this.followingsRepository.count({
- where: {
- followerId: me,
- followeeId: target,
- },
- take: 1,
- }).then(n => n > 0),
- isFollowed: this.followingsRepository.count({
+ const [
+ following,
+ isFollowed,
+ hasPendingFollowRequestFromYou,
+ hasPendingFollowRequestToYou,
+ isBlocking,
+ isBlocked,
+ isMuted,
+ isRenoteMuted,
+ ] = await Promise.all([
+ this.followingsRepository.findOneBy({
+ followerId: me,
+ followeeId: target,
+ }),
+ this.followingsRepository.exist({
where: {
followerId: target,
followeeId: me,
},
- take: 1,
- }).then(n => n > 0),
- hasPendingFollowRequestFromYou: this.followRequestsRepository.count({
+ }),
+ this.followRequestsRepository.exist({
where: {
followerId: me,
followeeId: target,
},
- take: 1,
- }).then(n => n > 0),
- hasPendingFollowRequestToYou: this.followRequestsRepository.count({
+ }),
+ this.followRequestsRepository.exist({
where: {
followerId: target,
followeeId: me,
},
- take: 1,
- }).then(n => n > 0),
- isBlocking: this.blockingsRepository.count({
+ }),
+ this.blockingsRepository.exist({
where: {
blockerId: me,
blockeeId: target,
},
- take: 1,
- }).then(n => n > 0),
- isBlocked: this.blockingsRepository.count({
+ }),
+ this.blockingsRepository.exist({
where: {
blockerId: target,
blockeeId: me,
},
- take: 1,
- }).then(n => n > 0),
- isMuted: this.mutingsRepository.count({
+ }),
+ this.mutingsRepository.exist({
where: {
muterId: me,
muteeId: target,
},
- take: 1,
- }).then(n => n > 0),
- isRenoteMuted: this.renoteMutingsRepository.count({
+ }),
+ this.renoteMutingsRepository.exist({
where: {
muterId: me,
muteeId: target,
},
- take: 1,
- }).then(n => n > 0),
- });
+ }),
+ ]);
+
+ return {
+ id: target,
+ following,
+ isFollowing: following != null,
+ isFollowed,
+ hasPendingFollowRequestFromYou,
+ hasPendingFollowRequestToYou,
+ isBlocking,
+ isBlocked,
+ isMuted,
+ isRenoteMuted,
+ };
}
@bindThis
@@ -261,17 +237,34 @@ export class UserEntityService implements OnModuleInit {
}
@bindThis
- public async getHasUnreadNotification(userId: MiUser['id']): Promise {
+ public async getNotificationsInfo(userId: MiUser['id']): Promise<{
+ hasUnread: boolean;
+ unreadCount: number;
+ }> {
+ const response = {
+ hasUnread: false,
+ unreadCount: 0,
+ };
+
const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${userId}`);
- const latestNotificationIdsRes = await this.redisClient.xrevrange(
- `notificationTimeline:${userId}`,
- '+',
- '-',
- 'COUNT', 1);
- const latestNotificationId = latestNotificationIdsRes[0]?.[0];
+ if (!latestReadNotificationId) {
+ response.unreadCount = await this.redisClient.xlen(`notificationTimeline:${userId}`);
+ } else {
+ const latestNotificationIdsRes = await this.redisClient.xrevrange(
+ `notificationTimeline:${userId}`,
+ '+',
+ latestReadNotificationId,
+ );
- return latestNotificationId != null && (latestReadNotificationId == null || latestReadNotificationId < latestNotificationId);
+ response.unreadCount = (latestNotificationIdsRes.length - 1 >= 0) ? latestNotificationIdsRes.length - 1 : 0;
+ }
+
+ if (response.unreadCount > 0) {
+ response.hasUnread = true;
+ }
+
+ return response;
}
@bindThis
@@ -327,24 +320,6 @@ export class UserEntityService implements OnModuleInit {
const user = typeof src === 'object' ? src : await this.usersRepository.findOneByOrFail({ id: src });
- // migration
- if (user.avatarId != null && user.avatarUrl === null) {
- const avatar = await this.driveFilesRepository.findOneByOrFail({ id: user.avatarId });
- user.avatarUrl = this.driveFileEntityService.getPublicUrl(avatar, 'avatar');
- this.usersRepository.update(user.id, {
- avatarUrl: user.avatarUrl,
- avatarBlurhash: avatar.blurhash,
- });
- }
- if (user.bannerId != null && user.bannerUrl === null) {
- const banner = await this.driveFilesRepository.findOneByOrFail({ id: user.bannerId });
- user.bannerUrl = this.driveFileEntityService.getPublicUrl(banner);
- this.usersRepository.update(user.id, {
- bannerUrl: user.bannerUrl,
- bannerBlurhash: banner.blurhash,
- });
- }
-
const meId = me ? me.id : null;
const isMe = meId === user.id;
const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false;
@@ -368,12 +343,12 @@ export class UserEntityService implements OnModuleInit {
(profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount :
null;
- const isModerator = isMe && opts.detail ? await this.roleService.isModerator(user) : null;
- const isAdmin = isMe && opts.detail ? await this.roleService.isAdministrator(user) : null;
+ const isModerator = isMe && opts.detail ? this.roleService.isModerator(user) : null;
+ const isAdmin = isMe && opts.detail ? this.roleService.isAdministrator(user) : null;
const policies = opts.detail ? await this.roleService.getUserPolicies(user.id) : null;
const unreadAnnouncements = isMe && opts.detail ? await this.announcementService.getUnreadAnnouncements(user) : null;
- const falsy = opts.detail ? false : undefined;
+ const notificationsInfo = isMe && opts.detail ? await this.getNotificationsInfo(user.id) : null;
const packed = {
id: user.id,
@@ -382,8 +357,14 @@ export class UserEntityService implements OnModuleInit {
host: user.host,
avatarUrl: user.avatarUrl ?? this.getIdenticonUrl(user),
avatarBlurhash: user.avatarBlurhash,
- isBot: user.isBot ?? falsy,
- isCat: user.isCat ?? falsy,
+ avatarDecorations: user.avatarDecorations.length > 0 ? this.avatarDecorationService.getAll().then(decorations => user.avatarDecorations.filter(ud => decorations.some(d => d.id === ud.id)).map(ud => ({
+ id: ud.id,
+ angle: ud.angle || undefined,
+ flipH: ud.flipH || undefined,
+ url: decorations.find(d => d.id === ud.id)!.url,
+ }))) : [],
+ isBot: user.isBot,
+ isCat: user.isCat,
instance: user.host ? this.federatedInstanceService.federatedInstanceCache.fetch(user.host).then(instance => instance ? {
name: instance.name,
softwareName: instance.softwareName,
@@ -409,7 +390,7 @@ export class UserEntityService implements OnModuleInit {
? Promise.all(user.alsoKnownAs.map(uri => this.apPersonService.fetchPerson(uri).then(user => user?.id).catch(() => null)))
.then(xs => xs.length === 0 ? null : xs.filter(x => x != null) as string[])
: null,
- createdAt: user.createdAt.toISOString(),
+ createdAt: this.idService.parse(user.id).date.toISOString(),
updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null,
lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null,
bannerUrl: user.bannerUrl,
@@ -417,12 +398,13 @@ export class UserEntityService implements OnModuleInit {
isLocked: user.isLocked,
isSilenced: !policies?.canPublicNote,
isLimited: !(policies?.canCreateContent && policies.canUpdateContent && policies.canDeleteContent),
- isSuspended: user.isSuspended ?? falsy,
+ isSuspended: user.isSuspended,
description: profile!.description,
location: profile!.location,
birthday: profile!.birthday,
lang: profile!.lang,
fields: profile!.fields,
+ verifiedLinks: profile!.verifiedLinks,
followersCount: followersCount ?? 0,
followingCount: followingCount ?? 0,
notesCount: user.notesCount,
@@ -473,7 +455,7 @@ export class UserEntityService implements OnModuleInit {
preventAiLearning: profile!.preventAiLearning,
isExplorable: user.isExplorable,
isDeleted: user.isDeleted,
- twoFactorBackupCodes: profile?.twoFactorBackupSecret?.length === 20 ? 'full' : (profile?.twoFactorBackupSecret?.length ?? 0) > 0 ? 'partial' : 'none',
+ twoFactorBackupCodesStock: profile?.twoFactorBackupSecret?.length === 20 ? 'full' : (profile?.twoFactorBackupSecret?.length ?? 0) > 0 ? 'partial' : 'none',
hideOnlineStatus: user.hideOnlineStatus,
hasUnreadSpecifiedNotes: this.noteUnreadsRepository.count({
where: { userId: user.id, isSpecified: true },
@@ -487,11 +469,13 @@ export class UserEntityService implements OnModuleInit {
unreadAnnouncements,
hasUnreadAntenna: this.getHasUnreadAntenna(user.id),
hasUnreadChannel: false, // 後方互換性のため
- hasUnreadNotification: this.getHasUnreadNotification(user.id),
+ hasUnreadNotification: notificationsInfo?.hasUnread, // 後方互換性のため
hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id),
+ unreadNotificationsCount: notificationsInfo?.unreadCount,
mutedWords: profile!.mutedWords,
mutedInstances: profile!.mutedInstances,
- mutingNotificationTypes: profile!.mutingNotificationTypes,
+ mutingNotificationTypes: [], // 後方互換性のため
+ notificationRecieveConfig: profile!.notificationRecieveConfig,
emailNotificationTypes: profile!.emailNotificationTypes,
achievements: profile!.achievements,
loggedInDays: profile!.loggedInDates.length,
@@ -524,6 +508,8 @@ export class UserEntityService implements OnModuleInit {
isBlocked: relation.isBlocked,
isMuted: relation.isMuted,
isRenoteMuted: relation.isRenoteMuted,
+ notify: relation.following?.notify ?? 'none',
+ withReplies: relation.following?.withReplies ?? false,
} : {}),
} as Promiseable> as Promiseable>;
diff --git a/packages/backend/src/core/entities/UserListEntityService.ts b/packages/backend/src/core/entities/UserListEntityService.ts
index 7797ec5d39..c1e945b98f 100644
--- a/packages/backend/src/core/entities/UserListEntityService.ts
+++ b/packages/backend/src/core/entities/UserListEntityService.ts
@@ -5,10 +5,11 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { UserListJoiningsRepository, UserListsRepository } from '@/models/index.js';
+import type { MiUser, MiUserListMembership, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js';
import type { Packed } from '@/misc/json-schema.js';
-import type { MiUserList } from '@/models/entities/UserList.js';
+import type { MiUserList } from '@/models/UserList.js';
import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
@@ -17,10 +18,11 @@ export class UserListEntityService {
@Inject(DI.userListsRepository)
private userListsRepository: UserListsRepository,
- @Inject(DI.userListJoiningsRepository)
- private userListJoiningsRepository: UserListJoiningsRepository,
+ @Inject(DI.userListMembershipsRepository)
+ private userListMembershipsRepository: UserListMembershipsRepository,
private userEntityService: UserEntityService,
+ private idService: IdService,
) {
}
@@ -30,17 +32,41 @@ export class UserListEntityService {
): Promise> {
const userList = typeof src === 'object' ? src : await this.userListsRepository.findOneByOrFail({ id: src });
- const users = await this.userListJoiningsRepository.findBy({
+ const users = await this.userListMembershipsRepository.findBy({
userListId: userList.id,
});
return {
id: userList.id,
- createdAt: userList.createdAt.toISOString(),
+ createdAt: this.idService.parse(userList.id).date.toISOString(),
name: userList.name,
userIds: users.map(x => x.userId),
isPublic: userList.isPublic,
};
}
+
+ @bindThis
+ public async packMemberships(
+ src: MiUserListMembership,
+ me: { id: MiUser['id'] } | null | undefined,
+ ): Promise> {
+ return {
+ id: src.id,
+ createdAt: this.idService.parse(src.id).date.toISOString(),
+ userId: src.userId,
+ user: await this.userEntityService.pack(src.userId, me),
+ withReplies: src.withReplies,
+ };
+ }
+
+ @bindThis
+ public async packMembershipsMany(
+ memberships: MiUserListMembership[],
+ me: { id: MiUser['id'] } | null | undefined,
+ ): Promise[]> {
+ return (await Promise.allSettled(memberships.map(u => this.packMemberships(u, me))))
+ .filter(result => result.status === 'fulfilled')
+ .map(result => (result as PromiseFulfilledResult>).value);
+ }
}
diff --git a/packages/backend/src/daemons/QueueStatsService.ts b/packages/backend/src/daemons/QueueStatsService.ts
index dc9482f022..5edc0f45ab 100644
--- a/packages/backend/src/daemons/QueueStatsService.ts
+++ b/packages/backend/src/daemons/QueueStatsService.ts
@@ -19,7 +19,7 @@ const interval = 10000;
@Injectable()
export class QueueStatsService implements OnApplicationShutdown {
- private intervalId: NodeJS.Timer;
+ private intervalId: NodeJS.Timeout;
constructor(
@Inject(DI.config)
diff --git a/packages/backend/src/daemons/ServerStatsService.ts b/packages/backend/src/daemons/ServerStatsService.ts
index 95026099e8..c5ef9b2fa3 100644
--- a/packages/backend/src/daemons/ServerStatsService.ts
+++ b/packages/backend/src/daemons/ServerStatsService.ts
@@ -20,7 +20,7 @@ const round = (num: number) => Math.round(num * 10) / 10;
@Injectable()
export class ServerStatsService implements OnApplicationShutdown {
- private intervalId: NodeJS.Timer | null = null;
+ private intervalId: NodeJS.Timeout | null = null;
constructor(
private metaService: MetaService,
@@ -108,6 +108,5 @@ async function net() {
// FS STAT
async function fs() {
- const data = await si.disksIO().catch(() => ({ rIO_sec: 0, wIO_sec: 0 }));
- return data ?? { rIO_sec: 0, wIO_sec: 0 };
+ return await si.disksIO().catch(() => ({ rIO_sec: 0, wIO_sec: 0 }));
}
diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts
index e409cedecd..8403f80659 100644
--- a/packages/backend/src/di-symbols.ts
+++ b/packages/backend/src/di-symbols.ts
@@ -19,6 +19,7 @@ export const DI = {
announcementsRepository: Symbol('announcementsRepository'),
announcementReadsRepository: Symbol('announcementReadsRepository'),
appsRepository: Symbol('appsRepository'),
+ avatarDecorationsRepository: Symbol('avatarDecorationsRepository'),
noteFavoritesRepository: Symbol('noteFavoritesRepository'),
noteThreadMutingsRepository: Symbol('noteThreadMutingsRepository'),
noteReactionsRepository: Symbol('noteReactionsRepository'),
@@ -32,7 +33,7 @@ export const DI = {
userPublickeysRepository: Symbol('userPublickeysRepository'),
userListsRepository: Symbol('userListsRepository'),
userListFavoritesRepository: Symbol('userListFavoritesRepository'),
- userListJoiningsRepository: Symbol('userListJoiningsRepository'),
+ userListMembershipsRepository: Symbol('userListMembershipsRepository'),
userNotePiningsRepository: Symbol('userNotePiningsRepository'),
userIpsRepository: Symbol('userIpsRepository'),
usedUsernamesRepository: Symbol('usedUsernamesRepository'),
@@ -65,7 +66,6 @@ export const DI = {
promoNotesRepository: Symbol('promoNotesRepository'),
promoReadsRepository: Symbol('promoReadsRepository'),
relaysRepository: Symbol('relaysRepository'),
- mutedNotesRepository: Symbol('mutedNotesRepository'),
channelsRepository: Symbol('channelsRepository'),
channelFollowingsRepository: Symbol('channelFollowingsRepository'),
channelFavoritesRepository: Symbol('channelFavoritesRepository'),
diff --git a/packages/backend/src/logger.ts b/packages/backend/src/logger.ts
index 92ea3d13a1..5c10559ec6 100644
--- a/packages/backend/src/logger.ts
+++ b/packages/backend/src/logger.ts
@@ -18,6 +18,7 @@ type Context = {
type Level = 'error' | 'success' | 'warning' | 'debug' | 'info';
+// eslint-disable-next-line import/no-default-export
export default class Logger {
private context: Context;
private parentLogger: Logger | null = null;
diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts
index 5b841fbaa1..742dad0857 100644
--- a/packages/backend/src/misc/cache.ts
+++ b/packages/backend/src/misc/cache.ts
@@ -196,7 +196,7 @@ function nothingToDo(value: T): V {
export class MemoryKVCache {
public cache: Map;
private lifetime: number;
- private gcIntervalHandle: NodeJS.Timer;
+ private gcIntervalHandle: NodeJS.Timeout;
private toMapConverter: (value: T) => V;
private fromMapConverter: (cached: V) => T | undefined;
diff --git a/packages/backend/src/misc/check-word-mute.ts b/packages/backend/src/misc/check-word-mute.ts
index a7a60c20e9..cef5595451 100644
--- a/packages/backend/src/misc/check-word-mute.ts
+++ b/packages/backend/src/misc/check-word-mute.ts
@@ -5,8 +5,8 @@
import { AhoCorasick } from 'slacc';
import RE2 from 're2';
-import type { MiNote } from '@/models/entities/Note.js';
-import type { MiUser } from '@/models/entities/User.js';
+import type { MiNote } from '@/models/Note.js';
+import type { MiUser } from '@/models/User.js';
type NoteLike = {
userId: MiNote['userId'];
diff --git a/packages/backend/src/misc/correct-filename.ts b/packages/backend/src/misc/correct-filename.ts
index a702f0be0d..9130af44c3 100644
--- a/packages/backend/src/misc/correct-filename.ts
+++ b/packages/backend/src/misc/correct-filename.ts
@@ -3,18 +3,56 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-// 与えられた拡張子とファイル名が一致しているかどうかを確認し、
-// 一致していない場合は拡張子を付与して返す
+/**
+ * Array.includes()よりSet.has()の方が高速
+ */
+const targetExtsToSkip = new Set([
+ '.gz',
+ '.tar',
+ '.tgz',
+ '.bz2',
+ '.xz',
+ '.zip',
+ '.7z',
+]);
+
+const extRegExp = /\.[0-9a-zA-Z]+$/i;
+
+/**
+ * 与えられた拡張子とファイル名が一致しているかどうかを確認し、
+ * 一致していない場合は拡張子を付与して返す
+ *
+ * extはfile-typeのextを想定
+ */
export function correctFilename(filename: string, ext: string | null) {
- const dotExt = ext ? ext.startsWith('.') ? ext : `.${ext}` : '.unknown';
- if (filename.endsWith(dotExt)) {
- return filename;
- }
- if (ext === 'jpg' && filename.endsWith('.jpeg')) {
- return filename;
- }
- if (ext === 'tif' && filename.endsWith('.tiff')) {
+ const dotExt = ext ? ext[0] === '.' ? ext : `.${ext}` : '.unknown';
+
+ const match = extRegExp.exec(filename);
+ if (!match || !match[0]) {
+ // filenameが拡張子を持っていない場合は拡張子をつける
+ return `${filename}${dotExt}`;
+ }
+
+ const filenameExt = match[0].toLowerCase();
+ if (
+ // 未知のファイル形式かつ拡張子がある場合は何もしない
+ ext === null ||
+ // 拡張子が一致している場合は何もしない
+ filenameExt === dotExt ||
+
+ // jpeg, tiffを同一視
+ dotExt === '.jpg' && filenameExt === '.jpeg' ||
+ dotExt === '.tif' && filenameExt === '.tiff' ||
+ // dllもexeもportable executableなので判定が正しく行われない
+ dotExt === '.exe' && filenameExt === '.dll' ||
+
+ // 圧縮形式っぽければ下手に拡張子を変えない
+ // https://github.com/misskey-dev/misskey/issues/11482
+ targetExtsToSkip.has(dotExt)
+ ) {
return filename;
}
+
+ // 拡張子があるが一致していないなどの場合は拡張子を付け足す
return `${filename}${dotExt}`;
}
diff --git a/packages/backend/src/misc/generate-native-user-token.ts b/packages/backend/src/misc/generate-native-user-token.ts
index bfde2bc489..094c625120 100644
--- a/packages/backend/src/misc/generate-native-user-token.ts
+++ b/packages/backend/src/misc/generate-native-user-token.ts
@@ -5,4 +5,5 @@
import { secureRndstr } from '@/misc/secure-rndstr.js';
+// eslint-disable-next-line import/no-default-export
export default () => secureRndstr(16);
diff --git a/packages/backend/src/misc/get-reaction-emoji.ts b/packages/backend/src/misc/get-reaction-emoji.ts
index eac6630968..80ef7ff7bc 100644
--- a/packages/backend/src/misc/get-reaction-emoji.ts
+++ b/packages/backend/src/misc/get-reaction-emoji.ts
@@ -3,6 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
+// eslint-disable-next-line import/no-default-export
export default function(reaction: string): string {
switch (reaction) {
case 'like': return '👍';
diff --git a/packages/backend/src/misc/id/aid.ts b/packages/backend/src/misc/id/aid.ts
index ec8aa849c9..e7b59f262b 100644
--- a/packages/backend/src/misc/id/aid.ts
+++ b/packages/backend/src/misc/id/aid.ts
@@ -24,8 +24,7 @@ function getNoise(): string {
return counter.toString(36).padStart(2, '0').slice(-2);
}
-export function genAid(date: Date): string {
- const t = date.getTime();
+export function genAid(t: number): string {
if (isNaN(t)) throw new Error('Failed to create AID: Invalid Date');
counter++;
return getTime(t) + getNoise();
diff --git a/packages/backend/src/misc/id/aidx.ts b/packages/backend/src/misc/id/aidx.ts
new file mode 100644
index 0000000000..bed223225a
--- /dev/null
+++ b/packages/backend/src/misc/id/aidx.ts
@@ -0,0 +1,43 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+// AIDX
+// 長さ8の[2000年1月1日からの経過ミリ秒をbase36でエンコードしたもの] + 長さ4の[個体ID] + 長さ4の[カウンタ]
+// (c) mei23
+// https://misskey.m544.net/notes/71899acdcc9859ec5708ac24
+
+import { customAlphabet } from 'nanoid';
+
+export const aidxRegExp = /^[0-9a-z]{16}$/;
+
+const TIME2000 = 946684800000;
+const TIME_LENGTH = 8;
+const NODE_LENGTH = 4;
+const NOISE_LENGTH = 4;
+
+const nodeId = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', NODE_LENGTH)();
+let counter = 0;
+
+function getTime(time: number): string {
+ time = time - TIME2000;
+ if (time < 0) time = 0;
+
+ return time.toString(36).padStart(TIME_LENGTH, '0').slice(-TIME_LENGTH);
+}
+
+function getNoise(): string {
+ return counter.toString(36).padStart(NOISE_LENGTH, '0').slice(-NOISE_LENGTH);
+}
+
+export function genAidx(t: number): string {
+ if (isNaN(t)) throw new Error('Failed to create AIDX: Invalid Date');
+ counter++;
+ return getTime(t) + nodeId + getNoise();
+}
+
+export function parseAidx(id: string): { date: Date; } {
+ const time = parseInt(id.slice(0, TIME_LENGTH), 36) + TIME2000;
+ return { date: new Date(time) };
+}
diff --git a/packages/backend/src/misc/id/meid.ts b/packages/backend/src/misc/id/meid.ts
index 82cda37237..366738de05 100644
--- a/packages/backend/src/misc/id/meid.ts
+++ b/packages/backend/src/misc/id/meid.ts
@@ -29,8 +29,8 @@ function getRandom() {
return str;
}
-export function genMeid(date: Date): string {
- return getTime(date.getTime()) + getRandom();
+export function genMeid(t: number): string {
+ return getTime(t) + getRandom();
}
export function parseMeid(id: string): { date: Date; } {
diff --git a/packages/backend/src/misc/id/meidg.ts b/packages/backend/src/misc/id/meidg.ts
index fba7156718..426a46970b 100644
--- a/packages/backend/src/misc/id/meidg.ts
+++ b/packages/backend/src/misc/id/meidg.ts
@@ -29,8 +29,8 @@ function getRandom() {
return str;
}
-export function genMeidg(date: Date): string {
- return 'g' + getTime(date.getTime()) + getRandom();
+export function genMeidg(t: number): string {
+ return 'g' + getTime(t) + getRandom();
}
export function parseMeidg(id: string): { date: Date; } {
diff --git a/packages/backend/src/misc/id/object-id.ts b/packages/backend/src/misc/id/object-id.ts
index e3b6e8e433..49bd9591c0 100644
--- a/packages/backend/src/misc/id/object-id.ts
+++ b/packages/backend/src/misc/id/object-id.ts
@@ -29,8 +29,8 @@ function getRandom() {
return str;
}
-export function genObjectId(date: Date): string {
- return getTime(date.getTime()) + getRandom();
+export function genObjectId(t: number): string {
+ return getTime(t) + getRandom();
}
export function parseObjectId(id: string): { date: Date; } {
diff --git a/packages/backend/src/misc/is-native-token.ts b/packages/backend/src/misc/is-native-token.ts
index 3046e72a34..618e60b7d8 100644
--- a/packages/backend/src/misc/is-native-token.ts
+++ b/packages/backend/src/misc/is-native-token.ts
@@ -3,4 +3,5 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
+// eslint-disable-next-line import/no-default-export
export default (token: string) => token.length === 16;
diff --git a/packages/backend/src/misc/is-pure-renote.ts b/packages/backend/src/misc/is-pure-renote.ts
new file mode 100644
index 0000000000..994d981522
--- /dev/null
+++ b/packages/backend/src/misc/is-pure-renote.ts
@@ -0,0 +1,10 @@
+import type { MiNote } from '@/models/Note.js';
+
+export function isPureRenote(note: MiNote): note is MiNote & { renoteId: NonNullable } {
+ if (!note.renoteId) return false;
+
+ if (note.text) return false; // it's quoted with text
+ if (note.fileIds.length !== 0) return false; // it's quoted with files
+ if (note.hasPoll) return false; // it's quoted with poll
+ return true;
+}
diff --git a/packages/backend/src/misc/is-quote.ts b/packages/backend/src/misc/is-quote.ts
index 34c7477bc5..059f6a4b5f 100644
--- a/packages/backend/src/misc/is-quote.ts
+++ b/packages/backend/src/misc/is-quote.ts
@@ -3,8 +3,9 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import type { MiNote } from '@/models/entities/Note.js';
+import type { MiNote } from '@/models/Note.js';
+// eslint-disable-next-line import/no-default-export
export default function(note: MiNote): boolean {
return note.renoteId != null && (note.text != null || note.hasPoll || (note.fileIds != null && note.fileIds.length > 0));
}
diff --git a/packages/backend/src/misc/is-user-related.ts b/packages/backend/src/misc/is-user-related.ts
index edd65a3c1c..6efb1194d3 100644
--- a/packages/backend/src/misc/is-user-related.ts
+++ b/packages/backend/src/misc/is-user-related.ts
@@ -3,16 +3,16 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-export function isUserRelated(note: any, userIds: Set): boolean {
- if (userIds.has(note.userId)) {
+export function isUserRelated(note: any, userIds: Set, ignoreAuthor = false): boolean {
+ if (userIds.has(note.userId) && !ignoreAuthor) {
return true;
}
- if (note.reply != null && userIds.has(note.reply.userId)) {
+ if (note.reply != null && note.reply.userId !== note.userId && userIds.has(note.reply.userId)) {
return true;
}
- if (note.renote != null && userIds.has(note.renote.userId)) {
+ if (note.renote != null && note.renote.userId !== note.userId && userIds.has(note.renote.userId)) {
return true;
}
diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts
index 4cc53cf547..093168202c 100644
--- a/packages/backend/src/misc/json-schema.ts
+++ b/packages/backend/src/misc/json-schema.ts
@@ -20,7 +20,6 @@ import { packedChannelSchema } from '@/models/json-schema/channel.js';
import { packedClipSchema } from '@/models/json-schema/clip.js';
import { packedDriveFileSchema } from '@/models/json-schema/drive-file.js';
import { packedDriveFolderSchema } from '@/models/json-schema/drive-folder.js';
-import { packedEmojiDetailedSchema, packedEmojiSimpleSchema } from '@/models/json-schema/emoji.js';
import { packedFederationInstanceSchema } from '@/models/json-schema/federation-instance.js';
import { packedFlashLikeSchema, packedFlashSchema } from '@/models/json-schema/flash.js';
import { packedFollowRequestSchema, packedFollowingSchema } from '@/models/json-schema/following.js';
@@ -35,9 +34,10 @@ import { packedNoteSchema } from '@/models/json-schema/note.js';
import { packedNotificationSchema } from '@/models/json-schema/notification.js';
import { packedPageLikeSchema, packedPageSchema } from '@/models/json-schema/page.js';
import { packedQueueCountSchema } from '@/models/json-schema/queue.js';
+import { packedEmojiDetailedSchema, packedEmojiSimpleSchema } from '@/models/json-schema/emoji.js';
import { packedRenoteMutingSchema } from '@/models/json-schema/renote-muting.js';
import { packedRoleSchema } from '@/models/json-schema/role.js';
-import { packedUserListSchema } from '@/models/json-schema/user-list.js';
+import { packedUserListMembershipSchema, packedUserListSchema } from '@/models/json-schema/user-list.js';
import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
export const refs = {
@@ -50,6 +50,7 @@ export const refs = {
User: packedUserSchema,
UserList: packedUserListSchema,
+ UserListMembership: packedUserListMembershipSchema,
Announcement: packedAnnouncementSchema,
App: packedAppSchema,
Note: packedNoteSchema,
diff --git a/packages/backend/src/misc/loader.ts b/packages/backend/src/misc/loader.ts
index 49aac5d883..25f7b54d31 100644
--- a/packages/backend/src/misc/loader.ts
+++ b/packages/backend/src/misc/loader.ts
@@ -1,9 +1,12 @@
export type FetchFunction = (key: K) => Promise;
+
type ResolveReject = Parameters>[0]>;
+
type ResolverPair = {
resolve: ResolveReject[0];
reject: ResolveReject[1];
};
+
export class DebounceLoader {
private resolverMap = new Map