Merge tag '13.11.0' into io

# Conflicts:
#	packages/backend/src/server/ServerService.ts
#	packages/backend/src/server/api/endpoints/notes/timeline.ts
This commit is contained in:
和風ドレッシング 2023-04-08 22:01:55 +09:00
commit 0e4fe59d57
No known key found for this signature in database
GPG Key ID: 82A8B5D42246F02D
650 changed files with 32472 additions and 9221 deletions

View File

@ -51,6 +51,23 @@ db:
#extra: #extra:
# ssl: true # ssl: true
dbReplications: false
# You can configure any number of replicas here
#dbSlaves:
# -
# host:
# port:
# db:
# user:
# pass:
# -
# host:
# port:
# db:
# user:
# pass:
# ┌─────────────────────┐ # ┌─────────────────────┐
#───┘ Redis configuration └───────────────────────────────────── #───┘ Redis configuration └─────────────────────────────────────
@ -62,6 +79,22 @@ redis:
#prefix: example-prefix #prefix: example-prefix
#db: 1 #db: 1
#redisForPubsub:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
#redisForJobQueue:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# ┌─────────────────────────────┐ # ┌─────────────────────────────┐
#───┘ Elasticsearch configuration └───────────────────────────── #───┘ Elasticsearch configuration └─────────────────────────────

View File

@ -51,6 +51,23 @@ db:
#extra: #extra:
# ssl: true # ssl: true
dbReplications: false
# You can configure any number of replicas here
#dbSlaves:
# -
# host:
# port:
# db:
# user:
# pass:
# -
# host:
# port:
# db:
# user:
# pass:
# ┌─────────────────────┐ # ┌─────────────────────┐
#───┘ Redis configuration └───────────────────────────────────── #───┘ Redis configuration └─────────────────────────────────────
@ -62,6 +79,22 @@ redis:
#prefix: example-prefix #prefix: example-prefix
#db: 1 #db: 1
#redisForPubsub:
# host: localhost
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
#redisForJobQueue:
# host: localhost
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# ┌─────────────────────────────┐ # ┌─────────────────────────────┐
#───┘ Elasticsearch configuration └───────────────────────────── #───┘ Elasticsearch configuration └─────────────────────────────

View File

@ -7,5 +7,18 @@
"ghcr.io/devcontainers-contrib/features/pnpm:2": {} "ghcr.io/devcontainers-contrib/features/pnpm:2": {}
}, },
"forwardPorts": [3000], "forwardPorts": [3000],
"postCreateCommand": "sudo chmod 755 .devcontainer/init.sh && .devcontainer/init.sh" "postCreateCommand": "sudo chmod 755 .devcontainer/init.sh && .devcontainer/init.sh",
"customizations": {
"vscode": {
"extensions": [
"editorconfig.editorconfig",
"dbaeumer.vscode-eslint",
"Vue.volar",
"Vue.vscode-typescript-vue-plugin",
"Orta.vscode-jest",
"dbaeumer.vscode-eslint",
"mrmlnc.vscode-json5"
]
}
}
} }

View File

@ -51,6 +51,23 @@ db:
#extra: #extra:
# ssl: true # ssl: true
dbReplications: false
# You can configure any number of replicas here
#dbSlaves:
# -
# host:
# port:
# db:
# user:
# pass:
# -
# host:
# port:
# db:
# user:
# pass:
# ┌─────────────────────┐ # ┌─────────────────────┐
#───┘ Redis configuration └───────────────────────────────────── #───┘ Redis configuration └─────────────────────────────────────
@ -62,6 +79,22 @@ redis:
#prefix: example-prefix #prefix: example-prefix
#db: 1 #db: 1
#redisForPubsub:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
#redisForJobQueue:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# ┌─────────────────────────────┐ # ┌─────────────────────────────┐
#───┘ Elasticsearch configuration └───────────────────────────── #───┘ Elasticsearch configuration └─────────────────────────────

View File

@ -16,7 +16,7 @@ services:
- external_network - external_network
redis: redis:
restart: always restart: unless-stopped
image: redis:7-alpine image: redis:7-alpine
networks: networks:
- internal_network - internal_network

View File

@ -4,6 +4,7 @@ set -xe
sudo chown -R node /workspace sudo chown -R node /workspace
git submodule update --init git submodule update --init
pnpm config set store-dir /home/node/.local/share/pnpm/store
pnpm install --frozen-lockfile pnpm install --frozen-lockfile
cp .devcontainer/devcontainer.yml .config/default.yml cp .devcontainer/devcontainer.yml .config/default.yml
pnpm build pnpm build

View File

@ -25,6 +25,8 @@ fluent-emojis/
!.yarn/sdks !.yarn/sdks
!.yarn/versions !.yarn/versions
.pnpm-store
.idea/ .idea/
packages/*/.vscode/ packages/*/.vscode/
packages/backend/test/docker-compose.yml packages/backend/test/docker-compose.yml

View File

@ -4,14 +4,20 @@ Thank you for your PR! Before creating a PR, please check the contribution guide
https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md
--> -->
# What ## What
<!-- このPRで何をしたのか どう変わるのか? --> <!-- このPRで何をしたのか どう変わるのか? -->
<!-- What did you do with this PR? How will it change things? --> <!-- What did you do with this PR? How will it change things? -->
# Why ## Why
<!-- なぜそうするのか? どういう意図なのか? 何が困っているのか? --> <!-- なぜそうするのか? どういう意図なのか? 何が困っているのか? -->
<!-- Why do you do it? What are your intentions? What is the problem? --> <!-- Why do you do it? What are your intentions? What is the problem? -->
# Additional info (optional) ## Additional info (optional)
<!-- テスト観点など --> <!-- テスト観点など -->
<!-- Test perspective, etc --> <!-- Test perspective, etc -->
## Checklist
- [ ] Read the [contribution guide](https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md)
- [ ] Test working in a local environment
- [ ] (If needed) Update CHANGELOG.md
- [ ] (If possible) Add tests

View File

@ -4,14 +4,20 @@ Thank you for your PR! Before creating a PR, please check the contribution guide
https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md
--> -->
# What ## What
<!-- このPRで何をしたのか どう変わるのか? --> <!-- このPRで何をしたのか どう変わるのか? -->
<!-- What did you do with this PR? How will it change things? --> <!-- What did you do with this PR? How will it change things? -->
# Why ## Why
<!-- なぜそうするのか? どういう意図なのか? 何が困っているのか? --> <!-- なぜそうするのか? どういう意図なのか? 何が困っているのか? -->
<!-- Why do you do it? What are your intentions? What is the problem? --> <!-- Why do you do it? What are your intentions? What is the problem? -->
# Additional info (optional) ## Additional info (optional)
<!-- テスト観点など --> <!-- テスト観点など -->
<!-- Test perspective, etc --> <!-- Test perspective, etc -->
## Checklist
- [ ] Read the [contribution guide](https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md)
- [ ] Test working in a local environment
- [ ] (If needed) Update CHANGELOG.md
- [ ] (If possible) Add tests

23
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1,23 @@
<!-- お読みください / README
PRありがとうございます PRを作成する前に、コントリビューションガイドをご確認ください:
Thank you for your PR! Before creating a PR, please check the contribution guide:
https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md
-->
## What
<!-- このPRで何をしたのか どう変わるのか? -->
<!-- What did you do with this PR? How will it change things? -->
## Why
<!-- なぜそうするのか? どういう意図なのか? 何が困っているのか? -->
<!-- Why do you do it? What are your intentions? What is the problem? -->
## Additional info (optional)
<!-- テスト観点など -->
<!-- Test perspective, etc -->
## Checklist
- [ ] Read the [contribution guide](https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md)
- [ ] Test working in a local environment
- [ ] (If needed) Update CHANGELOG.md
- [ ] (If possible) Add tests

36
.github/workflows/api-misskey-js.yml vendored Normal file
View File

@ -0,0 +1,36 @@
name: API report (misskey.js)
on: [push, pull_request]
jobs:
report:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3.3.0
- run: corepack enable
- name: Setup Node.js
uses: actions/setup-node@v3.6.0
with:
node-version: 18.x
cache: 'pnpm'
- name: Install dependencies
run: pnpm i --frozen-lockfile
- name: Build
run: pnpm --filter misskey-js build
- name: Check files
run: ls packages/misskey-js/built
- name: API report
run: pnpm --filter misskey-js api-prod
- name: Show report
if: always()
run: cat packages/misskey-js/temp/misskey-js.api.md

View File

@ -36,6 +36,7 @@ jobs:
- backend - backend
- frontend - frontend
- sw - sw
- misskey-js
steps: steps:
- uses: actions/checkout@v3.3.0 - uses: actions/checkout@v3.3.0
with: with:
@ -61,6 +62,7 @@ jobs:
matrix: matrix:
workspace: workspace:
- backend - backend
- misskey-js
steps: steps:
- uses: actions/checkout@v3.3.0 - uses: actions/checkout@v3.3.0
with: with:

74
.github/workflows/storybook.yml vendored Normal file
View File

@ -0,0 +1,74 @@
name: Storybook
on:
push:
branches-ignore:
- l10n_develop
jobs:
build:
runs-on: ubuntu-latest
env:
NODE_OPTIONS: "--max_old_space_size=7168"
steps:
- uses: actions/checkout@v3.3.0
with:
fetch-depth: 0
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 7
run_install: false
- name: Use Node.js 18.x
uses: actions/setup-node@v3.6.0
with:
node-version: 18.x
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Build misskey-js
run: pnpm --filter misskey-js build
- name: Build storybook
run: pnpm --filter frontend build-storybook
- name: Publish to Chromatic
if: github.ref == 'refs/heads/master'
run: pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static
env:
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
- name: Publish to Chromatic
if: github.ref != 'refs/heads/master'
id: chromatic
run: |
DIFF="${{ github.event.before }} HEAD"
if [ "$DIFF" = "0000000000000000000000000000000000000000 HEAD" ]; then
DIFF="HEAD"
fi
CHROMATIC_PARAMETER="$(node packages/frontend/.storybook/changes.js $(git diff-tree --no-commit-id --name-only -r $(echo "$DIFF") | xargs))"
if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
fi
pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static $(echo "$CHROMATIC_PARAMETER")
env:
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
- name: Notify that Chromatic will skip testing
uses: actions/github-script@v6.4.0
if: github.ref != 'refs/heads/master' && github.ref != 'refs/heads/develop' && steps.chromatic.outputs.skip == 'true'
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.repos.createCommitComment({
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: context.sha,
body: 'Chromatic will skip testing but you may still have to [review the changes on Chromatic](https://www.chromatic.com/pullrequests?appId=6428f7d7b962f0b79f97d6e4).'
})
- name: Upload Artifacts
uses: actions/upload-artifact@v3
with:
name: storybook
path: packages/frontend/storybook-static

59
.github/workflows/test-backend.yml vendored Normal file
View File

@ -0,0 +1,59 @@
name: Test (backend)
on:
push:
branches:
- master
- develop
pull_request:
jobs:
jest:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x]
services:
postgres:
image: postgres:13
ports:
- 54312:5432
env:
POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust
redis:
image: redis:7
ports:
- 56312:6379
steps:
- uses: actions/checkout@v3.3.0
with:
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 7
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3.6.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 .github/misskey/test.yml .config
- name: Build
run: pnpm build
- name: Test
run: pnpm jest-and-coverage
- name: Upload Coverage
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./packages/backend/coverage/coverage-final.json

View File

@ -1,4 +1,4 @@
name: Test name: Test (frontend)
on: on:
push: push:
@ -8,26 +8,13 @@ on:
pull_request: pull_request:
jobs: jobs:
jest: vitest:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
node-version: [18.x] node-version: [18.x]
services:
postgres:
image: postgres:13
ports:
- 54312:5432
env:
POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust
redis:
image: redis:6
ports:
- 56312:6379
steps: steps:
- uses: actions/checkout@v3.3.0 - uses: actions/checkout@v3.3.0
with: with:
@ -51,12 +38,12 @@ jobs:
- name: Build - name: Build
run: pnpm build run: pnpm build
- name: Test - name: Test
run: pnpm jest-and-coverage run: pnpm --filter frontend test-and-coverage
- name: Upload Coverage - name: Upload Coverage
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v3
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
files: ./packages/backend/coverage/coverage-final.json files: ./packages/frontend/coverage/coverage-final.json
e2e: e2e:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -76,7 +63,7 @@ jobs:
POSTGRES_DB: test-misskey POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust POSTGRES_HOST_AUTH_METHOD: trust
redis: redis:
image: redis:6 image: redis:7
ports: ports:
- 56312:6379 - 56312:6379

52
.github/workflows/test-misskey-js.yml vendored Normal file
View File

@ -0,0 +1,52 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Test (misskey.js)
on:
push:
branches: [ develop ]
pull_request:
branches: [ develop ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- name: Checkout
uses: actions/checkout@v3.3.0
- run: corepack enable
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3.6.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- name: Install dependencies
run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Build
run: pnpm --filter misskey-js build
- name: Test
run: pnpm --filter misskey-js test
env:
CI: true
- name: Upload Coverage
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./packages/misskey-js/coverage/coverage-final.json

5
.gitignore vendored
View File

@ -20,6 +20,9 @@ packages/frontend/.yarn/cache
packages/backend/.yarn/cache packages/backend/.yarn/cache
packages/sw/.yarn/cache packages/sw/.yarn/cache
# pnpm
.pnpm-store
# Cypress # Cypress
cypress/screenshots cypress/screenshots
cypress/videos cypress/videos
@ -52,6 +55,8 @@ api-docs.json
.DS_Store .DS_Store
/files /files
ormconfig.json ormconfig.json
temp
/packages/frontend/src/**/*.stories.ts
# blender backups # blender backups
*.blend1 *.blend1

View File

@ -1,9 +1,11 @@
{ {
"recommendations": [ "recommendations": [
"editorconfig.editorconfig", "editorconfig.editorconfig",
"eg2.vscode-npm-script",
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"Vue.volar", "Vue.volar",
"Vue.vscode-typescript-vue-plugin" "Vue.vscode-typescript-vue-plugin",
"Orta.vscode-jest",
"dbaeumer.vscode-eslint",
"mrmlnc.vscode-json5"
] ]
} }

View File

@ -5,5 +5,6 @@
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
"files.associations": { "files.associations": {
"*.test.ts": "typescript" "*.test.ts": "typescript"
} },
"jest.autoRun": "off"
} }

View File

@ -1,14 +1,157 @@
<!-- <!--
## 13.x.x (unreleased) ## 13.x.x (unreleased)
### Improvements ### General
- -
### Client
-
### Server
-
-->
## 13.11.0
### NOTE
- このバージョンからRedis 7.xが必要です。
- アップデートを行うと全ての通知およびアンテナのノートはリセットされます。
### General
- チャンネルをお気に入りに登録できるように
- チャンネルにノートをピン留めできるように
### Client
- 投稿フォームのデザインを改善
- 検索ページでURLを入力した際に照会したときと同等の挙動をするように
- ノートのリアクションを大きく表示するオプションを追加
- ギャラリー一覧にメディア表示と同じように NSFW 設定を反映するように(ホバーで表示)
- オブジェクトストレージの設定画面を分かりやすく
- 広告・お知らせが新規登録時に増殖しないように
- 「にゃああああああああああああああ!!!!!!!!!!!!」 (`isCat`) 有効時にアバターに表示される猫耳について挙動を変更
- 「UIにぼかし効果を使用」 (`useBlurEffect`) で次の挙動が有効になります
- 猫耳のアバター内部部分をぼかしでマスク表示してより猫耳っぽく見えるように
- 「UIのアニメーションを減らす」 (`reduceAnimation`) で猫耳を撫でられなくなります
- Add Minimizing ("folding") of windows
### Server
- PostgreSQLのレプリケーション対応
- 設定ファイルの `dbReplications` および `dbSlaves` にて設定できます
- イベント用Redisを別サーバーに分離できるように
- ジョブキュー用Redisを別サーバーに分離できるように
- サーバーの全体的なパフォーマンスを向上
- ノート作成時のパフォーマンスを向上
- アンテナのタイムライン取得時のパフォーマンスを向上
- チャンネルのタイムライン取得時のパフォーマンスを向上
- 通知に関する全体的なパフォーマンスを向上
- webhookがcontent-type text/plain;charset=UTF-8 で飛んでくる問題を修正
## 13.10.3
### Changes
- オブジェクトストレージのリージョン指定が必須になりました
- リージョンの指定の無いサービスは us-east-1 を設定してください
- 値が空の場合は設定ファイルまたは環境変数の使用を試みます
- e.g. ~/aws/config, AWS_REGION
### General
- コンディショナルロールの条件に「投稿数が~以下」「投稿数が~以上」を追加
- リアクション非対応AP実装からのLikeアクティビティの解釈を👍から♥に
### Client
- クリップボタンをノートアクションに追加できるように
- センシティブワードの一覧にピン留めユーザーのIDが表示される問題を修正
### Server
- リモートユーザーのチャート生成を無効にするオプションを追加
- リモートサーバーのチャート生成を無効にするオプションを追加
- ドライブのチャートはローカルユーザーのみ生成するように
- 空のアンテナが作成できるのを修正
## 13.10.2
### Server
- 絵文字を編集すると保存できないことがある問題を修正
### Client
- ドライブファイルのメニューが正常に動作しない問題を修正
## 13.10.1
### Client
- Misskey PlayのPlayボタンを押した時にエラーが発生する問題を修正
## 13.10.0
### General
- ユーザーごとにRenoteをミュートできるように
- ノートごとに絵文字リアクションを受け取るか設定できるように
- クリップをお気に入りに登録できるように
- ノート検索の利用可否をロールで制御可能に(デフォルトでオフ)
- ロールの並び順を設定可能に
- カスタム絵文字にライセンス情報を付与できるように
- 指定した文字列を含む投稿の公開範囲をホームにできるように
- 使われてないアンテナは自動停止されるように
### Client
- 設定から自分のロールを確認できるように
- 広告一覧ページを追加
- ドライブクリーナーを追加
- DM作成時にメンションも含むように
- フォロー申請のボタンのデザインを改善
- 付箋ウィジェットの高さを設定可能に
- APオブジェクトを入力してフェッチする機能とユーザーやートの検索機能を分離
- ナビゲーションバーの項目に「プロフィール」を追加できるように
- ナビゲーションバーのカスタマイズをドラッグ&ドロップで行えるように
- ジョブキューの再試行をワンクリックでできるように
- AiScriptを0.13.1に更新
- oEmbedをサポートしているウェブサイトのプレビューができるように
- YouTubeをoEmbedでロードし、プレビューで共有ボタンを押すとOSの共有画面がでるように
- ([FirefoxでSpotifyのプレビューを開けるとフルサイズじゃなくプレビューサイズだけ再生できる問題](https://bugzilla.mozilla.org/show_bug.cgi?id=1792395)があります)
- (すでにブラウザーでキャッシュされたリンクに対しては以前のプレビュー行動が行われてます。その場合、ブラウザーのキャッシュをクリアしてまた試してください。)
- プロフィールで設定した情報が削除できない問題を修正
- ロールで広告を無効にするとadmin/adsでプレビューがでてこない問題を修正
- /api-consoleページにアクセスすると404が出る問題を修正
- Safariでプラグインが複数ある場合に正常に読み込まれない問題を修正
- Bookwyrmのユーザーのプロフィールページで「リモートで表示」をタップしても反応がない問題を修正
- 非ログイン時の「Misskeyについて」の表示を修正
- PC版にて「設定」「コントロールパネル」のリンクを2度以上続けてクリックした際に空白のページが表示される問題を修正
### Server
- OpenAPIエンドポイントを復旧
- WebP/AVIF/JPEGのweb公開用画像は、サーバーサイドではJPEGではなくWebPに変換するように
- アニメーション画像のサムネイルを生成するように
- アクティブユーザー数チャートの記録上限値を拡張
- Playのソースコード上限文字数を2倍に拡張
- 配送先サーバーが410 Goneで応答してきた場合は自動で配送停止をするように
- avatarBlurHash/bannerBlurHashの型をstringに限定
- タイムライン取得時のパフォーマンスを改善
- SMTP Login id length is too short
- API上で`visibility`を`followers`に設定してrenoteすると連合や削除で不具合が発生する問題を修正
- AWS S3からのファイル削除でNoSuchKeyエラーが出ると進めらない状態になる問題を修正
- `disableCache: true`を設定している場合に絵文字管理操作でエラーが出る問題を修正
- リテンション分析が上手く機能しないことがあるのを修正
- 空のアンテナが作成できないように修正
- 特定の条件で通報が見れない問題を修正
- 絵文字の名前に任意の文字が使用できる問題を修正
## 13.9.2 (2023/03/06)
### Improvements
- クリップ、チャンネルページに共有ボタンを追加
- チャンネルでタイムライン上部に投稿フォームを表示するかどうかのオプションを追加
- ブラウザでメディアプロキシ(/proxy)からファイルを保存した際に、なるべくオリジナルのファイル名を継承するように
- ドライブの「URLからアップロード」で、content-dispositionのfilenameがあればそれをファイル名に
- Identiconがローカルとリモートで同じになるように
- これまでのIdenticonは異なる画像になります
- サーバーのパフォーマンスを改善
### Bugfixes ### Bugfixes
- - ロールの権限で「一般ユーザー」のロールがいきなり設定できない問題を修正
- ユーザーページのバッジ表示を適切に折り返すように @arrow2nd
You should also include the user name that made the change. - fix(client): みつけるのロール一覧でコンディショナルロールが含まれるのを修正
--> - macOSでDev Containerが動作しない問題を修正 @RyotaK
## 13.9.1 (2023/03/03) ## 13.9.1 (2023/03/03)

View File

@ -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. - To avoid duplication, please search for similar issues before creating a new issue.
- Do not use Issues to ask questions or troubleshooting. - Do not use Issues to ask questions or troubleshooting.
- Issues should only be used to feature requests, suggestions, and bug tracking. - 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/) or [Discord](https://discord.gg/Wp8gVStHW3). - 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).
> **Warning** > **Warning**
> Do not close issues that are about to be resolved. It should remain open until a commit that actually resolves it is merged. > Do not close issues that are about to be resolved. It should remain open until a commit that actually resolves it is merged.
@ -203,6 +203,116 @@ niraxは、Misskeyで使用しているオリジナルのフロントエンド
vue-routerとの最大の違いは、niraxは複数のルーターが存在することを許可している点です。 vue-routerとの最大の違いは、niraxは複数のルーターが存在することを許可している点です。
これにより、アプリ内ウィンドウでブラウザとは個別にルーティングすることなどが可能になります。 これにより、アプリ内ウィンドウでブラウザとは個別にルーティングすることなどが可能になります。
## Storybook
Misskey uses [Storybook](https://storybook.js.org/) for UI development.
### Setup & Run
#### Universal
##### Setup
```bash
pnpm --filter misskey-js build
pnpm --filter frontend tsc -p .storybook && (node packages/frontend/.storybook/preload-locale.js & node packages/frontend/.storybook/preload-theme.js)
```
##### Run
```bash
node packages/frontend/.storybook/generate.js && pnpm --filter frontend storybook dev
```
#### macOS & Linux
##### Setup
```bash
pnpm --filter misskey-js build
```
##### Run
```bash
pnpm --filter frontend storybook-dev
```
### Usage
When you create a new component (in this example, `MyComponent.vue`), the story file (`MyComponent.stories.ts`) will be automatically generated by the `.storybook/generate.js` script.
You can override the default story by creating a impl story file (`MyComponent.stories.impl.ts`).
```ts
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-duplicates */
import { StoryObj } from '@storybook/vue3';
import MyComponent from './MyComponent.vue';
export const Default = {
render(args) {
return {
components: {
MyComponent,
},
setup() {
return {
args,
};
},
computed: {
props() {
return {
...this.args,
};
},
},
template: '<MyComponent v-bind="props" />',
};
},
args: {
foo: 'bar',
},
parameters: {
layout: 'centered',
},
} satisfies StoryObj<typeof MkAvatar>;
```
If you want to opt-out from the automatic generation, create a `MyComponent.stories.impl.ts` file and add the following line to the file.
```ts
import MyComponent from './MyComponent.vue';
void MyComponent;
```
You can override the component meta by creating a meta story file (`MyComponent.stories.meta.ts`).
```ts
export const argTypes = {
scale: {
control: {
type: 'range',
min: 1,
max: 4,
},
};
```
Also, you can use msw to mock API requests in the storybook. Creating a `MyComponent.stories.msw.ts` file to define the mock handlers.
```ts
import { rest } from 'msw';
export const handlers = [
rest.post('/api/notes/timeline', (req, res, ctx) => {
return res(
ctx.json([]),
);
}),
];
```
Don't forget to re-run the `.storybook/generate.js` script after adding, editing, or removing the above files.
## Notes ## Notes
### How to resolve conflictions occurred at pnpm-lock.yaml? ### How to resolve conflictions occurred at pnpm-lock.yaml?

View File

@ -2,7 +2,9 @@
ARG NODE_VERSION=18.13.0-bullseye ARG NODE_VERSION=18.13.0-bullseye
FROM node:${NODE_VERSION} AS builder # build assets & compile TypeScript
FROM --platform=$BUILDPLATFORM node:${NODE_VERSION} AS native-builder
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \
@ -21,6 +23,7 @@ COPY --link ["scripts", "./scripts"]
COPY --link ["packages/backend/package.json", "./packages/backend/"] COPY --link ["packages/backend/package.json", "./packages/backend/"]
COPY --link ["packages/frontend/package.json", "./packages/frontend/"] COPY --link ["packages/frontend/package.json", "./packages/frontend/"]
COPY --link ["packages/sw/package.json", "./packages/sw/"] COPY --link ["packages/sw/package.json", "./packages/sw/"]
COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"]
RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \ RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
pnpm i --frozen-lockfile --aggregate-output pnpm i --frozen-lockfile --aggregate-output
@ -33,33 +36,49 @@ RUN git submodule update --init
RUN pnpm build RUN pnpm build
RUN rm -rf .git/ RUN rm -rf .git/
FROM node:${NODE_VERSION}-slim AS runner # build native dependencies for target platform
FROM --platform=$TARGETPLATFORM node:${NODE_VERSION} AS target-builder
RUN apt-get update \
&& apt-get install -yqq --no-install-recommends \
build-essential
RUN corepack enable
WORKDIR /misskey
COPY --link ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"]
COPY --link ["scripts", "./scripts"]
COPY --link ["packages/backend/package.json", "./packages/backend/"]
RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
pnpm i --frozen-lockfile --aggregate-output
FROM --platform=$TARGETPLATFORM node:${NODE_VERSION}-slim AS runner
ARG UID="991" ARG UID="991"
ARG GID="991" ARG GID="991"
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ RUN apt-get update \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
rm -f /etc/apt/apt.conf.d/docker-clean \
; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache \
&& apt-get update \
&& apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends \
ffmpeg tini curl \ ffmpeg tini curl \
&& corepack enable \ && corepack enable \
&& groupadd -g "${GID}" misskey \ && groupadd -g "${GID}" misskey \
&& useradd -l -u "${UID}" -g "${GID}" -m -d /misskey misskey \ && useradd -l -u "${UID}" -g "${GID}" -m -d /misskey misskey \
&& find / -type f -perm /u+s -ignore_readdir_race -exec chmod u-s {} \; \ && find / -type d -path /proc -prune -o -type f -perm /u+s -ignore_readdir_race -exec chmod u-s {} \; \
&& find / -type f -perm /g+s -ignore_readdir_race -exec chmod g-s {} \; && find / -type d -path /proc -prune -o -type f -perm /g+s -ignore_readdir_race -exec chmod g-s {} \; \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists
USER misskey USER misskey
WORKDIR /misskey WORKDIR /misskey
COPY --chown=misskey:misskey --from=builder /misskey/node_modules ./node_modules COPY --chown=misskey:misskey --from=target-builder /misskey/node_modules ./node_modules
COPY --chown=misskey:misskey --from=builder /misskey/built ./built COPY --chown=misskey:misskey --from=target-builder /misskey/packages/backend/node_modules ./packages/backend/node_modules
COPY --chown=misskey:misskey --from=builder /misskey/packages/backend/node_modules ./packages/backend/node_modules COPY --chown=misskey:misskey --from=native-builder /misskey/built ./built
COPY --chown=misskey:misskey --from=builder /misskey/packages/backend/built ./packages/backend/built COPY --chown=misskey:misskey --from=native-builder /misskey/packages/backend/built ./packages/backend/built
COPY --chown=misskey:misskey --from=builder /misskey/packages/frontend/node_modules ./packages/frontend/node_modules COPY --chown=misskey:misskey --from=native-builder /misskey/fluent-emojis /misskey/fluent-emojis
COPY --chown=misskey:misskey --from=builder /misskey/fluent-emojis /misskey/fluent-emojis
COPY --chown=misskey:misskey . ./ COPY --chown=misskey:misskey . ./
ENV NODE_ENV=production ENV NODE_ENV=production

View File

@ -54,6 +54,25 @@ With Misskey's built in drive, you get cloud storage right in your social media,
Misskey Documentation can be found at [Misskey Hub](https://misskey-hub.net/), some of the links and graphics above also lead to specific portions of it. Misskey Documentation can be found at [Misskey Hub](https://misskey-hub.net/), some of the links and graphics above also lead to specific portions of it.
## Sponsors ## Sponsors
<div align="center"> <div align="center">
<a class="rss3" title="RSS3" href="https://rss3.io/" target="_blank"><img src="https://rss3.mypinata.cloud/ipfs/QmUG6H3Z7D5P511shn7sB4CPmpjH5uZWu4m5mWX7U3Gqbu" alt="RSS3" height="60"></a> <a class="rss3" title="RSS3" href="https://rss3.io/" target="_blank"><img src="https://rss3.mypinata.cloud/ipfs/QmUG6H3Z7D5P511shn7sB4CPmpjH5uZWu4m5mWX7U3Gqbu" alt="RSS3" height="60"></a>
</div> </div>
## Thanks
<a href="https://www.chromatic.com/"><img src="https://user-images.githubusercontent.com/321738/84662277-e3db4f80-af1b-11ea-88f5-91d67a5e59f6.png" height="30" alt="Chromatic" /></a>
Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testing platform that helps us review UI changes and catch visual regressions.
<a href="https://about.codecov.io/for/open-source/"><img src="https://about.codecov.io/wp-content/themes/codecov/assets/brand/sentry-cobranding/logos/codecov-by-sentry-logo.svg" height="30" alt="Codecov" /></a>
Thanks to [Codecov](https://about.codecov.io/for/open-source/) for providing the code coverage platform that helps us improve our test coverage.
<a href="https://crowdin.com/"><img src="https://user-images.githubusercontent.com/20679825/230709597-1299a011-171a-4294-a91e-355a9b37c672.svg" height="30" alt="Crowdin" /></a>
Thanks to [Crowdin](https://crowdin.com/) for providing the localization platform that helps us translate Misskey into many languages.
<a href="https://hub.docker.com/"><img src="https://user-images.githubusercontent.com/20679825/230148221-f8e73a32-a49b-47c3-9029-9a15c3824f92.png" height="30" alt="Docker" /></a>
Thanks to [Docker](https://hub.docker.com/) for providing the container platform that helps us run Misskey in production.

View File

@ -72,16 +72,50 @@ db:
#extra: #extra:
# ssl: true # ssl: true
dbReplications: false
# You can configure any number of replicas here
#dbSlaves:
# -
# host:
# port:
# db:
# user:
# pass:
# -
# host:
# port:
# db:
# user:
# pass:
# ┌─────────────────────┐ # ┌─────────────────────┐
#───┘ Redis configuration └───────────────────────────────────── #───┘ Redis configuration └─────────────────────────────────────
redis: redis:
host: localhost host: localhost
port: 6379 port: 6379
#family: 0 # 0=Both, 4=IPv4, 6=IPv6
#pass: example-pass #pass: example-pass
#prefix: example-prefix #prefix: example-prefix
#db: 1 #db: 1
#redisForPubsub:
# host: localhost
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
#redisForJobQueue:
# host: localhost
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# ┌─────────────────────────────┐ # ┌─────────────────────────────┐
#───┘ Elasticsearch configuration └───────────────────────────── #───┘ Elasticsearch configuration └─────────────────────────────

View File

@ -52,13 +52,30 @@ describe('After setup instance', () => {
cy.intercept('POST', '/api/signup').as('signup'); cy.intercept('POST', '/api/signup').as('signup');
cy.get('[data-cy-signup]').click(); cy.get('[data-cy-signup]').click();
cy.get('[data-cy-signup-submit]').should('be.disabled');
cy.get('[data-cy-signup-username] input').type('alice'); cy.get('[data-cy-signup-username] input').type('alice');
cy.get('[data-cy-signup-submit]').should('be.disabled');
cy.get('[data-cy-signup-password] input').type('alice1234'); cy.get('[data-cy-signup-password] input').type('alice1234');
cy.get('[data-cy-signup-submit]').should('be.disabled');
cy.get('[data-cy-signup-password-retype] input').type('alice1234'); cy.get('[data-cy-signup-password-retype] input').type('alice1234');
cy.get('[data-cy-signup-submit]').should('not.be.disabled');
cy.get('[data-cy-signup-submit]').click(); cy.get('[data-cy-signup-submit]').click();
cy.wait('@signup'); cy.wait('@signup');
}); });
it('signup with duplicated username', () => {
cy.registerUser('alice', 'alice1234');
cy.visitHome();
// ユーザー名が重複している場合の挙動確認
cy.get('[data-cy-signup]').click();
cy.get('[data-cy-signup-username] input').type('alice');
cy.get('[data-cy-signup-password] input').type('alice1234');
cy.get('[data-cy-signup-password-retype] input').type('alice1234');
cy.get('[data-cy-signup-submit]').should('be.disabled');
});
}); });
describe('After user signup', () => { describe('After user signup', () => {

View File

@ -29,17 +29,17 @@ describe('After user signed in', () => {
it('first widget should be removed', () => { it('first widget should be removed', () => {
cy.get('.mk-widget-edit').click(); cy.get('.mk-widget-edit').click();
cy.get('.data-cy-customize-container:first-child .data-cy-customize-container-remove._button').click(); cy.get('[data-cy-customize-container]:first-child [data-cy-customize-container-remove]._button').click();
cy.get('.data-cy-customize-container').should('have.length', 2); cy.get('[data-cy-customize-container]').should('have.length', 2);
}); });
function buildWidgetTest(widgetName) { function buildWidgetTest(widgetName) {
it(`${widgetName} widget should get added`, () => { it(`${widgetName} widget should get added`, () => {
cy.get('.mk-widget-edit').click(); cy.get('.mk-widget-edit').click();
cy.get('.mk-widget-select select').select(widgetName, { force: true }); cy.get('.mk-widget-select select').select(widgetName, { force: true });
cy.get('.data-cy-bg._modalBg.data-cy-transparent').click({ multiple: true, force: true }); cy.get('[data-cy-bg]._modalBg[data-cy-transparent]').click({ multiple: true, force: true });
cy.get('.mk-widget-add').click({ force: true }); cy.get('.mk-widget-add').click({ force: true });
cy.get(`.data-cy-mkw-${widgetName}`).should('exist'); cy.get(`[data-cy-mkw-${widgetName}]`).should('exist');
}); });
} }

View File

@ -545,7 +545,6 @@ tokenRequested: "منح حق الوصول إلى الحساب"
pluginTokenRequestedDescription: "ستتمكن الإضافة من استخدام هذه الأذونات." pluginTokenRequestedDescription: "ستتمكن الإضافة من استخدام هذه الأذونات."
notificationType: "أنواع الإشعارات" notificationType: "أنواع الإشعارات"
edit: "التعديل" edit: "التعديل"
useStarForReactionFallback: "استخدم ★ كبديل إذا كان التفاعل مجهولًا"
emailServer: "خادم البريد الإلكتروني" emailServer: "خادم البريد الإلكتروني"
emailConfigInfo: "يستخدم لتأكيد عنوان بريدك الإلكتروني ولإعادة تعيين كلمة المرور إن نسيتها." emailConfigInfo: "يستخدم لتأكيد عنوان بريدك الإلكتروني ولإعادة تعيين كلمة المرور إن نسيتها."
email: "البريد الإلكتروني " email: "البريد الإلكتروني "
@ -1275,3 +1274,6 @@ _deck:
channel: "القنوات" channel: "القنوات"
mentions: "الإشارات" mentions: "الإشارات"
direct: "مباشرة" direct: "مباشرة"
_webhookSettings:
name: "الإسم"
active: "مفعّل"

View File

@ -562,7 +562,6 @@ tokenRequested: "অ্যাকাউন্টে অ্যাক্সেস
pluginTokenRequestedDescription: "এই প্লাগইনটি এখানে দেওয়া অনুমুতিসমূহ ব্যাবহার করবে" pluginTokenRequestedDescription: "এই প্লাগইনটি এখানে দেওয়া অনুমুতিসমূহ ব্যাবহার করবে"
notificationType: "বিজ্ঞপ্তির ধরন" notificationType: "বিজ্ঞপ্তির ধরন"
edit: "সম্পাদনা" edit: "সম্পাদনা"
useStarForReactionFallback: "রিঅ্যাকশনের ইমোজি না জানলে ★ ব্যবহার করুন"
emailServer: "ইমেইল সার্ভার" emailServer: "ইমেইল সার্ভার"
enableEmail: "ইমেইল বিতরণ চালু করুন" enableEmail: "ইমেইল বিতরণ চালু করুন"
emailConfigInfo: "আপনার ইমেল ঠিকানা নিশ্চিত করতে এবং আপনার পাসওয়ার্ড পুনরায় সেট করতে ব্যবহৃত হয়" emailConfigInfo: "আপনার ইমেল ঠিকানা নিশ্চিত করতে এবং আপনার পাসওয়ার্ড পুনরায় সেট করতে ব্যবহৃত হয়"
@ -1354,3 +1353,6 @@ _deck:
channel: "চ্যানেলগুলি" channel: "চ্যানেলগুলি"
mentions: "উল্লেখসমূহ" mentions: "উল্লেখসমূহ"
direct: "ডাইরেক্ট নোটগুলি" direct: "ডাইরেক্ট নোটগুলি"
_webhookSettings:
name: "নাম"
active: "চালু"

View File

@ -776,3 +776,6 @@ _deck:
list: "Seznamy" list: "Seznamy"
channel: "Kanály" channel: "Kanály"
mentions: "Zmínění" mentions: "Zmínění"
_webhookSettings:
name: "Jméno"
active: "Zapnuto"

View File

@ -67,7 +67,7 @@ import: "Import"
export: "Export" export: "Export"
files: "Dateien" files: "Dateien"
download: "Herunterladen" download: "Herunterladen"
driveFileDeleteConfirm: "Möchtest du die Datei „{name}“ wirklich löschen? Notizen mit dieser Datei werden ebenso verschwinden." driveFileDeleteConfirm: "Möchtest du die Datei „{name}“ wirklich löschen? Sie wird in allen Inhalten, die sie verwenden, auch verschwinden."
unfollowConfirm: "Möchtest du {name} wirklich nicht mehr folgen?" 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." 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." importRequested: "Du hast einen Import angefragt. Dies kann etwas Zeit in Anspruch nehmen."
@ -122,6 +122,8 @@ unmarkAsSensitive: "Als nicht NSFW markieren"
enterFileName: "Dateinamen eingeben" enterFileName: "Dateinamen eingeben"
mute: "Stummschalten" mute: "Stummschalten"
unmute: "Stummschaltung aufheben" unmute: "Stummschaltung aufheben"
renoteMute: "Renotes stummschalten"
renoteUnmute: "Renote-Stummschaltung aufheben"
block: "Blockieren" block: "Blockieren"
unblock: "Blockierung aufheben" unblock: "Blockierung aufheben"
suspend: "Sperren" suspend: "Sperren"
@ -153,6 +155,7 @@ flagShowTimelineReplies: "Antworten in der Chronik anzeigen"
flagShowTimelineRepliesDescription: "Ist diese Option aktiviert, so werden Antworten von Benutzern auf die Notizen anderer Benutzer in der Chronik angezeigt." flagShowTimelineRepliesDescription: "Ist diese Option aktiviert, so werden Antworten von Benutzern auf die Notizen anderer Benutzer in der Chronik angezeigt."
autoAcceptFollowed: "Follow-Anfragen von Benutzern, denen du folgst, automatisch akzeptieren" autoAcceptFollowed: "Follow-Anfragen von Benutzern, denen du folgst, automatisch akzeptieren"
addAccount: "Benutzerkonto hinzufügen" addAccount: "Benutzerkonto hinzufügen"
reloadAccountsList: "Benutzerkontoliste aktualisieren"
loginFailed: "Anmeldung fehlgeschlagen" loginFailed: "Anmeldung fehlgeschlagen"
showOnRemote: "Auf Ursprungsinstanz ansehen" showOnRemote: "Auf Ursprungsinstanz ansehen"
general: "Allgemein" general: "Allgemein"
@ -345,7 +348,7 @@ basicInfo: "Grundlegende Informationen"
pinnedUsers: "Angeheftete Benutzer" pinnedUsers: "Angeheftete Benutzer"
pinnedUsersDescription: "Gib durch Leerzeichen getrennte Benutzer an, die an die \"Erkunden\"-Seite angeheftet werden sollen." pinnedUsersDescription: "Gib durch Leerzeichen getrennte Benutzer an, die an die \"Erkunden\"-Seite angeheftet werden sollen."
pinnedPages: "Angeheftete Seiten" pinnedPages: "Angeheftete Seiten"
pinnedPagesDescription: "Gib durch Leerzeilen getrennte Pfäde zu Seiten an, die an die Startseite dieser Instanz angeheftet werden sollen.\n" pinnedPagesDescription: "Gib durch Leerzeilen getrennte Pfade zu Seiten an, die an die Startseite dieser Instanz angeheftet werden sollen."
pinnedClipId: "ID des anzuheftenden Clips" pinnedClipId: "ID des anzuheftenden Clips"
pinnedNotes: "Angeheftete Notizen" pinnedNotes: "Angeheftete Notizen"
hcaptcha: "hCaptcha" hcaptcha: "hCaptcha"
@ -404,7 +407,7 @@ securityKey: "Sicherheitsschlüssel"
lastUsed: "Zuletzt benutzt" lastUsed: "Zuletzt benutzt"
lastUsedAt: "Zuletzt verwendet: {t}" lastUsedAt: "Zuletzt verwendet: {t}"
unregister: "Deaktivieren" unregister: "Deaktivieren"
passwordLessLogin: "Passwortloses Anmelden einrichten" passwordLessLogin: "Passwortloses Anmelden"
passwordLessLoginDescription: "Ermöglicht passwortfreies Einloggen, nur via Security-Token oder Passkey" passwordLessLoginDescription: "Ermöglicht passwortfreies Einloggen, nur via Security-Token oder Passkey"
resetPassword: "Passwort zurücksetzen" resetPassword: "Passwort zurücksetzen"
newPasswordIs: "Das neue Passwort ist „{password}“" newPasswordIs: "Das neue Passwort ist „{password}“"
@ -457,7 +460,7 @@ aboutX: "Über {x}"
emojiStyle: "Emoji-Stil" emojiStyle: "Emoji-Stil"
native: "Nativ" native: "Nativ"
disableDrawer: "Keine ausfahrbaren Menüs verwenden" disableDrawer: "Keine ausfahrbaren Menüs verwenden"
showNoteActionsOnlyHover: "Aktionen für Notizen nur bei Mouseover anzeigen" showNoteActionsOnlyHover: "Notizmenü nur bei Mouseover anzeigen"
noHistory: "Kein Verlauf gefunden" noHistory: "Kein Verlauf gefunden"
signinHistory: "Anmeldungsverlauf" signinHistory: "Anmeldungsverlauf"
enableAdvancedMfm: "Erweitertes MFM aktivieren" enableAdvancedMfm: "Erweitertes MFM aktivieren"
@ -497,15 +500,17 @@ objectStoragePrefixDesc: "Dateien werden in Ordnern unter diesem Prefix gespeich
objectStorageEndpoint: "Endpoint" objectStorageEndpoint: "Endpoint"
objectStorageEndpointDesc: "Im Falle von S3 leerlassen, für andere Anbieter den relevanten Endpoint im Format „<host>“ oder „<host>:<port>“ angeben." objectStorageEndpointDesc: "Im Falle von S3 leerlassen, für andere Anbieter den relevanten Endpoint im Format „<host>“ oder „<host>:<port>“ angeben."
objectStorageRegion: "Region" objectStorageRegion: "Region"
objectStorageRegionDesc: "Gib eine Region wie z.B. „xx-east-1“ an. Falls dein Anbieter nicht zwischen Regionen unterscheidet, lass dieses Feld leer oder gib „us-east-1“ an." objectStorageRegionDesc: "Gib eine Region wie z.B. „xx-east-1“ an. Falls dein Anbieter nicht zwischen Regionen unterscheidet, gib „us-east-1“ an. Lasse es leer bei Verwendung von AWS Konfigurationsdateien oder Umgebungsvariablen."
objectStorageUseSSL: "SSL verwenden" objectStorageUseSSL: "SSL verwenden"
objectStorageUseSSLDesc: "Deaktiviere dies, falls du für API-Verbindungen kein HTTPS verwenden wirst" objectStorageUseSSLDesc: "Deaktiviere dies, falls du für API-Verbindungen kein HTTPS verwenden wirst"
objectStorageUseProxy: "Über Proxy verbinden" objectStorageUseProxy: "Über Proxy verbinden"
objectStorageUseProxyDesc: "Deaktiviere dies, falls du für Verbindungen zur API keinen Proxy verwenden wirst" objectStorageUseProxyDesc: "Deaktiviere dies, falls du für Verbindungen zur API keinen Proxy verwenden wirst"
objectStorageSetPublicRead: "Bei Upload auf \"public-read\" stellen" objectStorageSetPublicRead: "Bei Upload auf \"public-read\" stellen"
s3ForcePathStyleDesc: "Ist s3ForcePathStyle aktiviert, so muss der Bucketname nicht im Hostnamen der URL, sondern im Pfad der URL angeben werden. Diese Option muss eventuell aktiviert werden, wenn Dienste wie z.B. eine selbstbetriebene Minio-Instanz verwendet werden."
serverLogs: "Serverprotokolle" serverLogs: "Serverprotokolle"
deleteAll: "Alle löschen" deleteAll: "Alle löschen"
showFixedPostForm: "Bereich zum Schreiben neuer Notizen am Anfang der Chronik anzeigen" showFixedPostForm: "Bereich zum Schreiben neuer Notizen am Anfang der Chronik anzeigen"
showFixedPostFormInChannel: "Bereich zum Schreiben neuer Notizen am Anfang der Chronik anzeigen (Kanäle)"
newNoteRecived: "Es gibt neue Notizen" newNoteRecived: "Es gibt neue Notizen"
sounds: "Töne" sounds: "Töne"
sound: "Töne" sound: "Töne"
@ -543,6 +548,10 @@ userSuspended: "Dieser Benutzer wurde gesperrt."
userSilenced: "Dieser Benutzer wurde instanzweit stummgeschaltet." userSilenced: "Dieser Benutzer wurde instanzweit stummgeschaltet."
yourAccountSuspendedTitle: "Dieses Benutzerkonto ist gesperrt" yourAccountSuspendedTitle: "Dieses Benutzerkonto ist gesperrt"
yourAccountSuspendedDescription: "Dieses Benutzerkonto wurde gesperrt, da es gegen die Nutzungsbedingungen dieses Servers verstoßen hat. Trete mit dem Betreiber in Kontakt, falls du weitere Details erfahren möchtest. Bitte erstelle kein neues Benutzerkonto." yourAccountSuspendedDescription: "Dieses Benutzerkonto wurde gesperrt, da es gegen die Nutzungsbedingungen dieses Servers verstoßen hat. Trete mit dem Betreiber in Kontakt, falls du weitere Details erfahren möchtest. Bitte erstelle kein neues Benutzerkonto."
tokenRevoked: "Ungültiger Token"
tokenRevokedDescription: "Der Token ist abgelaufen. Bitte melde dich erneut an."
accountDeleted: "Benutzerkonto wurde gelöscht"
accountDeletedDescription: "Dieses Konto wurde gelöscht."
menu: "Menü" menu: "Menü"
divider: "Trenner" divider: "Trenner"
addItem: "Element hinzufügen" addItem: "Element hinzufügen"
@ -586,7 +595,6 @@ tokenRequested: "Zugriff zum Benutzerkonto gewähren"
pluginTokenRequestedDescription: "Dieses Plugin wird die hier konfigurierten Berechtigungen verwenden können." pluginTokenRequestedDescription: "Dieses Plugin wird die hier konfigurierten Berechtigungen verwenden können."
notificationType: "Art der Benachrichtigung" notificationType: "Art der Benachrichtigung"
edit: "Bearbeiten" edit: "Bearbeiten"
useStarForReactionFallback: "Verwende ★ falls das Reaktions-Emoji unbekannt ist"
emailServer: "Email-Server" emailServer: "Email-Server"
enableEmail: "Email-Versand aktivieren" enableEmail: "Email-Versand aktivieren"
emailConfigInfo: "Zur Email-Bestätigung bei Registrierung oder zum Zurücksetzen des Passworts verwendet" emailConfigInfo: "Zur Email-Bestätigung bei Registrierung oder zum Zurücksetzen des Passworts verwendet"
@ -912,6 +920,7 @@ pushNotificationNotSupported: "Entweder dein Browser oder deine Instanz unterst
sendPushNotificationReadMessage: "Push-Benachrichtigungen löschen, sobald die relevanten Benachrichtigungen oder Nachrichten gelesen wurden" sendPushNotificationReadMessage: "Push-Benachrichtigungen löschen, sobald die relevanten Benachrichtigungen oder Nachrichten gelesen wurden"
sendPushNotificationReadMessageCaption: "Eine Push-Benachrichtigungen mit dem Inhalt \"{emptyPushNotificationMessage}\" wird kurz eingeblendet. Dies kann gegebenenfalls den Batterieverbrauch deines Gerätes erhöhen." sendPushNotificationReadMessageCaption: "Eine Push-Benachrichtigungen mit dem Inhalt \"{emptyPushNotificationMessage}\" wird kurz eingeblendet. Dies kann gegebenenfalls den Batterieverbrauch deines Gerätes erhöhen."
windowMaximize: "Maximieren" windowMaximize: "Maximieren"
windowMinimize: "Minimieren"
windowRestore: "Wiederherstellen" windowRestore: "Wiederherstellen"
caption: "Beschreibung" caption: "Beschreibung"
loggedInAsBot: "Momentan als Bot angemeldet" loggedInAsBot: "Momentan als Bot angemeldet"
@ -953,8 +962,43 @@ copyErrorInfo: "Fehlerdetails kopieren"
joinThisServer: "Bei dieser Instanz registrieren" joinThisServer: "Bei dieser Instanz registrieren"
exploreOtherServers: "Eine andere Instanz finden" exploreOtherServers: "Eine andere Instanz finden"
letsLookAtTimeline: "Die Chronik durchstöbern" letsLookAtTimeline: "Die Chronik durchstöbern"
disableFederationWarn: "Dies deaktiviert Föderation, aber alle Notizen bleiben, sofern nicht umgestellt, öffentlich. In den meisten Fällen wird diese Option nicht benötigt." disableFederationConfirm: "Föderation wirklich deaktivieren?"
disableFederationConfirmWarn: "Auch mit deaktivierter Föderation bleiben Notizen, sofern nicht umgestellt, öffentlich. In den meisten Fällen wird dies nicht benötigt."
disableFederationOk: "Deaktivieren"
invitationRequiredToRegister: "Diese Instanz ist einladungsbasiert. Du musst einen validen Einladungscode eingeben, um dich zu registrieren." invitationRequiredToRegister: "Diese Instanz ist einladungsbasiert. Du musst einen validen Einladungscode eingeben, um dich zu registrieren."
emailNotSupported: "Diese Instanz unterstützt das Versenden von Emails nicht"
postToTheChannel: "In Kanal senden"
cannotBeChangedLater: "Kann später nicht mehr geändert werden."
reactionAcceptance: "Reaktionsannahme"
likeOnly: "Nur \"Gefällt mir\""
likeOnlyForRemote: "Nur \"Gefällt mir\" für fremde Instanzen"
rolesAssignedToMe: "Mir zugewiesene Rollen"
resetPasswordConfirm: "Wirklich Passwort zurücksetzen?"
sensitiveWords: "Sensible Wörter"
sensitiveWordsDescription: "Die Notizsichtbarkeit aller Notizen, die diese Wörter enthalten, wird automatisch auf \"Startseite\" gesetzt. Durch Zeilenumbrüche können mehrere konfiguriert werden."
notesSearchNotAvailable: "Die Notizsuche ist nicht verfügbar."
license: "Lizenz"
unfavoriteConfirm: "Wirklich aus Favoriten entfernen?"
myClips: "Meine Clips"
drivecleaner: "Drive-Reiniger"
retryAllQueuesNow: "Sofort Warteschlangen erneut ausführen"
retryAllQueuesConfirmTitle: "Wirklich erneut versuchen?"
retryAllQueuesConfirmText: "Dies wird zu einer temporären Erhöhung der Serverlast führen."
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"
noteIdOrUrl: "Notiz-ID oder URL"
accountMigration: "Konto-Umzug"
accountMoved: "Dieser Benutzer ist zu einem neuen Konto umgezogen:"
_accountMigration:
moveTo: "Dieses Konto zu einem neuen umziehen"
moveToLabel: "Umzugsziel:"
moveAccountDescription: "Ein Umzug kann nicht rückgängig gemacht werden. Stelle zuerst sicher, dass du auf dem Umzugsziel einen Alias erstellt hast. Gib dann das Umzugsziel in folgendem Format ein: @person@instance.com"
moveFrom: "Von einem anderen Konto zu diesem umziehen"
moveFromLabel: "Umzugsursprung:"
moveFromDescription: "Stelle sicher, dass du auf dem Umzugsursprungskonto einen Alias zu diesem Konto erstellt hast, falls du die Follower des Ursprungskontos übertragen möchtest. Dies muss vor dem Umzug geschehen! Gib dann das Ursprungskonto in folgendem Format an: @person@instance.com"
migrationConfirm: "Dieses Konto wirklich zu {account} umziehen? Sobald der Umzug beginnt, kann er nicht rückgängig gemacht werden, und dieses Konto nicht wieder im ursprünglichen Zustand verwendet werden.\n\nÜberprüfe zusätzlich, dass du auf dem Umzugsziel einen Alias eingerichtet hast."
_achievements: _achievements:
earnedAt: "Freigeschaltet am" earnedAt: "Freigeschaltet am"
_types: _types:
@ -1102,7 +1146,7 @@ _achievements:
title: "Beliebt" title: "Beliebt"
description: "Die Anzahl deiner Follower hat 100 überschritten" description: "Die Anzahl deiner Follower hat 100 überschritten"
_followers300: _followers300:
title: "Stellt euch bitte in einer Reihe auf" title: "Eine geordnete Reihe, bitte!"
description: "Die Anzahl deiner Follower hat 300 überschritten" description: "Die Anzahl deiner Follower hat 300 überschritten"
_followers500: _followers500:
title: "Funkmast" title: "Funkmast"
@ -1163,7 +1207,7 @@ _achievements:
description: "Du hast hier geklickt" description: "Du hast hier geklickt"
_justPlainLucky: _justPlainLucky:
title: "Pures Glück" title: "Pures Glück"
description: "Kann alle 10 Sekunden mit einer Warscheinlichkeit von 0.01% erhalten werden" description: "Kann alle 10 Sekunden mit einer Warscheinlichkeit von 0.005% erhalten werden"
_setNameToSyuilo: _setNameToSyuilo:
title: "Gottkomplex" title: "Gottkomplex"
description: "Setze deinen Namen auf \"syuilo\"" description: "Setze deinen Namen auf \"syuilo\""
@ -1214,6 +1258,8 @@ _role:
iconUrl: "Icon-URL" iconUrl: "Icon-URL"
asBadge: "Als Abzeichen anzeigen" asBadge: "Als Abzeichen anzeigen"
descriptionOfAsBadge: "Ist dies aktiviert, so wird das Icon dieser Rolle an der Seite der Namen von Benutzern mit dieser Rolle angezeigt." descriptionOfAsBadge: "Ist dies aktiviert, so wird das Icon dieser Rolle an der Seite der Namen von Benutzern mit dieser Rolle angezeigt."
displayOrder: "Position"
descriptionOfDisplayOrder: "Je höher die Nummer, desto höher die UI-Position."
canEditMembersByModerator: "Moderatoren können Benutzern diese Rolle zuweisen" canEditMembersByModerator: "Moderatoren können Benutzern diese Rolle zuweisen"
descriptionOfCanEditMembersByModerator: "Wenn aktiviert, so können Moderatoren und Adminstratoren anderen Benutzern diese Rolle zuweisen bzw. diese Zuweisung aufheben. Wenn deaktiviert, so ist es nur Administratoren möglich, Zuweisungen dieser Rolle zu verwalten." descriptionOfCanEditMembersByModerator: "Wenn aktiviert, so können Moderatoren und Adminstratoren anderen Benutzern diese Rolle zuweisen bzw. diese Zuweisung aufheben. Wenn deaktiviert, so ist es nur Administratoren möglich, Zuweisungen dieser Rolle zu verwalten."
priority: "Priorität" priority: "Priorität"
@ -1225,7 +1271,7 @@ _role:
gtlAvailable: "Kann auf die globale Chronik zugreifen" gtlAvailable: "Kann auf die globale Chronik zugreifen"
ltlAvailable: "Kann auf die lokale Chronik zugreifen" ltlAvailable: "Kann auf die lokale Chronik zugreifen"
canPublicNote: "Kann öffentliche Notizen erstellen" canPublicNote: "Kann öffentliche Notizen erstellen"
canInvite: "Kann Einladungscodes für diese Instanz erstellen" canInvite: "Erstellung von Einladungscodes für diese Instanz"
canManageCustomEmojis: "Benutzerdefinierte Emojis verwalten" canManageCustomEmojis: "Benutzerdefinierte Emojis verwalten"
driveCapacity: "Drive-Kapazität" driveCapacity: "Drive-Kapazität"
pinMax: "Maximale Anzahl an angehefteten Notizen" pinMax: "Maximale Anzahl an angehefteten Notizen"
@ -1239,6 +1285,7 @@ _role:
rateLimitFactor: "Versuchsanzahl" rateLimitFactor: "Versuchsanzahl"
descriptionOfRateLimitFactor: "Je niedriger desto weniger restriktiv, je höher destro restriktiver." descriptionOfRateLimitFactor: "Je niedriger desto weniger restriktiv, je höher destro restriktiver."
canHideAds: "Kann Werbung ausblenden" canHideAds: "Kann Werbung ausblenden"
canSearchNotes: "Nutzung der Notizsuchfunktion"
_condition: _condition:
isLocal: "Lokaler Benutzer" isLocal: "Lokaler Benutzer"
isRemote: "Benutzer fremder Instanz" isRemote: "Benutzer fremder Instanz"
@ -1248,6 +1295,8 @@ _role:
followersMoreThanOrEq: "Hat X oder mehr Follower" followersMoreThanOrEq: "Hat X oder mehr Follower"
followingLessThanOrEq: "Folgt X oder weniger Benutzern" followingLessThanOrEq: "Folgt X oder weniger Benutzern"
followingMoreThanOrEq: "Folgt X oder mehr Benutzern" followingMoreThanOrEq: "Folgt X oder mehr Benutzern"
notesLessThanOrEq: "Beitragszahl ist kleiner-gleich"
notesMoreThanOrEq: "Beitragszahl ist größer-gleich"
and: "UND-Bedingung" and: "UND-Bedingung"
or: "ODER-Bedingung" or: "ODER-Bedingung"
not: "NICHT-Bedingung" not: "NICHT-Bedingung"
@ -1375,7 +1424,7 @@ _instanceMute:
instanceMuteDescription: "Schaltet alle Notizen/Renotes stumm, die von den gelisteten Instanzen stammen, inklusive Antworten von Benutzern an einen Benutzer einer stummgeschalteten Instanz." 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" instanceMuteDescription2: "Instanzen getrennt durch Zeilenumbrüchen angeben"
title: "Blendet Notizen von stummgeschalteten Instanzen aus." title: "Blendet Notizen von stummgeschalteten Instanzen aus."
heading: "Liste der stummzuschaltenden Instanzen" heading: "Stummzuschaltende Instanzen"
_theme: _theme:
explore: "Farbschemata erforschen" explore: "Farbschemata erforschen"
install: "Farbschemata installieren" install: "Farbschemata installieren"
@ -1510,7 +1559,7 @@ _2fa:
step2Url: "Nutzt du ein Desktopprogramm kannst du alternativ diese URL eingeben:" step2Url: "Nutzt du ein Desktopprogramm kannst du alternativ diese URL eingeben:"
step3Title: "Authentifizierungsscode eingeben" step3Title: "Authentifizierungsscode eingeben"
step3: "Gib zum Abschluss den Token ein, der von deiner App angezeigt wird." step3: "Gib zum Abschluss den Token ein, der von deiner App angezeigt wird."
step4: "Alle folgenden Anmeldungsversuche werden ab sofort die Eingabe eines solchen Tokens benötigen." step4: "Alle folgenden Anmeldeversuche werden ab sofort die Eingabe eines solchen Tokens benötigen."
securityKeyNotSupported: "Dein Browser unterstützt keine Security-Tokens." securityKeyNotSupported: "Dein Browser unterstützt keine Security-Tokens."
registerTOTPBeforeKey: "Um einen Security-Token oder einen Passkey zu registrieren, musst du zuerst eine Authentifizierungs-App registrieren." 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." securityKeyInfo: "Du kannst neben Fingerabdruck- oder PIN-Authentifizierung auf deinem Gerät auch Anmeldung mit Hilfe eines FIDO2-kompatiblen Hardware-Sicherheitsschlüssels einrichten."
@ -1840,3 +1889,23 @@ _deck:
_dialog: _dialog:
charactersExceeded: "Maximallänge überschritten! Momentan {current} von {max}" charactersExceeded: "Maximallänge überschritten! Momentan {current} von {max}"
charactersBelow: "Minimallänge unterschritten! Momentan {current} von {min}" charactersBelow: "Minimallänge unterschritten! Momentan {current} von {min}"
_disabledTimeline:
title: "Chronik deaktiviert"
description: "Mit deinen jetzigen Rollen ist diese Chronik nicht verfügbar."
_drivecleaner:
orderBySizeDesc: "Absteigende Dateigrößen"
orderByCreatedAtAsc: "Aufsteigendes Erstelldatum"
_webhookSettings:
createWebhook: "Webhook erstellen"
name: "Name"
secret: "Secret"
events: "Webhook-Ereignisse"
active: "Aktiviert"
_events:
follow: "Wenn du jemandem folgst"
followed: "Wenn dir jemand folgt"
note: "Wenn du eine Notiz schickst"
reply: "Wenn du eine Antwort erhältst"
renote: "Wenn du ein Renote erhältst"
reaction: "Wenn du eine Reaktion erhältst"
mention: "Wenn du erwähnt wirst"

View File

@ -392,3 +392,5 @@ _deck:
antenna: "Αντένες" antenna: "Αντένες"
list: "Λίστα" list: "Λίστα"
mentions: "Επισημάνσεις" mentions: "Επισημάνσεις"
_webhookSettings:
name: "Όνομα"

View File

@ -67,7 +67,7 @@ import: "Import"
export: "Export" export: "Export"
files: "Files" files: "Files"
download: "Download" download: "Download"
driveFileDeleteConfirm: "Are you sure you want to delete the file \"{name}\"? Notes with this file attached will also be deleted." driveFileDeleteConfirm: "Are you sure you want to delete \"{name}\"? All notes with this file attached will also be deleted."
unfollowConfirm: "Are you sure you want to unfollow {name}?" 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." 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." importRequested: "You've requested an import. This may take a while."
@ -122,6 +122,8 @@ unmarkAsSensitive: "Unmark as NSFW"
enterFileName: "Enter filename" enterFileName: "Enter filename"
mute: "Mute" mute: "Mute"
unmute: "Unmute" unmute: "Unmute"
renoteMute: "Mute Renotes"
renoteUnmute: "Unmute Renotes"
block: "Block" block: "Block"
unblock: "Unblock" unblock: "Unblock"
suspend: "Suspend" suspend: "Suspend"
@ -153,6 +155,7 @@ flagShowTimelineReplies: "Show replies in timeline"
flagShowTimelineRepliesDescription: "Shows replies of users to notes of other users in the timeline if turned on." flagShowTimelineRepliesDescription: "Shows replies of users to notes of other users in the timeline if turned on."
autoAcceptFollowed: "Automatically approve follow requests from users you're following" autoAcceptFollowed: "Automatically approve follow requests from users you're following"
addAccount: "Add account" addAccount: "Add account"
reloadAccountsList: "Reload account list"
loginFailed: "Failed to sign in" loginFailed: "Failed to sign in"
showOnRemote: "View on remote instance" showOnRemote: "View on remote instance"
general: "General" general: "General"
@ -197,7 +200,7 @@ clearQueueConfirmText: "Any undelivered notes remaining in the queue will not be
clearCachedFiles: "Clear cache" clearCachedFiles: "Clear cache"
clearCachedFilesConfirm: "Are you sure that you want to delete all cached remote files?" clearCachedFilesConfirm: "Are you sure that you want to delete all cached remote files?"
blockedInstances: "Blocked Instances" blockedInstances: "Blocked Instances"
blockedInstancesDescription: "List the hostnames of the instances that you want to block. Listed instances will no longer be able to communicate with this instance." 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."
muteAndBlock: "Mutes and Blocks" muteAndBlock: "Mutes and Blocks"
mutedUsers: "Muted users" mutedUsers: "Muted users"
blockedUsers: "Blocked users" blockedUsers: "Blocked users"
@ -503,9 +506,11 @@ objectStorageUseSSLDesc: "Turn this off if you are not going to use HTTPS for AP
objectStorageUseProxy: "Connect over Proxy" objectStorageUseProxy: "Connect over Proxy"
objectStorageUseProxyDesc: "Turn this off if you are not going to use a Proxy for API connections" objectStorageUseProxyDesc: "Turn this off if you are not going to use a Proxy for API connections"
objectStorageSetPublicRead: "Set \"public-read\" on upload" objectStorageSetPublicRead: "Set \"public-read\" on upload"
s3ForcePathStyleDesc: "If s3ForcePathStyle is enabled, the bucket name has to included in the path of the URL as opposed to the hostname of the URL. You may need to enable this setting when using services such as a self-hosted Minio instance."
serverLogs: "Server logs" serverLogs: "Server logs"
deleteAll: "Delete all" deleteAll: "Delete all"
showFixedPostForm: "Display the posting form at the top of the timeline" showFixedPostForm: "Display the posting form at the top of the timeline"
showFixedPostFormInChannel: "Display the posting form at the top of the timeline (Channels)"
newNoteRecived: "There are new notes" newNoteRecived: "There are new notes"
sounds: "Sounds" sounds: "Sounds"
sound: "Sounds" sound: "Sounds"
@ -526,7 +531,7 @@ nothing: "There's nothing to see here"
installedDate: "Authorized at" installedDate: "Authorized at"
lastUsedDate: "Last used at" lastUsedDate: "Last used at"
state: "State" state: "State"
sort: "Sort" sort: "Sorting order"
ascendingOrder: "Ascending" ascendingOrder: "Ascending"
descendingOrder: "Descending" descendingOrder: "Descending"
scratchpad: "Scratchpad" scratchpad: "Scratchpad"
@ -543,6 +548,10 @@ userSuspended: "This user has been suspended."
userSilenced: "This user is being silenced." userSilenced: "This user is being silenced."
yourAccountSuspendedTitle: "This account is suspended" yourAccountSuspendedTitle: "This account is suspended"
yourAccountSuspendedDescription: "This account has been suspended due to breaking the server's terms of services or similar. Contact the administrator if you would like to know a more detailed reason. Please do not create a new account." yourAccountSuspendedDescription: "This account has been suspended due to breaking the server's terms of services or similar. Contact the administrator if you would like to know a more detailed reason. Please do not create a new account."
tokenRevoked: "Invalid token"
tokenRevokedDescription: "This token has expired. Please log in again."
accountDeleted: "Account deleted"
accountDeletedDescription: "This account has been deleted."
menu: "Menu" menu: "Menu"
divider: "Divider" divider: "Divider"
addItem: "Add Item" addItem: "Add Item"
@ -586,7 +595,6 @@ tokenRequested: "Grant access to account"
pluginTokenRequestedDescription: "This plugin will be able to use the permissions set here." pluginTokenRequestedDescription: "This plugin will be able to use the permissions set here."
notificationType: "Notification type" notificationType: "Notification type"
edit: "Edit" edit: "Edit"
useStarForReactionFallback: "Use ★ as fallback if the reaction emoji is unknown"
emailServer: "Email server" emailServer: "Email server"
enableEmail: "Enable email distribution" enableEmail: "Enable email distribution"
emailConfigInfo: "Used to confirm your email during sign-up or if you forget your password" emailConfigInfo: "Used to confirm your email during sign-up or if you forget your password"
@ -912,6 +920,7 @@ pushNotificationNotSupported: "Your browser or instance does not support push no
sendPushNotificationReadMessage: "Delete push notifications once the relevant notifications or messages have been read" sendPushNotificationReadMessage: "Delete push notifications once the relevant notifications or messages have been read"
sendPushNotificationReadMessageCaption: "A notification containing the text \"{emptyPushNotificationMessage}\" will be displayed for a short time. This may increase the battery usage of your device, if applicable." sendPushNotificationReadMessageCaption: "A notification containing the text \"{emptyPushNotificationMessage}\" will be displayed for a short time. This may increase the battery usage of your device, if applicable."
windowMaximize: "Maximize" windowMaximize: "Maximize"
windowMinimize: "Minimize"
windowRestore: "Restore" windowRestore: "Restore"
caption: "Caption" caption: "Caption"
loggedInAsBot: "Currently logged in as bot" loggedInAsBot: "Currently logged in as bot"
@ -953,8 +962,44 @@ copyErrorInfo: "Copy error details"
joinThisServer: "Sign up at this instance" joinThisServer: "Sign up at this instance"
exploreOtherServers: "Look for another instance" exploreOtherServers: "Look for another instance"
letsLookAtTimeline: "Have a look at the timeline" letsLookAtTimeline: "Have a look at the timeline"
disableFederationWarn: "This will disable federation, but posts will continue to be public unless set otherwise. You usually do not need to use this setting." disableFederationConfirm: "Really disable federation?"
disableFederationConfirmWarn: "Even if defederated, posts will continue to be public unless set otherwise. You usually do not need to do this."
disableFederationOk: "Disable"
invitationRequiredToRegister: "This instance is invite-only. You must enter a valid invite code sign up." invitationRequiredToRegister: "This instance is invite-only. You must enter a valid invite code sign up."
emailNotSupported: "This instance does not support sending emails"
postToTheChannel: "Post to channel"
cannotBeChangedLater: "This cannot be changed later."
reactionAcceptance: "Reaction Acceptance"
likeOnly: "Only likes"
likeOnlyForRemote: "Only likes for remote instances"
rolesAssignedToMe: "Roles assigned to me"
resetPasswordConfirm: "Really reset your password?"
sensitiveWords: "Sensitive words"
sensitiveWordsDescription: "The visibility of all notes containing any of the configured words will be set to \"Home\" automatically. You can list multiple by separating them via line breaks."
notesSearchNotAvailable: "Note search is unavailable."
license: "License"
unfavoriteConfirm: "Really remove from favorites?"
myClips: "My clips"
drivecleaner: "Drive Cleaner"
retryAllQueuesNow: "Retry running all queues"
retryAllQueuesConfirmTitle: "Really retry all?"
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"
noteIdOrUrl: "Note ID or URL"
accountMigration: "Account Migration"
accountMoved: "This user has moved to a new account:"
_accountMigration:
moveTo: "Migrate this account to a different one"
moveToLabel: "Account to move to:"
moveAccountDescription: "This action is irreversible. First, create an alias for this account on the account you wish to move to. Then, enter the account to move to in the following format: @person@instance.com"
moveFrom: "Migrate another account to this one"
moveFromLabel: "Account to move from:"
moveFromDescription: "Create an alias for the account to move from on this account if you wish to transfer its followers. This has to be done before the transfer! Then, enter the account to move to in the following format: @person@instance.com"
migrationConfirm: "Really migrate this account to {account}? Once started, this process cannot be stopped or taken back, and you will not be able to use this account in its original state anymore.\n\nAlso, confirm you've created an alias at the account to migrate to."
_achievements: _achievements:
earnedAt: "Unlocked at" earnedAt: "Unlocked at"
_types: _types:
@ -1182,7 +1227,7 @@ _achievements:
_loggedInOnNewYearsDay: _loggedInOnNewYearsDay:
title: "Happy New Year!" title: "Happy New Year!"
description: "Logged in on the first day of the year" description: "Logged in on the first day of the year"
flavor: "To another great year!" flavor: "To another great year on this instance"
_cookieClicked: _cookieClicked:
title: "A game in which you click cookies" title: "A game in which you click cookies"
description: "Clicked the cookie" description: "Clicked the cookie"
@ -1214,6 +1259,8 @@ _role:
iconUrl: "Icon URL" iconUrl: "Icon URL"
asBadge: "Show as badge" asBadge: "Show as badge"
descriptionOfAsBadge: "This role's icon will be displayed next to the username of users with this role if turned on." descriptionOfAsBadge: "This role's icon will be displayed next to the username of users with this role if turned on."
displayOrder: "Position"
descriptionOfDisplayOrder: "The higher the number, the higher its UI position."
canEditMembersByModerator: "Allow moderators to edit the list of members for this role" canEditMembersByModerator: "Allow moderators to edit the list of members for this role"
descriptionOfCanEditMembersByModerator: "When turned on, moderators as well as administrators will be able to assign and unassign users to this role. When turned off, only administrators will be able to assign users." descriptionOfCanEditMembersByModerator: "When turned on, moderators as well as administrators will be able to assign and unassign users to this role. When turned off, only administrators will be able to assign users."
priority: "Priority" priority: "Priority"
@ -1239,6 +1286,7 @@ _role:
rateLimitFactor: "Rate limit" rateLimitFactor: "Rate limit"
descriptionOfRateLimitFactor: "Lower rate limits are less restrictive, higher ones more restrictive. " descriptionOfRateLimitFactor: "Lower rate limits are less restrictive, higher ones more restrictive. "
canHideAds: "Can hide ads" canHideAds: "Can hide ads"
canSearchNotes: "Usage of note search"
_condition: _condition:
isLocal: "Local user" isLocal: "Local user"
isRemote: "Remote user" isRemote: "Remote user"
@ -1248,6 +1296,8 @@ _role:
followersMoreThanOrEq: "Has X or more followers" followersMoreThanOrEq: "Has X or more followers"
followingLessThanOrEq: "Follows X or fewer accounts" followingLessThanOrEq: "Follows X or fewer accounts"
followingMoreThanOrEq: "Follows X or more accounts" followingMoreThanOrEq: "Follows X or more accounts"
notesLessThanOrEq: "Post count is less than/equal to"
notesMoreThanOrEq: "Post count is greater than/equal to"
and: "AND-Condition" and: "AND-Condition"
or: "OR-Condition" or: "OR-Condition"
not: "NOT-Condition" not: "NOT-Condition"
@ -1840,3 +1890,23 @@ _deck:
_dialog: _dialog:
charactersExceeded: "You've exceeded the maximum character limit! Currently at {current} of {max}." charactersExceeded: "You've exceeded the maximum character limit! Currently at {current} of {max}."
charactersBelow: "You're below the minimum character limit! Currently at {current} of {min}." charactersBelow: "You're below the minimum character limit! Currently at {current} of {min}."
_disabledTimeline:
title: "Timeline disabled"
description: "You cannot use this timeline under your current roles."
_drivecleaner:
orderBySizeDesc: "Descending Filesizes"
orderByCreatedAtAsc: "Ascending Dates"
_webhookSettings:
createWebhook: "Create Webhook"
name: "Name"
secret: "Secret"
events: "Webhook Events"
active: "Enabled"
_events:
follow: "When following a user"
followed: "When being followed"
note: "When posting a note"
reply: "When receiving a reply"
renote: "When renoted"
reaction: "When receiving a reaction"
mention: "When being mentioned"

View File

@ -122,6 +122,8 @@ unmarkAsSensitive: "Desmarcar como sensible"
enterFileName: "Ingrese el nombre del archivo" enterFileName: "Ingrese el nombre del archivo"
mute: "Silenciar" mute: "Silenciar"
unmute: "Dejar de silenciar" unmute: "Dejar de silenciar"
renoteMute: "Silenciar renota"
renoteUnmute: "Desilenciar renota"
block: "Bloquear" block: "Bloquear"
unblock: "Dejar de bloquear" unblock: "Dejar de bloquear"
suspend: "Suspender" suspend: "Suspender"
@ -153,6 +155,7 @@ flagShowTimelineReplies: "Mostrar respuestas a las notas en la biografía"
flagShowTimelineRepliesDescription: "Cuando se marca, la línea de tiempo muestra respuestas a otras notas además de las notas del usuario" flagShowTimelineRepliesDescription: "Cuando se marca, la línea de tiempo muestra respuestas a otras notas además de las notas del usuario"
autoAcceptFollowed: "Aceptar automáticamente las solicitudes de seguimiento de los usuarios que sigues" autoAcceptFollowed: "Aceptar automáticamente las solicitudes de seguimiento de los usuarios que sigues"
addAccount: "Agregar Cuenta" addAccount: "Agregar Cuenta"
reloadAccountsList: "Recargar lista de cuentas"
loginFailed: "Error al iniciar sesión." loginFailed: "Error al iniciar sesión."
showOnRemote: "Ver en una instancia remota" showOnRemote: "Ver en una instancia remota"
general: "General" general: "General"
@ -503,9 +506,11 @@ objectStorageUseSSLDesc: "Desactive esto si no va a usar HTTPS para la conexión
objectStorageUseProxy: "Conectarse a través de Proxy" objectStorageUseProxy: "Conectarse a través de Proxy"
objectStorageUseProxyDesc: "Desactive esto si no va a usar Proxy para la conexión de Almacenamiento de objetos" objectStorageUseProxyDesc: "Desactive esto si no va a usar Proxy para la conexión de Almacenamiento de objetos"
objectStorageSetPublicRead: "Seleccionar \"public-read\" al subir " objectStorageSetPublicRead: "Seleccionar \"public-read\" al subir "
s3ForcePathStyleDesc: "Si s3ForcePathStyle esta habilitado el nombre del bucket debe ser especificado como parte de la URL en lugar del nombre de host en la URL. Puede ser necesario activar esta opción cuando se utilice, por ejemplo, Minio en un servidor propio."
serverLogs: "Registros del servidor" serverLogs: "Registros del servidor"
deleteAll: "Eliminar todos" deleteAll: "Eliminar todos"
showFixedPostForm: "Mostrar el formulario de las entradas encima de la línea de tiempo" 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)"
newNoteRecived: "Tienes una nota nueva" newNoteRecived: "Tienes una nota nueva"
sounds: "Sonidos" sounds: "Sonidos"
sound: "Sonidos" sound: "Sonidos"
@ -543,6 +548,10 @@ userSuspended: "Este usuario ha sido suspendido."
userSilenced: "Este usuario ha sido silenciado." userSilenced: "Este usuario ha sido silenciado."
yourAccountSuspendedTitle: "Esta cuenta ha sido suspendida" yourAccountSuspendedTitle: "Esta cuenta ha sido suspendida"
yourAccountSuspendedDescription: "Esta cuenta ha sido suspendida debido a violaciones de los términos de servicio del servidor y otras razones. Para más información, póngase en contacto con el administrador. Por favor, no cree una nueva cuenta." yourAccountSuspendedDescription: "Esta cuenta ha sido suspendida debido a violaciones de los términos de servicio del servidor y otras razones. Para más información, póngase en contacto con el administrador. Por favor, no cree una nueva cuenta."
tokenRevoked: "Token inválido"
tokenRevokedDescription: "Este token expiró, vuelve a iniciar sesión."
accountDeleted: "Cuenta borrada"
accountDeletedDescription: "Esta cuenta ha sido borrada."
menu: "Menú" menu: "Menú"
divider: "Divisor" divider: "Divisor"
addItem: "Agregar elemento" addItem: "Agregar elemento"
@ -586,7 +595,6 @@ tokenRequested: "Permiso de acceso a la cuenta"
pluginTokenRequestedDescription: "Este plugin podrá usar los permisos descritos aquí" pluginTokenRequestedDescription: "Este plugin podrá usar los permisos descritos aquí"
notificationType: "Tipo de notificación" notificationType: "Tipo de notificación"
edit: "Editar" edit: "Editar"
useStarForReactionFallback: "En caso de que los emojis de reacciones no sean claros, usar en su lugar una estrella"
emailServer: "Servidor de correo" emailServer: "Servidor de correo"
enableEmail: "Activar el envío de correos electrónicos" enableEmail: "Activar el envío de correos electrónicos"
emailConfigInfo: "Usar en caso de validación de correo electrónico y pedido de contraseña" emailConfigInfo: "Usar en caso de validación de correo electrónico y pedido de contraseña"
@ -912,6 +920,7 @@ pushNotificationNotSupported: "El navegador o la instancia no admiten notificaci
sendPushNotificationReadMessage: "Eliminar las notificaciones push después de leer las notificaciones y los mensajes" sendPushNotificationReadMessage: "Eliminar las notificaciones push después de leer las notificaciones y los mensajes"
sendPushNotificationReadMessageCaption: "La notificación \"{emptyPushNotificationMessage}\" aparecerá momentáneamente. Esto puede aumentar el consumo de batería del dispositivo." sendPushNotificationReadMessageCaption: "La notificación \"{emptyPushNotificationMessage}\" aparecerá momentáneamente. Esto puede aumentar el consumo de batería del dispositivo."
windowMaximize: "Maximizar" windowMaximize: "Maximizar"
windowMinimize: "Minimizar"
windowRestore: "Regresar" windowRestore: "Regresar"
caption: "Pie de foto" caption: "Pie de foto"
loggedInAsBot: "Inicio sesión como cuenta bot." loggedInAsBot: "Inicio sesión como cuenta bot."
@ -953,8 +962,43 @@ copyErrorInfo: "Copiar detalles del error"
joinThisServer: "Registrarse en esta instancia" joinThisServer: "Registrarse en esta instancia"
exploreOtherServers: "Buscar otra instancia" exploreOtherServers: "Buscar otra instancia"
letsLookAtTimeline: "Mirar la línea de tiempo local" letsLookAtTimeline: "Mirar la línea de tiempo local"
disableFederationWarn: "Esto desactivará la federación, pero las publicaciones segurán siendo públicas al menos que se configure diferente. Usualmente no necesitas usar esta configuración." disableFederationConfirm: "¿Estas seguro que quieres desactivar la federación?"
disableFederationConfirmWarn: "Aunque no exista federación los posts no serán marcados como privados. En la mayoría de los casos, no es necesario hacer los posts no federar."
disableFederationOk: "Desactivar."
invitationRequiredToRegister: "Esta instancia está configurada sólo por invitación, tienes que ingresar un código de invitación válido." invitationRequiredToRegister: "Esta instancia está configurada sólo por invitación, tienes que ingresar un código de invitación válido."
emailNotSupported: "Esta instancia no soporta el envío de correo electrónico"
postToTheChannel: "Publicar en el canal"
cannotBeChangedLater: "Esto no podrá ser cambiado después."
reactionAcceptance: "Aceptación de reacciones"
likeOnly: "Sólo 'me gusta'"
likeOnlyForRemote: "Sólo reacciones de instancias remotas"
rolesAssignedToMe: "Roles asignados a mí"
resetPasswordConfirm: "¿Realmente quieres cambiar la contraseña?"
sensitiveWords: "Palabras sensibles"
sensitiveWordsDescription: "La visibilidad de todas las notas que contienen cualquiera de las palabras configuradas serán puestas en \"Inicio\" automáticamente. Puedes enumerás varias separándolas con saltos de línea"
notesSearchNotAvailable: "No se puede buscar una nota"
license: "Licencia"
unfavoriteConfirm: "¿Desea quitar de favoritos?"
myClips: "Mis clips"
drivecleaner: "Limpiador del Drive"
retryAllQueuesNow: "Reintentar inmediatamente todas las colas"
retryAllQueuesConfirmTitle: "Desea ¿reintentar inmediatamente todas las colas?"
retryAllQueuesConfirmText: "La carga del servidor está incrementándose temporalmente "
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"
noteIdOrUrl: "ID o URL de la nota"
accountMigration: "Migración de cuenta"
accountMoved: "Este usuario se ha mudado a una nueva cuenta:"
_accountMigration:
moveTo: "Mover esta cuenta a una nueva"
moveToLabel: "Cuenta destino:"
moveAccountDescription: "Esta operación no puede deshacerse. En primer lugar, asegúrese de haber creado un alias para esta cuenta en la cuenta a la que se va a trasladar. Después de crear el alias, introduzca la cuenta a la que se está trasladando de la siguiente manera: @person@instance.com"
moveFrom: "Trasladar de otra cuenta a ésta"
moveFromLabel: "Cuenta desde la que se realiza el traslado:"
moveFromDescription: "Si quieres transferir seguidores de otra cuenta a esta cuenta y trasladarlos, tendrás que crear un alias aquí. Asegúrate de crearlo antes de realizar el traslado. Introduce la cuenta desde la que estás moviendo los seguidores así: @person@instance.com"
migrationConfirm: "¿Estás seguro de que quieres mover esta cuenta a {account}? Una vez trasladada, no podrás deshacer el traslado y no podrás volver a utilizar la cuenta original.\n\nAdemás, compruebe que ha configurado un alias en el destino del traslado."
_achievements: _achievements:
earnedAt: "Desbloqueado el" earnedAt: "Desbloqueado el"
_types: _types:
@ -1214,6 +1258,8 @@ _role:
iconUrl: "URL del ícono" iconUrl: "URL del ícono"
asBadge: "Mostrar como emblema" asBadge: "Mostrar como emblema"
descriptionOfAsBadge: "Este ícono de rol se mostrará a lado del nombre de usuario cuando este rol se encuentre activo." descriptionOfAsBadge: "Este ícono de rol se mostrará a lado del nombre de usuario cuando este rol se encuentre activo."
displayOrder: "Posición"
descriptionOfDisplayOrder: "Entre más alto el número, mayor es la posición en la interfaz."
canEditMembersByModerator: "Permitir a los moderadores editar los miembros" canEditMembersByModerator: "Permitir a los moderadores editar los miembros"
descriptionOfCanEditMembersByModerator: "Si se activa, los moderadores, al igual que los administradores, serán capaces de asignar/quitar usuarios a éste rol. Si se desactiva, sólo los administradores podrán hacerlo." descriptionOfCanEditMembersByModerator: "Si se activa, los moderadores, al igual que los administradores, serán capaces de asignar/quitar usuarios a éste rol. Si se desactiva, sólo los administradores podrán hacerlo."
priority: "Prioridad" priority: "Prioridad"
@ -1239,6 +1285,7 @@ _role:
rateLimitFactor: "Limitador" rateLimitFactor: "Limitador"
descriptionOfRateLimitFactor: "Límites más bajos son menos restrictivos, más altos menos restrictivos" descriptionOfRateLimitFactor: "Límites más bajos son menos restrictivos, más altos menos restrictivos"
canHideAds: "Puede ocultar anuncios" canHideAds: "Puede ocultar anuncios"
canSearchNotes: "Uso de la búsqueda de notas"
_condition: _condition:
isLocal: "Usuario local" isLocal: "Usuario local"
isRemote: "Usuario remoto" isRemote: "Usuario remoto"
@ -1248,6 +1295,8 @@ _role:
followersMoreThanOrEq: "Tiene X o más seguidores" followersMoreThanOrEq: "Tiene X o más seguidores"
followingLessThanOrEq: "Sigue X o menos cuentas" followingLessThanOrEq: "Sigue X o menos cuentas"
followingMoreThanOrEq: "Sigue X o más cuentas" followingMoreThanOrEq: "Sigue X o más cuentas"
notesLessThanOrEq: "El número de notas es inferior o igual a"
notesMoreThanOrEq: "El número de notas es superior o igual a"
and: "Condicional AND" and: "Condicional AND"
or: "Condicional OR" or: "Condicional OR"
not: "Condicional NOT" not: "Condicional NOT"
@ -1840,3 +1889,23 @@ _deck:
_dialog: _dialog:
charactersExceeded: "¡Has excedido el límite de caracteres! Actualmente {current} de {max}." charactersExceeded: "¡Has excedido el límite de caracteres! Actualmente {current} de {max}."
charactersBelow: "¡Estás por debajo del límite de caracteres! Actualmente {current} de {min}." charactersBelow: "¡Estás por debajo del límite de caracteres! Actualmente {current} de {min}."
_disabledTimeline:
title: "Línea de tiempo deshabilitada"
description: "No puedes usar esta línea de tiempo con tus roles actuales."
_drivecleaner:
orderBySizeDesc: "Más grandes"
orderByCreatedAtAsc: "Más antiguos"
_webhookSettings:
createWebhook: "Crear Webhook"
name: "Nombre"
secret: "Secreto"
events: "Eventos de webhook"
active: "Activado"
_events:
follow: "Cuando se sigue a alguien"
followed: "Cuando se es seguido"
note: "Cuando se publica una nota"
reply: "Cuando se recibe una respuesta"
renote: "Cuando reciba un \"re-note\""
reaction: "Cuando se recibe una reacción"
mention: "Cuando hay una mención"

View File

@ -575,7 +575,6 @@ tokenRequested: "Autoriser l'accès au compte"
pluginTokenRequestedDescription: "Ce plugin pourra utiliser les autorisations définies ici." pluginTokenRequestedDescription: "Ce plugin pourra utiliser les autorisations définies ici."
notificationType: "Type de notifications" notificationType: "Type de notifications"
edit: "Editer" edit: "Editer"
useStarForReactionFallback: "Utiliser ★ comme alternative si lémoji de réaction est inconnu"
emailServer: "Serveur mail" emailServer: "Serveur mail"
enableEmail: "Activer la distribution de courriel" enableEmail: "Activer la distribution de courriel"
emailConfigInfo: "Utilisé pour confirmer votre adresse de courriel et la réinitialisation de votre mot de passe en cas doubli." emailConfigInfo: "Utilisé pour confirmer votre adresse de courriel et la réinitialisation de votre mot de passe en cas doubli."
@ -1468,3 +1467,6 @@ _deck:
channel: "Canaux" channel: "Canaux"
mentions: "Mentions" mentions: "Mentions"
direct: "Direct" direct: "Direct"
_webhookSettings:
name: "Nom"
active: "Activé"

View File

@ -579,7 +579,6 @@ tokenRequested: "Berikan ijin akses ke akun"
pluginTokenRequestedDescription: "Plugin ini dapat menggunakan setelan ijin disini." pluginTokenRequestedDescription: "Plugin ini dapat menggunakan setelan ijin disini."
notificationType: "Jenis pemberitahuan" notificationType: "Jenis pemberitahuan"
edit: "Sunting" edit: "Sunting"
useStarForReactionFallback: "Gunakan ★ sebagai fallback jika reaksi emoji tidak diketahui"
emailServer: "Peladen surel" emailServer: "Peladen surel"
enableEmail: "Nyalakan distribusi surel" enableEmail: "Nyalakan distribusi surel"
emailConfigInfo: "Digunakan untuk mengonfirmasi surel kamu disaat mendaftar dan lupa kata sandi" emailConfigInfo: "Digunakan untuk mengonfirmasi surel kamu disaat mendaftar dan lupa kata sandi"
@ -1804,3 +1803,6 @@ _deck:
channel: "Kanal" channel: "Kanal"
mentions: "Sebutan" mentions: "Sebutan"
direct: "Langsung" direct: "Langsung"
_webhookSettings:
name: "Nama"
active: "Aktif"

View File

@ -54,8 +54,8 @@ copyUsername: "Copia nome utente"
searchUser: "Cerca utente" searchUser: "Cerca utente"
reply: "Rispondi" reply: "Rispondi"
loadMore: "Mostra di più" loadMore: "Mostra di più"
showMore: "Mostra di più" showMore: "Espandi"
showLess: "Chiudi" showLess: "Comprimi"
youGotNewFollower: "Ha iniziato a seguirti" youGotNewFollower: "Ha iniziato a seguirti"
receiveFollowRequest: "Hai ricevuto una richiesta di follow" receiveFollowRequest: "Hai ricevuto una richiesta di follow"
followRequestAccepted: "Richiesta di follow accettata" followRequestAccepted: "Richiesta di follow accettata"
@ -76,7 +76,7 @@ noLists: "Nessuna lista"
note: "Nota" note: "Nota"
notes: "Note" notes: "Note"
following: "Follow" following: "Follow"
followers: "Followers" followers: "Follower"
followsYou: "Ti segue" followsYou: "Ti segue"
createList: "Aggiungi una nuova lista" createList: "Aggiungi una nuova lista"
manageLists: "Gestisci liste" manageLists: "Gestisci liste"
@ -122,6 +122,8 @@ unmarkAsSensitive: "Segna come non sensibile"
enterFileName: "Nome del file" enterFileName: "Nome del file"
mute: "Silenzia" mute: "Silenzia"
unmute: "Riattiva l'audio" unmute: "Riattiva l'audio"
renoteMute: "Silenzia i Rinota"
renoteUnmute: "Non silenziare i Rinota"
block: "Blocca" block: "Blocca"
unblock: "Sblocca" unblock: "Sblocca"
suspend: "Sospendi" suspend: "Sospendi"
@ -153,6 +155,7 @@ flagShowTimelineReplies: "Mostra le risposte alle note sulla timeline."
flagShowTimelineRepliesDescription: "Se è attiva, la timeline mostra le risposte alle altre note dell'utente oltre a quelle dell'utente stesso." flagShowTimelineRepliesDescription: "Se è attiva, la timeline mostra le risposte alle altre note dell'utente oltre a quelle dell'utente stesso."
autoAcceptFollowed: "Accetta automaticamente le richieste di follow da utenti che già segui" autoAcceptFollowed: "Accetta automaticamente le richieste di follow da utenti che già segui"
addAccount: "Aggiungi profilo" addAccount: "Aggiungi profilo"
reloadAccountsList: "Ricarica l'elenco dei profili"
loginFailed: "Accesso non riuscito" loginFailed: "Accesso non riuscito"
showOnRemote: "Leggi sull'istanza remota" showOnRemote: "Leggi sull'istanza remota"
general: "Generali" general: "Generali"
@ -167,7 +170,7 @@ proxyAccountDescription: "Un profilo proxy funziona come follower per i profili
host: "Server remoto" host: "Server remoto"
selectUser: "Seleziona profilo" selectUser: "Seleziona profilo"
recipient: "Destinatario" recipient: "Destinatario"
annotation: "Descrizione" annotation: "Annotazione"
federation: "Federazione" federation: "Federazione"
instances: "Istanza" instances: "Istanza"
registeredAt: "Registrato presso" registeredAt: "Registrato presso"
@ -209,7 +212,7 @@ intro: "L'installazione di Misskey è terminata! Si prega di creare il profilo a
done: "Fine" done: "Fine"
processing: "In elaborazione" processing: "In elaborazione"
preview: "Anteprima" preview: "Anteprima"
default: "Medio" default: "Predefinito"
defaultValueIs: "Predefinito: {value}" defaultValueIs: "Predefinito: {value}"
noCustomEmojis: "Nessun emoji" noCustomEmojis: "Nessun emoji"
noJobs: "Nessun lavoro" noJobs: "Nessun lavoro"
@ -234,14 +237,14 @@ more: "Di più!"
featured: "Tendenze" featured: "Tendenze"
usernameOrUserId: "Nome utente o ID utente" usernameOrUserId: "Nome utente o ID utente"
noSuchUser: "Nessun utente trovato" noSuchUser: "Nessun utente trovato"
lookup: "Cerca" lookup: "Ricerca remota"
announcements: "Annunci" announcements: "Annunci"
imageUrl: "URL dell'immagine" imageUrl: "URL dell'immagine"
remove: "Elimina" remove: "Elimina"
removed: "Eliminato con successo" removed: "Eliminato con successo"
removeAreYouSure: "Vuoi davvero eliminare \"{x}\"?" removeAreYouSure: "Vuoi davvero eliminare \"{x}\"?"
deleteAreYouSure: "Eliminare \"{x}\"?" deleteAreYouSure: "Eliminare \"{x}\"?"
resetAreYouSure: "Reimposta" resetAreYouSure: "Ripristinare?"
saved: "Salvato" saved: "Salvato"
messaging: "Messaggi" messaging: "Messaggi"
upload: "Carica" upload: "Carica"
@ -406,7 +409,7 @@ lastUsedAt: "Uso più recente: {t}"
unregister: "Annulla l'iscrizione" unregister: "Annulla l'iscrizione"
passwordLessLogin: "Accedi senza password" passwordLessLogin: "Accedi senza password"
passwordLessLoginDescription: "Accedi senza password, usando la chiave di sicurezza" passwordLessLoginDescription: "Accedi senza password, usando la chiave di sicurezza"
resetPassword: "Reimposta password" resetPassword: "Ripristina la password"
newPasswordIs: "La tua nuova password è「{password}」" newPasswordIs: "La tua nuova password è「{password}」"
reduceUiAnimation: "Ridurre le animazioni dell'interfaccia" reduceUiAnimation: "Ridurre le animazioni dell'interfaccia"
share: "Condividi" share: "Condividi"
@ -506,6 +509,7 @@ objectStorageSetPublicRead: "Imposta \"visibilità pubblica\" al momento di cari
serverLogs: "Log del server" serverLogs: "Log del server"
deleteAll: "Cancella cronologia" deleteAll: "Cancella cronologia"
showFixedPostForm: "Visualizzare la finestra di pubblicazione in cima alla timeline" 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" newNoteRecived: "Vedi le nuove note"
sounds: "Impostazioni suoni" sounds: "Impostazioni suoni"
sound: "Impostazioni suoni" sound: "Impostazioni suoni"
@ -543,6 +547,10 @@ userSuspended: "L'utente è in sospensione"
userSilenced: "L'utente è silenziat@." userSilenced: "L'utente è silenziat@."
yourAccountSuspendedTitle: "Questo profilo è sospeso" 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." 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"
tokenRevokedDescription: "Il token di accesso è scaduto. Per favore, accedi nuovamente."
accountDeleted: "Profilo eliminato"
accountDeletedDescription: "Questo profilo è stato eliminato."
menu: "Menù" menu: "Menù"
divider: "Linea di separazione" divider: "Linea di separazione"
addItem: "Aggiungi elemento" addItem: "Aggiungi elemento"
@ -557,8 +565,8 @@ enableInfiniteScroll: "Abilita scorrimento infinito"
visibility: "Visibilità" visibility: "Visibilità"
poll: "Sondaggio" poll: "Sondaggio"
useCw: "Nascondere media" useCw: "Nascondere media"
enablePlayer: "Apri in lettore video" enablePlayer: "Visualizza"
disablePlayer: "Chiudi lettore video" disablePlayer: "Chiudi"
expandTweet: "Espandi tweet" expandTweet: "Espandi tweet"
themeEditor: "Editor di temi" themeEditor: "Editor di temi"
description: "Descrizione" description: "Descrizione"
@ -586,10 +594,9 @@ tokenRequested: "Autorizza accesso al profilo"
pluginTokenRequestedDescription: "Il plugin potrà utilizzare le autorizzazioni impostate qui." pluginTokenRequestedDescription: "Il plugin potrà utilizzare le autorizzazioni impostate qui."
notificationType: "Tipo di notifiche" notificationType: "Tipo di notifiche"
edit: "Modifica" edit: "Modifica"
useStarForReactionFallback: "Se è sconosciuto l'emoji di reazione, usare la ★ come alternativa."
emailServer: "Server email" emailServer: "Server email"
enableEmail: "Abilita consegna email" enableEmail: "Abilita consegna email"
emailConfigInfo: "Utilizzato per verificare il tuo indirizzo di posta elettronica e per reimpostare la tua password" emailConfigInfo: "Utilizzato per verificare il tuo indirizzo di posta elettronica e per ripristinare la password"
email: "Email" email: "Email"
emailAddress: "Indirizzo di posta elettronica" emailAddress: "Indirizzo di posta elettronica"
smtpConfig: "Impostazioni del server SMTP" smtpConfig: "Impostazioni del server SMTP"
@ -953,8 +960,30 @@ copyErrorInfo: "Copia le informazioni sull'errore"
joinThisServer: "Registrati su questa istanza" joinThisServer: "Registrati su questa istanza"
exploreOtherServers: "Trova altre istanze" exploreOtherServers: "Trova altre istanze"
letsLookAtTimeline: "Sbircia la timeline" letsLookAtTimeline: "Sbircia la timeline"
disableFederationWarn: "Disabilita la federazione. Questo cambiamento non rende le pubblicazioni private. Di solito non è necessario abilitare questa opzione." invitationRequiredToRegister: "L'accesso a questa istanza è solo ad invito. Può registrarsi solo chi ha un codice fornito dall'amministrazione."
invitationRequiredToRegister: "L'accesso a questo nodo è solo ad invito. Devi inserire un codice d'invito valido. Puoi richiedere un codice all'amministratore." emailNotSupported: "L'istanza non supporta l'invio di email"
postToTheChannel: "Pubblica nel canale"
cannotBeChangedLater: "Non sarà più modificabile"
reactionAcceptance: "Accettazione reazioni"
likeOnly: "Solo i Like"
likeOnlyForRemote: "Solo Like remoti"
rolesAssignedToMe: "I miei ruoli"
resetPasswordConfirm: "Vuoi davvero ripristinare la password?"
sensitiveWords: "Parole sensibili"
sensitiveWordsDescription: "Imposta automaticamente \"Home\" alla visibilità delle Note che contengono una qualsiasi parola tra queste configurate. Puoi separarle per riga."
notesSearchNotAvailable: "Non è possibile cercare tra le Note."
license: "Licenza"
unfavoriteConfirm: "Vuoi davvero rimuovere la preferenza?"
myClips: "Le mie Clip"
drivecleaner: "Drive cleaner"
retryAllQueuesNow: "Ritenta di consumare tutte le code"
retryAllQueuesConfirmTitle: "Vuoi ritentare adesso?"
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"
noteIdOrUrl: "ID della Nota o URL"
_achievements: _achievements:
earnedAt: "Data di conseguimento" earnedAt: "Data di conseguimento"
_types: _types:
@ -1214,6 +1243,8 @@ _role:
iconUrl: "URL dell'icona" iconUrl: "URL dell'icona"
asBadge: "Mostra come badge" asBadge: "Mostra come badge"
descriptionOfAsBadge: "Se indicato, accanto al nome utente viene visualizzata l'icona del ruolo." descriptionOfAsBadge: "Se indicato, accanto al nome utente viene visualizzata l'icona del ruolo."
displayOrder: "Ordine di visualizzazione"
descriptionOfDisplayOrder: "I valori più alti vengono visualizzati per primi"
canEditMembersByModerator: "Anche i Moderatori assegnano profili a questo ruolo" canEditMembersByModerator: "Anche i Moderatori assegnano profili a questo ruolo"
descriptionOfCanEditMembersByModerator: "Se disattivo, potranno farlo solamente gli Amministratori." descriptionOfCanEditMembersByModerator: "Se disattivo, potranno farlo solamente gli Amministratori."
priority: "Priorità" priority: "Priorità"
@ -1239,6 +1270,7 @@ _role:
rateLimitFactor: "Limite del rapporto" rateLimitFactor: "Limite del rapporto"
descriptionOfRateLimitFactor: "I rapporti più bassi sono meno restrittivi, quelli più alti lo sono di più." descriptionOfRateLimitFactor: "I rapporti più bassi sono meno restrittivi, quelli più alti lo sono di più."
canHideAds: "Può nascondere i banner" canHideAds: "Può nascondere i banner"
canSearchNotes: "Ricercare nelle Note"
_condition: _condition:
isLocal: "Profilo locale" isLocal: "Profilo locale"
isRemote: "Profilo remoto" isRemote: "Profilo remoto"
@ -1248,6 +1280,8 @@ _role:
followersMoreThanOrEq: "Ha più di N follower" followersMoreThanOrEq: "Ha più di N follower"
followingLessThanOrEq: "Segue N profili o meno" followingLessThanOrEq: "Segue N profili o meno"
followingMoreThanOrEq: "Segue N profili o più" followingMoreThanOrEq: "Segue N profili o più"
notesLessThanOrEq: "Conteggio Note inferiore o uguale a"
notesMoreThanOrEq: "Conteggio Note maggiore o uguale a"
and: "E" and: "E"
or: "O" or: "O"
not: "NON" not: "NON"
@ -1286,8 +1320,8 @@ _ad:
hide: "Nascondi" hide: "Nascondi"
_forgotPassword: _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." 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 nessun indirizzo e-mail è stato registrato, si prega di contattare l'amministratore·trice dell'istanza." ifNoEmail: "Se il tuo indirizzo email non risulta registrato, contatta l'amministrazione dell'istanza."
contactAdmin: "Poiché questa istanza non permette l'utilizzo di una mail, si prega di contattare l'amministratore·trice dell'istanza per poter ripristinare la password." contactAdmin: "Poiché questa istanza non permette di impostare l'indirizzo mail, contatta l'amministrazione per ripristinare la password.\n"
_gallery: _gallery:
my: "Le mie pubblicazioni" my: "Le mie pubblicazioni"
liked: "Pubblicazioni che mi piacciono" liked: "Pubblicazioni che mi piacciono"
@ -1613,7 +1647,7 @@ _widgets:
clicker: "Cliccaggio" clicker: "Cliccaggio"
_cw: _cw:
hide: "Nascondere" hide: "Nascondere"
show: "Mostra di più" show: "Apri..."
chars: "{count} caratteri" chars: "{count} caratteri"
files: "{count} file" files: "{count} file"
_poll: _poll:
@ -1840,3 +1874,23 @@ _deck:
_dialog: _dialog:
charactersExceeded: "Hai superato il limite di {max} caratteri! ({corrente})" charactersExceeded: "Hai superato il limite di {max} caratteri! ({corrente})"
charactersBelow: "Sei al di sotto del minimo di {min} caratteri! ({corrente})" charactersBelow: "Sei al di sotto del minimo di {min} caratteri! ({corrente})"
_disabledTimeline:
title: "Timeline disabilitata"
description: "Il ruolo in cui sei non ti permette di leggere questa timeline"
_drivecleaner:
orderBySizeDesc: "Dal più grande al più piccolo"
orderByCreatedAtAsc: "Dal più vecchio al più recente"
_webhookSettings:
createWebhook: "Creazione Webhook"
name: "Nome"
secret: "Segreto"
events: "Quando eseguire il Webhook"
active: "Attivo"
_events:
follow: "Quando segui un profilo"
followed: "Quando ti segue un profilo"
note: "Quando pubblichi una Nota"
reply: "Quando rispondono ad una Nota"
renote: "Quando la Nota è Rinotata"
reaction: "Quando ricevo una reazione"
mention: "Quando mi menzionano"

View File

@ -67,7 +67,7 @@ import: "インポート"
export: "エクスポート" export: "エクスポート"
files: "ファイル" files: "ファイル"
download: "ダウンロード" download: "ダウンロード"
driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?このファイルを添付したノートも消えます。" driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?このファイルを使用した全てのコンテンツからも削除されます。"
unfollowConfirm: "{name}のフォローを解除しますか?" unfollowConfirm: "{name}のフォローを解除しますか?"
exportRequested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。" exportRequested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。"
importRequested: "インポートをリクエストしました。これには時間がかかる場合があります。" importRequested: "インポートをリクエストしました。これには時間がかかる場合があります。"
@ -122,6 +122,8 @@ unmarkAsSensitive: "閲覧注意を解除する"
enterFileName: "ファイル名を入力" enterFileName: "ファイル名を入力"
mute: "ミュート" mute: "ミュート"
unmute: "ミュート解除" unmute: "ミュート解除"
renoteMute: "リノートをミュート"
renoteUnmute: "リノートのミュートを解除"
block: "ブロック" block: "ブロック"
unblock: "ブロック解除" unblock: "ブロック解除"
suspend: "凍結" suspend: "凍結"
@ -153,6 +155,7 @@ flagShowTimelineReplies: "タイムラインにノートへの返信を表示す
flagShowTimelineRepliesDescription: "オンにすると、タイムラインにユーザーのノート以外にもそのユーザーの他のノートへの返信を表示します。" flagShowTimelineRepliesDescription: "オンにすると、タイムラインにユーザーのノート以外にもそのユーザーの他のノートへの返信を表示します。"
autoAcceptFollowed: "フォロー中ユーザーからのフォロリクを自動承認" autoAcceptFollowed: "フォロー中ユーザーからのフォロリクを自動承認"
addAccount: "アカウントを追加" addAccount: "アカウントを追加"
reloadAccountsList: "アカウントリストの情報を更新"
loginFailed: "ログインに失敗しました" loginFailed: "ログインに失敗しました"
showOnRemote: "リモートで表示" showOnRemote: "リモートで表示"
general: "全般" general: "全般"
@ -457,7 +460,7 @@ aboutX: "{x}について"
emojiStyle: "絵文字のスタイル" emojiStyle: "絵文字のスタイル"
native: "ネイティブ" native: "ネイティブ"
disableDrawer: "メニューをドロワーで表示しない" disableDrawer: "メニューをドロワーで表示しない"
showNoteActionsOnlyHover: "ノートの操作部をホバー時のみ表示する" showNoteActionsOnlyHover: "ノートのアクションをホバー時のみ表示する"
noHistory: "履歴はありません" noHistory: "履歴はありません"
signinHistory: "ログイン履歴" signinHistory: "ログイン履歴"
enableAdvancedMfm: "高度なMFMを有効にする" enableAdvancedMfm: "高度なMFMを有効にする"
@ -497,15 +500,17 @@ objectStoragePrefixDesc: "このprefixのディレクトリ下に格納されま
objectStorageEndpoint: "Endpoint" objectStorageEndpoint: "Endpoint"
objectStorageEndpointDesc: "S3の場合は空、それ以外の場合は各サービスのendpointを指定してください。'<host>'または'<host>:<port>'のように指定します。" objectStorageEndpointDesc: "S3の場合は空、それ以外の場合は各サービスのendpointを指定してください。'<host>'または'<host>:<port>'のように指定します。"
objectStorageRegion: "Region" objectStorageRegion: "Region"
objectStorageRegionDesc: "'xx-east-1'のようなregionを指定してください。使用サービスにregionの概念がない場合は、空または'us-east-1'にしてください。" objectStorageRegionDesc: "'xx-east-1'のようなregionを指定してください。使用サービスにregionの概念がない場合は'us-east-1'にしてください。AWS設定ファイルまたは環境変数を参照する場合は空にしてください。"
objectStorageUseSSL: "SSLを使用する" objectStorageUseSSL: "SSLを使用する"
objectStorageUseSSLDesc: "API接続にhttpsを使用しない場合はオフにしてください" objectStorageUseSSLDesc: "API接続にhttpsを使用しない場合はオフにしてください"
objectStorageUseProxy: "Proxyを利用する" objectStorageUseProxy: "Proxyを利用する"
objectStorageUseProxyDesc: "API接続にproxyを利用しない場合はオフにしてください" objectStorageUseProxyDesc: "API接続にproxyを利用しない場合はオフにしてください"
objectStorageSetPublicRead: "アップロード時に'public-read'を設定する" objectStorageSetPublicRead: "アップロード時に'public-read'を設定する"
s3ForcePathStyleDesc: "s3ForcePathStyleを有効にすると、バケット名をURLのホスト名ではなくパスの一部として指定することを強制します。セルフホストされたMinioなどの使用時に有効にする必要がある場合があります。"
serverLogs: "サーバーログ" serverLogs: "サーバーログ"
deleteAll: "全て削除" deleteAll: "全て削除"
showFixedPostForm: "タイムライン上部に投稿フォームを表示する" showFixedPostForm: "タイムライン上部に投稿フォームを表示する"
showFixedPostFormInChannel: "タイムライン上部に投稿フォームを表示する(チャンネル)"
newNoteRecived: "新しいノートがあります" newNoteRecived: "新しいノートがあります"
sounds: "サウンド" sounds: "サウンド"
sound: "サウンド" sound: "サウンド"
@ -543,6 +548,10 @@ userSuspended: "このユーザーは凍結されています。"
userSilenced: "このユーザーはサイレンスされています。" userSilenced: "このユーザーはサイレンスされています。"
yourAccountSuspendedTitle: "アカウントが凍結されています" yourAccountSuspendedTitle: "アカウントが凍結されています"
yourAccountSuspendedDescription: "このアカウントは、サーバーの利用規約に違反したなどの理由により、凍結されています。詳細については管理者までお問い合わせください。新しいアカウントを作らないでください。" yourAccountSuspendedDescription: "このアカウントは、サーバーの利用規約に違反したなどの理由により、凍結されています。詳細については管理者までお問い合わせください。新しいアカウントを作らないでください。"
tokenRevoked: "トークンが無効です"
tokenRevokedDescription: "ログイントークンが失効しています。ログインし直してください。"
accountDeleted: "アカウントは削除されています"
accountDeletedDescription: "このアカウントは削除されています。"
menu: "メニュー" menu: "メニュー"
divider: "分割線" divider: "分割線"
addItem: "項目を追加" addItem: "項目を追加"
@ -586,7 +595,6 @@ tokenRequested: "アカウントへのアクセス許可"
pluginTokenRequestedDescription: "このプラグインはここで設定した権限を行使できるようになります。" pluginTokenRequestedDescription: "このプラグインはここで設定した権限を行使できるようになります。"
notificationType: "通知の種類" notificationType: "通知の種類"
edit: "編集" edit: "編集"
useStarForReactionFallback: "リアクション絵文字が不明な場合、代わりに★を使う"
emailServer: "メールサーバー" emailServer: "メールサーバー"
enableEmail: "メール配信機能を有効化する" enableEmail: "メール配信機能を有効化する"
emailConfigInfo: "メールアドレスの確認やパスワードリセットの際に使います" emailConfigInfo: "メールアドレスの確認やパスワードリセットの際に使います"
@ -912,6 +920,7 @@ pushNotificationNotSupported: "ブラウザかサーバーがプッシュ通知
sendPushNotificationReadMessage: "通知やメッセージが既読になったらプッシュ通知を削除する" sendPushNotificationReadMessage: "通知やメッセージが既読になったらプッシュ通知を削除する"
sendPushNotificationReadMessageCaption: "「{emptyPushNotificationMessage}」という通知が一瞬表示されるようになります。端末の電池消費量が増加する可能性があります。" sendPushNotificationReadMessageCaption: "「{emptyPushNotificationMessage}」という通知が一瞬表示されるようになります。端末の電池消費量が増加する可能性があります。"
windowMaximize: "最大化" windowMaximize: "最大化"
windowMinimize: "最小化"
windowRestore: "元に戻す" windowRestore: "元に戻す"
caption: "キャプション" caption: "キャプション"
loggedInAsBot: "Botアカウントでログイン中" loggedInAsBot: "Botアカウントでログイン中"
@ -953,8 +962,44 @@ copyErrorInfo: "エラー情報をコピー"
joinThisServer: "このサーバーに登録する" joinThisServer: "このサーバーに登録する"
exploreOtherServers: "他のサーバーを探す" exploreOtherServers: "他のサーバーを探す"
letsLookAtTimeline: "タイムラインを見てみる" letsLookAtTimeline: "タイムラインを見てみる"
disableFederationWarn: "連合が無効になっています。無効にしても投稿が非公開にはなりません。ほとんどの場合、このオプションを有効にする必要はありません。" disableFederationConfirm: "連合なしにしますか?"
disableFederationConfirmWarn: "連合なしにしても投稿は非公開になりません。ほとんどの場合、連合なしにする必要はありません。"
disableFederationOk: "連合なしにする"
invitationRequiredToRegister: "現在このサーバーは招待制です。招待コードをお持ちの方のみ登録できます。" invitationRequiredToRegister: "現在このサーバーは招待制です。招待コードをお持ちの方のみ登録できます。"
emailNotSupported: "このサーバーではメール配信はサポートされていません"
postToTheChannel: "チャンネルに投稿"
cannotBeChangedLater: "後から変更できません。"
reactionAcceptance: "リアクションの受け入れ"
likeOnly: "いいねのみ"
likeOnlyForRemote: "リモートからはいいねのみ"
rolesAssignedToMe: "自分に割り当てられたロール"
resetPasswordConfirm: "パスワードリセットしますか?"
sensitiveWords: "センシティブワード"
sensitiveWordsDescription: "設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。"
notesSearchNotAvailable: "ノート検索は利用できません。"
license: "ライセンス"
unfavoriteConfirm: "お気に入り解除しますか?"
myClips: "自分のクリップ"
drivecleaner: "ドライブクリーナー"
retryAllQueuesNow: "すべてのキューを今すぐ再試行"
retryAllQueuesConfirmTitle: "今すぐ再試行しますか?"
retryAllQueuesConfirmText: "一時的にサーバーの負荷が増大することがあります。"
enableChartsForRemoteUser: "リモートユーザーのチャートを生成"
enableChartsForFederatedInstances: "リモートサーバーのチャートを生成"
showClipButtonInNoteFooter: "ノートのアクションにクリップを追加"
largeNoteReactions: "ノートのリアクションを大きく表示"
noteIdOrUrl: "ートIDまたはURL"
accountMigration: "アカウントの引っ越し"
accountMoved: "このユーザーは新しいアカウントに引っ越しました:"
_accountMigration:
moveTo: "このアカウントを新しいアカウントに引っ越す"
moveToLabel: "引っ越し先のアカウント:"
moveAccountDescription: "この操作は取り消せません。まずは引っ越し先のアカウントでこのアカウントに対しエイリアスを作成したことを確認してください。エイリアス作成後、引っ越し先のアカウントをこのように入力してください:@person@instance.com"
moveFrom: "別のアカウントからこのアカウントに引っ越す"
moveFromLabel: "引っ越し元のアカウント:"
moveFromDescription: "別のアカウントからこのアカウントにフォロワーを引き継いで引っ越したい場合、ここでエイリアスを作成しておく必要があります。必ず引っ越しを実行する前に作成してください!引っ越し元のアカウントをこのように入力してください:@person@instance.com"
migrationConfirm: "本当にこのアカウントを {account} に引っ越しますか?一度引っ越しを行うと取り消せず、二度とこのアカウントを元の状態で使用できなくなります。\nまた、引っ越し先のアカウントでエイリアスを作成したことを確認してください。"
_achievements: _achievements:
earnedAt: "獲得日時" earnedAt: "獲得日時"
@ -1216,6 +1261,8 @@ _role:
iconUrl: "アイコン画像のURL" iconUrl: "アイコン画像のURL"
asBadge: "バッジとして表示" asBadge: "バッジとして表示"
descriptionOfAsBadge: "オンにすると、ユーザー名の横にロールのアイコンが表示されます。" descriptionOfAsBadge: "オンにすると、ユーザー名の横にロールのアイコンが表示されます。"
displayOrder: "表示順"
descriptionOfDisplayOrder: "数値が大きいほどUI上で先頭に表示されます。"
canEditMembersByModerator: "モデレーターのメンバー編集を許可" canEditMembersByModerator: "モデレーターのメンバー編集を許可"
descriptionOfCanEditMembersByModerator: "オンにすると、管理者に加えてモデレーターもこのロールへユーザーをアサイン/アサイン解除できるようになります。オフにすると管理者のみが行えます。" descriptionOfCanEditMembersByModerator: "オンにすると、管理者に加えてモデレーターもこのロールへユーザーをアサイン/アサイン解除できるようになります。オフにすると管理者のみが行えます。"
priority: "優先度" priority: "優先度"
@ -1241,6 +1288,7 @@ _role:
rateLimitFactor: "レートリミット" rateLimitFactor: "レートリミット"
descriptionOfRateLimitFactor: "小さいほど制限が緩和され、大きいほど制限が強化されます。" descriptionOfRateLimitFactor: "小さいほど制限が緩和され、大きいほど制限が強化されます。"
canHideAds: "広告の非表示" canHideAds: "広告の非表示"
canSearchNotes: "ノート検索の利用可否"
_condition: _condition:
isLocal: "ローカルユーザー" isLocal: "ローカルユーザー"
isRemote: "リモートユーザー" isRemote: "リモートユーザー"
@ -1250,6 +1298,8 @@ _role:
followersMoreThanOrEq: "フォロワー数が~以上" followersMoreThanOrEq: "フォロワー数が~以上"
followingLessThanOrEq: "フォロー数が~以下" followingLessThanOrEq: "フォロー数が~以下"
followingMoreThanOrEq: "フォロー数が~以上" followingMoreThanOrEq: "フォロー数が~以上"
notesLessThanOrEq: "投稿数が~以下"
notesMoreThanOrEq: "投稿数が~以上"
and: "~かつ~" and: "~かつ~"
or: "~または~" or: "~または~"
not: "~ではない" not: "~ではない"
@ -1893,3 +1943,26 @@ _deck:
_dialog: _dialog:
charactersExceeded: "最大文字数を超えています! 現在 {current} / 制限 {max}" charactersExceeded: "最大文字数を超えています! 現在 {current} / 制限 {max}"
charactersBelow: "最小文字数を下回っています! 現在 {current} / 制限 {min}" charactersBelow: "最小文字数を下回っています! 現在 {current} / 制限 {min}"
_disabledTimeline:
title: "無効化されたタイムライン"
description: "現在のロールでは、このタイムラインを使用することはできません。"
_drivecleaner:
orderBySizeDesc: "サイズが大きい順"
orderByCreatedAtAsc: "追加日が古い順"
_webhookSettings:
createWebhook: "Webhookを作成"
name: "名前"
secret: "シークレット"
events: "Webhookを実行するタイミング"
active: "有効"
_events:
follow: "フォローしたとき"
followed: "フォローされたとき"
note: "ノートを投稿したとき"
reply: "返信されたとき"
renote: "Renoteされたとき"
reaction: "リアクションがあったとき"
mention: "メンションされたとき"

View File

@ -2,7 +2,7 @@
_lang_: "日本語 (関西弁)" _lang_: "日本語 (関西弁)"
headlineMisskey: "ノートでつながるネットワーク" headlineMisskey: "ノートでつながるネットワーク"
introMisskey: "ようお越しMisskeyは、オープンソースの分散型マイクロブログサービスやねん。\n「ート」を作って、いま起こっとることを共有したり、あんたについて皆に発信しよう📡\n「リアクション」機能で、皆のートに素早く反応を追加したりもできるで✌\nほな新しい世界を探検しよか🚀" introMisskey: "ようお越しMisskeyは、オープンソースの分散型マイクロブログサービスやねん。\n「ート」を作って、いま起こっとることを共有したり、あんたについて皆に発信しよう📡\n「リアクション」機能で、皆のートに素早く反応を追加したりもできるで✌\nほな新しい世界を探検しよか🚀"
poweredByMisskeyDescription: "{name}は、オープンソースのプラットフォーム<b>Misskey</b>を使ったサービス(Misskeyインスタンスと呼ばれるやつや)のひとつやで。" poweredByMisskeyDescription: "{name}は、オープンソースのプラットフォーム<b>Misskey</b>のサーバーのひとつなんやで。"
monthAndDay: "{month}月 {day}日" monthAndDay: "{month}月 {day}日"
search: "探す" search: "探す"
notifications: "通知" notifications: "通知"
@ -15,23 +15,23 @@ gotIt: "ほい"
cancel: "やめとく" cancel: "やめとく"
noThankYou: "やめとく" noThankYou: "やめとく"
enterUsername: "ユーザー名を入れてや" enterUsername: "ユーザー名を入れてや"
renotedBy: "{user}がRenote" renotedBy: "{user}がRenoteしたで"
noNotes: "ノートはあらへん" noNotes: "ノートはあらへん"
noNotifications: "通知はあらへん" noNotifications: "通知はあらへん"
instance: "インスタンス" instance: "サーバー"
settings: "設定" settings: "設定"
basicSettings: "基本設定" basicSettings: "基本設定"
otherSettings: "その他の設定" otherSettings: "ほかの設定"
openInWindow: "ウィンドウで開くで" openInWindow: "ウィンドウで開くで"
profile: "プロフィール" profile: "プロフィール"
timeline: "タイムライン" timeline: "タイムライン"
noAccountDescription: "自己紹介食ってもた" noAccountDescription: "自己紹介はあらへん"
login: "ログイン" login: "ログイン"
loggingIn: "ログインしよるで" loggingIn: "ログインしよるで"
logout: "ログアウト" logout: "ログアウト"
signup: "新規登録" signup: "新規登録"
uploading: "アップロードしとるで" uploading: "アップロードしとるで"
save: "保存" save: "とっとく"
users: "ユーザー" users: "ユーザー"
addUser: "ユーザーを追加や" addUser: "ユーザーを追加や"
favorite: "お気に入り" favorite: "お気に入り"
@ -55,7 +55,7 @@ searchUser: "ユーザーを検索"
reply: "返事" reply: "返事"
loadMore: "まだまだあるで!" loadMore: "まだまだあるで!"
showMore: "まだまだあるで!" showMore: "まだまだあるで!"
showLess: "閉じる" showLess: "さいなら"
youGotNewFollower: "フォローされたで" youGotNewFollower: "フォローされたで"
receiveFollowRequest: "フォローリクエストされたで" receiveFollowRequest: "フォローリクエストされたで"
followRequestAccepted: "フォローが承認されたで" followRequestAccepted: "フォローが承認されたで"
@ -81,15 +81,15 @@ followsYou: "フォローされとるで"
createList: "リスト作る" createList: "リスト作る"
manageLists: "リストの管理" manageLists: "リストの管理"
error: "エラー" error: "エラー"
somethingHappened: "なんかアカンことが起こったで" somethingHappened: "なんかあかんわ"
retry: "もっぺんやる?" retry: "もっぺんやる?"
pageLoadError: "ページの読み込みに失敗してもうたわ…" pageLoadError: "ページが読み込めんかったわ。"
pageLoadErrorDescription: "これは普通、ネットワークかブラウザキャッシュが原因やからね。キャッシュをクリアするか、もうちっとだけ待ってくれへんか" pageLoadErrorDescription: "これは普通ならネットワークかブラウザキャッシュが悪さしてるんよ。キャッシュをほかすか、もうちょっとだけ待ってくれへん"
serverIsDead: "サーバーからの応答がないで。もうちょい待ってから試してみてな。" serverIsDead: "サーバーからの応答がないで。もうちょい待ってから試してみてな。"
youShouldUpgradeClient: "このページを表示するには、リロードして新しいバージョンのクライアントを使ってなー。" youShouldUpgradeClient: "このページを表示するには、リロードして新しいバージョンのクライアントを使ってなー。"
enterListName: "リスト名を入れてや" enterListName: "リスト名を入れてや"
privacy: "プライバシー" privacy: "プライバシー"
makeFollowManuallyApprove: "他人のフォローは許可してからや!" makeFollowManuallyApprove: "ええって言わなフォローできへんようにする"
defaultNoteVisibility: "もとからの公開範囲" defaultNoteVisibility: "もとからの公開範囲"
follow: "フォロー" follow: "フォロー"
followRequest: "フォローを頼む" followRequest: "フォローを頼む"
@ -108,12 +108,12 @@ inChannelQuote: "チャンネル内引用"
pinnedNote: "ピン留めされとるノート" pinnedNote: "ピン留めされとるノート"
pinned: "ピン留めしとく" pinned: "ピン留めしとく"
you: "あんた" you: "あんた"
clickToShow: "押したら見えるで" clickToShow: "押したら出ら"
sensitive: "ちょっとアカンやつやで" sensitive: "気いつけて見いや"
add: "増やす" add: "増やす"
reaction: "リアクション" reaction: "リアクション"
reactions: "リアクション" reactions: "リアクション"
reactionSetting: "Reaction that will be displayed in Picker. " reactionSetting: "ピッカーに出しとくリアクション"
reactionSettingDescription2: "ドラッグで並び替え、クリックで削除、+を押して追加やで。" reactionSettingDescription2: "ドラッグで並び替え、クリックで削除、+を押して追加やで。"
rememberNoteVisibility: "公開範囲覚えといて" rememberNoteVisibility: "公開範囲覚えといて"
attachCancel: "のっけるのやめる" attachCancel: "のっけるのやめる"
@ -122,6 +122,8 @@ unmarkAsSensitive: "そこまでアカンことないやろ"
enterFileName: "ファイル名を入れてや" enterFileName: "ファイル名を入れてや"
mute: "ミュート" mute: "ミュート"
unmute: "ミュートやめたる" unmute: "ミュートやめたる"
renoteMute: "Renoteは見いひん"
renoteUnmute: "Renoteもやっぱ見るわ"
block: "ブロック" block: "ブロック"
unblock: "ブロックやめたる" unblock: "ブロックやめたる"
suspend: "凍結" suspend: "凍結"
@ -139,26 +141,27 @@ editWidgetsExit: "編集終ったで"
customEmojis: "カスタム絵文字" customEmojis: "カスタム絵文字"
emoji: "絵文字" emoji: "絵文字"
emojis: "絵文字" emojis: "絵文字"
emojiName: "絵文字名" emojiName: "絵文字はんの"
emojiUrl: "絵文字画像URL" emojiUrl: "絵文字画像URL"
addEmoji: "絵文字を追加" addEmoji: "絵文字を追加"
settingGuide: "ええ感じの設定" settingGuide: "ええ感じの設定"
cacheRemoteFiles: "リモートのファイルをキャッシュする" cacheRemoteFiles: "リモートのファイルをキャッシュする"
cacheRemoteFilesDescription: "この設定を切っとくと、リモートファイルをキャッシュせず直リンクするようになるで。サーバーの容量は節約できるけど、サムネイルが作られんくなるから通信量が増えるで。" cacheRemoteFilesDescription: "この設定を切っとったら、リモートファイルをキャッシュせんと直リンクするようになるで。サーバーの容量は節約できるけど、サムネイルを作らんなるから通信量が増えるで。"
flagAsBot: "Botで" flagAsBot: "Botにするで"
flagAsBotDescription: "もしこのアカウントがプログラムによって運用されるんやったら、このフラグをオンにしてたのむで。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Misskeyのシステム上での扱いがBotに合ったもんになるんやで。" flagAsBotDescription: "もしこのアカウントをプログラム使うて運用するんやったら、このフラグをオンにしてや。オンにすれば、反応がバーッて連鎖せんように開発者が使うたり、Misskeyのシステム上での扱いがBotに合ったもんになるからな。"
flagAsCat: "Catやで" flagAsCat: "Catやで"
flagAsCatDescription: "ワレ、猫ちゃんならこのフラグをつけてみ?" flagAsCatDescription: "ワレ、猫ちゃんならこのフラグをつけてみ?"
flagShowTimelineReplies: "タイムラインにノートへの返信を表示するで" flagShowTimelineReplies: "タイムラインにノートへの返信を表示するで"
flagShowTimelineRepliesDescription: "オンにしたら、タイムラインにユーザーのノートの他にもそのユーザーの他のノートへの返信を表示するで。" flagShowTimelineRepliesDescription: "オンにしたら、タイムラインにユーザーのノートの他にもそのユーザーの他のノートへの返信を表示するで。"
autoAcceptFollowed: "フォローしとるユーザーからのフォローリクエストを勝手に許可しとく" autoAcceptFollowed: "フォローしとるユーザーからのフォローリクエストを勝手に許可しとく"
addAccount: "アカウントを追加" addAccount: "アカウントを追加"
reloadAccountsList: "アカウントリストの情報を更新"
loginFailed: "ログインに失敗してもうた…" loginFailed: "ログインに失敗してもうた…"
showOnRemote: "リモートで見る" showOnRemote: "リモートで見る"
general: "全般" general: "全般"
wallpaper: "壁紙" wallpaper: "壁紙"
setWallpaper: "壁紙を設定" setWallpaper: "壁紙を設定"
removeWallpaper: "壁紙を削除" removeWallpaper: "壁紙ほかす"
searchWith: "検索: {q}" searchWith: "検索: {q}"
youHaveNoLists: "リストがあらへんで?" youHaveNoLists: "リストがあらへんで?"
followConfirm: "{name}をフォローしてええか?" followConfirm: "{name}をフォローしてええか?"
@ -169,7 +172,7 @@ selectUser: "ユーザーを選ぶ"
recipient: "宛先" recipient: "宛先"
annotation: "注釈" annotation: "注釈"
federation: "連合" federation: "連合"
instances: "インスタンス" instances: "サーバー"
registeredAt: "初観測" registeredAt: "初観測"
latestRequestReceivedAt: "ちょっと前のリクエスト受信" latestRequestReceivedAt: "ちょっと前のリクエスト受信"
latestStatus: "ちょっと前のステータス" latestStatus: "ちょっと前のステータス"
@ -178,7 +181,7 @@ charts: "チャート"
perHour: "1時間ごと" perHour: "1時間ごと"
perDay: "1日ごと" perDay: "1日ごと"
stopActivityDelivery: "アクティビティの配送をやめる" stopActivityDelivery: "アクティビティの配送をやめる"
blockThisInstance: "このインスタンスをブロック" blockThisInstance: "このサーバーをブロックすんで"
operations: "操作" operations: "操作"
software: "ソフトウェア" software: "ソフトウェア"
version: "バージョン" version: "バージョン"
@ -189,28 +192,28 @@ jobQueue: "ジョブキュー"
cpuAndMemory: "CPUとメモリ" cpuAndMemory: "CPUとメモリ"
network: "ネットワーク" network: "ネットワーク"
disk: "ディスク" disk: "ディスク"
instanceInfo: "インスタンス情報" instanceInfo: "サーバー情報"
statistics: "統計" statistics: "統計"
clearQueue: "キューにさいなら" clearQueue: "キューをほかす"
clearQueueConfirmTitle: "キューをクリアしまっか?" clearQueueConfirmTitle: "キューをほかしとこか?"
clearQueueConfirmText: "未配達の投稿は配送されなくなるで。通常この操作を行う必要はあらへんや。" clearQueueConfirmText: "未配達の投稿は配送されんなるで。ふつうこの操作を行う必要は無いんやけどな。"
clearCachedFiles: "キャッシュにさいなら" clearCachedFiles: "キャッシュをほかす"
clearCachedFilesConfirm: "キャッシュされとるリモートファイルをみんなほかしてええか?" clearCachedFilesConfirm: "キャッシュされとるリモートファイルをみんなほかしてええか?"
blockedInstances: "インスタンスブロック" blockedInstances: "ブロックしたサーバー"
blockedInstancesDescription: "ブロックしたいインスタンスのホストを改行で区切って設定してな。ブロックされてもうたインスタンスとはもう金輪際やり取りできひんくなるで。" blockedInstancesDescription: "ブロックしたいサーバーのホストを改行で区切って設定してな。ブロックされてもうたサーバーとはもう金輪際やり取りできひんくなるで。ついでにそのサブドメインもブロックするで。"
muteAndBlock: "ミュートとブロック" muteAndBlock: "ミュートとブロック"
mutedUsers: "ミュートしたユーザー" mutedUsers: "ミュートしたユーザー"
blockedUsers: "ブロックしたユーザー" blockedUsers: "ブロックしたユーザー"
noUsers: "ユーザーはおらん" noUsers: "ユーザーはおらん"
editProfile: "プロフィールをいじる" editProfile: "プロフィールをいじる"
noteDeleteConfirm: "このノートを削除しまっか?" noteDeleteConfirm: "このノートをほかしてええか?"
pinLimitExceeded: "これ以上ピン留めできひん" pinLimitExceeded: "これ以上ピン留めできひん"
intro: "Misskeyのインストールが完了してん!管理者アカウントを作ってや。" intro: "Misskeyのインストールが完了したで!管理者アカウントを作ってや。"
done: "でけた" done: "でけた"
processing: "処理しとる" processing: "処理しとる"
preview: "プレビュー" preview: "プレビュー"
default: "デフォルト" default: "デフォルト"
defaultValueIs: "デフォルト" defaultValueIs: "デフォルト: {value}"
noCustomEmojis: "絵文字はあらへん" noCustomEmojis: "絵文字はあらへん"
noJobs: "ジョブはあらへん" noJobs: "ジョブはあらへん"
federating: "連合しとる" federating: "連合しとる"
@ -220,17 +223,17 @@ all: "みんな"
subscribing: "購読しとる" subscribing: "購読しとる"
publishing: "配信しとる" publishing: "配信しとる"
notResponding: "応答してへんで" notResponding: "応答してへんで"
instanceFollowing: "インスタンスのフォロー" instanceFollowing: "サーバーのフォロー"
instanceFollowers: "インスタンスのフォロワー\n" instanceFollowers: "サーバーのフォロワー\n"
instanceUsers: "インスタンスのユーザー" instanceUsers: "サーバーのユーザー"
changePassword: "パスワード変える" changePassword: "パスワードをいじる"
security: "セキュリティ" security: "セキュリティ"
retypedNotMatch: "そやないねん。" retypedNotMatch: "入れたやつ合うてへんわ。"
currentPassword: "今のパスワード" currentPassword: "今のパスワード"
newPassword: "今度のパスワード" newPassword: "のパスワード"
newPasswordRetype: "今度のパスワード(もっぺん入れて)" newPasswordRetype: "今度のパスワード(もっぺん入れて)"
attachFile: "ファイルのっける" attachFile: "ファイルのっける"
more: "他のやつ!" more: "他の"
featured: "ハイライト" featured: "ハイライト"
usernameOrUserId: "ユーザー名かユーザーID" usernameOrUserId: "ユーザー名かユーザーID"
noSuchUser: "ユーザーが見つからへんで" noSuchUser: "ユーザーが見つからへんで"
@ -238,15 +241,15 @@ lookup: "見てきて"
announcements: "お知らせ" announcements: "お知らせ"
imageUrl: "画像URL" imageUrl: "画像URL"
remove: "ほかす" remove: "ほかす"
removed: "削除したで!" removed: "ほかしたで!"
removeAreYouSure: "「{x}」はほかしてええか?" removeAreYouSure: "「{x}」はほかしてええか?"
deleteAreYouSure: "「{x}」はほかしてええか?" deleteAreYouSure: "「{x}」はほかしてええか?"
resetAreYouSure: "リセットしてええん?" resetAreYouSure: "リセットしてええん?"
saved: "保存したで!" saved: "保存したで!"
messaging: "チャット" messaging: "チャット"
upload: "アップロード" upload: "アップロード"
keepOriginalUploading: "オリジナル画像を保持するわ" keepOriginalUploading: "オリジナル画像のまんま"
keepOriginalUploadingDescription: "画像を上げるときにオリジナル版を保持するで。オフにしたら上げたときにブラウザでWeb公開用の画像を生成するで。 " keepOriginalUploadingDescription: "画像を上げるときにオリジナル版のまんまにするで。オフにしたら、上げたときにブラウザでWeb公開用の画像を生成するで。 "
fromDrive: "ドライブから" fromDrive: "ドライブから"
fromUrl: "URLから" fromUrl: "URLから"
uploadFromUrl: "URLアップロード" uploadFromUrl: "URLアップロード"
@ -255,7 +258,7 @@ uploadFromUrlRequested: "アップロードしたい言うといたで"
uploadFromUrlMayTakeTime: "アップロード終わるんにちょい時間かかるかもしれへんわ。" uploadFromUrlMayTakeTime: "アップロード終わるんにちょい時間かかるかもしれへんわ。"
explore: "みつける" explore: "みつける"
messageRead: "もう読んだ" messageRead: "もう読んだ"
noMoreHistory: "これより過去の履歴はあらへんで" noMoreHistory: "これより昔のんはあらへんで"
startMessaging: "チャットやるで" startMessaging: "チャットやるで"
nUsersRead: "{n}人が読んでもうた" nUsersRead: "{n}人が読んでもうた"
agreeTo: "{0}に同意したで" agreeTo: "{0}に同意したで"
@ -272,8 +275,8 @@ yearsOld: "{age}歳"
registeredDate: "始めた日" registeredDate: "始めた日"
location: "場所" location: "場所"
theme: "テーマ" theme: "テーマ"
themeForLightMode: "ライトモードではこのテーマつこて" themeForLightMode: "ライトモードではこのテーマ使うて"
themeForDarkMode: "ダークモードではこのテーマつこて" themeForDarkMode: "ダークモードではこのテーマ使うて"
light: "ライト" light: "ライト"
dark: "ダーク" dark: "ダーク"
lightThemes: "デイゲーム" lightThemes: "デイゲーム"
@ -289,22 +292,22 @@ renameFile: "ファイル名をいらう"
folderName: "フォルダー名" folderName: "フォルダー名"
createFolder: "フォルダー作る" createFolder: "フォルダー作る"
renameFolder: "フォルダー名を変える" renameFolder: "フォルダー名を変える"
deleteFolder: "フォルダーを消してまう" deleteFolder: "フォルダーをほかす"
addFile: "ファイルを追加" addFile: "ファイルを追加"
emptyDrive: "ドライブにはなんも残っとらん" emptyDrive: "ドライブは空っぽや"
emptyFolder: "ふぉろだーにはなんも残っとらん" emptyFolder: "このフォルダーは空や"
unableToDelete: "消そうおもってんけどな、あかんかったわ" unableToDelete: "消んかったわ"
inputNewFileName: "今度のファイル名は何にするん?" inputNewFileName: "今度のファイル名は何にするん?"
inputNewDescription: "新しいキャプションを入力しましょ" inputNewDescription: "新しいキャプションを入れてや"
inputNewFolderName: "今度のフォルダ名は何にするん?" inputNewFolderName: "今度のフォルダ名は何にするん?"
circularReferenceFolder: "移動先のフォルダーは、移動するフォルダーのサブフォルダーや。" circularReferenceFolder: "移動先のフォルダーは、移動するフォルダーのサブフォルダーや。"
hasChildFilesOrFolders: "このフォルダ、まだなんか入っとるから消されへん" hasChildFilesOrFolders: "このフォルダは空っぽちゃうから消されへん"
copyUrl: "URLをコピー" copyUrl: "URLをコピー"
rename: "名前を変えるで" rename: "名前を変えるで"
avatar: "アイコン" avatar: "アイコン"
banner: "バナー" banner: "バナー"
nsfw: "閲覧注意" nsfw: "見るんは気いつけてな"
whenServerDisconnected: "サーバーとの接続が切れたとき" whenServerDisconnected: "サーバーとの接続が失くなってしもうたとき"
disconnectedFromServer: "サーバーが機嫌悪いねん" disconnectedFromServer: "サーバーが機嫌悪いねん"
reload: "リロード" reload: "リロード"
doNothing: "何もせんとく" doNothing: "何もせんとく"
@ -314,10 +317,10 @@ unwatch: "ウォッチやめる"
accept: "ええで" accept: "ええで"
reject: "あかん" reject: "あかん"
normal: "ええ感じ" normal: "ええ感じ"
instanceName: "インスタンス名" instanceName: "サーバー名"
instanceDescription: "インスタンスの紹介" instanceDescription: "サーバーの紹介"
maintainerName: "管理者の名前" maintainerName: "管理者はんの名前"
maintainerEmail: "管理者のメールアドレス" maintainerEmail: "管理者はんのメールアドレス"
tosUrl: "利用規約のURL" tosUrl: "利用規約のURL"
thisYear: "今年" thisYear: "今年"
thisMonth: "今月" thisMonth: "今月"
@ -329,23 +332,23 @@ pages: "ページ"
integration: "連携" integration: "連携"
connectService: "つなげるで" connectService: "つなげるで"
disconnectService: "切るで" disconnectService: "切るで"
enableLocalTimeline: "ローカルタイムラインを使えるようにする" enableLocalTimeline: "ローカルタイムラインを使えるようにする"
enableGlobalTimeline: "グローバルタイムラインを使えるようにする" enableGlobalTimeline: "グローバルタイムラインを使えるようにする"
disablingTimelinesInfo: "ここらへんのタイムラインを使えんようにしてしもても、管理者とモデレーターは使えるままになってるで、そうやなかったら不便やからな。" disablingTimelinesInfo: "ここらへんのタイムラインを使えんようにしてしもても、管理者とモデレーターは使えるままになってるで、そうやなかったら不便やからな。"
registration: "登録" registration: "登録"
enableRegistration: "一見さんでも誰でもいらっしゃ~い" enableRegistration: "一見さんでも誰でもいらっしゃ~い"
invite: "来てや" invite: "来てや"
driveCapacityPerLocalAccount: "ローカルユーザーひとりあたりのドライブ容量" driveCapacityPerLocalAccount: "ローカルユーザーはんひとりあたりのドライブ容量"
driveCapacityPerRemoteAccount: "リモートユーザーひとりあたりのドライブ容量" driveCapacityPerRemoteAccount: "リモートユーザーはんひとりあたりのドライブ容量"
inMb: "メガバイト単位" inMb: "メガバイト単位"
iconUrl: "アイコン画像のURL" iconUrl: "アイコン画像のURL"
bannerUrl: "バナー画像のURL" bannerUrl: "バナー画像のURL"
backgroundImageUrl: "背景画像のURL" backgroundImageUrl: "背景画像のURL"
basicInfo: "基本情報" basicInfo: "基本情報"
pinnedUsers: "ピン留めしたユーザー" pinnedUsers: "ピン留めしたユーザー"
pinnedUsersDescription: "「みつける」ページとかにピン留めしたいユーザーをここに書けばええんやで。他ん人との名前は改行で区切ればええんやで。" pinnedUsersDescription: "「みつける」ページとかにピン留めしたいユーザーをここに書けばええんやで。ユーザー毎に改行してや。"
pinnedPages: "ピン留めページ" pinnedPages: "ピン留めページ"
pinnedPagesDescription: "インスタンスのいっちゃん上にピン留めしたいページのパスを改行で区切って記述してな" pinnedPagesDescription: "サーバーのいっちゃん上にピン留めしたいページのパスを改行で区切って記述してな"
pinnedClipId: "ピン留めするクリップのID" pinnedClipId: "ピン留めするクリップのID"
pinnedNotes: "ピン留めされとるノート" pinnedNotes: "ピン留めされとるノート"
hcaptcha: "hCaptchaキャプチャ" hcaptcha: "hCaptchaキャプチャ"
@ -370,7 +373,7 @@ antennaExcludeKeywords: "除外キーワード"
antennaKeywordsDescription: "スペースで区切ったるとAND指定で、改行で区切ったるとOR指定や" antennaKeywordsDescription: "スペースで区切ったるとAND指定で、改行で区切ったるとOR指定や"
notifyAntenna: "新しいノートを通知すんで" notifyAntenna: "新しいノートを通知すんで"
withFileAntenna: "なんか添付されたノートだけ" withFileAntenna: "なんか添付されたノートだけ"
enableServiceworker: "ServiceWorkerをつこて" enableServiceworker: "ブラウザにプッシュ通知が行くようにする"
antennaUsersDescription: "ユーザー名を改行で区切ったってな" antennaUsersDescription: "ユーザー名を改行で区切ったってな"
caseSensitive: "大文字と小文字は別もんや" caseSensitive: "大文字と小文字は別もんや"
withReplies: "返信も入れたって" withReplies: "返信も入れたって"
@ -395,23 +398,23 @@ administrator: "管理者"
token: "トークン" token: "トークン"
2fa: "二要素認証" 2fa: "二要素認証"
totp: "認証アプリ" totp: "認証アプリ"
totpDescription: "認証アプリ使てワンタイムパスワードを入れる" totpDescription: "認証アプリ使てワンタイムパスワードを入れる"
moderator: "モデレーター" moderator: "モデレーター"
moderation: "モデレーション" moderation: "モデレーション"
nUsersMentioned: "{n}人が投稿" nUsersMentioned: "{n}人が投稿"
securityKeyAndPasskey: "セキュリティキー・パスキー" securityKeyAndPasskey: "セキュリティキー・パスキー"
securityKey: "セキュリティキー" securityKey: "セキュリティキー"
lastUsed: "最後につこうた日" lastUsed: "最後につこうた日"
lastUsedAt: "最後に使たん: {t}" lastUsedAt: "最後に使たん: {t}"
unregister: "登録やめる" unregister: "登録やめる"
passwordLessLogin: "パスワード無くてもログインできるようにする" passwordLessLogin: "パスワード無くてもログインできるようにする"
passwordLessLoginDescription: "パスワードやなくて、セキュリティキーとかパスキーだけでログインするわ" passwordLessLoginDescription: "パスワードなんかいらん、セキュリティキーとかパスキーだけでログインするわ"
resetPassword: "パスワードをリセット" resetPassword: "パスワードをリセット"
newPasswordIs: "今度のパスワードは「{password}」や" newPasswordIs: "今度のパスワードは「{password}」や"
reduceUiAnimation: "UIの動きやアニメーションを減らす" reduceUiAnimation: "UIの動きやアニメーションを少なする"
share: "わけわけ" share: "わけわけ"
notFound: "見つからへんね" notFound: "見つからへんね"
notFoundDescription: "指定されたURLに該当するページはあらへんやった。" notFoundDescription: "言われたURLにはまるページはなかったで。"
uploadFolder: "とりあえずアップロードしたやつ置いとく所" uploadFolder: "とりあえずアップロードしたやつ置いとく所"
cacheClear: "キャッシュをほかす" cacheClear: "キャッシュをほかす"
markAsReadAllNotifications: "通知はもう全て読んだわっ" markAsReadAllNotifications: "通知はもう全て読んだわっ"
@ -419,37 +422,37 @@ markAsReadAllUnreadNotes: "投稿は全て読んだわっ"
markAsReadAllTalkMessages: "チャットはもうぜんぶ読んだわっ" markAsReadAllTalkMessages: "チャットはもうぜんぶ読んだわっ"
help: "ヘルプ" help: "ヘルプ"
inputMessageHere: "ここにメッセージ書いてや" inputMessageHere: "ここにメッセージ書いてや"
close: "閉じる" close: "さいなら"
invites: "来てや" invites: "来てや"
members: "メンバー" members: "メンバーはん"
transfer: "譲渡" transfer: "譲渡"
title: "タイトル" title: "タイトル"
text: "テキスト" text: "テキスト"
enable: "有効にするで" enable: "有効にするで"
next: "次" next: "次"
retype: "もっかい入力" retype: "もっかい入力"
noteOf: "{user}のノート" noteOf: "{user}はんのノート"
quoteAttached: "引用付いとるで" quoteAttached: "引用付いとるで"
quoteQuestion: "引用として添付してもええか?" quoteQuestion: "引用として添付してもええか?"
noMessagesYet: "まだチャットはあらへんで" noMessagesYet: "まだチャットはあらへんで"
newMessageExists: "新しいメッセージがきたで" newMessageExists: "新しいメッセージがきたで"
onlyOneFileCanBeAttached: "すまん、メッセージに添付できるファイルはひとつだけなんや。" onlyOneFileCanBeAttached: "ごめんな、メッセージに添付できるファイルはひとつだけなんよ。"
signinRequired: "ログインしてくれへん?" signinRequired: "ログインしてくれへん?"
invitations: "来てや" invitations: "来てや"
invitationCode: "招待コード" invitationCode: "招待コード"
checking: "確認しとるで" checking: "確認しとるで"
available: "利用できる\n" available: "使えるで"
unavailable: "利用できん" unavailable: "利用できん"
usernameInvalidFormat: "a~z、A~Z、0~9、_が使えるで" usernameInvalidFormat: "a~z、A~Z、0~9、_が使えるで"
tooShort: "短すぎやろ!" tooShort: "短すぎやろ!"
tooLong: "長すぎやろ!" tooLong: "長すぎやろ!"
weakPassword: "へぼいパスワード" weakPassword: "へぼいパスワード"
normalPassword: "普通のパスワード" normalPassword: "ぼちぼちのパスワード"
strongPassword: "ええ感じのパスワード" strongPassword: "ええ感じのパスワード"
passwordMatched: "よし!一致や!" passwordMatched: "よし!一致や!"
passwordNotMatched: "一致しとらんで?" passwordNotMatched: "一致しとらんで?"
signinWith: "{x}でログイン" signinWith: "{x}でログイン"
signinFailed: "ログインできんかったで。もっかいユーザー名とパスワードを確認してみて。" signinFailed: "ログインできんかったで。もっかいユーザー名とパスワードを確認してみて。"
or: "それか" or: "それか"
language: "言語" language: "言語"
uiLanguage: "UIの表示言語" uiLanguage: "UIの表示言語"
@ -458,7 +461,7 @@ emojiStyle: "絵文字のスタイル"
native: "ネイティブ" native: "ネイティブ"
disableDrawer: "メニューをドロワーで表示せぇへん" disableDrawer: "メニューをドロワーで表示せぇへん"
showNoteActionsOnlyHover: "ノートの操作部をホバー時のみ表示するで" showNoteActionsOnlyHover: "ノートの操作部をホバー時のみ表示するで"
noHistory: "履歴はあらへんねぇ。" noHistory: "履歴はないわ。"
signinHistory: "ログイン履歴" signinHistory: "ログイン履歴"
enableAdvancedMfm: "ややこしいMFMもありにする" enableAdvancedMfm: "ややこしいMFMもありにする"
enableAnimatedMfm: "動きがやかましいMFMも許したる" enableAnimatedMfm: "動きがやかましいMFMも許したる"
@ -466,12 +469,12 @@ doing: "やっとるがな"
category: "カテゴリ" category: "カテゴリ"
tags: "タグ" tags: "タグ"
docSource: "このドキュメントのソース" docSource: "このドキュメントのソース"
createAccount: "アカウントを作" createAccount: "アカウントを作るで"
existingAccount: "既存のアカウント" existingAccount: "前に作ったアカウント"
regenerate: "再生成" regenerate: "もっぺん生成するで"
fontSize: "フォントサイズ" fontSize: "字の大きさ"
noFollowRequests: "フォロー申請はあらへんで" noFollowRequests: "フォロー申請はあらへんで"
openImageInNewTab: "画像を新しいタブで開く" openImageInNewTab: "画像を新しいタブで開く"
dashboard: "ダッシュボード" dashboard: "ダッシュボード"
local: "ローカル" local: "ローカル"
remote: "リモート" remote: "リモート"
@ -503,9 +506,11 @@ objectStorageUseSSLDesc: "API接続にhttpsを使わん場合はオフにする
objectStorageUseProxy: "Proxyを使う" objectStorageUseProxy: "Proxyを使う"
objectStorageUseProxyDesc: "API接続にproxy使わんのやったら切ってくれへん" objectStorageUseProxyDesc: "API接続にproxy使わんのやったら切ってくれへん"
objectStorageSetPublicRead: "アップロードした時に'public-read'を設定してや" objectStorageSetPublicRead: "アップロードした時に'public-read'を設定してや"
s3ForcePathStyleDesc: "s3ForcePathStyleを使たらバケット名をURLのホスト名やなくてパスの一部として必ず指定させるようになるで。セルフホストされたMinioとかを使うてるんやったら有効にせなあかん場合があるで。"
serverLogs: "サーバーログ" serverLogs: "サーバーログ"
deleteAll: "全て削除してや" deleteAll: "全部ほかす"
showFixedPostForm: "タイムラインの上の方で投稿できるようにやってくれへん?" showFixedPostForm: "タイムラインの上の方で投稿できるようにやってくれへん?"
showFixedPostFormInChannel: "タイムラインの上の方で投稿できるようにするわ(チャンネル)"
newNoteRecived: "新しいノートがあるで" newNoteRecived: "新しいノートがあるで"
sounds: "サウンド" sounds: "サウンド"
sound: "サウンド" sound: "サウンド"
@ -513,11 +518,11 @@ listen: "聴く"
none: "なし" none: "なし"
showInPage: "ページで表示" showInPage: "ページで表示"
popout: "ポップアウト" popout: "ポップアウト"
volume: "音量" volume: "やかましさ"
masterVolume: "全体の音量" masterVolume: "全体のやかましさ"
details: "もっと" details: "もっと"
chooseEmoji: "絵文字を選ぶ" chooseEmoji: "絵文字を選ぶ"
unableToProcess: "なんか作業が止まってしまったようやね" unableToProcess: "なんか奥の方で詰まってもうた"
recentUsed: "最近使ったやつ" recentUsed: "最近使ったやつ"
install: "インストール" install: "インストール"
uninstall: "アンインストール" uninstall: "アンインストール"
@ -535,14 +540,18 @@ output: "出力"
script: "スクリプト" script: "スクリプト"
disablePagesScript: "Pagesのスクリプトを無効にしてや" disablePagesScript: "Pagesのスクリプトを無効にしてや"
updateRemoteUser: "リモートユーザー情報の更新してくれん?" updateRemoteUser: "リモートユーザー情報の更新してくれん?"
deleteAllFiles: "すべてのファイルを削除" deleteAllFiles: "ファイルを全部ほかす"
deleteAllFilesConfirm: "ホンマにすべてのファイルを削除するん?消したもんはもう戻ってこんのやで?" deleteAllFilesConfirm: "ホンマにファイル全部ほかすんか?消したもんはもう戻ってこんのやで?"
removeAllFollowing: "フォローを全解除" removeAllFollowing: "フォローを全解除"
removeAllFollowingDescription: "{host}からのフォローをすべて解除するで。そのインスタンスが消えて無くなった時とかには便利な機能やで。" removeAllFollowingDescription: "{host}からのフォローをすべて解除するで。そのインスタンスが消えて無くなった時とかには便利な機能やで。"
userSuspended: "このユーザーは...凍結されとる。" userSuspended: "このユーザーは...凍結されとる。"
userSilenced: "このユーザーは...サイレンスされとる。" userSilenced: "このユーザーは...サイレンスされとる。"
yourAccountSuspendedTitle: "あんたのアカウント凍結されとるで" yourAccountSuspendedTitle: "あんたのアカウント凍結されとるで"
yourAccountSuspendedDescription: "あんたのアカウントは、サーバーの利用規約に違反したとかの理由で、凍結されとるで。細かいことは管理者までお問い合わせたってなー。絶対に新しいアカウント作ったらあかんで。絶対やで。" yourAccountSuspendedDescription: "あんたのアカウントは、サーバーの利用規約に違反したとかの理由で、凍結されとるで。細かいことは管理者までお問い合わせたってなー。絶対に新しいアカウント作ったらあかんで。絶対やで。"
tokenRevoked: "トークンが無効やで"
tokenRevokedDescription: "ログイントークンが失効しとるで。もっかいログインしてもろてもええか?"
accountDeleted: "アカウントは削除されとるで"
accountDeletedDescription: "このアカウントは削除されとるで。"
menu: "メニュー" menu: "メニュー"
divider: "分割線" divider: "分割線"
addItem: "項目を追加" addItem: "項目を追加"
@ -565,7 +574,7 @@ description: "説明"
describeFile: "キャプションを付ける" describeFile: "キャプションを付ける"
enterFileDescription: "キャプションを入力" enterFileDescription: "キャプションを入力"
author: "作者" author: "作者"
leaveConfirm: "未保存の変更があるで!ほかしてええか?" leaveConfirm: "あんた、いじったのにまだ保存してないで!ほかしてええか?"
manage: "管理" manage: "管理"
plugins: "プラグイン" plugins: "プラグイン"
preferencesBackups: "設定のバックアップ" preferencesBackups: "設定のバックアップ"
@ -586,7 +595,6 @@ tokenRequested: "アカウントへのアクセス許してやったらどうや
pluginTokenRequestedDescription: "このプラグインはここで設定した権限を使えるようになるで。" pluginTokenRequestedDescription: "このプラグインはここで設定した権限を使えるようになるで。"
notificationType: "通知の種類" notificationType: "通知の種類"
edit: "編集" edit: "編集"
useStarForReactionFallback: "リアクションがようわからん場合、★を使う"
emailServer: "メールサーバー" emailServer: "メールサーバー"
enableEmail: "メール配信を受け取る" enableEmail: "メール配信を受け取る"
emailConfigInfo: "メールアドレスの確認とかパスワードリセットの時に使うで" emailConfigInfo: "メールアドレスの確認とかパスワードリセットの時に使うで"
@ -599,12 +607,12 @@ smtpUser: "ユーザー名"
smtpPass: "パスワード" smtpPass: "パスワード"
emptyToDisableSmtpAuth: "ユーザー名とパスワードになんも入れんかったら、SMTP認証を無効化するで" emptyToDisableSmtpAuth: "ユーザー名とパスワードになんも入れんかったら、SMTP認証を無効化するで"
smtpSecure: "SMTP 接続に暗黙的なSSL/TLSを使用する" smtpSecure: "SMTP 接続に暗黙的なSSL/TLSを使用する"
smtpSecureInfo: "STARTTLS使っとる時はオフにするで。" smtpSecureInfo: "STARTTLS使っとる時はオフにしてや。"
testEmail: "配信テスト" testEmail: "配信テスト"
wordMute: "ワードミュート" wordMute: "ワードミュート"
regexpError: "正規表現エラー" regexpError: "正規表現エラー"
regexpErrorDescription: "{tab}ワードミュートの{line}行目の正規表現にエラーが出てきたで:" regexpErrorDescription: "{tab}ワードミュートの{line}行目の正規表現にエラーが出てきたで:"
instanceMute: "インスタンスミュート" instanceMute: "サーバーミュート"
userSaysSomething: "{name}が何か言うとるわ" userSaysSomething: "{name}が何か言うとるわ"
makeActive: "使うで" makeActive: "使うで"
display: "表示" display: "表示"
@ -623,7 +631,7 @@ useGlobalSettingDesc: "オンにすると、アカウントの通知設定が使
other: "その他" other: "その他"
regenerateLoginToken: "ログイントークンを再生成" regenerateLoginToken: "ログイントークンを再生成"
regenerateLoginTokenDescription: "ログインに使われる内部トークンをもっかい作るで。いつもならこれをやる必要はないで。もっかい作ると、全部のデバイスでログアウトされるで気ぃつけてなー。" regenerateLoginTokenDescription: "ログインに使われる内部トークンをもっかい作るで。いつもならこれをやる必要はないで。もっかい作ると、全部のデバイスでログアウトされるで気ぃつけてなー。"
setMultipleBySeparatingWithSpace: "スペースで区切って複数設定できるで。" setMultipleBySeparatingWithSpace: "スペースで区切って何個でも設定できるで。"
fileIdOrUrl: "ファイルIDかURL" fileIdOrUrl: "ファイルIDかURL"
behavior: "動作" behavior: "動作"
sample: "サンプル" sample: "サンプル"
@ -635,15 +643,15 @@ abuseReported: "無事内容が送信されたみたいやで。おおきに〜
reporter: "通報者" reporter: "通報者"
reporteeOrigin: "通報先" reporteeOrigin: "通報先"
reporterOrigin: "通報元" reporterOrigin: "通報元"
forwardReport: "リモートインスタンスに通報を転送するで" forwardReport: "リモートサーバーに通報を転送するで"
forwardReportIsAnonymous: "リモートインスタンスからはあんたの情報は見れへんくって、匿名のシステムアカウントとして表示されるで。" forwardReportIsAnonymous: "リモートサーバーからはあんたの情報は見えんなって、匿名のシステムアカウントとして表示されるで。"
send: "送信" send: "送信"
abuseMarkAsResolved: "対応したで" abuseMarkAsResolved: "対応したで"
openInNewTab: "新しいタブで開く" openInNewTab: "新しいタブで開く"
openInSideView: "サイドビューで開く" openInSideView: "サイドビューで開く"
defaultNavigationBehaviour: "デフォルトのナビゲーション" defaultNavigationBehaviour: "デフォルトのナビゲーション"
editTheseSettingsMayBreakAccount: "このへんの設定をようわからんままイジるとアカウントが壊れて使えんくなるかも知れへんで?" editTheseSettingsMayBreakAccount: "このへんの設定をようわからんままイジるとアカウントが壊れて使えんくなるかも知れへんで?"
instanceTicker: "ノートのインスタンス情報" instanceTicker: "ノートのサーバー情報"
waitingFor: "{x}を待っとるで" waitingFor: "{x}を待っとるで"
random: "ランダム" random: "ランダム"
system: "システム" system: "システム"
@ -654,7 +662,7 @@ createNew: "新しく作るで"
optional: "任意" optional: "任意"
createNewClip: "新しいクリップを作るで" createNewClip: "新しいクリップを作るで"
unclip: "クリップ解除するで" unclip: "クリップ解除するで"
confirmToUnclipAlreadyClippedNote: "このノートはすでにクリップ「{name}」に含まれとるで。ノートをこのクリップから除外したる" confirmToUnclipAlreadyClippedNote: "このノートはすでにクリップ「{name}」に含まれとるで。ノートをこのクリップから除外しよか"
public: "パブリック" public: "パブリック"
i18nInfo: "Misskeyは有志によっていろんな言語に翻訳されとるで。{link}で翻訳に協力したってやー。" i18nInfo: "Misskeyは有志によっていろんな言語に翻訳されとるで。{link}で翻訳に協力したってやー。"
manageAccessTokens: "アクセストークンの管理" manageAccessTokens: "アクセストークンの管理"
@ -671,15 +679,15 @@ receivedReactionsCount: "リアクションされた数"
pollVotesCount: "アンケートに投票した数" pollVotesCount: "アンケートに投票した数"
pollVotedCount: "アンケートに投票された数" pollVotedCount: "アンケートに投票された数"
yes: "ええで" yes: "ええで"
no: "あかん" no: "あかん"
driveFilesCount: "ドライブのファイル数" driveFilesCount: "ドライブのファイル数"
driveUsage: "ドライブ使用量やで" driveUsage: "ドライブ使用量やで"
noCrawle: "クローラーによるインデックスを拒否するで" noCrawle: "クローラーによるインデックスを拒否するで"
noCrawleDescription: "検索エンジンにあんたのユーザーページ、ート、Pagesとかのコンテンツを登録(インデックス)せぇへんように頼むで。" noCrawleDescription: "検索エンジンにあんたのユーザーページ、ート、Pagesとかのコンテンツを登録(インデックス)せんように頼むで。邪魔すんねんやったら帰って〜。"
lockedAccountInfo: "フォローを承認制にしとっても、ノートの公開範囲を「フォロワー」にせぇへん限り、誰でもあんたのノートを見れるで。" lockedAccountInfo: "フォローを承認制にしとっても、ノートの公開範囲を「フォロワー」にせぇへん限り、誰でもあんたのノートを見れるで。"
alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にするで" alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にするで"
loadRawImages: "添付画像のサムネイルをオリジナル画質にするで" loadRawImages: "添付画像のサムネイルをオリジナル画質にするで"
disableShowingAnimatedImages: "アニメーション画像を再生しやへんで" disableShowingAnimatedImages: "アニメーション画像を再生せんとくで"
verificationEmailSent: "無事確認のメールを送れたで。メールに書いてあるリンクにアクセスして、設定を完了してなー。" verificationEmailSent: "無事確認のメールを送れたで。メールに書いてあるリンクにアクセスして、設定を完了してなー。"
notSet: "未設定" notSet: "未設定"
emailVerified: "メールアドレスは確認されたで" emailVerified: "メールアドレスは確認されたで"
@ -689,14 +697,14 @@ pageLikedCount: "Pageにええやんと思ってくれた数"
contact: "連絡先" contact: "連絡先"
useSystemFont: "システムのデフォルトのフォントを使うで" useSystemFont: "システムのデフォルトのフォントを使うで"
clips: "クリップ" clips: "クリップ"
experimentalFeatures: "実験的機能やで" experimentalFeatures: "おためし機能やで"
developer: "開発者やで" developer: "開発者やで"
makeExplorable: "アカウントを見つけやすくするで" makeExplorable: "アカウントを見つけやすくするで"
makeExplorableDescription: "オフにすると、「みつける」にアカウントが載らんくなるで。" makeExplorableDescription: "オフにすると、「みつける」にアカウントが載らんくなるで。"
showGapBetweenNotesInTimeline: "タイムラインのノートを離して表示するで" showGapBetweenNotesInTimeline: "タイムラインのノートを離して表示するで"
duplicate: "複製" duplicate: "複製"
left: "左" left: "左"
center: "" center: "真ん中"
wide: "広い" wide: "広い"
narrow: "狭い" narrow: "狭い"
reloadToApplySetting: "設定はページリロード後に反映されるで。今リロードしとくか?" reloadToApplySetting: "設定はページリロード後に反映されるで。今リロードしとくか?"
@ -707,7 +715,7 @@ onlineUsersCount: "{n}人が起きとるで"
nUsers: "{n}ユーザー" nUsers: "{n}ユーザー"
nNotes: "{n}ノート" nNotes: "{n}ノート"
sendErrorReports: "エラーリポートを送る" sendErrorReports: "エラーリポートを送る"
sendErrorReportsDescription: "オンにしたら、なんか変なことが起きたときにエラーの詳細がMisskeyに共有されて、ソフトウェアの品質向上に役立てられるんや。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴などが含まれるで。" sendErrorReportsDescription: "オンにしたら、変なことが起きたときにエラーの詳細がMisskeyに送られて、ソフトウェアの品質向上に使えるようになるで。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴なんかが含まれるで。"
myTheme: "マイテーマ" myTheme: "マイテーマ"
backgroundColor: "背景" backgroundColor: "背景"
accentColor: "アクセント" accentColor: "アクセント"
@ -732,7 +740,7 @@ capacity: "容量"
inUse: "使用中" inUse: "使用中"
editCode: "コードを編集" editCode: "コードを編集"
apply: "適用" apply: "適用"
receiveAnnouncementFromInstance: "インスタンスからのお知らせを受け取る" receiveAnnouncementFromInstance: "サーバーからのお知らせを受け取る"
emailNotification: "メール通知" emailNotification: "メール通知"
publish: "公開" publish: "公開"
inChannelSearch: "チャンネル内検索" inChannelSearch: "チャンネル内検索"
@ -760,7 +768,7 @@ active: "アクティブ"
offline: "オフライン" offline: "オフライン"
notRecommended: "あんま推奨しやんで" notRecommended: "あんま推奨しやんで"
botProtection: "Botプロテクション" botProtection: "Botプロテクション"
instanceBlocking: "インスタンスブロック" instanceBlocking: "サーバーブロック"
selectAccount: "アカウントを選んでなー" selectAccount: "アカウントを選んでなー"
switchAccount: "アカウントを変えるで" switchAccount: "アカウントを変えるで"
enabled: "有効" enabled: "有効"
@ -844,8 +852,8 @@ themeColor: "テーマカラー"
size: "大きさ" size: "大きさ"
numberOfColumn: "列の数" numberOfColumn: "列の数"
searchByGoogle: "探す" searchByGoogle: "探す"
instanceDefaultLightTheme: "インスタンスの最初の明るいテーマ" instanceDefaultLightTheme: "サーバーおすすめの明るいテーマ"
instanceDefaultDarkTheme: "インスタンスの最初の暗いテーマ" instanceDefaultDarkTheme: "サーバーおすすめのの暗いテーマ"
instanceDefaultThemeDescription: "オブジェクト形式のテーマコードを記入するで。" instanceDefaultThemeDescription: "オブジェクト形式のテーマコードを記入するで。"
mutePeriod: "ミュートする期間" mutePeriod: "ミュートする期間"
period: "期限" period: "期限"
@ -859,7 +867,7 @@ reflectMayTakeTime: "反映されるまで時間がかかることがあるで"
failedToFetchAccountInformation: "アカウントの取得に失敗したみたいや…" failedToFetchAccountInformation: "アカウントの取得に失敗したみたいや…"
rateLimitExceeded: "レート制限が超えたみたいやで" rateLimitExceeded: "レート制限が超えたみたいやで"
cropImage: "画像のクロップ" cropImage: "画像のクロップ"
cropImageAsk: "画像をクロップしたってええか?" cropImageAsk: "画像をクロップしええか?"
cropYes: "切り抜いたる" cropYes: "切り抜いたる"
cropNo: "切り抜かへん" cropNo: "切り抜かへん"
file: "ファイル" file: "ファイル"
@ -876,7 +884,7 @@ isSystemAccount: "システムが自動で作成・管理しとるアカウン
typeToConfirm: "この操作をやるんなら {x} と入力してなー" typeToConfirm: "この操作をやるんなら {x} と入力してなー"
deleteAccount: "アカウント削除するで" deleteAccount: "アカウント削除するで"
document: "ドキュメント" document: "ドキュメント"
numberOfPageCache: "ページキャッシュ数やで" numberOfPageCache: "ページ、どんだけキャッシュすんの?"
numberOfPageCacheDescription: "増やすと使いやすくなる、負荷とメモリ使用量が増えてくで。一長一短やな。" numberOfPageCacheDescription: "増やすと使いやすくなる、負荷とメモリ使用量が増えてくで。一長一短やな。"
logoutConfirm: "ログアウトしまっか?" logoutConfirm: "ログアウトしまっか?"
lastActiveDate: "最後に使った日時" lastActiveDate: "最後に使った日時"
@ -894,11 +902,11 @@ sensitiveMediaDetection: "センシティブなメディアの検出"
localOnly: "ローカルのみ" localOnly: "ローカルのみ"
remoteOnly: "リモートのみ" remoteOnly: "リモートのみ"
failedToUpload: "アップロードに失敗してもうたわ…" failedToUpload: "アップロードに失敗してもうたわ…"
cannotUploadBecauseInappropriate: "不適切な内容を含むかもしれへんって判定されたでアップロードできまへん。" cannotUploadBecauseInappropriate: "不適切な内容を含むかもしれへんって判定されたからアップロードできへんわ。"
cannotUploadBecauseNoFreeSpace: "ドライブの空き容量が無いでアップロードできまへん。" cannotUploadBecauseNoFreeSpace: "ドライブの空き容量が無いからアップロードできへんわ。"
beta: "ベータ" beta: "ベータ"
enableAutoSensitive: "自動NSFW判定" enableAutoSensitive: "自動NSFW判定"
enableAutoSensitiveDescription: "使える時は、機械学習を使って自動でメディアにNSFWフラグを設定するで。この機能をオフにしても、インスタンスによっては自動で設定されることがあるで。" enableAutoSensitiveDescription: "使える時は、機械学習を使って自動でメディアにNSFWフラグを設定するで。この機能をオフにしても、サーバーによっては自動で設定されることがあるで。"
activeEmailValidationDescription: "ユーザーのメールアドレスのバリデーションを、捨てアドかどうかや実際に通信可能かどうかとかを判定して積極的に行うで。オフにすると単に文字列として正しいかどうかだけチェックするで。" activeEmailValidationDescription: "ユーザーのメールアドレスのバリデーションを、捨てアドかどうかや実際に通信可能かどうかとかを判定して積極的に行うで。オフにすると単に文字列として正しいかどうかだけチェックするで。"
navbar: "ナビゲーションバー" navbar: "ナビゲーションバー"
shuffle: "シャッフルするで" shuffle: "シャッフルするで"
@ -908,10 +916,11 @@ pushNotification: "プッシュ通知"
subscribePushNotification: "プッシュ通知をオンにするで" subscribePushNotification: "プッシュ通知をオンにするで"
unsubscribePushNotification: "プッシュ通知を止めるで" unsubscribePushNotification: "プッシュ通知を止めるで"
pushNotificationAlreadySubscribed: "プッシュ通知はオンになってるで" pushNotificationAlreadySubscribed: "プッシュ通知はオンになってるで"
pushNotificationNotSupported: "ブラウザかインスタンスがプッシュ通知に対応してないみたいやで。" pushNotificationNotSupported: "ブラウザかサーバーがプッシュ通知に対応してないみたいやで。"
sendPushNotificationReadMessage: "通知やメッセージが既読ったらプッシュ通知を消すで" sendPushNotificationReadMessage: "通知やメッセージが既読になったらプッシュ通知を消すで"
sendPushNotificationReadMessageCaption: "「{emptyPushNotificationMessage}」っていう表示が一瞬表示されるようになるで。端末の電池使用量が増える可能性があるで。" sendPushNotificationReadMessageCaption: "「{emptyPushNotificationMessage}」っていう表示が一瞬表示されるようになるで。端末の電池使用量が増える可能性があるで。"
windowMaximize: "最大化" windowMaximize: "最大化"
windowMinimize: "最小化"
windowRestore: "元に戻す" windowRestore: "元に戻す"
caption: "キャプション" caption: "キャプション"
loggedInAsBot: "Botアカウントでログイン中やで" loggedInAsBot: "Botアカウントでログイン中やで"
@ -924,7 +933,7 @@ numberOfLikes: "いいね数"
show: "表示" show: "表示"
neverShow: "今後表示しない" neverShow: "今後表示しない"
remindMeLater: "また後で" remindMeLater: "また後で"
didYouLikeMisskey: "Misskeyを気に入っとっただけましたん" didYouLikeMisskey: "Misskey気に入ってくれた"
pleaseDonate: "Misskeyは{host}が使用している無料のソフトウェアやで。これからも開発を続けれるように、寄付したってな~。" pleaseDonate: "Misskeyは{host}が使用している無料のソフトウェアやで。これからも開発を続けれるように、寄付したってな~。"
roles: "ロール" roles: "ロール"
role: "ロール" role: "ロール"
@ -934,7 +943,7 @@ assign: "アサイン"
unassign: "アサインを解除" unassign: "アサインを解除"
color: "色" color: "色"
manageCustomEmojis: "カスタム絵文字の管理" manageCustomEmojis: "カスタム絵文字の管理"
youCannotCreateAnymore: "これ以上作れなさそうや" youCannotCreateAnymore: "これ以上作れなさそうや"
cannotPerformTemporary: "一時的に利用できへんで" cannotPerformTemporary: "一時的に利用できへんで"
cannotPerformTemporaryDescription: "操作回数が制限を超えたから一時的に利用できへんくなったで。ちょっと時間置いてからもう一回やってやー。" cannotPerformTemporaryDescription: "操作回数が制限を超えたから一時的に利用できへんくなったで。ちょっと時間置いてからもう一回やってやー。"
preset: "プリセット" preset: "プリセット"
@ -946,15 +955,41 @@ thisPostMayBeAnnoying: "この投稿は迷惑かもしらんで。"
thisPostMayBeAnnoyingHome: "ホームに投稿" thisPostMayBeAnnoyingHome: "ホームに投稿"
thisPostMayBeAnnoyingCancel: "やめとく" thisPostMayBeAnnoyingCancel: "やめとく"
thisPostMayBeAnnoyingIgnore: "このまま投稿" thisPostMayBeAnnoyingIgnore: "このまま投稿"
collapseRenotes: "見たことあるRenoteは省略やで" collapseRenotes: "見たことあるRenoteは飛ばして表示するで"
internalServerError: "サーバー内部エラー" internalServerError: "サーバー内部エラー"
internalServerErrorDescription: "サーバー内部でよう分からんエラーやわ" internalServerErrorDescription: "サーバー内部でよう分からんエラーやわ"
copyErrorInfo: "エラー情報をコピー" copyErrorInfo: "エラー情報をコピー"
joinThisServer: "このサーバーに登録するわ" joinThisServer: "このサーバーに登録するわ"
exploreOtherServers: "他のサーバー見てみる" exploreOtherServers: "他のサーバー見てみる"
letsLookAtTimeline: "タイムライン見てみーや" letsLookAtTimeline: "タイムライン見てみーや"
disableFederationWarn: "連合が無効になっとるで。無効にしても投稿は非公開ってわけちゃうねん。大体の場合はこのオプションを有効にする必要は別にないで。" disableFederationConfirm: "連合なしにしとくか?"
disableFederationConfirmWarn: "連合なしにしても投稿は非公開にはならへんで。大体の場合は連合なしにする必要はないで。"
disableFederationOk: "連合なしにしとく"
invitationRequiredToRegister: "今このサーバー招待制になってもうてんねん。招待コードを持っとるんやったら登録できるで。" invitationRequiredToRegister: "今このサーバー招待制になってもうてんねん。招待コードを持っとるんやったら登録できるで。"
emailNotSupported: "このサーバーはメール配信がサポートされてへんみたいやわ"
postToTheChannel: "チャンネルに投稿"
cannotBeChangedLater: "後からは変えられへんで。"
reactionAcceptance: "リアクションの受け入れ"
likeOnly: "いいねだけ"
likeOnlyForRemote: "リモートからはいいねだけな"
rolesAssignedToMe: "自分に割り当てられたロール"
resetPasswordConfirm: "パスワード作り直すんでええな?"
sensitiveWords: "けったいな単語"
sensitiveWordsDescription: "設定した単語が入っとるノートの公開範囲をホームにしたるわ。改行で区切ったら複数設定できるで。"
notesSearchNotAvailable: "ノート検索は使われへんで。"
license: "ライセンス"
unfavoriteConfirm: "ほんまに気に入らんの?"
myClips: "自分のクリップ"
drivecleaner: "ドライブキレイキレイ"
retryAllQueuesNow: "キューを全部もっかいやり直す"
retryAllQueuesConfirmTitle: "もっかいやってみるか?"
retryAllQueuesConfirmText: "一時的にサーバー重なるかもしれへんで。"
enableChartsForRemoteUser: "リモートユーザーのチャートを作る"
enableChartsForFederatedInstances: "リモートサーバーのチャートを作る"
showClipButtonInNoteFooter: "ノートのアクションにクリップを追加"
largeNoteReactions: "ノートのリアクションを大きする"
noteIdOrUrl: "ートIDかURL"
accountMoved: "このユーザーはさらのアカウントに引っ越したで:"
_achievements: _achievements:
earnedAt: "貰った日ぃ" earnedAt: "貰った日ぃ"
_types: _types:
@ -972,7 +1007,7 @@ _achievements:
title: "ノートの生駒山" title: "ノートの生駒山"
description: "ートを500回投稿した" description: "ートを500回投稿した"
_notes1000: _notes1000:
title: "ノートの山" title: "ノートの六甲山"
description: "ートを1,000回投稿した" description: "ートを1,000回投稿した"
_notes5000: _notes5000:
title: "箕面の滝からノート" title: "箕面の滝からノート"
@ -1015,7 +1050,7 @@ _achievements:
_login7: _login7:
title: "ビギナーⅡ" title: "ビギナーⅡ"
description: "通算7日ログインした" description: "通算7日ログインした"
flavor: "慣れてきたんちゃう?" flavor: "慣れてきたんちゃう?"
_login15: _login15:
title: "ビギナーⅢ" title: "ビギナーⅢ"
description: "通算15日ログインした" description: "通算15日ログインした"
@ -1072,7 +1107,7 @@ _achievements:
description: "プロフィールを設定した" description: "プロフィールを設定した"
_markedAsCat: _markedAsCat:
title: "吾輩は猫やねん" title: "吾輩は猫やねん"
description: "アカウントがCatになってもうた" description: "アカウントをCatにしたった"
flavor: "名前はまだないねん。" flavor: "名前はまだないねん。"
_following1: _following1:
title: "はじめてのフォロー" title: "はじめてのフォロー"
@ -1119,7 +1154,7 @@ _achievements:
_iLoveMisskey: _iLoveMisskey:
title: "Misskey好きやねん" title: "Misskey好きやねん"
description: "\"I ❤ #Misskey\"を投稿した" description: "\"I ❤ #Misskey\"を投稿した"
flavor: "Misskeyを使ってくれてありがとう by 開発チーム" flavor: "Misskeyを使ってくれておおきに by 開発チーム"
_foundTreasure: _foundTreasure:
title: "なんでも鑑定団" title: "なんでも鑑定団"
description: "隠されたお宝を発見した" description: "隠されたお宝を発見した"
@ -1145,7 +1180,7 @@ _achievements:
description: "ホームタイムラインの流速が20npmを超す" description: "ホームタイムラインの流速が20npmを超す"
_viewInstanceChart: _viewInstanceChart:
title: "アナリスト" title: "アナリスト"
description: "インスタンスのチャートを表示した" description: "サーバーのチャートを表示した"
_outputHelloWorldOnScratchpad: _outputHelloWorldOnScratchpad:
title: "Hello, world!" title: "Hello, world!"
description: "スクラッチパッドで hello worldを出力した" description: "スクラッチパッドで hello worldを出力した"
@ -1182,7 +1217,7 @@ _achievements:
_loggedInOnNewYearsDay: _loggedInOnNewYearsDay:
title: "あけましておめでとうございます!" title: "あけましておめでとうございます!"
description: "元旦にログインした" description: "元旦にログインした"
flavor: "今年も弊インスタンスをよろしくお願いします" flavor: "今年も弊サーバーをよろしゅう頼みますわ"
_cookieClicked: _cookieClicked:
title: "クッキー叩くやつ" title: "クッキー叩くやつ"
description: "クッキー叩いてもうた" description: "クッキー叩いてもうた"
@ -1197,8 +1232,8 @@ _role:
name: "ロール名" name: "ロール名"
description: "ロールの説明" description: "ロールの説明"
permission: "ロールの権限" permission: "ロールの権限"
descriptionOfPermission: "<b>モデレーター</b>は基本的なモデレーションに関わる操作を行えるで。\n<b>管理者</b>はインスタンスの全ての設定を変更できるで。" descriptionOfPermission: "<b>モデレーター</b>は基本的なモデレーションに関わる操作を行えるで。\n<b>管理者</b>はサーバーの全ての設定を変更できるで。"
assignTarget: "アサインターゲット" assignTarget: "アサイン"
descriptionOfAssignTarget: "<b>マニュアル</b>は誰がこのロールに含まれてるかを手動で管理するで。\n<b>コンディショナル</b>は条件を設定して、それに合うユーザーが自動で含まれるようになるで。" descriptionOfAssignTarget: "<b>マニュアル</b>は誰がこのロールに含まれてるかを手動で管理するで。\n<b>コンディショナル</b>は条件を設定して、それに合うユーザーが自動で含まれるようになるで。"
manual: "マニュアル" manual: "マニュアル"
conditional: "コンディショナル" conditional: "コンディショナル"
@ -1214,6 +1249,8 @@ _role:
iconUrl: "アイコン画像のURL" iconUrl: "アイコン画像のURL"
asBadge: "バッジとして見せる" asBadge: "バッジとして見せる"
descriptionOfAsBadge: "オンにすると、ユーザー名の横んとこにロールのアイコンが表示されるで。" descriptionOfAsBadge: "オンにすると、ユーザー名の横んとこにロールのアイコンが表示されるで。"
displayOrder: "表示順"
descriptionOfDisplayOrder: "数がでかいほど、UI上で先に表示されるで。"
canEditMembersByModerator: "モデレーターのメンバー編集を許可" canEditMembersByModerator: "モデレーターのメンバー編集を許可"
descriptionOfCanEditMembersByModerator: "オンにすると、管理者に加えてモデレーターもこのロールへユーザーをアサイン/アサイン解除できるようになるで。オフにすると管理者のみが行えるで。" descriptionOfCanEditMembersByModerator: "オンにすると、管理者に加えてモデレーターもこのロールへユーザーをアサイン/アサイン解除できるようになるで。オフにすると管理者のみが行えるで。"
priority: "優先度" priority: "優先度"
@ -1225,7 +1262,7 @@ _role:
gtlAvailable: "グローバルタイムラインの閲覧" gtlAvailable: "グローバルタイムラインの閲覧"
ltlAvailable: "ローカルタイムラインの閲覧" ltlAvailable: "ローカルタイムラインの閲覧"
canPublicNote: "パブリック投稿の許可" canPublicNote: "パブリック投稿の許可"
canInvite: "インスタンス招待コードの発行" canInvite: "サーバー招待コードの発行"
canManageCustomEmojis: "カスタム絵文字の管理" canManageCustomEmojis: "カスタム絵文字の管理"
driveCapacity: "ドライブ容量" driveCapacity: "ドライブ容量"
pinMax: "ノートのピン留めの最大数" pinMax: "ノートのピン留めの最大数"
@ -1237,8 +1274,9 @@ _role:
userListMax: "ユーザーリストの作成可能数" userListMax: "ユーザーリストの作成可能数"
userEachUserListsMax: "ユーザーリスト内のユーザーの最大数" userEachUserListsMax: "ユーザーリスト内のユーザーの最大数"
rateLimitFactor: "レートリミット" rateLimitFactor: "レートリミット"
descriptionOfRateLimitFactor: "ちっちゃいほど制限が緩なって、大きいほど制限されるで。" descriptionOfRateLimitFactor: "ちっちゃいほど制限が緩なって、大きいほど制限されるで。"
canHideAds: "広告を表示させへん" canHideAds: "広告を表示させへん"
canSearchNotes: "ノート検索を使わすかどうか"
_condition: _condition:
isLocal: "ローカルユーザー" isLocal: "ローカルユーザー"
isRemote: "リモートユーザー" isRemote: "リモートユーザー"
@ -1248,6 +1286,8 @@ _role:
followersMoreThanOrEq: "フォロワー数が~以上" followersMoreThanOrEq: "フォロワー数が~以上"
followingLessThanOrEq: "フォロー数が~以下" followingLessThanOrEq: "フォロー数が~以下"
followingMoreThanOrEq: "フォロー数が~以上" followingMoreThanOrEq: "フォロー数が~以上"
notesLessThanOrEq: "投稿数が~以下しかない"
notesMoreThanOrEq: "投稿を~以上しとる"
and: "~かつ~" and: "~かつ~"
or: "~または~" or: "~または~"
not: "~ではない" not: "~ではない"
@ -1287,7 +1327,7 @@ _ad:
_forgotPassword: _forgotPassword:
enterEmail: "アカウントに登録したメールアドレスをここに入力してや。そのアドレス宛に、パスワードリセット用のリンクが送られるから待っててな~。" enterEmail: "アカウントに登録したメールアドレスをここに入力してや。そのアドレス宛に、パスワードリセット用のリンクが送られるから待っててな~。"
ifNoEmail: "メールアドレスを登録してへんのやったら、管理者まで教えてな~。" ifNoEmail: "メールアドレスを登録してへんのやったら、管理者まで教えてな~。"
contactAdmin: "このインスタンスはメールに対応してへんから、パスワードリセットをしたいときは管理者まで教えてな~。" contactAdmin: "このサーバーはメールに対応してへんから、パスワードリセットをしたいときは管理者まで教えてな~。"
_gallery: _gallery:
my: "あんたの投稿" my: "あんたの投稿"
liked: "いいねした投稿" liked: "いいねした投稿"
@ -1372,10 +1412,10 @@ _wordMute:
hard: "ハード" hard: "ハード"
mutedNotes: "ミュートされたノート" mutedNotes: "ミュートされたノート"
_instanceMute: _instanceMute:
instanceMuteDescription: "ミュートしたインスタンスのユーザーへの返信を含めて、設定したインスタンスの全てのートとRenoteをミュートにするで。" instanceMuteDescription: "ミュートしたサーバーのユーザーへの返信を含めて、設定したインスタンスの全てのートとRenoteをミュートにするで。"
instanceMuteDescription2: "改行で区切って設定するで" instanceMuteDescription2: "改行で区切って設定するんやで"
title: "設定したインスタンスのノートを隠すで。" title: "設定したサーバーのノートを隠すで。"
heading: "ミュートするインスタンス" heading: "ミュートするサーバー"
_theme: _theme:
explore: "テーマを探す" explore: "テーマを探す"
install: "テーマのインストール" install: "テーマのインストール"
@ -1460,7 +1500,7 @@ _sfx:
channel: "チャンネル通知" channel: "チャンネル通知"
_ago: _ago:
future: "未来" future: "未来"
justNow: "たった今" justNow: "ついさっき"
secondsAgo: "{n}秒前" secondsAgo: "{n}秒前"
minutesAgo: "{n}分前" minutesAgo: "{n}分前"
hoursAgo: "{n}時間前" hoursAgo: "{n}時間前"
@ -1583,7 +1623,7 @@ _weekday:
saturday: "土曜日" saturday: "土曜日"
_widgets: _widgets:
profile: "プロフィール" profile: "プロフィール"
instanceInfo: "インスタンス情報" instanceInfo: "サーバー情報"
memo: "付箋" memo: "付箋"
notifications: "通知" notifications: "通知"
timeline: "タイムライン" timeline: "タイムライン"
@ -1597,7 +1637,7 @@ _widgets:
digitalClock: "デジタル時計" digitalClock: "デジタル時計"
unixClock: "UNIX時計" unixClock: "UNIX時計"
federation: "連合" federation: "連合"
instanceCloud: "インスタンスクラウド" instanceCloud: "サーバークラウド"
postForm: "投稿フォーム" postForm: "投稿フォーム"
slideshow: "スライドショー" slideshow: "スライドショー"
button: "ボタン" button: "ボタン"
@ -1648,7 +1688,7 @@ _visibility:
specified: "ダイレクト" specified: "ダイレクト"
specifiedDescription: "選んだユーザーのみに公開するで" specifiedDescription: "選んだユーザーのみに公開するで"
disableFederation: "連合なし" disableFederation: "連合なし"
disableFederationDescription: "他インスタンスへは送らんとくわ" disableFederationDescription: "他サーバーへは送らんとくわ"
_postForm: _postForm:
replyPlaceholder: "このノートに返信..." replyPlaceholder: "このノートに返信..."
quotePlaceholder: "このノートを引用..." quotePlaceholder: "このノートを引用..."
@ -1686,7 +1726,7 @@ _charts:
apRequest: "リクエスト" apRequest: "リクエスト"
usersIncDec: "ユーザーの増減" usersIncDec: "ユーザーの増減"
usersTotal: "ユーザーの合計" usersTotal: "ユーザーの合計"
activeUsers: "アクティブユーザー数" activeUsers: "いまおるユーザー数"
notesIncDec: "ノートの増減" notesIncDec: "ノートの増減"
localNotesIncDec: "ローカルのノートの増減" localNotesIncDec: "ローカルのノートの増減"
remoteNotesIncDec: "リモートのノートの増減" remoteNotesIncDec: "リモートのノートの増減"
@ -1840,3 +1880,23 @@ _deck:
_dialog: _dialog:
charactersExceeded: "最大の文字数を上回っとるで!今は {current} / 最大でも {max}" charactersExceeded: "最大の文字数を上回っとるで!今は {current} / 最大でも {max}"
charactersBelow: "最小の文字数を下回っとるで!今は {current} / 最低でも {min}" charactersBelow: "最小の文字数を下回っとるで!今は {current} / 最低でも {min}"
_disabledTimeline:
title: "使われへんタイムライン"
description: "あんたの今のロールやったら、このタイムラインは使われへんで。"
_drivecleaner:
orderBySizeDesc: "サイズのでかい順"
orderByCreatedAtAsc: "追加日の古い順"
_webhookSettings:
createWebhook: "Webhookをつくる"
name: "名前"
secret: "シークレット"
events: "Webhookを投げるタイミング"
active: "有効"
_events:
follow: "フォローしたとき~!"
followed: "フォローもらったとき~!"
note: "ノートを投稿したとき~!"
reply: "返信があるとき~!"
renote: "Renoteされるとき"
reaction: "リアクションがあるとき~!"
mention: "メンションがあるとき~!"

View File

@ -122,6 +122,8 @@ unmarkAsSensitive: "열람주의 해제"
enterFileName: "파일명을 입력" enterFileName: "파일명을 입력"
mute: "뮤트" mute: "뮤트"
unmute: "뮤트 해제" unmute: "뮤트 해제"
renoteMute: "리노트를 뮤트"
renoteUnmute: "리노트 뮤트 해제"
block: "차단" block: "차단"
unblock: "차단 해제" unblock: "차단 해제"
suspend: "정지" suspend: "정지"
@ -153,6 +155,7 @@ flagShowTimelineReplies: "타임라인에 노트의 답글을 표시하기"
flagShowTimelineRepliesDescription: "이 설정을 활성화하면 타임라인에 다른 유저 간의 답글을 표시합니다." flagShowTimelineRepliesDescription: "이 설정을 활성화하면 타임라인에 다른 유저 간의 답글을 표시합니다."
autoAcceptFollowed: "팔로우 중인 유저로부터의 팔로우 요청을 자동 수락" autoAcceptFollowed: "팔로우 중인 유저로부터의 팔로우 요청을 자동 수락"
addAccount: "계정 추가" addAccount: "계정 추가"
reloadAccountsList: "계정 리스트 정보 갱신"
loginFailed: "로그인에 실패했습니다" loginFailed: "로그인에 실패했습니다"
showOnRemote: "리모트에서 보기" showOnRemote: "리모트에서 보기"
general: "일반" general: "일반"
@ -506,6 +509,7 @@ objectStorageSetPublicRead: "업로드할 때 'public-read'를 설정하기"
serverLogs: "서버 로그" serverLogs: "서버 로그"
deleteAll: "모두 삭제" deleteAll: "모두 삭제"
showFixedPostForm: "타임라인 상단에 글 작성란을 표시" showFixedPostForm: "타임라인 상단에 글 작성란을 표시"
showFixedPostFormInChannel: "채널 타임라인 상단에 글 작성란을 표시"
newNoteRecived: "새 노트가 있습니다" newNoteRecived: "새 노트가 있습니다"
sounds: "소리" sounds: "소리"
sound: "소리" sound: "소리"
@ -543,6 +547,8 @@ userSuspended: "이 계정은 정지된 상태입니다."
userSilenced: "이 계정은 사일런스된 상태입니다." userSilenced: "이 계정은 사일런스된 상태입니다."
yourAccountSuspendedTitle: "계정이 정지되었습니다" yourAccountSuspendedTitle: "계정이 정지되었습니다"
yourAccountSuspendedDescription: "이 계정은 서버의 이용 약관을 위반하거나, 기타 다른 이유로 인해 정지되었습니다. 자세한 사항은 관리자에게 문의해 주십시오. 계정을 새로 생성하지 마십시오." yourAccountSuspendedDescription: "이 계정은 서버의 이용 약관을 위반하거나, 기타 다른 이유로 인해 정지되었습니다. 자세한 사항은 관리자에게 문의해 주십시오. 계정을 새로 생성하지 마십시오."
accountDeleted: "계정이 정지되었습니다"
accountDeletedDescription: "이 계정이 삭제되었습니다."
menu: "메뉴" menu: "메뉴"
divider: "구분선" divider: "구분선"
addItem: "항목 추가" addItem: "항목 추가"
@ -586,7 +592,6 @@ tokenRequested: "계정 접근 허용"
pluginTokenRequestedDescription: "이 플러그인은 여기서 설정한 권한을 사용할 수 있게 됩니다." pluginTokenRequestedDescription: "이 플러그인은 여기서 설정한 권한을 사용할 수 있게 됩니다."
notificationType: "알림 유형" notificationType: "알림 유형"
edit: "편집" edit: "편집"
useStarForReactionFallback: "알 수 없는 리액션 이모지 대신 ★ 사용"
emailServer: "메일 서버" emailServer: "메일 서버"
enableEmail: "이메일 송신 기능 활성화" enableEmail: "이메일 송신 기능 활성화"
emailConfigInfo: "가입 시 메일 주소 확인이나 비밀번호 초기화 시에 사용합니다." emailConfigInfo: "가입 시 메일 주소 확인이나 비밀번호 초기화 시에 사용합니다."
@ -953,8 +958,10 @@ copyErrorInfo: "오류 정보 복사"
joinThisServer: "이 서버에 가입" joinThisServer: "이 서버에 가입"
exploreOtherServers: "다른 서버 둘러보기" exploreOtherServers: "다른 서버 둘러보기"
letsLookAtTimeline: "타임라인 구경하기" letsLookAtTimeline: "타임라인 구경하기"
disableFederationWarn: "연합이 비활성화됩니다. 비활성화해도 게시물이 비공개가 되지는 않습니다. 대부분의 경우 이 옵션을 활성화할 필요가 없습니다."
invitationRequiredToRegister: "현재 이 서버는 비공개입니다. 회원가입을 하시려면 초대 코드가 필요합니다." invitationRequiredToRegister: "현재 이 서버는 비공개입니다. 회원가입을 하시려면 초대 코드가 필요합니다."
emailNotSupported: "이 서버에서는 메일 전송을 지원하지 않습니다"
postToTheChannel: "채널에 게시하기"
cannotBeChangedLater: "나중에 변경할 수 없습니다."
_achievements: _achievements:
earnedAt: "달성 일시" earnedAt: "달성 일시"
_types: _types:
@ -1840,3 +1847,6 @@ _deck:
_dialog: _dialog:
charactersExceeded: "최대 글자수를 초과하였습니다! 현재 {current} / 최대 {min}" charactersExceeded: "최대 글자수를 초과하였습니다! 현재 {current} / 최대 {min}"
charactersBelow: "최소 글자수 미만입니다! 현재 {current} / 최소 {min}" charactersBelow: "최소 글자수 미만입니다! 현재 {current} / 최소 {min}"
_webhookSettings:
name: "이름"
active: "활성화"

View File

@ -168,7 +168,9 @@ done: "ສຳເລັດ"
processing: "ກຳລັງປະມວນຜົນ" processing: "ກຳລັງປະມວນຜົນ"
preview: "ສະແດງເປັນຕົວຢ່າງ" preview: "ສະແດງເປັນຕົວຢ່າງ"
default: "ຄ່າເລີ່ມຕົ້ນ" default: "ຄ່າເລີ່ມຕົ້ນ"
federating: "ສະຫະພັນ"
blocked: "ບລັອກແລ້ວ " blocked: "ບລັອກແລ້ວ "
suspended: "ໂຈະ"
all: "ທັງໝົດ" all: "ທັງໝົດ"
subscribing: "ສະໝັກສະມາຊິກແລັວ" subscribing: "ສະໝັກສະມາຊິກແລັວ"
publishing: "ການ​ພິມ​ເຜີຍ​ແຜ່" publishing: "ການ​ພິມ​ເຜີຍ​ແຜ່"
@ -177,15 +179,35 @@ instanceFollowing: "ກຳລັງຕິດຕາມສຸດຕົວຢ່າ
instanceFollowers: "ຜູ້ຕິດຕາມຕົວຢ່າງ" instanceFollowers: "ຜູ້ຕິດຕາມຕົວຢ່າງ"
instanceUsers: "ຜູ້​ຊົມ​ໃຊ້​ຂອງ​ຕົວ​ຢ່າງ​ນີ້​" instanceUsers: "ຜູ້​ຊົມ​ໃຊ້​ຂອງ​ຕົວ​ຢ່າງ​ນີ້​"
changePassword: "ປ່ຽນ​ລະ​ຫັດ​ຜ່ານ" changePassword: "ປ່ຽນ​ລະ​ຫັດ​ຜ່ານ"
security: "ຄວາມປອດໄພ"
retypedNotMatch: "ວັດສະດຸປ້ອນບໍ່ກົງກັນ"
currentPassword: "ລະຫັດຜ່ານປະຈຸບັນ"
more: "ເພີ່ມເຕີມ!"
featured: "ໄຮໄລທ໌" featured: "ໄຮໄລທ໌"
usernameOrUserId: "ຊື່ຜູ້ໃຊ້ ຫຼື id ຜູ້ໃຊ້"
noSuchUser: "ບໍ່ພົບຜູ້ໃຊ້"
lookup: "ຄົ້ນ​ຫາ"
announcements: "ປະກາດ" announcements: "ປະກາດ"
imageUrl: "URL ຮູບພາບ"
remove: "ລຶບ" remove: "ລຶບ"
removed: "ລຶບແລ້ວ"
resetAreYouSure: "ຣີ​ເຊັດບໍ?"
saved: "ບັນທຶກແລ້ວ"
messaging: "ແຊ໋ດ" messaging: "ແຊ໋ດ"
upload: "ອັບໂຫຼດ"
keepOriginalUploading: "ຮັກສາຮູບພາບຕົ້ນສະບັບ"
fromUrl: "ຈາກ URL"
uploadFromUrl: "ອັບໂຫຼດຈາກ URL"
uploadFromUrlDescription: "URL ຂອງໄຟລ໌ທີ່ທ່ານຕ້ອງການອັບໂຫລດ"
messageRead: "ອ່ານແລ້ວ"
startMessaging: "ເລີ່ມການສົນທະນາໃໝ່"
nUsersRead: "ອ່ານໂດຍ {n}"
tos: "ເງື່ອນໄຂການໃຫ້ບໍລິການ" tos: "ເງື່ອນໄຂການໃຫ້ບໍລິການ"
start: "ເລີ່ມຕົ້ນນຳໃຊ້ເລີຍ" start: "ເລີ່ມຕົ້ນນຳໃຊ້ເລີຍ"
home: "ໜ້າຫຼັກ" home: "ໜ້າຫຼັກ"
images: "ຮູບພາບ" images: "ຮູບພາບ"
birthday: "ວັນເກີດ" birthday: "ວັນເກີດ"
yearsOld: "{age} ປີ"
registeredDate: "ວັນທີ່ເປັນສະມາຊິກ" registeredDate: "ວັນທີ່ເປັນສະມາຊິກ"
location: "ທີ່ຕັ້ງ" location: "ທີ່ຕັ້ງ"
theme: "ແທ໋ມ" theme: "ແທ໋ມ"
@ -193,17 +215,96 @@ light: "ສະຫວ່າງ"
dark: "ມືດ" dark: "ມືດ"
lightThemes: "ຊຸດຮູບແບບສະຫວ່າງ" lightThemes: "ຊຸດຮູບແບບສະຫວ່າງ"
darkThemes: "ຮູບແບບສີສັນມືດ" darkThemes: "ຮູບແບບສີສັນມືດ"
drive: "ຂັບ"
fileName: "ຊື່ໄຟລ໌" fileName: "ຊື່ໄຟລ໌"
selectFile: "ເລືອກໄຟລ໌" selectFile: "ເລືອກໄຟລ໌"
selectFiles: "ເລືອກໄຟລ໌" selectFiles: "ເລືອກໄຟລ໌"
selectFolder: "ເລືອກໂຟລເດີ"
selectFolders: "ເລືອກໂຟລເດີ"
renameFile: "ປ່ຽນຊື່ໄຟລ໌"
folderName: "ຊື່ໂຟນເດີ"
createFolder: "​ສ້າງ​ໂຟ​ລ​ເດີ"
renameFolder: "ປ່ຽນຊື່ໂຟນເດີນີ້"
deleteFolder: "ລົບໂຟ​ລ​ເດີ​"
addFile: "ເພີ່ມໄຟລ໌"
emptyDrive: "Drive ຂອງທ່ານຫວ່າງເປົ່າ"
emptyFolder: "ໂຟນເດີນີ້ເປົ່າຫວ່າງ"
unableToDelete: "ບໍ່​ສາ​ມາດລົບໄດ້"
inputNewFileName: "ໃສ່ຊື່ໄຟລ໌ໃໝ່"
inputNewDescription: "ໃສ່ຄຳບັນຍາຍໃໝ່"
inputNewFolderName: "ໃສ່ຊື່ໂຟນເດີໃໝ່"
circularReferenceFolder: "ໂຟນເດີປາຍທາງແມ່ນໂຟນເດີຍ່ອຍຂອງໂຟນເດີທີ່ທ່ານຕ້ອງການຍ້າຍ"
rename: "ປ່ຽນຊື່"
nsfw: "NSFW" nsfw: "NSFW"
watch: "ເບິ່ງ"
unwatch: "ຢຸດເບິ່ງ"
accept: "ອະນຸຍາດ" accept: "ອະນຸຍາດ"
reject: "ປະຕິເສດ"
normal: "ປົກກະຕິ"
instanceName: "ຊື່ເຊີເວີ້"
instanceDescription: "ຄໍາອະທິບາຍຕົວຢ່າງ"
maintainerName: "ຜູ້ດູແລ"
maintainerEmail: "ອີເມວ admin"
tosUrl: "ເງື່ອນໄຂການໃຫ້ບໍລິການ URL"
thisYear: "ປີນີ້"
thisMonth: "ເດືອນນີ້"
today: "ມື້ນີ້"
dayX: "ວັນ {day}"
monthX: "ເດືອນ {month}"
yearX: "ປີ {year}"
pages: "ໜ້າ"
integration: "ຄວາມສຳພັນຂອງ"
connectService: "ເຊື່ອມຕໍ່"
disconnectService: "ຕັດການເຊື່ອມຕໍ່"
enableLocalTimeline: "ເປີດໃຊ້ທາມລາຍທ້ອງຖິ່ນ"
enableGlobalTimeline: "ເປີດໃຊ້ທາມລາຍທົ່ວໂລກ"
disablingTimelinesInfo: "ຜູ້ເບິ່ງແຍງລະບົບ ແລະຜູ້ຄວບຄຸມຈະມີການເຂົ້າເຖິງທຸກກຳນົດເວລາ, ເຖິງແມ່ນວ່າຈະບໍ່ໄດ້ເປີດໃຊ້ງານກໍຕາມ"
registration: "ລົງທະບຽນ"
enableRegistration: "ເປີດໃຊ້ການລົງທະບຽນຜູ້ໃຊ້ໃໝ່"
invite: "ເຊີນ"
driveCapacityPerLocalAccount: "ຄວາມອາດສາມາດຂັບຕໍ່ຜູ້ໃຊ້ທ້ອງຖິ່ນ"
driveCapacityPerRemoteAccount: "ໄດຣຟ໌ຄວາມອາດສາມາດຕໍ່ຜູ້ໃຊ້ທາງໄກ"
pinnedNotes: "ບັນທຶກທີ່ປັກໝຸດໄວ້" pinnedNotes: "ບັນທຶກທີ່ປັກໝຸດໄວ້"
userList: "ລາຍການ" userList: "ລາຍການ"
about: "ກ່ຽວກັບ"
aboutMisskey: "ກ່ຽວກັບ Misskey"
administrator: "ຜູ້ບໍລິຫານ"
share: "ແບ່ງປັນ"
notFound: "ບໍ່ພົບ"
cacheClear: "ລຶບລ້າງແຄສ"
invites: "ເຊີນ"
title: "ຫົວຂໍ້"
text: "ຂໍ້ຄວາມ"
enable: "ເປີດໃຊ້"
next: "ຕໍ່ໄປ"
invitations: "ເຊີນ"
language: "ພາສາ"
native: "ພາ​ສາ​ແມ່"
category: "ຫມວດຫມູ່"
tags: "ແທ໋ກ"
createAccount: "ສ້າງບັນຊີ"
existingAccount: "ທີ່ມີຢູ່"
dashboard: "ໜ້າປັດ"
local: "ທ້ອງຖິ່ນ"
objectStorageRegion: "ພາກ​ພື້ນ"
sounds: "ສຽງ"
sound: "ສຽງ"
none: "ບໍ່ມີ"
volume: "ລະດັບສຽງ"
details: "ລາຍລະອຽດ"
install: "ຕິດຕັ້ງ"
uninstall: "ຖອນການຕິດຕັ້ງ"
state: "ສະຖານະ"
sort: "ຈັດຮຽງໂດຍ"
ascendingOrder: "ນ້ອຍໄປຫາໃຫຍ່"
descendingOrder: "ໃຫຍ່ຫານ້ອຍ"
output: "ຜົນຜະລິດ"
script: "ບົດ​ຄວາມ"
smtpHost: "ໂຮດສ" smtpHost: "ໂຮດສ"
smtpUser: "ຊື່ຜູ້ໃຊ້" smtpUser: "ຊື່ຜູ້ໃຊ້"
smtpPass: "ລະຫັດຜ່ານ" smtpPass: "ລະຫັດຜ່ານ"
clearCache: "ລຶບລ້າງແຄສ" clearCache: "ລຶບລ້າງແຄສ"
info: "ກ່ຽວກັບ"
user: "ຜູ້ໃຊ້ຕ່າງໆ" user: "ຜູ້ໃຊ້ຕ່າງໆ"
searchByGoogle: "ຄົ້ນຫາ" searchByGoogle: "ຄົ້ນຫາ"
file: "ໄຟລ໌" file: "ໄຟລ໌"
@ -244,6 +345,8 @@ _charts:
federation: "ສະຫະພັນ" federation: "ສະຫະພັນ"
_timelines: _timelines:
home: "ໜ້າຫຼັກ" home: "ໜ້າຫຼັກ"
_play:
script: "ບົດ​ຄວາມ"
_pages: _pages:
blocks: blocks:
image: "ຮູບພາບ" image: "ຮູບພາບ"

View File

@ -483,3 +483,5 @@ _deck:
antenna: "Antennes" antenna: "Antennes"
list: "Lijsten" list: "Lijsten"
mentions: "Vermeldingen" mentions: "Vermeldingen"
_webhookSettings:
name: "Naam"

View File

@ -129,6 +129,7 @@ unblockConfirm: "Czy na pewno chcesz odblokować to konto?"
suspendConfirm: "Czy na pewno chcesz zawiesić to konto?" suspendConfirm: "Czy na pewno chcesz zawiesić to konto?"
unsuspendConfirm: "Czy na pewno chcesz cofnąć zawieszenie tego konta?" unsuspendConfirm: "Czy na pewno chcesz cofnąć zawieszenie tego konta?"
selectList: "Wybierz listę" selectList: "Wybierz listę"
selectChannel: "Wybierz kanał"
selectAntenna: "Wybierz Antennę" selectAntenna: "Wybierz Antennę"
selectWidget: "Wybierz widżet" selectWidget: "Wybierz widżet"
editWidgets: "Edytuj widżety" editWidgets: "Edytuj widżety"
@ -149,6 +150,7 @@ flagAsCatDescription: "Przełącz tę opcję, aby konto było oznaczone jako kot
flagShowTimelineReplies: "Pokazuj odpowiedzi na osi czasu" flagShowTimelineReplies: "Pokazuj odpowiedzi na osi czasu"
autoAcceptFollowed: "Automatycznie przyjmuj prośby o możliwość obserwacji od użytkowników, których obserwujesz" autoAcceptFollowed: "Automatycznie przyjmuj prośby o możliwość obserwacji od użytkowników, których obserwujesz"
addAccount: "Dodaj konto" addAccount: "Dodaj konto"
reloadAccountsList: "Odśwież listę kont"
loginFailed: "Nie udało się zalogować" loginFailed: "Nie udało się zalogować"
showOnRemote: "Zobacz na zdalnej instancji" showOnRemote: "Zobacz na zdalnej instancji"
general: "Ogólne" general: "Ogólne"
@ -159,6 +161,7 @@ searchWith: "Szukaj: {q}"
youHaveNoLists: "Nie masz żadnej listy" youHaveNoLists: "Nie masz żadnej listy"
followConfirm: "Czy na pewno chcesz zaobserwować {name}?" followConfirm: "Czy na pewno chcesz zaobserwować {name}?"
proxyAccount: "Konto proxy" proxyAccount: "Konto proxy"
proxyAccountDescription: "Opis konta pełnomocniczego"
host: "Host" host: "Host"
selectUser: "Wybierz użytkownika" selectUser: "Wybierz użytkownika"
recipient: "Odbiorca" recipient: "Odbiorca"
@ -253,6 +256,7 @@ noMoreHistory: "Nie ma dalszej historii"
startMessaging: "Rozpocznij czat" startMessaging: "Rozpocznij czat"
nUsersRead: "przeczytano przez {n}" nUsersRead: "przeczytano przez {n}"
agreeTo: "Wyrażam zgodę na {0}" agreeTo: "Wyrażam zgodę na {0}"
agreeBelow: "Zaakceptuj poniżej"
tos: "Regulamin" tos: "Regulamin"
start: "Rozpocznij" start: "Rozpocznij"
home: "Strona główna" home: "Strona główna"
@ -385,13 +389,19 @@ about: "Informacje"
aboutMisskey: "O Misskey" aboutMisskey: "O Misskey"
administrator: "Admin" administrator: "Admin"
token: "Token" token: "Token"
2fa: "Klucz 2FA "
totp: "Klucz aplikacji uwierzytelniającej (totp)"
totpDescription: "Opis klucza czasowego"
moderator: "Moderator" moderator: "Moderator"
moderation: "Moderacja" moderation: "Moderacja"
nUsersMentioned: "{n} wspomnianych użytkowników" nUsersMentioned: "{n} wspomnianych użytkowników"
securityKeyAndPasskey: "Klucz bezpieczeństwa i klucze Passkey"
securityKey: "Klucz bezpieczeństwa" securityKey: "Klucz bezpieczeństwa"
lastUsed: "Ostatnio używane" lastUsed: "Ostatnio używane"
lastUsedAt: "Ostatnio używane w"
unregister: "Cofnij rejestrację" unregister: "Cofnij rejestrację"
passwordLessLogin: "Skonfiguruj logowanie bez użycia hasła" passwordLessLogin: "Skonfiguruj logowanie bez użycia hasła"
passwordLessLoginDescription: "Opis logowania bez użycia hasła"
resetPassword: "Zresetuj hasło" resetPassword: "Zresetuj hasło"
newPasswordIs: "Nowe hasło to „{password}”" newPasswordIs: "Nowe hasło to „{password}”"
reduceUiAnimation: "Ogranicz animacje w UI" reduceUiAnimation: "Ogranicz animacje w UI"
@ -518,11 +528,16 @@ disablePagesScript: "Wyłącz AiScript na Stronach"
updateRemoteUser: "Aktualizuj zdalne dane o użytkowniku" updateRemoteUser: "Aktualizuj zdalne dane o użytkowniku"
deleteAllFiles: "Usuń wszystkie pliki" deleteAllFiles: "Usuń wszystkie pliki"
deleteAllFilesConfirm: "Czy na pewno chcesz usunąć wszystkie pliki?" deleteAllFilesConfirm: "Czy na pewno chcesz usunąć wszystkie pliki?"
removeAllFollowing: "Przestań obserwować"
removeAllFollowingDescription: "Przestań obserwować wszystkie konta z {host}. Wykonaj to, jeżeli instancja już nie istnieje." removeAllFollowingDescription: "Przestań obserwować wszystkie konta z {host}. Wykonaj to, jeżeli instancja już nie istnieje."
userSuspended: "To konto zostało zawieszone." userSuspended: "To konto zostało zawieszone."
userSilenced: "Ten użytkownik został wyciszony." userSilenced: "Ten użytkownik został wyciszony."
yourAccountSuspendedTitle: "To konto jest zawieszone" yourAccountSuspendedTitle: "To konto jest zawieszone"
yourAccountSuspendedDescription: "To konto zostało zawieszone z powodu złamania regulaminu serwera lub innych podobnych. Skontaktuj się z administratorem, jeśli chciałbyś poznać bardziej szczegółowy powód. Proszę nie zakładać nowego konta." yourAccountSuspendedDescription: "To konto zostało zawieszone z powodu złamania regulaminu serwera lub innych podobnych. Skontaktuj się z administratorem, jeśli chciałbyś poznać bardziej szczegółowy powód. Proszę nie zakładać nowego konta."
tokenRevoked: "Token odrzucony"
tokenRevokedDescription: "Opis odrzuconego tokena"
accountDeleted: "Konto usunięte"
accountDeletedDescription: "Opis konta usuniętego"
menu: "Menu" menu: "Menu"
divider: "Rozdzielacz" divider: "Rozdzielacz"
addItem: "Dodaj element" addItem: "Dodaj element"
@ -548,7 +563,9 @@ author: "Autor"
leaveConfirm: "Są niezapisane zmiany. Czy chcesz je odrzucić?" leaveConfirm: "Są niezapisane zmiany. Czy chcesz je odrzucić?"
manage: "Zarządzanie" manage: "Zarządzanie"
plugins: "Wtyczki" plugins: "Wtyczki"
preferencesBackups: "Kopia zapasowa ustawień"
deck: "Tablica" deck: "Tablica"
undeck: "oddkouj"
useBlurEffectForModal: "Używaj efektu rozmycia w modalach" useBlurEffectForModal: "Używaj efektu rozmycia w modalach"
useFullReactionPicker: "Używaj pełnowymiarowego wybornika reakcji" useFullReactionPicker: "Używaj pełnowymiarowego wybornika reakcji"
width: "Szerokość" width: "Szerokość"
@ -564,7 +581,6 @@ tokenRequested: "Przydziel dostęp do konta"
pluginTokenRequestedDescription: "Ta wtyczka będzie mogła korzystać z ustawionych tu uprawnień." pluginTokenRequestedDescription: "Ta wtyczka będzie mogła korzystać z ustawionych tu uprawnień."
notificationType: "Rodzaj powiadomień" notificationType: "Rodzaj powiadomień"
edit: "Edytuj" edit: "Edytuj"
useStarForReactionFallback: "Użyj ★ jako zapasowego emoji, gdy emoji reakcji jest nieznane"
emailServer: "Serwer poczty e-mail" emailServer: "Serwer poczty e-mail"
enableEmail: "Włącz dostarczanie wiadomości e-mail" enableEmail: "Włącz dostarczanie wiadomości e-mail"
emailConfigInfo: "Wykorzystywany do potwierdzenia adresu e-mail w trakcie rejestracji, lub gdy zapomnisz hasła" emailConfigInfo: "Wykorzystywany do potwierdzenia adresu e-mail w trakcie rejestracji, lub gdy zapomnisz hasła"
@ -816,6 +832,8 @@ tenMinutes: "10 minut"
oneHour: "1 godzina" oneHour: "1 godzina"
oneDay: "1 dzień" oneDay: "1 dzień"
oneWeek: "1 tydzień" oneWeek: "1 tydzień"
oneMonth: "jeden miesiąc"
failedToFetchAccountInformation: "Nie udało się uzyskać informacji o koncie"
file: "Pliki" file: "Pliki"
recommended: "Zalecane" recommended: "Zalecane"
check: "Zweryfikuj" check: "Zweryfikuj"
@ -1358,3 +1376,6 @@ _deck:
channel: "Kanały" channel: "Kanały"
mentions: "Wspomnienia" mentions: "Wspomnienia"
direct: "Bezpośredni" direct: "Bezpośredni"
_webhookSettings:
name: "Nazwa"
active: "Właczono"

View File

@ -555,3 +555,5 @@ _deck:
list: "Listas" list: "Listas"
mentions: "Menções" mentions: "Menções"
direct: "Notas diretas" direct: "Notas diretas"
_webhookSettings:
name: "Nome"

View File

@ -561,7 +561,6 @@ tokenRequested: "Acordă acces la cont"
pluginTokenRequestedDescription: "Acest plugin va putea să folosească permisiunile setate aici." pluginTokenRequestedDescription: "Acest plugin va putea să folosească permisiunile setate aici."
notificationType: "Tipul notificării" notificationType: "Tipul notificării"
edit: "Editează" edit: "Editează"
useStarForReactionFallback: "Folosește ★ ca fallback dacă emoji-ul este necunoscut"
emailServer: "Server email" emailServer: "Server email"
enableEmail: "Activează distribuția de emailuri" enableEmail: "Activează distribuția de emailuri"
emailConfigInfo: "Folosit pentru a confirma emailul tău în timpul logări dacă îți uiți parola" emailConfigInfo: "Folosit pentru a confirma emailul tău în timpul logări dacă îți uiți parola"
@ -702,3 +701,5 @@ _deck:
list: "Liste" list: "Liste"
channel: "Canale" channel: "Canale"
mentions: "Mențiuni" mentions: "Mențiuni"
_webhookSettings:
name: "Nume"

View File

@ -585,7 +585,6 @@ tokenRequested: "Открыть доступ к учётной записи"
pluginTokenRequestedDescription: "Это расширение сможет пользоваться разрешениями, установленными здесь." pluginTokenRequestedDescription: "Это расширение сможет пользоваться разрешениями, установленными здесь."
notificationType: "Тип уведомления" notificationType: "Тип уведомления"
edit: "Изменить" edit: "Изменить"
useStarForReactionFallback: "Ставить ★ в качестве реакции вместо неизвестного эмодзи"
emailServer: "Сервер электронной почты" emailServer: "Сервер электронной почты"
enableEmail: "Включить обмен электронной почтой" enableEmail: "Включить обмен электронной почтой"
emailConfigInfo: "Используется для подтверждения адреса электронной почты и сброса пароля." emailConfigInfo: "Используется для подтверждения адреса электронной почты и сброса пароля."
@ -951,7 +950,6 @@ copyErrorInfo: "Скопировать код ошибки"
joinThisServer: "Присоединяйтесь к этому серверу" joinThisServer: "Присоединяйтесь к этому серверу"
exploreOtherServers: "Искать другие сервера" exploreOtherServers: "Искать другие сервера"
letsLookAtTimeline: "Давайте посмотрим на ленту" letsLookAtTimeline: "Давайте посмотрим на ленту"
disableFederationWarn: "Объединение отключено. Если вы отключите это, сообщение не будет приватным. В большинстве случаев вам не нужно включать эту опцию."
_achievements: _achievements:
earnedAt: "Разблокировано в" earnedAt: "Разблокировано в"
_types: _types:
@ -1837,3 +1835,6 @@ _deck:
_dialog: _dialog:
charactersExceeded: "Превышено максимальное количество символов! У вас {current} / из {max}" charactersExceeded: "Превышено максимальное количество символов! У вас {current} / из {max}"
charactersBelow: "Это ниже минимального количества символов! У вас {current} / из {min}" charactersBelow: "Это ниже минимального количества символов! У вас {current} / из {min}"
_webhookSettings:
name: "Название"
active: "Вкл."

View File

@ -586,7 +586,6 @@ tokenRequested: "Povoliť prístup k účtu"
pluginTokenRequestedDescription: "Tento plugin bude môcť používať oprávnenia nastavené tu." pluginTokenRequestedDescription: "Tento plugin bude môcť používať oprávnenia nastavené tu."
notificationType: "Typ oznámenia" notificationType: "Typ oznámenia"
edit: "Upraviť" edit: "Upraviť"
useStarForReactionFallback: "Použiť ★ keď emoji reakcie nie je známe"
emailServer: "Email server" emailServer: "Email server"
enableEmail: "Zapnúť email" enableEmail: "Zapnúť email"
emailConfigInfo: "Používa sa na overenie emaily pri registrácii alebo pri zabudnutí hesla" emailConfigInfo: "Používa sa na overenie emaily pri registrácii alebo pri zabudnutí hesla"
@ -1475,3 +1474,6 @@ _deck:
channel: "Kanály" channel: "Kanály"
mentions: "Zmienky" mentions: "Zmienky"
direct: "Priame poznámky" direct: "Priame poznámky"
_webhookSettings:
name: "Názov"
active: "Zapnuté"

View File

@ -343,9 +343,15 @@ recentlyRegisteredUsers: "Nyligen registrerade användare"
userList: "Listor" userList: "Listor"
aboutMisskey: "Om Misskey" aboutMisskey: "Om Misskey"
administrator: "Administratör" administrator: "Administratör"
passwordLessLogin: "Lösenordsfri inloggning"
passwordLessLoginDescription: "Tillåter lösenordsfri inloggning med endast en säkerhetsnyckel eller en passkey."
newPasswordIs: "Det nya lösenordet är \"{password}\"" newPasswordIs: "Det nya lösenordet är \"{password}\""
share: "Dela" share: "Dela"
enable: "Aktivera" enable: "Aktivera"
weakPassword: "Svagt Lösenord"
normalPassword: "Medel Lösenord"
strongPassword: "Starkt Lösenord"
signinFailed: "Kan inte logga in. Det angivna användarnamnet eller lösenordet är felaktigt."
serviceworkerInfo: "Måste vara aktiverad för pushnotiser." serviceworkerInfo: "Måste vara aktiverad för pushnotiser."
enableInfiniteScroll: "Ladda mer automatiskt" enableInfiniteScroll: "Ladda mer automatiskt"
enablePlayer: "Öppna videospelare" enablePlayer: "Öppna videospelare"
@ -354,11 +360,13 @@ enableEmail: "Aktivera epost-utskick"
smtpHost: "Värd" smtpHost: "Värd"
smtpUser: "Användarnamn" smtpUser: "Användarnamn"
smtpPass: "Lösenord" smtpPass: "Lösenord"
emptyToDisableSmtpAuth: "Lämna användarnamn och lösenord tomt för att avaktivera SMTP verifiering"
clearCache: "Rensa cache" clearCache: "Rensa cache"
enabled: "Aktiverad" enabled: "Aktiverad"
user: "Användare" user: "Användare"
global: "Global" global: "Global"
squareAvatars: "Visa fyrkantiga profilbilder" squareAvatars: "Visa fyrkantiga profilbilder"
incorrectPassword: "Fel lösenord."
searchByGoogle: "Sök" searchByGoogle: "Sök"
file: "Filer" file: "Filer"
enableAutoSensitive: "Automatisk NSFW markering" enableAutoSensitive: "Automatisk NSFW markering"
@ -368,6 +376,15 @@ subscribePushNotification: "Aktivera pushnotiser"
unsubscribePushNotification: "Avaktivera pushnotiser" unsubscribePushNotification: "Avaktivera pushnotiser"
pushNotificationAlreadySubscribed: "Pushnotiser är redan aktiverade" pushNotificationAlreadySubscribed: "Pushnotiser är redan aktiverade"
pushNotificationNotSupported: "Din webbläsare eller instans har inte stöd för pushnotiser" pushNotificationNotSupported: "Din webbläsare eller instans har inte stöd för pushnotiser"
windowMaximize: "Maximera"
windowMinimize: "Minimera"
windowRestore: "Återställ"
resetPasswordConfirm: "Återställ verkligen ditt lösenord?"
_achievements:
_types:
_open3windows:
title: "Flera Fönster"
description: "Ha minst 3 fönster öppna samtidigt"
_email: _email:
_follow: _follow:
title: "följde dig" title: "följde dig"
@ -384,6 +401,7 @@ _sfx:
chat: "Chatt" chat: "Chatt"
antenna: "Antenner" antenna: "Antenner"
_2fa: _2fa:
passwordToTOTP: "Skriv in ditt lösenord"
renewTOTPCancel: "Nej tack" renewTOTPCancel: "Nej tack"
_antennaSources: _antennaSources:
all: "Alla noter" all: "Alla noter"
@ -442,3 +460,5 @@ _deck:
antenna: "Antenner" antenna: "Antenner"
list: "Listor" list: "Listor"
mentions: "Omnämningar" mentions: "Omnämningar"
_webhookSettings:
active: "Aktiverad"

View File

@ -268,7 +268,7 @@ remoteUserCaution: "เนื่องจากผู้ใช้งานรา
activity: "กิจกรรม" activity: "กิจกรรม"
images: "รูปภาพ" images: "รูปภาพ"
birthday: "วันเกิด" birthday: "วันเกิด"
yearsOld: "{อายุ} ปี" yearsOld: "{age} ปี"
registeredDate: "วันที่สมัครสมาชิก" registeredDate: "วันที่สมัครสมาชิก"
location: "ตำแหน่งที่ตั้ง" location: "ตำแหน่งที่ตั้ง"
theme: "ธีม" theme: "ธีม"
@ -506,6 +506,7 @@ objectStorageSetPublicRead: "ตั้งค่า \"public-read\" ในกา
serverLogs: "บันทึกของเซิร์ฟเวอร์" serverLogs: "บันทึกของเซิร์ฟเวอร์"
deleteAll: "ลบทั้งหมด" deleteAll: "ลบทั้งหมด"
showFixedPostForm: "แสดงแบบฟอร์มการโพสต์ที่ด้านบนสุดของไทม์ไลน์" showFixedPostForm: "แสดงแบบฟอร์มการโพสต์ที่ด้านบนสุดของไทม์ไลน์"
showFixedPostFormInChannel: "แสดงแบบฟอร์มกำลังโพสต์ที่ด้านบนของไทม์ไลน์ (แชนแนล)"
newNoteRecived: "มีโน้ตใหม่" newNoteRecived: "มีโน้ตใหม่"
sounds: "เสียง" sounds: "เสียง"
sound: "เสียง" sound: "เสียง"
@ -543,6 +544,8 @@ userSuspended: "ผู้ใช้รายนี้ถูกระงับก
userSilenced: "ผู้ใช้รายนี้กำลังถูกปิดกั้น" userSilenced: "ผู้ใช้รายนี้กำลังถูกปิดกั้น"
yourAccountSuspendedTitle: "บัญชีนี้นั้นถูกระงับ" yourAccountSuspendedTitle: "บัญชีนี้นั้นถูกระงับ"
yourAccountSuspendedDescription: "บัญชีนี้ถูกระงับ เนื่องจากละเมิดข้อกำหนดในการให้บริการของเซิร์ฟเวอร์หรืออาจจะละเมิดหลักเกณฑ์ชุมชน หรือ อาจจะโดนร้องเรียนเรื่องการละเมิดลิขสิทธิ์และอื่นๆอย่างต่อเนื่องซ้ำๆ หากคุณคิดว่าไม่ได้ทำผิดจริงๆหรือตัดสินผิดพลาด ได้โปรดกรุณาติดต่อผู้ดูแลระบบหากคุณต้องการทราบเหตุผลโดยละเอียดเพิ่มเติม และขอความกรุณาอย่าสร้างบัญชีใหม่" yourAccountSuspendedDescription: "บัญชีนี้ถูกระงับ เนื่องจากละเมิดข้อกำหนดในการให้บริการของเซิร์ฟเวอร์หรืออาจจะละเมิดหลักเกณฑ์ชุมชน หรือ อาจจะโดนร้องเรียนเรื่องการละเมิดลิขสิทธิ์และอื่นๆอย่างต่อเนื่องซ้ำๆ หากคุณคิดว่าไม่ได้ทำผิดจริงๆหรือตัดสินผิดพลาด ได้โปรดกรุณาติดต่อผู้ดูแลระบบหากคุณต้องการทราบเหตุผลโดยละเอียดเพิ่มเติม และขอความกรุณาอย่าสร้างบัญชีใหม่"
tokenRevoked: "โทเค็นไม่ถูกต้อง"
accountDeleted: "ลบบัญชีแล้ว"
menu: "เมนู" menu: "เมนู"
divider: "ตัวแบ่ง" divider: "ตัวแบ่ง"
addItem: "เพิ่มรายการ" addItem: "เพิ่มรายการ"
@ -586,7 +589,6 @@ tokenRequested: "ให้สิทธิ์การเข้าถึงบั
pluginTokenRequestedDescription: "ปลั๊กอินนี้จะสามารถใช้การอนุญาตที่ตั้งค่าไว้ที่นี่นะ" pluginTokenRequestedDescription: "ปลั๊กอินนี้จะสามารถใช้การอนุญาตที่ตั้งค่าไว้ที่นี่นะ"
notificationType: "ประเภทการแจ้งเตือน" notificationType: "ประเภทการแจ้งเตือน"
edit: "แก้ไข" edit: "แก้ไข"
useStarForReactionFallback: "ใช้ ★ เป็นทางเลือกแทนถ้าหากไม่ทราบอิโมจิ"
emailServer: "อีเมล์เซิร์ฟเวอร์" emailServer: "อีเมล์เซิร์ฟเวอร์"
enableEmail: "เปิดใช้งานการกระจายอีเมล" enableEmail: "เปิดใช้งานการกระจายอีเมล"
emailConfigInfo: "ใช้เพื่อยืนยันอีเมลของคุณระหว่างการสมัครหรือถ้าหากคุณลืมรหัสผ่าน" emailConfigInfo: "ใช้เพื่อยืนยันอีเมลของคุณระหว่างการสมัครหรือถ้าหากคุณลืมรหัสผ่าน"
@ -953,8 +955,22 @@ copyErrorInfo: "คัดลอกรายละเอียดข้อผิ
joinThisServer: "ลงชื่อสมัครใช้ในอินสแตนซ์นี้" joinThisServer: "ลงชื่อสมัครใช้ในอินสแตนซ์นี้"
exploreOtherServers: "มองหาอินสแตนซ์อื่น" exploreOtherServers: "มองหาอินสแตนซ์อื่น"
letsLookAtTimeline: "ลองดูที่ไทม์ไลน์" letsLookAtTimeline: "ลองดูที่ไทม์ไลน์"
disableFederationWarn: "การดำเนินการนี้ถ้าหากจะปิดใช้งานการรวมศูนย์ แต่โพสต์ดังกล่าวนั้นจะยังคงเป็นสาธารณะต่อไป ยกเว้นแต่ว่าจะตั้งค่าเป็นอย่างอื่น โดยปกติคุณไม่จำเป็นต้องใช้การตั้งค่านี้นะ"
invitationRequiredToRegister: "อินสแตนซ์นี้เป็นแบบรับเชิญเท่านั้น คุณต้องป้อนรหัสเชิญที่ถูกต้องถึงจะลงทะเบียนได้นะค่ะ" invitationRequiredToRegister: "อินสแตนซ์นี้เป็นแบบรับเชิญเท่านั้น คุณต้องป้อนรหัสเชิญที่ถูกต้องถึงจะลงทะเบียนได้นะค่ะ"
emailNotSupported: "อินสแตนซ์นี้ไม่รองรับการส่งอีเมลนะค่ะ"
postToTheChannel: "โพสต์ลงช่อง"
cannotBeChangedLater: "สิ่งนี้ไม่สามารถเปลี่ยนแปลงได้ในภายหลังนะ"
likeOnly: "ที่ชอบเท่านั้น"
resetPasswordConfirm: "รีเซ็ตรหัสผ่านของคุณจริงๆหรอ?"
sensitiveWords: "คำที่ละเอียดอ่อน"
sensitiveWordsDescription: "การเปิดเผยโน้ตทั้งหมดที่มีคำที่กำหนดค่าไว้จะถูกตั้งค่าเป็น \"หน้าแรก\" โดยอัตโนมัติ คุณยังสามารถแสดงหลายรายการได้โดยแยกรายการโดยใช้ตัวแบ่งบรรทัดได้นะ"
notesSearchNotAvailable: "การค้นหาโน้ตไม่พร้อมใช้งานนะค่ะ"
license: "ใบอนุญาต"
unfavoriteConfirm: "ลบออกจากรายการโปรดแน่ใจหรอ?"
myClips: "คลิปของฉัน"
drivecleaner: "ทำความสะอาดไดรฟ์"
retryAllQueuesNow: "ลองเรียกใช้คิวทั้งหมดอีกครั้ง"
retryAllQueuesConfirmTitle: "ลองใหม่ทั้งหมดจริงๆหรอแน่ใจนะ?"
retryAllQueuesConfirmText: "สิ่งนี้จะเพิ่มการโหลดเซิร์ฟเวอร์ชั่วคราวนะ"
_achievements: _achievements:
earnedAt: "ได้รับเมื่อ" earnedAt: "ได้รับเมื่อ"
_types: _types:
@ -1214,6 +1230,8 @@ _role:
iconUrl: "ไอคอน URL" iconUrl: "ไอคอน URL"
asBadge: "แสดงเป็นตรา" asBadge: "แสดงเป็นตรา"
descriptionOfAsBadge: "ไอคอนของบทบาทนี้จะปรากฏถัดจากชื่อผู้ใช้ของผู้ใช้งานด้วยบทบาทนี้ถ้าหากเปิดใช้งาน" descriptionOfAsBadge: "ไอคอนของบทบาทนี้จะปรากฏถัดจากชื่อผู้ใช้ของผู้ใช้งานด้วยบทบาทนี้ถ้าหากเปิดใช้งาน"
displayOrder: "ตำแหน่ง"
descriptionOfDisplayOrder: "ยิ่งตัวเลขสูง ตำแหน่ง UI ก็ยิ่งสูงขึ้นนะ"
canEditMembersByModerator: "อนุญาตให้ผู้ดูแลแก้ไขสมาชิก" canEditMembersByModerator: "อนุญาตให้ผู้ดูแลแก้ไขสมาชิก"
descriptionOfCanEditMembersByModerator: "เมื่อเปิดใช้ ผู้ดูแลนอกเหนือจากผู้ดูแลระบบแล้ว จะสามารถกำหนดและยกเลิกการมอบหมายบทบาทนี้ให้กับผู้ใช้ได้ เมื่อปิด เฉพาะผู้ดูแลระบบเท่านั้นที่จะสามารถกำหนดผู้ใช้ได้นะ" descriptionOfCanEditMembersByModerator: "เมื่อเปิดใช้ ผู้ดูแลนอกเหนือจากผู้ดูแลระบบแล้ว จะสามารถกำหนดและยกเลิกการมอบหมายบทบาทนี้ให้กับผู้ใช้ได้ เมื่อปิด เฉพาะผู้ดูแลระบบเท่านั้นที่จะสามารถกำหนดผู้ใช้ได้นะ"
priority: "ลำดับความสำคัญ" priority: "ลำดับความสำคัญ"
@ -1239,6 +1257,7 @@ _role:
rateLimitFactor: "ขีดจำกัดอัตรา" rateLimitFactor: "ขีดจำกัดอัตรา"
descriptionOfRateLimitFactor: "ขีดจํากัดอัตราที่ต่ำกว่ามีข้อจํากัดน้อยกว่าข้อจํากัดที่สูงกว่า" descriptionOfRateLimitFactor: "ขีดจํากัดอัตราที่ต่ำกว่ามีข้อจํากัดน้อยกว่าข้อจํากัดที่สูงกว่า"
canHideAds: "ซ่อนโฆษณา" canHideAds: "ซ่อนโฆษณา"
canSearchNotes: "การใช้การค้นหาโน้ต"
_condition: _condition:
isLocal: "ผู้ใช้ภายใน" isLocal: "ผู้ใช้ภายใน"
isRemote: "ผู้ใช้ระยะไกล" isRemote: "ผู้ใช้ระยะไกล"
@ -1840,3 +1859,12 @@ _deck:
_dialog: _dialog:
charactersExceeded: "คุณกำลังมีตัวอักขระเกินขีดจำกัดสูงสุดแล้วนะ! ปัจจุบันอยู่ที่ {current} จาก {max}" charactersExceeded: "คุณกำลังมีตัวอักขระเกินขีดจำกัดสูงสุดแล้วนะ! ปัจจุบันอยู่ที่ {current} จาก {max}"
charactersBelow: "คุณกำลังใช้อักขระต่ำกว่าขีดจำกัดขั้นต่ำเลยนะ! ปัจจุบันอยู่ที่ {current} จาก {min}" charactersBelow: "คุณกำลังใช้อักขระต่ำกว่าขีดจำกัดขั้นต่ำเลยนะ! ปัจจุบันอยู่ที่ {current} จาก {min}"
_disabledTimeline:
title: "ปิดใช้งานไทม์ไลน์"
description: "คุณไม่สามารถใช้ไทม์ไลน์นี้ภายใต้บทบาทปัจจุบันของคุณได้"
_drivecleaner:
orderBySizeDesc: "ขนาดไฟล์จากมากไปหาน้อย"
orderByCreatedAtAsc: "วันที่จากน้อยไปหามาก"
_webhookSettings:
name: "ชื่อ"
active: "เปิดใช้งาน"

View File

@ -576,7 +576,6 @@ tokenRequested: "Надати доступ до акаунту"
pluginTokenRequestedDescription: "Цей плагін зможе використовувати дозволи які тут вказані." pluginTokenRequestedDescription: "Цей плагін зможе використовувати дозволи які тут вказані."
notificationType: "Тип сповіщення" notificationType: "Тип сповіщення"
edit: "Редагувати" edit: "Редагувати"
useStarForReactionFallback: "Використовувати ★ як запасний варіант, якщо емодзі реакції невідомий"
emailServer: "Email сервер" emailServer: "Email сервер"
enableEmail: "Увімкнути функцію доставки пошти" enableEmail: "Увімкнути функцію доставки пошти"
emailConfigInfo: "Використовується для підтвердження електронної пошти підчас реєстрації, а також для відновлення паролю." emailConfigInfo: "Використовується для підтвердження електронної пошти підчас реєстрації, а також для відновлення паролю."
@ -1639,3 +1638,6 @@ _deck:
channel: "Канали" channel: "Канали"
mentions: "Згадки" mentions: "Згадки"
direct: "Особисте" direct: "Особисте"
_webhookSettings:
name: "Ім'я"
active: "Увімкнено"

View File

@ -585,7 +585,6 @@ tokenRequested: "Cấp quyền truy cập vào tài khoản"
pluginTokenRequestedDescription: "Plugin này sẽ có thể sử dụng các quyền được đặt ở đây." pluginTokenRequestedDescription: "Plugin này sẽ có thể sử dụng các quyền được đặt ở đây."
notificationType: "Loại thông báo" notificationType: "Loại thông báo"
edit: "Sửa" edit: "Sửa"
useStarForReactionFallback: "Dùng ★ nếu emoji biểu cảm không có"
emailServer: "Email máy chủ" emailServer: "Email máy chủ"
enableEmail: "Bật phân phối email" enableEmail: "Bật phân phối email"
emailConfigInfo: "Được dùng để xác minh email của bạn lúc đăng ký hoặc nếu bạn quên mật khẩu của mình" emailConfigInfo: "Được dùng để xác minh email của bạn lúc đăng ký hoặc nếu bạn quên mật khẩu của mình"
@ -1705,3 +1704,6 @@ _deck:
_dialog: _dialog:
charactersExceeded: "Bạn nhắn quá giới hạn ký tự!! Hiện nay {current} / giới hạn {max}" 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}" charactersBelow: "Bạn nhắn quá ít tối thiểu ký tự!! Hiện nay {current} / Tối thiểu {min}"
_webhookSettings:
name: "Tên"
active: "Đã bật"

View File

@ -2,7 +2,7 @@
_lang_: "中文(简体)" _lang_: "中文(简体)"
headlineMisskey: "通过帖子连接在一起的网络" headlineMisskey: "通过帖子连接在一起的网络"
introMisskey: "欢迎Misskey是一个开源的、去中心化的“微博客”服务。\n通过编写「帖文」来和大家分享你的以及你周围的事情吧📡\n通过「回应」功能可以让你快速地对大家的帖文表达反馈👍\n来探索新的世界吧🚀" introMisskey: "欢迎Misskey是一个开源的、去中心化的“微博客”服务。\n通过编写「帖文」来和大家分享你的以及你周围的事情吧📡\n通过「回应」功能可以让你快速地对大家的帖文表达反馈👍\n来探索新的世界吧🚀"
poweredByMisskeyDescription: "{name} 由开源平台 <b>Misskey</b> 驱动(也被称为 Misskey 实例)" poweredByMisskeyDescription: "{name} 是开源平台 <b>Misskey</b> 的服务器之一。"
monthAndDay: "{month}月 {day}日" monthAndDay: "{month}月 {day}日"
search: "搜索" search: "搜索"
notifications: "通知" notifications: "通知"
@ -16,16 +16,16 @@ cancel: "取消"
noThankYou: "不用,谢谢" noThankYou: "不用,谢谢"
enterUsername: "输入用户名" enterUsername: "输入用户名"
renotedBy: "由 {user} 转贴" renotedBy: "由 {user} 转贴"
noNotes: "没有帖" noNotes: "没有帖"
noNotifications: "无通知" noNotifications: "无通知"
instance: "实例" instance: "服务器"
settings: "设置" settings: "设置"
basicSettings: "基本设置" basicSettings: "基本设置"
otherSettings: "其他设置" otherSettings: "其他设置"
openInWindow: "在新窗口中打开" openInWindow: "在新窗口中打开"
profile: "个人资料" profile: "个人资料"
timeline: "时间线" timeline: "时间线"
noAccountDescription: "这个人很懒,没有写自我介绍" noAccountDescription: "此用户尚无自我介绍"
login: "登录" login: "登录"
loggingIn: "正在登录..." loggingIn: "正在登录..."
logout: "登出" logout: "登出"
@ -60,7 +60,7 @@ youGotNewFollower: "你有新的关注者"
receiveFollowRequest: "您收到了关注请求" receiveFollowRequest: "您收到了关注请求"
followRequestAccepted: "您的关注请求被通过了" followRequestAccepted: "您的关注请求被通过了"
mention: "提及" mention: "提及"
mentions: "提" mentions: "提到我的"
directNotes: "私信" directNotes: "私信"
importAndExport: "导入和导出" importAndExport: "导入和导出"
import: "导入" import: "导入"
@ -85,7 +85,7 @@ somethingHappened: "出现了一些问题!"
retry: "重试" retry: "重试"
pageLoadError: "页面加载失败。" pageLoadError: "页面加载失败。"
pageLoadErrorDescription: "这通常是由于网络或浏览器缓存的原因。请清除缓存或等待片刻后重试。" pageLoadErrorDescription: "这通常是由于网络或浏览器缓存的原因。请清除缓存或等待片刻后重试。"
serverIsDead: "服务器没有响应。 请稍等片刻,然后重试。" serverIsDead: "没有服务器响应。 请稍后再试。"
youShouldUpgradeClient: "请重新加载并使用新版本的客户端查看此页面。" youShouldUpgradeClient: "请重新加载并使用新版本的客户端查看此页面。"
enterListName: "输入列表名称" enterListName: "输入列表名称"
privacy: "隐私" privacy: "隐私"
@ -95,7 +95,7 @@ follow: "关注"
followRequest: "关注申请" followRequest: "关注申请"
followRequests: "关注申请" followRequests: "关注申请"
unfollow: "取消关注" unfollow: "取消关注"
followRequestPending: "发送关注请求" followRequestPending: "关注请求批准中"
enterEmoji: "输入表情符号" enterEmoji: "输入表情符号"
renote: "转发" renote: "转发"
unrenote: "取消转发" unrenote: "取消转发"
@ -119,9 +119,11 @@ rememberNoteVisibility: "保存上次设置的可见性"
attachCancel: "删除附件" attachCancel: "删除附件"
markAsSensitive: "标记为敏感内容" markAsSensitive: "标记为敏感内容"
unmarkAsSensitive: "取消标记为敏感内容" unmarkAsSensitive: "取消标记为敏感内容"
enterFileName: "输入文件名" enterFileName: "输入文件名"
mute: "屏蔽" mute: "屏蔽"
unmute: "解除屏蔽" unmute: "解除屏蔽"
renoteMute: "屏蔽转帖"
renoteUnmute: "解除屏蔽转帖"
block: "拉黑" block: "拉黑"
unblock: "取消拉黑" unblock: "取消拉黑"
suspend: "冻结" suspend: "冻结"
@ -140,21 +142,22 @@ customEmojis: "自定义表情符号"
emoji: "表情符号" emoji: "表情符号"
emojis: "表情符号" emojis: "表情符号"
emojiName: "表情符号名称" emojiName: "表情符号名称"
emojiUrl: "表情符号地址" emojiUrl: "emoji 地址"
addEmoji: "添加表情符号" addEmoji: "添加表情符号"
settingGuide: "推荐配置" settingGuide: "推荐配置"
cacheRemoteFiles: "远程文件缓存" cacheRemoteFiles: "缓存远程文件"
cacheRemoteFilesDescription: "当禁用此设定时远程文件将直接从远程实例载入。禁用后会减小储存空间需求,但是会增加流量,因为缩略图不会被生成。" cacheRemoteFilesDescription: "当禁用此设定时远程文件将直接从远程服务器载入。禁用后会减小储存空间需求,但是会增加流量,因为缩略图不会被生成。"
flagAsBot: "这是一个机器人账号" flagAsBot: "这是一个机器人账号"
flagAsBotDescription: "如果此帐户由程序控制请启用此项。启用后此标志可以帮助其他开发人员防止机器人之间产生无限互动的行为并让Misskey的内部系统将此帐户识别为机器人。" flagAsBotDescription: "如果此帐户由程序控制请启用此项。启用后此标志可以帮助其他开发人员防止机器人之间产生无限互动的行为并让Misskey的内部系统将此帐户识别为机器人。"
flagAsCat: "将这个账户设定为一只猫" flagAsCat: "将这个账户设定为一只猫"
flagAsCatDescription: "如果您想表明此帐户是一只猫,请打开此标志。\n开启后会在您的头像上出现猫耳朵并将你的帖子中的「na」替换为「nya」日文同理。" flagAsCatDescription: "如果您想表明此帐户是一只猫,请打开此标志。\n开启后会在您的头像上出现猫耳朵并将你的帖子中的「na」替换为「nya」日文同理。"
flagShowTimelineReplies: "在时间线上显示帖子的回复" flagShowTimelineReplies: "在时间线上显示帖子的回复"
flagShowTimelineRepliesDescription: "启用时,时间线除了显示用户的帖子外,还会显示其他用户对帖子的回复。" flagShowTimelineRepliesDescription: "启用时,时间线除了显示用户的帖子外,还会显示其他用户对帖子的回复。"
autoAcceptFollowed: "自动允许关注者的关注" autoAcceptFollowed: "自动允许来自我关注的用户对我的关注请求"
addAccount: "添加账户" addAccount: "添加账户"
reloadAccountsList: "更新账户列表"
loginFailed: "登录失败" loginFailed: "登录失败"
showOnRemote: "转到所在实例显示" showOnRemote: "转到所在服务器显示"
general: "常规设置" general: "常规设置"
wallpaper: "壁纸" wallpaper: "壁纸"
setWallpaper: "设置壁纸" setWallpaper: "设置壁纸"
@ -169,7 +172,7 @@ selectUser: "选择用户"
recipient: "收件人" recipient: "收件人"
annotation: "注解" annotation: "注解"
federation: "联合" federation: "联合"
instances: "实例" instances: "服务器"
registeredAt: "初次观测" registeredAt: "初次观测"
latestRequestReceivedAt: "上次收到的请求" latestRequestReceivedAt: "上次收到的请求"
latestStatus: "最后状态" latestStatus: "最后状态"
@ -178,7 +181,7 @@ charts: "图表"
perHour: "每小时" perHour: "每小时"
perDay: "每天" perDay: "每天"
stopActivityDelivery: "停止发送活动" stopActivityDelivery: "停止发送活动"
blockThisInstance: "阻止此实例向本实例推流" blockThisInstance: "阻止此服务器向本服务器推流"
operations: "操作" operations: "操作"
software: "软件" software: "软件"
version: "版本" version: "版本"
@ -189,18 +192,18 @@ jobQueue: "作业队列"
cpuAndMemory: "CPU和内存" cpuAndMemory: "CPU和内存"
network: "网络" network: "网络"
disk: "存储" disk: "存储"
instanceInfo: "实例信息" instanceInfo: "服务器信息"
statistics: "统计" statistics: "统计"
clearQueue: "清除队列" clearQueue: "清除队列"
clearQueueConfirmTitle: "确定清除队列?" clearQueueConfirmTitle: "确定清除队列?"
clearQueueConfirmText: "未送达的帖子将不会送达。 通常,您不需要这样做。" clearQueueConfirmText: "未送达的帖子将不会送达。 通常,您不需要这样做。"
clearCachedFiles: "清除缓存" clearCachedFiles: "清除缓存"
clearCachedFilesConfirm: "确定要清除缓存文件?" clearCachedFilesConfirm: "确定要清除缓存文件?"
blockedInstances: "被阻拦的实例" blockedInstances: "被阻拦的服务器"
blockedInstancesDescription: "设定要阻拦的实例,以换行来进行分割。被阻拦的实例将无法与本实例进行交换通讯。" blockedInstancesDescription: "设定要阻拦的服务器,以换行来进行分割。被阻拦的服务器将无法与本服务器进行交换通讯。"
muteAndBlock: "屏蔽/拉黑" muteAndBlock: "屏蔽/拉黑"
mutedUsers: "已屏蔽用户" mutedUsers: "已屏蔽用户"
blockedUsers: "拉黑的用户" blockedUsers: "拉黑的用户"
noUsers: "无用户" noUsers: "无用户"
editProfile: "编辑资料" editProfile: "编辑资料"
noteDeleteConfirm: "要删除该帖子吗?" noteDeleteConfirm: "要删除该帖子吗?"
@ -220,9 +223,9 @@ all: "全部"
subscribing: "已订阅" subscribing: "已订阅"
publishing: "投递中" publishing: "投递中"
notResponding: "没有响应" notResponding: "没有响应"
instanceFollowing: "关注实例" instanceFollowing: "关注服务器"
instanceFollowers: "关注实例" instanceFollowers: "关注的服务器"
instanceUsers: "实例用户" instanceUsers: "服务器用户"
changePassword: "修改密码" changePassword: "修改密码"
security: "安全" security: "安全"
retypedNotMatch: "两次输入不一致!" retypedNotMatch: "两次输入不一致!"
@ -264,7 +267,7 @@ basicNotesBeforeCreateAccount: "基本注意事项"
tos: "服务条款" tos: "服务条款"
start: "开始" start: "开始"
home: "首页" home: "首页"
remoteUserCaution: "由于此用户来自其它实例,显示的信息可能不完整。" remoteUserCaution: "由于此用户来自其它服务器,显示的信息可能不完整。"
activity: "活动" activity: "活动"
images: "图片" images: "图片"
birthday: "生日" birthday: "生日"
@ -314,8 +317,8 @@ unwatch: "取消关注"
accept: "允许" accept: "允许"
reject: "拒绝" reject: "拒绝"
normal: "正常" normal: "正常"
instanceName: "实例名称" instanceName: "服务器名称"
instanceDescription: "实例介绍" instanceDescription: "服务器简介"
maintainerName: "管理员名称" maintainerName: "管理员名称"
maintainerEmail: "管理员电子邮箱" maintainerEmail: "管理员电子邮箱"
tosUrl: "服务条款URL" tosUrl: "服务条款URL"
@ -333,7 +336,7 @@ enableLocalTimeline: "启用本地时间线功能"
enableGlobalTimeline: "启用全局时间线" enableGlobalTimeline: "启用全局时间线"
disablingTimelinesInfo: "即使时间线功能被禁用,出于方便,管理员和数据图表也可以继续使用。" disablingTimelinesInfo: "即使时间线功能被禁用,出于方便,管理员和数据图表也可以继续使用。"
registration: "注册" registration: "注册"
enableRegistration: "允许新用户注册" enableRegistration: "允许任何人注册"
invite: "邀请" invite: "邀请"
driveCapacityPerLocalAccount: "每个用户的网盘空间" driveCapacityPerLocalAccount: "每个用户的网盘空间"
driveCapacityPerRemoteAccount: "每个远程用户的网盘容量" driveCapacityPerRemoteAccount: "每个远程用户的网盘容量"
@ -345,17 +348,17 @@ basicInfo: "基本信息"
pinnedUsers: "置顶用户" pinnedUsers: "置顶用户"
pinnedUsersDescription: "在「发现」页面中使用换行标记想要置顶的用户。" pinnedUsersDescription: "在「发现」页面中使用换行标记想要置顶的用户。"
pinnedPages: "固定页面" pinnedPages: "固定页面"
pinnedPagesDescription: "输入您要固定到实例首页的页面路径,以换行符分隔。" pinnedPagesDescription: "输入您要固定到服务器首页的页面路径,以换行符分隔。"
pinnedClipId: "置顶的便签ID" pinnedClipId: "置顶的便签ID"
pinnedNotes: "已置顶的帖子" pinnedNotes: "已置顶的帖子"
hcaptcha: "hCaptcha" hcaptcha: "hCaptcha"
enableHcaptcha: "启用 hCaptcha" enableHcaptcha: "启用 hCaptcha"
hcaptchaSiteKey: "网站密钥" hcaptchaSiteKey: "网站密钥"
hcaptchaSecretKey: "hCaptcha 密钥(SecretKey)" hcaptchaSecretKey: "密钥"
recaptcha: "reCAPTCHA" recaptcha: "reCAPTCHA"
enableRecaptcha: "启用 reCAPTCHA\n(请注意, 此功能在中国大陆不可用. 如果启用, 可能导致无法正常使用登录或注册等功能)" enableRecaptcha: "启用 reCAPTCHA\n(请注意, 此功能在中国大陆不可用. 如果启用, 可能导致无法正常使用登录或注册等功能)"
recaptchaSiteKey: "网站密钥" recaptchaSiteKey: "网站密钥"
recaptchaSecretKey: "reCAPTCHA 密钥" recaptchaSecretKey: "reCAPTCHA 密钥(SecretKey)"
turnstile: "Turnstile" turnstile: "Turnstile"
enableTurnstile: "启用Turnstile" enableTurnstile: "启用Turnstile"
turnstileSiteKey: "网站密钥" turnstileSiteKey: "网站密钥"
@ -489,7 +492,7 @@ showFeaturedNotesInTimeline: "在时间线上显示热门推荐"
objectStorage: "对象存储" objectStorage: "对象存储"
useObjectStorage: "使用对象存储" useObjectStorage: "使用对象存储"
objectStorageBaseUrl: "Base URL" objectStorageBaseUrl: "Base URL"
objectStorageBaseUrlDesc: "用于引用的URL。如果您正在使用CDN或反向代理请指定其URL例如S3“https://<bucket>.s3.amazonaws.com”GCS“https://storage.googleapis.com/<bucket>”" objectStorageBaseUrlDesc: "这里是用于参考的URL如果您正在使用CDN或反向代理请指定其URL例如S3“https://<bucket>.s3.amazonaws.com”GCS“https://storage.googleapis.com/<bucket>”"
objectStorageBucket: "存储桶" objectStorageBucket: "存储桶"
objectStorageBucketDesc: "请指定使用的对象存储服务的存储桶名称。" objectStorageBucketDesc: "请指定使用的对象存储服务的存储桶名称。"
objectStoragePrefix: "前缀" objectStoragePrefix: "前缀"
@ -503,9 +506,11 @@ objectStorageUseSSLDesc: "如果不使用https进行API连接请关闭。"
objectStorageUseProxy: "使用代理" objectStorageUseProxy: "使用代理"
objectStorageUseProxyDesc: "如果您不使用代理进行API连接请将其关闭。" objectStorageUseProxyDesc: "如果您不使用代理进行API连接请将其关闭。"
objectStorageSetPublicRead: "上传时设置为public-read" objectStorageSetPublicRead: "上传时设置为public-read"
s3ForcePathStyleDesc: "启用 s3ForcePathStyle 会强制将存储桶名称指定为 URL 中路径的一部分,而不是主机名。使用自托管 Minio 等时可能需要启用。"
serverLogs: "服务器日志" serverLogs: "服务器日志"
deleteAll: "全部删除" deleteAll: "全部删除"
showFixedPostForm: "在时间线顶部显示发帖框" showFixedPostForm: "在时间线顶部显示发帖框"
showFixedPostFormInChannel: "在时间线顶部显示发帖对话框(频道)"
newNoteRecived: "有新的帖子" newNoteRecived: "有新的帖子"
sounds: "提示音" sounds: "提示音"
sound: "提示音" sound: "提示音"
@ -538,11 +543,15 @@ updateRemoteUser: "更新远程用户信息"
deleteAllFiles: "删除所有文件" deleteAllFiles: "删除所有文件"
deleteAllFilesConfirm: "要删除所有文件吗?" deleteAllFilesConfirm: "要删除所有文件吗?"
removeAllFollowing: "取消所有关注" removeAllFollowing: "取消所有关注"
removeAllFollowingDescription: "取消{host}的所有关注者。当实例不存在时执行。" removeAllFollowingDescription: "取消{host}的所有关注者。当服务器不再存在时执行。"
userSuspended: "该用户已被冻结。" userSuspended: "该用户已被冻结。"
userSilenced: "该用户已被禁言。" userSilenced: "该用户已被禁言。"
yourAccountSuspendedTitle: "账户已被冻结" yourAccountSuspendedTitle: "账户已被冻结"
yourAccountSuspendedDescription: "由于违反了服务器的服务条款或其他原因,该账户已被冻结。 您可以与管理员联系以了解更多信息。 请不要创建一个新的账户。" yourAccountSuspendedDescription: "由于违反了服务器的服务条款或其他原因,该账户已被冻结。 您可以与管理员联系以了解更多信息。 请不要创建一个新的账户。"
tokenRevoked: "令牌无效"
tokenRevokedDescription: "登录令牌已经失效。请重新登录。"
accountDeleted: "帐户已删除"
accountDeletedDescription: "此帐户已经被删除。"
menu: "菜单" menu: "菜单"
divider: "分割线" divider: "分割线"
addItem: "添加项目" addItem: "添加项目"
@ -586,7 +595,6 @@ tokenRequested: "允许访问账户"
pluginTokenRequestedDescription: "此插件将能够拥有此处设置的权限" pluginTokenRequestedDescription: "此插件将能够拥有此处设置的权限"
notificationType: "通知类型" notificationType: "通知类型"
edit: "编辑" edit: "编辑"
useStarForReactionFallback: "如果回应的是未知表情符号,则使用★作为代替"
emailServer: "邮件服务器" emailServer: "邮件服务器"
enableEmail: "启用发送邮件功能" enableEmail: "启用发送邮件功能"
emailConfigInfo: "用于确认电子邮件和密码重置" emailConfigInfo: "用于确认电子邮件和密码重置"
@ -635,15 +643,15 @@ abuseReported: "内容已发送。感谢您提交信息。"
reporter: "举报者" reporter: "举报者"
reporteeOrigin: "举报来源" reporteeOrigin: "举报来源"
reporterOrigin: "举报者来源" reporterOrigin: "举报者来源"
forwardReport: "将该举报信息转发给远程实例" forwardReport: "将该举报信息转发给远程服务器"
forwardReportIsAnonymous: "勾选则在远程实例上显示的举报者是匿名的系统账号,而不是您的账号。" forwardReportIsAnonymous: "勾选则在远程服务器上显示的举报者是匿名的系统账号,而不是您的账号。"
send: "发送" send: "发送"
abuseMarkAsResolved: "处理完毕" abuseMarkAsResolved: "处理完毕"
openInNewTab: "在新标签页中打开" openInNewTab: "在新标签页中打开"
openInSideView: "在侧边栏中打开" openInSideView: "在侧边栏中打开"
defaultNavigationBehaviour: "默认导航" defaultNavigationBehaviour: "默认导航"
editTheseSettingsMayBreakAccount: "编辑这些设置可以会损坏您的账号" editTheseSettingsMayBreakAccount: "编辑这些设置可以会损坏您的账号"
instanceTicker: "帖子的实例信息" instanceTicker: "帖子的服务器来源"
waitingFor: "等待{x}" waitingFor: "等待{x}"
random: "随机" random: "随机"
system: "系统" system: "系统"
@ -732,7 +740,7 @@ capacity: "容量"
inUse: "已使用" inUse: "已使用"
editCode: "编辑代码" editCode: "编辑代码"
apply: "应用" apply: "应用"
receiveAnnouncementFromInstance: "从实例接收通知" receiveAnnouncementFromInstance: "从服务器接收通知"
emailNotification: "邮件通知" emailNotification: "邮件通知"
publish: "发布" publish: "发布"
inChannelSearch: "频道内搜索" inChannelSearch: "频道内搜索"
@ -760,7 +768,7 @@ active: "活动"
offline: "离线" offline: "离线"
notRecommended: "不推荐" notRecommended: "不推荐"
botProtection: "Bot防御" botProtection: "Bot防御"
instanceBlocking: "被阻拦的实例" instanceBlocking: "被阻拦的服务器"
selectAccount: "选择账户" selectAccount: "选择账户"
switchAccount: "切换账户" switchAccount: "切换账户"
enabled: "已启用" enabled: "已启用"
@ -844,8 +852,8 @@ themeColor: "主题颜色"
size: "大小" size: "大小"
numberOfColumn: "列数" numberOfColumn: "列数"
searchByGoogle: "Google" searchByGoogle: "Google"
instanceDefaultLightTheme: "实例默认浅色主题" instanceDefaultLightTheme: "服务器默认浅色主题"
instanceDefaultDarkTheme: "实例默认深色主题" instanceDefaultDarkTheme: "服务器默认深色主题"
instanceDefaultThemeDescription: "以对象格式键入主题代码" instanceDefaultThemeDescription: "以对象格式键入主题代码"
mutePeriod: "屏蔽期限" mutePeriod: "屏蔽期限"
period: "截止时间" period: "截止时间"
@ -898,7 +906,7 @@ cannotUploadBecauseInappropriate: "因为可能含有不适宜的内容,无法
cannotUploadBecauseNoFreeSpace: "因为已无可用空间,无法上传。" cannotUploadBecauseNoFreeSpace: "因为已无可用空间,无法上传。"
beta: "测试" beta: "测试"
enableAutoSensitive: "自动 NSFW 识别" enableAutoSensitive: "自动 NSFW 识别"
enableAutoSensitiveDescription: "如果可用,请使用机器学习在媒体上自动设置 NSFW 标志。即使关闭此功能,也可能会根据实例自动设置。" enableAutoSensitiveDescription: "如果可用,请使用机器学习在媒体上自动设置 NSFW 标志。即使关闭此功能,也可能会根据服务器自动设置。"
activeEmailValidationDescription: "开启用户的电子邮件地址验证,判断它是一次性的电子邮件地址,还是可以实际通信的地址。关闭时,则只检查字符串是否正确。" activeEmailValidationDescription: "开启用户的电子邮件地址验证,判断它是一次性的电子邮件地址,还是可以实际通信的地址。关闭时,则只检查字符串是否正确。"
navbar: "导航栏" navbar: "导航栏"
shuffle: "随机" shuffle: "随机"
@ -908,10 +916,11 @@ pushNotification: "推送通知"
subscribePushNotification: "启用推送通知消息" subscribePushNotification: "启用推送通知消息"
unsubscribePushNotification: "停用推送通知消息" unsubscribePushNotification: "停用推送通知消息"
pushNotificationAlreadySubscribed: "推送通知消息已启用" pushNotificationAlreadySubscribed: "推送通知消息已启用"
pushNotificationNotSupported: "浏览器或实例不支持推送通知消息" pushNotificationNotSupported: "浏览器或服务器不支持推送通知消息"
sendPushNotificationReadMessage: "删除已读推送通知消息" sendPushNotificationReadMessage: "删除已读推送通知消息"
sendPushNotificationReadMessageCaption: "“{emptyPushNotificationMessage}”的通知消息将会显示。您终端设备的电池消耗可能会增加。" sendPushNotificationReadMessageCaption: "“{emptyPushNotificationMessage}”的通知消息将会显示。您终端设备的电池消耗可能会增加。"
windowMaximize: "最大化" windowMaximize: "最大化"
windowMinimize: "最小化"
windowRestore: "还原" windowRestore: "还原"
caption: "标题" caption: "标题"
loggedInAsBot: "以Bot账户登录" loggedInAsBot: "以Bot账户登录"
@ -950,11 +959,36 @@ collapseRenotes: "省略显示已经看过的转发内容"
internalServerError: "内部服务器错误" internalServerError: "内部服务器错误"
internalServerErrorDescription: "内部服务器发生了预期外的错误" internalServerErrorDescription: "内部服务器发生了预期外的错误"
copyErrorInfo: "复制错误信息" copyErrorInfo: "复制错误信息"
joinThisServer: "在本实例上注册" joinThisServer: "在本服务器上注册"
exploreOtherServers: "探索其他实例" exploreOtherServers: "探索其他服务器"
letsLookAtTimeline: "时间线" letsLookAtTimeline: "时间线"
disableFederationWarn: "联合被禁用。 禁用它并不能使帖子变成私人的。 在大多数情况下,这个选项不需要被启用。" disableFederationConfirm: "确定要禁用联合?"
invitationRequiredToRegister: "此实例目前只允许拥有邀请码的人注册。" disableFederationConfirmWarn: "禁用联合不会将帖子设为私有。在大多数情况下,不需要禁用联合。"
disableFederationOk: "联合禁用"
invitationRequiredToRegister: "此服务器目前只允许拥有邀请码的人注册。"
emailNotSupported: "此服务器不支持发送邮件"
postToTheChannel: "发布到频道"
cannotBeChangedLater: "之后不能再更改。"
reactionAcceptance: "接受表情回应"
likeOnly: "仅点赞"
likeOnlyForRemote: "远程仅点赞"
rolesAssignedToMe: "指派给自己的角色"
resetPasswordConfirm: "确定重置密码?"
sensitiveWords: "敏感词"
sensitiveWordsDescription: "将包含设置词的帖子的可见范围设置为首页。可以通过用换行符分隔来设置多个。"
notesSearchNotAvailable: "帖子检索不可用"
license: "许可信息"
unfavoriteConfirm: "确定要取消收藏吗?"
myClips: "我的便签"
drivecleaner: "网盘整理"
retryAllQueuesNow: "立刻重试所有队列"
retryAllQueuesConfirmTitle: "要再尝试一次吗?"
retryAllQueuesConfirmText: "可能会使服务器负荷在一定时间内增加"
enableChartsForRemoteUser: "生成远程用户的图表"
enableChartsForFederatedInstances: "生成远程服务器的图表"
showClipButtonInNoteFooter: "在贴文下方显示便签按钮"
largeNoteReactions: "使用大图标来显示回应"
noteIdOrUrl: "帖子ID或URL"
_achievements: _achievements:
earnedAt: "达成时间" earnedAt: "达成时间"
_types: _types:
@ -1145,7 +1179,7 @@ _achievements:
description: "在首页时间线的流速超过20npm" description: "在首页时间线的流速超过20npm"
_viewInstanceChart: _viewInstanceChart:
title: "分析师" title: "分析师"
description: "查看了实例信息中的图表" description: "查看了服务器信息中的图表"
_outputHelloWorldOnScratchpad: _outputHelloWorldOnScratchpad:
title: "Hello, world!" title: "Hello, world!"
description: "在AiScript控制台中输出 hello world" description: "在AiScript控制台中输出 hello world"
@ -1182,7 +1216,7 @@ _achievements:
_loggedInOnNewYearsDay: _loggedInOnNewYearsDay:
title: "恭贺新禧" title: "恭贺新禧"
description: "在元旦登入" description: "在元旦登入"
flavor: "今年也请对本实例多多指教!" flavor: "今年也请对本服务器多多指教!"
_cookieClicked: _cookieClicked:
title: "点击饼干小游戏" title: "点击饼干小游戏"
description: "点击了可疑的饼干" description: "点击了可疑的饼干"
@ -1197,7 +1231,7 @@ _role:
name: "角色名称" name: "角色名称"
description: "角色描述" description: "角色描述"
permission: "角色权限" permission: "角色权限"
descriptionOfPermission: "<b>监察员</b>可以执行基本的审核操作。\n<b>管理员</b>可以更改实例的所有设置。" descriptionOfPermission: "<b>监察员</b>可以执行基本地审核操作。\n<b>管理员</b>可以更改服务器的所有设置。"
assignTarget: "授权对象" assignTarget: "授权对象"
descriptionOfAssignTarget: "<b>手动</b>指手动选择谁被包括在这个角色中。\n<b>符合条件</b>指设置条件以自动包括符合条件的用户。" descriptionOfAssignTarget: "<b>手动</b>指手动选择谁被包括在这个角色中。\n<b>符合条件</b>指设置条件以自动包括符合条件的用户。"
manual: "手动" manual: "手动"
@ -1214,6 +1248,8 @@ _role:
iconUrl: "图标URL" iconUrl: "图标URL"
asBadge: "作为徽章显示" asBadge: "作为徽章显示"
descriptionOfAsBadge: "开启后,用户名旁边将会出现角色图标。" descriptionOfAsBadge: "开启后,用户名旁边将会出现角色图标。"
displayOrder: "显示顺序"
descriptionOfDisplayOrder: "数字越大,显示位置越靠前。"
canEditMembersByModerator: "允许监察者编辑成员" canEditMembersByModerator: "允许监察者编辑成员"
descriptionOfCanEditMembersByModerator: "如果选中,监察者和管理员都能够为用户分配/取消分配角色。如果未选中,则只有管理员可以执行此操作。" descriptionOfCanEditMembersByModerator: "如果选中,监察者和管理员都能够为用户分配/取消分配角色。如果未选中,则只有管理员可以执行此操作。"
priority: "优先级" priority: "优先级"
@ -1225,7 +1261,7 @@ _role:
gtlAvailable: "查看全局时间线" gtlAvailable: "查看全局时间线"
ltlAvailable: "查看本地时间线" ltlAvailable: "查看本地时间线"
canPublicNote: "允许公开发帖" canPublicNote: "允许公开发帖"
canInvite: "发放实例邀请码" canInvite: "发放服务器邀请码"
canManageCustomEmojis: "管理自定义表情符号" canManageCustomEmojis: "管理自定义表情符号"
driveCapacity: "网盘容量" driveCapacity: "网盘容量"
pinMax: "帖子置顶数量限制" pinMax: "帖子置顶数量限制"
@ -1239,6 +1275,7 @@ _role:
rateLimitFactor: "速率限制" rateLimitFactor: "速率限制"
descriptionOfRateLimitFactor: "值越小限制越少,值越大限制越多。" descriptionOfRateLimitFactor: "值越小限制越少,值越大限制越多。"
canHideAds: "可以隐藏广告" canHideAds: "可以隐藏广告"
canSearchNotes: "是否可以搜索帖子"
_condition: _condition:
isLocal: "是本地用户" isLocal: "是本地用户"
isRemote: "是远程用户" isRemote: "是远程用户"
@ -1248,6 +1285,8 @@ _role:
followersMoreThanOrEq: "关注者不少于" followersMoreThanOrEq: "关注者不少于"
followingLessThanOrEq: "关注中不多于" followingLessThanOrEq: "关注中不多于"
followingMoreThanOrEq: "关注中不少于" followingMoreThanOrEq: "关注中不少于"
notesLessThanOrEq: "帖子数在~以下"
notesMoreThanOrEq: "帖子数在~以上"
and: "符合以下全部条件" and: "符合以下全部条件"
or: "符合以下任一条件" or: "符合以下任一条件"
not: "不符合以下任何条件" not: "不符合以下任何条件"
@ -1287,7 +1326,7 @@ _ad:
_forgotPassword: _forgotPassword:
enterEmail: "请输入您验证账号时用的电子邮箱地址,密码重置链接将发送至该邮箱上。" enterEmail: "请输入您验证账号时用的电子邮箱地址,密码重置链接将发送至该邮箱上。"
ifNoEmail: "如果您没有使用电子邮件地址进行验证,请联系管理员。" ifNoEmail: "如果您没有使用电子邮件地址进行验证,请联系管理员。"
contactAdmin: "该实例不支持发送电子邮件。如果您想重设密码,请联系管理员。" contactAdmin: "该服务器不支持发送电子邮件。如果您想重设密码,请联系管理员。"
_gallery: _gallery:
my: "我的图库" my: "我的图库"
liked: "喜欢的图片" liked: "喜欢的图片"
@ -1372,10 +1411,10 @@ _wordMute:
hard: "硬屏蔽" hard: "硬屏蔽"
mutedNotes: "被屏蔽的帖子" mutedNotes: "被屏蔽的帖子"
_instanceMute: _instanceMute:
instanceMuteDescription: "屏蔽配置实例中的所有帖子和转帖,包括实例的用户回复。" instanceMuteDescription: "屏蔽配置服务器中的所有帖子和转帖,包括这些服务器上的用户回复。"
instanceMuteDescription2: "设置时用换行符来分隔" instanceMuteDescription2: "设置时用换行符来分隔"
title: "隐藏实例已设置的帖子。" title: "隐藏服务器已设置的帖子。"
heading: "屏蔽实例" heading: "屏蔽服务器"
_theme: _theme:
explore: "寻找主题" explore: "寻找主题"
install: "安装主题" install: "安装主题"
@ -1513,7 +1552,7 @@ _2fa:
step4: "从现在开始,任何登录操作都将要求您提供动态口令。" step4: "从现在开始,任何登录操作都将要求您提供动态口令。"
securityKeyNotSupported: "您的浏览器不支持安全密钥。" securityKeyNotSupported: "您的浏览器不支持安全密钥。"
registerTOTPBeforeKey: "要注册安全密钥或Passkey请先设置验证器应用程序。" registerTOTPBeforeKey: "要注册安全密钥或Passkey请先设置验证器应用程序。"
securityKeyInfo: "您可以设置使用支持FIDO2的硬件安全密钥、设备上的指纹或PIN来保护您的登录过程。" securityKeyInfo: "注册兼容 WebAuthn 的密钥,例如支持 FIDO2 的硬件安全密钥、设备上的生物识别功能、PIN 码以及 Passkey 等。"
chromePasskeyNotSupported: "目前不支持 Chrome 的Passkey。" chromePasskeyNotSupported: "目前不支持 Chrome 的Passkey。"
registerSecurityKey: "注册安全密钥或Passkey" registerSecurityKey: "注册安全密钥或Passkey"
securityKeyName: "输入密钥名称" securityKeyName: "输入密钥名称"
@ -1583,7 +1622,7 @@ _weekday:
saturday: "星期六" saturday: "星期六"
_widgets: _widgets:
profile: "个人资料" profile: "个人资料"
instanceInfo: "实例信息" instanceInfo: "服务器信息"
memo: "便签" memo: "便签"
notifications: "通知" notifications: "通知"
timeline: "时间线" timeline: "时间线"
@ -1596,8 +1635,8 @@ _widgets:
photos: "照片" photos: "照片"
digitalClock: "数字时钟" digitalClock: "数字时钟"
unixClock: "UNIX时钟" unixClock: "UNIX时钟"
federation: "联邦宇宙" federation: "联"
instanceCloud: "实例云" instanceCloud: "服务器云"
postForm: "投稿窗口" postForm: "投稿窗口"
slideshow: "幻灯片展示" slideshow: "幻灯片展示"
button: "按钮" button: "按钮"
@ -1648,7 +1687,7 @@ _visibility:
specified: "指定用户" specified: "指定用户"
specifiedDescription: "仅发送至指定用户" specifiedDescription: "仅发送至指定用户"
disableFederation: "不参与联合" disableFederation: "不参与联合"
disableFederationDescription: "不发送到其他实例" disableFederationDescription: "不发送到其他服务器"
_postForm: _postForm:
replyPlaceholder: "回复这个帖子..." replyPlaceholder: "回复这个帖子..."
quotePlaceholder: "引用这个帖子..." quotePlaceholder: "引用这个帖子..."
@ -1840,3 +1879,23 @@ _deck:
_dialog: _dialog:
charactersExceeded: "已经超过了最大字符数! 当前字符数 {current} / 限制字符数 {max}" charactersExceeded: "已经超过了最大字符数! 当前字符数 {current} / 限制字符数 {max}"
charactersBelow: "低于最小字符数!当前字符数 {current} / 限制字符数 {min}" charactersBelow: "低于最小字符数!当前字符数 {current} / 限制字符数 {min}"
_disabledTimeline:
title: "时间线已禁用"
description: "您不能在当前角色使用时间线。"
_drivecleaner:
orderBySizeDesc: "按大小降序排列"
orderByCreatedAtAsc: "按添加日期降序排列"
_webhookSettings:
createWebhook: "创建 Webhook"
name: "名称"
secret: "密钥"
events: "何时运行Webhook"
active: "已启用"
_events:
follow: "关注时"
followed: "被关注时"
note: "发布贴文时"
reply: "收到回复时"
renote: "被转发时"
reaction: "被回应时"
mention: "被提及时"

View File

@ -15,7 +15,7 @@ gotIt: "知道了"
cancel: "取消" cancel: "取消"
noThankYou: "現在不要" noThankYou: "現在不要"
enterUsername: "輸入使用者名稱" enterUsername: "輸入使用者名稱"
renotedBy: "{user} 轉了" renotedBy: "{user} 轉了"
noNotes: "無貼文。" noNotes: "無貼文。"
noNotifications: "沒有通知" noNotifications: "沒有通知"
instance: "實例" instance: "實例"
@ -99,9 +99,9 @@ followRequestPending: "追隨許可批准中"
enterEmoji: "輸入表情符號" enterEmoji: "輸入表情符號"
renote: "轉發" renote: "轉發"
unrenote: "取消轉發" unrenote: "取消轉發"
renoted: "轉成功" renoted: "轉成功"
cantRenote: "無法轉發此貼文。" cantRenote: "無法轉發此貼文。"
cantReRenote: "無法轉傳之前已經轉傳過的內容。" cantReRenote: "無法轉發之前已經轉發過的內容。"
quote: "引用" quote: "引用"
inChannelRenote: "在頻道內轉發" inChannelRenote: "在頻道內轉發"
inChannelQuote: "在頻道內引用" inChannelQuote: "在頻道內引用"
@ -122,14 +122,16 @@ unmarkAsSensitive: "取消標記為敏感內容"
enterFileName: "請輸入檔案名稱" enterFileName: "請輸入檔案名稱"
mute: "靜音" mute: "靜音"
unmute: "解除靜音" unmute: "解除靜音"
renoteMute: "將轉發貼文靜音"
renoteUnmute: "解除轉發貼文的靜音"
block: "封鎖" block: "封鎖"
unblock: "解除封鎖" unblock: "解除封鎖"
suspend: "凍結" suspend: "凍結"
unsuspend: "解除凍結" unsuspend: "解除凍結"
blockConfirm: "確定要封鎖此用戶?" blockConfirm: "確定要封鎖此用戶?"
unblockConfirm: "確定解除封鎖此用戶?" unblockConfirm: "確定解除封鎖此用戶?"
suspendConfirm: "確定凍結此帳" suspendConfirm: "確定凍結此帳"
unsuspendConfirm: "確定解凍此帳" unsuspendConfirm: "確定解凍此帳"
selectList: "選擇清單" selectList: "選擇清單"
selectChannel: "選擇頻道" selectChannel: "選擇頻道"
selectAntenna: "選擇天線" selectAntenna: "選擇天線"
@ -153,6 +155,7 @@ flagShowTimelineReplies: "在時間軸上顯示貼文的回覆"
flagShowTimelineRepliesDescription: "啟用時,時間線除了顯示用戶的貼文以外,還會顯示用戶對其他貼文的回覆。" flagShowTimelineRepliesDescription: "啟用時,時間線除了顯示用戶的貼文以外,還會顯示用戶對其他貼文的回覆。"
autoAcceptFollowed: "自動追隨中使用者的追隨請求" autoAcceptFollowed: "自動追隨中使用者的追隨請求"
addAccount: "添加帳戶" addAccount: "添加帳戶"
reloadAccountsList: "更新帳戶清單的資訊"
loginFailed: "登入失敗" loginFailed: "登入失敗"
showOnRemote: "轉到所在實例顯示" showOnRemote: "轉到所在實例顯示"
general: "一般" general: "一般"
@ -169,7 +172,7 @@ selectUser: "選取使用者"
recipient: "收件人" recipient: "收件人"
annotation: "註解" annotation: "註解"
federation: "站台聯邦" federation: "站台聯邦"
instances: "實例" instances: "伺服器"
registeredAt: "初次觀測" registeredAt: "初次觀測"
latestRequestReceivedAt: "上次收到的請求" latestRequestReceivedAt: "上次收到的請求"
latestStatus: "最後狀態" latestStatus: "最後狀態"
@ -403,7 +406,7 @@ securityKeyAndPasskey: "安全金鑰・Passkey"
securityKey: "安全金鑰" securityKey: "安全金鑰"
lastUsed: "上次使用" lastUsed: "上次使用"
lastUsedAt: "最後使用:{t}" lastUsedAt: "最後使用:{t}"
unregister: "註銷帳" unregister: "註銷帳"
passwordLessLogin: "設置無密碼登入" passwordLessLogin: "設置無密碼登入"
passwordLessLoginDescription: "不使用密碼,以安全金鑰或 Passkey 登入" passwordLessLoginDescription: "不使用密碼,以安全金鑰或 Passkey 登入"
resetPassword: "重置密碼" resetPassword: "重置密碼"
@ -506,6 +509,7 @@ objectStorageSetPublicRead: "上傳時設定為\"public-read\""
serverLogs: "伺服器日誌" serverLogs: "伺服器日誌"
deleteAll: "刪除所有記錄" deleteAll: "刪除所有記錄"
showFixedPostForm: "於時間軸頁頂顯示「發送貼文」方框" showFixedPostForm: "於時間軸頁頂顯示「發送貼文」方框"
showFixedPostFormInChannel: "於時間軸頁頂顯示「發送貼文」方框(頻道)"
newNoteRecived: "發現新的貼文" newNoteRecived: "發現新的貼文"
sounds: "音效" sounds: "音效"
sound: "音效" sound: "音效"
@ -527,8 +531,8 @@ installedDate: "安裝時間"
lastUsedDate: "最後上線日期" lastUsedDate: "最後上線日期"
state: "狀態" state: "狀態"
sort: "排序" sort: "排序"
ascendingOrder: "遞增" ascendingOrder: "昇冪"
descendingOrder: "遞減" descendingOrder: "降冪"
scratchpad: "暫存記憶體" scratchpad: "暫存記憶體"
scratchpadDescription: "AiScript控制台為AiScript提供了實驗環境。您可以在此編寫、執行和確認代碼與Misskey互動的结果。" scratchpadDescription: "AiScript控制台為AiScript提供了實驗環境。您可以在此編寫、執行和確認代碼與Misskey互動的结果。"
output: "輸出" output: "輸出"
@ -543,6 +547,10 @@ userSuspended: "該使用者已被停用"
userSilenced: "該用戶已被禁言。" userSilenced: "該用戶已被禁言。"
yourAccountSuspendedTitle: "帳戶已被凍結" yourAccountSuspendedTitle: "帳戶已被凍結"
yourAccountSuspendedDescription: "由於違反了伺服器的服務條款或其他原因,該帳戶已被凍結。 您可以與管理員連繫以了解更多訊息。 請不要創建一個新的帳戶。" yourAccountSuspendedDescription: "由於違反了伺服器的服務條款或其他原因,該帳戶已被凍結。 您可以與管理員連繫以了解更多訊息。 請不要創建一個新的帳戶。"
tokenRevoked: "權杖無效"
tokenRevokedDescription: "登入權杖失效,請重新登入。"
accountDeleted: "帳戶已被刪除"
accountDeletedDescription: "這個帳戶已被刪除。"
menu: "選單" menu: "選單"
divider: "分割線" divider: "分割線"
addItem: "新增項目" addItem: "新增項目"
@ -586,7 +594,6 @@ tokenRequested: "允許存取帳戶"
pluginTokenRequestedDescription: "此外掛將擁有在此設定的權限。" pluginTokenRequestedDescription: "此外掛將擁有在此設定的權限。"
notificationType: "通知形式" notificationType: "通知形式"
edit: "編輯" edit: "編輯"
useStarForReactionFallback: "以★代替未知的表情符號"
emailServer: "電郵伺服器" emailServer: "電郵伺服器"
enableEmail: "啟用發送電郵功能" enableEmail: "啟用發送電郵功能"
emailConfigInfo: "用於確認電郵地址及密碼重置" emailConfigInfo: "用於確認電郵地址及密碼重置"
@ -670,8 +677,8 @@ sentReactionsCount: "反應發送次數"
receivedReactionsCount: "收到反應次數" receivedReactionsCount: "收到反應次數"
pollVotesCount: "已統計的投票數" pollVotesCount: "已統計的投票數"
pollVotedCount: "已投票數" pollVotedCount: "已投票數"
yes: "確定" yes: ""
no: "取消" no: ""
driveFilesCount: "雲端硬碟檔案數量" driveFilesCount: "雲端硬碟檔案數量"
driveUsage: "雲端硬碟使用量" driveUsage: "雲端硬碟使用量"
noCrawle: "拒絕搜尋引擎索引" noCrawle: "拒絕搜尋引擎索引"
@ -871,10 +878,10 @@ recommended: "推薦"
check: "檢查" check: "檢查"
driveCapOverrideLabel: "更改這個使用者的雲端硬碟容量上限" driveCapOverrideLabel: "更改這個使用者的雲端硬碟容量上限"
driveCapOverrideCaption: "如果指定0以下的值就會被取消。" driveCapOverrideCaption: "如果指定0以下的值就會被取消。"
requireAdminForView: "必須以管理員帳登入才可以檢視。" requireAdminForView: "必須以管理員帳登入才可以檢視。"
isSystemAccount: "由系統自動建立與管理的帳。" isSystemAccount: "由系統自動建立與管理的帳。"
typeToConfirm: "要執行這項操作,請輸入 {x} " typeToConfirm: "要執行這項操作,請輸入 {x} "
deleteAccount: "刪除帳" deleteAccount: "刪除帳"
document: "文件" document: "文件"
numberOfPageCache: "快取頁面數" numberOfPageCache: "快取頁面數"
numberOfPageCacheDescription: "增加數量會提高便利性,但也會增加負荷與記憶體使用量。" numberOfPageCacheDescription: "增加數量會提高便利性,但也會增加負荷與記憶體使用量。"
@ -914,7 +921,7 @@ sendPushNotificationReadMessageCaption: "「{emptyPushNotificationMessage}」通
windowMaximize: "最大化" windowMaximize: "最大化"
windowRestore: "復原" windowRestore: "復原"
caption: "標題" caption: "標題"
loggedInAsBot: "以機器人帳登入中" loggedInAsBot: "以機器人帳登入中"
tools: "工具" tools: "工具"
cannotLoad: "無法載入" cannotLoad: "無法載入"
numberOfProfileView: "個人檔案檢視次數" numberOfProfileView: "個人檔案檢視次數"
@ -953,8 +960,30 @@ copyErrorInfo: "複製錯誤資訊"
joinThisServer: "在此伺服器上註冊" joinThisServer: "在此伺服器上註冊"
exploreOtherServers: "探索其他伺服器" exploreOtherServers: "探索其他伺服器"
letsLookAtTimeline: "看看時間軸" letsLookAtTimeline: "看看時間軸"
disableFederationWarn: "聯邦被停用了。即使停用也不會讓您的貼文不公開,在大多數情況下,不需要啟用這個選項。"
invitationRequiredToRegister: "目前這個伺服器為邀請制,必須擁有邀請碼才能註冊。" invitationRequiredToRegister: "目前這個伺服器為邀請制,必須擁有邀請碼才能註冊。"
emailNotSupported: "這個伺服器不支援寄送郵件"
postToTheChannel: "發布到頻道"
cannotBeChangedLater: "之後不能變更。"
reactionAcceptance: "接受表情反應"
likeOnly: "僅限讚"
likeOnlyForRemote: "遠端僅限讚"
rolesAssignedToMe: "指派給自己的角色"
resetPasswordConfirm: "重設密碼?"
sensitiveWords: "敏感詞"
sensitiveWordsDescription: "將含有設定詞彙的貼文可見性設為發送至首頁。可以用換行來進行複數的設定。"
notesSearchNotAvailable: "無法使用搜尋貼文功能。"
license: "授權"
unfavoriteConfirm: "要取消收錄我的最愛嗎?"
myClips: "我的摘錄"
drivecleaner: "雲端硬碟清掃器"
retryAllQueuesNow: "立刻重試所有佇列"
retryAllQueuesConfirmTitle: "要現在重試嗎?"
retryAllQueuesConfirmText: "伺服器的負荷可能會暫時增加。"
enableChartsForRemoteUser: "生成遠端用戶的圖表"
enableChartsForFederatedInstances: "生成遠端伺服器的圖表"
showClipButtonInNoteFooter: "將摘錄添加至貼文"
largeNoteReactions: "將貼文的反應放大顯示"
noteIdOrUrl: "貼文ID或URL"
_achievements: _achievements:
earnedAt: "獲得日期" earnedAt: "獲得日期"
_types: _types:
@ -1071,7 +1100,7 @@ _achievements:
title: "有備而來" title: "有備而來"
description: "設定了個人檔案" description: "設定了個人檔案"
_markedAsCat: _markedAsCat:
title: "我是貓" title: "吾輩乃貓是也"
description: "已將帳戶設定為貓" description: "已將帳戶設定為貓"
flavor: "還沒有名字。" flavor: "還沒有名字。"
_following1: _following1:
@ -1214,6 +1243,8 @@ _role:
iconUrl: "圖示的URL" iconUrl: "圖示的URL"
asBadge: "顯示為徽章" asBadge: "顯示為徽章"
descriptionOfAsBadge: "開啟的話,角色圖示會顯示在用戶名旁邊。" descriptionOfAsBadge: "開啟的話,角色圖示會顯示在用戶名旁邊。"
displayOrder: "顯示順序"
descriptionOfDisplayOrder: "數字越大顯示在UI上的越上面。"
canEditMembersByModerator: "允許編輯審查員的成員" canEditMembersByModerator: "允許編輯審查員的成員"
descriptionOfCanEditMembersByModerator: "如果開啟,管理員與審查員都可以為使用者指派/解除指派該角色。如果關閉,則只有管理員可以執行。" descriptionOfCanEditMembersByModerator: "如果開啟,管理員與審查員都可以為使用者指派/解除指派該角色。如果關閉,則只有管理員可以執行。"
priority: "優先級" priority: "優先級"
@ -1239,6 +1270,7 @@ _role:
rateLimitFactor: "速率限制" rateLimitFactor: "速率限制"
descriptionOfRateLimitFactor: "值越小限制越少,值越大限制越多。" descriptionOfRateLimitFactor: "值越小限制越少,值越大限制越多。"
canHideAds: "不顯示廣告" canHideAds: "不顯示廣告"
canSearchNotes: "可否搜尋貼文"
_condition: _condition:
isLocal: "本地使用者" isLocal: "本地使用者"
isRemote: "遠端使用者" isRemote: "遠端使用者"
@ -1248,6 +1280,8 @@ _role:
followersMoreThanOrEq: "追隨者人數在~以上" followersMoreThanOrEq: "追隨者人數在~以上"
followingLessThanOrEq: "追隨人數在~以下" followingLessThanOrEq: "追隨人數在~以下"
followingMoreThanOrEq: "追隨人數在~以上" followingMoreThanOrEq: "追隨人數在~以上"
notesLessThanOrEq: "發布數在~以下"
notesMoreThanOrEq: "發布數在~以上"
and: "~和~" and: "~和~"
or: "~或~" or: "~或~"
not: "~否" not: "~否"
@ -1477,7 +1511,7 @@ _time:
_tutorial: _tutorial:
title: "Misskey使用方法" title: "Misskey使用方法"
step1_1: "歡迎!" step1_1: "歡迎!"
step1_2: "此為「時間軸」頁面,它會按照時間順序顯示你「追隨」的人發出的「貼文」" step1_2: "此為「時間軸」頁面,它會按照時間順序顯示你「追隨」的人發出的「貼文」"
step1_3: "由於你沒有發佈任何貼文,也沒有追隨任何人,所以你的時間軸目前是空的。" step1_3: "由於你沒有發佈任何貼文,也沒有追隨任何人,所以你的時間軸目前是空的。"
step2_1: "在發文或追隨其他人之前先讓我們設定一下個人資料吧。" step2_1: "在發文或追隨其他人之前先讓我們設定一下個人資料吧。"
step2_2: "提供一些關於自己的資訊來讓其他人更有追隨你的意願。" step2_2: "提供一些關於自己的資訊來讓其他人更有追隨你的意願。"
@ -1710,7 +1744,7 @@ _instanceCharts:
_timelines: _timelines:
home: "首頁" home: "首頁"
local: "本地" local: "本地"
social: "社" social: "社"
global: "公開" global: "公開"
_play: _play:
new: "新增Play" new: "新增Play"
@ -1840,3 +1874,23 @@ _deck:
_dialog: _dialog:
charactersExceeded: "已超過最大字數!現在 {current} / 限制 {max}" charactersExceeded: "已超過最大字數!現在 {current} / 限制 {max}"
charactersBelow: "低於最少字數!現在 {current} / 限制 {max}" charactersBelow: "低於最少字數!現在 {current} / 限制 {max}"
_disabledTimeline:
title: "停用的時間軸"
description: "目前的角色無法使用這個時間軸。"
_drivecleaner:
orderBySizeDesc: "檔案由大到小"
orderByCreatedAtAsc: "依照加入的日期順序"
_webhookSettings:
createWebhook: "建立 Webhook"
name: "名稱"
secret: "秘密"
events: "什麼時候運行Webhook"
active: "已啟用"
_events:
follow: "當你追隨時"
followed: "當被追隨時"
note: "當發布貼文時"
reply: "當收到回覆時"
renote: "當被轉發時"
reaction: "當獲得反應時"
mention: "當被提到時"

View File

@ -1,12 +1,12 @@
{ {
"name": "misskey", "name": "misskey",
"version": "13.9.1", "version": "13.11.0",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/misskey-dev/misskey.git" "url": "https://github.com/misskey-dev/misskey.git"
}, },
"packageManager": "pnpm@7.27.0", "packageManager": "pnpm@8.1.1",
"workspaces": [ "workspaces": [
"packages/frontend", "packages/frontend",
"packages/backend", "packages/backend",
@ -16,6 +16,7 @@
"scripts": { "scripts": {
"build-pre": "node ./scripts/build-pre.js", "build-pre": "node ./scripts/build-pre.js",
"build": "pnpm build-pre && pnpm -r build && pnpm gulp", "build": "pnpm build-pre && pnpm -r build && pnpm gulp",
"build-storybook": "pnpm --filter frontend build-storybook",
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/index.js", "start": "pnpm check:connect && cd packages/backend && node ./built/boot/index.js",
"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/index.js", "start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/index.js",
"init": "pnpm migrate", "init": "pnpm migrate",
@ -31,8 +32,8 @@
"e2e": "pnpm start-server-and-test start:test http://localhost:61812 cy:run", "e2e": "pnpm start-server-and-test start:test http://localhost:61812 cy:run",
"jest": "cd packages/backend && pnpm jest", "jest": "cd packages/backend && pnpm jest",
"jest-and-coverage": "cd packages/backend && pnpm jest-and-coverage", "jest-and-coverage": "cd packages/backend && pnpm jest-and-coverage",
"test": "pnpm jest", "test": "pnpm -r test",
"test-and-coverage": "pnpm jest-and-coverage", "test-and-coverage": "pnpm -r test-and-coverage",
"format": "pnpm exec gulp format", "format": "pnpm exec gulp format",
"clean": "node ./scripts/clean.js", "clean": "node ./scripts/clean.js",
"clean-all": "node ./scripts/clean-all.js", "clean-all": "node ./scripts/clean-all.js",
@ -50,17 +51,17 @@
"gulp-replace": "1.1.4", "gulp-replace": "1.1.4",
"gulp-terser": "2.1.0", "gulp-terser": "2.1.0",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"typescript": "4.9.5" "typescript": "5.0.3"
}, },
"devDependencies": { "devDependencies": {
"@types/gulp": "4.0.10", "@types/gulp": "4.0.10",
"@types/gulp-rename": "2.0.1", "@types/gulp-rename": "2.0.1",
"@typescript-eslint/eslint-plugin": "5.53.0", "@typescript-eslint/eslint-plugin": "5.57.1",
"@typescript-eslint/parser": "5.53.0", "@typescript-eslint/parser": "5.57.1",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "12.7.0", "cypress": "12.9.0",
"eslint": "8.35.0", "eslint": "8.37.0",
"start-server-and-test": "1.15.4" "start-server-and-test": "2.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@tensorflow/tfjs-core": "4.2.0" "@tensorflow/tfjs-core": "4.2.0"

View File

@ -19,6 +19,6 @@
</head> </head>
<body> <body>
<redoc spec-url="/api.json" expand-responses="200" expand-single-schema-field="true"></redoc> <redoc spec-url="/api.json" expand-responses="200" expand-single-schema-field="true"></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@2.0.0-rc.50/bundles/redoc.standalone.js" integrity="sha256-WJbngBWN9vp6vkEuzeoSj5tE5saW9Hfj6/SinkzhL2s=" crossorigin="anonymous"></script> <script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>
</body> </body>
</html> </html>

View File

@ -1,8 +1,15 @@
import Redis from 'ioredis';
import { loadConfig } from './built/config.js'; import { loadConfig } from './built/config.js';
import { createRedisConnection } from './built/redis.js';
const config = loadConfig(); const config = loadConfig();
const redis = createRedisConnection(config); const redis = new Redis({
port: config.redis.port,
host: config.redis.host,
family: config.redis.family == null ? 0 : config.redis.family,
password: config.redis.pass,
keyPrefix: `${config.redis.prefix}:`,
db: config.redis.db ?? 0,
});
redis.on('connect', () => redis.disconnect()); redis.on('connect', () => redis.disconnect());
redis.on('error', (e) => { redis.on('error', (e) => {

View File

@ -0,0 +1,16 @@
export class addRenoteMuting1665091090561 {
constructor() {
this.name = 'addRenoteMuting1665091090561';
}
async up(queryRunner) {
await queryRunner.query(`CREATE TABLE "renote_muting" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "muteeId" character varying(32) NOT NULL, "muterId" character varying(32) NOT NULL, CONSTRAINT "PK_renoteMuting_id" PRIMARY KEY ("id"))`);
await queryRunner.query(`CREATE INDEX "IDX_renote_muting_createdAt" ON "muting" ("createdAt") `);
await queryRunner.query(`CREATE INDEX "IDX_renote_muting_muteeId" ON "muting" ("muteeId") `);
await queryRunner.query(`CREATE INDEX "IDX_renote_muting_muterId" ON "muting" ("muterId") `);
}
async down(queryRunner) {
}
}

View File

@ -0,0 +1,15 @@
export class fixforeignkeyreports1675053125067 {
name = 'fixforeignkeyreports1675053125067'
async up(queryRunner) {
await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_a9021cc2e1feb5f72d3db6e9f5" ON "abuse_user_report" ("targetUserId")`);
await queryRunner.query(`DELETE FROM "abuse_user_report" WHERE "targetUserId" NOT IN (SELECT "id" FROM "user")`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT IF EXISTS "FK_a9021cc2e1feb5f72d3db6e9f5f"`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_a9021cc2e1feb5f72d3db6e9f5f" FOREIGN KEY ("targetUserId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
async down(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_a9021cc2e1feb5f72d3db6e9f5"`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT "FK_a9021cc2e1feb5f72d3db6e9f5f"`);
}
}

View File

@ -0,0 +1,11 @@
export class perNoteReactionAcceptance1678164627293 {
name = 'perNoteReactionAcceptance1678164627293'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" ADD "reactionAcceptance" character varying(64)`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "reactionAcceptance"`);
}
}

View File

@ -0,0 +1,68 @@
export class tweakVarcharLength1678426061773 {
name = 'tweakVarcharLength1678426061773'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "name" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "maintainerName" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "maintainerEmail" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "langs" TYPE character varying(1024) array`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "pinnedUsers" TYPE character varying(1024) array`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "hiddenTags" TYPE character varying(1024) array`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "blockedHosts" TYPE character varying(1024) array`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "themeColor" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "mascotImageUrl" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "bannerUrl" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "backgroundImageUrl" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "logoImageUrl" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "errorImageUrl" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "iconUrl" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "hcaptchaSiteKey" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "hcaptchaSecretKey" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "recaptchaSiteKey" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "recaptchaSecretKey" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "turnstileSiteKey" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "turnstileSecretKey" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "summalyProxy" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "email" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "smtpHost" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "smtpUser" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "smtpPass" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "swPublicKey" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "swPrivateKey" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "deeplAuthKey" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" RENAME COLUMN "ToSUrl" TO "termsOfServiceUrl"`);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "termsOfServiceUrl" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "repositoryUrl" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "feedbackUrl" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "objectStorageBucket" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "objectStoragePrefix" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "objectStorageBaseUrl" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "objectStorageEndpoint" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "objectStorageRegion" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "objectStorageAccessKey" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "objectStorageSecretKey" TYPE character varying(1024)`, undefined);
await queryRunner.query(`ALTER TABLE "flash" ALTER COLUMN "script" TYPE character varying(65536)`, undefined);
await queryRunner.query(`ALTER TABLE "__chart__active_users" ALTER COLUMN "___readWrite" TYPE integer`);
await queryRunner.query(`ALTER TABLE "__chart__active_users" ALTER COLUMN "___read" TYPE integer`);
await queryRunner.query(`ALTER TABLE "__chart__active_users" ALTER COLUMN "___write" TYPE integer`);
await queryRunner.query(`ALTER TABLE "__chart__active_users" ALTER COLUMN "___registeredWithinWeek" TYPE integer`);
await queryRunner.query(`ALTER TABLE "__chart__active_users" ALTER COLUMN "___registeredWithinMonth" TYPE integer`);
await queryRunner.query(`ALTER TABLE "__chart__active_users" ALTER COLUMN "___registeredWithinYear" TYPE integer`);
await queryRunner.query(`ALTER TABLE "__chart__active_users" ALTER COLUMN "___registeredOutsideWeek" TYPE integer`);
await queryRunner.query(`ALTER TABLE "__chart__active_users" ALTER COLUMN "___registeredOutsideMonth" TYPE integer`);
await queryRunner.query(`ALTER TABLE "__chart__active_users" ALTER COLUMN "___registeredOutsideYear" TYPE integer`);
await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ALTER COLUMN "___readWrite" TYPE integer`);
await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ALTER COLUMN "___read" TYPE integer`);
await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ALTER COLUMN "___write" TYPE integer`);
await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ALTER COLUMN "___registeredWithinWeek" TYPE integer`);
await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ALTER COLUMN "___registeredWithinMonth" TYPE integer`);
await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ALTER COLUMN "___registeredWithinYear" TYPE integer`);
await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ALTER COLUMN "___registeredOutsideWeek" TYPE integer`);
await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ALTER COLUMN "___registeredOutsideMonth" TYPE integer`);
await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ALTER COLUMN "___registeredOutsideYear" TYPE integer`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" RENAME COLUMN "termsOfServiceUrl" TO "ToSUrl"`);
}
}

View File

@ -0,0 +1,13 @@
export class removeUnused1678427401214 {
name = 'removeUnused1678427401214'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "pinnedPages"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "pinnedClipId"`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "pinnedClipId" character varying(32)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "pinnedPages" character varying(512) array NOT NULL DEFAULT '{/featured,/channels,/explore,/pages,/about-misskey}'`);
}
}

View File

@ -0,0 +1,11 @@
export class roleDisplayOrder1678602320354 {
name = 'roleDisplayOrder1678602320354'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "role" ADD "displayOrder" integer NOT NULL DEFAULT '0'`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "displayOrder"`);
}
}

View File

@ -0,0 +1,11 @@
export class sensitiveWords1678694614599 {
name = 'sensitiveWords1678694614599'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "sensitiveWords" character varying(1024) array NOT NULL DEFAULT '{}'`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "sensitiveWords"`);
}
}

View File

@ -0,0 +1,14 @@
export class retentionDateKey1678869617549 {
name = 'retentionDateKey1678869617549'
async up(queryRunner) {
await queryRunner.query(`TRUNCATE TABLE "retention_aggregation"`, undefined);
await queryRunner.query(`ALTER TABLE "retention_aggregation" ADD "dateKey" character varying(512) NOT NULL`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_f7c3576b37bd2eec966ae24477" ON "retention_aggregation" ("dateKey") `);
}
async down(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_f7c3576b37bd2eec966ae24477"`);
await queryRunner.query(`ALTER TABLE "retention_aggregation" DROP COLUMN "dateKey"`);
}
}

View File

@ -0,0 +1,11 @@
export class addPropsForCustomEmoji1678945242650 {
name = 'addPropsForCustomEmoji1678945242650'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "emoji" ADD "license" character varying(1024)`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "license"`);
}
}

View File

@ -0,0 +1,23 @@
export class clipFavorite1678953978856 {
name = 'clipFavorite1678953978856'
async up(queryRunner) {
await queryRunner.query(`CREATE TABLE "clip_favorite" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "clipId" character varying(32) NOT NULL, CONSTRAINT "PK_1b539f43906f05ebcabe752a977" PRIMARY KEY ("id"))`);
await queryRunner.query(`CREATE INDEX "IDX_25a31662b0b0cc9af6549a9d71" ON "clip_favorite" ("userId") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_b1754a39d0b281e07ed7c078ec" ON "clip_favorite" ("userId", "clipId") `);
await queryRunner.query(`ALTER TABLE "clip" ADD "lastClippedAt" TIMESTAMP WITH TIME ZONE`);
await queryRunner.query(`CREATE INDEX "IDX_a3eac04ae2aa9e221e7596114a" ON "clip" ("lastClippedAt") `);
await queryRunner.query(`ALTER TABLE "clip_favorite" ADD CONSTRAINT "FK_25a31662b0b0cc9af6549a9d711" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "clip_favorite" ADD CONSTRAINT "FK_fce61c7986cee54393e79f1d849" FOREIGN KEY ("clipId") REFERENCES "clip"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "clip_favorite" DROP CONSTRAINT "FK_fce61c7986cee54393e79f1d849"`);
await queryRunner.query(`ALTER TABLE "clip_favorite" DROP CONSTRAINT "FK_25a31662b0b0cc9af6549a9d711"`);
await queryRunner.query(`DROP INDEX "public"."IDX_a3eac04ae2aa9e221e7596114a"`);
await queryRunner.query(`ALTER TABLE "clip" DROP COLUMN "lastClippedAt"`);
await queryRunner.query(`DROP INDEX "public"."IDX_b1754a39d0b281e07ed7c078ec"`);
await queryRunner.query(`DROP INDEX "public"."IDX_25a31662b0b0cc9af6549a9d71"`);
await queryRunner.query(`DROP TABLE "clip_favorite"`);
}
}

View File

@ -0,0 +1,17 @@
export class antennaActive1679309757174 {
name = 'antennaActive1679309757174'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "antenna" ADD "lastUsedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT 'now'`);
await queryRunner.query(`ALTER TABLE "antenna" ADD "isActive" boolean NOT NULL DEFAULT true`);
await queryRunner.query(`CREATE INDEX "IDX_084c2abb8948ef59a37dce6ac1" ON "antenna" ("lastUsedAt") `);
await queryRunner.query(`CREATE INDEX "IDX_36ef5192a1ce55ed0e40aa4db5" ON "antenna" ("isActive") `);
}
async down(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_36ef5192a1ce55ed0e40aa4db5"`);
await queryRunner.query(`DROP INDEX "public"."IDX_084c2abb8948ef59a37dce6ac1"`);
await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "isActive"`);
await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "lastUsedAt"`);
}
}

View File

@ -0,0 +1,11 @@
export class enableChartsForRemoteUser1679639483253 {
name = 'enableChartsForRemoteUser1679639483253'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "enableChartsForRemoteUser" boolean NOT NULL DEFAULT true`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableChartsForRemoteUser"`);
}
}

View File

@ -0,0 +1,11 @@
export class cleanup1679651580149 {
name = 'cleanup1679651580149'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "useStarForReactionFallback"`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "useStarForReactionFallback" boolean NOT NULL DEFAULT false`);
}
}

View File

@ -0,0 +1,11 @@
export class enableChartsForFederatedInstances1679652081809 {
name = 'enableChartsForFederatedInstances1679652081809'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "enableChartsForFederatedInstances" boolean NOT NULL DEFAULT true`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableChartsForFederatedInstances"`);
}
}

View File

@ -0,0 +1,21 @@
export class channelFavorite1680228513388 {
name = 'channelFavorite1680228513388'
async up(queryRunner) {
await queryRunner.query(`CREATE TABLE "channel_favorite" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "channelId" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, CONSTRAINT "PK_59bddfd54d48689a298d41af00c" PRIMARY KEY ("id")); COMMENT ON COLUMN "channel_favorite"."createdAt" IS 'The created date of the ChannelFavorite.'`);
await queryRunner.query(`CREATE INDEX "IDX_735a5544f9249d412255f47f95" ON "channel_favorite" ("createdAt") `);
await queryRunner.query(`CREATE INDEX "IDX_d3ca0db011b75ac2a940a2337d" ON "channel_favorite" ("channelId") `);
await queryRunner.query(`CREATE INDEX "IDX_8302bd27226605ece14842fb25" ON "channel_favorite" ("userId") `);
await queryRunner.query(`ALTER TABLE "channel_favorite" ADD CONSTRAINT "FK_d3ca0db011b75ac2a940a2337d2" FOREIGN KEY ("channelId") REFERENCES "channel"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "channel_favorite" ADD CONSTRAINT "FK_8302bd27226605ece14842fb25a" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "channel_favorite" DROP CONSTRAINT "FK_8302bd27226605ece14842fb25a"`);
await queryRunner.query(`ALTER TABLE "channel_favorite" DROP CONSTRAINT "FK_d3ca0db011b75ac2a940a2337d2"`);
await queryRunner.query(`DROP INDEX "public"."IDX_8302bd27226605ece14842fb25"`);
await queryRunner.query(`DROP INDEX "public"."IDX_d3ca0db011b75ac2a940a2337d"`);
await queryRunner.query(`DROP INDEX "public"."IDX_735a5544f9249d412255f47f95"`);
await queryRunner.query(`DROP TABLE "channel_favorite"`);
}
}

View File

@ -0,0 +1,11 @@
export class channelNotePining1680238118084 {
name = 'channelNotePining1680238118084'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "channel" ADD "pinnedNoteIds" character varying(128) array NOT NULL DEFAULT '{}'`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "channel" DROP COLUMN "pinnedNoteIds"`);
}
}

View File

@ -0,0 +1,10 @@
export class cleanup1680491187535 {
name = 'cleanup1680491187535'
async up(queryRunner) {
await queryRunner.query(`DROP TABLE "antenna_note" `);
}
async down(queryRunner) {
}
}

View File

@ -0,0 +1,11 @@
export class cleanup1680582195041 {
name = 'cleanup1680582195041'
async up(queryRunner) {
await queryRunner.query(`DROP TABLE "notification" `);
}
async down(queryRunner) {
}
}

View File

@ -0,0 +1,17 @@
export class AvatarUrlAndBannerUrl1680775031481 {
name = 'AvatarUrlAndBannerUrl1680775031481'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "user" ADD "avatarUrl" character varying(512)`);
await queryRunner.query(`ALTER TABLE "user" ADD "bannerUrl" character varying(512)`);
await queryRunner.query(`ALTER TABLE "user" ADD "avatarBlurhash" character varying(128)`);
await queryRunner.query(`ALTER TABLE "user" ADD "bannerBlurhash" character varying(128)`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "bannerBlurhash"`);
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "avatarBlurhash"`);
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "bannerUrl"`);
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "avatarUrl"`);
}
}

View File

@ -0,0 +1,17 @@
export class AccountMove1680931179228 {
name = 'AccountMove1680931179228'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "user" ADD "movedToUri" character varying(512)`);
await queryRunner.query(`COMMENT ON COLUMN "user"."movedToUri" IS 'The URI of the new account of the User'`);
await queryRunner.query(`ALTER TABLE "user" ADD "alsoKnownAs" text`);
await queryRunner.query(`COMMENT ON COLUMN "user"."alsoKnownAs" IS 'URIs the user is known as too'`);
}
async down(queryRunner) {
await queryRunner.query(`COMMENT ON COLUMN "user"."alsoKnownAs" IS 'URIs the user is known as too'`);
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "alsoKnownAs"`);
await queryRunner.query(`COMMENT ON COLUMN "user"."movedToUri" IS 'The URI of the new account of the User'`);
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "movedToUri"`);
}
}

View File

@ -22,44 +22,46 @@
"test-and-coverage": "pnpm jest-and-coverage" "test-and-coverage": "pnpm jest-and-coverage"
}, },
"optionalDependencies": { "optionalDependencies": {
"@swc/core-android-arm64": "^1.3.11", "@swc/core-android-arm64": "1.3.11",
"@swc/core-darwin-arm64": "^1.3.36", "@swc/core-darwin-arm64": "1.3.46",
"@swc/core-darwin-x64": "^1.3.36", "@swc/core-darwin-x64": "1.3.46",
"@swc/core-linux-arm-gnueabihf": "^1.3.36", "@swc/core-linux-arm-gnueabihf": "1.3.46",
"@swc/core-linux-arm64-gnu": "^1.3.36", "@swc/core-linux-arm64-gnu": "1.3.46",
"@swc/core-linux-arm64-musl": "^1.3.36", "@swc/core-linux-arm64-musl": "1.3.46",
"@swc/core-linux-x64-gnu": "^1.3.36", "@swc/core-linux-x64-gnu": "1.3.46",
"@swc/core-linux-x64-musl": "^1.3.36", "@swc/core-linux-x64-musl": "1.3.46",
"@swc/core-win32-arm64-msvc": "^1.3.36", "@swc/core-win32-arm64-msvc": "1.3.46",
"@swc/core-win32-ia32-msvc": "^1.3.36", "@swc/core-win32-ia32-msvc": "1.3.46",
"@swc/core-win32-x64-msvc": "^1.3.36", "@swc/core-win32-x64-msvc": "1.3.46",
"@tensorflow/tfjs": "4.2.0", "@tensorflow/tfjs": "4.2.0",
"@tensorflow/tfjs-node": "4.2.0" "@tensorflow/tfjs-node": "4.2.0"
}, },
"dependencies": { "dependencies": {
"@bull-board/api": "4.12.1", "@aws-sdk/client-s3": "3.306.0",
"@bull-board/fastify": "4.12.1", "@aws-sdk/lib-storage": "3.306.0",
"@bull-board/ui": "4.12.1", "@aws-sdk/node-http-handler": "3.306.0",
"@discordapp/twemoji": "14.0.2", "@bull-board/api": "5.0.0",
"@bull-board/fastify": "5.0.0",
"@bull-board/ui": "5.0.0",
"@discordapp/twemoji": "14.1.2",
"@fastify/accepts": "4.1.0", "@fastify/accepts": "4.1.0",
"@fastify/cookie": "8.3.0", "@fastify/cookie": "8.3.0",
"@fastify/cors": "8.2.0", "@fastify/cors": "8.2.1",
"@fastify/http-proxy": "8.4.0", "@fastify/http-proxy": "9.0.0",
"@fastify/multipart": "7.4.1", "@fastify/multipart": "7.5.0",
"@fastify/static": "6.9.0", "@fastify/static": "6.10.0",
"@fastify/view": "7.4.1", "@fastify/view": "7.4.1",
"@nestjs/common": "9.3.9", "@nestjs/common": "9.4.0",
"@nestjs/core": "9.3.9", "@nestjs/core": "9.4.0",
"@nestjs/testing": "9.3.9", "@nestjs/testing": "9.4.0",
"@peertube/http-signature": "1.7.0", "@peertube/http-signature": "1.7.0",
"@sinonjs/fake-timers": "10.0.2", "@sinonjs/fake-timers": "10.0.2",
"@swc/cli": "0.1.62", "@swc/cli": "0.1.62",
"@swc/core": "1.3.36", "@swc/core": "1.3.46",
"accepts": "1.3.8", "accepts": "1.3.8",
"ajv": "8.12.0", "ajv": "8.12.0",
"archiver": "5.3.1", "archiver": "5.3.1",
"autwh": "0.1.0", "autwh": "0.1.0",
"aws-sdk": "2.1318.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "2.0.5", "blurhash": "2.0.5",
"bull": "4.10.4", "bull": "4.10.4",
@ -74,35 +76,35 @@
"date-fns": "2.29.3", "date-fns": "2.29.3",
"deep-email-validator": "0.1.21", "deep-email-validator": "0.1.21",
"escape-regexp": "0.0.1", "escape-regexp": "0.0.1",
"fastify": "4.13.0", "fastify": "4.15.0",
"feed": "4.2.2", "feed": "4.2.2",
"file-type": "18.2.1", "file-type": "18.2.1",
"fluent-ffmpeg": "2.1.2", "fluent-ffmpeg": "2.1.2",
"form-data": "4.0.0", "form-data": "4.0.0",
"got": "12.5.3", "got": "12.6.0",
"happy-dom": "8.9.0", "happy-dom": "8.9.0",
"hpagent": "1.2.0", "hpagent": "1.2.0",
"ioredis": "4.28.5", "ioredis": "4.28.5",
"ip-cidr": "3.1.0", "ip-cidr": "3.1.0",
"is-svg": "4.3.2", "is-svg": "4.3.2",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"jsdom": "21.1.0", "jsdom": "21.1.1",
"json5": "2.2.3", "json5": "2.2.3",
"jsonld": "8.1.1", "jsonld": "8.1.1",
"jsrsasign": "10.6.1", "jsrsasign": "10.7.0",
"mfm-js": "0.23.3", "mfm-js": "0.23.3",
"mime-types": "2.1.35", "mime-types": "2.1.35",
"misskey-js": "0.0.15", "misskey-js": "workspace:*",
"ms": "3.0.0-canary.1", "ms": "3.0.0-canary.1",
"nested-property": "4.0.0", "nested-property": "4.0.0",
"node-fetch": "3.3.0", "node-fetch": "3.3.1",
"nodemailer": "6.9.1", "nodemailer": "6.9.1",
"nsfwjs": "2.4.2", "nsfwjs": "2.4.2",
"oauth": "0.10.0", "oauth": "0.10.0",
"os-utils": "0.0.14", "os-utils": "0.0.14",
"otpauth": "^9.0.2", "otpauth": "9.1.1",
"parse5": "7.1.2", "parse5": "7.1.2",
"pg": "8.9.0", "pg": "8.10.0",
"private-ip": "3.0.0", "private-ip": "3.0.0",
"probe-image-size": "7.2.3", "probe-image-size": "7.2.3",
"promise-limit": "2.7.0", "promise-limit": "2.7.0",
@ -123,32 +125,33 @@
"sanitize-html": "2.10.0", "sanitize-html": "2.10.0",
"seedrandom": "3.0.5", "seedrandom": "3.0.5",
"semver": "7.3.8", "semver": "7.3.8",
"sharp": "0.31.3", "sharp": "0.32.0",
"sharp-read-bmp": "github:misskey-dev/sharp-read-bmp",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0", "stringz": "2.1.0",
"summaly": "github:misskey-dev/summaly", "summaly": "github:misskey-dev/summaly",
"systeminformation": "5.17.10", "systeminformation": "5.17.12",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tmp": "0.2.1", "tmp": "0.2.1",
"tsc-alias": "1.8.2", "tsc-alias": "1.8.5",
"tsconfig-paths": "4.1.2", "tsconfig-paths": "4.2.0",
"twemoji-parser": "14.0.0", "twemoji-parser": "14.0.0",
"typeorm": "0.3.11", "typeorm": "0.3.13",
"typescript": "4.9.5", "typescript": "5.0.3",
"ulid": "2.3.0", "ulid": "2.3.0",
"unzipper": "0.10.11", "unzipper": "0.10.11",
"uuid": "9.0.0", "uuid": "9.0.0",
"vary": "1.1.2", "vary": "1.1.2",
"web-push": "3.5.0", "web-push": "3.5.0",
"websocket": "1.0.34", "websocket": "1.0.34",
"ws": "8.12.1", "ws": "8.13.0",
"xev": "3.0.2" "xev": "3.0.2"
}, },
"devDependencies": { "devDependencies": {
"@jest/globals": "29.4.3", "@jest/globals": "29.5.0",
"@swc/jest": "0.2.24", "@swc/jest": "0.2.24",
"@types/accepts": "1.3.5", "@types/accepts": "1.3.5",
"@types/archiver": "5.3.1", "@types/archiver": "5.3.2",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/bull": "4.10.0", "@types/bull": "4.10.0",
"@types/cbor": "6.0.0", "@types/cbor": "6.0.0",
@ -157,13 +160,13 @@
"@types/escape-regexp": "0.0.1", "@types/escape-regexp": "0.0.1",
"@types/fluent-ffmpeg": "2.1.21", "@types/fluent-ffmpeg": "2.1.21",
"@types/ioredis": "4.28.10", "@types/ioredis": "4.28.10",
"@types/jest": "29.4.0", "@types/jest": "29.5.0",
"@types/js-yaml": "4.0.5", "@types/js-yaml": "4.0.5",
"@types/jsdom": "21.1.0", "@types/jsdom": "21.1.1",
"@types/jsonld": "1.5.8", "@types/jsonld": "1.5.8",
"@types/jsrsasign": "10.5.5", "@types/jsrsasign": "10.5.8",
"@types/mime-types": "2.1.1", "@types/mime-types": "2.1.1",
"@types/node": "18.14.1", "@types/node": "18.15.11",
"@types/node-fetch": "3.0.3", "@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.7", "@types/nodemailer": "6.4.7",
"@types/oauth": "0.9.1", "@types/oauth": "0.9.1",
@ -175,7 +178,7 @@
"@types/ratelimiter": "3.4.4", "@types/ratelimiter": "3.4.4",
"@types/redis": "4.0.11", "@types/redis": "4.0.11",
"@types/rename": "1.0.4", "@types/rename": "1.0.4",
"@types/sanitize-html": "2.8.0", "@types/sanitize-html": "2.9.0",
"@types/semver": "7.3.13", "@types/semver": "7.3.13",
"@types/sharp": "0.31.1", "@types/sharp": "0.31.1",
"@types/sinonjs__fake-timers": "8.1.2", "@types/sinonjs__fake-timers": "8.1.2",
@ -187,13 +190,14 @@
"@types/web-push": "3.3.2", "@types/web-push": "3.3.2",
"@types/websocket": "1.0.5", "@types/websocket": "1.0.5",
"@types/ws": "8.5.4", "@types/ws": "8.5.4",
"@typescript-eslint/eslint-plugin": "5.52.0", "@typescript-eslint/eslint-plugin": "5.57.1",
"@typescript-eslint/parser": "5.53.0", "@typescript-eslint/parser": "5.57.1",
"aws-sdk-client-mock": "^2.1.1",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint": "8.35.0", "eslint": "8.37.0",
"eslint-plugin-import": "2.27.5", "eslint-plugin-import": "2.27.5",
"execa": "6.1.0", "execa": "6.1.0",
"jest": "29.4.3", "jest": "29.5.0",
"jest-mock": "29.4.3" "jest-mock": "29.5.0"
} }
} }

View File

@ -2,18 +2,15 @@ import { setTimeout } from 'node:timers/promises';
import { Global, Inject, Module } from '@nestjs/common'; import { Global, Inject, Module } from '@nestjs/common';
import Redis from 'ioredis'; import Redis from 'ioredis';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import { createRedisConnection } from '@/redis.js';
import { DI } from './di-symbols.js'; import { DI } from './di-symbols.js';
import { loadConfig } from './config.js'; import { loadConfig } from './config.js';
import { createPostgresDataSource } from './postgres.js'; import { createPostgresDataSource } from './postgres.js';
import { RepositoryModule } from './models/RepositoryModule.js'; import { RepositoryModule } from './models/RepositoryModule.js';
import type { Provider, OnApplicationShutdown } from '@nestjs/common'; import type { Provider, OnApplicationShutdown } from '@nestjs/common';
const config = loadConfig();
const $config: Provider = { const $config: Provider = {
provide: DI.config, provide: DI.config,
useValue: config, useValue: loadConfig(),
}; };
const $db: Provider = { const $db: Provider = {
@ -28,18 +25,31 @@ const $db: Provider = {
const $redis: Provider = { const $redis: Provider = {
provide: DI.redis, provide: DI.redis,
useFactory: (config) => { useFactory: (config) => {
const redisClient = createRedisConnection(config); return new Redis({
return redisClient; port: config.redis.port,
host: config.redis.host,
family: config.redis.family == null ? 0 : config.redis.family,
password: config.redis.pass,
keyPrefix: `${config.redis.prefix}:`,
db: config.redis.db ?? 0,
});
}, },
inject: [DI.config], inject: [DI.config],
}; };
const $redisSubscriber: Provider = { const $redisForPubsub: Provider = {
provide: DI.redisSubscriber, provide: DI.redisForPubsub,
useFactory: (config) => { useFactory: (config) => {
const redisSubscriber = createRedisConnection(config); const redis = new Redis({
redisSubscriber.subscribe(config.host); port: config.redisForPubsub.port,
return redisSubscriber; host: config.redisForPubsub.host,
family: config.redisForPubsub.family == null ? 0 : config.redisForPubsub.family,
password: config.redisForPubsub.pass,
keyPrefix: `${config.redisForPubsub.prefix}:`,
db: config.redisForPubsub.db ?? 0,
});
redis.subscribe(config.host);
return redis;
}, },
inject: [DI.config], inject: [DI.config],
}; };
@ -47,14 +57,14 @@ const $redisSubscriber: Provider = {
@Global() @Global()
@Module({ @Module({
imports: [RepositoryModule], imports: [RepositoryModule],
providers: [$config, $db, $redis, $redisSubscriber], providers: [$config, $db, $redis, $redisForPubsub],
exports: [$config, $db, $redis, $redisSubscriber, RepositoryModule], exports: [$config, $db, $redis, $redisForPubsub, RepositoryModule],
}) })
export class GlobalModule implements OnApplicationShutdown { export class GlobalModule implements OnApplicationShutdown {
constructor( constructor(
@Inject(DI.db) private db: DataSource, @Inject(DI.db) private db: DataSource,
@Inject(DI.redis) private redisClient: Redis.Redis, @Inject(DI.redis) private redisClient: Redis.Redis,
@Inject(DI.redisSubscriber) private redisSubscriber: Redis.Redis, @Inject(DI.redisForPubsub) private redisForPubsub: Redis.Redis,
) {} ) {}
async onApplicationShutdown(signal: string): Promise<void> { async onApplicationShutdown(signal: string): Promise<void> {
@ -69,7 +79,7 @@ export class GlobalModule implements OnApplicationShutdown {
await Promise.all([ await Promise.all([
this.db.destroy(), this.db.destroy(),
this.redisClient.disconnect(), this.redisClient.disconnect(),
this.redisSubscriber.disconnect(), this.redisForPubsub.disconnect(),
]); ]);
} }
} }

View File

@ -25,6 +25,14 @@ export type Source = {
disableCache?: boolean; disableCache?: boolean;
extra?: { [x: string]: string }; extra?: { [x: string]: string };
}; };
dbReplications?: boolean;
dbSlaves?: {
host: string;
port: number;
db: string;
user: string;
pass: string;
}[];
redis: { redis: {
host: string; host: string;
port: number; port: number;
@ -33,6 +41,22 @@ export type Source = {
db?: number; db?: number;
prefix?: string; prefix?: string;
}; };
redisForPubsub?: {
host: string;
port: number;
family?: number;
pass: string;
db?: number;
prefix?: string;
};
redisForJobQueue?: {
host: string;
port: number;
family?: number;
pass: string;
db?: number;
prefix?: string;
};
elasticsearch: { elasticsearch: {
host: string; host: string;
port: number; port: number;
@ -93,6 +117,8 @@ export type Mixin = {
mediaProxy: string; mediaProxy: string;
externalMediaProxyEnabled: boolean; externalMediaProxyEnabled: boolean;
videoThumbnailGenerator: string | null; videoThumbnailGenerator: string | null;
redisForPubsub: NonNullable<Source['redisForPubsub']>;
redisForJobQueue: NonNullable<Source['redisForJobQueue']>;
}; };
export type Config = Source & Mixin; export type Config = Source & Mixin;
@ -153,6 +179,8 @@ export function loadConfig() {
: null; : null;
if (!config.redis.prefix) config.redis.prefix = mixin.host; if (!config.redis.prefix) config.redis.prefix = mixin.host;
if (config.redisForPubsub == null) config.redisForPubsub = config.redis;
if (config.redisForJobQueue == null) config.redisForJobQueue = config.redis;
return Object.assign(config, mixin); return Object.assign(config, mixin);
} }

View File

@ -0,0 +1,114 @@
import { Inject, Injectable } from '@nestjs/common';
import { IsNull } from 'typeorm';
import { bindThis } from '@/decorators.js';
import { DI } from '@/di-symbols.js';
import type { LocalUser } from '@/models/entities/User.js';
import { User } from '@/models/entities/User.js';
import type { FollowingsRepository, UsersRepository } from '@/models/index.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { UserFollowingService } from '@/core/UserFollowingService.js';
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { AccountUpdateService } from '@/core/AccountUpdateService.js';
import { RelayService } from '@/core/RelayService.js';
@Injectable()
export class AccountMoveService {
constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,
private userEntityService: UserEntityService,
private apRendererService: ApRendererService,
private apDeliverManagerService: ApDeliverManagerService,
private globalEventService: GlobalEventService,
private userFollowingService: UserFollowingService,
private accountUpdateService: AccountUpdateService,
private relayService: RelayService,
) {
}
/**
* Move a local account to a remote account.
*
* After delivering Move activity, its local followers unfollow the old account and then follow the new one.
*/
@bindThis
public async moveToRemote(src: LocalUser, dst: User): Promise<unknown> {
// Make sure that the destination is a remote account.
if (this.userEntityService.isLocalUser(dst)) throw new Error('move destiantion is not remote');
if (!dst.uri) throw new Error('destination uri is empty');
// add movedToUri to indicate that the user has moved
const update = {} as Partial<User>;
update.alsoKnownAs = src.alsoKnownAs?.concat([dst.uri]) ?? [dst.uri];
update.movedToUri = dst.uri;
await this.usersRepository.update(src.id, update);
const srcPerson = await this.apRendererService.renderPerson(src);
const updateAct = this.apRendererService.addContext(this.apRendererService.renderUpdate(srcPerson, src));
await this.apDeliverManagerService.deliverToFollowers(src, updateAct);
this.relayService.deliverToRelays(src, updateAct);
// Deliver Move activity to the followers of the old account
const moveAct = this.apRendererService.addContext(this.apRendererService.renderMove(src, dst));
await this.apDeliverManagerService.deliverToFollowers(src, moveAct);
// Publish meUpdated event
const iObj = await this.userEntityService.pack<true, true>(src.id, src, { detail: true, includeSecrets: true });
this.globalEventService.publishMainStream(src.id, 'meUpdated', iObj);
// follow the new account and unfollow the old one
const followings = await this.followingsRepository.find({
relations: {
follower: true,
},
where: {
followeeId: src.id,
followerHost: IsNull(), // follower is local
},
});
for (const following of followings) {
if (!following.follower) continue;
try {
await this.userFollowingService.follow(following.follower, dst);
await this.userFollowingService.unfollow(following.follower, src);
} catch {
/* empty */
}
}
return iObj;
}
/**
* Create an alias of an old remote account.
*
* The user's new profile will be published to the followers.
*/
@bindThis
public async createAlias(me: LocalUser, updates: Partial<User>): Promise<unknown> {
await this.usersRepository.update(me.id, updates);
// Publish meUpdated event
const iObj = await this.userEntityService.pack<true, true>(me.id, me, {
detail: true,
includeSecrets: true,
});
this.globalEventService.publishMainStream(me.id, 'meUpdated', iObj);
if (me.isLocked === false) {
await this.userFollowingService.acceptAllFollowRequests(me);
}
this.accountUpdateService.publishToFollowers(me.id);
return iObj;
}
}

View File

@ -3,7 +3,7 @@ import type { UserProfilesRepository, UsersRepository } from '@/models/index.js'
import type { User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { CreateNotificationService } from '@/core/CreateNotificationService.js'; import { NotificationService } from '@/core/NotificationService.js';
export const ACHIEVEMENT_TYPES = [ export const ACHIEVEMENT_TYPES = [
'notes1', 'notes1',
@ -90,7 +90,7 @@ export class AchievementService {
@Inject(DI.userProfilesRepository) @Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository, private userProfilesRepository: UserProfilesRepository,
private createNotificationService: CreateNotificationService, private notificationService: NotificationService,
) { ) {
} }
@ -114,7 +114,7 @@ export class AchievementService {
}], }],
}); });
this.createNotificationService.createNotification(userId, 'achievementEarned', { this.notificationService.createNotification(userId, 'achievementEarned', {
achievement: type, achievement: type,
}); });
} }

View File

@ -10,9 +10,9 @@ import { isUserRelated } from '@/misc/is-user-related.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
import { PushNotificationService } from '@/core/PushNotificationService.js'; import { PushNotificationService } from '@/core/PushNotificationService.js';
import * as Acct from '@/misc/acct.js'; import * as Acct from '@/misc/acct.js';
import type { Packed } from '@/misc/schema.js'; import type { Packed } from '@/misc/json-schema.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { MutingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserListJoiningsRepository } from '@/models/index.js'; import type { MutingsRepository, NotesRepository, AntennasRepository, UserListJoiningsRepository } from '@/models/index.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { StreamMessages } from '@/server/api/stream/types.js'; import { StreamMessages } from '@/server/api/stream/types.js';
@ -24,8 +24,11 @@ export class AntennaService implements OnApplicationShutdown {
private antennas: Antenna[]; private antennas: Antenna[];
constructor( constructor(
@Inject(DI.redisSubscriber) @Inject(DI.redis)
private redisSubscriber: Redis.Redis, private redisClient: Redis.Redis,
@Inject(DI.redisForPubsub)
private redisForPubsub: Redis.Redis,
@Inject(DI.mutingsRepository) @Inject(DI.mutingsRepository)
private mutingsRepository: MutingsRepository, private mutingsRepository: MutingsRepository,
@ -33,9 +36,6 @@ export class AntennaService implements OnApplicationShutdown {
@Inject(DI.notesRepository) @Inject(DI.notesRepository)
private notesRepository: NotesRepository, private notesRepository: NotesRepository,
@Inject(DI.antennaNotesRepository)
private antennaNotesRepository: AntennaNotesRepository,
@Inject(DI.antennasRepository) @Inject(DI.antennasRepository)
private antennasRepository: AntennasRepository, private antennasRepository: AntennasRepository,
@ -52,12 +52,12 @@ export class AntennaService implements OnApplicationShutdown {
this.antennasFetched = false; this.antennasFetched = false;
this.antennas = []; this.antennas = [];
this.redisSubscriber.on('message', this.onRedisMessage); this.redisForPubsub.on('message', this.onRedisMessage);
} }
@bindThis @bindThis
public onApplicationShutdown(signal?: string | undefined) { public onApplicationShutdown(signal?: string | undefined) {
this.redisSubscriber.off('message', this.onRedisMessage); this.redisForPubsub.off('message', this.onRedisMessage);
} }
@bindThis @bindThis
@ -71,12 +71,14 @@ export class AntennaService implements OnApplicationShutdown {
this.antennas.push({ this.antennas.push({
...body, ...body,
createdAt: new Date(body.createdAt), createdAt: new Date(body.createdAt),
lastUsedAt: new Date(body.lastUsedAt),
}); });
break; break;
case 'antennaUpdated': case 'antennaUpdated':
this.antennas[this.antennas.findIndex(a => a.id === body.id)] = { this.antennas[this.antennas.findIndex(a => a.id === body.id)] = {
...body, ...body,
createdAt: new Date(body.createdAt), createdAt: new Date(body.createdAt),
lastUsedAt: new Date(body.lastUsedAt),
}; };
break; break;
case 'antennaDeleted': case 'antennaDeleted':
@ -90,54 +92,13 @@ export class AntennaService implements OnApplicationShutdown {
@bindThis @bindThis
public async addNoteToAntenna(antenna: Antenna, note: Note, noteUser: { id: User['id']; }): Promise<void> { public async addNoteToAntenna(antenna: Antenna, note: Note, noteUser: { id: User['id']; }): Promise<void> {
// 通知しない設定になっているか、自分自身の投稿なら既読にする this.redisClient.xadd(
const read = !antenna.notify || (antenna.userId === noteUser.id); `antennaTimeline:${antenna.id}`,
'MAXLEN', '~', '200',
this.antennaNotesRepository.insert({ `${this.idService.parse(note.id).date.getTime()}-*`,
id: this.idService.genId(), 'note', note.id);
antennaId: antenna.id,
noteId: note.id,
read: read,
});
this.globalEventService.publishAntennaStream(antenna.id, 'note', note); this.globalEventService.publishAntennaStream(antenna.id, 'note', note);
if (!read) {
const mutings = await this.mutingsRepository.find({
where: {
muterId: antenna.userId,
},
select: ['muteeId'],
});
// Copy
const _note: Note = {
...note,
};
if (note.replyId != null) {
_note.reply = await this.notesRepository.findOneByOrFail({ id: note.replyId });
}
if (note.renoteId != null) {
_note.renote = await this.notesRepository.findOneByOrFail({ id: note.renoteId });
}
if (isUserRelated(_note, new Set<string>(mutings.map(x => x.muteeId)))) {
return;
}
// 2秒経っても既読にならなかったら通知
setTimeout(async () => {
const unread = await this.antennaNotesRepository.findOneBy({ antennaId: antenna.id, read: false });
if (unread) {
this.globalEventService.publishMainStream(antenna.userId, 'unreadAntenna', antenna);
this.pushNotificationService.pushNotification(antenna.userId, 'unreadAntennaNote', {
antenna: { id: antenna.id, name: antenna.name },
note: await this.noteEntityService.pack(note),
});
}
}, 2000);
}
} }
// NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている // NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている
@ -217,7 +178,9 @@ export class AntennaService implements OnApplicationShutdown {
@bindThis @bindThis
public async getAntennas() { public async getAntennas() {
if (!this.antennasFetched) { if (!this.antennasFetched) {
this.antennas = await this.antennasRepository.find(); this.antennas = await this.antennasRepository.findBy({
isActive: true,
});
this.antennasFetched = true; this.antennasFetched = true;
} }

View File

@ -0,0 +1,172 @@
import { Inject, Injectable } from '@nestjs/common';
import Redis from 'ioredis';
import type { BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, UserProfile, UserProfilesRepository, UsersRepository } from '@/models/index.js';
import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
import type { LocalUser, User } from '@/models/entities/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 { OnApplicationShutdown } from '@nestjs/common';
@Injectable()
export class CacheService implements OnApplicationShutdown {
public userByIdCache: MemoryKVCache<User>;
public localUserByNativeTokenCache: MemoryKVCache<LocalUser | null>;
public localUserByIdCache: MemoryKVCache<LocalUser>;
public uriPersonCache: MemoryKVCache<User | null>;
public userProfileCache: RedisKVCache<UserProfile>;
public userMutingsCache: RedisKVCache<Set<string>>;
public userBlockingCache: RedisKVCache<Set<string>>;
public userBlockedCache: RedisKVCache<Set<string>>; // NOTE: 「被」Blockキャッシュ
public renoteMutingsCache: RedisKVCache<Set<string>>;
public userFollowingsCache: RedisKVCache<Set<string>>;
public userFollowingChannelsCache: RedisKVCache<Set<string>>;
constructor(
@Inject(DI.redis)
private redisClient: Redis.Redis,
@Inject(DI.redisForPubsub)
private redisForPubsub: Redis.Redis,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
@Inject(DI.mutingsRepository)
private mutingsRepository: MutingsRepository,
@Inject(DI.blockingsRepository)
private blockingsRepository: BlockingsRepository,
@Inject(DI.renoteMutingsRepository)
private renoteMutingsRepository: RenoteMutingsRepository,
@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,
@Inject(DI.channelFollowingsRepository)
private channelFollowingsRepository: ChannelFollowingsRepository,
private userEntityService: UserEntityService,
) {
//this.onMessage = this.onMessage.bind(this);
this.userByIdCache = new MemoryKVCache<User>(Infinity);
this.localUserByNativeTokenCache = new MemoryKVCache<LocalUser | null>(Infinity);
this.localUserByIdCache = new MemoryKVCache<LocalUser>(Infinity);
this.uriPersonCache = new MemoryKVCache<User | null>(Infinity);
this.userProfileCache = new RedisKVCache<UserProfile>(this.redisClient, 'userProfile', {
lifetime: 1000 * 60 * 30, // 30m
memoryCacheLifetime: 1000 * 60, // 1m
fetcher: (key) => this.userProfilesRepository.findOneByOrFail({ userId: key }),
toRedisConverter: (value) => JSON.stringify(value),
fromRedisConverter: (value) => JSON.parse(value), // TODO: date型の考慮
});
this.userMutingsCache = new RedisKVCache<Set<string>>(this.redisClient, 'userMutings', {
lifetime: 1000 * 60 * 30, // 30m
memoryCacheLifetime: 1000 * 60, // 1m
fetcher: (key) => this.mutingsRepository.find({ where: { muterId: key }, select: ['muteeId'] }).then(xs => new Set(xs.map(x => x.muteeId))),
toRedisConverter: (value) => JSON.stringify(Array.from(value)),
fromRedisConverter: (value) => new Set(JSON.parse(value)),
});
this.userBlockingCache = new RedisKVCache<Set<string>>(this.redisClient, 'userBlocking', {
lifetime: 1000 * 60 * 30, // 30m
memoryCacheLifetime: 1000 * 60, // 1m
fetcher: (key) => this.blockingsRepository.find({ where: { blockerId: key }, select: ['blockeeId'] }).then(xs => new Set(xs.map(x => x.blockeeId))),
toRedisConverter: (value) => JSON.stringify(Array.from(value)),
fromRedisConverter: (value) => new Set(JSON.parse(value)),
});
this.userBlockedCache = new RedisKVCache<Set<string>>(this.redisClient, 'userBlocked', {
lifetime: 1000 * 60 * 30, // 30m
memoryCacheLifetime: 1000 * 60, // 1m
fetcher: (key) => this.blockingsRepository.find({ where: { blockeeId: key }, select: ['blockerId'] }).then(xs => new Set(xs.map(x => x.blockerId))),
toRedisConverter: (value) => JSON.stringify(Array.from(value)),
fromRedisConverter: (value) => new Set(JSON.parse(value)),
});
this.renoteMutingsCache = new RedisKVCache<Set<string>>(this.redisClient, 'renoteMutings', {
lifetime: 1000 * 60 * 30, // 30m
memoryCacheLifetime: 1000 * 60, // 1m
fetcher: (key) => this.renoteMutingsRepository.find({ where: { muterId: key }, select: ['muteeId'] }).then(xs => new Set(xs.map(x => x.muteeId))),
toRedisConverter: (value) => JSON.stringify(Array.from(value)),
fromRedisConverter: (value) => new Set(JSON.parse(value)),
});
this.userFollowingsCache = new RedisKVCache<Set<string>>(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)),
});
this.userFollowingChannelsCache = new RedisKVCache<Set<string>>(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.redisForPubsub.on('message', this.onMessage);
}
@bindThis
private async onMessage(_: string, data: string): Promise<void> {
const obj = JSON.parse(data);
if (obj.channel === 'internal') {
const { type, body } = obj.message as StreamMessages['internal']['payload'];
switch (type) {
case 'userChangeSuspendedState':
case 'remoteUserUpdated': {
const user = await this.usersRepository.findOneByOrFail({ id: body.id });
this.userByIdCache.set(user.id, user);
for (const [k, v] of this.uriPersonCache.cache.entries()) {
if (v.value?.id === user.id) {
this.uriPersonCache.set(k, user);
}
}
if (this.userEntityService.isLocalUser(user)) {
this.localUserByNativeTokenCache.set(user.token!, user);
this.localUserByIdCache.set(user.id, user);
}
break;
}
case 'userTokenRegenerated': {
const user = await this.usersRepository.findOneByOrFail({ id: body.id }) as LocalUser;
this.localUserByNativeTokenCache.delete(body.oldToken);
this.localUserByNativeTokenCache.set(body.newToken, user);
break;
}
case 'follow': {
const follower = this.userByIdCache.get(body.followerId);
if (follower) follower.followingCount++;
const followee = this.userByIdCache.get(body.followeeId);
if (followee) followee.followersCount++;
break;
}
default:
break;
}
}
}
@bindThis
public findUserById(userId: User['id']) {
return this.userByIdCache.fetch(userId, () => this.usersRepository.findOneByOrFail({ id: userId }));
}
@bindThis
public onApplicationShutdown(signal?: string | undefined) {
this.redisForPubsub.off('message', this.onMessage);
}
}

View File

@ -1,11 +1,11 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { AccountMoveService } from './AccountMoveService.js';
import { AccountUpdateService } from './AccountUpdateService.js'; import { AccountUpdateService } from './AccountUpdateService.js';
import { AiService } from './AiService.js'; import { AiService } from './AiService.js';
import { AntennaService } from './AntennaService.js'; import { AntennaService } from './AntennaService.js';
import { AppLockService } from './AppLockService.js'; import { AppLockService } from './AppLockService.js';
import { AchievementService } from './AchievementService.js'; import { AchievementService } from './AchievementService.js';
import { CaptchaService } from './CaptchaService.js'; import { CaptchaService } from './CaptchaService.js';
import { CreateNotificationService } from './CreateNotificationService.js';
import { CreateSystemUserService } from './CreateSystemUserService.js'; import { CreateSystemUserService } from './CreateSystemUserService.js';
import { CustomEmojiService } from './CustomEmojiService.js'; import { CustomEmojiService } from './CustomEmojiService.js';
import { DeleteAccountService } from './DeleteAccountService.js'; import { DeleteAccountService } from './DeleteAccountService.js';
@ -39,9 +39,9 @@ import { S3Service } from './S3Service.js';
import { SignupService } from './SignupService.js'; import { SignupService } from './SignupService.js';
import { TwoFactorAuthenticationService } from './TwoFactorAuthenticationService.js'; import { TwoFactorAuthenticationService } from './TwoFactorAuthenticationService.js';
import { UserBlockingService } from './UserBlockingService.js'; import { UserBlockingService } from './UserBlockingService.js';
import { UserCacheService } from './UserCacheService.js'; import { CacheService } from './CacheService.js';
import { UserFollowingService } from './UserFollowingService.js'; import { UserFollowingService } from './UserFollowingService.js';
import { UserKeypairStoreService } from './UserKeypairStoreService.js'; import { UserKeypairService } from './UserKeypairService.js';
import { UserListService } from './UserListService.js'; import { UserListService } from './UserListService.js';
import { UserMutingService } from './UserMutingService.js'; import { UserMutingService } from './UserMutingService.js';
import { UserSuspendService } from './UserSuspendService.js'; import { UserSuspendService } from './UserSuspendService.js';
@ -82,6 +82,7 @@ import { HashtagEntityService } from './entities/HashtagEntityService.js';
import { InstanceEntityService } from './entities/InstanceEntityService.js'; import { InstanceEntityService } from './entities/InstanceEntityService.js';
import { ModerationLogEntityService } from './entities/ModerationLogEntityService.js'; import { ModerationLogEntityService } from './entities/ModerationLogEntityService.js';
import { MutingEntityService } from './entities/MutingEntityService.js'; import { MutingEntityService } from './entities/MutingEntityService.js';
import { RenoteMutingEntityService } from './entities/RenoteMutingEntityService.js';
import { NoteEntityService } from './entities/NoteEntityService.js'; import { NoteEntityService } from './entities/NoteEntityService.js';
import { NoteFavoriteEntityService } from './entities/NoteFavoriteEntityService.js'; import { NoteFavoriteEntityService } from './entities/NoteFavoriteEntityService.js';
import { NoteReactionEntityService } from './entities/NoteReactionEntityService.js'; import { NoteReactionEntityService } from './entities/NoteReactionEntityService.js';
@ -119,13 +120,13 @@ import type { Provider } from '@nestjs/common';
//#region 文字列ベースでのinjection用(循環参照対応のため) //#region 文字列ベースでのinjection用(循環参照対応のため)
const $LoggerService: Provider = { provide: 'LoggerService', useExisting: LoggerService }; const $LoggerService: Provider = { provide: 'LoggerService', useExisting: LoggerService };
const $AccountMoveService: Provider = { provide: 'AccountMoveService', useExisting: AccountMoveService };
const $AccountUpdateService: Provider = { provide: 'AccountUpdateService', useExisting: AccountUpdateService }; const $AccountUpdateService: Provider = { provide: 'AccountUpdateService', useExisting: AccountUpdateService };
const $AiService: Provider = { provide: 'AiService', useExisting: AiService }; const $AiService: Provider = { provide: 'AiService', useExisting: AiService };
const $AntennaService: Provider = { provide: 'AntennaService', useExisting: AntennaService }; const $AntennaService: Provider = { provide: 'AntennaService', useExisting: AntennaService };
const $AppLockService: Provider = { provide: 'AppLockService', useExisting: AppLockService }; const $AppLockService: Provider = { provide: 'AppLockService', useExisting: AppLockService };
const $AchievementService: Provider = { provide: 'AchievementService', useExisting: AchievementService }; const $AchievementService: Provider = { provide: 'AchievementService', useExisting: AchievementService };
const $CaptchaService: Provider = { provide: 'CaptchaService', useExisting: CaptchaService }; const $CaptchaService: Provider = { provide: 'CaptchaService', useExisting: CaptchaService };
const $CreateNotificationService: Provider = { provide: 'CreateNotificationService', useExisting: CreateNotificationService };
const $CreateSystemUserService: Provider = { provide: 'CreateSystemUserService', useExisting: CreateSystemUserService }; const $CreateSystemUserService: Provider = { provide: 'CreateSystemUserService', useExisting: CreateSystemUserService };
const $CustomEmojiService: Provider = { provide: 'CustomEmojiService', useExisting: CustomEmojiService }; const $CustomEmojiService: Provider = { provide: 'CustomEmojiService', useExisting: CustomEmojiService };
const $DeleteAccountService: Provider = { provide: 'DeleteAccountService', useExisting: DeleteAccountService }; const $DeleteAccountService: Provider = { provide: 'DeleteAccountService', useExisting: DeleteAccountService };
@ -160,9 +161,9 @@ const $S3Service: Provider = { provide: 'S3Service', useExisting: S3Service };
const $SignupService: Provider = { provide: 'SignupService', useExisting: SignupService }; const $SignupService: Provider = { provide: 'SignupService', useExisting: SignupService };
const $TwoFactorAuthenticationService: Provider = { provide: 'TwoFactorAuthenticationService', useExisting: TwoFactorAuthenticationService }; const $TwoFactorAuthenticationService: Provider = { provide: 'TwoFactorAuthenticationService', useExisting: TwoFactorAuthenticationService };
const $UserBlockingService: Provider = { provide: 'UserBlockingService', useExisting: UserBlockingService }; const $UserBlockingService: Provider = { provide: 'UserBlockingService', useExisting: UserBlockingService };
const $UserCacheService: Provider = { provide: 'UserCacheService', useExisting: UserCacheService }; const $CacheService: Provider = { provide: 'CacheService', useExisting: CacheService };
const $UserFollowingService: Provider = { provide: 'UserFollowingService', useExisting: UserFollowingService }; const $UserFollowingService: Provider = { provide: 'UserFollowingService', useExisting: UserFollowingService };
const $UserKeypairStoreService: Provider = { provide: 'UserKeypairStoreService', useExisting: UserKeypairStoreService }; const $UserKeypairService: Provider = { provide: 'UserKeypairService', useExisting: UserKeypairService };
const $UserListService: Provider = { provide: 'UserListService', useExisting: UserListService }; const $UserListService: Provider = { provide: 'UserListService', useExisting: UserListService };
const $UserMutingService: Provider = { provide: 'UserMutingService', useExisting: UserMutingService }; const $UserMutingService: Provider = { provide: 'UserMutingService', useExisting: UserMutingService };
const $UserSuspendService: Provider = { provide: 'UserSuspendService', useExisting: UserSuspendService }; const $UserSuspendService: Provider = { provide: 'UserSuspendService', useExisting: UserSuspendService };
@ -203,6 +204,7 @@ const $HashtagEntityService: Provider = { provide: 'HashtagEntityService', useEx
const $InstanceEntityService: Provider = { provide: 'InstanceEntityService', useExisting: InstanceEntityService }; const $InstanceEntityService: Provider = { provide: 'InstanceEntityService', useExisting: InstanceEntityService };
const $ModerationLogEntityService: Provider = { provide: 'ModerationLogEntityService', useExisting: ModerationLogEntityService }; const $ModerationLogEntityService: Provider = { provide: 'ModerationLogEntityService', useExisting: ModerationLogEntityService };
const $MutingEntityService: Provider = { provide: 'MutingEntityService', useExisting: MutingEntityService }; const $MutingEntityService: Provider = { provide: 'MutingEntityService', useExisting: MutingEntityService };
const $RenoteMutingEntityService: Provider = { provide: 'RenoteMutingEntityService', useExisting: RenoteMutingEntityService };
const $NoteEntityService: Provider = { provide: 'NoteEntityService', useExisting: NoteEntityService }; const $NoteEntityService: Provider = { provide: 'NoteEntityService', useExisting: NoteEntityService };
const $NoteFavoriteEntityService: Provider = { provide: 'NoteFavoriteEntityService', useExisting: NoteFavoriteEntityService }; const $NoteFavoriteEntityService: Provider = { provide: 'NoteFavoriteEntityService', useExisting: NoteFavoriteEntityService };
const $NoteReactionEntityService: Provider = { provide: 'NoteReactionEntityService', useExisting: NoteReactionEntityService }; const $NoteReactionEntityService: Provider = { provide: 'NoteReactionEntityService', useExisting: NoteReactionEntityService };
@ -242,13 +244,13 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
], ],
providers: [ providers: [
LoggerService, LoggerService,
AccountMoveService,
AccountUpdateService, AccountUpdateService,
AiService, AiService,
AntennaService, AntennaService,
AppLockService, AppLockService,
AchievementService, AchievementService,
CaptchaService, CaptchaService,
CreateNotificationService,
CreateSystemUserService, CreateSystemUserService,
CustomEmojiService, CustomEmojiService,
DeleteAccountService, DeleteAccountService,
@ -283,9 +285,9 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
SignupService, SignupService,
TwoFactorAuthenticationService, TwoFactorAuthenticationService,
UserBlockingService, UserBlockingService,
UserCacheService, CacheService,
UserFollowingService, UserFollowingService,
UserKeypairStoreService, UserKeypairService,
UserListService, UserListService,
UserMutingService, UserMutingService,
UserSuspendService, UserSuspendService,
@ -325,6 +327,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
InstanceEntityService, InstanceEntityService,
ModerationLogEntityService, ModerationLogEntityService,
MutingEntityService, MutingEntityService,
RenoteMutingEntityService,
NoteEntityService, NoteEntityService,
NoteFavoriteEntityService, NoteFavoriteEntityService,
NoteReactionEntityService, NoteReactionEntityService,
@ -359,13 +362,13 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
//#region 文字列ベースでのinjection用(循環参照対応のため) //#region 文字列ベースでのinjection用(循環参照対応のため)
$LoggerService, $LoggerService,
$AccountMoveService,
$AccountUpdateService, $AccountUpdateService,
$AiService, $AiService,
$AntennaService, $AntennaService,
$AppLockService, $AppLockService,
$AchievementService, $AchievementService,
$CaptchaService, $CaptchaService,
$CreateNotificationService,
$CreateSystemUserService, $CreateSystemUserService,
$CustomEmojiService, $CustomEmojiService,
$DeleteAccountService, $DeleteAccountService,
@ -400,9 +403,9 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$SignupService, $SignupService,
$TwoFactorAuthenticationService, $TwoFactorAuthenticationService,
$UserBlockingService, $UserBlockingService,
$UserCacheService, $CacheService,
$UserFollowingService, $UserFollowingService,
$UserKeypairStoreService, $UserKeypairService,
$UserListService, $UserListService,
$UserMutingService, $UserMutingService,
$UserSuspendService, $UserSuspendService,
@ -442,6 +445,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$InstanceEntityService, $InstanceEntityService,
$ModerationLogEntityService, $ModerationLogEntityService,
$MutingEntityService, $MutingEntityService,
$RenoteMutingEntityService,
$NoteEntityService, $NoteEntityService,
$NoteFavoriteEntityService, $NoteFavoriteEntityService,
$NoteReactionEntityService, $NoteReactionEntityService,
@ -477,13 +481,13 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
exports: [ exports: [
QueueModule, QueueModule,
LoggerService, LoggerService,
AccountMoveService,
AccountUpdateService, AccountUpdateService,
AiService, AiService,
AntennaService, AntennaService,
AppLockService, AppLockService,
AchievementService, AchievementService,
CaptchaService, CaptchaService,
CreateNotificationService,
CreateSystemUserService, CreateSystemUserService,
CustomEmojiService, CustomEmojiService,
DeleteAccountService, DeleteAccountService,
@ -518,9 +522,9 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
SignupService, SignupService,
TwoFactorAuthenticationService, TwoFactorAuthenticationService,
UserBlockingService, UserBlockingService,
UserCacheService, CacheService,
UserFollowingService, UserFollowingService,
UserKeypairStoreService, UserKeypairService,
UserListService, UserListService,
UserMutingService, UserMutingService,
UserSuspendService, UserSuspendService,
@ -559,6 +563,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
InstanceEntityService, InstanceEntityService,
ModerationLogEntityService, ModerationLogEntityService,
MutingEntityService, MutingEntityService,
RenoteMutingEntityService,
NoteEntityService, NoteEntityService,
NoteFavoriteEntityService, NoteFavoriteEntityService,
NoteReactionEntityService, NoteReactionEntityService,
@ -593,13 +598,13 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
//#region 文字列ベースでのinjection用(循環参照対応のため) //#region 文字列ベースでのinjection用(循環参照対応のため)
$LoggerService, $LoggerService,
$AccountMoveService,
$AccountUpdateService, $AccountUpdateService,
$AiService, $AiService,
$AntennaService, $AntennaService,
$AppLockService, $AppLockService,
$AchievementService, $AchievementService,
$CaptchaService, $CaptchaService,
$CreateNotificationService,
$CreateSystemUserService, $CreateSystemUserService,
$CustomEmojiService, $CustomEmojiService,
$DeleteAccountService, $DeleteAccountService,
@ -634,9 +639,9 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$SignupService, $SignupService,
$TwoFactorAuthenticationService, $TwoFactorAuthenticationService,
$UserBlockingService, $UserBlockingService,
$UserCacheService, $CacheService,
$UserFollowingService, $UserFollowingService,
$UserKeypairStoreService, $UserKeypairService,
$UserListService, $UserListService,
$UserMutingService, $UserMutingService,
$UserSuspendService, $UserSuspendService,
@ -675,6 +680,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$InstanceEntityService, $InstanceEntityService,
$ModerationLogEntityService, $ModerationLogEntityService,
$MutingEntityService, $MutingEntityService,
$RenoteMutingEntityService,
$NoteEntityService, $NoteEntityService,
$NoteFavoriteEntityService, $NoteFavoriteEntityService,
$NoteReactionEntityService, $NoteReactionEntityService,
@ -708,4 +714,4 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
//#endregion //#endregion
], ],
}) })
export class CoreModule {} export class CoreModule { }

View File

@ -1,125 +0,0 @@
import { setTimeout } from 'node:timers/promises';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import type { MutingsRepository, NotificationsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
import type { User } from '@/models/entities/User.js';
import type { Notification } from '@/models/entities/Notification.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { IdService } from '@/core/IdService.js';
import { DI } from '@/di-symbols.js';
import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js';
import { PushNotificationService } from '@/core/PushNotificationService.js';
import { bindThis } from '@/decorators.js';
@Injectable()
export class CreateNotificationService implements OnApplicationShutdown {
#shutdownController = new AbortController();
constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
@Inject(DI.notificationsRepository)
private notificationsRepository: NotificationsRepository,
@Inject(DI.mutingsRepository)
private mutingsRepository: MutingsRepository,
private notificationEntityService: NotificationEntityService,
private idService: IdService,
private globalEventService: GlobalEventService,
private pushNotificationService: PushNotificationService,
) {
}
@bindThis
public async createNotification(
notifieeId: User['id'],
type: Notification['type'],
data: Partial<Notification>,
): Promise<Notification | null> {
if (data.notifierId && (notifieeId === data.notifierId)) {
return null;
}
const profile = await this.userProfilesRepository.findOneBy({ userId: notifieeId });
const isMuted = profile?.mutingNotificationTypes.includes(type);
// Create notification
const notification = await this.notificationsRepository.insert({
id: this.idService.genId(),
createdAt: new Date(),
notifieeId: notifieeId,
type: type,
// 相手がこの通知をミュートしているようなら、既読を予めつけておく
isRead: isMuted,
...data,
} as Partial<Notification>)
.then(x => this.notificationsRepository.findOneByOrFail(x.identifiers[0]));
const packed = await this.notificationEntityService.pack(notification, {});
// Publish notification event
this.globalEventService.publishMainStream(notifieeId, 'notification', packed);
// 2秒経っても(今回作成した)通知が既読にならなかったら「未読の通知がありますよ」イベントを発行する
setTimeout(2000, 'unread note', { signal: this.#shutdownController.signal }).then(async () => {
const fresh = await this.notificationsRepository.findOneBy({ id: notification.id });
if (fresh == null) return; // 既に削除されているかもしれない
if (fresh.isRead) return;
//#region ただしミュートしているユーザーからの通知なら無視
const mutings = await this.mutingsRepository.findBy({
muterId: notifieeId,
});
if (data.notifierId && mutings.map(m => m.muteeId).includes(data.notifierId)) {
return;
}
//#endregion
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! }));
}, () => { /* aborted, ignore it */ });
return notification;
}
// TODO
//const locales = await import('../../../../locales/index.js');
// TODO: locale ファイルをクライアント用とサーバー用で分けたい
@bindThis
private async emailNotificationFollow(userId: User['id'], follower: User) {
/*
const userProfile = await UserProfiles.findOneByOrFail({ userId: userId });
if (!userProfile.email || !userProfile.emailNotificationTypes.includes('follow')) return;
const locale = locales[userProfile.lang ?? 'ja-JP'];
const i18n = new I18n(locale);
// TODO: render user information html
sendEmail(userProfile.email, i18n.t('_email._follow.title'), `${follower.name} (@${Acct.toString(follower)})`, `${follower.name} (@${Acct.toString(follower)})`);
*/
}
@bindThis
private async emailNotificationReceiveFollowRequest(userId: User['id'], follower: User) {
/*
const userProfile = await UserProfiles.findOneByOrFail({ userId: userId });
if (!userProfile.email || !userProfile.emailNotificationTypes.includes('receiveFollowRequest')) return;
const locale = locales[userProfile.lang ?? 'ja-JP'];
const i18n = new I18n(locale);
// TODO: render user information html
sendEmail(userProfile.email, i18n.t('_email._receiveFollowRequest.title'), `${follower.name} (@${Acct.toString(follower)})`, `${follower.name} (@${Acct.toString(follower)})`);
*/
}
onApplicationShutdown(signal?: string | undefined): void {
this.#shutdownController.abort();
}
}

View File

@ -1,24 +1,28 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { DataSource, In, IsNull } from 'typeorm'; import { DataSource, In, IsNull } from 'typeorm';
import Redis from 'ioredis';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { DriveFile } from '@/models/entities/DriveFile.js';
import type { Emoji } from '@/models/entities/Emoji.js'; import type { Emoji } from '@/models/entities/Emoji.js';
import type { EmojisRepository, Note } from '@/models/index.js'; import type { EmojisRepository } from '@/models/index.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { Cache } from '@/misc/cache.js'; import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { ReactionService } from '@/core/ReactionService.js';
import { query } from '@/misc/prelude/url.js'; import { query } from '@/misc/prelude/url.js';
@Injectable() @Injectable()
export class CustomEmojiService { export class CustomEmojiService {
private cache: Cache<Emoji | null>; private cache: MemoryKVCache<Emoji | null>;
public localEmojisCache: RedisSingleCache<Map<string, Emoji>>;
constructor( constructor(
@Inject(DI.redis)
private redisClient: Redis.Redis,
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@ -32,9 +36,16 @@ export class CustomEmojiService {
private idService: IdService, private idService: IdService,
private emojiEntityService: EmojiEntityService, private emojiEntityService: EmojiEntityService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
private reactionService: ReactionService,
) { ) {
this.cache = new Cache<Emoji | null>(1000 * 60 * 60 * 12); this.cache = new MemoryKVCache<Emoji | null>(1000 * 60 * 60 * 12);
this.localEmojisCache = new RedisSingleCache<Map<string, Emoji>>(this.redisClient, 'localEmojis', {
lifetime: 1000 * 60 * 30, // 30m
memoryCacheLifetime: 1000 * 60 * 3, // 3m
fetcher: () => this.emojisRepository.find({ where: { host: IsNull() } }).then(emojis => new Map(emojis.map(emoji => [emoji.name, emoji]))),
toRedisConverter: (value) => JSON.stringify(value.values()),
fromRedisConverter: (value) => new Map(JSON.parse(value).map((x: Emoji) => [x.name, x])), // TODO: Date型の変換
});
} }
@bindThis @bindThis
@ -44,6 +55,7 @@ export class CustomEmojiService {
category: string | null; category: string | null;
aliases: string[]; aliases: string[];
host: string | null; host: string | null;
license: string | null;
}): Promise<Emoji> { }): Promise<Emoji> {
const emoji = await this.emojisRepository.insert({ const emoji = await this.emojisRepository.insert({
id: this.idService.genId(), id: this.idService.genId(),
@ -55,10 +67,11 @@ export class CustomEmojiService {
originalUrl: data.driveFile.url, originalUrl: data.driveFile.url,
publicUrl: data.driveFile.webpublicUrl ?? data.driveFile.url, publicUrl: data.driveFile.webpublicUrl ?? data.driveFile.url,
type: data.driveFile.webpublicType ?? data.driveFile.type, type: data.driveFile.webpublicType ?? data.driveFile.type,
license: data.license,
}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0])); }).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
if (data.host == null) { if (data.host == null) {
await this.db.queryResultCache!.remove(['meta_emojis']); this.localEmojisCache.refresh();
this.globalEventService.publishBroadcastStream('emojiAdded', { this.globalEventService.publishBroadcastStream('emojiAdded', {
emoji: await this.emojiEntityService.packDetailed(emoji.id), emoji: await this.emojiEntityService.packDetailed(emoji.id),
@ -68,6 +81,146 @@ export class CustomEmojiService {
return emoji; return emoji;
} }
@bindThis
public async update(id: Emoji['id'], data: {
name?: string;
category?: string | null;
aliases?: string[];
license?: string | null;
}): Promise<void> {
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');
await this.emojisRepository.update(emoji.id, {
updatedAt: new Date(),
name: data.name,
category: data.category,
aliases: data.aliases,
license: data.license,
});
this.localEmojisCache.refresh();
const updated = await this.emojiEntityService.packDetailed(emoji.id);
if (emoji.name === data.name) {
this.globalEventService.publishBroadcastStream('emojiUpdated', {
emojis: [updated],
});
} else {
this.globalEventService.publishBroadcastStream('emojiDeleted', {
emojis: [await this.emojiEntityService.packDetailed(emoji)],
});
this.globalEventService.publishBroadcastStream('emojiAdded', {
emoji: updated,
});
}
}
@bindThis
public async addAliasesBulk(ids: Emoji['id'][], aliases: string[]) {
const emojis = await this.emojisRepository.findBy({
id: In(ids),
});
for (const emoji of emojis) {
await this.emojisRepository.update(emoji.id, {
updatedAt: new Date(),
aliases: [...new Set(emoji.aliases.concat(aliases))],
});
}
this.localEmojisCache.refresh();
this.globalEventService.publishBroadcastStream('emojiUpdated', {
emojis: await this.emojiEntityService.packDetailedMany(ids),
});
}
@bindThis
public async setAliasesBulk(ids: Emoji['id'][], aliases: string[]) {
await this.emojisRepository.update({
id: In(ids),
}, {
updatedAt: new Date(),
aliases: aliases,
});
this.localEmojisCache.refresh();
this.globalEventService.publishBroadcastStream('emojiUpdated', {
emojis: await this.emojiEntityService.packDetailedMany(ids),
});
}
@bindThis
public async removeAliasesBulk(ids: Emoji['id'][], aliases: string[]) {
const emojis = await this.emojisRepository.findBy({
id: In(ids),
});
for (const emoji of emojis) {
await this.emojisRepository.update(emoji.id, {
updatedAt: new Date(),
aliases: emoji.aliases.filter(x => !aliases.includes(x)),
});
}
this.localEmojisCache.refresh();
this.globalEventService.publishBroadcastStream('emojiUpdated', {
emojis: await this.emojiEntityService.packDetailedMany(ids),
});
}
@bindThis
public async setCategoryBulk(ids: Emoji['id'][], category: string | null) {
await this.emojisRepository.update({
id: In(ids),
}, {
updatedAt: new Date(),
category: category,
});
this.localEmojisCache.refresh();
this.globalEventService.publishBroadcastStream('emojiUpdated', {
emojis: await this.emojiEntityService.packDetailedMany(ids),
});
}
@bindThis
public async delete(id: Emoji['id']) {
const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
await this.emojisRepository.delete(emoji.id);
this.localEmojisCache.refresh();
this.globalEventService.publishBroadcastStream('emojiDeleted', {
emojis: [await this.emojiEntityService.packDetailed(emoji)],
});
}
@bindThis
public async deleteBulk(ids: Emoji['id'][]) {
const emojis = await this.emojisRepository.findBy({
id: In(ids),
});
for (const emoji of emojis) {
await this.emojisRepository.delete(emoji.id);
}
this.localEmojisCache.refresh();
this.globalEventService.publishBroadcastStream('emojiDeleted', {
emojis: await this.emojiEntityService.packDetailedMany(emojis),
});
}
@bindThis @bindThis
private normalizeHost(src: string | undefined, noteUserHost: string | null): string | null { private normalizeHost(src: string | undefined, noteUserHost: string | null): string | null {
// クエリに使うホスト // クエリに使うホスト
@ -82,7 +235,7 @@ export class CustomEmojiService {
} }
@bindThis @bindThis
private parseEmojiStr(emojiName: string, noteUserHost: string | null) { public parseEmojiStr(emojiName: string, noteUserHost: string | null) {
const match = emojiName.match(/^(\w+)(?:@([\w.-]+))?$/); const match = emojiName.match(/^(\w+)(?:@([\w.-]+))?$/);
if (!match) return { name: null, host: null }; if (!match) return { name: null, host: null };
@ -141,30 +294,6 @@ export class CustomEmojiService {
return res; return res;
} }
@bindThis
public aggregateNoteEmojis(notes: Note[]) {
let emojis: { name: string | null; host: string | null; }[] = [];
for (const note of notes) {
emojis = emojis.concat(note.emojis
.map(e => this.parseEmojiStr(e, note.userHost)));
if (note.renote) {
emojis = emojis.concat(note.renote.emojis
.map(e => this.parseEmojiStr(e, note.renote!.userHost)));
if (note.renote.user) {
emojis = emojis.concat(note.renote.user.emojis
.map(e => this.parseEmojiStr(e, note.renote!.userHost)));
}
}
const customReactions = Object.keys(note.reactions).map(x => this.reactionService.decodeReaction(x)).filter(x => x.name != null) as typeof emojis;
emojis = emojis.concat(customReactions);
if (note.user) {
emojis = emojis.concat(note.user.emojis
.map(e => this.parseEmojiStr(e, note.userHost)));
}
}
return emojis.filter(x => x.name != null && x.host != null) as { name: string; host: string; }[];
}
/** /**
* *
*/ */

View File

@ -36,8 +36,5 @@ export class DeleteAccountService {
await this.usersRepository.update(user.id, { await this.usersRepository.update(user.id, {
isDeleted: true, isDeleted: true,
}); });
// Terminate streaming
this.globalEventService.publishUserEvent(user.id, 'terminate', {});
} }
} }

View File

@ -6,6 +6,7 @@ import IPCIDR from 'ip-cidr';
import PrivateIp from 'private-ip'; import PrivateIp from 'private-ip';
import chalk from 'chalk'; import chalk from 'chalk';
import got, * as Got from 'got'; import got, * as Got from 'got';
import { parse } from 'content-disposition';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { HttpRequestService } from '@/core/HttpRequestService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js';
@ -32,13 +33,18 @@ export class DownloadService {
} }
@bindThis @bindThis
public async downloadUrl(url: string, path: string): Promise<void> { public async downloadUrl(url: string, path: string): Promise<{
filename: string;
}> {
this.logger.info(`Downloading ${chalk.cyan(url)} to ${chalk.cyanBright(path)} ...`); this.logger.info(`Downloading ${chalk.cyan(url)} to ${chalk.cyanBright(path)} ...`);
const timeout = 30 * 1000; const timeout = 30 * 1000;
const operationTimeout = 60 * 1000; const operationTimeout = 60 * 1000;
const maxSize = this.config.maxFileSize ?? 262144000; const maxSize = this.config.maxFileSize ?? 262144000;
const urlObj = new URL(url);
let filename = urlObj.pathname.split('/').pop() ?? 'untitled';
const req = got.stream(url, { const req = got.stream(url, {
headers: { headers: {
'User-Agent': this.config.userAgent, 'User-Agent': this.config.userAgent,
@ -77,6 +83,14 @@ export class DownloadService {
req.destroy(); req.destroy();
} }
} }
const contentDisposition = res.headers['content-disposition'];
if (contentDisposition != null) {
const parsed = parse(contentDisposition);
if (parsed.parameters.filename) {
filename = parsed.parameters.filename;
}
}
}).on('downloadProgress', (progress: Got.Progress) => { }).on('downloadProgress', (progress: Got.Progress) => {
if (progress.transferred > maxSize) { if (progress.transferred > maxSize) {
this.logger.warn(`maxSize exceeded (${progress.transferred} > ${maxSize}) on downloadProgress`); this.logger.warn(`maxSize exceeded (${progress.transferred} > ${maxSize}) on downloadProgress`);
@ -95,6 +109,10 @@ export class DownloadService {
} }
this.logger.succ(`Download finished: ${chalk.cyan(url)}`); this.logger.succ(`Download finished: ${chalk.cyan(url)}`);
return {
filename,
};
} }
@bindThis @bindThis

View File

@ -2,7 +2,9 @@ import * as fs from 'node:fs';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import sharp from 'sharp'; import sharp from 'sharp';
import { sharpBmp } from 'sharp-read-bmp';
import { IsNull } from 'typeorm'; import { IsNull } from 'typeorm';
import { DeleteObjectCommandInput, PutObjectCommandInput, NoSuchKey } from '@aws-sdk/client-s3';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository } from '@/models/index.js'; import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository } from '@/models/index.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
@ -33,7 +35,8 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { FileInfoService } from '@/core/FileInfoService.js'; import { FileInfoService } from '@/core/FileInfoService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js'; import { RoleService } from '@/core/RoleService.js';
import type S3 from 'aws-sdk/clients/s3.js'; import { correctFilename } from '@/misc/correct-filename.js';
import { isMimeImage } from '@/misc/is-mime-image.js';
type AddFileArgs = { type AddFileArgs = {
/** User who wish to add file */ /** User who wish to add file */
@ -78,6 +81,7 @@ type UploadFromUrlArgs = {
export class DriveService { export class DriveService {
private registerLogger: Logger; private registerLogger: Logger;
private downloaderLogger: Logger; private downloaderLogger: Logger;
private deleteLogger: Logger;
constructor( constructor(
@Inject(DI.config) @Inject(DI.config)
@ -115,6 +119,7 @@ export class DriveService {
const logger = new Logger('drive', 'blue'); const logger = new Logger('drive', 'blue');
this.registerLogger = logger.createSubLogger('register', 'yellow'); this.registerLogger = logger.createSubLogger('register', 'yellow');
this.downloaderLogger = logger.createSubLogger('downloader'); this.downloaderLogger = logger.createSubLogger('downloader');
this.deleteLogger = logger.createSubLogger('delete');
} }
/*** /***
@ -168,7 +173,7 @@ export class DriveService {
//#region Uploads //#region Uploads
this.registerLogger.info(`uploading original: ${key}`); this.registerLogger.info(`uploading original: ${key}`);
const uploads = [ const uploads = [
this.upload(key, fs.createReadStream(path), type, name), this.upload(key, fs.createReadStream(path), type, ext, name),
]; ];
if (alts.webpublic) { if (alts.webpublic) {
@ -176,7 +181,7 @@ export class DriveService {
webpublicUrl = `${ baseUrl }/${ webpublicKey }`; webpublicUrl = `${ baseUrl }/${ webpublicKey }`;
this.registerLogger.info(`uploading webpublic: ${webpublicKey}`); this.registerLogger.info(`uploading webpublic: ${webpublicKey}`);
uploads.push(this.upload(webpublicKey, alts.webpublic.data, alts.webpublic.type, name)); uploads.push(this.upload(webpublicKey, alts.webpublic.data, alts.webpublic.type, alts.webpublic.ext, name));
} }
if (alts.thumbnail) { if (alts.thumbnail) {
@ -184,7 +189,7 @@ export class DriveService {
thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`; thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`;
this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`); this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`);
uploads.push(this.upload(thumbnailKey, alts.thumbnail.data, alts.thumbnail.type)); uploads.push(this.upload(thumbnailKey, alts.thumbnail.data, alts.thumbnail.type, alts.thumbnail.ext));
} }
await Promise.all(uploads); await Promise.all(uploads);
@ -273,8 +278,8 @@ export class DriveService {
} }
} }
if (!['image/jpeg', 'image/png', 'image/webp', 'image/avif', 'image/svg+xml'].includes(type)) { if (!isMimeImage(type, 'sharp-convertible-image-with-bmp')) {
this.registerLogger.debug('web image and thumbnail not created (not an required file)'); this.registerLogger.debug('web image and thumbnail not created (cannot convert by sharp)');
return { return {
webpublic: null, webpublic: null,
thumbnail: null, thumbnail: null,
@ -283,22 +288,16 @@ export class DriveService {
let img: sharp.Sharp | null = null; let img: sharp.Sharp | null = null;
let satisfyWebpublic: boolean; let satisfyWebpublic: boolean;
let isAnimated: boolean;
try { try {
img = sharp(path); img = await sharpBmp(path, type);
const metadata = await img.metadata(); const metadata = await img.metadata();
const isAnimated = metadata.pages && metadata.pages > 1; isAnimated = !!(metadata.pages && metadata.pages > 1);
// skip animated
if (isAnimated) {
return {
webpublic: null,
thumbnail: null,
};
}
satisfyWebpublic = !!( satisfyWebpublic = !!(
type !== 'image/svg+xml' && type !== 'image/webp' && type !== 'image/avif' && type !== 'image/svg+xml' && // security reason
type !== 'image/avif' && // not supported by Mastodon and MS Edge
!(metadata.exif ?? metadata.iptc ?? metadata.xmp ?? metadata.tifftagPhotoshop) && !(metadata.exif ?? metadata.iptc ?? metadata.xmp ?? metadata.tifftagPhotoshop) &&
metadata.width && metadata.width <= 2048 && metadata.width && metadata.width <= 2048 &&
metadata.height && metadata.height <= 2048 metadata.height && metadata.height <= 2048
@ -314,15 +313,13 @@ export class DriveService {
// #region webpublic // #region webpublic
let webpublic: IImage | null = null; let webpublic: IImage | null = null;
if (generateWeb && !satisfyWebpublic) { if (generateWeb && !satisfyWebpublic && !isAnimated) {
this.registerLogger.info('creating web image'); this.registerLogger.info('creating web image');
try { try {
if (['image/jpeg', 'image/webp', 'image/avif'].includes(type)) { if (['image/jpeg', 'image/webp', 'image/avif'].includes(type)) {
webpublic = await this.imageProcessingService.convertSharpToJpeg(img, 2048, 2048); webpublic = await this.imageProcessingService.convertSharpToWebp(img, 2048, 2048);
} else if (['image/png'].includes(type)) { } else if (['image/png', 'image/bmp', 'image/svg+xml'].includes(type)) {
webpublic = await this.imageProcessingService.convertSharpToPng(img, 2048, 2048);
} else if (['image/svg+xml'].includes(type)) {
webpublic = await this.imageProcessingService.convertSharpToPng(img, 2048, 2048); webpublic = await this.imageProcessingService.convertSharpToPng(img, 2048, 2048);
} else { } else {
this.registerLogger.debug('web image not created (not an required image)'); this.registerLogger.debug('web image not created (not an required image)');
@ -332,6 +329,7 @@ export class DriveService {
} }
} else { } else {
if (satisfyWebpublic) this.registerLogger.info('web image not created (original satisfies webpublic)'); if (satisfyWebpublic) this.registerLogger.info('web image not created (original satisfies webpublic)');
else if (isAnimated) this.registerLogger.info('web image not created (animated image)');
else this.registerLogger.info('web image not created (from remote)'); else this.registerLogger.info('web image not created (from remote)');
} }
// #endregion webpublic // #endregion webpublic
@ -340,10 +338,10 @@ export class DriveService {
let thumbnail: IImage | null = null; let thumbnail: IImage | null = null;
try { try {
if (['image/jpeg', 'image/webp', 'image/avif', 'image/png', 'image/svg+xml'].includes(type)) { if (isAnimated) {
thumbnail = await this.imageProcessingService.convertSharpToWebp(img, 498, 280); thumbnail = await this.imageProcessingService.convertSharpToWebp(sharp(path, { animated: true }), 374, 317, { alphaQuality: 70 });
} else { } else {
this.registerLogger.debug('thumbnail not created (not an required file)'); thumbnail = await this.imageProcessingService.convertSharpToWebp(img, 498, 422);
} }
} catch (err) { } catch (err) {
this.registerLogger.warn('thumbnail not created (an error occured)', err as Error); this.registerLogger.warn('thumbnail not created (an error occured)', err as Error);
@ -360,7 +358,7 @@ export class DriveService {
* Upload to ObjectStorage * Upload to ObjectStorage
*/ */
@bindThis @bindThis
private async upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) { private async upload(key: string, stream: fs.ReadStream | Buffer, type: string, ext?: string | null, filename?: string) {
if (type === 'image/apng') type = 'image/png'; if (type === 'image/apng') type = 'image/png';
if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream'; if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream';
@ -372,26 +370,26 @@ export class DriveService {
Body: stream, Body: stream,
ContentType: type, ContentType: type,
CacheControl: 'max-age=31536000, immutable', CacheControl: 'max-age=31536000, immutable',
} as S3.PutObjectRequest; } as PutObjectCommandInput;
if (filename) params.ContentDisposition = contentDisposition('inline', filename); if (filename) params.ContentDisposition = contentDisposition(
'inline',
// 拡張子からContent-Typeを設定してそうな挙動を示すオブジェクトストレージ (upcloud?) も存在するので、
// 許可されているファイル形式でしか拡張子をつけない
ext ? correctFilename(filename, ext) : filename,
);
if (meta.objectStorageSetPublicRead) params.ACL = 'public-read'; if (meta.objectStorageSetPublicRead) params.ACL = 'public-read';
const s3 = this.s3Service.getS3(meta); await this.s3Service.upload(meta, params)
const upload = s3.upload(params, {
partSize: s3.endpoint.hostname === 'storage.googleapis.com' ? 500 * 1024 * 1024 : 8 * 1024 * 1024,
});
await upload.promise()
.then( .then(
result => { result => {
if (result) { if ('Bucket' in result) { // CompleteMultipartUploadCommandOutput
this.registerLogger.debug(`Uploaded: ${result.Bucket}/${result.Key} => ${result.Location}`); this.registerLogger.debug(`Uploaded: ${result.Bucket}/${result.Key} => ${result.Location}`);
} else { } else { // AbortMultipartUploadCommandOutput
this.registerLogger.error(`Upload Result Empty: key = ${key}, filename = ${filename}`); this.registerLogger.error(`Upload Result Aborted: key = ${key}, filename = ${filename}`);
} }
}, })
.catch(
err => { err => {
this.registerLogger.error(`Upload Failed: key = ${key}, filename = ${filename}`, err); this.registerLogger.error(`Upload Failed: key = ${key}, filename = ${filename}`, err);
}, },
@ -466,7 +464,12 @@ export class DriveService {
//} //}
// detect name // detect name
const detectedName = name ?? (info.type.ext ? `untitled.${info.type.ext}` : 'untitled'); const detectedName = correctFilename(
// DriveFile.nameは256文字, validateFileNameは200文字制限であるため、
// extを付加してデータベースの文字数制限に当たることはまずない
(name && this.driveFileEntityService.validateFileName(name)) ? name : 'untitled',
info.type.ext,
);
if (user && !force) { if (user && !force) {
// Check if there is a file with the same hash // Check if there is a file with the same hash
@ -616,12 +619,15 @@ export class DriveService {
}); });
} }
// 統計を更新
this.driveChart.update(file, true); this.driveChart.update(file, true);
if (file.userHost == null) {
// ローカルユーザーのみ
this.perUserDriveChart.update(file, true); this.perUserDriveChart.update(file, true);
if (file.userHost !== null) { } else {
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
this.instanceChart.updateDrive(file, true); this.instanceChart.updateDrive(file, true);
} }
}
return file; return file;
} }
@ -703,24 +709,37 @@ export class DriveService {
this.driveFilesRepository.delete(file.id); this.driveFilesRepository.delete(file.id);
} }
// 統計を更新
this.driveChart.update(file, false); this.driveChart.update(file, false);
if (file.userHost == null) {
// ローカルユーザーのみ
this.perUserDriveChart.update(file, false); this.perUserDriveChart.update(file, false);
if (file.userHost !== null) { } else {
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
this.instanceChart.updateDrive(file, false); this.instanceChart.updateDrive(file, false);
} }
} }
}
@bindThis @bindThis
public async deleteObjectStorageFile(key: string) { public async deleteObjectStorageFile(key: string) {
const meta = await this.metaService.fetch(); const meta = await this.metaService.fetch();
try {
const s3 = this.s3Service.getS3(meta); const param = {
Bucket: meta.objectStorageBucket,
await s3.deleteObject({
Bucket: meta.objectStorageBucket!,
Key: key, Key: key,
}).promise(); } as DeleteObjectCommandInput;
await this.s3Service.delete(meta, param);
} catch (err: any) {
if (err.name === 'NoSuchKey') {
this.deleteLogger.warn(`The object storage had no such key to delete: ${key}. Skipping this.`, err as Error);
return;
} else {
throw new Error(`Failed to delete the file from the object storage with the given key: ${key}`, {
cause: err,
});
}
}
} }
@bindThis @bindThis
@ -736,10 +755,12 @@ export class DriveService {
requestIp = null, requestIp = null,
requestHeaders = null, requestHeaders = null,
}: UploadFromUrlArgs): Promise<DriveFile> { }: UploadFromUrlArgs): Promise<DriveFile> {
let name = new URL(url).pathname.split('/').pop() ?? null; // Create temp file
if (name == null || !this.driveFileEntityService.validateFileName(name)) { const [path, cleanup] = await createTemp();
name = null;
} try {
// write content at URL to temp file
const { filename: name } = await this.downloadService.downloadUrl(url, path);
// If the comment is same as the name, skip comment // If the comment is same as the name, skip comment
// (image.name is passed in when receiving attachment) // (image.name is passed in when receiving attachment)
@ -747,13 +768,6 @@ export class DriveService {
comment = null; comment = null;
} }
// Create temp file
const [path, cleanup] = await createTemp();
try {
// write content at URL to temp file
await this.downloadService.downloadUrl(url, path);
const driveFile = await this.addFile({ user, path, name, comment, folderId, force, isLink, url, uri, sensitive, requestIp, requestHeaders }); const driveFile = await this.addFile({ user, path, name, comment, folderId, force, isLink, url, uri, sensitive, requestIp, requestHeaders });
this.downloaderLogger.succ(`Got: ${driveFile.id}`); this.downloaderLogger.succ(`Got: ${driveFile.id}`);
return driveFile!; return driveFile!;

View File

@ -1,7 +1,8 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import Redis from 'ioredis';
import type { InstancesRepository } from '@/models/index.js'; import type { InstancesRepository } from '@/models/index.js';
import type { Instance } from '@/models/entities/Instance.js'; import type { Instance } from '@/models/entities/Instance.js';
import { Cache } from '@/misc/cache.js'; import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
@ -9,23 +10,40 @@ import { bindThis } from '@/decorators.js';
@Injectable() @Injectable()
export class FederatedInstanceService { export class FederatedInstanceService {
private cache: Cache<Instance>; public federatedInstanceCache: RedisKVCache<Instance | null>;
constructor( constructor(
@Inject(DI.redis)
private redisClient: Redis.Redis,
@Inject(DI.instancesRepository) @Inject(DI.instancesRepository)
private instancesRepository: InstancesRepository, private instancesRepository: InstancesRepository,
private utilityService: UtilityService, private utilityService: UtilityService,
private idService: IdService, private idService: IdService,
) { ) {
this.cache = new Cache<Instance>(1000 * 60 * 60); this.federatedInstanceCache = new RedisKVCache<Instance | null>(this.redisClient, 'federatedInstance', {
lifetime: 1000 * 60 * 60 * 24, // 24h
memoryCacheLifetime: 1000 * 60 * 30, // 30m
fetcher: (key) => this.instancesRepository.findOneBy({ host: key }),
toRedisConverter: (value) => JSON.stringify(value),
fromRedisConverter: (value) => {
const parsed = JSON.parse(value);
return {
...parsed,
firstRetrievedAt: new Date(parsed.firstRetrievedAt),
latestRequestReceivedAt: parsed.latestRequestReceivedAt ? new Date(parsed.latestRequestReceivedAt) : null,
infoUpdatedAt: parsed.infoUpdatedAt ? new Date(parsed.infoUpdatedAt) : null,
};
},
});
} }
@bindThis @bindThis
public async fetch(host: string): Promise<Instance> { public async fetch(host: string): Promise<Instance> {
host = this.utilityService.toPuny(host); host = this.utilityService.toPuny(host);
const cached = this.cache.get(host); const cached = await this.federatedInstanceCache.get(host);
if (cached) return cached; if (cached) return cached;
const index = await this.instancesRepository.findOneBy({ host }); const index = await this.instancesRepository.findOneBy({ host });
@ -37,10 +55,10 @@ export class FederatedInstanceService {
firstRetrievedAt: new Date(), firstRetrievedAt: new Date(),
}).then(x => this.instancesRepository.findOneByOrFail(x.identifiers[0])); }).then(x => this.instancesRepository.findOneByOrFail(x.identifiers[0]));
this.cache.set(host, i); this.federatedInstanceCache.set(host, i);
return i; return i;
} else { } else {
this.cache.set(host, index); this.federatedInstanceCache.set(host, index);
return index; return index;
} }
} }
@ -49,10 +67,10 @@ export class FederatedInstanceService {
public async updateCachePartial(host: string, data: Partial<Instance>): Promise<void> { public async updateCachePartial(host: string, data: Partial<Instance>): Promise<void> {
host = this.utilityService.toPuny(host); host = this.utilityService.toPuny(host);
const cached = this.cache.get(host); const cached = await this.federatedInstanceCache.get(host);
if (cached == null) return; if (cached == null) return;
this.cache.set(host, { this.federatedInstanceCache.set(host, {
...cached, ...cached,
...data, ...data,
}); });

View File

@ -14,9 +14,8 @@ import type {
MainStreamTypes, MainStreamTypes,
NoteStreamTypes, NoteStreamTypes,
UserListStreamTypes, UserListStreamTypes,
UserStreamTypes,
} from '@/server/api/stream/types.js'; } from '@/server/api/stream/types.js';
import type { Packed } from '@/misc/schema.js'; import type { Packed } from '@/misc/json-schema.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@ -49,11 +48,6 @@ export class GlobalEventService {
this.publish('internal', type, typeof value === 'undefined' ? null : value); this.publish('internal', type, typeof value === 'undefined' ? null : value);
} }
@bindThis
public publishUserEvent<K extends keyof UserStreamTypes>(userId: User['id'], type: K, value?: UserStreamTypes[K]): void {
this.publish(`user:${userId}`, type, typeof value === 'undefined' ? null : value);
}
@bindThis @bindThis
public publishBroadcastStream<K extends keyof BroadcastTypes>(type: K, value?: BroadcastTypes[K]): void { public publishBroadcastStream<K extends keyof BroadcastTypes>(type: K, value?: BroadcastTypes[K]): void {
this.publish('broadcast', type, typeof value === 'undefined' ? null : value); this.publish('broadcast', type, typeof value === 'undefined' ? null : value);

View File

@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { ulid } from 'ulid'; import { ulid } from 'ulid';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { genAid } from '@/misc/id/aid.js'; import { genAid, parseAid } from '@/misc/id/aid.js';
import { genMeid } from '@/misc/id/meid.js'; import { genMeid } from '@/misc/id/meid.js';
import { genMeidg } from '@/misc/id/meidg.js'; import { genMeidg } from '@/misc/id/meidg.js';
import { genObjectId } from '@/misc/id/object-id.js'; import { genObjectId } from '@/misc/id/object-id.js';
@ -32,4 +32,17 @@ export class IdService {
default: throw new Error('unrecognized id generation method'); default: throw new Error('unrecognized id generation method');
} }
} }
@bindThis
public parse(id: string): { date: Date; } {
switch (this.method) {
case 'aid': return parseAid(id);
// TODO
//case 'meid':
//case 'meidg':
//case 'ulid':
//case 'objectid':
default: throw new Error('unrecognized id generation method');
}
}
} }

View File

@ -15,15 +15,28 @@ export type IImageStream = {
type: string; type: string;
}; };
export type IImageStreamable = IImage | IImageStream; export type IImageSharp = {
data: sharp.Sharp;
ext: string | null;
type: string;
};
export type IImageStreamable = IImage | IImageStream | IImageSharp;
export const webpDefault: sharp.WebpOptions = { export const webpDefault: sharp.WebpOptions = {
quality: 85, quality: 77,
alphaQuality: 95, alphaQuality: 95,
lossless: false, lossless: false,
nearLossless: false, nearLossless: false,
smartSubsample: true, smartSubsample: true,
mixed: true, mixed: true,
effort: 2,
};
export const avifDefault: sharp.AvifOptions = {
quality: 60,
lossless: false,
effort: 2,
}; };
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@ -37,36 +50,6 @@ export class ImageProcessingService {
) { ) {
} }
/**
* Convert to JPEG
* with resize, remove metadata, resolve orientation, stop animation
*/
@bindThis
public async convertToJpeg(path: string, width: number, height: number): Promise<IImage> {
return this.convertSharpToJpeg(await sharp(path), width, height);
}
@bindThis
public async convertSharpToJpeg(sharp: sharp.Sharp, width: number, height: number): Promise<IImage> {
const data = await sharp
.resize(width, height, {
fit: 'inside',
withoutEnlargement: true,
})
.rotate()
.jpeg({
quality: 85,
progressive: true,
})
.toBuffer();
return {
data,
ext: 'jpg',
type: 'image/jpeg',
};
}
/** /**
* Convert to WebP * Convert to WebP
* with resize, remove metadata, resolve orientation, stop animation * with resize, remove metadata, resolve orientation, stop animation
@ -78,29 +61,22 @@ export class ImageProcessingService {
@bindThis @bindThis
public async convertSharpToWebp(sharp: sharp.Sharp, width: number, height: number, options: sharp.WebpOptions = webpDefault): Promise<IImage> { public async convertSharpToWebp(sharp: sharp.Sharp, width: number, height: number, options: sharp.WebpOptions = webpDefault): Promise<IImage> {
const data = await sharp const result = this.convertSharpToWebpStream(sharp, width, height, options);
.resize(width, height, {
fit: 'inside',
withoutEnlargement: true,
})
.rotate()
.webp(options)
.toBuffer();
return { return {
data, data: await result.data.toBuffer(),
ext: 'webp', ext: result.ext,
type: 'image/webp', type: result.type,
}; };
} }
@bindThis @bindThis
public convertToWebpStream(path: string, width: number, height: number, options: sharp.WebpOptions = webpDefault): IImageStream { public convertToWebpStream(path: string, width: number, height: number, options: sharp.WebpOptions = webpDefault): IImageSharp {
return this.convertSharpToWebpStream(sharp(path), width, height, options); return this.convertSharpToWebpStream(sharp(path), width, height, options);
} }
@bindThis @bindThis
public convertSharpToWebpStream(sharp: sharp.Sharp, width: number, height: number, options: sharp.WebpOptions = webpDefault): IImageStream { public convertSharpToWebpStream(sharp: sharp.Sharp, width: number, height: number, options: sharp.WebpOptions = webpDefault): IImageSharp {
const data = sharp const data = sharp
.resize(width, height, { .resize(width, height, {
fit: 'inside', fit: 'inside',
@ -115,13 +91,56 @@ export class ImageProcessingService {
type: 'image/webp', type: 'image/webp',
}; };
} }
/**
* Convert to Avif
* with resize, remove metadata, resolve orientation, stop animation
*/
@bindThis
public async convertToAvif(path: string, width: number, height: number, options: sharp.AvifOptions = avifDefault): Promise<IImage> {
return this.convertSharpToAvif(sharp(path), width, height, options);
}
@bindThis
public async convertSharpToAvif(sharp: sharp.Sharp, width: number, height: number, options: sharp.AvifOptions = avifDefault): Promise<IImage> {
const result = this.convertSharpToAvifStream(sharp, width, height, options);
return {
data: await result.data.toBuffer(),
ext: result.ext,
type: result.type,
};
}
@bindThis
public convertToAvifStream(path: string, width: number, height: number, options: sharp.AvifOptions = avifDefault): IImageSharp {
return this.convertSharpToAvifStream(sharp(path), width, height, options);
}
@bindThis
public convertSharpToAvifStream(sharp: sharp.Sharp, width: number, height: number, options: sharp.AvifOptions = avifDefault): IImageSharp {
const data = sharp
.resize(width, height, {
fit: 'inside',
withoutEnlargement: true,
})
.rotate()
.avif(options);
return {
data,
ext: 'avif',
type: 'image/avif',
};
}
/** /**
* Convert to PNG * Convert to PNG
* with resize, remove metadata, resolve orientation, stop animation * with resize, remove metadata, resolve orientation, stop animation
*/ */
@bindThis @bindThis
public async convertToPng(path: string, width: number, height: number): Promise<IImage> { public async convertToPng(path: string, width: number, height: number): Promise<IImage> {
return this.convertSharpToPng(await sharp(path), width, height); return this.convertSharpToPng(sharp(path), width, height);
} }
@bindThis @bindThis

View File

@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { IsNull } from 'typeorm'; import { IsNull } from 'typeorm';
import type { LocalUser } from '@/models/entities/User.js'; import type { LocalUser } from '@/models/entities/User.js';
import type { UsersRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js';
import { Cache } from '@/misc/cache.js'; import { MemorySingleCache } from '@/misc/cache.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { CreateSystemUserService } from '@/core/CreateSystemUserService.js'; import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@ -11,7 +11,7 @@ const ACTOR_USERNAME = 'instance.actor' as const;
@Injectable() @Injectable()
export class InstanceActorService { export class InstanceActorService {
private cache: Cache<LocalUser>; private cache: MemorySingleCache<LocalUser>;
constructor( constructor(
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
@ -19,12 +19,12 @@ export class InstanceActorService {
private createSystemUserService: CreateSystemUserService, private createSystemUserService: CreateSystemUserService,
) { ) {
this.cache = new Cache<LocalUser>(Infinity); this.cache = new MemorySingleCache<LocalUser>(Infinity);
} }
@bindThis @bindThis
public async getInstanceActor(): Promise<LocalUser> { public async getInstanceActor(): Promise<LocalUser> {
const cached = this.cache.get(null); const cached = this.cache.get();
if (cached) return cached; if (cached) return cached;
const user = await this.usersRepository.findOneBy({ const user = await this.usersRepository.findOneBy({
@ -33,11 +33,11 @@ export class InstanceActorService {
}) as LocalUser | undefined; }) as LocalUser | undefined;
if (user) { if (user) {
this.cache.set(null, user); this.cache.set(user);
return user; return user;
} else { } else {
const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME) as LocalUser; const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME) as LocalUser;
this.cache.set(null, created); this.cache.set(created);
return created; return created;
} }
} }

View File

@ -14,8 +14,8 @@ export class MetaService implements OnApplicationShutdown {
private intervalId: NodeJS.Timer; private intervalId: NodeJS.Timer;
constructor( constructor(
@Inject(DI.redisSubscriber) @Inject(DI.redisForPubsub)
private redisSubscriber: Redis.Redis, private redisForPubsub: Redis.Redis,
@Inject(DI.db) @Inject(DI.db)
private db: DataSource, private db: DataSource,
@ -33,7 +33,7 @@ export class MetaService implements OnApplicationShutdown {
}, 1000 * 60 * 5); }, 1000 * 60 * 5);
} }
this.redisSubscriber.on('message', this.onMessage); this.redisForPubsub.on('message', this.onMessage);
} }
@bindThis @bindThis
@ -122,6 +122,6 @@ export class MetaService implements OnApplicationShutdown {
@bindThis @bindThis
public onApplicationShutdown(signal?: string | undefined) { public onApplicationShutdown(signal?: string | undefined) {
clearInterval(this.intervalId); clearInterval(this.intervalId);
this.redisSubscriber.off('message', this.onMessage); this.redisForPubsub.off('message', this.onMessage);
} }
} }

View File

@ -1,6 +1,7 @@
import { setImmediate } from 'node:timers/promises'; import { setImmediate } from 'node:timers/promises';
import * as mfm from 'mfm-js'; import * as mfm from 'mfm-js';
import { In, DataSource } from 'typeorm'; import { In, DataSource } from 'typeorm';
import Redis from 'ioredis';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { extractMentions } from '@/misc/extract-mentions.js'; import { extractMentions } from '@/misc/extract-mentions.js';
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
@ -19,7 +20,7 @@ import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js
import { checkWordMute } from '@/misc/check-word-mute.js'; import { checkWordMute } from '@/misc/check-word-mute.js';
import type { Channel } from '@/models/entities/Channel.js'; import type { Channel } from '@/models/entities/Channel.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { Cache } from '@/misc/cache.js'; import { MemorySingleCache } from '@/misc/cache.js';
import type { UserProfile } from '@/models/entities/UserProfile.js'; import type { UserProfile } from '@/models/entities/UserProfile.js';
import { RelayService } from '@/core/RelayService.js'; import { RelayService } from '@/core/RelayService.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
@ -30,7 +31,7 @@ import PerUserNotesChart from '@/core/chart/charts/per-user-notes.js';
import InstanceChart from '@/core/chart/charts/instance.js'; import InstanceChart from '@/core/chart/charts/instance.js';
import ActiveUsersChart from '@/core/chart/charts/active-users.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
import { CreateNotificationService } from '@/core/CreateNotificationService.js'; import { NotificationService } from '@/core/NotificationService.js';
import { WebhookService } from '@/core/WebhookService.js'; import { WebhookService } from '@/core/WebhookService.js';
import { HashtagService } from '@/core/HashtagService.js'; import { HashtagService } from '@/core/HashtagService.js';
import { AntennaService } from '@/core/AntennaService.js'; import { AntennaService } from '@/core/AntennaService.js';
@ -44,8 +45,9 @@ import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
import { RoleService } from '@/core/RoleService.js'; import { RoleService } from '@/core/RoleService.js';
import { MetaService } from '@/core/MetaService.js';
const mutedWordsCache = new Cache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5); const mutedWordsCache = new MemorySingleCache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@ -59,7 +61,7 @@ class NotificationManager {
constructor( constructor(
private mutingsRepository: MutingsRepository, private mutingsRepository: MutingsRepository,
private createNotificationService: CreateNotificationService, private notificationService: NotificationService,
notifier: { id: User['id']; }, notifier: { id: User['id']; },
note: Note, note: Note,
) { ) {
@ -100,7 +102,7 @@ class NotificationManager {
// 通知される側のユーザーが通知する側のユーザーをミュートしていない限りは通知する // 通知される側のユーザーが通知する側のユーザーをミュートしていない限りは通知する
if (!mentioneesMutedUserIds.includes(this.notifier.id)) { if (!mentioneesMutedUserIds.includes(this.notifier.id)) {
this.createNotificationService.createNotification(x.target, x.reason, { this.notificationService.createNotification(x.target, x.reason, {
notifierId: this.notifier.id, notifierId: this.notifier.id,
noteId: this.note.id, noteId: this.note.id,
}); });
@ -125,6 +127,7 @@ type Option = {
files?: DriveFile[] | null; files?: DriveFile[] | null;
poll?: IPoll | null; poll?: IPoll | null;
localOnly?: boolean | null; localOnly?: boolean | null;
reactionAcceptance?: Note['reactionAcceptance'];
cw?: string | null; cw?: string | null;
visibility?: string; visibility?: string;
visibleUsers?: MinimumUser[] | null; visibleUsers?: MinimumUser[] | null;
@ -148,6 +151,9 @@ export class NoteCreateService implements OnApplicationShutdown {
@Inject(DI.db) @Inject(DI.db)
private db: DataSource, private db: DataSource,
@Inject(DI.redis)
private redisClient: Redis.Redis,
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@ -181,7 +187,7 @@ export class NoteCreateService implements OnApplicationShutdown {
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
private queueService: QueueService, private queueService: QueueService,
private noteReadService: NoteReadService, private noteReadService: NoteReadService,
private createNotificationService: CreateNotificationService, private notificationService: NotificationService,
private relayService: RelayService, private relayService: RelayService,
private federatedInstanceService: FederatedInstanceService, private federatedInstanceService: FederatedInstanceService,
private hashtagService: HashtagService, private hashtagService: HashtagService,
@ -191,11 +197,12 @@ export class NoteCreateService implements OnApplicationShutdown {
private apDeliverManagerService: ApDeliverManagerService, private apDeliverManagerService: ApDeliverManagerService,
private apRendererService: ApRendererService, private apRendererService: ApRendererService,
private roleService: RoleService, private roleService: RoleService,
private metaService: MetaService,
private notesChart: NotesChart, private notesChart: NotesChart,
private perUserNotesChart: PerUserNotesChart, private perUserNotesChart: PerUserNotesChart,
private activeUsersChart: ActiveUsersChart, private activeUsersChart: ActiveUsersChart,
private instanceChart: InstanceChart, private instanceChart: InstanceChart,
) {} ) { }
@bindThis @bindThis
public async create(user: { public async create(user: {
@ -229,7 +236,9 @@ export class NoteCreateService implements OnApplicationShutdown {
if (data.channel != null) data.localOnly = true; if (data.channel != null) data.localOnly = true;
if (data.visibility === 'public' && data.channel == null) { if (data.visibility === 'public' && data.channel == null) {
if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) { if ((data.text != null) && (await this.metaService.fetch()).sensitiveWords.some(w => data.text!.includes(w))) {
data.visibility = 'home';
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
data.visibility = 'home'; data.visibility = 'home';
} }
} }
@ -316,6 +325,14 @@ export class NoteCreateService implements OnApplicationShutdown {
const note = await this.insertNote(user, data, tags, emojis, mentionedUsers); const note = await this.insertNote(user, data, tags, emojis, mentionedUsers);
if (data.channel) {
this.redisClient.xadd(
`channelTimeline:${data.channel.id}`,
'MAXLEN', '~', '1000',
`${this.idService.parse(note.id).date.getTime()}-*`,
'note', note.id);
}
setImmediate('post created', { signal: this.#shutdownController.signal }).then( setImmediate('post created', { signal: this.#shutdownController.signal }).then(
() => this.postNoteCreated(note, user, data, silent, tags!, mentionedUsers!), () => this.postNoteCreated(note, user, data, silent, tags!, mentionedUsers!),
() => { /* aborted, ignore this */ }, () => { /* aborted, ignore this */ },
@ -346,6 +363,7 @@ export class NoteCreateService implements OnApplicationShutdown {
emojis, emojis,
userId: user.id, userId: user.id,
localOnly: data.localOnly!, localOnly: data.localOnly!,
reactionAcceptance: data.reactionAcceptance,
visibility: data.visibility as any, visibility: data.visibility as any,
visibleUserIds: data.visibility === 'specified' visibleUserIds: data.visibility === 'specified'
? data.visibleUsers ? data.visibleUsers
@ -429,15 +447,20 @@ export class NoteCreateService implements OnApplicationShutdown {
createdAt: User['createdAt']; createdAt: User['createdAt'];
isBot: User['isBot']; isBot: User['isBot'];
}, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) { }, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) {
// 統計を更新 const meta = await this.metaService.fetch();
this.notesChart.update(note, true); this.notesChart.update(note, true);
if (meta.enableChartsForRemoteUser || (user.host == null)) {
this.perUserNotesChart.update(user, note, true); this.perUserNotesChart.update(user, note, true);
}
// Register host // Register host
if (this.userEntityService.isRemoteUser(user)) { if (this.userEntityService.isRemoteUser(user)) {
this.federatedInstanceService.fetch(user.host).then(i => { this.federatedInstanceService.fetch(user.host).then(async i => {
this.instancesRepository.increment({ id: i.id }, 'notesCount', 1); this.instancesRepository.increment({ id: i.id }, 'notesCount', 1);
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
this.instanceChart.updateNote(i.host, note, true); this.instanceChart.updateNote(i.host, note, true);
}
}); });
} }
@ -450,7 +473,7 @@ export class NoteCreateService implements OnApplicationShutdown {
this.incNotesCountOfUser(user); this.incNotesCountOfUser(user);
// Word mute // Word mute
mutedWordsCache.fetch(null, () => this.userProfilesRepository.find({ mutedWordsCache.fetch(() => this.userProfilesRepository.find({
where: { where: {
enableWordMute: true, enableWordMute: true,
}, },
@ -479,18 +502,6 @@ export class NoteCreateService implements OnApplicationShutdown {
}); });
} }
// Channel
if (note.channelId) {
this.channelFollowingsRepository.findBy({ followeeId: note.channelId }).then(followings => {
for (const following of followings) {
this.noteReadService.insertNoteUnread(following.followerId, note, {
isSpecified: false,
isMentioned: false,
});
}
});
}
if (data.reply) { if (data.reply) {
this.saveReply(data.reply, note); this.saveReply(data.reply, note);
} }
@ -552,7 +563,7 @@ export class NoteCreateService implements OnApplicationShutdown {
} }
}); });
const nm = new NotificationManager(this.mutingsRepository, this.createNotificationService, user, note); const nm = new NotificationManager(this.mutingsRepository, this.notificationService, user, note);
await this.createMentionedEvents(mentionedUsers, note, nm); await this.createMentionedEvents(mentionedUsers, note, nm);

View File

@ -16,6 +16,7 @@ import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerServ
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { MetaService } from '@/core/MetaService.js';
@Injectable() @Injectable()
export class NoteDeleteService { export class NoteDeleteService {
@ -39,6 +40,7 @@ export class NoteDeleteService {
private federatedInstanceService: FederatedInstanceService, private federatedInstanceService: FederatedInstanceService,
private apRendererService: ApRendererService, private apRendererService: ApRendererService,
private apDeliverManagerService: ApDeliverManagerService, private apDeliverManagerService: ApDeliverManagerService,
private metaService: MetaService,
private notesChart: NotesChart, private notesChart: NotesChart,
private perUserNotesChart: PerUserNotesChart, private perUserNotesChart: PerUserNotesChart,
private instanceChart: InstanceChart, private instanceChart: InstanceChart,
@ -95,14 +97,19 @@ export class NoteDeleteService {
} }
//#endregion //#endregion
// 統計を更新 const meta = await this.metaService.fetch();
this.notesChart.update(note, false); this.notesChart.update(note, false);
if (meta.enableChartsForRemoteUser || (user.host == null)) {
this.perUserNotesChart.update(user, note, false); this.perUserNotesChart.update(user, note, false);
}
if (this.userEntityService.isRemoteUser(user)) { if (this.userEntityService.isRemoteUser(user)) {
this.federatedInstanceService.fetch(user.host).then(i => { this.federatedInstanceService.fetch(user.host).then(async i => {
this.instancesRepository.decrement({ id: i.id }, 'notesCount', 1); this.instancesRepository.decrement({ id: i.id }, 'notesCount', 1);
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
this.instanceChart.updateNote(i.host, note, false); this.instanceChart.updateNote(i.host, note, false);
}
}); });
} }
} }

View File

@ -1,28 +1,20 @@
import { setTimeout } from 'node:timers/promises'; import { setTimeout } from 'node:timers/promises';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { In, IsNull, Not } from 'typeorm'; import { In } from 'typeorm';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
import type { Channel } from '@/models/entities/Channel.js'; import type { Packed } from '@/misc/json-schema.js';
import type { Packed } from '@/misc/schema.js';
import type { Note } from '@/models/entities/Note.js'; import type { Note } from '@/models/entities/Note.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
import type { UsersRepository, NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository, FollowingsRepository, ChannelFollowingsRepository, AntennaNotesRepository } from '@/models/index.js'; import type { NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository } from '@/models/index.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { NotificationService } from './NotificationService.js';
import { AntennaService } from './AntennaService.js';
import { PushNotificationService } from './PushNotificationService.js';
@Injectable() @Injectable()
export class NoteReadService implements OnApplicationShutdown { export class NoteReadService implements OnApplicationShutdown {
#shutdownController = new AbortController(); #shutdownController = new AbortController();
constructor( constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.noteUnreadsRepository) @Inject(DI.noteUnreadsRepository)
private noteUnreadsRepository: NoteUnreadsRepository, private noteUnreadsRepository: NoteUnreadsRepository,
@ -32,21 +24,8 @@ export class NoteReadService implements OnApplicationShutdown {
@Inject(DI.noteThreadMutingsRepository) @Inject(DI.noteThreadMutingsRepository)
private noteThreadMutingsRepository: NoteThreadMutingsRepository, private noteThreadMutingsRepository: NoteThreadMutingsRepository,
@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,
@Inject(DI.channelFollowingsRepository)
private channelFollowingsRepository: ChannelFollowingsRepository,
@Inject(DI.antennaNotesRepository)
private antennaNotesRepository: AntennaNotesRepository,
private userEntityService: UserEntityService,
private idService: IdService, private idService: IdService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
private notificationService: NotificationService,
private antennaService: AntennaService,
private pushNotificationService: PushNotificationService,
) { ) {
} }
@ -57,7 +36,6 @@ export class NoteReadService implements OnApplicationShutdown {
isMentioned: boolean; isMentioned: boolean;
}): Promise<void> { }): Promise<void> {
//#region ミュートしているなら無視 //#region ミュートしているなら無視
// TODO: 現在の仕様ではChannelにミュートは適用されないのでよしなにケアする
const mute = await this.mutingsRepository.findBy({ const mute = await this.mutingsRepository.findBy({
muterId: userId, muterId: userId,
}); });
@ -77,7 +55,6 @@ export class NoteReadService implements OnApplicationShutdown {
userId: userId, userId: userId,
isSpecified: params.isSpecified, isSpecified: params.isSpecified,
isMentioned: params.isMentioned, isMentioned: params.isMentioned,
noteChannelId: note.channelId,
noteUserId: note.userId, noteUserId: note.userId,
}; };
@ -95,9 +72,6 @@ export class NoteReadService implements OnApplicationShutdown {
if (params.isSpecified) { if (params.isSpecified) {
this.globalEventService.publishMainStream(userId, 'unreadSpecifiedNote', note.id); this.globalEventService.publishMainStream(userId, 'unreadSpecifiedNote', note.id);
} }
if (note.channelId) {
this.globalEventService.publishMainStream(userId, 'unreadChannel', note.id);
}
}, () => { /* aborted, ignore it */ }); }, () => { /* aborted, ignore it */ });
} }
@ -105,23 +79,9 @@ export class NoteReadService implements OnApplicationShutdown {
public async read( public async read(
userId: User['id'], userId: User['id'],
notes: (Note | Packed<'Note'>)[], notes: (Note | Packed<'Note'>)[],
info?: {
following: Set<User['id']>;
followingChannels: Set<Channel['id']>;
},
): Promise<void> { ): Promise<void> {
const followingChannels = info?.followingChannels ? info.followingChannels : new Set<string>((await this.channelFollowingsRepository.find({
where: {
followerId: userId,
},
select: ['followeeId'],
})).map(x => x.followeeId));
const myAntennas = (await this.antennaService.getAntennas()).filter(a => a.userId === userId);
const readMentions: (Note | Packed<'Note'>)[] = []; const readMentions: (Note | Packed<'Note'>)[] = [];
const readSpecifiedNotes: (Note | Packed<'Note'>)[] = []; const readSpecifiedNotes: (Note | Packed<'Note'>)[] = [];
const readChannelNotes: (Note | Packed<'Note'>)[] = [];
const readAntennaNotes: (Note | Packed<'Note'>)[] = [];
for (const note of notes) { for (const note of notes) {
if (note.mentions && note.mentions.includes(userId)) { if (note.mentions && note.mentions.includes(userId)) {
@ -129,25 +89,13 @@ export class NoteReadService implements OnApplicationShutdown {
} else if (note.visibleUserIds && note.visibleUserIds.includes(userId)) { } else if (note.visibleUserIds && note.visibleUserIds.includes(userId)) {
readSpecifiedNotes.push(note); readSpecifiedNotes.push(note);
} }
if (note.channelId && followingChannels.has(note.channelId)) {
readChannelNotes.push(note);
} }
if (note.user != null) { // たぶんnullになることは無いはずだけど一応 if ((readMentions.length > 0) || (readSpecifiedNotes.length > 0)) {
for (const antenna of myAntennas) {
if (await this.antennaService.checkHitAntenna(antenna, note, note.user)) {
readAntennaNotes.push(note);
}
}
}
}
if ((readMentions.length > 0) || (readSpecifiedNotes.length > 0) || (readChannelNotes.length > 0)) {
// Remove the record // Remove the record
await this.noteUnreadsRepository.delete({ await this.noteUnreadsRepository.delete({
userId: userId, userId: userId,
noteId: In([...readMentions.map(n => n.id), ...readSpecifiedNotes.map(n => n.id), ...readChannelNotes.map(n => n.id)]), noteId: In([...readMentions.map(n => n.id), ...readSpecifiedNotes.map(n => n.id)]),
}); });
// TODO: ↓まとめてクエリしたい // TODO: ↓まとめてクエリしたい
@ -171,49 +119,6 @@ export class NoteReadService implements OnApplicationShutdown {
this.globalEventService.publishMainStream(userId, 'readAllUnreadSpecifiedNotes'); this.globalEventService.publishMainStream(userId, 'readAllUnreadSpecifiedNotes');
} }
}); });
this.noteUnreadsRepository.countBy({
userId: userId,
noteChannelId: Not(IsNull()),
}).then(channelNoteCount => {
if (channelNoteCount === 0) {
// 全て既読になったイベントを発行
this.globalEventService.publishMainStream(userId, 'readAllChannels');
}
});
this.notificationService.readNotificationByQuery(userId, {
noteId: In([...readMentions.map(n => n.id), ...readSpecifiedNotes.map(n => n.id)]),
});
}
if (readAntennaNotes.length > 0) {
await this.antennaNotesRepository.update({
antennaId: In(myAntennas.map(a => a.id)),
noteId: In(readAntennaNotes.map(n => n.id)),
}, {
read: true,
});
// TODO: まとめてクエリしたい
for (const antenna of myAntennas) {
const count = await this.antennaNotesRepository.countBy({
antennaId: antenna.id,
read: false,
});
if (count === 0) {
this.globalEventService.publishMainStream(userId, 'readAntenna', antenna);
this.pushNotificationService.pushNotification(userId, 'readAntenna', { antennaId: antenna.id });
}
}
this.userEntityService.getHasUnreadAntenna(userId).then(unread => {
if (!unread) {
this.globalEventService.publishMainStream(userId, 'readAllAntennas');
this.pushNotificationService.pushNotification(userId, 'readAllAntennas', undefined);
}
});
} }
} }

View File

@ -1,70 +1,157 @@
import { Inject, Injectable } from '@nestjs/common'; import { setTimeout } from 'node:timers/promises';
import Redis from 'ioredis';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { In } from 'typeorm'; import { In } from 'typeorm';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { NotificationsRepository } from '@/models/index.js'; import type { MutingsRepository, UserProfile, UserProfilesRepository, UsersRepository } from '@/models/index.js';
import type { User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
import type { Notification } from '@/models/entities/Notification.js'; import type { Notification } from '@/models/entities/Notification.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { GlobalEventService } from './GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
import { PushNotificationService } from './PushNotificationService.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';
@Injectable() @Injectable()
export class NotificationService { export class NotificationService implements OnApplicationShutdown {
constructor( #shutdownController = new AbortController();
@Inject(DI.notificationsRepository)
private notificationsRepository: NotificationsRepository,
constructor(
@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 userEntityService: UserEntityService,
private idService: IdService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
private pushNotificationService: PushNotificationService, private pushNotificationService: PushNotificationService,
private cacheService: CacheService,
) { ) {
} }
@bindThis @bindThis
public async readNotification( public async readAllNotification(
userId: User['id'], userId: User['id'],
notificationIds: Notification['id'][], force = false,
) { ) {
if (notificationIds.length === 0) return; const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${userId}`);
// Update documents const latestNotificationIdsRes = await this.redisClient.xrevrange(
const result = await this.notificationsRepository.update({ `notificationTimeline:${userId}`,
notifieeId: userId, '+',
id: In(notificationIds), '-',
isRead: false, 'COUNT', 1);
}, { const latestNotificationId = latestNotificationIdsRes[0]?.[0];
isRead: true,
});
if (result.affected === 0) return; if (latestNotificationId == null) return;
if (!await this.userEntityService.getHasUnreadNotification(userId)) return this.postReadAllNotifications(userId); this.redisClient.set(`latestReadNotification:${userId}`, latestNotificationId);
else return this.postReadNotifications(userId, notificationIds);
if (force || latestReadNotificationId == null || (latestReadNotificationId < latestNotificationId)) {
return this.postReadAllNotifications(userId);
} }
@bindThis
public async readNotificationByQuery(
userId: User['id'],
query: Record<string, any>,
) {
const notificationIds = await this.notificationsRepository.findBy({
...query,
notifieeId: userId,
isRead: false,
}).then(notifications => notifications.map(notification => notification.id));
return this.readNotification(userId, notificationIds);
} }
@bindThis @bindThis
private postReadAllNotifications(userId: User['id']) { private postReadAllNotifications(userId: User['id']) {
this.globalEventService.publishMainStream(userId, 'readAllNotifications'); this.globalEventService.publishMainStream(userId, 'readAllNotifications');
return this.pushNotificationService.pushNotification(userId, 'readAllNotifications', undefined);
} }
@bindThis @bindThis
private postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) { public async createNotification(
return this.pushNotificationService.pushNotification(userId, 'readNotifications', { notificationIds }); notifieeId: User['id'],
type: Notification['type'],
data: Partial<Notification>,
): Promise<Notification | null> {
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) {
return null;
}
const mutings = await this.cacheService.userMutingsCache.fetch(notifieeId);
if (mutings.has(data.notifierId)) {
return null;
}
}
const notification = {
id: this.idService.genId(),
createdAt: new Date(),
type: type,
...data,
} as Notification;
const redisIdPromise = this.redisClient.xadd(
`notificationTimeline:${notifieeId}`,
'MAXLEN', '~', '300',
`${this.idService.parse(notification.id).date.getTime()}-*`,
'data', JSON.stringify(notification));
const packed = await this.notificationEntityService.pack(notification, notifieeId, {});
// Publish notification event
this.globalEventService.publishMainStream(notifieeId, 'notification', packed);
// 2秒経っても(今回作成した)通知が既読にならなかったら「未読の通知がありますよ」イベントを発行する
setTimeout(2000, '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! }));
}, () => { /* aborted, ignore it */ });
return notification;
}
// TODO
//const locales = await import('../../../../locales/index.js');
// TODO: locale ファイルをクライアント用とサーバー用で分けたい
@bindThis
private async emailNotificationFollow(userId: User['id'], follower: User) {
/*
const userProfile = await UserProfiles.findOneByOrFail({ userId: userId });
if (!userProfile.email || !userProfile.emailNotificationTypes.includes('follow')) return;
const locale = locales[userProfile.lang ?? 'ja-JP'];
const i18n = new I18n(locale);
// TODO: render user information html
sendEmail(userProfile.email, i18n.t('_email._follow.title'), `${follower.name} (@${Acct.toString(follower)})`, `${follower.name} (@${Acct.toString(follower)})`);
*/
}
@bindThis
private async emailNotificationReceiveFollowRequest(userId: User['id'], follower: User) {
/*
const userProfile = await UserProfiles.findOneByOrFail({ userId: userId });
if (!userProfile.email || !userProfile.emailNotificationTypes.includes('receiveFollowRequest')) return;
const locale = locales[userProfile.lang ?? 'ja-JP'];
const i18n = new I18n(locale);
// TODO: render user information html
sendEmail(userProfile.email, i18n.t('_email._receiveFollowRequest.title'), `${follower.name} (@${Acct.toString(follower)})`, `${follower.name} (@${Acct.toString(follower)})`);
*/
}
onApplicationShutdown(signal?: string | undefined): void {
this.#shutdownController.abort();
} }
} }

View File

@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import push from 'web-push'; import push from 'web-push';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import type { Packed } from '@/misc/schema'; import type { Packed } from '@/misc/json-schema';
import { getNoteSummary } from '@/misc/get-note-summary.js'; import { getNoteSummary } from '@/misc/get-note-summary.js';
import type { SwSubscriptionsRepository } from '@/models/index.js'; import type { SwSubscriptionsRepository } from '@/models/index.js';
import { MetaService } from '@/core/MetaService.js'; import { MetaService } from '@/core/MetaService.js';
@ -15,10 +15,6 @@ type PushNotificationsTypes = {
antenna: { id: string, name: string }; antenna: { id: string, name: string };
note: Packed<'Note'>; note: Packed<'Note'>;
}; };
'readNotifications': { notificationIds: string[] };
'readAllNotifications': undefined;
'readAntenna': { antennaId: string };
'readAllAntennas': undefined;
}; };
// Reduce length because push message servers have character limits // Reduce length because push message servers have character limits
@ -72,14 +68,6 @@ export class PushNotificationService {
}); });
for (const subscription of subscriptions) { for (const subscription of subscriptions) {
// Continue if sendReadMessage is false
if ([
'readNotifications',
'readAllNotifications',
'readAntenna',
'readAllAntennas',
].includes(type) && !subscription.sendReadMessage) continue;
const pushSubscription = { const pushSubscription = {
endpoint: subscription.endpoint, endpoint: subscription.endpoint,
keys: { keys: {

View File

@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { Brackets, ObjectLiteral } from 'typeorm'; import { Brackets, ObjectLiteral } from 'typeorm';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, MutedNotesRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository } from '@/models/index.js'; import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, MutedNotesRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository, RenoteMutingsRepository } from '@/models/index.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import type { SelectQueryBuilder } from 'typeorm'; import type { SelectQueryBuilder } from 'typeorm';
@ -29,6 +29,9 @@ export class QueryService {
@Inject(DI.mutingsRepository) @Inject(DI.mutingsRepository)
private mutingsRepository: MutingsRepository, private mutingsRepository: MutingsRepository,
@Inject(DI.renoteMutingsRepository)
private renoteMutingsRepository: RenoteMutingsRepository,
) { ) {
} }
@ -269,5 +272,24 @@ export class QueryService {
q.setParameters({ meId: me.id }); q.setParameters({ meId: me.id });
} }
} }
}
@bindThis
public generateMutedUserRenotesQueryForNotes(q: SelectQueryBuilder<any>, me: { id: User['id'] }): void {
const mutingQuery = this.renoteMutingsRepository.createQueryBuilder('renote_muting')
.select('renote_muting.muteeId')
.where('renote_muting.muterId = :muterId', { muterId: me.id });
q.andWhere(new Brackets(qb => {
qb
.where(new Brackets(qb => {
qb.where('note.renoteId IS NOT NULL');
qb.andWhere('note.text IS NULL');
qb.andWhere(`note.userId NOT IN (${ mutingQuery.getQuery() })`);
}))
.orWhere('note.renoteId IS NULL')
.orWhere('note.text IS NOT NULL');
}));
q.setParameters(mutingQuery.getParameters());
}
}

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