diff --git a/.config/example.yml b/.config/example.yml index c127eaae22..489cceec34 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -105,6 +105,16 @@ port: 3000 # socket: /path/to/misskey.sock # chmodSocket: '777' +# Proxy trust settings +# +# Changes how the server interpret the origin IP of the request. +# +# Any format supported by Fastify is accepted. +# Default: trust all proxies (i.e. trustProxy: true) +# See: https://fastify.dev/docs/latest/reference/server/#trustproxy +# +# trustProxy: 1 + # ┌──────────────────────────┐ #───┘ PostgreSQL configuration └──────────────────────────────── diff --git a/.editorconfig b/.editorconfig index def7baa1a8..ccf388f06e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,3 +13,7 @@ trim_trailing_whitespace = false [*.{yml,yaml}] indent_style = space + +[packages/backend/migration/*.js] +indent_style = space +indent_size = 4 diff --git a/.github/min.node-version b/.github/min.node-version index d5a159609d..b8ffd70759 100644 --- a/.github/min.node-version +++ b/.github/min.node-version @@ -1 +1 @@ -20.10.0 +22.15.0 diff --git a/.github/misskey/test.yml b/.github/misskey/test.yml index 3c807e8b9e..513bfb1ac0 100644 --- a/.github/misskey/test.yml +++ b/.github/misskey/test.yml @@ -15,3 +15,5 @@ redis: host: 127.0.0.1 port: 56312 id: aidx + +proxyRemoteFiles: true diff --git a/.github/workflows/api-misskey-js.yml b/.github/workflows/api-misskey-js.yml index 6117e69c03..76a0bae8cc 100644 --- a/.github/workflows/api-misskey-js.yml +++ b/.github/workflows/api-misskey-js.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v4.3.0 - name: Setup pnpm uses: pnpm/action-setup@v4.1.0 diff --git a/.github/workflows/changelog-check.yml b/.github/workflows/changelog-check.yml index 5ca27749bb..f4abedd960 100644 --- a/.github/workflows/changelog-check.yml +++ b/.github/workflows/changelog-check.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Checkout head - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v4.3.0 - name: Setup Node.js uses: actions/setup-node@v4.4.0 with: diff --git a/.github/workflows/check-misskey-js-autogen.yml b/.github/workflows/check-misskey-js-autogen.yml index 22d500c306..05034ea0f4 100644 --- a/.github/workflows/check-misskey-js-autogen.yml +++ b/.github/workflows/check-misskey-js-autogen.yml @@ -18,7 +18,7 @@ jobs: if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }} steps: - name: checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v4.3.0 with: submodules: true persist-credentials: false @@ -66,7 +66,7 @@ jobs: if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }} steps: - name: checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v4.3.0 with: submodules: true persist-credentials: false diff --git a/.github/workflows/check-misskey-js-version.yml b/.github/workflows/check-misskey-js-version.yml index 2b15cbee53..0e336a1551 100644 --- a/.github/workflows/check-misskey-js-version.yml +++ b/.github/workflows/check-misskey-js-version.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v4.3.0 - name: Check version run: | if [ "$(jq -r '.version' package.json)" != "$(jq -r '.version' packages/misskey-js/package.json)" ]; then diff --git a/.github/workflows/check-spdx-license-id.yml b/.github/workflows/check-spdx-license-id.yml index e40a4557df..d0b1be4991 100644 --- a/.github/workflows/check-spdx-license-id.yml +++ b/.github/workflows/check-spdx-license-id.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v4.3.0 - name: Check run: | counter=0 @@ -50,6 +50,7 @@ jobs: "packages/backend/test" "packages/frontend-shared/@types" "packages/frontend-shared/js" + "packages/frontend-builder" "packages/frontend/.storybook" "packages/frontend/@types" "packages/frontend/lib" diff --git a/.github/workflows/check_copyright_year.yml b/.github/workflows/check_copyright_year.yml index eaf922d4bc..d891a538c6 100644 --- a/.github/workflows/check_copyright_year.yml +++ b/.github/workflows/check_copyright_year.yml @@ -10,7 +10,7 @@ jobs: check_copyright_year: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 - run: | if [ "$(grep Copyright COPYING | sed -e 's/.*2014-\([0-9]*\) .*/\1/g')" -ne "$(date +%Y)" ]; then echo "Please change copyright year!" diff --git a/.github/workflows/deploy-test-environment.yml b/.github/workflows/deploy-test-environment.yml index 46baf7421b..d838bc35e5 100644 --- a/.github/workflows/deploy-test-environment.yml +++ b/.github/workflows/deploy-test-environment.yml @@ -28,7 +28,7 @@ jobs: wait_time: ${{ steps.get-wait-time.outputs.wait_time }} steps: - name: Checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v4.3.0 - name: Check allowed users id: check-allowed-users diff --git a/.github/workflows/docker-develop.yml b/.github/workflows/docker-develop.yml index 56dedf273d..e24ef00d78 100644 --- a/.github/workflows/docker-develop.yml +++ b/.github/workflows/docker-develop.yml @@ -27,7 +27,7 @@ jobs: platform=${{ matrix.platform }} echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Check out the repo - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v4.3.0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to Docker Hub diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index eb98273ba0..991fb85d85 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -32,7 +32,7 @@ jobs: platform=${{ matrix.platform }} echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Check out the repo - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v4.3.0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Docker meta diff --git a/.github/workflows/dockle.yml b/.github/workflows/dockle.yml index 3054607913..1c0863e274 100644 --- a/.github/workflows/dockle.yml +++ b/.github/workflows/dockle.yml @@ -15,7 +15,7 @@ jobs: DOCKER_CONTENT_TRUST: 1 DOCKLE_VERSION: 0.4.14 steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 - name: Download and install dockle v${{ env.DOCKLE_VERSION }} run: | curl -L -o dockle.deb "https://github.com/goodwithtech/dockle/releases/download/v${DOCKLE_VERSION}/dockle_${DOCKLE_VERSION}_Linux-64bit.deb" @@ -25,7 +25,7 @@ jobs: cp ./compose_example.yml ./compose.yml - run: | docker compose up -d web - docker tag "$(docker compose images web | awk 'OFS=":" {print $4}' | tail -n +2)" misskey-web:latest + docker tag "$(docker compose images --format json web | jq -r '.[] | .ID')" misskey-web:latest - run: | cmd="dockle --exit-code 1 misskey-web:latest ${image_name}" echo "> ${cmd}" diff --git a/.github/workflows/get-api-diff.yml b/.github/workflows/get-api-diff.yml index 933404dfa5..1cd1ef0ebd 100644 --- a/.github/workflows/get-api-diff.yml +++ b/.github/workflows/get-api-diff.yml @@ -25,7 +25,7 @@ jobs: ref: refs/pull/${{ github.event.number }}/merge steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: ref: ${{ matrix.ref }} submodules: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 235faeb807..91813cebc3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -9,6 +9,7 @@ on: - packages/backend/** - packages/frontend/** - packages/frontend-shared/** + - packages/frontend-builder/** - packages/frontend-embed/** - packages/icons-subsetter/** - packages/sw/** @@ -22,6 +23,7 @@ on: - packages/backend/** - packages/frontend/** - packages/frontend-shared/** + - packages/frontend-builder/** - packages/frontend-embed/** - packages/icons-subsetter/** - packages/sw/** @@ -34,7 +36,7 @@ jobs: pnpm_install: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: fetch-depth: 0 submodules: true @@ -56,6 +58,7 @@ jobs: - backend - frontend - frontend-shared + - frontend-builder - frontend-embed - icons-subsetter - sw @@ -66,7 +69,7 @@ jobs: eslint-cache-version: v1 eslint-cache-path: ${{ github.workspace }}/node_modules/.cache/eslint-${{ matrix.workspace }} steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: fetch-depth: 0 submodules: true @@ -78,7 +81,7 @@ jobs: cache: 'pnpm' - run: pnpm i --frozen-lockfile - name: Restore eslint cache - uses: actions/cache@v4.2.3 + uses: actions/cache@v4.2.4 with: path: ${{ env.eslint-cache-path }} key: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }} @@ -96,7 +99,7 @@ jobs: - sw - misskey-js steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: fetch-depth: 0 submodules: true diff --git a/.github/workflows/locale.yml b/.github/workflows/locale.yml index 68e45fdf61..63702c8bc7 100644 --- a/.github/workflows/locale.yml +++ b/.github/workflows/locale.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: fetch-depth: 0 submodules: true diff --git a/.github/workflows/on-release-created.yml b/.github/workflows/on-release-created.yml index 7787d6055b..f36eb0037d 100644 --- a/.github/workflows/on-release-created.yml +++ b/.github/workflows/on-release-created.yml @@ -16,7 +16,7 @@ jobs: id-token: write steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: submodules: true - name: Setup pnpm diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml index b1d95c1b33..7f964ef1d7 100644 --- a/.github/workflows/storybook.yml +++ b/.github/workflows/storybook.yml @@ -22,12 +22,12 @@ jobs: NODE_OPTIONS: "--max_old_space_size=7168" steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 if: github.event_name != 'pull_request_target' with: fetch-depth: 0 submodules: true - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 if: github.event_name == 'pull_request_target' with: fetch-depth: 0 diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index 0c40f2c52a..e9791ac95e 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -50,7 +50,7 @@ jobs: - 56312:6379 steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: submodules: true - name: Setup pnpm @@ -109,6 +109,7 @@ jobs: name: E2E tests (backend) runs-on: ubuntu-latest strategy: + fail-fast: false matrix: node-version-file: - .node-version @@ -128,7 +129,7 @@ jobs: - 56312:6379 steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: submodules: true - name: Setup pnpm @@ -152,3 +153,47 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: ./packages/backend/coverage/coverage-final.json + + migration: + name: Migration tests (backend) + runs-on: ubuntu-latest + strategy: + matrix: + node-version-file: + - .node-version + #- .github/min.node-version + + services: + postgres: + image: postgres:15 + ports: + - 54312:5432 + env: + POSTGRES_DB: test-misskey + POSTGRES_HOST_AUTH_METHOD: trust + + steps: + - uses: actions/checkout@v4.3.0 + with: + submodules: true + - name: Setup pnpm + uses: pnpm/action-setup@v4.1.0 + - name: Get current date + id: current-date + run: echo "today=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT + - name: Use Node.js + uses: actions/setup-node@v4.4.0 + with: + node-version-file: ${{ matrix.node-version-file }} + cache: 'pnpm' + - 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: Run migrations + run: MISSKEY_CONFIG_YML=test.yml pnpm --filter backend migrate + - name: Check no migrations are remaining + run: MISSKEY_CONFIG_YML=test.yml pnpm --filter backend check-migrations diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml index 94e43cf91e..da3c9255f2 100644 --- a/.github/workflows/test-frontend.yml +++ b/.github/workflows/test-frontend.yml @@ -28,7 +28,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: submodules: true - name: Setup pnpm @@ -76,7 +76,7 @@ jobs: - 56312:6379 steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: submodules: true # https://github.com/cypress-io/cypress-docker-images/issues/150 diff --git a/.github/workflows/test-misskey-js.yml b/.github/workflows/test-misskey-js.yml index f6d16bbd76..0fad57f8da 100644 --- a/.github/workflows/test-misskey-js.yml +++ b/.github/workflows/test-misskey-js.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v4.3.0 - name: Setup pnpm uses: pnpm/action-setup@v4.1.0 diff --git a/.github/workflows/test-production.yml b/.github/workflows/test-production.yml index 751c374608..08289dfd3b 100644 --- a/.github/workflows/test-production.yml +++ b/.github/workflows/test-production.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: submodules: true - name: Setup pnpm diff --git a/.github/workflows/validate-api-json.yml b/.github/workflows/validate-api-json.yml index edff7dbecb..25353e4d29 100644 --- a/.github/workflows/validate-api-json.yml +++ b/.github/workflows/validate-api-json.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: submodules: true - name: Setup pnpm diff --git a/.vscode/settings.json b/.vscode/settings.json index 0ceec23acd..5f36a32af4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,8 +6,12 @@ "files.associations": { "*.test.ts": "typescript" }, - "jest.jestCommandLine": "pnpm run jest", "jest.runMode": "on-demand", + "jest.virtualFolders": [ + { "name": "backend unit", "jestCommandLine": "pnpm -F backend run test" }, + { "name": "backend e2e", "jestCommandLine": "pnpm -F backend run test:e2e"}, + { "name": "misskey-js", "jestCommandLine": "pnpm -F misskey-js run jest" } + ], "editor.codeActionsOnSave": { "source.fixAll": "explicit" }, diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fcab48640..3672772665 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,20 +1,155 @@ -## 2025.6.4 +## 2025.9.1 + +### NOTE +- pnpm 10.16.0 が必要です + +### General +- Enhance: 広告ごとにセンシティブフラグを設定できるようになりました + +### Client +- Feat: アカウントのQRコードを表示・読み取りできるようになりました +- Feat: 動画を圧縮してアップロードできるようになりました +- Enhance: チャットの日本語名称がダイレクトメッセージに戻るとともに、ベータ版機能ではなくなりました +- Enhance: 画像編集にマスクエフェクト(塗りつぶし、ぼかし)を追加 +- Enhance: ウォーターマークにアカウントのQRコードを追加できるように +- Enhance: 絵文字ピッカーのサイズをより大きくできるように +- Enhance: 時刻計算のための基準値を一か所で管理するようにし、パフォーマンスを向上 +- Fix: iOSで、デバイスがダークモードだと初回読み込み時にエラーになる問題を修正 + +### Server +- Enhance: ユーザーIPを確実に取得できるために設定ファイルにFastifyOptions.trustProxyを追加しました + +## 2025.9.0 + +### Client +- Enhance: AiScriptAppウィジェットで構文エラーを検知してもダイアログではなくウィジェット内にエラーを表示するように +- Enhance: /flushページでサイトキャッシュをクリアできるようになりました +- Enhance: クリップ/リスト/アンテナ/ロール追加系メニュー項目において、表示件数を拡張 +- Enhance: 「キャッシュを削除」ボタンでブラウザの内部キャッシュの削除も行えるように +- Enhance: Ctrlキー(Commandキー)を押下しながらリンクをクリックすると新しいタブで開くように +- Fix: プッシュ通知を有効にできない問題を修正 +- Fix: RSSティッカーウィジェットが正しく動作しない問題を修正 +- Fix: プロファイルを復元後アカウントの切り替えができない問題を修正 +- Fix: エラー画像が横に引き伸ばされてしまう問題に対応 + +### Server +- Fix: webpなどの画像に対してセンシティブなメディアの検出が適用されていなかった問題を修正 + +## 2025.8.0 + +### Note +- サポートされるNode.jsの最小バージョンが**22.15.0**になりました + +### General +- ノートを削除した際、関連するノートが同時に削除されないようになりました + - APIで、「replyIdが存在しているのにreplyがnull」や「renoteIdが存在しているのにrenoteがnull」であるという、今までにはなかったパターンが表れることになります +- 定期的に古いリモートの投稿を削除する機能が実装されました + - コントロールパネル→パフォーマンス→Remote Notes Cleaning で有効化できます + - データベースの肥大化を防止することが可能です + - 既存のサーバーで当機能を有効化した場合は、処理量が多くなるため、一時的にストレージ使用量が増加する可能性があります。 + - 増加量を抑えるには、最大処理継続時間をデフォルトより短くしてください。 + - データベースサイズへの効果が見られない場合はautovacuumが有効になっているか確認してください +- サーバーの初期設定が完了するまでは連合がオンにならないようになりました +- 日本語における公開範囲名称の「ダイレクト」が「指名」に改称されました + - 実際の動作に即した名称になり、馴染みのない人でも理解しやすくなりました + - 他サービスにおける「ダイレクトメッセージ」に相当するMisskeyの機能は「チャット」ですが(過去のバージョンのMisskeyでも、当該機能は「チャット」ではなく「ダイレクトメッセージ」でした)、「ダイレクト投稿」という名称の機能が存在するとそちらがダイレクトメッセージ機能であるような誤解を生んでいました + - 今後、「チャット」の名称を「ダイレクトメッセージ」に戻す可能性があります +- mfm.jsをアップデートしました + - Enhance: Unicode 15.1 および 16.0 に収録されている絵文字に対応 + - Enhance: acctに `.` が入っているユーザーのメンションに対応 + - Fix: Unicode絵文字に隣接する異体字セレクタ(`U+FE0F`)が絵文字として認識される問題を修正 +- Enhance: ユーザー検索をロールポリシーで制限できるように + +### Client +- Feat: AiScriptが1.1.0に更新されました + - プラグインは1.xに対応したものが必要です + - Playはそのまま動作しますが、新規に作られるプリセットは1.xになります + - 以前のバージョンから無効化されていた note_view_interruptor が有効になりました + - ハンドラは同期的である必要があります +- Feat: セーフモード + - プラグイン・テーマ・カスタムCSSの使用でクライアントの起動に問題が発生した際に、これらを無効にして起動できます + - 以下の方法でセーフモードを起動できます + - `g` キーを連打する + - URLに`?safemode=true`を付ける + - PWAのショートカットで Safemode を選択して起動する +- Feat: 非ログイン時に表示されるトップページのスタイルを選択できるように + - コントロールパネル→ブランディング→エントランスページのスタイル +- Feat: ページのタブバーを下部に表示できるように +- Feat: (実験的)iOSでの触覚フィードバックを有効にできるように +- Feat: コントロールパネルを検索できるように +- Enhance: 「自動でもっと見る」オプションが有効になり、安定性が向上しました +- Enhance: トルコ語 (tr-TR) に対応 +- Enhance: 不必要な翻訳データを読み込まなくなり、パフォーマンスが向上しました +- Enhance: 画像エフェクトのパラメータ名の多言語対応 +- Enhance: ノートを非表示にする相対期間を1ヶ月単位で自由に指定できるように +- Enhance: メールアドレス確認画面のUIを改善 +- Enhance: アイコンのスクロール追従を無効化する際の適用範囲を強化 +- Enhance: レンダリングパフォーマンスの向上 +- Enhance: 依存ソフトウェアの更新 +- Fix: 投稿フォームでファイルのアップロードが中止または失敗した際のハンドリングを修正 +- Fix: 一部の設定検索結果が存在しないパスになる問題を修正 + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1171) +- Fix: テーマエディタが動作しない問題を修正 +- Fix: チャンネルのハイライトページにノートが表示されない問題を修正 +- Fix: カラムの名前が正しくリスト/チャンネルの名前にならない問題を修正 +- Fix: 複数のメンションを1行に記述した場合に、サジェストが正しく表示されない問題を修正 +- Fix: メンションとしての条件を満たしていても、特定の条件(`-`が含まれる場合など)で正しくサジェストされない問題を一部修正 +- Fix: ユーザーの前後ノートを閲覧する機能が動作しない問題を修正 +- Fix: 照会ダイアログでap/showでローカルユーザーを解決した際@username@nullに飛ばされる問題を修正 +- Fix: アイコンのデコレーションを付ける際にデコレーションが表示されなくなる問題を修正 +- Fix: タッチ操作時にマウスホバー時のユーザープレビューが開くことがある問題を修正 +- Fix: 管理中アカウント一覧で正しい表示が行われない問題を修正 +- Fix: lookupページでリモートURLを指定した際に正しく動作しない問題を修正 + +### Server +- Feat: サーバー管理コマンド + - `pnpm cli foo` の形式で実行可能です + - 現在以下のコマンドが利用可能です + - `reset-captcha` - CAPTCHA設定をリセットします +- Enhance: ノートの削除処理の効率化 +- Enhance: 全体的なパフォーマンスの向上 +- Enhance: 依存ソフトウェアの更新 +- Enhance: `clips/list` APIがページネーションに対応しました +- Fix: `notes/mentions` で場合によっては並び順が正しく返されない問題を修正 +- Fix: SystemWebhook設定でsecretを空に出来ない問題を修正 +- Fix: 削除されたユーザーがチャットメッセージにリアクションしている場合`chat/history`などでエラーになる問題を修正 +- Fix: Pageのアイキャッチ画像をドライブから消してもPageごと消えないように +- Fix: タイムラインAPIの withRenotes: false 時のレスポンスを修正 + + +## 2025.7.0 + +### Note +- Node.jsの最小バージョンを20.10.0から20.18.1に引き上げました + - なお、特に必要がない限りNode.jsは推奨バージョンであるv22を使用するようにしてください ### General - Feat: ノートの下書き機能 - Feat: クリップ内でノートを検索できるように +- Feat: Playを検索できるように +- Feat: モデレーションにおいて、特定のドライブファイルを添付しているチャットメッセージを一覧できるように +- Enhance: ウォーターマーク機能をロールで制御可能に ### Client +- Note: 「自動でもっと見る」オプションは無効になっています - Feat: モデログを検索できるように - Enhance: 設定の自動バックアップをオンにした直後に自動バックアップするように - Enhance: ファイルアップロード前にキャプション設定を行えるように - Enhance: ファイルアップロード時にセンシティブ設定されているか表示するように +- Enhance: 投稿フォームにファイルをペースト/ドロップした際のUXを改善 - Enhance: ページネーション(一覧表示)の並び順を逆にできるように - Enhance: ページネーション(一覧表示)の基準日時を指定できるように +- Enhance: レンダリングパフォーマンスの向上 - Fix: ファイルがドライブの既定アップロード先に指定したフォルダにアップロードされない問題を修正 +- Fix: プラグインをアンインストールしてもセーブデータが残る問題を修正 +- Fix: 数時間後Misskeyのタブに戻った際に、タブがスロットリングされている間の更新アニメーションを延々見せ続けられる問題を修正 +- Fix: 非ログイン時のハイライトノートの画像がCWの有無を考慮せず表示される問題を修正 +- Fix: レンジ選択・ドロップダウンにて、操作を無効にすべきところで無効にならない問題を修正 +- Fix: Pull to refreshが有効なときに横スクロールができない問題を修正 ### Server - Enhance: sinceId/untilIdが指定可能なエンドポイントにおいて、sinceDate/untilDateも指定可能に +- Enhance: メールの送信者としてサーバー名を表示するように (サーバー名が設定されている場合) - Fix: ジョブキューのProgressの値を正しく計算する diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8e10806686..95e6077d93 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -618,3 +618,23 @@ color: hsl(from var(--MI_THEME-accent) h s calc(l - 10)); color: color(from var(--MI_THEME-accent) srgb r g b / 0.5); ``` +## 考え方 +### DRYに囚われるな +必要なのは一般化ではなく抽象化と考えます。 +盲信せず、誤った・不必要な共通化は避け、それが自然だと感じる場合は重複させる勇気を持ちましょう。 + +### Misskeyを複雑にしない実装 +それがいくら複雑であっても、Misskey固有のコンテキストと関心が分離されている(もしくは事実上分離されていると見做すことができる)実装であれば、それはMisskeyのコードベースに対する複雑性に影響を与えないと考えます。 + +例えるなら、VueやAiScriptといったMisskeyが使用しているライブラリの内部実装がいくら複雑だったとしても、「それを使用しているからMisskeyの実装は複雑である」ということにはならないのと同じです。 + +Misskeyのドメイン知識から関心が分離されているということは、Misskeyの実装について考える時にそれらの内部実装を考慮する必要が無く、認知負荷を増やさないからです。 + +また重要な点は、その実装が、Misskeyリポジトリの外部にあるか・内部にあるかということや、Misskeyがメンテナンスするものか・第三者がメンテナンスするものかといったことは複雑性を考える上ではほとんど無視できるという点です。 + +もちろんその実装がMisskeyリポジトリにあり、Misskeyがメンテナンスしなければならないものは、保守のコストはかかります。 +しかし、Misskeyの本質的な設計・実装という観点で見たときは、その実装は実質的に外部ライブラリのように振る舞います。 +換言すれば「たまたまMisskeyの開発者と同じ人たちがメンテナンスしているし、たまたまMisskeyのリポジトリ内に置いてあるだけの外部ライブラリ」です。 + +そのため、実装をなるべくMisskeyのドメイン知識から独立したものにすれば、Misskeyのコードベースの複雑性を上げることなく機能実装を行うことができ、お得であると言えます。 +もちろんそれにこだわって、些細な実装でもそのように分離してしまうとかえって認知負荷が増えたり、実装量が増えてメリットをデメリットが上回る場合もあるので、ケースバイケースではあります。 diff --git a/Dockerfile b/Dockerfile index 77277db8cb..370bed5751 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,10 +18,12 @@ WORKDIR /misskey COPY --link ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"] COPY --link ["scripts", "./scripts"] +COPY --link ["patches", "./patches"] COPY --link ["packages/backend/package.json", "./packages/backend/"] COPY --link ["packages/frontend-shared/package.json", "./packages/frontend-shared/"] COPY --link ["packages/frontend/package.json", "./packages/frontend/"] COPY --link ["packages/frontend-embed/package.json", "./packages/frontend-embed/"] +COPY --link ["packages/frontend-builder/package.json", "./packages/frontend-builder/"] COPY --link ["packages/icons-subsetter/package.json", "./packages/icons-subsetter/"] COPY --link ["packages/sw/package.json", "./packages/sw/"] COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"] @@ -53,6 +55,7 @@ WORKDIR /misskey COPY --link ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"] COPY --link ["scripts", "./scripts"] +COPY --link ["patches", "./patches"] COPY --link ["packages/backend/package.json", "./packages/backend/"] COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"] COPY --link ["packages/misskey-reversi/package.json", "./packages/misskey-reversi/"] diff --git a/cypress/e2e/basic.cy.ts b/cypress/e2e/basic.cy.ts index 2ce39737ed..4f2f700146 100644 --- a/cypress/e2e/basic.cy.ts +++ b/cypress/e2e/basic.cy.ts @@ -78,6 +78,8 @@ describe('After setup instance', () => { 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-submit]').should('be.disabled'); + cy.get('[data-cy-signup-invitation-code] input').type('test-invitation-code'); cy.get('[data-cy-signup-submit]').should('not.be.disabled'); cy.get('[data-cy-signup-submit]').click(); diff --git a/idea/MkAbuseReport.stories.impl.ts b/idea/MkAbuseReport.stories.impl.ts index 717bceb23d..138e5cd0cd 100644 --- a/idea/MkAbuseReport.stories.impl.ts +++ b/idea/MkAbuseReport.stories.impl.ts @@ -4,8 +4,8 @@ */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ -import { action } from '@storybook/addon-actions'; -import { StoryObj } from '@storybook/vue3'; +import { action } from 'storybook/actions'; +import type { StoryObj } from '@storybook/vue3'; import { HttpResponse, http } from 'msw'; import { abuseUserReport } from '../packages/frontend/.storybook/fakes.js'; import { commonHandlers } from '../packages/frontend/.storybook/mocks.js'; diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index 73f3870f18..24a2caea35 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -68,7 +68,7 @@ receiveFollowRequest: "تلقيت طلب متابعة" followRequestAccepted: "قُبل طلب المتابعة" mention: "أشر الى" mentions: "الإشارات" -directNotes: "الملاحظات المباشرة" +directNotes: "رسالة خاصة" importAndExport: "إستورد / صدر" import: "استيراد" export: "تصدير" @@ -1008,6 +1008,8 @@ lastNDays: "آخر {n} أيام" surrender: "ألغِ" postForm: "أنشئ ملاحظة" information: "عن" +inMinutes: "د" +inDays: "ي" _chat: invitations: "دعوة" noHistory: "السجل فارغ" @@ -1597,3 +1599,9 @@ _watermarkEditor: type: "نوع" image: "صور" advanced: "متقدم" +_imageEffector: + _fxProps: + scale: "الحجم" + size: "الحجم" + color: "اللون" + opacity: "الشفافية" diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index 26902eb07f..2b0645b77d 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -848,6 +848,8 @@ sourceCode: "সোর্স কোড" flip: "উল্টান" postForm: "নোট লিখুন" information: "আপনার সম্পর্কে" +inMinutes: "মিনিট" +inDays: "দিন" _chat: invitations: "আমন্ত্রণ" noHistory: "কোনো ইতিহাস নেই" @@ -1355,3 +1357,10 @@ _watermarkEditor: text: "লেখা" image: "ছবি" advanced: "উন্নত" +_imageEffector: + _fxProps: + scale: "আকার" + size: "আকার" + color: "রং" + opacity: "অস্বচ্ছতা" + lightness: "উজ্জ্বল করুন" diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index 4b9c3ffa38..63878bf1b7 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -896,7 +896,7 @@ searchResult: "Resultats de la cerca" hashtags: "Etiquetes" troubleshooting: "Solucionar problemes" useBlurEffect: "Fes servir efectes de desenfocament a la interfície" -learnMore: "Saber més " +learnMore: "Saber-ne més " misskeyUpdated: "Misskey s'ha actualitzat " whatIsNew: "Mostra canvis" translate: "Traduir " @@ -1054,6 +1054,7 @@ permissionDeniedError: "Operació no permesa " permissionDeniedErrorDescription: "Aquest compte no té suficients permisos per dur a terme aquesta acció " preset: "Predefinit" selectFromPresets: "Escull des dels predefinits" +custom: "Personalitzat" achievements: "Assoliments" gotInvalidResponseError: "Resposta del servidor invàlida " gotInvalidResponseErrorDescription: "No es pot contactar amb el servidor o potser es troba fora de línia per manteniment. Provar-ho de nou més tard." @@ -1092,6 +1093,7 @@ prohibitedWordsDescription2: "Fent servir espais crearà expressions AND si l'ex hiddenTags: "Etiquetes ocultes" hiddenTagsDescription: "La visibilitat de totes les notes que continguin qualsevol de les paraules configurades seran, automàticament, afegides a \"Inici\". Pots llistar diferents paraules separant les per línies noves." notesSearchNotAvailable: "La cerca de notes no es troba disponible." +usersSearchNotAvailable: "La cerca d'usuaris no està disponible." license: "Llicència" unfavoriteConfirm: "Esborrar dels favorits?" myClips: "Els meus retalls" @@ -1243,7 +1245,7 @@ releaseToRefresh: "Deixar anar per actualitzar" refreshing: "Recarregant..." pullDownToRefresh: "Llisca cap a baix per recarregar" useGroupedNotifications: "Mostrar les notificacions agrupades " -signupPendingError: "Hi ha hagut un problema verificant l'adreça de correu electrònic. L'enllaç pot haver caducat." +emailVerificationFailedError: "Hem tingut un problema en verificar la teva adreça de correu electrònic. És probable que l'enllaç estigui caducat." cwNotationRequired: "Si està activat \"Amagar contingut\" s'ha d'escriure una descripció " doReaction: "Afegeix una reacció " code: "Codi" @@ -1368,9 +1370,16 @@ redisplayAllTips: "Torna ha mostrat tots els trucs i consells" hideAllTips: "Amagar tots els trucs i consells" defaultImageCompressionLevel: "Nivell de comprensió de la imatge per defecte" defaultImageCompressionLevel_description: "Baixa, conserva la qualitat de la imatge però la mida de l'arxiu és més gran.
Alta, redueix la mida de l'arxiu però també la qualitat de la imatge." +inMinutes: "Minut(s)" +inDays: "Di(a)(es)" +safeModeEnabled: "Mode segur activat" +pluginsAreDisabledBecauseSafeMode: "Els afegits no estan activats perquè el mode segur està activat." +customCssIsDisabledBecauseSafeMode: "El CSS personalitzat no s'aplica perquè el mode segur es troba activat." +themeIsDefaultBecauseSafeMode: "El tema predeterminat es farà servir mentre el mode segur estigui activat. Una vegada es desactivi el mode segur es restablirà el tema escollit." +thankYouForTestingBeta: "Gràcies per ajudar-nos a provar la versió beta!" _order: newest: "Més recent" - oldest: "Cronològic" + oldest: "Antigues primer" _chat: noMessagesYet: "Encara no tens missatges " newMessage: "Missatge nou" @@ -1459,6 +1468,7 @@ _settings: contentsUpdateFrequency_description2: "Quan s'activa el mode en temps real, el contingut s'actualitza en temps real, independentment d'aquesta configuració." showUrlPreview: "Mostrar vista prèvia d'URL" showAvailableReactionsFirstInNote: "Mostra les reacciones que pots fer servir al damunt" + showPageTabBarBottom: "Mostrar les pestanyes de les línies de temps a la part inferior" _chat: showSenderName: "Mostrar el nom del remitent" sendOnEnter: "Introdueix per enviar" @@ -1632,6 +1642,10 @@ _serverSettings: fanoutTimelineDbFallback: "Carregar de la base de dades" fanoutTimelineDbFallbackDescription: "Quan s'activa, la línia de temps fa servir la base de dades per consultes adicionals si la línia de temps no es troba a la memòria cau. Si és desactiva la càrrega del servidor és veure reduïda, però també és reduirà el nombre de línies de temps que és poden obtenir." reactionsBufferingDescription: "Quan s'activa aquesta opció millora bastant el rendiment en recuperar les línies de temps reduint la càrrega de la base. Com a contrapunt, augmentarà l'ús de memòria de Redís. Desactiva aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes d'inestabilitat." + remoteNotesCleaning: "Neteja automàtica de notes remotes" + remoteNotesCleaning_description: "Quan activis aquesta opció, periòdicament es netejaran les notes remotes que no es consultin, això evitarà que la base de dades se" + remoteNotesCleaningMaxProcessingDuration: "Duració màxima del temps de funcionament del procés de neteja" + remoteNotesCleaningExpiryDaysForEachNotes: "Duració mínima de conservació de les notes" inquiryUrl: "URL de consulta " inquiryUrlDescription: "Escriu adreça URL per al formulari de consulta per al mantenidor del servidor o una pàgina web amb el contacte d'informació." openRegistration: "Registres oberts" @@ -1650,6 +1664,11 @@ _serverSettings: userGeneratedContentsVisibilityForVisitor: "L'abast de la publicació del contingut generat per l'usuari" userGeneratedContentsVisibilityForVisitor_description: "Això ajuda a evitar problemes com que continguts remots inadequats que no hagin estat moderats correctament es publiquin a internet mitjançant el teu servidor." userGeneratedContentsVisibilityForVisitor_description2: "La publicació incondicional de tots els continguts del servidor a internet, incloent-hi els continguts remots rebuts pel servidor, comporta riscos. Això és extremadament important per els espectadors que desconeixen el caràcter descentralitzat dels continguts, ja que poden percebre erroneament els continguts remots com contingut generat per el propi servidor." + restartServerSetupWizardConfirm_title: "Vols tornar a executar l'assistent de configuració inicial del servidor?" + restartServerSetupWizardConfirm_text: "Algunes configuracions actuals seran restablertes." + entrancePageStyle: "Estil de la pàgina d'inici" + showTimelineForVisitor: "Mostrar la línia de temps" + showActivitiesForVisitor: "Mostrar activitat" _userGeneratedContentsVisibilityForVisitor: all: "Tot obert al públic " localOnly: "Només es publiquen els continguts locals, el contingut remot es manté privat" @@ -1986,6 +2005,7 @@ _role: descriptionOfRateLimitFactor: "Límits baixos són menys restrictius, límits alts són més restrictius." canHideAds: "Pot amagar la publicitat" canSearchNotes: "Pot cercar notes" + canSearchUsers: "Pot cercar usuaris" canUseTranslator: "Pot fer servir el traductor" avatarDecorationLimit: "Nombre màxim de decoracions que es poden aplicar els avatars" canImportAntennas: "Autoritza la importació d'antenes " @@ -1998,6 +2018,7 @@ _role: uploadableFileTypes_caption: "Especifica el tipus MIME. Es poden especificar diferents tipus MIME separats amb una nova línia, i es poden especificar comodins amb asteriscs (*). (Per exemple: image/*)" uploadableFileTypes_caption2: "Pot que no sigui possible determinar el tipus MIME d'alguns arxius. Per permetre aquests tipus d'arxius afegeix {x} a les especificacions." noteDraftLimit: "Nombre possible d'esborranys de notes al servidor" + watermarkAvailable: "Pots fer servir la marca d'aigua" _condition: roleAssignedTo: "Assignat a rols manuals" isLocal: "Usuari local" @@ -2257,6 +2278,7 @@ _time: minute: "Minut(s)" hour: "Hor(a)(es)" day: "Di(a)(es)" + month: "Mes(os)" _2fa: alreadyRegistered: "J has registrat un dispositiu d'autenticació de doble factor." registerTOTP: "Registrar una aplicació autenticadora" @@ -2806,6 +2828,7 @@ _fileViewer: url: "URL" uploadedAt: "Pujat el" attachedNotes: "Notes amb aquest fitxer" + usage: "Ús " thisPageCanBeSeenFromTheAuthor: "Aquesta pàgina només la pot veure l'usuari que ha pujat aquest fitxer." _externalResourceInstaller: title: "Instal·lar des d'un lloc extern" @@ -3058,6 +3081,7 @@ _bootErrors: otherOption1: "Esborrar la configuració i la memòria cau del client" otherOption2: "Iniciar client senzill" otherOption3: "Iniciar l'eina de reparació " + otherOption4: "Iniciar Misskey en mode segur" _search: searchScopeAll: "Tot" searchScopeLocal: "Local" @@ -3094,6 +3118,8 @@ _serverSetupWizard: doYouConnectToFediverse_description1: "Quan es connecta amb una xarxa de servidors distribuïts (Fedivers), els continguts poden intercanviar-se amb altres servidors i entre ells." doYouConnectToFediverse_description2: "La connexió amb el Fedivers també es coneix com a \"federació\"." youCanConfigureMoreFederationSettingsLater: "Les configuracions avançades, com especificar els servidors amb els quals es pot federar, es poden fer més tard." + remoteContentsCleaning: "Neteja automàtica del contingut rebut" + remoteContentsCleaning_description: "Quan es comença a federar es rep un munt de contingut, quan s'activa la neteja automàtica el contingut antic que no es consulta serà eliminat del servidor, el que permet estalviar espai d'emmagatzematge." adminInfo: "Informació de l'administrador " adminInfo_description: "Estableix la informació de l'administrador que es farà servir per rebre consultes." adminInfo_mustBeFilled: "Aquesta informació ha de ser omplerta si el servidor té els registres oberts o la federació es troba activada." @@ -3146,10 +3172,10 @@ _watermarkEditor: type: "Tipus" image: "Imatges" advanced: "Avançat" + angle: "Angle" stripe: "Bandes" stripeWidth: "Amplada de la banda" stripeFrequency: "Freqüència de la banda" - angle: "Angle" polkadot: "Lunars" checker: "Escacs" polkadotMainDotOpacity: "Opacitat del lunar principal" @@ -3161,6 +3187,7 @@ _imageEffector: title: "Efecte" addEffect: "Afegeix un efecte" discardChangesConfirm: "Vols descartar els canvis i sortir?" + nothingToConfigure: "No hi ha opcions de configuració disponibles" _fxs: chromaticAberration: "Aberració cromàtica" glitch: "Glitch" @@ -3178,11 +3205,43 @@ _imageEffector: checker: "Escacs" blockNoise: "Bloqueig de soroll" tearing: "Trencament d'imatge " + _fxProps: + angle: "Angle" + scale: "Mida" + size: "Mida" + color: "Color" + opacity: "Opacitat" + normalize: "Normalitzar" + amount: "Quantitat" + lightness: "Brillantor" + contrast: "Contrast" + hue: "Tonalitat" + brightness: "Brillantor" + saturation: "Saturació" + max: "Màxim" + min: "Mínim" + direction: "Direcció " + phase: "Fase" + frequency: "Freqüència " + strength: "Intensitat" + glitchChannelShift: "Canvi de canal " + seed: "Llindar" + redComponent: "Component vermell" + greenComponent: "Component verd" + blueComponent: "Component blau" + threshold: "Llindar" + centerX: "Centre de X" + centerY: "Centre de Y" + zoomLinesSmoothing: "Suavitzat" + zoomLinesSmoothingDescription: "Els paràmetres de suavitzat i amplada de línia en augmentar no es poden fer servir junts." + zoomLinesThreshold: "Amplada de línia a l'augmentar " + zoomLinesMaskSize: "Diàmetre del centre" + zoomLinesBlack: "Obscurir" drafts: "Esborrany " _drafts: select: "Seleccionar esborrany" cannotCreateDraftAnymore: "S'ha sobrepassat el nombre màxim d'esborranys que es poden crear." - cannotCreateDraftOfRenote: "No es poden crear esborranys de remotes." + cannotCreateDraft: "Amb aquest contingut no es poden crear esborranys." delete: "Esborrar esborranys" deleteAreYouSure: "Vols esborrar els esborranys?" noDrafts: "No hi ha esborranys" diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml index 5fc772a361..21be386e26 100644 --- a/locales/cs-CZ.yml +++ b/locales/cs-CZ.yml @@ -1107,6 +1107,8 @@ lastNDays: "Posledních {n} dnů" surrender: "Zrušit" postForm: "Formulář pro odeslání" information: "Informace" +inMinutes: "Minut" +inDays: "Dnů" _chat: invitations: "Pozvat" noHistory: "Žádná historie" @@ -2002,7 +2004,7 @@ _deck: list: "Seznamy" channel: "Kanály" mentions: "Zmínění" - direct: "Přímý" + direct: "Přímé poznámky" roleTimeline: "Časová osa role" _dialog: charactersExceeded: "Překročili jste maximální počet znaků! V současné době je na hodnotě {current} z {max}." @@ -2051,3 +2053,10 @@ _watermarkEditor: type: "Typ" image: "Obrázky" advanced: "Pokročilé" +_imageEffector: + _fxProps: + scale: "Velikost" + size: "Velikost" + color: "Barva" + opacity: "Průhlednost" + lightness: "Zesvětlit" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 5f4c4d05a9..2d7d41bfdd 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -1243,7 +1243,6 @@ releaseToRefresh: "Zum Aktualisieren loslassen" refreshing: "Wird aktualisiert..." pullDownToRefresh: "Zum Aktualisieren ziehen" useGroupedNotifications: "Benachrichtigungen gruppieren" -signupPendingError: "Beim Überprüfen der Mailadresse ist etwas schiefgelaufen. Der Link könnte abgelaufen sein." cwNotationRequired: "Ist \"Inhaltswarnung verwenden\" aktiviert, muss eine Beschreibung gegeben werden." doReaction: "Reagieren" code: "Code" @@ -1313,6 +1312,7 @@ availableRoles: "Verfügbare Rollen" acknowledgeNotesAndEnable: "Schalten Sie dies erst ein, wenn Sie die Vorsichtsmaßnahmen verstanden haben." federationSpecified: "Dieser Server arbeitet mit Whitelist-Föderation. Er kann nicht mit anderen als den vom Administrator angegebenen Servern interagieren." federationDisabled: "Föderation ist auf diesem Server deaktiviert. Es ist nicht möglich, mit Benutzern auf anderen Servern zu interagieren." +draft: "Entwurf" confirmOnReact: "Reagieren bestätigen" reactAreYouSure: "Willst du eine \"{emoji}\"-Reaktion hinzufügen?" markAsSensitiveConfirm: "Möchtest du dieses Medium als sensibel kennzeichnen?" @@ -1367,6 +1367,11 @@ redisplayAllTips: "Alle „Tipps und Tricks“ wieder anzeigen" hideAllTips: "Alle „Tipps und Tricks“ ausblenden" defaultImageCompressionLevel: "Standard-Bildkomprimierungsstufe" defaultImageCompressionLevel_description: "Ein niedrigerer Wert erhält die Bildqualität, erhöht aber die Dateigröße.
Höhere Werte reduzieren die Dateigröße, verringern aber die Bildqualität." +inMinutes: "Minute(n)" +inDays: "Tag(en)" +_order: + newest: "Neueste zuerst" + oldest: "Älteste zuerst" _chat: noMessagesYet: "Noch keine Nachrichten" newMessage: "Neue Nachricht" @@ -1993,6 +1998,8 @@ _role: uploadableFileTypes: "Hochladbare Dateitypen" uploadableFileTypes_caption: "Gibt die zulässigen MIME-/Dateitypen an. Mehrere MIME-Typen können durch einen Zeilenumbruch getrennt angegeben werden, und Platzhalter können mit einem Sternchen (*) angegeben werden. (z. B. image/*)" uploadableFileTypes_caption2: "Bei manchen Dateien ist es nicht möglich, den Typ zu bestimmen. Um solche Dateien zuzulassen, füge {x} der Spezifikation hinzu." + noteDraftLimit: "Anzahl der möglichen Entwürfe für serverseitige Notizen" + watermarkAvailable: "Kann die Wasserzeichenfunktion verwenden" _condition: roleAssignedTo: "Manuellen Rollen zugewiesen" isLocal: "Lokaler Benutzer" @@ -2152,6 +2159,7 @@ _theme: install: "Farbschemata installieren" manage: "Farbschemaverwaltung" code: "Farbschemencode" + copyThemeCode: "Farbschemencode kopieren" description: "Beschreibung" installed: "{name} wurde installiert" installedThemes: "Installierte Farbschemata" @@ -3103,6 +3111,7 @@ _serverSetupWizard: text2: "Wir würden uns über deine Unterstützung freuen, damit wir dieses Projekt auch in Zukunft weiterentwickeln können." text3: "Für Unterstützer gibt es auch besondere Vorteile!" _uploader: + editImage: "Bild bearbeiten" compressedToX: "Komprimiert zu {x}" savedXPercent: "{x}% gespart" abortConfirm: "Einige Dateien wurden nicht hochgeladen. Möchtest du den Vorgang abbrechen?" @@ -3137,10 +3146,16 @@ _watermarkEditor: type: "Art" image: "Bilder" advanced: "Fortgeschritten" + angle: "Winkel" stripe: "Streifen" stripeWidth: "Linienbreite" stripeFrequency: "Linienanzahl" - angle: "Winkel" + polkadot: "Punktmuster" + polkadotMainDotOpacity: "Deckkraft des Hauptpunktes" + polkadotMainDotRadius: "Größe des Hauptpunktes" + polkadotSubDotOpacity: "Deckkraft des Unterpunktes" + polkadotSubDotRadius: "Größe des Unterpunktes" + polkadotSubDotDivisions: "Anzahl der Unterpunkte" _imageEffector: title: "Effekte" addEffect: "Effekte hinzufügen" @@ -3156,5 +3171,25 @@ _imageEffector: colorClampAdvanced: "Farbkomprimierung (erweitert)" distort: "Verzerrung" stripe: "Streifen" + polkadot: "Punktmuster" + _fxProps: + angle: "Winkel" + scale: "Größe" + size: "Größe" + color: "Farbe" + opacity: "Transparenz" + lightness: "Erhellen" +drafts: "Entwurf" _drafts: + select: "Entwurf auswählen" + cannotCreateDraftAnymore: "Die Anzahl der Entwürfe, die erstellt werden können, wurde überschritten." + cannotCreateDraft: "Mit diesem Inhalt kann kein Entwurf erstellt werden." + delete: "Entwurf löschen" + deleteAreYouSure: "Entwurf löschen?" + noDrafts: "Keine Entwürfe" + replyTo: "Antwort an {user}" + quoteOf: "Zitat von {user}s Notiz" + saveToDraft: "Als Entwurf speichern" + restoreFromDraft: "Aus Entwurf wiederherstellen" restore: "Wiederherstellen" + listDrafts: "Liste der Entwürfe" diff --git a/locales/el-GR.yml b/locales/el-GR.yml index ad146866be..5fc2bd7221 100644 --- a/locales/el-GR.yml +++ b/locales/el-GR.yml @@ -353,6 +353,7 @@ _visibility: home: "Κεντρικό" homeDescription: "Δημοσίευση στο κεντρικό χρονολόγιο μόνο" followers: "Ακολουθούν" + specified: "Απευθείας σημειώματα" _profile: name: "Όνομα" username: "Όνομα μέλους" @@ -395,6 +396,7 @@ _deck: antenna: "Αντένες" list: "Λίστα" mentions: "Επισημάνσεις" + direct: "Απευθείας σημειώματα" _webhookSettings: name: "Όνομα" _moderationLogTypes: diff --git a/locales/en-US.yml b/locales/en-US.yml index f55c6c6763..faa7f5e59e 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -81,7 +81,7 @@ import: "Import" export: "Export" files: "Files" download: "Download" -driveFileDeleteConfirm: "Do you want to remove the file \"{name}\"? Some content using this file will also be removed." +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}?" 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." @@ -1054,6 +1054,7 @@ permissionDeniedError: "Operation denied" permissionDeniedErrorDescription: "This account does not have the permission to perform this action." preset: "Preset" selectFromPresets: "Choose from presets" +custom: "Custom" achievements: "Achievements" gotInvalidResponseError: "Invalid server response" gotInvalidResponseErrorDescription: "The server may be unreachable or undergoing maintenance. Please try again later." @@ -1092,6 +1093,7 @@ prohibitedWordsDescription2: "Using spaces will create AND expressions and surro hiddenTags: "Hidden hashtags" hiddenTagsDescription: "Select tags which will not shown on trend list.\nMultiple tags could be registered by lines." notesSearchNotAvailable: "Note search is unavailable." +usersSearchNotAvailable: "User search is not available." license: "License" unfavoriteConfirm: "Really remove from favorites?" myClips: "My clips" @@ -1216,8 +1218,8 @@ showRepliesToOthersInTimeline: "Show replies to others in timeline" hideRepliesToOthersInTimeline: "Hide replies to others from timeline" showRepliesToOthersInTimelineAll: "Show replies to others from everyone you follow in timeline" hideRepliesToOthersInTimelineAll: "Hide replies to others from everyone you follow in timeline" -confirmShowRepliesAll: "This operation is irreversible. Would you really like to show replies to others from everyone you follow in your timeline?" -confirmHideRepliesAll: "This operation is irreversible. Would you really like to hide replies to others from everyone you follow in your timeline?" +confirmShowRepliesAll: "Are you sure you want to show replies from everyone you follow in your timeline? This action is irreversible." +confirmHideRepliesAll: "Are you sure you want to hide replies from everyone you follow in your timeline? This action is irreversible." externalServices: "External Services" sourceCode: "Source code" sourceCodeIsNotYetProvided: "Source code is not yet available. Contact the administrator to fix this problem." @@ -1243,7 +1245,7 @@ releaseToRefresh: "Release to refresh" refreshing: "Refreshing..." pullDownToRefresh: "Pull down to refresh" useGroupedNotifications: "Display grouped notifications" -signupPendingError: "There was a problem verifying the email address. The link may have expired." +emailVerificationFailedError: "A problem occurred while verifying your email address. The link may have expired." cwNotationRequired: "If \"Hide content\" is enabled, a description must be provided." doReaction: "Add reaction" code: "Code" @@ -1302,7 +1304,7 @@ passkeyVerificationSucceededButPasswordlessLoginDisabled: "Passkey verification messageToFollower: "Message to followers" target: "Target" testCaptchaWarning: "This function is intended for CAPTCHA testing purposes.\nDo not use in a production environment." -prohibitedWordsForNameOfUser: "Prohibited words for user names" +prohibitedWordsForNameOfUser: "Prohibited words for usernames" prohibitedWordsForNameOfUserDescription: "If any of the strings in this list are included in the user's name, the name will be denied. Users with moderator privileges are not affected by this restriction." yourNameContainsProhibitedWords: "Your name contains prohibited words" yourNameContainsProhibitedWordsDescription: "If you wish to use this name, please contact your server administrator." @@ -1367,7 +1369,14 @@ tip: "Tips & Tricks" redisplayAllTips: "Show all “Tips & Tricks” again" hideAllTips: "Hide all \"Tips & Tricks\"" defaultImageCompressionLevel: "Default image compression level" -defaultImageCompressionLevel_description: "High, reduces the file size but also the image quality.
High, reduces the file size but also the image quality." +defaultImageCompressionLevel_description: "Lower level preserves image quality but increases file size.
Higher level reduce file size, but reduce image quality." +inMinutes: "Minute(s)" +inDays: "Day(s)" +safeModeEnabled: "Safe mode is enabled" +pluginsAreDisabledBecauseSafeMode: "All plugins are disabled because safe mode is enabled." +customCssIsDisabledBecauseSafeMode: "Custom CSS is not applied because safe mode is enabled." +themeIsDefaultBecauseSafeMode: "While safe mode is active, the default theme is used. Disabling safe mode will revert these changes." +thankYouForTestingBeta: "Thank you for helping us test the beta version!" _order: newest: "Newest First" oldest: "Oldest First" @@ -1400,7 +1409,7 @@ _chat: muteThisRoom: "Mute room" deleteRoom: "Delete room" chatNotAvailableForThisAccountOrServer: "Chat is not enabled on this server or for this account." - chatIsReadOnlyForThisAccountOrServer: "Chat is read-only on this instance or this account. You cannot write new messages or create/join chat rooms." + chatIsReadOnlyForThisAccountOrServer: "Chat is read-only on this server or this account. You cannot write new messages or create/join chat rooms." chatNotAvailableInOtherAccount: "The chat function is disabled for the other user." cannotChatWithTheUser: "Cannot start a chat with this user" cannotChatWithTheUser_description: "Chat is either unavailable or the other party has not enabled chat." @@ -1459,6 +1468,7 @@ _settings: contentsUpdateFrequency_description2: "When real-time mode is on, content is updated in real time regardless of this setting." showUrlPreview: "Show URL preview" showAvailableReactionsFirstInNote: "Show available reactions at the top." + showPageTabBarBottom: "Show page tab bar at the bottom" _chat: showSenderName: "Show sender's name" sendOnEnter: "Press Enter to send" @@ -1498,7 +1508,7 @@ _abuseUserReport: resolveTutorial: "If the report's content is legitimate, select \"Accept\" to mark it as resolved.\nIf the report's content is illegitimate, select \"Reject\" to ignore it." _delivery: status: "Delivery status" - stop: "Suspended" + stop: "Suspend" resume: "Delivery resume" _type: none: "Publishing" @@ -1632,6 +1642,10 @@ _serverSettings: fanoutTimelineDbFallback: "Fallback to database" fanoutTimelineDbFallbackDescription: "When enabled, the timeline will fall back to the database for additional queries if the timeline is not cached. Disabling it further reduces the server load by eliminating the fallback process, but limits the range of timelines that can be retrieved." reactionsBufferingDescription: "When enabled, performance during reaction creation will be greatly improved, reducing the load on the database. However, Redis memory usage will increase." + remoteNotesCleaning: "Automatic cleanup of remote notes" + remoteNotesCleaning_description: "When enabled, unused and outdated remote notes will be periodically cleaned up to prevent database bloat." + remoteNotesCleaningMaxProcessingDuration: "Maximum cleanup processing time" + remoteNotesCleaningExpiryDaysForEachNotes: "Minimum days to retain notes" inquiryUrl: "Inquiry URL" inquiryUrlDescription: "Specify a URL for the inquiry form to the server maintainer or a web page for the contact information." openRegistration: "Make the account creation open" @@ -1650,6 +1664,11 @@ _serverSettings: userGeneratedContentsVisibilityForVisitor: "Visibility of user-generated content to guests" userGeneratedContentsVisibilityForVisitor_description: "This is useful for preventing problems caused by inappropriate remote content that is not well moderated from being unintentionally published on the Internet via your own server." userGeneratedContentsVisibilityForVisitor_description2: "Unconditionally publishing all content on the server to the Internet, including remote content received by the server is risky. This is especially important for guests who are unaware of the distributed nature of the content, as they may mistakenly believe that even remote content is content created by users on the server." + restartServerSetupWizardConfirm_title: "Restart server setup wizard?" + restartServerSetupWizardConfirm_text: "Some current settings will be reset." + entrancePageStyle: "Entrance page style" + showTimelineForVisitor: "Show timeline" + showActivitiesForVisitor: "Show activities" _userGeneratedContentsVisibilityForVisitor: all: "Everything is public" localOnly: "Only local content is published, remote content is kept private" @@ -1986,18 +2005,20 @@ _role: descriptionOfRateLimitFactor: "Lower rate limits are less restrictive, higher ones more restrictive. " canHideAds: "Can hide ads" canSearchNotes: "Usage of note search" + canSearchUsers: "User search" canUseTranslator: "Translator usage" - avatarDecorationLimit: "Maximum number of avatar decorations that can be applied" - canImportAntennas: "Allow importing antennas" - canImportBlocking: "Allow importing blocking" - canImportFollowing: "Allow importing following" - canImportMuting: "Allow importing muting" - canImportUserLists: "Allow importing lists" - chatAvailability: "Allow Chat" + avatarDecorationLimit: "Maximum number of avatar decorations" + canImportAntennas: "Can import antennas" + canImportBlocking: "Can import blocking" + canImportFollowing: "Can import following" + canImportMuting: "Can import muting" + canImportUserLists: "Can import lists" + chatAvailability: "Chat" uploadableFileTypes: "Uploadable file types" uploadableFileTypes_caption: "Specifies the allowed MIME/file types. Multiple MIME types can be specified by separating them with a new line, and wildcards can be specified with an asterisk (*). (e.g., image/*)" uploadableFileTypes_caption2: "Some files types might fail to be detected. To allow such files, add {x} to the specification." noteDraftLimit: "Number of possible drafts of server notes" + watermarkAvailable: "Watermark function" _condition: roleAssignedTo: "Assigned to manual roles" isLocal: "Local user" @@ -2257,6 +2278,7 @@ _time: minute: "Minute(s)" hour: "Hour(s)" day: "Day(s)" + month: "Month(s)" _2fa: alreadyRegistered: "You have already registered a 2-factor authentication device." registerTOTP: "Register authenticator app" @@ -2329,7 +2351,7 @@ _permissions: "read:admin:index-stats": "View database index stats" "read:admin:table-stats": "View database table stats" "read:admin:user-ips": "View user IP addresses" - "read:admin:meta": "View instance metadata" + "read:admin:meta": "View server metadata" "write:admin:reset-password": "Reset user password" "write:admin:resolve-abuse-user-report": "Resolve user report" "write:admin:send-email": "Send email" @@ -2340,7 +2362,7 @@ _permissions: "write:admin:unset-user-avatar": "Remove user avatar" "write:admin:unset-user-banner": "Remove user banner" "write:admin:unsuspend-user": "Unsuspend user" - "write:admin:meta": "Manage instance metadata" + "write:admin:meta": "Manage server metadata" "write:admin:user-note": "Manage moderation note" "write:admin:roles": "Manage roles" "read:admin:roles": "View roles" @@ -2772,7 +2794,7 @@ _moderationLogTypes: resetPassword: "Password reset" suspendRemoteInstance: "Remote instance suspended" unsuspendRemoteInstance: "Remote instance unsuspended" - updateRemoteInstanceNote: "Moderation note updated for remote instance." + updateRemoteInstanceNote: "Updated moderation note for remote servers" markSensitiveDriveFile: "File marked as sensitive" unmarkSensitiveDriveFile: "File unmarked as sensitive" resolveAbuseReport: "Report resolved" @@ -2806,6 +2828,7 @@ _fileViewer: url: "URL" uploadedAt: "Uploaded at" attachedNotes: "Attached notes" + usage: "Used" thisPageCanBeSeenFromTheAuthor: "This page can only be seen by the user who uploaded this file." _externalResourceInstaller: title: "Install from external site" @@ -3058,6 +3081,7 @@ _bootErrors: otherOption1: "Delete client settings and cache" otherOption2: "Start the simple client" otherOption3: "Launch the repair tool" + otherOption4: "Launch Misskey in safe mode" _search: searchScopeAll: "All" searchScopeLocal: "Local" @@ -3094,6 +3118,8 @@ _serverSetupWizard: doYouConnectToFediverse_description1: "When connected to a network of distributed servers (Fediverse) content can be exchanged with other servers." doYouConnectToFediverse_description2: "Connecting with the Fediverse is also called \"federation\"" youCanConfigureMoreFederationSettingsLater: "Advanced settings such as specifying federated servers can be configured later." + remoteContentsCleaning: "Automatic cleanup of received contents" + remoteContentsCleaning_description: "Federation may result in a continuous inflow of content. Enabling automatic cleanup will remove outdated and unreferenced content from the server to save storage." adminInfo: "Administrator information" adminInfo_description: "Sets the administrator information used to receive inquiries." adminInfo_mustBeFilled: "Must be entered if public server or federation is on." @@ -3146,10 +3172,10 @@ _watermarkEditor: type: "Type" image: "Images" advanced: "Advanced" + angle: "Angle" stripe: "Stripes" stripeWidth: "Line width" stripeFrequency: "Lines count" - angle: "Angle" polkadot: "Polkadot" checker: "Checker" polkadotMainDotOpacity: "Opacity of the main dot" @@ -3161,12 +3187,14 @@ _imageEffector: title: "Effects" addEffect: "Add Effects" discardChangesConfirm: "Are you sure you want to leave? You have unsaved changes." + nothingToConfigure: "No configurable options available" _fxs: chromaticAberration: "Chromatic Aberration" glitch: "Glitch" mirror: "Mirror" invert: "Invert Colors" grayscale: "Grayscale" + blur: "Blur" colorAdjust: "Color Correction" colorClamp: "Color Compression" colorClampAdvanced: "Color Compression (Advanced)" @@ -3178,11 +3206,45 @@ _imageEffector: checker: "Checker" blockNoise: "Block Noise" tearing: "Tearing" + _fxProps: + angle: "Angle" + scale: "Size" + size: "Size" + radius: "Radius" + samples: "Samples" + color: "Color" + opacity: "Opacity" + normalize: "Normalize" + amount: "Amount" + lightness: "Lighten" + contrast: "Contrast" + hue: "Hue" + brightness: "Brightness" + saturation: "Saturation" + max: "Maximum" + min: "Minimum" + direction: "Direction" + phase: "Phase" + frequency: "Frequency" + strength: "Strength" + glitchChannelShift: "Channel shift" + seed: "Seed value" + redComponent: "Red component" + greenComponent: "Green component" + blueComponent: "Blue component" + threshold: "Threshold" + centerX: "Center X" + centerY: "Center Y" + zoomLinesSmoothing: "Smoothing" + zoomLinesSmoothingDescription: "Smoothing and zoom line width cannot be used together." + zoomLinesThreshold: "Zoom line width" + zoomLinesMaskSize: "Center diameter" + zoomLinesBlack: "Make black" drafts: "Drafts" _drafts: select: "Select Draft" cannotCreateDraftAnymore: "The number of drafts that can be created has been exceeded." - cannotCreateDraftOfRenote: "You cannot create a draft of a renote." + cannotCreateDraft: "You cannot create a draft with this content." delete: "Delete Draft" deleteAreYouSure: "Delete draft?" noDrafts: "No drafts" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 262cc42c82..8a1d2c458b 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -61,7 +61,7 @@ copyRSS: "Copiar RSS" copyUsername: "Copiar nombre de usuario" copyUserId: "Copiar ID del usuario" copyNoteId: "Copiar ID de la nota" -copyFileId: "Copiar ID del archivo" +copyFileId: "Copiar ID de archivo" copyFolderId: "Copiar ID de carpeta" copyProfileUrl: "Copiar la URL del perfil" searchUser: "Buscar un usuario" @@ -83,10 +83,10 @@ files: "Archivos" download: "Descargar" driveFileDeleteConfirm: "¿Desea borrar el archivo \"{name}\"? Las notas que tengan este archivo como adjunto serán eliminadas" unfollowConfirm: "¿Desea dejar de seguir a {name}?" -exportRequested: "Se ha solicitado la exportación. Puede tomar un tiempo. Cuando termine la exportación, se añadirá en el drive" -importRequested: "Se ha solicitado la importación. Puede tomar un tiempo." +exportRequested: "Has solicitado la exportación. Puede llevar un tiempo. Cuando termine la exportación, se añadirá al drive" +importRequested: "Has solicitado la importación. Puede llevar un tiempo." lists: "Listas" -noLists: "No tiene listas" +noLists: "No tienes ninguna lista" note: "Notas" notes: "Notas" following: "Siguiendo" @@ -99,9 +99,9 @@ somethingHappened: "Ocurrió un error" retry: "Reintentar" pageLoadError: "Error al leer la página" pageLoadErrorDescription: "Normalmente es debido a la red o al caché del navegador. Por favor limpie el caché o intente más tarde." -serverIsDead: "No hay respuesta del servidor. Espere un momento y vuelva a intentarlo." -youShouldUpgradeClient: "Para ver esta página, por favor refrezca el navegador y utiliza una versión más reciente del cliente." -enterListName: "Ingrese nombre de lista" +serverIsDead: "No hay respuesta del servidor. Espera un momento y vuelve a intentarlo." +youShouldUpgradeClient: "Para ver esta página, recarga el navegador para actualizar el cliente." +enterListName: "Introduce un nombre para la lista" privacy: "Privacidad" makeFollowManuallyApprove: "Aprobar manualmente las solicitudes de seguimiento" defaultNoteVisibility: "Visibilidad por defecto" @@ -134,28 +134,28 @@ emojiPicker: "Selector de emojis" pinnedEmojisForReactionSettingDescription: "Puedes seleccionar reacciones para fijarlos en el selector" pinnedEmojisSettingDescription: "Puedes seleccionar emojis para fijarlos en el selector" emojiPickerDisplay: "Mostrar el selector de emojis" -overwriteFromPinnedEmojisForReaction: "Sobreescribir las reacciones fijadas" -overwriteFromPinnedEmojis: "Sobreescribir los emojis fijados" -reactionSettingDescription2: "Arrastre para reordenar, click para borrar, apriete la tecla + para añadir." +overwriteFromPinnedEmojisForReaction: "Sobreescribir los ajustes de reacciones" +overwriteFromPinnedEmojis: "Sobreescribir los ajustes generales" +reactionSettingDescription2: "Arrastra para reordenar, click para borrar, pulsa \"+\" para añadir." rememberNoteVisibility: "Recordar visibilidad" attachCancel: "Quitar adjunto" -deleteFile: "Archivo eliminado" +deleteFile: "Eliminar archivo" markAsSensitive: "Marcar como sensible" unmarkAsSensitive: "Desmarcar como sensible" -enterFileName: "Ingrese el nombre del archivo" +enterFileName: "Introduce el nombre del archivo" mute: "Silenciar" unmute: "Dejar de silenciar" renoteMute: "Silenciar renota" renoteUnmute: "Desilenciar renota" block: "Bloquear" -unblock: "Dejar de bloquear" +unblock: "Desbloquear" suspend: "Suspender" unsuspend: "Dejar de suspender" -blockConfirm: "¿Quiere bloquear esta cuenta?" -unblockConfirm: "¿Quiere dejar de bloquear esta cuenta?" -suspendConfirm: "¿Quiere suspender esta cuenta?" -unsuspendConfirm: "¿Quiere dejar de suspender esta cuenta?" -selectList: "Seleccione una lista" +blockConfirm: "¿Quieres bloquear esta cuenta?" +unblockConfirm: "¿Quieres desbloquear esta cuenta?" +suspendConfirm: "¿Quieres suspender esta cuenta?" +unsuspendConfirm: "¿Quieres dejar de suspender esta cuenta?" +selectList: "Selecciona una lista" editList: "Editar lista" selectChannel: "Seleccionar canal" selectAntenna: "Seleccionar antena" @@ -163,55 +163,55 @@ editAntenna: "Editar antena" createAntenna: "Crear una antena" selectWidget: "Seleccionar widget" editWidgets: "Editar widgets" -editWidgetsExit: "Terminar edición" +editWidgetsExit: "Hecho" customEmojis: "Emojis personalizados" emoji: "Emoji" -emojis: "Emoji" +emojis: "Emojis" emojiName: "Nombre del emoji" -emojiUrl: "URL de la imagen del emoji" -addEmoji: "Agregar emoji" -settingGuide: "Configuración sugerida" -cacheRemoteFiles: "Mantener en cache los archivos remotos" -cacheRemoteFilesDescription: "Si desactiva esta configuración, Los archivos remotos se cargarán desde el link directo sin usar la caché. Con eso se puede ahorrar almacenamiento del servidor, pero eso aumentará el tráfico al no crear miniaturas." +emojiUrl: "URL del emoji" +addEmoji: "Añadir emoji" +settingGuide: "Configuración recomendada" +cacheRemoteFiles: "Mantener los archivos remotos en caché" +cacheRemoteFilesDescription: "Si desactivas esta configuración, los archivos remotos se cargarán directamente de los servidores remotos. Desactivar esto reducirá el uso de almacenamiento, pero incrementará el uso de tráfico, ya que no se generarán miniaturas." youCanCleanRemoteFilesCache: "Puedes vaciar la caché pulsando en el botón 🗑️ en el administrador de archivos." -cacheRemoteSensitiveFiles: "Cachear archivos remotos sensibles" -cacheRemoteSensitiveFilesDescription: "Cuando esta opción está desactivada, los archivos remotos sensibles son cargador directamente de la instancia origen sin ser cacheados." +cacheRemoteSensitiveFiles: "Mantener los archivos remotos sensibles en caché" +cacheRemoteSensitiveFilesDescription: "Cuando esta opción está desactivada, los archivos remotos sensibles se cargarán directamente desde los servidores remotos." flagAsBot: "Esta cuenta es un bot" -flagAsBotDescription: "En caso de que esta cuenta fuera usada por un programa, active esta opción. Al hacerlo, esta opción servirá para otros desarrolladores para evitar cadenas infinitas de reacciones, y ajustará los sistemas internos de Misskey para que trate a esta cuenta como un bot." -flagAsCat: "Esta cuenta es un gato" -flagAsCatDescription: "En caso de que declare que esta cuenta es de un gato, active esta opción." -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" +flagAsBotDescription: "Activa esta opción si la cuenta es utilizada por un programa. Si se activa, actuará como una etiqueta para otros desarrolladores para prevenir cadenas eternas de interacción con otros bots, y ajustará los sistemas internos de Misskey para tratar esta cuenta de manera acorde." +flagAsCat: "Marcar esta cuenta como gato" +flagAsCatDescription: "Activa esta opción para marcar esta cuenta como un gato." +flagShowTimelineReplies: "Mostrar respuestas en la línea de tiempo" +flagShowTimelineRepliesDescription: "Muestra respuestas de los usuarios a las notas de otros usuarios en la línea de tiempo al activar esta opción." 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." -showOnRemote: "Ver en una instancia remota" -continueOnRemote: "Ver en una instancia remota" +showOnRemote: "Ver en instancia remota" +continueOnRemote: "Continuar en una instancia remota" chooseServerOnMisskeyHub: "Elegir un servidor en Misskey Hub" specifyServerHost: "Especifica una instancia directamente" -inputHostName: "Introduzca el dominio" +inputHostName: "Introduce el dominio" general: "General" wallpaper: "Fondo de pantalla" setWallpaper: "Establecer fondo de pantalla" removeWallpaper: "Quitar fondo de pantalla" searchWith: "Buscar: {q}" -youHaveNoLists: "No tienes listas" -followConfirm: "¿Desea seguir a {name}?" +youHaveNoLists: "No tienes ninguna lista" +followConfirm: "¿Quieres seguir a {name}?" proxyAccount: "Cuenta proxy" -proxyAccountDescription: "Una cuenta proxy es una cuenta que actúa como un seguidor remoto de un usuario bajo ciertas condiciones. Por ejemplo, cuando un usuario añade un usuario remoto a una lista, si ningún usuario local sigue al usuario agregado a la lista, la instancia no puede obtener su actividad. Así que la cuenta proxy sigue al usuario añadido a la lista" -host: "Host" +proxyAccountDescription: "Una cuenta proxy es una cuenta que actúa como un seguidor remoto de un usuario bajo ciertas condiciones. Por ejemplo, cuando un usuario añade un usuario remoto a una lista, si ningún usuario local sigue al usuario agregado a la lista, la instancia no puede obtener su actividad, así que la cuenta proxy sigue al usuario añadido a la lista" +host: "Instancia" selectSelf: "Elígete a ti mismo" selectUser: "Elegir usuario" -recipient: "Recipiente" +recipient: "Receptor" annotation: "Anotación" federation: "Federación" -instances: "Instancia" +instances: "Instancias" registeredAt: "Registrado en" -latestRequestReceivedAt: "Ultimo pedido recibido" -latestStatus: "Último status" +latestRequestReceivedAt: "Última petición recibida" +latestStatus: "Último estado" storageUsage: "Almacenamiento usado" -charts: "Chat" +charts: "Métricas" perHour: "por hora" perDay: "por día" stopActivityDelivery: "Dejar de enviar actividades" @@ -226,40 +226,40 @@ metadata: "Metadatos" withNFiles: "{n} archivos" monitor: "Monitor" jobQueue: "Cola de trabajos" -cpuAndMemory: "CPU y Memoria" +cpuAndMemory: "CPU y memoria" network: "Red" disk: "Disco" -instanceInfo: "información de la instancia" +instanceInfo: "Información de la instancia" statistics: "Estadísticas" clearQueue: "Limpiar cola" -clearQueueConfirmTitle: "¿Desea limpiar la cola?" +clearQueueConfirmTitle: "¿Quieres limpiar la cola?" clearQueueConfirmText: "Las notas aún no entregadas no se federarán. Normalmente no se necesita ejecutar esta operación" clearCachedFiles: "Limpiar caché" -clearCachedFilesConfirm: "¿Desea borrar todos los archivos remotos cacheados?" +clearCachedFilesConfirm: "¿Quieres borrar todos los archivos remotos en caché?" blockedInstances: "Instancias bloqueadas" -blockedInstancesDescription: "Seleccione los hosts de las instancias que desea bloquear, separadas por una linea nueva. Las instancias bloqueadas no podrán comunicarse con esta instancia." +blockedInstancesDescription: "La lista de los dominios de las instancias que quieres bloquear, separadas por una linea nueva. Las instancias bloqueadas no podrán comunicarse con esta instancia." silencedInstances: "Instancias silenciadas" -silencedInstancesDescription: "Listar los hostname de las instancias que quieres silenciar. Todas las cuentas de las instancias listadas serán tratadas como silenciadas, solo podrán hacer peticiones de seguimiento, y no podrán mencionar cuentas locales si no las siguen. Esto no afecta a las instancias bloqueadas." -mediaSilencedInstances: "Servidores silenciados (Multimedia)" -mediaSilencedInstancesDescription: "Listar las instancias que quieres silenciar. Todas las cuentas de las instancias listadas serán tratadas como silenciadas, solo podrán hacer peticiones de seguimiento, y no podrán mencionar cuentas locales si no las siguen. Esto no afecta a las instancias bloqueadas." +silencedInstancesDescription: "La lista de los dominios de las instancias que quieres silenciar. Todas las cuentas de las instancias listadas serán tratadas como silenciadas, solo podrán hacer peticiones de seguimiento, y no podrán mencionar cuentas locales si no las siguen. Esto no afecta a las instancias bloqueadas." +mediaSilencedInstances: "Servidores con multimedia silenciada" +mediaSilencedInstancesDescription: "La lista de los dominios de las instancias cuya multimedia quieres silenciar. Todas las cuentas que pertenezcan a estas instancias serán marcadas como sensibles, y no podrán usar sus emojis personalizados. Esto no afectará a las instancias bloqueadas" federationAllowedHosts: "Servidores federados" -federationAllowedHostsDescription: "Establezca los nombres de los servidores que pueden federarse, separados por una nueva línea." +federationAllowedHostsDescription: "La lista de los dominios de las instancias cuya federación está permitida, separadas por saltos de línea." muteAndBlock: "Silenciar y bloquear" mutedUsers: "Usuarios silenciados" blockedUsers: "Usuarios bloqueados" noUsers: "No hay usuarios" editProfile: "Editar perfil" -noteDeleteConfirm: "¿Desea borrar esta nota?" -pinLimitExceeded: "Ya no se pueden fijar más posts" +noteDeleteConfirm: "¿Quieres borrar esta nota?" +pinLimitExceeded: "Ya no se pueden fijar más notas" done: "Terminado" -processing: "Procesando" +processing: "Procesando..." preview: "Vista previa" default: "Predeterminado" defaultValueIs: "Por defecto: {value}" noCustomEmojis: "No hay emojis personalizados" noJobs: "No hay trabajos" federating: "Federando" -blocked: "Bloqueando" +blocked: "Bloqueado" suspended: "Suspendido" all: "Todo" subscribing: "Suscribiendo" @@ -280,8 +280,8 @@ featured: "Destacados" usernameOrUserId: "Nombre o ID del usuario" noSuchUser: "No se encuentra el usuario" lookup: "Búsqueda" -announcements: "Anuncios" -imageUrl: "URL de la imágen" +announcements: "Avisos" +imageUrl: "URL de la imagen." remove: "Borrar" removed: "Borrado" removeAreYouSure: "¿Desea borrar \"{x}\"?" @@ -842,7 +842,7 @@ unlikeConfirm: "¿Quitar como favorito?" fullView: "Vista completa" quitFullView: "quitar vista completa" addDescription: "Agregar descripción" -userPagePinTip: "Puede mantener sus notas visibles aquí seleccionando Pin en el menú de notas individuales" +userPagePinTip: "Puede mantener sus notas visibles aquí seleccionando 'Fijar al perfil' en el menú de notas individuales" notSpecifiedMentionWarning: "Algunas menciones no están incluidas en el destino" info: "Información" userInfo: "Información del usuario" @@ -877,7 +877,7 @@ popularPosts: "Más vistos" shareWithNote: "Compartir con una nota" ads: "Anuncios" expiration: "Termina el" -startingperiod: "periodo de inicio" +startingperiod: "Comienzo" memo: "Notas" priority: "Prioridad" high: "Alta" @@ -1054,6 +1054,7 @@ permissionDeniedError: "Operación denegada" permissionDeniedErrorDescription: "Esta cuenta no tiene permisos para hacer esa acción." preset: "Predefinido" selectFromPresets: "Escoger desde predefinidos" +custom: "Personalizado" achievements: "Logros" gotInvalidResponseError: "Respuesta del servidor inválida" gotInvalidResponseErrorDescription: "Puede que el servidor esté caído o en mantenimiento. Favor de intentar más tarde" @@ -1092,6 +1093,7 @@ prohibitedWordsDescription2: "Si se usan espacios se crearán expresiones AND y hiddenTags: "Hashtags ocultos" hiddenTagsDescription: "Selecciona las etiquetas que no se mostrarán en tendencias. Una etiqueta por línea." notesSearchNotAvailable: "No se puede buscar una nota" +usersSearchNotAvailable: "La búsqueda de usuarios no está disponible." license: "Licencia" unfavoriteConfirm: "¿Desea quitar de favoritos?" myClips: "Mis clips" @@ -1143,7 +1145,7 @@ channelArchiveConfirmTitle: "¿Seguro de archivar {name}?" channelArchiveConfirmDescription: "Un canal archivado no aparecerá en la lista de canales ni en los resultados. Las nuevas publicaciones tampoco serán añadidas." thisChannelArchived: "El canal ha sido archivado." displayOfNote: "Mostrar notas" -initialAccountSetting: "Configración inicial de su cuenta\nか\nConfigración de inicio" +initialAccountSetting: "Configración inicial de su cuenta" youFollowing: "Siguiendo" preventAiLearning: "Rechazar el uso en el Aprendizaje de Máquinas. (IA Generativa)" preventAiLearningDescription: "Pedirle a las arañas (crawlers) no usar los textos publicados o imágenes en el aprendizaje automático (IA Predictiva / Generativa). Ésto se logra añadiendo una marca respuesta HTML con la cadena \"noai\" al cantenido. Una prevención total no podría lograrse sólo usando ésta marca, ya que puede ser simplemente ignorada." @@ -1243,7 +1245,7 @@ releaseToRefresh: "Soltar para recargar" refreshing: "Recargando..." pullDownToRefresh: "Tira hacia abajo para recargar" useGroupedNotifications: "Mostrar notificaciones agrupadas" -signupPendingError: "Ha habido un problema al verificar tu dirección de correo electrónico. Es posible que el enlace haya caducado." +emailVerificationFailedError: "Se ha producido un error al confirmar tu dirección de correo electrónico. Es posible que el enlace haya caducado." cwNotationRequired: "Si se ha activado \"ocultar contenido\", es necesario proporcionar una descripción." doReaction: "Añadir reacción" code: "Código" @@ -1358,8 +1360,8 @@ advice: "Consejos" realtimeMode: "Modo en tiempo real" turnItOn: "Activar" turnItOff: "Desactivar" -emojiMute: "Silenciar emojis" -emojiUnmute: "No Silenciar emojis" +emojiMute: "Silenciar emoji" +emojiUnmute: "No silenciar emoji" muteX: "Silenciar {x}" unmuteX: "Dejar de silenciar {x}" abort: "Abortar" @@ -1368,6 +1370,13 @@ redisplayAllTips: "Volver a mostrar todos \"Trucos y consejos\"" hideAllTips: "Ocultar todos los \"Trucos y consejos\"" defaultImageCompressionLevel: "Nivel de compresión de la imagen por defecto" defaultImageCompressionLevel_description: "Baja, conserva la calidad de la imagen pero la medida del archivo es más grande.
Alta, reduce la medida del archivo pero también la calidad de la imagen." +inMinutes: "Minutos" +inDays: "Días" +safeModeEnabled: "El modo seguro está activado" +pluginsAreDisabledBecauseSafeMode: "El modo seguro está activado, por lo que todos los plugins están desactivados." +customCssIsDisabledBecauseSafeMode: "El modo seguro está activado, por lo que no se aplica el CSS personalizado." +themeIsDefaultBecauseSafeMode: "Mientras el modo seguro esté activado, se utilizará el tema predeterminado. Cuando se desactive el modo seguro, se volverá al tema original." +thankYouForTestingBeta: "¡Gracias por tu colaboración en la prueba de la versión beta!" _order: newest: "Los más recientes primero" oldest: "Los más antiguos primero" @@ -1459,6 +1468,7 @@ _settings: contentsUpdateFrequency_description2: "Cuando el modo en tiempo real está activado, el contenido se actualiza en tiempo real independientemente de esta configuración." showUrlPreview: "Mostrar la vista previa de la URL" showAvailableReactionsFirstInNote: "Mostrar las reacciones disponibles en la parte superior." + showPageTabBarBottom: "Mostrar la barra de pestañas de la página en la parte inferior." _chat: showSenderName: "Mostrar el nombre del remitente" sendOnEnter: "Intro para enviar" @@ -1530,7 +1540,7 @@ _announcement: tooManyActiveAnnouncementDescription: "Tener demasiados anuncios activos empeora la experiencia de usuario. Por favor, considera archivar aquellos anuncios que hayan quedado obsoletos." readConfirmTitle: "¿Marcar como leído?" readConfirmText: "Esto marcará el contenido de \"{title}\" como leído." - shouldNotBeUsedToPresentPermanentInfo: "Dado que puede impactar en la experiencia de usuario de forma significativa, es recomendable usar notificaciones en el flujo de información en vez de información persistente." + shouldNotBeUsedToPresentPermanentInfo: "Se recomienda utilizar los avisos para publicar información que requiera inmediatez, en lugar de hacerlo constantemente, ya que esto perjudica especialmente la UX de los nuevos usuarios." dialogAnnouncementUxWarn: "Mostrar dos o más notificaciones en formato diálogo a la vez puede impactar en la experiencia de usuario de forma significativa, úsalos con cuidado." silence: "Silenciar notificaciones" silenceDescription: "Si lo activas, no enviarás notificación sobre este anuncio y el usuario no tendrá que leerlo." @@ -1632,6 +1642,10 @@ _serverSettings: fanoutTimelineDbFallback: "Cargar desde la base de datos" fanoutTimelineDbFallbackDescription: "Cuando esta opción está habilitada, la carga de peticiones adicionales de la línea de tiempo se hará desde la base de datos cuando éstas no se encuentren en la caché. Al deshabilitar esta opción se reduce la carga del servidor, pero limita el número de líneas de tiempo que pueden obtenerse." reactionsBufferingDescription: "Cuando se activa, el rendimiento durante la creación de reacciones mejorará considerablemente, reduciendo la carga de la base de datos. Sin embargo, aumentará el uso de memoria de Redis." + remoteNotesCleaning: "Limpieza automática de notas (publicaciones) remotas" + remoteNotesCleaning_description: "Al habilitar esta opción, se limpiarán periódicamente las entradas remotas antiguas que no se consultan, lo que evitará que la base de datos se sature." + remoteNotesCleaningMaxProcessingDuration: "Tiempo máximo de funcionamiento continuo del proceso de limpieza" + remoteNotesCleaningExpiryDaysForEachNotes: "Días mínimos para conservar las notas" inquiryUrl: "URL de consulta " inquiryUrlDescription: "Especifica una URL para el formulario de consulta al responsable del servidor o una página web para la información de contacto." openRegistration: "Registros Abiertos" @@ -1650,6 +1664,11 @@ _serverSettings: userGeneratedContentsVisibilityForVisitor: "Visibilidad de contenido generado por un usuario a invitados" userGeneratedContentsVisibilityForVisitor_description: "Esto es útil para evitar problemas causados por contenidos remotos inapropiados que no estén bien moderados y que se publiquen involuntariamente en Internet a través de su propio servidor." userGeneratedContentsVisibilityForVisitor_description2: "Publicar incondicionalmente todo el contenido del servidor en Internet, incluido el contenido remoto recibido por el servidor, es arriesgado. Esto es especialmente importante para los invitados que desconocen la naturaleza distribuida del contenido, ya que pueden creer erróneamente que incluso el contenido remoto es contenido creado por usuarios en el servidor." + restartServerSetupWizardConfirm_title: "¿Reiniciar el asistente de configuración del servidor?" + restartServerSetupWizardConfirm_text: "Algunas configuraciones actuales se restablecerán" + entrancePageStyle: "Estilo de la página de inicio" + showTimelineForVisitor: "Mostrar la línea de tiempo" + showActivitiesForVisitor: "Mostrar actividades" _userGeneratedContentsVisibilityForVisitor: all: "Todo es público." localOnly: "Sólo se publica el contenido local, el remoto se mantiene privado" @@ -1986,6 +2005,7 @@ _role: descriptionOfRateLimitFactor: "Límites más bajos son menos restrictivos, más altos menos restrictivos" canHideAds: "Puede ocultar anuncios" canSearchNotes: "Uso de la búsqueda de notas" + canSearchUsers: "Uso de la búsqueda de usuarios" canUseTranslator: "Uso de traductor" avatarDecorationLimit: "Número máximo de decoraciones de avatar" canImportAntennas: "Permitir la importación de antenas" @@ -1998,6 +2018,7 @@ _role: uploadableFileTypes_caption: "Especifica los tipos MIME/archivos permitidos. Se pueden especificar varios tipos MIME separándolos con una nueva línea, y se pueden especificar comodines con un asterisco (*). (por ejemplo, image/*)" uploadableFileTypes_caption2: "Es posible que no se detecten algunos tipos de archivos. Para permitir estos archivos, añade {x} a la especificación." noteDraftLimit: "Número de posibles borradores de notas del servidor" + watermarkAvailable: "Disponibilidad de la función de marca de agua" _condition: roleAssignedTo: "Asignado a roles manuales" isLocal: "Usuario local" @@ -2116,7 +2137,7 @@ _aboutMisskey: _displayOfSensitiveMedia: respect: "Esconder medios marcados como sensibles" ignore: "Mostrar medios marcados como sensibles" - force: "Esconder todala multimedia" + force: "Esconder toda la multimedia" _instanceTicker: none: "No mostrar" remote: "Mostrar a usuarios remotos" @@ -2257,6 +2278,7 @@ _time: minute: "Minutos" hour: "Horas" day: "Días" + month: "Mes(es)" _2fa: alreadyRegistered: "Ya has completado la configuración." registerTOTP: "Registrar aplicación autenticadora" @@ -2806,6 +2828,7 @@ _fileViewer: url: "URL" uploadedAt: "Subido el" attachedNotes: "Notas adjuntas" + usage: "Utilizado" thisPageCanBeSeenFromTheAuthor: "Esta página solo puede ser vista por el autor." _externalResourceInstaller: title: "Instalar desde sitio externo" @@ -3058,6 +3081,7 @@ _bootErrors: otherOption1: "Borra la configuración y la memoria caché del cliente" otherOption2: "Iniciar el cliente simple" otherOption3: "Iniciar la herramienta de reparación" + otherOption4: "Iniciar Misskey en modo seguro" _search: searchScopeAll: "Todo" searchScopeLocal: "Local" @@ -3094,6 +3118,8 @@ _serverSetupWizard: doYouConnectToFediverse_description1: "Cuando se conecta a una red de servidores distribuidos (Fediverso), el contenido puede intercambiarse con otros servidores." doYouConnectToFediverse_description2: "Conectarse con el Fediverso también se conoce como \"federación\"." youCanConfigureMoreFederationSettingsLater: "Los ajustes avanzados, como la especificación de servidores federados, pueden configurarse más adelante." + remoteContentsCleaning: "Limpieza automática de los contenidos recibidos" + remoteContentsCleaning_description: "La federación puede dar lugar a un flujo continuo de contenido. Al habilitar la limpieza automática, se eliminará del servidor el contenido obsoleto y sin referencias para ahorrar espacio de almacenamiento." adminInfo: "Información del administrador" adminInfo_description: "Establece la información del administrador para recibir consultas." adminInfo_mustBeFilled: "Esta información debe ser introducida en el caso de registros abiertos o la federación esté activada." @@ -3119,7 +3145,7 @@ _uploader: tip: "El archivo aún no se ha cargado, por lo que este cuadro de diálogo te permite confirmar, renombrar, comprimir y recortar el archivo antes de cargarlo. Cuando esté listo, puedes iniciar la carga pulsando el botón \"Cargar\"." _clientPerformanceIssueTip: title: "Si crees que el consumo de batería es demasiado alto" - makeSureDisabledAdBlocker: "Por favor, desactive el bloqueador de publicidad." + makeSureDisabledAdBlocker: "Por favor, desactiva el bloqueador de publicidad." makeSureDisabledAdBlocker_description: "Los bloqueadores de anuncios pueden afectar al rendimiento. Asegúrate de que no están activados en tu sistema o en las funciones/extensiones de tu navegador." makeSureDisabledCustomCss: "Desactiva el CSS personalizado" makeSureDisabledCustomCss_description: "Anular estilos puede afectar al rendimiento. Asegúrate de que el CSS personalizado o las extensiones que sobrescriben estilos no están activados." @@ -3146,10 +3172,10 @@ _watermarkEditor: type: "Tipo" image: "Imágenes" advanced: "Avanzado" + angle: "Ángulo" stripe: "Rayas" stripeWidth: "Anchura de línea" stripeFrequency: "Número de líneas." - angle: "Ángulo" polkadot: "Lunares" checker: "verificador" polkadotMainDotOpacity: "Opacidad del círculo principal" @@ -3161,6 +3187,7 @@ _imageEffector: title: "Efecto" addEffect: "Añadir Efecto" discardChangesConfirm: "¿Ignorar cambios y salir?" + nothingToConfigure: "No hay opciones configurables disponibles." _fxs: chromaticAberration: "Aberración Cromática" glitch: "Glitch" @@ -3178,11 +3205,43 @@ _imageEffector: checker: "Corrector" blockNoise: "Bloquear Ruido" tearing: "Rasgado de Imagen (Tearing)" + _fxProps: + angle: "Ángulo" + scale: "Tamaño" + size: "Tamaño" + color: "Color" + opacity: "Opacidad" + normalize: "Normalización" + amount: "Cantidad" + lightness: "Brillo" + contrast: "Contraste" + hue: "Tonalidad" + brightness: "Brillo" + saturation: "Saturación" + max: "Valor máximo" + min: "Valor mínimo" + direction: "Dirección" + phase: "Fase" + frequency: "Frecuencia" + strength: "Intensidad" + glitchChannelShift: "cambio de canal de imagen" + seed: "Valor de la semilla" + redComponent: "Componente rojo" + greenComponent: "Componente Verde" + blueComponent: "Componente Azul" + threshold: "Umbral" + centerX: "Centrar X" + centerY: "Centrar Y" + zoomLinesSmoothing: "Suavizado" + zoomLinesSmoothingDescription: "El suavizado y el ancho de línea de zoom no se pueden utilizar juntos." + zoomLinesThreshold: "Ancho de línea del zoom" + zoomLinesMaskSize: "Diámetro del centro" + zoomLinesBlack: "Hacer oscuro" drafts: "Borrador" _drafts: select: "Seleccionar borradores" cannotCreateDraftAnymore: "Se ha superado el número de borradores que se pueden crear." - cannotCreateDraftOfRenote: "No se pueden crear borradores de renotas." + cannotCreateDraft: "No se pueden crear borradores con este contenido." delete: "Eliminar borrador" deleteAreYouSure: "¿Quieres borrar el borrador?" noDrafts: "No hay borradores disponibles." diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index eba5a511de..8ea21bdb46 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -1208,7 +1208,6 @@ releaseToRefresh: "Relâcher pour rafraîchir" refreshing: "Rafraîchissement..." pullDownToRefresh: "Tirer vers le bas pour rafraîchir" useGroupedNotifications: "Grouper les notifications" -signupPendingError: "Un problème est survenu lors de la vérification de votre adresse e-mail. Le lien a peut-être expiré." cwNotationRequired: "Si « Masquer le contenu » est activé, une description doit être fournie." doReaction: "Réagir" code: "Code" @@ -1272,6 +1271,8 @@ pleaseSelectAccount: "Sélectionner un compte" availableRoles: "Rôles disponibles" postForm: "Formulaire de publication" information: "Informations" +inMinutes: "min" +inDays: "j" _chat: invitations: "Inviter" noHistory: "Pas d'historique" @@ -2370,3 +2371,11 @@ _watermarkEditor: image: "Images" advanced: "Avancé" angle: "Angle" +_imageEffector: + _fxProps: + angle: "Angle" + scale: "Taille" + size: "Taille" + color: "Couleur" + opacity: "Transparence" + lightness: "Clair" diff --git a/locales/generateDTS.js b/locales/generateDTS.js index 49807144ec..ab0613cc82 100644 --- a/locales/generateDTS.js +++ b/locales/generateDTS.js @@ -73,7 +73,7 @@ export default function generateDTS() { ts.NodeFlags.Const, ), ), - ts.factory.createInterfaceDeclaration( + ts.factory.createTypeAliasDeclaration( [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], ts.factory.createIdentifier('ParameterizedString'), [ @@ -84,20 +84,22 @@ export default function generateDTS() { ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), ), ], - undefined, - [ - ts.factory.createPropertySignature( - undefined, - ts.factory.createComputedPropertyName( - ts.factory.createIdentifier('kParameters'), - ), - undefined, - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier('T'), + ts.factory.createIntersectionTypeNode([ + ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), + ts.factory.createTypeLiteralNode([ + ts.factory.createPropertySignature( undefined, + ts.factory.createComputedPropertyName( + ts.factory.createIdentifier('kParameters'), + ), + undefined, + ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier('T'), + undefined, + ), ), - ), - ], + ]) + ]), ), ts.factory.createInterfaceDeclaration( [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], diff --git a/locales/id-ID.yml b/locales/id-ID.yml index 0e8e46dc7b..ac6aefa4b4 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -5,9 +5,13 @@ introMisskey: "Selamat datang! Misskey adalah perangkat mikroblog tercatu bersif poweredByMisskeyDescription: "{name} adalah sebuah layanan (instance) yang menggunakan platform sumber terbuka Misskey." monthAndDay: "{day} {month}" search: "Penelusuran" +reset: "Reset" notifications: "Notifikasi" username: "Nama Pengguna" password: "Kata sandi" +initialPasswordForSetup: "Kata sandi untuk memulai konfigurasi awal" +initialPasswordIsIncorrect: "Kata sandi untuk memulai konfigurasi awal salah." +initialPasswordForSetupDescription: "Jika Anda menginstal Misskey sendiri, gunakan kata sandi yang Anda masukkan di berkas konfigurasi.\nJika Anda menggunakan layanan hosting Misskey, gunakan kata sandi yang diberikan.\nJika Anda belum mengatur kata sandi, biarkan kosong dan lanjutkan." forgotPassword: "Lupa Kata Sandi" fetchingAsApObject: "Mengambil data dari Fediverse..." ok: "OK" @@ -45,6 +49,7 @@ pin: "Sematkan ke profil" unpin: "Lepas sematan dari profil" copyContent: "Salin konten" copyLink: "Salin tautan" +copyRemoteLink: "Salin tautan jarak jauh" copyLinkRenote: "Salin tautan renote" delete: "Hapus" deleteAndEdit: "Hapus dan sunting" @@ -212,8 +217,10 @@ perDay: "per Hari" stopActivityDelivery: "Berhenti mengirim aktivitas" blockThisInstance: "Blokir instansi ini" silenceThisInstance: "Senyapkan instansi ini" +mediaSilenceThisInstance: "Server media senyap" operations: "Tindakan" software: "Perangkat lunak" +softwareName: "Nama Perangkat Lunak" version: "Versi" metadata: "Metadata" withNFiles: "{n} berkas" @@ -1040,7 +1047,7 @@ disableFederationConfirmWarn: "Mematikan federasi tidak membuat kiriman menjadi disableFederationOk: "Matikan federasi" invitationRequiredToRegister: "Instansi ini dalam mode undangan-saja. Kamu harus memasukkan kode undangan yang valid untuk mendaftar." emailNotSupported: "Instansi ini tidak mendukung mengirim surel" -postToTheChannel: "Catat ke kanal" +postToTheChannel: "Buat Catatan ke Kanal" cannotBeChangedLater: "Hal ini nantinya tidak dapat diubah lagi." reactionAcceptance: "Penerimaan reaksi" likeOnly: "Hanya suka" @@ -1205,7 +1212,6 @@ releaseToRefresh: "Lepaskan untuk memuat ulang" refreshing: "Sedang memuat ulang..." pullDownToRefresh: "Tarik ke bawah untuk memuat ulang" useGroupedNotifications: "Tampilkan notifikasi secara dikelompokkan" -signupPendingError: "Terdapat masalah ketika memverifikasi alamat surel. Tautan kemungkinan telah kedaluwarsa." cwNotationRequired: "Jika \"Sembunyikan konten\" diaktifkan, deskripsi harus disediakan." doReaction: "Tambahkan reaksi" code: "Kode" @@ -1256,6 +1262,8 @@ thereAreNChanges: "Ada {n} perubahan" prohibitedWordsForNameOfUser: "Kata yang dilarang untuk nama pengguna" postForm: "Buat catatan" information: "Informasi" +inMinutes: "menit" +inDays: "hari" _chat: invitations: "Undang" noHistory: "Tidak ada riwayat" @@ -2400,7 +2408,7 @@ _deck: main: "Utama" widgets: "Widget" notifications: "Notifikasi" - tl: "Lini masa" + tl: "Beranda" antenna: "Antena" list: "Daftar" channel: "Kanal" @@ -2618,3 +2626,11 @@ _watermarkEditor: image: "Gambar" advanced: "Tingkat lanjut" angle: "Sudut" +_imageEffector: + _fxProps: + angle: "Sudut" + scale: "Ukuran" + size: "Ukuran" + color: "Warna" + opacity: "Opasitas" + lightness: "Menerangkan" diff --git a/locales/index.d.ts b/locales/index.d.ts index 52b333b689..4071d5c373 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -2,9 +2,9 @@ // This file is generated by locales/generateDTS.js // Do not edit this file directly. declare const kParameters: unique symbol; -export interface ParameterizedString { +export type ParameterizedString = string & { [kParameters]: T; -} +}; export interface ILocale { [_: string]: string | ParameterizedString | ILocale; } @@ -315,11 +315,11 @@ export interface Locale extends ILocale { */ "mention": string; /** - * あなた宛て + * メンション */ "mentions": string; /** - * ダイレクト投稿 + * 指名 */ "directNotes": string; /** @@ -1030,6 +1030,10 @@ export interface Locale extends ILocale { * 処理中 */ "processing": string; + /** + * 準備中 + */ + "preprocessing": string; /** * プレビュー */ @@ -1227,7 +1231,7 @@ export interface Locale extends ILocale { */ "noMoreHistory": string; /** - * チャットを始める + * メッセージを送る */ "startChat": string; /** @@ -1927,7 +1931,7 @@ export interface Locale extends ILocale { */ "markAsReadAllUnreadNotes": string; /** - * すべてのチャットを既読にする + * すべてのダイレクトメッセージを既読にする */ "markAsReadAllTalkMessages": string; /** @@ -2567,11 +2571,11 @@ export interface Locale extends ILocale { */ "serviceworkerInfo": string; /** - * 削除された投稿 + * 削除されたノート */ "deletedNote": string; /** - * 非公開の投稿 + * 非公開のノート */ "invisibleNote": string; /** @@ -4234,6 +4238,10 @@ export interface Locale extends ILocale { * プリセットから選択 */ "selectFromPresets": string; + /** + * カスタム + */ + "custom": string; /** * 実績 */ @@ -4386,6 +4394,10 @@ export interface Locale extends ILocale { * ノート検索は利用できません。 */ "notesSearchNotAvailable": string; + /** + * ユーザー検索は利用できません。 + */ + "usersSearchNotAvailable": string; /** * ライセンス */ @@ -4993,7 +5005,7 @@ export interface Locale extends ILocale { /** * メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。 */ - "signupPendingError": string; + "emailVerificationFailedError": string; /** * 「内容を隠す」がオンの場合は注釈の記述が必要です。 */ @@ -5231,7 +5243,7 @@ export interface Locale extends ILocale { */ "prohibitedWordsForNameOfUser": string; /** - * このリストに含まれる文字列がユーザーの名前に含まれる場合、ユーザーの名前の変更を拒否します。モデレーター権限を持つユーザーはこの制限の影響を受けません。 + * このリストに含まれる文字列がユーザーの名前に含まれる場合、ユーザーの名前の変更を拒否します。モデレーター権限を持つユーザーはこの制限の影響を受けません。ユーザー名(username)に対しても全て小文字に置き換えて検査します。 */ "prohibitedWordsForNameOfUserDescription": string; /** @@ -5382,6 +5394,14 @@ export interface Locale extends ILocale { * チャット */ "chat": string; + /** + * ダイレクトメッセージ + */ + "directMessage": string; + /** + * メッセージ + */ + "directMessage_short": string; /** * 旧設定情報を移行 */ @@ -5493,6 +5513,76 @@ export interface Locale extends ILocale { * 低くすると画質を保てますが、ファイルサイズは増加します。
高くするとファイルサイズを減らせますが、画質は低下します。 */ "defaultImageCompressionLevel_description": string; + /** + * デフォルトの圧縮度 + */ + "defaultCompressionLevel": string; + /** + * 低くすると品質を保てますが、ファイルサイズは増加します。
高くするとファイルサイズを減らせますが、品質は低下します。 + */ + "defaultCompressionLevel_description": string; + /** + * 分 + */ + "inMinutes": string; + /** + * 日 + */ + "inDays": string; + /** + * セーフモードが有効です + */ + "safeModeEnabled": string; + /** + * セーフモードが有効なため、プラグインはすべて無効化されています。 + */ + "pluginsAreDisabledBecauseSafeMode": string; + /** + * セーフモードが有効なため、カスタムCSSは適用されていません。 + */ + "customCssIsDisabledBecauseSafeMode": string; + /** + * セーフモードが有効な間はデフォルトのテーマが使用されます。セーフモードをオフにすると元に戻ります。 + */ + "themeIsDefaultBecauseSafeMode": string; + /** + * ベータ版の検証にご協力いただきありがとうございます! + */ + "thankYouForTestingBeta": string; + /** + * ユーザー指定ノートを作成 + */ + "createUserSpecifiedNote": string; + "_compression": { + "_quality": { + /** + * 高品質 + */ + "high": string; + /** + * 中品質 + */ + "medium": string; + /** + * 低品質 + */ + "low": string; + }; + "_size": { + /** + * サイズ大 + */ + "large": string; + /** + * サイズ中 + */ + "medium": string; + /** + * サイズ小 + */ + "small": string; + }; + }; "_order": { /** * 新しい順 @@ -5504,6 +5594,10 @@ export interface Locale extends ILocale { "oldest": string; }; "_chat": { + /** + * メッセージ + */ + "messages": string; /** * まだメッセージはありません */ @@ -5513,36 +5607,36 @@ export interface Locale extends ILocale { */ "newMessage": string; /** - * 個人チャット + * 個別 */ "individualChat": string; /** - * 特定ユーザーとの一対一のチャットができます。 + * 特定ユーザーと個別にメッセージのやりとりができます。 */ "individualChat_description": string; /** - * ルームチャット + * グループ */ "roomChat": string; /** - * 複数人でのチャットができます。 - * また、個人チャットを許可していないユーザーとでも、相手が受け入れればチャットができます。 + * 複数人でメッセージのやりとりができます。 + * また、個別のメッセージを許可していないユーザーとでも、相手が受け入れればやりとりできます。 */ "roomChat_description": string; /** - * ルームを作成 + * グループを作成 */ "createRoom": string; /** - * ユーザーを招待してチャットを始めましょう + * ユーザーを招待してメッセージを送信しましょう */ "inviteUserToChat": string; /** - * 作成したルーム + * 作成したグループ */ "yourRooms": string; /** - * 参加中のルーム + * 参加中のグループ */ "joiningRooms": string; /** @@ -5562,7 +5656,7 @@ export interface Locale extends ILocale { */ "noHistory": string; /** - * ルームはありません + * グループはありません */ "noRooms": string; /** @@ -5582,7 +5676,7 @@ export interface Locale extends ILocale { */ "ignore": string; /** - * ルームから退出 + * グループから退出 */ "leave": string; /** @@ -5606,35 +5700,35 @@ export interface Locale extends ILocale { */ "newline": string; /** - * このルームをミュート + * このグループをミュート */ "muteThisRoom": string; /** - * ルームを削除 + * グループを削除 */ "deleteRoom": string; /** - * このサーバー、またはこのアカウントでチャットは有効化されていません。 + * このサーバー、またはこのアカウントでダイレクトメッセージは有効化されていません。 */ "chatNotAvailableForThisAccountOrServer": string; /** - * このサーバー、またはこのアカウントでチャットは読み取り専用となっています。新たに書き込んだり、チャットルームを作成・参加したりすることはできません。 + * このサーバー、またはこのアカウントでダイレクトメッセージは読み取り専用となっています。新たに書き込んだり、グループを作成・参加したりすることはできません。 */ "chatIsReadOnlyForThisAccountOrServer": string; /** - * 相手のアカウントでチャット機能が使えない状態になっています。 + * 相手のアカウントでダイレクトメッセージが使えない状態になっています。 */ "chatNotAvailableInOtherAccount": string; /** - * このユーザーとのチャットを開始できません + * このユーザーとのダイレクトメッセージを開始できません */ "cannotChatWithTheUser": string; /** - * チャットが使えない状態になっているか、相手がチャットを開放していません。 + * ダイレクトメッセージが使えない状態になっているか、相手がダイレクトメッセージを開放していません。 */ "cannotChatWithTheUser_description": string; /** - * あなたはこのルームの参加者ではありませんが、招待が届いています。参加するには、招待を承認してください。 + * あなたはこのグループの参加者ではありませんが、招待が届いています。参加するには、招待を承認してください。 */ "youAreNotAMemberOfThisRoomButInvited": string; /** @@ -5642,31 +5736,31 @@ export interface Locale extends ILocale { */ "doYouAcceptInvitation": string; /** - * チャットする + * ダイレクトメッセージ */ "chatWithThisUser": string; /** - * このユーザーはフォロワーからのみチャットを受け付けています。 + * このユーザーはフォロワーからのみメッセージを受け付けています。 */ "thisUserAllowsChatOnlyFromFollowers": string; /** - * このユーザーは、このユーザーがフォローしているユーザーからのみチャットを受け付けています。 + * このユーザーは、このユーザーがフォローしているユーザーからのみメッセージを受け付けています。 */ "thisUserAllowsChatOnlyFromFollowing": string; /** - * このユーザーは相互フォローのユーザーからのみチャットを受け付けています。 + * このユーザーは相互フォローのユーザーからのみメッセージを受け付けています。 */ "thisUserAllowsChatOnlyFromMutualFollowing": string; /** - * このユーザーは誰からもチャットを受け付けていません。 + * このユーザーは誰からもメッセージを受け付けていません。 */ "thisUserNotAllowedChatAnyone": string; /** - * チャットを許可する相手 + * メッセージを許可する相手 */ "chatAllowedUsers": string; /** - * 自分からチャットメッセージを送った相手とはこの設定に関わらずチャットが可能です。 + * 自分からメッセージを送った相手とはこの設定に関わらずメッセージの送受信が可能です。 */ "chatAllowedUsers_note": string; "_chatAllowedUsers": { @@ -5847,6 +5941,10 @@ export interface Locale extends ILocale { * 利用できるリアクションを先頭に表示 */ "showAvailableReactionsFirstInNote": string; + /** + * ページのタブバーを下部に表示 + */ + "showPageTabBarBottom": string; "_chat": { /** * 送信者の名前を表示 @@ -6329,7 +6427,7 @@ export interface Locale extends ILocale { */ "followers": string; /** - * 指定したユーザーにのみ公開され、また相手に通知が入ります。ダイレクトメッセージのかわりにお使いいただけます。 + * 指定したユーザーにのみ公開され、また相手に通知が入ります。 */ "direct": string; /** @@ -6337,7 +6435,7 @@ export interface Locale extends ILocale { */ "doNotSendConfidencialOnDirect1": string; /** - * 送信先のサーバーの管理者は投稿内容を見ることが可能なので、信頼できないサーバーのユーザーにダイレクト投稿を送信する場合は、機密情報の扱いに注意が必要です。 + * 送信先のサーバーの管理者は投稿内容を見ることが可能なので、信頼できないサーバーのユーザーが含まれる限定公開のノートを作成する際は、機密情報の扱いに注意が必要です。 */ "doNotSendConfidencialOnDirect2": string; /** @@ -6486,6 +6584,22 @@ export interface Locale extends ILocale { * 有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。 */ "reactionsBufferingDescription": string; + /** + * リモート投稿の自動クリーニング + */ + "remoteNotesCleaning": string; + /** + * 有効にすると、一定期間経過したリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。 + */ + "remoteNotesCleaning_description": string; + /** + * 最大クリーニング処理継続時間 + */ + "remoteNotesCleaningMaxProcessingDuration": string; + /** + * 最低ノート保持日数 + */ + "remoteNotesCleaningExpiryDaysForEachNotes": string; /** * 問い合わせ先URL */ @@ -6558,6 +6672,26 @@ export interface Locale extends ILocale { * サーバーで受信したリモートのコンテンツを含め、サーバー内の全てのコンテンツを無条件でインターネットに公開することはリスクが伴います。特に、分散型の特性を知らない閲覧者にとっては、リモートのコンテンツであってもサーバー内で作成されたコンテンツであると誤って認識してしまう可能性があるため、注意が必要です。 */ "userGeneratedContentsVisibilityForVisitor_description2": string; + /** + * サーバーの初期設定ウィザードをやり直しますか? + */ + "restartServerSetupWizardConfirm_title": string; + /** + * 現在の一部の設定はリセットされます。 + */ + "restartServerSetupWizardConfirm_text": string; + /** + * エントランスページのスタイル + */ + "entrancePageStyle": string; + /** + * タイムラインを表示する + */ + "showTimelineForVisitor": string; + /** + * アクティビティを表示する + */ + "showActivitiesForVisitor": string; "_userGeneratedContentsVisibilityForVisitor": { /** * 全て公開 @@ -7747,6 +7881,10 @@ export interface Locale extends ILocale { * ノート検索の利用 */ "canSearchNotes": string; + /** + * ユーザー検索の利用 + */ + "canSearchUsers": string; /** * 翻訳機能の利用 */ @@ -7776,7 +7914,7 @@ export interface Locale extends ILocale { */ "canImportUserLists": string; /** - * チャットを許可 + * ダイレクトメッセージを許可 */ "chatAvailability": string; /** @@ -7795,6 +7933,10 @@ export interface Locale extends ILocale { * サーバーサイドのノートの下書きの作成可能数 */ "noteDraftLimit": string; + /** + * ウォーターマーク機能の使用可否 + */ + "watermarkAvailable": string; }; "_condition": { /** @@ -8622,7 +8764,7 @@ export interface Locale extends ILocale { */ "badge": string; /** - * チャットの背景 + * メッセージの背景 */ "messageBg": string; /** @@ -8649,7 +8791,7 @@ export interface Locale extends ILocale { */ "reaction": string; /** - * チャットのメッセージ + * ダイレクトメッセージ */ "chatMessage": string; }; @@ -8772,6 +8914,10 @@ export interface Locale extends ILocale { * 日 */ "day": string; + /** + * ヶ月 + */ + "month": string; }; "_2fa": { /** @@ -8929,11 +9075,11 @@ export interface Locale extends ILocale { */ "write:following": string; /** - * チャットを見る + * ダイレクトメッセージを見る */ "read:messaging": string; /** - * チャットを操作する + * ダイレクトメッセージを操作する */ "write:messaging": string; /** @@ -9225,11 +9371,11 @@ export interface Locale extends ILocale { */ "write:report-abuse": string; /** - * チャットを操作する + * ダイレクトメッセージを操作する */ "write:chat": string; /** - * チャットを閲覧する + * ダイレクトメッセージを閲覧する */ "read:chat": string; }; @@ -9455,7 +9601,7 @@ export interface Locale extends ILocale { */ "birthdayFollowings": string; /** - * チャット + * ダイレクトメッセージ */ "chat": string; }; @@ -9589,7 +9735,7 @@ export interface Locale extends ILocale { */ "followersDescription": string; /** - * ダイレクト + * 指名 */ "specified": string; /** @@ -10195,7 +10341,7 @@ export interface Locale extends ILocale { */ "roleAssigned": string; /** - * チャットルームへ招待されました + * ダイレクトメッセージのグループへ招待されました */ "chatRoomInvitationReceived": string; /** @@ -10308,7 +10454,7 @@ export interface Locale extends ILocale { */ "roleAssigned": string; /** - * チャットルームへ招待された + * ダイレクトメッセージのグループへ招待された */ "chatRoomInvitationReceived": string; /** @@ -10478,11 +10624,11 @@ export interface Locale extends ILocale { */ "channel": string; /** - * あなた宛て + * メンション */ "mentions": string; /** - * ダイレクト + * 指名 */ "direct": string; /** @@ -10490,7 +10636,7 @@ export interface Locale extends ILocale { */ "roleTimeline": string; /** - * チャット + * ダイレクトメッセージ */ "chat": string; }; @@ -10857,7 +11003,7 @@ export interface Locale extends ILocale { */ "deleteGalleryPost": string; /** - * チャットルームを削除 + * ダイレクトメッセージのグループを削除 */ "deleteChatRoom": string; /** @@ -10890,6 +11036,10 @@ export interface Locale extends ILocale { * 添付されているノート */ "attachedNotes": string; + /** + * 利用 + */ + "usage": string; /** * このページは、このファイルをアップロードしたユーザーしか閲覧できません。 */ @@ -11799,6 +11949,10 @@ export interface Locale extends ILocale { * 修復ツールを起動 */ "otherOption3": string; + /** + * Misskeyをセーフモードで起動 + */ + "otherOption4": string; }; "_search": { /** @@ -11935,6 +12089,14 @@ export interface Locale extends ILocale { * 連合可能なサーバーの指定など、高度な設定も後ほど可能です。 */ "youCanConfigureMoreFederationSettingsLater": string; + /** + * リモートコンテンツの自動クリーニング + */ + "remoteContentsCleaning": string; + /** + * 連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、一定期間経過したリモートコンテンツを自動でサーバーから削除し、ストレージを節約できます。 + */ + "remoteContentsCleaning_description": string; /** * 管理者情報 */ @@ -12115,10 +12277,18 @@ export interface Locale extends ILocale { * テキスト */ "text": string; + /** + * 二次元コード + */ + "qr": string; /** * 位置 */ "position": string; + /** + * マージン + */ + "margin": string; /** * タイプ */ @@ -12131,6 +12301,10 @@ export interface Locale extends ILocale { * 高度 */ "advanced": string; + /** + * 角度 + */ + "angle": string; /** * ストライプ */ @@ -12143,10 +12317,6 @@ export interface Locale extends ILocale { * ラインの数 */ "stripeFrequency": string; - /** - * 角度 - */ - "angle": string; /** * ポルカドット */ @@ -12175,6 +12345,10 @@ export interface Locale extends ILocale { * サブドットの数 */ "polkadotSubDotDivisions": string; + /** + * 空欄にするとアカウントのURLになります + */ + "leaveBlankToAccountUrl": string; }; "_imageEffector": { /** @@ -12189,6 +12363,10 @@ export interface Locale extends ILocale { * 変更を破棄して終了しますか? */ "discardChangesConfirm": string; + /** + * 設定項目はありません + */ + "nothingToConfigure": string; "_fxs": { /** * 色収差 @@ -12210,6 +12388,10 @@ export interface Locale extends ILocale { * 白黒 */ "grayscale": string; + /** + * ぼかし + */ + "blur": string; /** * 色調補正 */ @@ -12254,6 +12436,152 @@ export interface Locale extends ILocale { * ティアリング */ "tearing": string; + /** + * 塗りつぶし + */ + "fill": string; + }; + "_fxProps": { + /** + * 角度 + */ + "angle": string; + /** + * サイズ + */ + "scale": string; + /** + * サイズ + */ + "size": string; + /** + * 半径 + */ + "radius": string; + /** + * サンプル数 + */ + "samples": string; + /** + * 位置 + */ + "offset": string; + /** + * 色 + */ + "color": string; + /** + * 不透明度 + */ + "opacity": string; + /** + * 正規化 + */ + "normalize": string; + /** + * 量 + */ + "amount": string; + /** + * 明るさ + */ + "lightness": string; + /** + * コントラスト + */ + "contrast": string; + /** + * 色相 + */ + "hue": string; + /** + * 輝度 + */ + "brightness": string; + /** + * 彩度 + */ + "saturation": string; + /** + * 最大値 + */ + "max": string; + /** + * 最小値 + */ + "min": string; + /** + * 方向 + */ + "direction": string; + /** + * 位相 + */ + "phase": string; + /** + * 頻度 + */ + "frequency": string; + /** + * 強さ + */ + "strength": string; + /** + * ズレ + */ + "glitchChannelShift": string; + /** + * シード値 + */ + "seed": string; + /** + * 赤色成分 + */ + "redComponent": string; + /** + * 緑色成分 + */ + "greenComponent": string; + /** + * 青色成分 + */ + "blueComponent": string; + /** + * しきい値 + */ + "threshold": string; + /** + * 中心X + */ + "centerX": string; + /** + * 中心Y + */ + "centerY": string; + /** + * スムージング + */ + "zoomLinesSmoothing": string; + /** + * スムージングと集中線の幅の設定は併用できません。 + */ + "zoomLinesSmoothingDescription": string; + /** + * 集中線の幅 + */ + "zoomLinesThreshold": string; + /** + * 中心径 + */ + "zoomLinesMaskSize": string; + /** + * 黒色にする + */ + "zoomLinesBlack": string; + /** + * 円形 + */ + "circle": string; }; }; /** @@ -12270,9 +12598,9 @@ export interface Locale extends ILocale { */ "cannotCreateDraftAnymore": string; /** - * リノートの下書きは作成できません。 + * この内容では下書きを作成できません。 */ - "cannotCreateDraftOfRenote": string; + "cannotCreateDraft": string; /** * 下書きを削除 */ @@ -12314,6 +12642,68 @@ export interface Locale extends ILocale { */ "listDrafts": string; }; + /** + * 二次元コード + */ + "qr": string; + "_qr": { + /** + * 表示 + */ + "showTabTitle": string; + /** + * 読み取る + */ + "readTabTitle": string; + /** + * {name} {acct} + */ + "shareTitle": ParameterizedString<"name" | "acct">; + /** + * Fediverseで私をフォローしてください! + */ + "shareText": string; + /** + * カメラを選択 + */ + "chooseCamera": string; + /** + * ライト選択不可 + */ + "cannotToggleFlash": string; + /** + * ライトをオンにする + */ + "turnOnFlash": string; + /** + * ライトをオフにする + */ + "turnOffFlash": string; + /** + * コードリーダーを再開 + */ + "startQr": string; + /** + * コードリーダーを停止 + */ + "stopQr": string; + /** + * QRコードが見つかりません + */ + "noQrCodeFound": string; + /** + * 端末の画像をスキャン + */ + "scanFile": string; + /** + * テキスト + */ + "raw": string; + /** + * MFM + */ + "mfm": string; + }; } declare const locales: { [lang: string]: Locale; diff --git a/locales/index.js b/locales/index.js index 091d216dee..6d9cf4796b 100644 --- a/locales/index.js +++ b/locales/index.js @@ -36,6 +36,7 @@ const languages = [ 'ru-RU', 'sk-SK', 'th-TH', + 'tr-TR', 'ug-CN', 'uk-UA', 'vi-VN', diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 23d46d6e22..8ea11f81c9 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -139,7 +139,7 @@ overwriteFromPinnedEmojis: "Sovrascrivi con le impostazioni globali" reactionSettingDescription2: "Trascina per riorganizzare, clicca per cancellare, usa il pulsante \"+\" per aggiungere." rememberNoteVisibility: "Ricordare le impostazioni di visibilità delle note" attachCancel: "Rimuovi allegato" -deleteFile: "File da Drive eliminato" +deleteFile: "Elimina un file dal Drive" markAsSensitive: "Segna come esplicito" unmarkAsSensitive: "Non segnare come esplicito " enterFileName: "Nome del file" @@ -1054,6 +1054,7 @@ permissionDeniedError: "Errore, attività non autorizzata" permissionDeniedErrorDescription: "Non si dispone dell'autorizzazione per eseguire questa operazione." preset: "Preimpostato" selectFromPresets: "Seleziona preimpostato" +custom: "Personalizzato" achievements: "Conquiste" gotInvalidResponseError: "Risposta del server non valida" gotInvalidResponseErrorDescription: "Il server potrebbe essere irraggiungibile o in manutenzione. Riprova più tardi." @@ -1092,6 +1093,7 @@ prohibitedWordsDescription2: "Gli spazi creano la relazione \"E\" tra parole (qu hiddenTags: "Hashtag nascosti" hiddenTagsDescription: "Impedire la visualizzazione del tag impostato nei trend. Puoi impostare più valori, uno per riga." notesSearchNotAvailable: "Non è possibile cercare tra le Note." +usersSearchNotAvailable: "La ricerca profili non è disponibile." license: "Licenza" unfavoriteConfirm: "Vuoi davvero rimuovere la preferenza?" myClips: "Le mie Clip" @@ -1198,7 +1200,7 @@ replies: "Risposte" renotes: "Rinota" loadReplies: "Leggi le risposte" loadConversation: "Leggi la conversazione" -pinnedList: "Elenco in primo piano" +pinnedList: "Lista in primo piano" keepScreenOn: "Mantenere lo schermo acceso" verifiedLink: "Abbiamo confermato la validità di questo collegamento" notifyNotes: "Notifica nuove Note" @@ -1243,7 +1245,7 @@ releaseToRefresh: "Rilascia per aggiornare" refreshing: "Aggiornamento..." pullDownToRefresh: "Trascinare per aggiornare" useGroupedNotifications: "Mostra le notifiche raggruppate" -signupPendingError: "Si è verificato un problema durante la verifica del tuo indirizzo email. Potrebbe essere scaduto il collegamento temporaneo." +emailVerificationFailedError: "La verifica dell'indirizzo e-mail non è andata a buon fine. Il link potrebbe essere scaduto." cwNotationRequired: "Devi indicare perché il contenuto è indicato come esplicito." doReaction: "Reagisci" code: "Codice" @@ -1313,6 +1315,7 @@ availableRoles: "Ruoli disponibili" acknowledgeNotesAndEnable: "Attivare dopo averne compreso il comportamento." federationSpecified: "Questo server è federato solo con istanze specifiche del Fediverso. Puoi interagire solo con quelle scelte dall'amministrazione." federationDisabled: "Questo server ha la federazione disabilitata. Non puoi interagire con profili provenienti da altri server." +draft: "Bozza" confirmOnReact: "Confermare le reazioni" reactAreYouSure: "Vuoi davvero reagire con {emoji} ?" markAsSensitiveConfirm: "Vuoi davvero indicare questo contenuto multimediale come esplicito?" @@ -1367,6 +1370,16 @@ redisplayAllTips: "Mostra tutti i suggerimenti" hideAllTips: "Nascondi tutti i suggerimenti" defaultImageCompressionLevel: "Livello predefinito di compressione immagini" defaultImageCompressionLevel_description: "La compressione diminuisce la qualità dell'immagine, poca compressione mantiene alta qualità delle immagini. Aumentandola, si riducono le dimensioni del file, a discapito della qualità dell'immagine." +inMinutes: "min" +inDays: "giorni" +safeModeEnabled: "La modalità sicura è attiva" +pluginsAreDisabledBecauseSafeMode: "Tutti i plugin sono disattivati, poiché la modalità sicura è attiva." +customCssIsDisabledBecauseSafeMode: "Il CSS personalizzato non è stato applicato, poiché la modalità sicura è attiva." +themeIsDefaultBecauseSafeMode: "Quando la modalità sicura è attiva, viene utilizzato il tema predefinito. Quando la modalità sicura viene disattivata, il tema torna a essere quello precedente." +thankYouForTestingBeta: "Grazie per la tua collaborazione nella verifica delle versioni beta!" +_order: + newest: "Prima i più recenti" + oldest: "Meno recenti prima" _chat: noMessagesYet: "Ancora nessun messaggio" newMessage: "Nuovo messaggio" @@ -1455,6 +1468,7 @@ _settings: contentsUpdateFrequency_description2: "Quando la modalità è in tempo reale, arriveranno a prescindere." showUrlPreview: "Mostra anteprima dell'URL" showAvailableReactionsFirstInNote: "Mostra le reazioni disponibili in alto" + showPageTabBarBottom: "Visualizza le schede della pagina nella parte inferiore" _chat: showSenderName: "Mostra il nome del mittente" sendOnEnter: "Invio spedisce" @@ -1628,6 +1642,10 @@ _serverSettings: fanoutTimelineDbFallback: "Elaborazione dati alternativa" fanoutTimelineDbFallbackDescription: "Attivando l'elaborazione alternativa, verrà interrogato ulteriormente il database se la timeline non è nella cache. \nDisattivando, si può ridurre ulteriormente il carico del server, evitando l'elaborazione alternativa, ma limitando l'intervallo recuperabile delle timeline." reactionsBufferingDescription: "Attivando questa opzione, puoi migliorare significativamente le prestazioni durante la creazione delle reazioni e ridurre il carico sul database. Tuttavia, aumenterà l'impiego di memoria Redis." + remoteNotesCleaning: "Pulizia automatica dei contenuti remoti" + remoteNotesCleaning_description: "Se abilitata, verranno periodicamente rimosse le vecchie Note remote senza relazioni, per ridurre il sovraccarico del sistema." + remoteNotesCleaningMaxProcessingDuration: "Durata massima del processo di pulizia" + remoteNotesCleaningExpiryDaysForEachNotes: "Periodo minimo di conservazione delle note" inquiryUrl: "URL di contatto" inquiryUrlDescription: "Specificare l'URL al modulo di contatto, oppure le informazioni con i dati di contatto dell'amministrazione." openRegistration: "Registrazioni aperte" @@ -1646,6 +1664,11 @@ _serverSettings: userGeneratedContentsVisibilityForVisitor: "Visibilità dei contenuti generati dagli utenti ai non utenti" userGeneratedContentsVisibilityForVisitor_description: "Questa funzionalità è utile per impedire che contenuti remoti inappropriati e difficili da moderare vengano inavvertitamente resi pubblici su Internet tramite il proprio server." userGeneratedContentsVisibilityForVisitor_description2: "Esistono dei rischi nell'esporre incondizionatamente su internet tutto il contenuto del tuo server, incluso il contenuto remoto ricevuto da altri server. In particolare, occorre prestare attenzione, perché le persone non consapevoli della federazione potrebbero erroneamente credere che il contenuto remoto sia stato invece creato all'interno del proprio server." + restartServerSetupWizardConfirm_title: "Vuoi ripetere la procedura guidata di configurazione iniziale del server?" + restartServerSetupWizardConfirm_text: "Verranno ripristinate alcune tue impostazioni personalizzate." + entrancePageStyle: "Stile della pagina di ingresso" + showTimelineForVisitor: "Mostra la Timeline a visitatori non autenticati" + showActivitiesForVisitor: "Mostrare la propria attività" _userGeneratedContentsVisibilityForVisitor: all: "Tutto pubblico" localOnly: "Pubblica solo contenuti locali, mantieni privati ​​i contenuti remoti" @@ -1982,6 +2005,7 @@ _role: descriptionOfRateLimitFactor: "I rapporti più bassi sono meno restrittivi, quelli più alti lo sono di più." canHideAds: "Nascondere i banner" canSearchNotes: "Ricercare nelle Note" + canSearchUsers: "Può cercare profili" canUseTranslator: "Tradurre le Note" avatarDecorationLimit: "Numero massimo di decorazioni foto profilo installabili" canImportAntennas: "Può importare Antenne" @@ -1993,6 +2017,8 @@ _role: uploadableFileTypes: "Tipi di file caricabili" uploadableFileTypes_caption: "Specifica il tipo MIME. Puoi specificare più valori separandoli andando a capo, oppure indicare caratteri jolly con un asterisco (*). Ad esempio: image/*" uploadableFileTypes_caption2: "A seconda del file, il tipo potrebbe non essere determinato. Se si desidera consentire tali file, aggiungere {x} alla specifica." + noteDraftLimit: "Numero massimo di Note in bozza, lato server" + watermarkAvailable: "Disponibilità della funzione filigrana" _condition: roleAssignedTo: "Assegnato a ruoli manualmente" isLocal: "Profilo locale" @@ -2152,6 +2178,7 @@ _theme: install: "Installa un tema" manage: "Gestione dei temi" code: "Codice tema" + copyThemeCode: "Copia il codice del Tema" description: "Descrizione" installed: "{name} è installato" installedThemes: "Temi installati" @@ -2195,7 +2222,7 @@ _theme: hashtag: "Hashtag" mention: "Menzioni" mentionMe: "Menzioni (di me)" - renote: "Renota" + renote: "Rinota" modalBg: "Sfondo modale." divider: "Interruzione di linea" scrollbarHandle: "Maniglie della barra di scorrimento" @@ -2251,6 +2278,7 @@ _time: minute: "min" hour: "ore" day: "giorni" + month: "Mese" _2fa: alreadyRegistered: "La configurazione è stata già completata." registerTOTP: "Registra una App di autenticazione a due fattori (2FA/MFA)" @@ -2635,7 +2663,7 @@ _notification: createToken: "È stato creato un token di accesso" createTokenDescription: "In caso contrario, eliminare il token di accesso tramite ({text})." _types: - all: "Tutto" + all: "Tutte" note: "Nuove Note" follow: "Follower" mention: "Menzioni" @@ -2643,7 +2671,7 @@ _notification: renote: "Rinota" quote: "Cita" reaction: "Reazioni" - pollEnded: "Sondaggio chiuso." + pollEnded: "Sondaggio terminato" receiveFollowRequest: "Richieste di follow in arrivo" followRequestAccepted: "Richieste di follow accettate" roleAssigned: "Ruolo concesso" @@ -2651,7 +2679,7 @@ _notification: achievementEarned: "Risultato raggiunto" exportCompleted: "Esportazione completata" login: "Accessi" - createToken: "Creare un token di accesso" + createToken: "Aggiunto un token di accesso" test: "Notifiche di test" app: "Notifiche da applicazioni" _actions: @@ -2743,56 +2771,56 @@ _abuseReport: notifiedWebhook: "Webhook da usare" deleteConfirm: "Vuoi davvero rimuovere il destinatario della notifica?" _moderationLogTypes: - createRole: "Ruolo creato" - deleteRole: "Ruolo eliminato" - updateRole: "Ruolo aggiornato" - assignRole: "Ruolo assegnato" - unassignRole: "Ruolo disassegnato" - suspend: "Sospensione" - unsuspend: "Sospensione rimossa" - addCustomEmoji: "Emoji personalizzata aggiunta" - updateCustomEmoji: "Emoji personalizzata aggiornata" - deleteCustomEmoji: "Emoji personalizzata eliminata" - updateServerSettings: "Impostazioni del server aggiornate" - updateUserNote: "Promemoria di moderazione aggiornato" - deleteDriveFile: "File da Drive eliminato" - deleteNote: "Nota eliminata" - createGlobalAnnouncement: "Annuncio globale creato" - createUserAnnouncement: "Annuncio ai profili iscritti creato" - updateGlobalAnnouncement: "Annuncio globale aggiornato" - updateUserAnnouncement: "Annuncio ai profili iscritti aggiornato" - deleteGlobalAnnouncement: "Annuncio globale eliminato" - deleteUserAnnouncement: "Annuncio ai profili iscritti eliminato" - resetPassword: "Password azzerata" - suspendRemoteInstance: "Istanza remota sospesa" - unsuspendRemoteInstance: "Istanza remota riattivata" - updateRemoteInstanceNote: "Aggiornamento del promemoria di moderazione per il server remoto" - markSensitiveDriveFile: "File nel Drive segnato come esplicito" - unmarkSensitiveDriveFile: "File nel Drive segnato come non esplicito" - resolveAbuseReport: "Segnalazione risolta" - forwardAbuseReport: "Segnalazione inoltrata" - updateAbuseReportNote: "Ha aggiornato la segnalazione" - createInvitation: "Genera codice di invito" - createAd: "Banner creato" - deleteAd: "Banner eliminato" - updateAd: "Banner aggiornato" - createAvatarDecoration: "Creazione decorazione della foto profilo" - updateAvatarDecoration: "Aggiornamento decorazione foto profilo" - deleteAvatarDecoration: "Eliminazione decorazione della foto profilo" - unsetUserAvatar: "Rimossa foto profilo" - unsetUserBanner: "Rimossa intestazione profilo" - createSystemWebhook: "Crea un SystemWebhook" - updateSystemWebhook: "Modifica SystemWebhook" - deleteSystemWebhook: "Elimina SystemWebhook" + createRole: "Crea un Ruolo" + deleteRole: "Elimina un Ruolo" + updateRole: "Modifica un ruolo" + assignRole: "Assegna un Ruolo" + unassignRole: "Toglie un Ruolo al Profilo" + suspend: "Sospende" + unsuspend: "Solleva la sospensione" + addCustomEmoji: "Aggiunge Emoji personalizzata" + updateCustomEmoji: "Modifica Emoji personalizzata" + deleteCustomEmoji: "Elimina Emoji personalizzata" + updateServerSettings: "Modifica le impostazioni del server" + updateUserNote: "Modifica un promemoria di moderazione" + deleteDriveFile: "Elimina un file dal Drive" + deleteNote: "Elimina una Nota" + createGlobalAnnouncement: "Crea un annuncio globale" + createUserAnnouncement: "Crea un annuncio ai profili già iscritti" + updateGlobalAnnouncement: "Modifica un annuncio globale" + updateUserAnnouncement: "Modifica un annuncio ai profili già iscritti" + deleteGlobalAnnouncement: "Elimina un annuncio globale" + deleteUserAnnouncement: "Elimina un annuncio ai profili già iscritti" + resetPassword: "Azzera la password" + suspendRemoteInstance: "Sospende una istanza remota" + unsuspendRemoteInstance: "Riattiva una istanza remota" + updateRemoteInstanceNote: "Modifica il promemoria di moderazione per il server remoto" + markSensitiveDriveFile: "Aggiunge NSFW a un file nel Drive" + unmarkSensitiveDriveFile: "Toglie NSFW da un file nel Drive" + resolveAbuseReport: "Risolve una segnalazione" + forwardAbuseReport: "Inoltra una segnalazione" + updateAbuseReportNote: "Modifica una segnalazione" + createInvitation: "Genera un codice di invito" + createAd: "Aggiunge un Banner" + deleteAd: "Elimina un Banner" + updateAd: "Modifica un Banner" + createAvatarDecoration: "Crea una decorazione della foto profilo" + updateAvatarDecoration: "Modifica una decorazione della foto profilo" + deleteAvatarDecoration: "Elimina una decorazione della foto profilo" + unsetUserAvatar: "Toglie una foto profilo" + unsetUserBanner: "Toglie una immagine di intestazione profilo" + createSystemWebhook: "Aggiunge un System Webhook" + updateSystemWebhook: "Modifica un System Webhook" + deleteSystemWebhook: "Elimina un System Webhook" createAbuseReportNotificationRecipient: "Crea destinatario per le notifiche di segnalazioni" - updateAbuseReportNotificationRecipient: "Aggiorna destinatario notifiche di segnalazioni" - deleteAbuseReportNotificationRecipient: "Elimina destinatario notifiche di segnalazioni" - deleteAccount: "Quando viene eliminato un profilo" - deletePage: "Pagina eliminata" - deleteFlash: "Play eliminato" - deleteGalleryPost: "Eliminazione pubblicazione nella Galleria" - deleteChatRoom: "Elimina chat" - updateProxyAccountDescription: "Aggiornata la descrizione del profilo proxy" + updateAbuseReportNotificationRecipient: "Modifica un destinatario per le notifiche di segnalazioni" + deleteAbuseReportNotificationRecipient: "Elimina un destinatario per le notifiche di segnalazioni" + deleteAccount: "Elimina un profilo" + deletePage: "Elimina una Pagina" + deleteFlash: "Elimina un Play" + deleteGalleryPost: "Elimina pubblicazione nella Galleria" + deleteChatRoom: "Elimina una Chat" + updateProxyAccountDescription: "Aggiorna la descrizione del profilo proxy" _fileViewer: title: "Dettagli del file" type: "Tipo di file" @@ -2800,6 +2828,7 @@ _fileViewer: url: "URL" uploadedAt: "Caricato il" attachedNotes: "Note a cui è allegato" + usage: "In uso" thisPageCanBeSeenFromTheAuthor: "Questa pagina può essere vista solo da chi ha caricato il file." _externalResourceInstaller: title: "Installa da sito esterno" @@ -3052,6 +3081,7 @@ _bootErrors: otherOption1: "Nelle impostazioni, cancellare le impostazioni del client e svuotare la cache" otherOption2: "Avviare il client predefinito" otherOption3: "Avviare lo strumento di riparazione" + otherOption4: "Avvia Misskey in modalità sicura" _search: searchScopeAll: "Tutte" searchScopeLocal: "Locale" @@ -3088,6 +3118,8 @@ _serverSetupWizard: doYouConnectToFediverse_description1: "Collegandosi a una rete di server distribuiti, denominata Fediverso, potrai scambiare contenuti con altri server, tramite il protocollo di comunicazione ActivityPub." doYouConnectToFediverse_description2: "Connettersi al Fediverso è anche detto \"federazione\"." youCanConfigureMoreFederationSettingsLater: "Puoi svolgere la configurazione avanzata anche dopo. Ad esempio specificando quali server possono federarsi." + remoteContentsCleaning: "Pulizia automatica dei contenuti in arrivo" + remoteContentsCleaning_description: "Con la federazione funzionante, riceverai sempre più contenuti. Abilitando la pulizia automatica, i contenuti non referenziati e obsoleti verranno rimossi automaticamente dai tuoi server, risparmiando spazio di archiviazione." adminInfo: "Informazioni sull'amministratore" adminInfo_description: "Imposta le informazioni dell'amministratore utilizzate per accettare le richieste." adminInfo_mustBeFilled: "Questa operazione è necessaria su un server aperto o se è attiva la federazione." @@ -3103,6 +3135,7 @@ _serverSetupWizard: text2: "Se puoi, ti preghiamo di prendere in considerazione l'idea di fare una donazione, così potremo continuare a sviluppare." text3: "Sono previsti anche dei vantaggi speciali per i sostenitori!" _uploader: + editImage: "Modifica immagine" compressedToX: "Compresso in {x}" savedXPercent: "{x}% risparmiati" abortConfirm: "Alcuni file non sono stati caricati. Vuoi annullare l'operazione?" @@ -3139,10 +3172,10 @@ _watermarkEditor: type: "Tipo" image: "Immagini" advanced: "Avanzato" + angle: "Angolo" stripe: "Strisce" stripeWidth: "Larghezza della linea" stripeFrequency: "Il numero di linee" - angle: "Angolo" polkadot: "A pallini" checker: "revisore" polkadotMainDotOpacity: "Opacità del punto principale" @@ -3154,6 +3187,7 @@ _imageEffector: title: "Effetto" addEffect: "Aggiungi effetto" discardChangesConfirm: "Scarta le modifiche ed esci?" + nothingToConfigure: "Nessuna impostazione configurabile." _fxs: chromaticAberration: "Aberrazione cromatica" glitch: "Glitch" @@ -3169,5 +3203,52 @@ _imageEffector: stripe: "Strisce" polkadot: "A pallini" checker: "revisore" + blockNoise: "Attenua rumore" + tearing: "Strappa immagine" + _fxProps: + angle: "Angolo" + scale: "Dimensioni" + size: "Dimensioni" + color: "Colore" + opacity: "Opacità" + normalize: "Normalizza" + amount: "Quantità" + lightness: "Chiaro" + contrast: "Contrasto" + hue: "Tinta" + brightness: "Luminosità" + saturation: "Saturazione" + max: "Valore massimo" + min: "Valore minimo" + direction: "Orientamento" + phase: "Fasare" + frequency: "Frequenza" + strength: "Forza" + glitchChannelShift: "Glitch cambio canale" + seed: "Seme" + redComponent: "Rosso composito" + greenComponent: "Verde composito" + blueComponent: "Blu composito" + threshold: "Soglia" + centerX: "Centro orizzontale" + centerY: "Centro verticale" + zoomLinesSmoothing: "Levigatura" + zoomLinesSmoothingDescription: "Non si possono usare insieme la levigatura e la larghezza della linea centrale." + zoomLinesThreshold: "Limite delle linee zoom" + zoomLinesMaskSize: "Ampiezza del diametro" + zoomLinesBlack: "Bande nere" +drafts: "Bozze" _drafts: + select: "Selezionare bozza" + cannotCreateDraftAnymore: "Hai superato il numero massimo di bozze ammissibili." + cannotCreateDraft: "Impossibile creare una bozza di questo contenuto." + delete: "Elimina bozza" + deleteAreYouSure: "Vuoi davvero eliminare la bozza?" + noDrafts: "Non c'è nessuna bozza." + replyTo: "Rispondere a {user}" + quoteOf: "Citare la nota di {user}" + postTo: "Inserire in {channel}" + saveToDraft: "Salva come bozza" + restoreFromDraft: "Recuperare dalle bozze" restore: "Ripristina" + listDrafts: "Elenco bozze" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index dea1c4e4fa..c0e598ef7b 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -74,8 +74,8 @@ youGotNewFollower: "フォローされました" receiveFollowRequest: "フォローリクエストされました" followRequestAccepted: "フォローが承認されました" mention: "メンション" -mentions: "あなた宛て" -directNotes: "ダイレクト投稿" +mentions: "メンション" +directNotes: "指名" importAndExport: "インポートとエクスポート" import: "インポート" export: "エクスポート" @@ -253,6 +253,7 @@ noteDeleteConfirm: "このノートを削除しますか?" pinLimitExceeded: "これ以上ピン留めできません" done: "完了" processing: "処理中" +preprocessing: "準備中" preview: "プレビュー" default: "デフォルト" defaultValueIs: "デフォルト: {value}" @@ -302,7 +303,7 @@ uploadNFiles: "{n}個のファイルをアップロード" explore: "みつける" messageRead: "既読" noMoreHistory: "これより過去の履歴はありません" -startChat: "チャットを始める" +startChat: "メッセージを送る" nUsersRead: "{n}人が読みました" agreeTo: "{0}に同意" agree: "同意する" @@ -477,7 +478,7 @@ notFoundDescription: "指定されたURLに該当するページはありませ uploadFolder: "既定アップロード先" markAsReadAllNotifications: "すべての通知を既読にする" markAsReadAllUnreadNotes: "すべての投稿を既読にする" -markAsReadAllTalkMessages: "すべてのチャットを既読にする" +markAsReadAllTalkMessages: "すべてのダイレクトメッセージを既読にする" help: "ヘルプ" inputMessageHere: "ここにメッセージを入力" close: "閉じる" @@ -637,8 +638,8 @@ addRelay: "リレーの追加" inboxUrl: "inboxのURL" addedRelays: "追加済みのリレー" serviceworkerInfo: "プッシュ通知を行うには有効にする必要があります。" -deletedNote: "削除された投稿" -invisibleNote: "非公開の投稿" +deletedNote: "削除されたノート" +invisibleNote: "非公開のノート" enableInfiniteScroll: "自動でもっと見る" visibility: "公開範囲" poll: "アンケート" @@ -1054,6 +1055,7 @@ permissionDeniedError: "操作が拒否されました" permissionDeniedErrorDescription: "このアカウントにはこの操作を行うための権限がありません。" preset: "プリセット" selectFromPresets: "プリセットから選択" +custom: "カスタム" achievements: "実績" gotInvalidResponseError: "サーバーの応答が無効です" gotInvalidResponseErrorDescription: "サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから再度お試しください。" @@ -1092,6 +1094,7 @@ prohibitedWordsDescription2: "スペースで区切るとAND指定になり、 hiddenTags: "非表示ハッシュタグ" hiddenTagsDescription: "設定したタグをトレンドに表示させないようにします。改行で区切って複数設定できます。" notesSearchNotAvailable: "ノート検索は利用できません。" +usersSearchNotAvailable: "ユーザー検索は利用できません。" license: "ライセンス" unfavoriteConfirm: "お気に入り解除しますか?" myClips: "自分のクリップ" @@ -1243,7 +1246,7 @@ releaseToRefresh: "離してリロード" refreshing: "リロード中" pullDownToRefresh: "引っ張ってリロード" useGroupedNotifications: "通知をグルーピング" -signupPendingError: "メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。" +emailVerificationFailedError: "メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。" cwNotationRequired: "「内容を隠す」がオンの場合は注釈の記述が必要です。" doReaction: "リアクションする" code: "コード" @@ -1303,7 +1306,7 @@ messageToFollower: "フォロワーへのメッセージ" target: "対象" testCaptchaWarning: "CAPTCHAのテストを目的とした機能です。本番環境で使用しないでください。" prohibitedWordsForNameOfUser: "禁止ワード(ユーザーの名前)" -prohibitedWordsForNameOfUserDescription: "このリストに含まれる文字列がユーザーの名前に含まれる場合、ユーザーの名前の変更を拒否します。モデレーター権限を持つユーザーはこの制限の影響を受けません。" +prohibitedWordsForNameOfUserDescription: "このリストに含まれる文字列がユーザーの名前に含まれる場合、ユーザーの名前の変更を拒否します。モデレーター権限を持つユーザーはこの制限の影響を受けません。ユーザー名(username)に対しても全て小文字に置き換えて検査します。" yourNameContainsProhibitedWords: "変更しようとした名前に禁止された文字列が含まれています" yourNameContainsProhibitedWordsDescription: "名前に禁止されている文字列が含まれています。この名前を使用したい場合は、サーバー管理者にお問い合わせください。" thisContentsAreMarkedAsSigninRequiredByAuthor: "投稿者により、表示にはログインが必要と設定されています" @@ -1341,6 +1344,8 @@ postForm: "投稿フォーム" textCount: "文字数" information: "情報" chat: "チャット" +directMessage: "ダイレクトメッセージ" +directMessage_short: "メッセージ" migrateOldSettings: "旧設定情報を移行" migrateOldSettings_description: "通常これは自動で行われていますが、何らかの理由により上手く移行されなかった場合は手動で移行処理をトリガーできます。現在の設定情報は上書きされます。" compress: "圧縮" @@ -1368,53 +1373,74 @@ redisplayAllTips: "全ての「ヒントとコツ」を再表示" hideAllTips: "全ての「ヒントとコツ」を非表示" defaultImageCompressionLevel: "デフォルトの画像圧縮度" defaultImageCompressionLevel_description: "低くすると画質を保てますが、ファイルサイズは増加します。
高くするとファイルサイズを減らせますが、画質は低下します。" +defaultCompressionLevel: "デフォルトの圧縮度" +defaultCompressionLevel_description: "低くすると品質を保てますが、ファイルサイズは増加します。
高くするとファイルサイズを減らせますが、品質は低下します。" +inMinutes: "分" +inDays: "日" +safeModeEnabled: "セーフモードが有効です" +pluginsAreDisabledBecauseSafeMode: "セーフモードが有効なため、プラグインはすべて無効化されています。" +customCssIsDisabledBecauseSafeMode: "セーフモードが有効なため、カスタムCSSは適用されていません。" +themeIsDefaultBecauseSafeMode: "セーフモードが有効な間はデフォルトのテーマが使用されます。セーフモードをオフにすると元に戻ります。" +thankYouForTestingBeta: "ベータ版の検証にご協力いただきありがとうございます!" +createUserSpecifiedNote: "ユーザー指定ノートを作成" + +_compression: + _quality: + high: "高品質" + medium: "中品質" + low: "低品質" + _size: + large: "サイズ大" + medium: "サイズ中" + small: "サイズ小" _order: newest: "新しい順" oldest: "古い順" _chat: + messages: "メッセージ" noMessagesYet: "まだメッセージはありません" newMessage: "新しいメッセージ" - individualChat: "個人チャット" - individualChat_description: "特定ユーザーとの一対一のチャットができます。" - roomChat: "ルームチャット" - roomChat_description: "複数人でのチャットができます。\nまた、個人チャットを許可していないユーザーとでも、相手が受け入れればチャットができます。" - createRoom: "ルームを作成" - inviteUserToChat: "ユーザーを招待してチャットを始めましょう" - yourRooms: "作成したルーム" - joiningRooms: "参加中のルーム" + individualChat: "個別" + individualChat_description: "特定ユーザーと個別にメッセージのやりとりができます。" + roomChat: "グループ" + roomChat_description: "複数人でメッセージのやりとりができます。\nまた、個別のメッセージを許可していないユーザーとでも、相手が受け入れればやりとりできます。" + createRoom: "グループを作成" + inviteUserToChat: "ユーザーを招待してメッセージを送信しましょう" + yourRooms: "作成したグループ" + joiningRooms: "参加中のグループ" invitations: "招待" noInvitations: "招待はありません" history: "履歴" noHistory: "履歴はありません" - noRooms: "ルームはありません" + noRooms: "グループはありません" inviteUser: "ユーザーを招待" sentInvitations: "送信した招待" join: "参加" ignore: "無視" - leave: "ルームから退出" + leave: "グループから退出" members: "メンバー" searchMessages: "メッセージを検索" home: "ホーム" send: "送信" newline: "改行" - muteThisRoom: "このルームをミュート" - deleteRoom: "ルームを削除" - chatNotAvailableForThisAccountOrServer: "このサーバー、またはこのアカウントでチャットは有効化されていません。" - chatIsReadOnlyForThisAccountOrServer: "このサーバー、またはこのアカウントでチャットは読み取り専用となっています。新たに書き込んだり、チャットルームを作成・参加したりすることはできません。" - chatNotAvailableInOtherAccount: "相手のアカウントでチャット機能が使えない状態になっています。" - cannotChatWithTheUser: "このユーザーとのチャットを開始できません" - cannotChatWithTheUser_description: "チャットが使えない状態になっているか、相手がチャットを開放していません。" - youAreNotAMemberOfThisRoomButInvited: "あなたはこのルームの参加者ではありませんが、招待が届いています。参加するには、招待を承認してください。" + muteThisRoom: "このグループをミュート" + deleteRoom: "グループを削除" + chatNotAvailableForThisAccountOrServer: "このサーバー、またはこのアカウントでダイレクトメッセージは有効化されていません。" + chatIsReadOnlyForThisAccountOrServer: "このサーバー、またはこのアカウントでダイレクトメッセージは読み取り専用となっています。新たに書き込んだり、グループを作成・参加したりすることはできません。" + chatNotAvailableInOtherAccount: "相手のアカウントでダイレクトメッセージが使えない状態になっています。" + cannotChatWithTheUser: "このユーザーとのダイレクトメッセージを開始できません" + cannotChatWithTheUser_description: "ダイレクトメッセージが使えない状態になっているか、相手がダイレクトメッセージを開放していません。" + youAreNotAMemberOfThisRoomButInvited: "あなたはこのグループの参加者ではありませんが、招待が届いています。参加するには、招待を承認してください。" doYouAcceptInvitation: "招待を承認しますか?" - chatWithThisUser: "チャットする" - thisUserAllowsChatOnlyFromFollowers: "このユーザーはフォロワーからのみチャットを受け付けています。" - thisUserAllowsChatOnlyFromFollowing: "このユーザーは、このユーザーがフォローしているユーザーからのみチャットを受け付けています。" - thisUserAllowsChatOnlyFromMutualFollowing: "このユーザーは相互フォローのユーザーからのみチャットを受け付けています。" - thisUserNotAllowedChatAnyone: "このユーザーは誰からもチャットを受け付けていません。" - chatAllowedUsers: "チャットを許可する相手" - chatAllowedUsers_note: "自分からチャットメッセージを送った相手とはこの設定に関わらずチャットが可能です。" + chatWithThisUser: "ダイレクトメッセージ" + thisUserAllowsChatOnlyFromFollowers: "このユーザーはフォロワーからのみメッセージを受け付けています。" + thisUserAllowsChatOnlyFromFollowing: "このユーザーは、このユーザーがフォローしているユーザーからのみメッセージを受け付けています。" + thisUserAllowsChatOnlyFromMutualFollowing: "このユーザーは相互フォローのユーザーからのみメッセージを受け付けています。" + thisUserNotAllowedChatAnyone: "このユーザーは誰からもメッセージを受け付けていません。" + chatAllowedUsers: "メッセージを許可する相手" + chatAllowedUsers_note: "自分からメッセージを送った相手とはこの設定に関わらずメッセージの送受信が可能です。" _chatAllowedUsers: everyone: "誰でも" followers: "自分のフォロワーのみ" @@ -1463,6 +1489,7 @@ _settings: contentsUpdateFrequency_description2: "リアルタイムモードがオンのときは、この設定に関わらずリアルタイムでコンテンツが更新されます。" showUrlPreview: "URLプレビューを表示する" showAvailableReactionsFirstInNote: "利用できるリアクションを先頭に表示" + showPageTabBarBottom: "ページのタブバーを下部に表示" _chat: showSenderName: "送信者の名前を表示" @@ -1603,9 +1630,9 @@ _initialTutorial: public: "すべてのユーザーに公開。" home: "ホームタイムラインのみに公開。フォロワー・プロフィールを見に来た人・リノートから、他のユーザーも見ることができます。" followers: "フォロワーにのみ公開。本人以外がリノートすることはできず、またフォロワー以外は閲覧できません。" - direct: "指定したユーザーにのみ公開され、また相手に通知が入ります。ダイレクトメッセージのかわりにお使いいただけます。" + direct: "指定したユーザーにのみ公開され、また相手に通知が入ります。" doNotSendConfidencialOnDirect1: "機密情報は送信する際は注意してください。" - doNotSendConfidencialOnDirect2: "送信先のサーバーの管理者は投稿内容を見ることが可能なので、信頼できないサーバーのユーザーにダイレクト投稿を送信する場合は、機密情報の扱いに注意が必要です。" + doNotSendConfidencialOnDirect2: "送信先のサーバーの管理者は投稿内容を見ることが可能なので、信頼できないサーバーのユーザーが含まれる限定公開のノートを作成する際は、機密情報の扱いに注意が必要です。" localOnly: "他のサーバーに投稿を連合しません。上記の公開範囲に関わらず、他のサーバーのユーザーは、この設定がついたノートを直接閲覧することができなくなります。" _cw: title: "内容を隠す(CW)" @@ -1649,6 +1676,10 @@ _serverSettings: fanoutTimelineDbFallback: "データベースへのフォールバック" fanoutTimelineDbFallbackDescription: "有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。" reactionsBufferingDescription: "有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。" + remoteNotesCleaning: "リモート投稿の自動クリーニング" + remoteNotesCleaning_description: "有効にすると、一定期間経過したリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。" + remoteNotesCleaningMaxProcessingDuration: "最大クリーニング処理継続時間" + remoteNotesCleaningExpiryDaysForEachNotes: "最低ノート保持日数" inquiryUrl: "問い合わせ先URL" inquiryUrlDescription: "サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定します。" openRegistration: "アカウントの作成をオープンにする" @@ -1667,6 +1698,11 @@ _serverSettings: userGeneratedContentsVisibilityForVisitor: "非利用者に対するユーザー作成コンテンツの公開範囲" userGeneratedContentsVisibilityForVisitor_description: "モデレーションが行き届きにくい不適切なリモートコンテンツなどが、自サーバー経由で図らずもインターネットに公開されてしまうことによるトラブル防止などに役立ちます。" userGeneratedContentsVisibilityForVisitor_description2: "サーバーで受信したリモートのコンテンツを含め、サーバー内の全てのコンテンツを無条件でインターネットに公開することはリスクが伴います。特に、分散型の特性を知らない閲覧者にとっては、リモートのコンテンツであってもサーバー内で作成されたコンテンツであると誤って認識してしまう可能性があるため、注意が必要です。" + restartServerSetupWizardConfirm_title: "サーバーの初期設定ウィザードをやり直しますか?" + restartServerSetupWizardConfirm_text: "現在の一部の設定はリセットされます。" + entrancePageStyle: "エントランスページのスタイル" + showTimelineForVisitor: "タイムラインを表示する" + showActivitiesForVisitor: "アクティビティを表示する" _userGeneratedContentsVisibilityForVisitor: all: "全て公開" @@ -2007,6 +2043,7 @@ _role: descriptionOfRateLimitFactor: "小さいほど制限が緩和され、大きいほど制限が強化されます。" canHideAds: "広告の非表示" canSearchNotes: "ノート検索の利用" + canSearchUsers: "ユーザー検索の利用" canUseTranslator: "翻訳機能の利用" avatarDecorationLimit: "アイコンデコレーションの最大取付個数" canImportAntennas: "アンテナのインポートを許可" @@ -2014,11 +2051,12 @@ _role: canImportFollowing: "フォローのインポートを許可" canImportMuting: "ミュートのインポートを許可" canImportUserLists: "リストのインポートを許可" - chatAvailability: "チャットを許可" + chatAvailability: "ダイレクトメッセージを許可" uploadableFileTypes: "アップロード可能なファイル種別" uploadableFileTypes_caption: "MIMEタイプを指定します。改行で区切って複数指定できるほか、アスタリスク(*)でワイルドカード指定できます。(例: image/*)" uploadableFileTypes_caption2: "ファイルによっては種別を判定できないことがあります。そのようなファイルを許可する場合は {x} を指定に追加してください。" noteDraftLimit: "サーバーサイドのノートの下書きの作成可能数" + watermarkAvailable: "ウォーターマーク機能の使用可否" _condition: roleAssignedTo: "マニュアルロールにアサイン済み" isLocal: "ローカルユーザー" @@ -2260,7 +2298,7 @@ _theme: buttonHoverBg: "ボタンの背景 (ホバー)" inputBorder: "入力ボックスの縁取り" badge: "バッジ" - messageBg: "チャットの背景" + messageBg: "メッセージの背景" fgHighlighted: "強調された文字" _sfx: @@ -2268,7 +2306,7 @@ _sfx: noteMy: "ノート(自分)" notification: "通知" reaction: "リアクション選択時" - chatMessage: "チャットのメッセージ" + chatMessage: "ダイレクトメッセージ" _soundSettings: driveFile: "ドライブの音声を使用" @@ -2305,6 +2343,7 @@ _time: minute: "分" hour: "時間" day: "日" + month: "ヶ月" _2fa: alreadyRegistered: "既に設定は完了しています。" @@ -2347,8 +2386,8 @@ _permissions: "write:favorites": "お気に入りを操作する" "read:following": "フォローの情報を見る" "write:following": "フォロー・フォロー解除する" - "read:messaging": "チャットを見る" - "write:messaging": "チャットを操作する" + "read:messaging": "ダイレクトメッセージを見る" + "write:messaging": "ダイレクトメッセージを操作する" "read:mutes": "ミュートを見る" "write:mutes": "ミュートを操作する" "write:notes": "ノートを作成・削除する" @@ -2421,8 +2460,8 @@ _permissions: "read:clip-favorite": "クリップのいいねを見る" "read:federation": "連合に関する情報を取得する" "write:report-abuse": "違反を報告する" - "write:chat": "チャットを操作する" - "read:chat": "チャットを閲覧する" + "write:chat": "ダイレクトメッセージを操作する" + "read:chat": "ダイレクトメッセージを閲覧する" _auth: shareAccessTitle: "アプリへのアクセス許可" @@ -2485,7 +2524,7 @@ _widgets: chooseList: "リストを選択" clicker: "クリッカー" birthdayFollowings: "今日誕生日のユーザー" - chat: "チャット" + chat: "ダイレクトメッセージ" _cw: hide: "隠す" @@ -2523,7 +2562,7 @@ _visibility: homeDescription: "ホームタイムラインのみに公開" followers: "フォロワー" followersDescription: "自分のフォロワーのみに公開" - specified: "ダイレクト" + specified: "指名" specifiedDescription: "指定したユーザーのみに公開" disableFederation: "連合なし" disableFederationDescription: "他サーバーへの配信を行いません" @@ -2692,7 +2731,7 @@ _notification: newNote: "新しい投稿" unreadAntennaNote: "アンテナ {name}" roleAssigned: "ロールが付与されました" - chatRoomInvitationReceived: "チャットルームへ招待されました" + chatRoomInvitationReceived: "ダイレクトメッセージのグループへ招待されました" emptyPushNotificationMessage: "プッシュ通知の更新をしました" achievementEarned: "実績を獲得" testNotification: "通知テスト" @@ -2722,7 +2761,7 @@ _notification: receiveFollowRequest: "フォロー申請を受け取った" followRequestAccepted: "フォローが受理された" roleAssigned: "ロールが付与された" - chatRoomInvitationReceived: "チャットルームへ招待された" + chatRoomInvitationReceived: "ダイレクトメッセージのグループへ招待された" achievementEarned: "実績の獲得" exportCompleted: "エクスポートが完了した" login: "ログイン" @@ -2769,10 +2808,10 @@ _deck: antenna: "アンテナ" list: "リスト" channel: "チャンネル" - mentions: "あなた宛て" - direct: "ダイレクト" + mentions: "メンション" + direct: "指名" roleTimeline: "ロールタイムライン" - chat: "チャット" + chat: "ダイレクトメッセージ" _dialog: charactersExceeded: "最大文字数を超えています! 現在 {current} / 制限 {max}" @@ -2875,7 +2914,7 @@ _moderationLogTypes: deletePage: "ページを削除" deleteFlash: "Playを削除" deleteGalleryPost: "ギャラリーの投稿を削除" - deleteChatRoom: "チャットルームを削除" + deleteChatRoom: "ダイレクトメッセージのグループを削除" updateProxyAccountDescription: "プロキシアカウントの説明を更新" _fileViewer: @@ -2885,6 +2924,7 @@ _fileViewer: url: "URL" uploadedAt: "追加日" attachedNotes: "添付されているノート" + usage: "利用" thisPageCanBeSeenFromTheAuthor: "このページは、このファイルをアップロードしたユーザーしか閲覧できません。" _externalResourceInstaller: @@ -3154,6 +3194,7 @@ _bootErrors: otherOption1: "クライアント設定とキャッシュを削除" otherOption2: "簡易クライアントを起動" otherOption3: "修復ツールを起動" + otherOption4: "Misskeyをセーフモードで起動" _search: searchScopeAll: "全て" @@ -3192,6 +3233,8 @@ _serverSetupWizard: doYouConnectToFediverse_description1: "分散型サーバーで構成されるネットワーク(Fediverse)に接続すると、他のサーバーと相互にコンテンツのやり取りが可能です。" doYouConnectToFediverse_description2: "Fediverseと接続することは「連合」とも呼ばれます。" youCanConfigureMoreFederationSettingsLater: "連合可能なサーバーの指定など、高度な設定も後ほど可能です。" + remoteContentsCleaning: "リモートコンテンツの自動クリーニング" + remoteContentsCleaning_description: "連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、一定期間経過したリモートコンテンツを自動でサーバーから削除し、ストレージを節約できます。" adminInfo: "管理者情報" adminInfo_description: "問い合わせを受け付けるために使用される管理者情報を設定します。" adminInfo_mustBeFilled: "オープンサーバー、または連合がオンの場合は必ず入力が必要です。" @@ -3245,14 +3288,16 @@ _watermarkEditor: opacity: "不透明度" scale: "サイズ" text: "テキスト" + qr: "二次元コード" position: "位置" + margin: "マージン" type: "タイプ" image: "画像" advanced: "高度" + angle: "角度" stripe: "ストライプ" stripeWidth: "ラインの幅" stripeFrequency: "ラインの数" - angle: "角度" polkadot: "ポルカドット" checker: "チェッカー" polkadotMainDotOpacity: "メインドットの不透明度" @@ -3260,11 +3305,13 @@ _watermarkEditor: polkadotSubDotOpacity: "サブドットの不透明度" polkadotSubDotRadius: "サブドットの大きさ" polkadotSubDotDivisions: "サブドットの数" + leaveBlankToAccountUrl: "空欄にするとアカウントのURLになります" _imageEffector: title: "エフェクト" addEffect: "エフェクトを追加" discardChangesConfirm: "変更を破棄して終了しますか?" + nothingToConfigure: "設定項目はありません" _fxs: chromaticAberration: "色収差" @@ -3272,6 +3319,7 @@ _imageEffector: mirror: "ミラー" invert: "色の反転" grayscale: "白黒" + blur: "ぼかし" colorAdjust: "色調補正" colorClamp: "色の圧縮" colorClampAdvanced: "色の圧縮(高度)" @@ -3283,12 +3331,50 @@ _imageEffector: checker: "チェッカー" blockNoise: "ブロックノイズ" tearing: "ティアリング" + fill: "塗りつぶし" + + _fxProps: + angle: "角度" + scale: "サイズ" + size: "サイズ" + radius: "半径" + samples: "サンプル数" + offset: "位置" + color: "色" + opacity: "不透明度" + normalize: "正規化" + amount: "量" + lightness: "明るさ" + contrast: "コントラスト" + hue: "色相" + brightness: "輝度" + saturation: "彩度" + max: "最大値" + min: "最小値" + direction: "方向" + phase: "位相" + frequency: "頻度" + strength: "強さ" + glitchChannelShift: "ズレ" + seed: "シード値" + redComponent: "赤色成分" + greenComponent: "緑色成分" + blueComponent: "青色成分" + threshold: "しきい値" + centerX: "中心X" + centerY: "中心Y" + zoomLinesSmoothing: "スムージング" + zoomLinesSmoothingDescription: "スムージングと集中線の幅の設定は併用できません。" + zoomLinesThreshold: "集中線の幅" + zoomLinesMaskSize: "中心径" + zoomLinesBlack: "黒色にする" + circle: "円形" drafts: "下書き" _drafts: select: "下書きを選択" cannotCreateDraftAnymore: "下書きの作成可能数を超えています。" - cannotCreateDraftOfRenote: "リノートの下書きは作成できません。" + cannotCreateDraft: "この内容では下書きを作成できません。" delete: "下書きを削除" deleteAreYouSure: "下書きを削除しますか?" noDrafts: "下書きはありません" @@ -3299,3 +3385,20 @@ _drafts: restoreFromDraft: "下書きから復元" restore: "復元" listDrafts: "下書き一覧" + +qr: "二次元コード" +_qr: + showTabTitle: "表示" + readTabTitle: "読み取る" + shareTitle: "{name} {acct}" + shareText: "Fediverseで私をフォローしてください!" + chooseCamera: "カメラを選択" + cannotToggleFlash: "ライト選択不可" + turnOnFlash: "ライトをオンにする" + turnOffFlash: "ライトをオフにする" + startQr: "コードリーダーを再開" + stopQr: "コードリーダーを停止" + noQrCodeFound: "QRコードが見つかりません" + scanFile: "端末の画像をスキャン" + raw: "テキスト" + mfm: "MFM" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index 92f849efbb..d44ed1c3fc 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -300,6 +300,7 @@ uploadFromUrlMayTakeTime: "アップロード終わるんにちょい時間か explore: "みつける" messageRead: "もう読んだ" noMoreHistory: "これより昔のんはあらへんで" +startChat: "チャットを始めよか" nUsersRead: "{n}人が読んでもうた" agreeTo: "{0}に同意したで" agree: "せやな" @@ -324,6 +325,7 @@ dark: "ダーク" lightThemes: "デイゲーム" darkThemes: "ナイトゲーム" syncDeviceDarkMode: "デバイスのダークモードと一緒にする" +switchDarkModeManuallyWhenSyncEnabledConfirm: "「{x}」がオンになってるで。同期をオフにして手動でモードを切り替えることにします?" drive: "ドライブ" fileName: "ファイル名" selectFile: "ファイル選んでや" @@ -422,6 +424,7 @@ antennaExcludeBots: "Botアカウントを除外" antennaKeywordsDescription: "スペースで区切ったるとAND指定で、改行で区切ったるとOR指定や" notifyAntenna: "新しいノートを通知すんで" withFileAntenna: "なんか添付されたノートだけ" +excludeNotesInSensitiveChannel: "センシティブなチャンネルのノートは入れんとくわ" enableServiceworker: "ブラウザにプッシュ通知が行くようにする" antennaUsersDescription: "ユーザー名を改行で区切ったってな" caseSensitive: "大文字と小文字は別もんや" @@ -693,6 +696,7 @@ userSaysSomethingAbout: "{name}が「{word}」についてなんか言うてた makeActive: "使うで" display: "表示" copy: "コピー" +copiedToClipboard: "クリップボードにコピーされたで" metrics: "メトリクス" overview: "概要" logs: "ログ" @@ -787,6 +791,7 @@ wide: "広い" narrow: "狭い" reloadToApplySetting: "設定はページリロード後に反映されるで。今リロードしとくか?" needReloadToApply: "反映には再起動せなあかんで" +needToRestartServerToApply: "反映にはサーバーを再起動せなあかんのよ。" showTitlebar: "タイトルバーを見せる" clearCache: "キャッシュをほかす" onlineUsersCount: "{n}人が起きとるで" @@ -974,6 +979,7 @@ document: "ドキュメント" numberOfPageCache: "ページ、どんだけキャッシュすんの?" numberOfPageCacheDescription: "増やすと使いやすくなるけど、負荷とメモリ使用量が増えてくで。一長一短やな。" logoutConfirm: "ログアウトしまっか?" +logoutWillClearClientData: "ログアウトするとクライアントの設定情報がブラウザから消されてまうで。再ログイン時に設定情報を復元できるようにするためには、設定の自動バックアップを有効にするとええで。" lastActiveDate: "最後に使った日時" statusbar: "ステータスバー" pleaseSelect: "選んだってやー" @@ -992,6 +998,7 @@ failedToUpload: "アップロードに失敗してもうたわ…" cannotUploadBecauseInappropriate: "きわどい内容を含むかもしれへんって言われたからアップロードできへんわ。" cannotUploadBecauseNoFreeSpace: "ドライブがもうパンパンやからアップロードできへんわ。" cannotUploadBecauseExceedsFileSizeLimit: "ファイルが思うたよりも大きいさかいアップロードできへんでこれ。" +cannotUploadBecauseUnallowedFileType: "許可されてへんファイル種別やからアップロードできへんっぽい。" beta: "ベータ" enableAutoSensitive: "自動できわどいか判断する" enableAutoSensitiveDescription: "使える時は、機械学習を使って自動でメディアにNSFWフラグを設定するで。この機能をオフにしても、サーバーによっては自動で設定されることがあるで。" @@ -1232,7 +1239,6 @@ releaseToRefresh: "離したらリロード" refreshing: "リロードしとる" pullDownToRefresh: "引っ張ってリロードするで" useGroupedNotifications: "通知をグループ分けして出すで" -signupPendingError: "メアド確認してたらなんか変なことなったわ。リンクの期限切れてるかもしれん。" cwNotationRequired: "「内容を隠す」んやったら注釈書かなアカンで。" doReaction: "ツッコむで" code: "コード" @@ -1304,16 +1310,97 @@ federationSpecified: "このサーバーはホワイトリスト連合で運用 federationDisabled: "このサーバーは連合が無効化されてるで。他のサーバーのユーザーとやり取りすることはできひんで。" confirmOnReact: "ツッコむときに確認とる" reactAreYouSure: "\" {emoji} \" でツッコむ?" +markAsSensitiveConfirm: "このメディアをきわどい扱いしときますか?" +unmarkAsSensitiveConfirm: "このメディアはやっぱきわどくなかったってことでええんか?" +noName: "名前はあらへんで" +preferenceSyncConflictTitle: "サーバーに設定値があるみたいやわ" +preferenceSyncConflictText: "同期が有効にされた設定項目は設定値をサーバーに保存するねんけど、この設定項目はサーバーに保存されたやつがあるみたいやわ。どないするん?" +preferenceSyncConflictChoiceMerge: "ガッチャンコしよか" +preferenceSyncConflictChoiceCancel: "同期の有効化はやめとくわ" postForm: "投稿フォーム" information: "情報" +migrateOldSettings: "旧設定情報をお引っ越し" +migrateOldSettings_description: "通常これは自動で行われるはずなんやけど、なんかの理由で上手く移行できへんかったときは手動で移行処理をポチっとできるで。今の設定情報は上書きされるで。" +settingsMigrating: "設定を移行しとるで。ちょっと待っとってな... (後で、設定→その他→旧設定情報を移行 で手動で移行することもできるで)" +driveAboutTip: "ドライブでは、今までアップロードしたファイルがずらーっと表示されるで。
\nノートにファイルをもっかいのっけたり、あとで投稿するファイルをその辺に置いとくこともできるねん。
\nファイルをほかすと、前にそのファイルをのっけた全部の場所(ノート、ページ、アバター、バナー等)からも見えんくなるから気いつけてな。
\nフォルダを作って整理することもできるで。" +turnItOn: "オンにしとこ" +turnItOff: "オフでええわ" +emojiUnmute: "絵文字ミュートやめたる" +unmuteX: "{x}のミュートやめたる" +redisplayAllTips: "全部の「ヒントとコツ」をもっかい見して" +hideAllTips: "「ヒントとコツ」は全部表示せんでええ" +defaultImageCompressionLevel_description: "低くすると画質は保てるんやけど、ファイルサイズが増えるで。
高くするとファイルサイズは減らせるんやけど、画質が落ちるで。" +inMinutes: "分" +inDays: "日" +safeModeEnabled: "セーフモードがオンになってるで" +pluginsAreDisabledBecauseSafeMode: "セーフモードがオンやから、プラグインは全部無効化されてるで。" +customCssIsDisabledBecauseSafeMode: "セーフモードがオンやから、カスタムCSSは適用されてへんで。" +themeIsDefaultBecauseSafeMode: "セーフモードがオンの間はデフォルトのテーマを使うで。セーフモードをオフにれば元に戻るで。" _chat: + noMessagesYet: "まだメッセージはあらへんで" + individualChat_description: "特定のユーザーと一対一でチャットができるで。" + roomChat_description: "複数人でチャットできるで。\nあと、個人チャットを許可してへんユーザーとでも、相手がええって言うならチャットできるで。" + inviteUserToChat: "ユーザーを招待してチャットを始めてみ" invitations: "来てや" + noInvitations: "招待はあらへんで" noHistory: "履歴はないわ。" + noRooms: "ルームはあらへんで" members: "メンバーはん" home: "ホーム" send: "送信" + deleteRoom: "ルームをほかす" + chatNotAvailableForThisAccountOrServer: "このサーバー、もしくはこのアカウントでチャットが有効にされてへんで。" + chatIsReadOnlyForThisAccountOrServer: "このサーバー、もしくはこのアカウントでチャットが読み取り専用になっとるわ。新しく書き込んだり、チャットルームを作ったり参加したりはできへんで。" + chatNotAvailableInOtherAccount: "相手のアカウントでチャット機能が使えんくなっとるみたいやわ。" + cannotChatWithTheUser: "このユーザーとのチャットを開始できへんみたいやわ" + cannotChatWithTheUser_description: "チャットが使えん状態になっとるか、相手がチャットを開放してへんみたいやわ。" + youAreNotAMemberOfThisRoomButInvited: "あんたはこのルームの参加者ちゃうけど、招待が届いとるで。参加するんやったら、招待を承認してな。" + doYouAcceptInvitation: "招待を承認してもええんか?" + chatWithThisUser: "チャットしよか" + thisUserAllowsChatOnlyFromFollowers: "このユーザーはフォロワーからのチャットしか受け付けとらんみたいやわ。" + thisUserAllowsChatOnlyFromFollowing: "このユーザーは、このユーザーがフォローしとるユーザーからのチャットしか受け付けとらんみたいやわ。" + thisUserAllowsChatOnlyFromMutualFollowing: "このユーザーは相互フォローのユーザーからのチャットしか受け付けとらんみたいやわ。" + thisUserNotAllowedChatAnyone: "このユーザーは誰からのチャットも受け付けとらんみたいやわ。" + chatAllowedUsers: "チャットしてもええ相手" + chatAllowedUsers_note: "自分からチャットメッセージを送った相手やったらこの設定に関わらずチャットできるで。" + _chatAllowedUsers: + followers: "自分のフォロワーだけ" + following: "自分がフォローしとるユーザーだけ" + mutual: "相互フォローのユーザーだけ" + none: "誰もかもあかん" +_emojiPalette: + enableSyncBetweenDevicesForPalettes: "パレットのデバイス間同期をつけとく" + paletteForMain: "メインで使うパレット" + paletteForReaction: "リアクションで使うパレット" _settings: + driveBanner: "ドライブの管理と設定、使用量の確認、ファイルをアップロードするときの設定ができるで。" + pluginBanner: "プラグインを使うとクライアントの機能を拡張できるねん。プラグインのインストール、個別の設定と管理ができるで。" + notificationsBanner: "サーバーから受け取る通知の種類とか範囲、プッシュ通知の設定ができるで。" webhook: "Webhook" + serviceConnectionBanner: "外部のアプリ・サービスと連携するのに使うとるアクセストークンとかWebhookの管理と設定ができるで。" + accountDataBanner: "アカウントデータのアーカイブをエクスポート/インポートして管理できるで。" + muteAndBlockBanner: "見せんでええコンテンツの設定とか、特定のユーザーからのアクションを制限する設定と管理ができるで。" + accessibilityBanner: "クライアントの視覚や動作に関わるパーソナライズをして、よりええ感じに使えるように設定できるで。" + privacyBanner: "コンテンツの公開範囲、見つけやすさ、フォローの承認制とかアカウントのプライバシーに関わる設定ができるで。" + securityBanner: "パスワード、ログイン方法、認証アプリ、パスキーとかアカウントのセキュリティに関わる設定ができるで。" + preferencesBanner: "好みに応じた、クライアントの全体的な動作の設定ができるで。" + appearanceBanner: "好みに応じた、クライアントの見た目・表示方法に関わる設定ができるで。" + soundsBanner: "クライアントで流すサウンドの設定ができるで。" + makeEveryTextElementsSelectable: "全部のテキスト要素を選択できるようにする" + makeEveryTextElementsSelectable_description: "これをつけると、一部のシチュエーションでユーザビリティが低下するかもしれん。" + enablePullToRefresh_description: "マウスやったら、ホイールを押し込みながらドラッグしてな。" + realtimeMode_description: "サーバーと接続を確立して、リアルタイムでコンテンツを更新するで。通信量とバッテリーの消費が多くなるかもしれへん。" + contentsUpdateFrequency_description: "高いほどリアルタイムにコンテンツが更新されるんやけど、そのぶんパフォーマンスが低くなるし、通信量とバッテリーの消費も増えるねん。" + contentsUpdateFrequency_description2: "リアルタイムモードをつけてるんやったら、この設定がどうであれリアルタイムでコンテンツが更新されるで。" +_preferencesProfile: + profileNameDescription: "このデバイスはなんて呼んだらええんや?" +_preferencesBackup: + noBackupsFoundTitle: "バックアップが見つからへんね" + noBackupsFoundDescription: "自動で作られたバックアップは見つからんかったけど、バックアップファイルを手動で保存してるんやったら、それをインポートして復元できるで。" + selectBackupToRestore: "復元するバックアップを選んでや" + youNeedToNameYourProfileToEnableAutoBackup: "自動バックアップを有効するんやったらプロファイル名の設定が必要やな。" + autoPreferencesBackupIsNotEnabledForThisDevice: "このデバイスで設定の自動バックアップは有効になってへんで。" + backupFound: "設定のバックアップがあるみたいやわ" _accountSettings: requireSigninToViewContents: "ログインしてもらってからコンテンツ見てもらう" requireSigninToViewContentsDescription1: "あなたが作成した全部のノートとかのコンテンツを見れるようにするのにログインがいるようにするで。クローラーにいろいろ収集されるんを防げるかもしれん。" @@ -1324,6 +1411,7 @@ _accountSettings: makeNotesHiddenBefore: "昔のノートを見れんようにする" makeNotesHiddenBeforeDescription: "この機能が有効になってる間は、設定された日時より前、それか設定された時間が経ったノートがフォロワーのみ見れるようになるで。無効に戻すと、ノートの公開状態も戻るで。" mayNotEffectForFederatedNotes: "リモートサーバーに連合されたノートには効果が及ばんかもしれん。" + mayNotEffectSomeSituations: "これらの制限は簡易的なものやで。リモートサーバーでの閲覧とかモデレーション時とか、一部のシチュエーションでは適用されへんかもしれん。" notesHavePassedSpecifiedPeriod: "決めた時間が経ったノート" notesOlderThanSpecifiedDateAndTime: "決めた日時より前のノート" _abuseUserReport: @@ -1342,6 +1430,7 @@ _delivery: manuallySuspended: "手動停止中" goneSuspended: "サーバー削除のため停止中" autoSuspendedForNotResponding: "サーバー応答せえへんから停止中" + softwareSuspended: "配信停止中のソフトウェアやから停止中" _bubbleGame: howToPlay: "遊び方" hold: "ホールド" @@ -1468,11 +1557,21 @@ _serverSettings: fanoutTimelineDbFallback: "データベースにフォールバックする" fanoutTimelineDbFallbackDescription: "有効にしたら、タイムラインがキャッシュん中に入ってないときにDBにもっかい問い合わせるフォールバック処理ってのをやっとくで。切ったらフォールバック処理をやらんからサーバーはもっと軽くなんねんけど、タイムラインの取得範囲がちょっと減るで。" reactionsBufferingDescription: "有効にしたら、リアクション作るときのパフォーマンスがすっごい上がって、データベースへの負荷が減るで。代わりに、Redisのメモリ使用は増えるで。" + remoteNotesCleaning_description: "つけると、参照されてへん古いリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑えてくれるで。" inquiryUrl: "問い合わせ先URL" inquiryUrlDescription: "サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定するで。" openRegistration: "アカウントの作成をオープンにする" openRegistrationWarning: "登録を解放するのはリスクが伴うで。サーバーをいっつも監視して、なんか起きたらすぐに対応できるんやったら、オンにしてもええと思う。" thisSettingWillAutomaticallyOffWhenModeratorsInactive: "一定期間モデレーターがおらんかったら、スパムを防ぐためにこの設定は勝手に切られるで。" + deliverSuspendedSoftwareDescription: "脆弱性とかの理由で、サーバーのソフトウェアの名前とバージョンの範囲を決めて配信を止められるで。このバージョン情報はサーバーが提供したものやから、信頼性は保証されへん。バージョン指定には semver の範囲指定が使えるねんけど、>= 2024.3.1と指定すると 2024.3.1-custom.0 みたいなカスタムバージョンが含まれへんから、>= 2024.3.1-0 みたいに prerelease を指定するとええかもしれへんな。" + singleUserMode_description: "このサーバーを使うとるんが自分だけなんやったら、このモードを有効にすると動作がええ感じになるで。" + signToActivityPubGet_description: "通常はつけといてな。連合の通信に関わる問題があるんやったら、無効にすると改善するかもしれへんけど、逆にサーバーによっては通信ができんくなることがあるで。" + proxyRemoteFiles_description: "つけると、リモートのファイルをプロキシして提供するで。画像のサムネイル生成とかユーザーのプライバシー保護にええな。" + allowExternalApRedirect_description: "つけると、他のサーバーがうちのサーバーを通して第三者のコンテンツを照会できるようになるんやけど、コンテンツのなりすましが発生するかもしれへん。" + userGeneratedContentsVisibilityForVisitor_description: "モデレーションが行き届きにくい不適切なリモートコンテンツとかが、うちのサーバー経由で図らずもインターネットに公開されてまうことによるトラブルを防止できたりするで。" + userGeneratedContentsVisibilityForVisitor_description2: "サーバーで受け取ったリモートのコンテンツを含め、サーバー内の全部のコンテンツを何でもかんでもインターネットに公開するのはリスクを伴うねん。特に、分散型の特性を知らん閲覧者にとっては、リモートのコンテンツやったとしてもサーバー内で作られたコンテンツやと誤認してまうかもしれへんから、注意が必要やな。" + restartServerSetupWizardConfirm_title: "サーバーの初期設定ウィザードをやり直すん?" + restartServerSetupWizardConfirm_text: "現在の一部の設定はリセットされるで。" _accountMigration: moveFrom: "別のアカウントからこのアカウントに引っ越す" moveFromSub: "別のアカウントへエイリアスを作る" @@ -1769,6 +1868,7 @@ _role: descriptionOfIsExplorable: "オンにしたらロールの面子一覧が「みつける」で公開されるし、ロールのタイムラインが使えるようになるで。" displayOrder: "表示順" descriptionOfDisplayOrder: "数がでかいほど、UI上で先に表示されるで。" + preserveAssignmentOnMoveAccount_description: "つけると、このロールがのっかったアカウントが引っ越したときに、引っ越し先アカウントにもこのロールがのっかるようになるで。" canEditMembersByModerator: "モデレーターがメンバーいじるのを許す" descriptionOfCanEditMembersByModerator: "オンにすると、管理者だけやなくてモデレーターもこのロールにユーザーを入れたり抜いたりできるで。オフにすると管理者だけしかやれへんくなるで。" priority: "優先度" @@ -1809,6 +1909,8 @@ _role: canImportFollowing: "フォローのインポートを許す" canImportMuting: "ミュートのインポートを許す" canImportUserLists: "リストのインポートを許す" + uploadableFileTypes_caption: "MIMEタイプを指定してや。改行で区切って複数指定もできるし、アスタリスク(*)でワイルドカード指定もできるで。(例: image/*)" + uploadableFileTypes_caption2: "ファイルによっては種別がわからんこともあるで。そないなファイルを許可するんやったら {x} を指定に追加してな。" _condition: roleAssignedTo: "マニュアルロールにアサイン済み" isLocal: "ローカルユーザー" @@ -2008,7 +2110,7 @@ _theme: navIndicator: "サイドバーのインジケーター" link: "リンク" hashtag: "ハッシュタグ" - mention: "メンション" + mention: "あんた宛て" mentionMe: "うち宛てのメンション" renote: "Renote" modalBg: "モーダルの背景" @@ -2277,6 +2379,8 @@ _visibility: disableFederation: "連合なし" disableFederationDescription: "他サーバーへは送らんとくわ" _postForm: + quitInspiteOfThereAreUnuploadedFilesConfirm: "アップロードされてへんファイルがあるんやけど、ほかしてフォームを閉じてもええんか?" + uploaderTip: "ファイルはまだアップロードされてへんで。ファイルのメニューから、リネームとか画像のクロップ、ウォーターマークをのっける、圧縮するかどうかなんかを設定できるで。ファイルはノートを投稿するときに自動でアップロードされるで。" replyPlaceholder: "このノートに返信..." quotePlaceholder: "このノートを引用..." channelPlaceholder: "チャンネルに投稿..." @@ -2428,6 +2532,7 @@ _notification: newNote: "さらの投稿" unreadAntennaNote: "アンテナ {name}" roleAssigned: "ロールが付与されたで" + chatRoomInvitationReceived: "チャットルームへ招待されたで" emptyPushNotificationMessage: "プッシュ通知の更新をしといたで" achievementEarned: "実績を獲得しとるで" testNotification: "通知テスト" @@ -2447,7 +2552,7 @@ _notification: all: "すべて" note: "あんたらの新規投稿" follow: "フォロー" - mention: "メンション" + mention: "あんた宛て" reply: "リプライ" renote: "リノート" quote: "引用" @@ -2617,7 +2722,7 @@ _externalResourceInstaller: _errors: _invalidParams: title: "" - description: "" + description: "外部サイトからデータを持ってくるのに欲しい情報が足らへんみたいやわ。URLは合っとる?" _resourceTypeNotSupported: title: "" description: "" @@ -2647,8 +2752,12 @@ _dataSaver: _avatar: title: "アイコンの絵" description: "アイコン画像のアニメが止まるで。普通の画像よりもデータ量がでかいから、もっと通信量を節約できるねん。" + _urlPreviewThumbnail: + description: "URLプレビューのサムネイル画像が読み込まれへんくなるで。" + _disableUrlPreview: + description: "URLプレビュー機能を切るで。サムネイル画像だけと違って、リンク先の情報の読み込み自体を削減できるで。" _code: - title: "コードハイライト" + title: "コードハイライトは表示せんでええ" description: "MFMとかでコードハイライト記法が使われてるとき、タップするまで読み込まれへんくなるで。コードハイライトではハイライトする言語ごとにその決めてるファイルを読む必要はあんねんな。けどな、それは自動で読み込まれなくなるから、通信量を少なくできることができるねん。" _hemisphere: N: "北半球" @@ -2704,6 +2813,7 @@ _offlineScreen: _urlPreviewSetting: title: "URLプレビューの設定" enable: "URLプレビューを有効にする" + allowRedirectDescription: "入力されたURLがリダイレクトされるとき、そのリダイレクト先をたどってプレビューを表示するかどうかを設定できるで。無効にするとサーバーリソースを節約できるんやけど、リダイレクト先の内容は表示されへんくなるで。" timeout: "プレビュー取得時のタイムアウト(ms)" timeoutDescription: "プレビュー取得の所要時間がこの値を超えた場合、プレビューは生成されへんで。" maximumContentLength: "Content-Lengthの最大値(byte)" @@ -2848,8 +2958,57 @@ _search: searchScopeAll: "みんな" searchScopeLocal: "ローカル" searchScopeUser: "ユーザー指定" + pleaseEnterServerHost: "サーバーのホストはどないするん?" + pleaseSelectUser: "ユーザーを選んでや" +_serverSetupWizard: + installCompleted: "Misskeyのインストールが終わったで!" + firstCreateAccount: "最初は、管理者アカウントを作成しよか。" + accountCreated: "管理者アカウントができたで!" + youCanEasilyConfigureOptimalServerSettingsWithThisWizard: "このウィザードで簡単にええ感じのサーバーの設定ができるで。" + settingsYouMakeHereCanBeChangedLater: "ここでの設定は、あとからでも変えられるで。" + howWillYouUseMisskey: "Misskeyをどんな感じに使うん?" + _use: + single_youCanCreateMultipleAccounts: "お一人様サーバーとして運用するとしても、アカウントは必要に応じて複数作れるで。" + openServerAdvice: "不特定多数の利用者を受け入れるには相応のリスクがあるで。トラブルに対処できるよう、ちゃんとしたモデレーション体制で運営しいや。" + openServerAntiSpamAdvice: "うちのサーバーがスパムの踏み台にならへんように、reCAPTCHAとかのアンチボット機能を使う、みたいなセキュリティ対策もしっかり考えてな。" + howManyUsersDoYouExpect: "どれくらいの人数を考えとるん?" + largeScaleServerAdvice: "大規模なサーバーやったら、ロードバランシングとかデータベースのレプリケーションみたいな、高度なインフラストラクチャーの知識が必要になるかもしれへんわ。" + doYouConnectToFediverse: "Fediverseと接続するんやっけ?" + doYouConnectToFediverse_description1: "分散型サーバーでできたネットワーク(Fediverse)に繋げると、他のサーバーと相互にコンテンツのやり取りができるようになるで。" + doYouConnectToFediverse_description2: "Fediverseと接続することは「連合」とも呼ばれるな。" + youCanConfigureMoreFederationSettingsLater: "連合してもええサーバーの指定とか、高度な設定も後でできるで。" + remoteContentsCleaning_description: "連合すると、ぎょうさんコンテンツを受け取り続けることになるねん。自動クリーニングをつけると、参照されてない古いコンテンツを自動でサーバーからほかして、ストレージを節約できるで。" + adminInfo_description: "問い合わせを受け付けるのに使う管理者情報を設定しよか。" + adminInfo_mustBeFilled: "オープンサーバー、もしくは連合を入れとるんやったら必ず入力せなあかんで。" + followingSettingsAreRecommended: "こういう設定がええかもな" + settingsCompleted: "設定が終わったで!" + settingsCompleted_description: "お疲れさん。準備ができたから、さっそくサーバーを使い始められるで。" + settingsCompleted_description2: "細かいサーバー設定は、「コントロールパネル」を見てみてな。" + _donationRequest: + text1: "Misskeyは有志で開発されとる無料のソフトウェアやで。" + text2: "今後も開発を続けられるように、よかったらぜひカンパをお願いするわ。" + text3: "支援者向け特典もあるで!" +_uploader: + abortConfirm: "アップロードされてへんファイルがあるんやけど、やめてもええんか?" + doneConfirm: "アップロードされてへんファイルがあるんやけど、完了してもええんか?" + maxFileSizeIsX: "アップロードできるファイルサイズは{x}までやで。" + tip: "ファイルはまだアップロードされてへんで。このダイアログで、アップロードする前に確認・リネーム・圧縮・クロッピングとかをできるで。準備が出来たら、「アップロード」ボタンを押してアップロードしてな。" +_clientPerformanceIssueTip: + makeSureDisabledAdBlocker: "アドブロッカーを切ってみてや" + makeSureDisabledAdBlocker_description: "アドブロッカーはパフォーマンスに影響があるかもしれへん。OSの機能とかブラウザの機能・アドオンとかでアドブロッカーが有効になってないか確認してや。" + makeSureDisabledCustomCss: "カスタムCSSを無効にしてみてや" + makeSureDisabledCustomCss_description: "スタイルを上書きするとパフォーマンスに影響があるかもしれへん。カスタムCSSとか、スタイルを上書きする拡張機能が有効になってないか確認してや。" + makeSureDisabledAddons: "拡張機能を無効にしてみてや" + makeSureDisabledAddons_description: "なんかの拡張機能がクライアントの動作にちょっかいをかけてパフォーマンスに影響を与えてるかもしれへん。ブラウザの拡張機能を無効にして良くなるか確認してや。" +_clip: + tip: "クリップは、ノートをまとめられる機能やで。" +_userLists: + tip: "好きなユーザーを含むリストを作れるねん。作ったリストはタイムラインとして表示できるで。" _watermarkEditor: + tip: "画像にクレジット情報とかのウォーターマークをのっけられるで。" + quitWithoutSaveConfirm: "保存せずに終わってもええんか?" driveFileTypeWarn: "このファイルは対応しとらへん" + driveFileTypeWarnDescription: "画像ファイルを選んでや" opacity: "不透明度" scale: "大きさ" text: "テキスト" @@ -2858,3 +3017,18 @@ _watermarkEditor: image: "画像" advanced: "高度" angle: "角度" +_imageEffector: + discardChangesConfirm: "変更をせんで終わるか?" + _fxProps: + angle: "角度" + scale: "大きさ" + size: "大きさ" + color: "色" + opacity: "不透明度" + lightness: "明るさ" +_drafts: + cannotCreateDraftAnymore: "下書きはこれ以上は作れへんな。" + cannotCreateDraft: "この内容で下書きは作れへんな。" + delete: "下書きをほかす" + deleteAreYouSure: "下書きをほかしてもええか?" + noDrafts: "下書きはあらへん" diff --git a/locales/kn-IN.yml b/locales/kn-IN.yml index 222599572a..cdd9a5b2ae 100644 --- a/locales/kn-IN.yml +++ b/locales/kn-IN.yml @@ -44,6 +44,7 @@ showMore: "ಇನ್ನಷ್ಟು ನೋಡು" youGotNewFollower: "ಹಿಂಬಾಲಿಸಿದರು" receiveFollowRequest: "ಹಿಂಬಾಲನೆ ವಿನಂತಿ ಬಂದಿದೆ" followRequestAccepted: "ಹಿಂಬಾಲನೆ ವಿನಂತಿ ಸ್ವೀಕರಿಸಲಾಯಿತು" +mention: "ಹೆಸರಿಸಿದ" mentions: "ಹೆಸರಿಸಿದ" directNotes: "ನೇರ ಟಿಪ್ಪಣಿಗಳು" importAndExport: "ಆಮದು/ರಫ್ತು" @@ -65,6 +66,9 @@ replies: "ಉತ್ತರಿಸು" _email: _follow: title: "ಹಿಂಬಾಲಿಸಿದರು" +_theme: + keys: + mention: "ಹೆಸರಿಸಿದ" _sfx: notification: "ಅಧಿಸೂಚನೆಗಳು" _widgets: @@ -73,11 +77,14 @@ _widgets: timeline: "ಸಮಯಸಾಲು" _cw: show: "ಇನ್ನಷ್ಟು ನೋಡು" +_visibility: + specified: "ನೇರ ಟಿಪ್ಪಣಿಗಳು" _profile: username: "ಬಳಕೆಹೆಸರು" _notification: youWereFollowed: "ಹಿಂಬಾಲಿಸಿದರು" _types: + mention: "ಹೆಸರಿಸಿದ" login: "ಪ್ರವೇಶ" _actions: reply: "ಉತ್ತರಿಸು" @@ -86,3 +93,4 @@ _deck: notifications: "ಅಧಿಸೂಚನೆಗಳು" tl: "ಸಮಯಸಾಲು" mentions: "ಹೆಸರಿಸಿದ" + direct: "ನೇರ ಟಿಪ್ಪಣಿಗಳು" diff --git a/locales/ko-GS.yml b/locales/ko-GS.yml index 554eb42694..59fe63be6c 100644 --- a/locales/ko-GS.yml +++ b/locales/ko-GS.yml @@ -745,7 +745,7 @@ _menuDisplay: _theme: description: "설멩" keys: - mention: "멘션" + mention: "받언 멘션" renote: "리노트" _sfx: note: "새 노트" @@ -775,6 +775,7 @@ _cw: _visibility: home: "덜머리" followers: "팔로워" + specified: "쪽지 서기" _postForm: _placeholders: e: "옇다 서 주이소" @@ -809,7 +810,7 @@ _notification: newNote: "새 걸" _types: follow: "팔로잉" - mention: "멘션" + mention: "받언 멘션" renote: "리노트" quote: "따오기" reaction: "반엉" @@ -824,6 +825,7 @@ _deck: antenna: "안테나" list: "리스트" mentions: "받언 멘션" + direct: "쪽지 서기" _webhookSettings: name: "이럼" _abuseReport: diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 458f75dfea..8809d3b4d3 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1054,6 +1054,7 @@ permissionDeniedError: "작업이 거부되었습니다" permissionDeniedErrorDescription: "이 작업을 수행할 권한이 없습니다." preset: "프리셋" selectFromPresets: "프리셋에서 선택" +custom: "커스텀" achievements: "도전 과제" gotInvalidResponseError: "서버의 응답이 올바르지 않습니다" gotInvalidResponseErrorDescription: " 서버가 다운되었거나 점검중일 가능성이 있습니다. 잠시후에 다시 시도해 주십시오." @@ -1092,6 +1093,7 @@ prohibitedWordsDescription2: "공백으로 구분하면 AND 지정이 되며, hiddenTags: "숨긴 해시태그" hiddenTagsDescription: "설정한 태그를 트렌드에 표시하지 않도록 합니다. 줄 바꿈으로 하나씩 나눠서 설정할 수 있습니다." notesSearchNotAvailable: "노트 검색을 이용하실 수 없습니다." +usersSearchNotAvailable: "유저 검색을 이용하실 수 없습니다." license: "라이선스" unfavoriteConfirm: "즐겨찾기를 해제하시겠습니까?" myClips: "내 클립" @@ -1243,7 +1245,7 @@ releaseToRefresh: "놓아서 새로고침" refreshing: "새로고침 중" pullDownToRefresh: "아래로 내려서 새로고침" useGroupedNotifications: "알림을 그룹화하고 표시" -signupPendingError: "메일 주소 확인중에 문제가 발생했습니다. 링크의 유효기간이 지났을 가능성이 있습니다." +emailVerificationFailedError: "메일 주소 확인에 실패했습니다. 확인에 필요한 URL의 유효기간이 지났을 가능성이 있습니다." cwNotationRequired: "'내용을 숨기기'를 체크한 경우 주석을 써야 합니다." doReaction: "리액션 추가" code: "문자열" @@ -1368,6 +1370,13 @@ redisplayAllTips: "모든 '팁과 유용한 정보'를 재표시" hideAllTips: "모든 '팁과 유용한 정보'를 비표시" defaultImageCompressionLevel: "기본 이미지 압축 정도" defaultImageCompressionLevel_description: "낮추면 화질을 유지합니다만 파일 크기는 증가합니다.
높이면 파일 크기를 줄일 수 있습니다만 화질은 저하됩니다." +inMinutes: "분" +inDays: "일" +safeModeEnabled: "세이프 모드가 활성화돼있습니다" +pluginsAreDisabledBecauseSafeMode: "세이프 모드가 활성화돼있기에 플러그인은 전부 비활성화됩니다." +customCssIsDisabledBecauseSafeMode: "세이프 모드가 활성화돼있기에 커스텀 CSS는 적용되지 않습니다." +themeIsDefaultBecauseSafeMode: "세이프 모드가 활성화돼있는 동안에는 기본 테마가 사용됩니다. 세이프 모드를 끄면 원래대로 돌아옵니다." +thankYouForTestingBeta: "베타 버전의 검증에 협력해 주셔서 감사합니다!" _order: newest: "최신 순" oldest: "오래된 순" @@ -1459,6 +1468,7 @@ _settings: contentsUpdateFrequency_description2: "실시간 모드가 켜져 있을 때는 이 설정과 상관없이 실시간으로 콘텐츠가 업데이트됩니다." showUrlPreview: "URL 미리보기 표시" showAvailableReactionsFirstInNote: "이용 가능한 리액션을 선두로 표시" + showPageTabBarBottom: "페이지의 탭 바를 아래쪽에 표시" _chat: showSenderName: "발신자 이름 표시" sendOnEnter: "엔터로 보내기" @@ -1632,6 +1642,10 @@ _serverSettings: fanoutTimelineDbFallback: "데이터베이스를 예비로 사용하기" fanoutTimelineDbFallbackDescription: "활성화하면 타임라인의 캐시되어 있지 않은 부분에 대해 DB에 질의하여 정보를 가져옵니다. 비활성화하면 이를 실행하지 않음으로써 서버의 부하를 줄일 수 있지만, 타임라인에서 가져올 수 있는 게시물 범위가 한정됩니다." reactionsBufferingDescription: "활성화 한 경우, 리액션 작성 퍼포먼스가 대폭 향상되어 DB의 부하를 줄일 수 있으나, Redis의 메모리 사용량이 많아집니다." + remoteNotesCleaning: "리모트 서버 노트 자동 정리 " + remoteNotesCleaning_description: "더 이상 사용되지 않는 오래된 리모트 노트를 정기적으로 정리하여, 데이터 베이스의 사용량을 절약할 수 있습니다." + remoteNotesCleaningMaxProcessingDuration: "리모트 노트 자동 정리 최대 실행 시간" + remoteNotesCleaningExpiryDaysForEachNotes: "리모트 노트 저장 최소 일수" inquiryUrl: "문의처 URL" inquiryUrlDescription: "서버 운영자에게 보내는 문의 양식의 URL이나 운영자의 연락처 등이 적힌 웹 페이지의 URL을 설정합니다." openRegistration: "회원 가입을 활성화 하기" @@ -1650,6 +1664,11 @@ _serverSettings: userGeneratedContentsVisibilityForVisitor: "비이용자에 대한 유저 작성 콘텐츠의 공개 범위" userGeneratedContentsVisibilityForVisitor_description: "조정을 하기 힘든 부적절한 리모트 콘텐츠 등이 자신의 서버 경유로 의도치 않게 인터넷에 공개되는 문제의 방지 등에 도움을 줍니다." userGeneratedContentsVisibilityForVisitor_description2: "서버에서 받은 리모트 콘텐츠를 포함해 서버 내의 모든 콘텐츠를 무조건 인터넷에 공개하는 것에는 위험이 따릅니다. 특히, 분산형 특성에 대해 모르는 열람자에게는 리모트 콘텐츠여도 서버 내에서 작성된 콘텐츠라고 잘못 인식할 수 있기에 주의가 필요합니다." + restartServerSetupWizardConfirm_title: "서버의 초기 설정 위자드를 재시도하시겠습니까?" + restartServerSetupWizardConfirm_text: "현재 일부 설정은 리셋됩니다." + entrancePageStyle: "입구 페이지의 스타일" + showTimelineForVisitor: "타임라인 표시" + showActivitiesForVisitor: "액티비티 표시하기" _userGeneratedContentsVisibilityForVisitor: all: "모두 공개" localOnly: "로컬 콘텐츠만 공개하고 리모트 콘텐츠는 비공개" @@ -1986,6 +2005,7 @@ _role: descriptionOfRateLimitFactor: "작을수록 제한이 완화되고, 클수록 제한이 강화됩니다." canHideAds: "광고 숨기기" canSearchNotes: "노트 검색 이용 가능 여부" + canSearchUsers: "유저 검색 이용" canUseTranslator: "번역 기능의 사용" avatarDecorationLimit: "아바타 장식의 최대 붙임 개수" canImportAntennas: "안테나 가져오기 허용" @@ -1998,6 +2018,7 @@ _role: uploadableFileTypes_caption: "MIME 유형을 " uploadableFileTypes_caption2: "파일에 따라서는 유형을 검사하지 못하는 경우가 있습니다. 그러한 파일을 허가하는 경우에는 {x}를 지정으로 추가해주십시오." noteDraftLimit: "서버측 노트 초안 작성 가능 수" + watermarkAvailable: "워터마크 기능의 사용 여부" _condition: roleAssignedTo: "수동 역할에 이미 할당됨" isLocal: "로컬 유저" @@ -2257,6 +2278,7 @@ _time: minute: "분" hour: "시간" day: "일" + month: "개월" _2fa: alreadyRegistered: "이미 설정이 완료되었습니다." registerTOTP: "인증 앱 설정 시작" @@ -2806,6 +2828,7 @@ _fileViewer: url: "URL" uploadedAt: "업로드 날짜" attachedNotes: "첨부된 노트" + usage: "이용" thisPageCanBeSeenFromTheAuthor: "이 페이지는 파일 소유자만 열람할 수 있습니다" _externalResourceInstaller: title: "외부 사이트로부터 설치" @@ -3058,6 +3081,7 @@ _bootErrors: otherOption1: "클라이언트 설정 및 캐시 삭제" otherOption2: "간편 클라이언트 실행" otherOption3: "복구 툴 실행" + otherOption4: "Misskey를 세이프 모드로 열기" _search: searchScopeAll: "전체" searchScopeLocal: "로컬" @@ -3094,6 +3118,8 @@ _serverSetupWizard: doYouConnectToFediverse_description1: "분산형 서버로 구성된 네트워크(Fediverse)에 접속하면 다른 서버와 서로 콘텐츠의 주고받기를 할 수 있습니다." doYouConnectToFediverse_description2: "Fediverse에 접속하는 것을 '연합'이라고도 부릅니다." youCanConfigureMoreFederationSettingsLater: "나중에 연합 가능한 서버의 지정 등 고급 설정을 할 수 있습니다." + remoteContentsCleaning: "리모트 콘텐츠 자동 정리" + remoteContentsCleaning_description: "연합 중인 서버가 있는 경우, 리모트 서버에서 대단히 많은 콘텐츠를 받아오게 됩니다. 자동 정리 기능을 활성화하면, 오래되고 서버에서 더 이상 조회되지 않는 콘텐츠를 자동으로 서버에서 삭제하여, 스토리지를 절약할 수 있습니다." adminInfo: "관리자 정보" adminInfo_description: "문의 접수를 위해 사용되는 관리자 정보를 설정합니다." adminInfo_mustBeFilled: "오픈 서버 혹은 연합이 켜져 있는 경우 반드시 입력해야 합니다." @@ -3146,10 +3172,10 @@ _watermarkEditor: type: "종류" image: "이미지" advanced: "고급" + angle: "각도" stripe: "줄무늬" stripeWidth: "라인의 폭" stripeFrequency: "라인의 수" - angle: "각도" polkadot: "물방울 무늬" checker: "체크 무늬" polkadotMainDotOpacity: "주요 물방울의 불투명도" @@ -3161,6 +3187,7 @@ _imageEffector: title: "이펙트" addEffect: "이펙트를 추가" discardChangesConfirm: "변경을 취소하고 종료하시겠습니까?" + nothingToConfigure: "설정 항목이 없습니다." _fxs: chromaticAberration: "색수차" glitch: "글리치" @@ -3178,11 +3205,43 @@ _imageEffector: checker: "체크 무늬" blockNoise: "노이즈 방지" tearing: "티어링" + _fxProps: + angle: "각도" + scale: "크기" + size: "크기" + color: "색" + opacity: "불투명도" + normalize: "노멀라이즈" + amount: "양" + lightness: "밝음" + contrast: "대비" + hue: "색조" + brightness: "밝기" + saturation: "채도" + max: "최대 값" + min: "최소 값" + direction: "방향" + phase: "위상" + frequency: "빈도" + strength: "강도" + glitchChannelShift: "글리치" + seed: "시드 값" + redComponent: "빨간색 요소" + greenComponent: "녹색 요소" + blueComponent: "파란색 요소" + threshold: "한계 값" + centerX: "X축 중심" + centerY: "Y축 중심" + zoomLinesSmoothing: "다듬기" + zoomLinesSmoothingDescription: "다듬기와 집중선 폭 설정은 같이 쓸 수 없습니다." + zoomLinesThreshold: "집중선 폭" + zoomLinesMaskSize: "중앙 값" + zoomLinesBlack: "검은색으로 하기" drafts: "초안" _drafts: select: "초안 선택" cannotCreateDraftAnymore: "초안 작성 가능 수를 초과했습니다." - cannotCreateDraftOfRenote: "리노트 초안은 작성할 수 없습니다." + cannotCreateDraft: "이 내용으로는 초안을 작성할 수 없습니다. " delete: "초안 삭제\n" deleteAreYouSure: "초안을 삭제하시겠습니까?" noDrafts: "초안 없음\n" diff --git a/locales/lo-LA.yml b/locales/lo-LA.yml index 49270119b3..aec49a2965 100644 --- a/locales/lo-LA.yml +++ b/locales/lo-LA.yml @@ -433,6 +433,7 @@ _cw: _visibility: home: "ໜ້າຫຼັກ" followers: "ຜູ້ຕິດຕາມ" + specified: "ໂພສ Direct note" _profile: name: "ຊື່" username: "ຊື່ຜູ້ໃຊ້" @@ -470,6 +471,7 @@ _deck: list: "ລາຍການ" channel: "ຊ່ອງ" mentions: "ກ່າວເຖິງເຈົ້າ" + direct: "ໂພສ Direct note" _webhookSettings: name: "ຊື່" _abuseReport: diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml index 62f31dcced..e4efbc7e39 100644 --- a/locales/nl-NL.yml +++ b/locales/nl-NL.yml @@ -1019,6 +1019,7 @@ _cw: _visibility: home: "Startpagina" followers: "Volgers" + specified: "Directe notities" _profile: name: "Naam" username: "Gebruikersnaam" @@ -1061,6 +1062,7 @@ _deck: list: "Lijsten" channel: "Kanalen" mentions: "Vermeldingen" + direct: "Directe notities" _webhookSettings: name: "Naam" active: "Ingeschakeld" diff --git a/locales/no-NO.yml b/locales/no-NO.yml index b161ce19b6..1eafd31c4f 100644 --- a/locales/no-NO.yml +++ b/locales/no-NO.yml @@ -461,6 +461,8 @@ replies: "Svar" renotes: "Renote" surrender: "Avbryt" information: "Informasjon" +inMinutes: "Minutter" +inDays: "Dager" _chat: invitations: "Inviter" members: "Medlemmer" @@ -740,3 +742,8 @@ _watermarkEditor: text: "Tekst" type: "Type" image: "Bilder" +_imageEffector: + _fxProps: + scale: "Størrelse" + size: "Størrelse" + color: "Farge" diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index a72b9f2c7d..fbd898016e 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -1040,6 +1040,8 @@ surrender: "Odrzuć" gameRetry: "Spróbuj ponownie" postForm: "Formularz tworzenia wpisu" information: "Informacje" +inMinutes: "minuta" +inDays: "dzień" _chat: invitations: "Zaproś" noHistory: "Brak historii" @@ -1591,3 +1593,10 @@ _watermarkEditor: type: "Typ" image: "Zdjęcia" advanced: "Zaawansowane" +_imageEffector: + _fxProps: + scale: "Rozmiar" + size: "Rozmiar" + color: "Kolor" + opacity: "Przezroczystość" + lightness: "Rozjaśnij" diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml index 213313349c..013d2ef549 100644 --- a/locales/pt-PT.yml +++ b/locales/pt-PT.yml @@ -446,7 +446,7 @@ exploreUsersCount: "Há um utilizador de {count}" exploreFediverse: "Explorar Fediverse" popularTags: "Tags populares" userList: "Listas" -about: "Informações" +about: "Sobre" aboutMisskey: "Sobre Misskey" administrator: "Administrador" token: "Símbolo" @@ -1243,7 +1243,6 @@ releaseToRefresh: "Solte para atualizar" refreshing: "Atualizando..." pullDownToRefresh: "Puxe para baixo para atualizar" useGroupedNotifications: "Agrupar notificações" -signupPendingError: "Houve um problema ao verificar o endereço de email. O link pode ter expirado." cwNotationRequired: "Se \"Esconder conteúdo\" está habilitado, uma descrição deve ser adicionada." doReaction: "Adicionar reação" code: "Código" @@ -1339,7 +1338,7 @@ paste: "Colar" emojiPalette: "Paleta de emojis" postForm: "Campo de postagem" textCount: "Contagem de caracteres" -information: "Informações" +information: "Sobre" chat: "Conversas" migrateOldSettings: "Migrar configurações antigas de cliente" migrateOldSettings_description: "Isso deve ser feito automaticamente. Caso o processo de migração tenha falhado, você pode acioná-lo manualmente. As informações atuais de migração serão substituídas." @@ -1368,6 +1367,8 @@ redisplayAllTips: "Mostrar todas as \"Dicas e Truques\" novamente" hideAllTips: "Ocultas todas as \"Dicas e Truques\"" defaultImageCompressionLevel: "Nível de compressão de imagem padrão" defaultImageCompressionLevel_description: "Alto, reduz o tamanho do arquivo mas, também, a qualidade da imagem.
Alto, reduz o tamanho do arquivo mas, também, a qualidade da imagem." +inMinutes: "Minuto(s)" +inDays: "Dia(s)" _order: newest: "Priorizar Mais Novos" oldest: "Priorizar Mais Antigos" @@ -1998,6 +1999,7 @@ _role: uploadableFileTypes_caption: "Especifica tipos MIME permitidos. Múltiplos tipos MIME podem ser especificados separando-os por linha. Curingas podem ser especificados com um asterisco (*). (exemplo, image/*)" uploadableFileTypes_caption2: "Alguns tipos de arquivos podem não ser detectados. Para permiti-los, adicione {x} à especificação." noteDraftLimit: "Limite de rascunhos possíveis" + watermarkAvailable: "Disponibilidade da função de marca d'água" _condition: roleAssignedTo: "Atribuído a cargos manuais" isLocal: "Usuário local" @@ -2806,6 +2808,7 @@ _fileViewer: url: "URL" uploadedAt: "Adicionado em" attachedNotes: "Notas anexadas" + usage: "Usado" thisPageCanBeSeenFromTheAuthor: "Essa página só pode ser vista pelo usuário que enviou esse arquivo." _externalResourceInstaller: title: "Instalar de site externo" @@ -3146,10 +3149,10 @@ _watermarkEditor: type: "Tipo" image: "imagem" advanced: "Avançado" + angle: "Ângulo" stripe: "Listras" stripeWidth: "Largura da linha" stripeFrequency: "Número de linhas" - angle: "Ângulo" polkadot: "Bolinhas" checker: "Xadrez" polkadotMainDotOpacity: "Opacidade da bolinha principal" @@ -3178,10 +3181,25 @@ _imageEffector: checker: "Xadrez" blockNoise: "Bloquear Ruído" tearing: "Descontinuidade" + _fxProps: + angle: "Ângulo" + scale: "Tamanho" + size: "Tamanho" + color: "Cor" + opacity: "Opacidade" + lightness: "Esclarecer" drafts: "Rascunhos" _drafts: select: "Selecionar Rascunho" cannotCreateDraftAnymore: "O número máximo de rascunhos foi excedido." - cannotCreateDraftOfRenote: "Você não pode criar o rascunho de uma repostagem." + cannotCreateDraft: "Você não pode criar um rascunho com esse conteúdo." delete: "Excluir Rascunho" + deleteAreYouSure: "Excluir rascunho?" + noDrafts: "Sem rascunhos" + replyTo: "Resposta a {user}" + quoteOf: "Citação à nota de {user}" + postTo: "Publicando em {channel}" + saveToDraft: "Salvar como Rascunho" + restoreFromDraft: "Restaurar de Rascunho" restore: "Redefinir" + listDrafts: "Lista de Rascunhos" diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml index de3e17fce3..b08341711a 100644 --- a/locales/ro-RO.yml +++ b/locales/ro-RO.yml @@ -1302,6 +1302,7 @@ _cw: _visibility: home: "Acasă" followers: "Urmăritori" + specified: "Note directe" _postForm: replyPlaceholder: "Răspunde la această notă..." quotePlaceholder: "Citează aceasta nota..." @@ -1356,6 +1357,7 @@ _deck: list: "Liste" channel: "Canale" mentions: "Mențiuni" + direct: "Note directe" roleTimeline: "Cronologia rolului" _webhookSettings: name: "Nume" @@ -1398,3 +1400,7 @@ _watermarkEditor: type: "Tip" image: "Imagini" advanced: "Avansat" +_imageEffector: + _fxProps: + scale: "Dimensiune" + size: "Dimensiune" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index 1e34af9351..5e52143f83 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -2,7 +2,7 @@ _lang_: "Русский" headlineMisskey: "Сеть, сплетённая из заметок" introMisskey: "Добро пожаловать! Misskey — это децентрализованный сервис микроблогов с открытым исходным кодом.\nПишите «заметки» — делитесь со всеми происходящим вокруг или рассказывайте о себе 📡\nСтавьте «реакции» — выражайте свои чувства и эмоции от заметок других 👍\nОткройте для себя новый мир 🚀" -poweredByMisskeyDescription: "{name} – сервис на платформе с открытым исходным кодом Misskey, называемый экземпляром Misskey." +poweredByMisskeyDescription: "{name} – один из инстансов (также называемый экземпляром Misskey), использующий платформу с открытым исходным кодом Misskey." monthAndDay: "{day}.{month}" search: "Поиск" reset: "Сброс" @@ -82,7 +82,7 @@ export: "Экспорт" files: "Файлы" download: "Скачать" driveFileDeleteConfirm: "Удалить файл «{name}»? Заметки с ним также будут удалены." -unfollowConfirm: "Удалить из подписок пользователя {name}?" +unfollowConfirm: "Отписаться от {name} ?" exportRequested: "Вы запросили экспорт. Это может занять некоторое время. Результат будет добавлен на «Диск»." importRequested: "Вы запросили импорт. Это может занять некоторое время." lists: "Списки" @@ -298,6 +298,7 @@ uploadFromUrl: "Загрузить по ссылке" uploadFromUrlDescription: "Ссылка на файл, который хотите загрузить" uploadFromUrlRequested: "Загрузка выбранного" uploadFromUrlMayTakeTime: "Загрузка может занять некоторое время." +uploadNFiles: "Загрузить {n} файл" explore: "Обзор" messageRead: "Прочитали" noMoreHistory: "История закончилась" @@ -575,8 +576,10 @@ showFixedPostForm: "Показывать поле для ввода новой showFixedPostFormInChannel: "Показывать поле для ввода новой заметки наверху ленты (каналы)" withRepliesByDefaultForNewlyFollowed: "По умолчанию включайте ответы новых пользователей, на которых вы подписались, во временную шкалу" newNoteRecived: "Появилась новая заметка" +newNote: "Новая заметка" sounds: "Звуки" sound: "Звуки" +notificationSoundSettings: "Настройки звука уведомлений" listen: "Слушать" none: "Ничего" showInPage: "Показать страницу" @@ -791,6 +794,7 @@ wide: "Толстый" narrow: "Тонкий" reloadToApplySetting: "Это настройка вступает в силу при загрузке страницы. Перезагрузить сейчас?" needReloadToApply: "Изменения вступят в силу после перезагрузки страницы." +needToRestartServerToApply: "Для вступления изменений в силу необходимо перезапустить сервер." showTitlebar: "Показать заголовок" clearCache: "Очистить кэш" onlineUsersCount: "Пользователей сейчас в сети: {n}" @@ -1176,13 +1180,25 @@ unused: "Неиспользованное" used: "Использован" expired: "Срок действия приглашения истёк" doYouAgree: "Согласны?" +beSureToReadThisAsItIsImportant: "Это важно, поэтому, пожалуйста, прочтите это." +iHaveReadXCarefullyAndAgree: "Я прочитал(а) и согласен(сна) с условиями \"{x}" +dialog: "Диалог" icon: "Аватар" +currentAnnouncements: "Текущие новости" +pastAnnouncements: "Предыдущие новости" +youHaveUnreadAnnouncements: "У вас есть непрочитанные уведомления" replies: "Ответы" renotes: "Репост" loadReplies: "Показать ответы" +loadConversation: "Загрузить беседу" pinnedList: "Закреплённый список" keepScreenOn: "Держать экран включённым" +unnotifyNotes: "Отписаться от сообщений" +authentication: "Аутентификация" +authenticationRequiredToContinue: "Пожалуйста, пройдите аутентификацию, чтобы продолжить" +dateAndTime: "Дата и время" showRenotes: "Показывать репосты" +edited: "Изменено" mutualFollow: "Взаимные подписки" followingOrFollower: "Подписки или подписчики" fileAttachedOnly: "Только заметки с файлами" @@ -1193,30 +1209,74 @@ sourceCode: "Исходный код" sourceCodeIsNotYetProvided: "Исходный код пока не доступен. Свяжитесь с администратором, чтобы исправить эту проблему." repositoryUrl: "Ссылка на репозиторий" repositoryUrlDescription: "Если вы используете Misskey как есть (без изменений в исходном коде), введите https://github.com/misskey-dev/misskey" +feedback: "Обратная связь" privacyPolicy: "Политика Конфиденциальности" privacyPolicyUrl: "Ссылка на Политику Конфиденциальности" +tosAndPrivacyPolicy: "Условия использования и политика конфиденциальности" +avatarDecorations: "Украшения для аватара" attach: "Прикрепить" +detachAll: "Убрать всё" angle: "Угол" flip: "Переворот" +showAvatarDecorations: "Показать украшения для аватара" +pullDownToRefresh: "Опустите что бы обновить" useGroupedNotifications: "Отображать уведомления сгруппировано" +cwNotationRequired: "Если включена опция «Скрыть содержимое», необходимо написать аннотацию." doReaction: "Добавить реакцию" code: "Код" +reloadRequiredToApplySettings: "Для применения настроек необходима обновить страницу." remainingN: "Остаётся: {n}" +overwriteContentConfirm: "Текущее содержимое будет перезаписано. Вы уверены?" seasonalScreenEffect: "Эффект времени года на экране" decorate: "Украсить" addMfmFunction: "Добавить MFM" +bubbleGame: "BubbleGame" +sfx: "Звуковые эффекты" +soundWillBePlayed: "Будет воспроизведен звук" +showReplay: "Показать повтор" +endReplay: "Конец повтора" lastNDays: "Последние {n} сут" hemisphere: "Место проживания" +userSaysSomethingSensitive: "Сообщение, содержит конфиденциальные файлы от {name}" enableHorizontalSwipe: "Смахните в сторону, чтобы сменить вкладки" surrender: "Этот пост не может быть отменен." +gameRetry: "Повторить попытку" +notUsePleaseLeaveBlank: "Если не используется, оставьте пустым" useNativeUIForVideoAudioPlayer: "Использовать интерфейс браузера при проигрывании видео и звука" keepOriginalFilename: "Сохранять исходное имя файла" keepOriginalFilenameDescription: "Если вы выключите данную настройку, имена файлов будут автоматически заменены случайной строкой при загрузке." alwaysConfirmFollow: "Всегда подтверждать подписку" inquiry: "Связаться" +fromX: "Из {x}" +genEmbedCode: "Сгенерировать код для " +noteOfThisUser: "Список заметок этого пользователя" +clipNoteLimitExceeded: "К этому клипу больше нельзя добавить заметки" +performance: "Производительность" +modified: "Изменено" +signinWithPasskey: "Войдите в систему, используя свой пароль" +unknownWebAuthnKey: "Неизвестный ключ" +passkeyVerificationFailed: "Ошибка проверка ключа доступа " messageToFollower: "Сообщение подписчикам" +testCaptchaWarning: "Эта функция предназначена для тестирования CAPTCHA. Не использовать это в рабочей среде" +prohibitedWordsForNameOfUser: "Запрещенные слова (имя пользователя)" +prohibitedWordsForNameOfUserDescription: "Если имя пользователя содержит строку из этого списка, изменение имени пользователя будет запрещено. На пользователей с правами модератора это ограничение не распространяется. Имена пользователей также проверяются путём замены всех букв в нижнем регистре" +yourNameContainsProhibitedWords: "Имя, которое вы пытаетесь изменить, содержит запрещенную строку символов" +yourNameContainsProhibitedWordsDescription: "Имя содержит запрещённую строку символов. Если вы хотите использовать это имя, обратитесь к администратору сервера" +thisContentsAreMarkedAsSigninRequiredByAuthor: "Автор сообщения установил требование в виде авторизации для просмотра" +lockdown: "Доступ ограничен" +pleaseSelectAccount: "Выберите свой аккаунт" +availableRoles: "Доступные роли" +federationDisabled: "Федерация отключена для этого сервера. Вы не можете взаимодействовать с пользователями на других серверах." +draft: "Черновик" +markAsSensitiveConfirm: "Отметить контент как чувствительный?" +preferences: "Основное" +resetToDefaultValue: "Сбросить настройки до стандартных" +syncBetweenDevices: "Синхронизировать между устройствами" postForm: "Форма отправки" +textCount: "Количество символов" information: "Описание" +inMinutes: "мин" +inDays: "сут" _chat: invitations: "Пригласить" noHistory: "История пока пуста" @@ -1225,6 +1285,11 @@ _chat: send: "Отправить" _settings: webhook: "Вебхук" + preferencesBanner: "Вы можете настроить общее поведение клиента по вашим предпочтениям" + timelineAndNote: "Лента и заметки" + _chat: + showSenderName: "Показывать имя отправителя" + sendOnEnter: "Использовать Enter для отправки" _delivery: stop: "Заморожено" _type: @@ -1473,7 +1538,7 @@ _achievements: description: "Нажато здесь" _justPlainLucky: title: "Чистая удача" - description: "Может достаться с вероятностью 0,01% каждые 10 секунд." + description: "Может достаться с вероятностью 0,005% каждые 10 секунд." _setNameToSyuilo: title: "Комплекс бога" description: "Установлено «syuilo» в качестве имени" @@ -1501,6 +1566,12 @@ _achievements: title: "Brain Diver" description: "Опубликована ссылка на песню «Brain Diver»" flavor: "Мисски-Мисски Ла-Ту-Ма" + _bubbleGameExplodingHead: + title: "🤯" + description: "Самый большой объект в Bubble game" + _bubbleGameDoubleExplodingHead: + title: "Двойной🤯" + description: "Два самых больших объекта в Bubble game одновременно!" _role: new: "Новая роль" edit: "Изменить роль" @@ -2200,3 +2271,12 @@ _watermarkEditor: image: "Изображения" advanced: "Для продвинутых" angle: "Угол" +_imageEffector: + _fxProps: + angle: "Угол" + scale: "Размер" + size: "Размер" + color: "Цвет" + opacity: "Непрозрачность" + lightness: "Осветление" +drafts: "Черновик" diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index 651e91fa0a..c9fd8e6ae9 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -913,6 +913,8 @@ flip: "Preklopiť" lastNDays: "Posledných {n} dní" postForm: "Napísať poznámku" information: "Informácie" +inMinutes: "min" +inDays: "dní" _chat: invitations: "Pozvať" noHistory: "Žiadna história" @@ -1457,3 +1459,10 @@ _watermarkEditor: type: "Typ" image: "Obrázky" advanced: "Rozšírené" +_imageEffector: + _fxProps: + scale: "Veľkosť" + size: "Veľkosť" + color: "Farba" + opacity: "Priehľadnosť" + lightness: "Zosvetliť" diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml index 4d1708c7d1..96944706e9 100644 --- a/locales/sv-SE.yml +++ b/locales/sv-SE.yml @@ -646,6 +646,7 @@ _poll: _visibility: home: "Hem" followers: "Följare" + specified: "Direktnoter" _profile: name: "Namn" username: "Användarnamn" @@ -692,6 +693,7 @@ _deck: list: "Listor" channel: "kanal" mentions: "Omnämningar" + direct: "Direktnoter" _webhookSettings: name: "Namn" active: "Aktiverad" @@ -714,3 +716,8 @@ _search: _watermarkEditor: scale: "Storlek" image: "Bilder" +_imageEffector: + _fxProps: + scale: "Storlek" + size: "Storlek" + color: "Färg" diff --git a/locales/th-TH.yml b/locales/th-TH.yml index 8492b8310b..f70b0d5be8 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -146,7 +146,7 @@ enterFileName: "พิมพ์ชื่อไฟล์" mute: "ปิดเสียง" unmute: "ยกเลิกการปิดเสียง" renoteMute: "ปิดเสียงรีโน้ต" -renoteUnmute: "เปิดเสียง รีโน้ต" +renoteUnmute: "เลิกปิดเสียงรีโน้ต" block: "บล็อก" unblock: "เลิกบล็อก" suspend: "ระงับ" @@ -242,8 +242,8 @@ silencedInstances: "ปิดปากเซิร์ฟเวอร์นี้ silencedInstancesDescription: "ระบุโฮสต์ของเซิร์ฟเวอร์ที่ต้องการปิดปาก คั่นด้วยการขึ้นบรรทัดใหม่, บัญชีทั้งหมดของเซิร์ฟเวอร์ดังกล่าวจะถือว่าถูกปิดปากเช่นกัน ทำได้เฉพาะคำขอติดตามเท่านั้น และไม่สามารถกล่าวถึงบัญชีในเซิร์ฟเวอร์นี้ได้หากไม่ได้ถูกติดตามกลับ | สิ่งนี้ไม่มีผลต่ออินสแตนซ์ที่ถูกบล็อก" mediaSilencedInstances: "เซิร์ฟเวอร์ที่ถูกปิดปากสื่อ" mediaSilencedInstancesDescription: "ระบุโฮสต์ของเซิร์ฟเวอร์ที่ต้องการปิดปากสื่อ คั่นด้วยการขึ้นบรรทัดใหม่, ไฟล์ที่ถูกส่งจากบัญชีของเซิร์ฟเวอร์ดังกล่าวจะถือว่าถูกปิดปาก แล้วจะถูกติดเครื่องหมายว่ามีเนื้อหาละเอียดอ่อน และเอโมจิแบบกำหนดเองก็จะใช้ไม่ได้ด้วย | สิ่งนี้ไม่มีผลต่ออินสแตนซ์ที่ถูกบล็อก" -federationAllowedHosts: "เซิร์ฟเวอร์ที่เปิดให้บริการแบบเฟเดอเรชั่น" -federationAllowedHostsDescription: "ระบุชื่อโฮสต์ของเซิร์ฟเวอร์ที่คุณต้องการอนุญาตให้เชื่อมต่อแบบเฟเดอเรชั่น โดยต้องเว้นวรรคแต่ละบรรทัด" +federationAllowedHosts: "เซิร์ฟเวอร์ที่อนุญาตให้เชื่อมกับสหพันธ์" +federationAllowedHostsDescription: "ระบุโฮสต์ของเซิร์ฟเวอร์ที่อนุญาตให้เชื่อมกับสหพันธ์ โดยแยกแต่ละรายการด้วยบรรทัดใหม่" muteAndBlock: "ปิดเสียงและบล็อก" mutedUsers: "ผู้ใช้ที่ถูกปิดเสียง" blockedUsers: "ผู้ใช้ที่ถูกบล็อก" @@ -298,9 +298,11 @@ uploadFromUrl: "อัปโหลดจาก URL" uploadFromUrlDescription: "URL ของไฟล์ที่คุณต้องการอัปโหลด" uploadFromUrlRequested: "ร้องขอการอัปโหลดแล้ว" uploadFromUrlMayTakeTime: "การอัปโหลดอาจใช้เวลาสักครู่จึงจะเสร็จสมบูรณ์" +uploadNFiles: "อัปโหลด {n} ไฟล์" explore: "สำรวจ" messageRead: "อ่านแล้ว" noMoreHistory: "ไม่มีประวัติเพิ่มเติม" +startChat: "เริ่มแชต" nUsersRead: "อ่านโดย {n}" agreeTo: "ฉันยอมรับ {0}" agree: "ยอมรับ" @@ -325,6 +327,7 @@ dark: "มืด" lightThemes: "ธีมสว่าง" darkThemes: "ธีมมืด" syncDeviceDarkMode: "ซิงค์โหมดมืดกับการตั้งค่าอุปกรณ์ของคุณ" +switchDarkModeManuallyWhenSyncEnabledConfirm: "“{x}” เปิดอยู่ ต้องการปิดการซิงค์และสลับโหมดด้วยตนเองหรือไม่?" drive: "ไดรฟ์" fileName: "ชื่อไฟล์" selectFile: "เลือกไฟล์" @@ -365,7 +368,7 @@ reject: "ปฏิเสธ" normal: "ปกติ" instanceName: "ชื่อเซิร์ฟเวอร์" instanceDescription: "คำอธิบายแนะนำเซิร์ฟเวอร์" -maintainerName: "ผู้ดูแล" +maintainerName: "ชื่อผู้ดูแลระบบ" maintainerEmail: "อีเมลผู้ดูแลระบบ" tosUrl: "URL เงื่อนไขการให้บริการ" thisYear: "ปีนี้" @@ -423,6 +426,7 @@ antennaExcludeBots: "ยกเว้นบัญชีบอต" antennaKeywordsDescription: "คั่นด้วยเว้นวรรคสำหรับเงื่อนไข AND, หรือขึ้นบรรทัดใหม่สำหรับเงื่อนไข OR" notifyAntenna: "แจ้งเตือนเกี่ยวกับโน้ตใหม่" withFileAntenna: "เฉพาะโน้ตที่มีไฟล์" +excludeNotesInSensitiveChannel: "ไม่รวมโน้ตจากช่องเนื้อหาละเอียดอ่อน" enableServiceworker: "เปิดใช้งานการแจ้งเตือนแบบพุชไปยังเบราว์เซอร์ของคุณ" antennaUsersDescription: "ระบุหนึ่งชื่อผู้ใช้ต่อบรรทัด" caseSensitive: "อักษรพิมพ์ใหญ่-พิมพ์เล็กความหมายต่างกัน" @@ -453,17 +457,17 @@ totpDescription: "ใช้แอปยืนยันตัวตนเพื moderator: "ผู้ควบคุม" moderation: "การกลั่นกรอง" moderationNote: "โน้ตการกลั่นกรอง" -moderationNoteDescription: "คุณสามารถใส่โน้ตส่วนตัวที่เฉพาะผู้ดูแลระบบเท่านั้นที่สามารถเข้าถึงได้" +moderationNoteDescription: "สามารถจดเมโมที่จะแบ่งปันเฉพาะระหว่างผู้ควบคุมได้" addModerationNote: "เพิ่มโน้ตการกลั่นกรอง" moderationLogs: "ปูมการควบคุมดูแล" nUsersMentioned: "กล่าวถึงโดยผู้ใช้ {n} ราย" -securityKeyAndPasskey: "ความปลอดภัยและรหัสผ่าน" -securityKey: "กุญแจความปลอดภัย" +securityKeyAndPasskey: "Security key และ Passkey" +securityKey: "Security Key" lastUsed: "ใช้ล่าสุด" lastUsedAt: "ใช้งานครั้งล่าสุด: {t}" unregister: "เลิกติดตาม" passwordLessLogin: "เข้าสู่ระบบแบบไม่ใช้รหัสผ่าน" -passwordLessLoginDescription: "อนุญาตให้เข้าสู่ระบบโดยไม่ต้องใช้รหัสผ่านโดยใช้รหัสรักษาความปลอดภัยหรือรหัสผ่านเท่านั้น" +passwordLessLoginDescription: "เข้าสู่ระบบโดยไม่ใช้รหัสผ่าน โดยใช้เฉพาะ Security Key หรือ Passkey เท่านั้น" resetPassword: "รีเซ็ตรหัสผ่าน" newPasswordIs: "รหัสผ่านใหม่คือ “{password}”" reduceUiAnimation: "ลดภาพเคลื่อนไหว UI" @@ -573,8 +577,10 @@ showFixedPostForm: "แสดงแบบฟอร์มการโพสต์ showFixedPostFormInChannel: "แสดงแบบฟอร์มการโพสต์ที่ด้านบนของไทม์ไลน์ (ช่อง)" withRepliesByDefaultForNewlyFollowed: "แสดงการตอบกลับจากผู้ใช้ที่คุณเพิ่งติดตามลงไทม์ไลน์ตามค่าเริ่มต้น" newNoteRecived: "มีโน้ตใหม่" +newNote: "โน้ตใหม่" sounds: "เสียง" sound: "เสียง" +notificationSoundSettings: "ตั้งค่าเสียงแจ้งเตือน" listen: "ฟัง" none: "ไม่มี" showInPage: "แสดงในเพจ" @@ -606,8 +612,8 @@ output: "เอาท์พุต" script: "สคริปต์" disablePagesScript: "ปิดการใช้งาน AiScript บนเพจ" updateRemoteUser: "อัปเดตข้อมูลผู้ใช้งานระยะไกล" -unsetUserAvatar: "เลิกตั้งอวตาร" -unsetUserAvatarConfirm: "ต้องการเลิกตั้งอวตารใข่ไหม?" +unsetUserAvatar: "เลิกตั้งไอคอน" +unsetUserAvatarConfirm: "ต้องการเลิกตั้งไอคอนประจำตัวหรือไม่?" unsetUserBanner: "เลิกตั้งแบนเนอร์" unsetUserBannerConfirm: "ต้องการเลิกตั้งแบนเนอร์?" deleteAllFiles: "ลบไฟล์ทั้งหมด" @@ -682,13 +688,15 @@ smtpSecure: "ใช้โดยนัย SSL/TLS สำหรับการเ smtpSecureInfo: "ปิดสิ่งนี้เมื่อใช้ STARTTLS" testEmail: "ทดสอบการส่งอีเมล" wordMute: "ปิดเสียงคำ" +wordMuteDescription: "ย่อโน้ตที่มีวลีที่ระบุ สามารถดูโน้ตที่ย่อแล้วได้โดยคลิกที่โน้ตเหล่านั้น" hardWordMute: "ปิดเสียงคำแบบแข็งโป๊ก" -hardWordMuteDescription: "ซ่อนหมายเหตุที่มีวลีที่ระบุ ต่างจากการปิดเสียงคำ โน้ตต่างๆ จะถูกซ่อนไว้อย่างสมบูรณ์" +showMutedWord: "แสดงคำที่ถูกปิดเสียง" +hardWordMuteDescription: "จะซ่อนโน้ตที่มีคำที่ระบุไว้ ซึ่งไม่เหมือนการปิดเสียงคำ ในกรณีนี้โน้ตจะไม่แสดงเลย" regexpError: "เกิดข้อผิดพลาดใน regular expression" regexpErrorDescription: "เกิดข้อผิดพลาดใน regular expression บรรทัดที่ {line} ของการปิดเสียงคำ {tab} :" instanceMute: "ปิดเสียงเซิร์ฟเวอร์" userSaysSomething: "{name} พูดอะไรบางอย่าง" -userSaysSomethingAbout: "{name} พูดอะไรบางอย่างเกี่ยวกับ \"{word}\"" +userSaysSomethingAbout: "{name} พูดบางอย่างเกี่ยวกับ “{word}”" makeActive: "เปิดใช้งาน" display: "แสดงผล" copy: "คัดลอก" @@ -758,7 +766,7 @@ yes: "ใช่" no: "ไม่" driveFilesCount: "จำนวนไฟล์ไดรฟ์" driveUsage: "การใช้พื้นที่ไดรฟ์" -noCrawle: "ปฏิเสธการจัดทำดัชนีของโปรแกรมรวบรวมข้อมูล" +noCrawle: "ปฏิเสธการจัดทำดัชนีของ Crawler (โปรแกรมรวบรวมข้อมูล)" noCrawleDescription: "ขอให้เครื่องมือค้นหาไม่จัดทำดัชนีหน้าโปรไฟล์ โน้ต หน้าเพจ ฯลฯ" lockedAccountInfo: "แม้ว่าการอนุมัติการติดตามถูกเปิดใช้งานอยู่ทุกคนก็ยังคงสามารถเห็นโน้ตของคุณได้ เว้นแต่ว่าคุณจะเปลี่ยนการเปิดเผยโน้ตของคุณเป็น “เฉพาะผู้ติดตาม”" alwaysMarkSensitive: "ทำเครื่องหมายว่ามีเนื้อหาละเอียดอ่อนเป็นค่าเริ่มต้น" @@ -768,7 +776,7 @@ highlightSensitiveMedia: "ไฮไลท์สื่อที่มีเนื verificationEmailSent: "ได้ส่งอีเมลยืนยันแล้ว กรุณาเข้าลิงก์ที่ระบุในอีเมลเพื่อทำการตั้งค่าให้เสร็จสิ้น" notSet: "ไม่ได้ตั้งค่า" emailVerified: "อีเมลได้รับการยืนยันแล้ว" -noteFavoritesCount: "จำนวนโน้ตที่ชื่นชอบ" +noteFavoritesCount: "จำนวนโน้ตโปรด" pageLikesCount: "จำนวนเพจที่ถูกใจ" pageLikedCount: "จำนวนการกดถูกใจเพจที่ได้รับแล้ว" contact: "ติดต่อ" @@ -881,7 +889,7 @@ previewNoteText: "แสดงตัวอย่าง" customCss: "CSS ที่กำหนดเอง" customCssWarn: "ควรใช้การตั้งค่านี้เฉพาะต่อเมื่อคุณรู้มันใช้ทำอะไร การตั้งค่าที่ไม่เหมาะสมอาจทำให้ไคลเอ็นต์ไม่สามารถใช้งานได้อย่างถูกต้อง" global: "ทั่วโลก" -squareAvatars: "แสดงผลอวตารเป็นสี่เหลี่ยม" +squareAvatars: "แสดงไอคอนประจำตัวเป็นสี่เหลี่ยม" sent: "ส่ง" received: "ได้รับแล้ว" searchResult: "ผลการค้นหา" @@ -948,6 +956,9 @@ oneHour: "1 ชั่วโมง" oneDay: "1 วัน" oneWeek: "1 สัปดาห์" oneMonth: "หนึ่งเดือน" +threeMonths: "3 เดือน" +oneYear: "1 ปี" +threeDays: "3 วัน" reflectMayTakeTime: "อาจจำเป็นต้องใช้เวลาสักระยะหนึ่งจึงจะเห็นแสดงผลได้นะ" failedToFetchAccountInformation: "ไม่สามารถเรียกดึงข้อมูลบัญชีได้" rateLimitExceeded: "เกินขีดจำกัดอัตรา" @@ -972,6 +983,7 @@ document: "เอกสาร" numberOfPageCache: "จำนวนหน้าเพจที่แคช" numberOfPageCacheDescription: "การเพิ่มจำนวนนี้จะช่วยเพิ่มความสะดวกให้กับผู้ใช้งาน แต่จะทำให้เซิร์ฟเวอร์โหลดมากขึ้นและต้องใช้หน่วยความจำมากขึ้นอีกด้วย" logoutConfirm: "ต้องการออกจากระบบใช่ไหม?" +logoutWillClearClientData: "เมื่อออกจากระบบ ข้อมูลการตั้งค่าของไคลเอนต์จะถูกลบออกจากเบราว์เซอร์ เพื่อให้สามารถกู้คืนข้อมูลการตั้งค่าได้เมื่อกลับมาเข้าสู่ระบบอีกครั้ง โปรดเปิดใช้งานการสำรองข้อมูลการตั้งค่าอัตโนมัติ" lastActiveDate: "ใช้งานล่าสุดเมื่อ" statusbar: "แถบสถานะ" pleaseSelect: "ตัวเลือก" @@ -990,6 +1002,7 @@ failedToUpload: "การอัปโหลดล้มเหลว" cannotUploadBecauseInappropriate: "ไม่สามารถอัปโหลดไฟล์นี้ได้เนื่องจากระบบตรวจพบบางส่วนของไฟล์ว่านี้อาจจะเป็น NSFW" cannotUploadBecauseNoFreeSpace: "ไม่สามารถอัปโหลดได้เนื่องจากไม่มีพื้นที่ว่างในไดรฟ์เหลือแล้ว" cannotUploadBecauseExceedsFileSizeLimit: "ไม่สามารถอัปโหลดไฟล์นี้ได้แล้วเนื่องจากเกินขีดจำกัดของขนาดไฟล์แล้ว" +cannotUploadBecauseUnallowedFileType: "ไม่สามารถอัปโหลดได้เนื่องจากเป็นชนิดไฟล์ที่ไม่ได้รับอนุญาต" beta: "เบต้า" enableAutoSensitive: "ทำเครื่องหมายว่ามีเนื้อหาที่ละเอียดอ่อนโดยอัตโนมัติ" enableAutoSensitiveDescription: "อนุญาตให้ตรวจหาและทำเครื่องหมายสื่อว่ามีเนื้อหาโดยละเอียดอ่อนโดยอัตโนมัติ ผ่าน Machine Learning หากเป็นไปได้ แม้ว่าคุณจะปิดคุณสมบัตินี้ ก็อาจถูกตั้งค่าโดยอัตโนมัติ ทั้งนี้ขึ้นอยู่กับเซิร์ฟเวอร์" @@ -1009,7 +1022,7 @@ windowMaximize: "ขยายใหญ่สุด" windowMinimize: "ย่อเล็กที่สุด" windowRestore: "เลิกทำ" caption: "คำอธิบาย" -loggedInAsBot: "ล็อกอินเป็นบอตอยู่ในขณะนี้" +loggedInAsBot: "เข้าสู่ระบบเป็นบอตอยู่ในขณะนี้" tools: "เครื่องมือ" cannotLoad: "ไม่สามารถโหลดได้" numberOfProfileView: "มุมมองโปรไฟล์" @@ -1041,6 +1054,7 @@ permissionDeniedError: "การดำเนินถูกปฏิเสธ" permissionDeniedErrorDescription: "บัญชีนี้ไม่มีสิทธิ์อนุญาตในการดำเนินการนี้" preset: "พรีเซ็ต" selectFromPresets: "เลือกจากการพรีเซ็ต" +custom: "แบบกำหนดเอง" achievements: "ความสำเร็จ" gotInvalidResponseError: "การตอบสนองเซิร์ฟเวอร์ไม่ถูกต้อง" gotInvalidResponseErrorDescription: "เซิร์ฟเวอร์อาจไม่สามารถเข้าถึงได้หรืออาจจะกำลังอยู่ในระหว่างปรับปรุง กรุณาลองใหม่อีกครั้งในภายหลังนะคะ" @@ -1058,7 +1072,7 @@ exploreOtherServers: "มองหาเซิร์ฟเวอร์อื่ letsLookAtTimeline: "มาดูไทม์ไลน์กัน" disableFederationConfirm: "ปิดใช้งานสหพันธ์เลยใช่ไหม?" disableFederationConfirmWarn: "โพสต์จะยังคงเป็นสาธารณะต่อไป เว้นแต่จะตั้งค่าเป็นอย่างอื่น" -disableFederationOk: "ปิดการใช้งาน" +disableFederationOk: "ปิดการใช้งานสหพันธ์" invitationRequiredToRegister: "เซิร์ฟเวอร์นี้เป็นแบบรับเชิญ เฉพาะผู้มีรหัสเชิญเท่านั้นถึงสามารถลงทะเบียนได้" emailNotSupported: "เซิร์ฟเวอร์นี้ไม่รองรับการส่งอีเมล" postToTheChannel: "โพสต์ลงช่อง" @@ -1079,6 +1093,7 @@ prohibitedWordsDescription2: "ถ้าแยกด้วยเว้นวร hiddenTags: "แฮชแท็กที่ซ่อนอยู่" hiddenTagsDescription: "เลือกแท็กที่จะไม่แสดงในรายการเทรนด์ สามารถลงทะเบียนหลายแท็กได้โดยขึ้นบรรทัดใหม่" notesSearchNotAvailable: "การค้นหาโน้ตไม่พร้อมใช้งาน" +usersSearchNotAvailable: "การค้นหาผู้ใช้ไม่พร้อมใช้งาน" license: "ใบอนุญาต" unfavoriteConfirm: "ลบออกจากรายการโปรดแน่ใจหรอ?" myClips: "คลิปของฉัน" @@ -1088,7 +1103,7 @@ retryAllQueuesConfirmTitle: "ลองใหม่ทั้งหมดจริ retryAllQueuesConfirmText: "สิ่งนี้จะเพิ่มการโหลดเซิร์ฟเวอร์ชั่วคราวนะ" enableChartsForRemoteUser: "สร้างแผนภูมิข้อมูลผู้ใช้ระยะไกล" enableChartsForFederatedInstances: "สร้างแผนภูมิของเซิร์ฟเวอร์ระยะไกล" -enableStatsForFederatedInstances: "ดึงข้อมูลสถิติจากเซิร์ฟเวอร์ที่อยู่ห่างไกล" +enableStatsForFederatedInstances: "ดึงข้อมูลจากเซิร์ฟเวอร์ระยะไกล" showClipButtonInNoteFooter: "เพิ่ม “คลิป” ไปยังเมนูสั่งการของโน้ต" reactionsDisplaySize: "ขนาดของรีแอคชั่น" limitWidthOfReaction: "จำกัดความกว้างสูงสุดของรีแอคชั่นและแสดงให้เล็กลง" @@ -1219,18 +1234,17 @@ impressumDescription: "การติดป้ายกำกับ (Impressum) privacyPolicy: "นโยบายความเป็นส่วนตัว" privacyPolicyUrl: "URL นโยบายความเป็นส่วนตัว" tosAndPrivacyPolicy: "เงื่อนไขในการให้บริการและนโยบายความเป็นส่วนตัว" -avatarDecorations: "การตกแต่งอวตาร" +avatarDecorations: "ของตกแต่งไอคอน" attach: "แนบ" detach: "นำออก" detachAll: "เอาออกทั้งหมด" angle: "แองเกิล" flip: "พลิก" -showAvatarDecorations: "แสดงตกแต่งอวตาร" +showAvatarDecorations: "แสดงของตกแต่งไอคอน" releaseToRefresh: "ปล่อยเพื่อรีเฟรช" refreshing: "กำลังรีเฟรช..." pullDownToRefresh: "ดึงลงเพื่อรีเฟรช" useGroupedNotifications: "แสดงผลการแจ้งเตือนแบบกลุ่มแล้ว" -signupPendingError: "มีปัญหาในการตรวจสอบที่อยู่อีเมลลิงก์อาจหมดอายุแล้ว" cwNotationRequired: "หากเปิดใช้งาน “ซ่อนเนื้อหา” จะต้องระบุคำอธิบาย" doReaction: "เพิ่มรีแอคชั่น" code: "โค้ด" @@ -1281,51 +1295,216 @@ clipNoteLimitExceeded: "ไม่สามารถเพิ่มโน้ต performance: "ประสิทธิภาพ​" modified: "แก้ไข" discard: "ละทิ้ง" -thereAreNChanges: "มีอยู่ {n} เปลี่ยนแปลง(s)" +thereAreNChanges: "มีการเปลี่ยนแปลง {n} รายการ" signinWithPasskey: "ลงชื่อเข้าใช้ด้วย Passkey" -unknownWebAuthnKey: "พาสคีย์ไม่ถูกต้องค่ะ" -passkeyVerificationFailed: "การยืนยันกุญแจดิจิทัลไม่สำเร็จค่ะ" -passkeyVerificationSucceededButPasswordlessLoginDisabled: "การยืนยันพาสคีย์สำเร็จแล้ว แต่การลงชื่อเข้าใช้แบบไม่ต้องใส่รหัสผ่านถูกปิดใช้งานแล้ว" +unknownWebAuthnKey: "เป็น Passkey ที่ยังไม่ได้ลงทะเบียน" +passkeyVerificationFailed: "การยืนยัน Passkey ล้มเหลว" +passkeyVerificationSucceededButPasswordlessLoginDisabled: "การยืนยัน Passkey สำเร็จ แต่การเข้าสู่ระบบแบบไม่ใช้รหัสผ่านถูกปิดใช้งานอยู่" messageToFollower: "ข้อความถึงผู้ติดตาม" target: "เป้า" testCaptchaWarning: "ฟังก์ชันนี้มีไว้สำหรับทดสอบ CAPTCHA เท่านั้น\nห้ามนำไปใช้ในระบบจริงโดยเด็ดขาด" prohibitedWordsForNameOfUser: "คำนี้ไม่สามารถใช้เป็นชื่อผู้ใช้ได้" -prohibitedWordsForNameOfUserDescription: "หากมีสตริงใดๆ ในรายการนี้ปรากฏอยู่ในชื่อของผู้ใช้ ชื่อนั้นจะถูกปฏิเสธ ผู้ใช้ที่มีสิทธิ์แต่ผู้ดูแลระบบนั้นจะไม่ได้รับผลกระทบใดๆจากข้อจำกัดนี้ค่ะ" +prohibitedWordsForNameOfUserDescription: "จะไม่อนุญาตให้เปลี่ยนชื่อผู้ใช้หากชื่อของผู้ใช้มีข้อความที่อยู่ในรายการนี้ แต่ผู้ใช้ที่มีสิทธิ์เป็นผู้ควบคุมจะไม่ได้รับผลกระทบจากข้อจำกัดนี้" yourNameContainsProhibitedWords: "ชื่อของคุณนั้นมีคำที่ต้องห้าม" yourNameContainsProhibitedWordsDescription: "ถ้าหากคุณต้องการใช้ชื่อนี้ กรุณาติดต่อผู้ดูแลระบบของเซิร์ฟเวอร์นะค่ะ" -federationDisabled: "เซิร์ฟเวอร์นี้ปิดการใช้งานการรวมกลุ่ม คุณไม่สามารถโต้ตอบกับผู้ใช้บนเซิร์ฟเวอร์อื่นได้" -reactAreYouSure: "คุณต้องการที่จะตอบสนองต่อ \" {emoji}\" หรือไม่?" -markAsSensitiveConfirm: "คุณต้องการทำเครื่องหมายสื่อนี้ว่าละเอียดอ่อนหรือไม่?" -unmarkAsSensitiveConfirm: "คุณต้องการลบการกำหนดความไวของสื่อนี้หรือไม่?" +thisContentsAreMarkedAsSigninRequiredByAuthor: "ผู้โพสต์ได้ตั้งค่าว่าต้องเข้าสู่ระบบจึงจะสามารถดูได้" +lockdown: "ล็อกดาวน์" +pleaseSelectAccount: "โปรดเลือกบัญชี" +availableRoles: "บทบาทที่ใช้ได้" +acknowledgeNotesAndEnable: "เปิดใช้งานหลังจากที่เข้าใจข้อควรระวังแล้ว" +federationSpecified: "เซิร์ฟเวอร์นี้ดำเนินงานในระบบกลุ่มไวท์ลิสต์ ไม่สามารถติดต่อกับเซิร์ฟเวอร์อื่นที่ไม่ได้รับอนุญาตจากผู้ดูแลระบบได้" +federationDisabled: "เซิร์ฟเวอร์นี้ปิดใช้งานสหพันธ์ ไม่สามารถติดต่อหรือแลกเปลี่ยนข้อมูลกับผู้ใช้จากเซิร์ฟเวอร์อื่นได้" +draft: "ร่าง" +confirmOnReact: "ยืนยันเมื่อทำการรีแอคชั่น" +reactAreYouSure: "ต้องการใส่รีแอคชั่นด้วย \"{emoji}\" หรือไม่?" +markAsSensitiveConfirm: "ต้องการตั้งค่าสื่อนี้ว่าเป็นเนื้อหาละเอียดอ่อนหรือไม่?" +unmarkAsSensitiveConfirm: "ต้องการยกเลิกการระบุว่าสื่อนี้มีเนื้อหาละเอียดอ่อนหรือไม่?" preferences: "การตั้งค่าสภาพแวดล้อม" +accessibility: "การช่วยการเข้าถึง" preferencesProfile: "โปรไฟล์การกำหนดค่า" +copyPreferenceId: "คัดลือก ID การตั้งค่า" +resetToDefaultValue: "คืนค่าเป็นค่าเริ่มต้น" +overrideByAccount: "เขียนทับด้วยบัญชี" +untitled: "ไม่มีชื่อ" +noName: "ไม่มีชื่อ" +skip: "ข้าม" +restore: "กู้คืน" +syncBetweenDevices: "ซิงค์ระหว่างอุปกรณ์" preferenceSyncConflictTitle: "การตั้งค่ามีอยู่บนเซิร์ฟเวอร์" -preferenceSyncConflictText: "รายการการตั้งค่าที่เปิดใช้งานการซิงโครไนซ์จะจัดเก็บค่าไว้บนเซิร์ฟเวอร์ และพบค่าที่จัดเก็บบนเซิร์ฟเวอร์สำหรับรายการการตั้งค่านี้ คุณต้องการทำอย่างไร?" +preferenceSyncConflictText: "การตั้งค่าที่เปิดใช้งานการซิงค์จะบันทึกค่าลงในเซิร์ฟเวอร์ อย่างไรก็ดี พบว่ามีค่าการตั้งค่านี้ที่เคยบันทึกไว้ในเซิร์ฟเวอร์แล้ว ต้องการดำเนินการอย่างไร?" +preferenceSyncConflictChoiceMerge: "รวมเข้าด้วยกัน" +preferenceSyncConflictChoiceServer: "เขียนทับด้วยค่าการตั้งค่าเซิร์ฟเวอร์" +preferenceSyncConflictChoiceDevice: "เขียนทับด้วยค่าการตั้งค่าอุปกรณ์" +preferenceSyncConflictChoiceCancel: "ยกเลิกการเปิดใช้งานการซิงค์" +paste: "วาง" +emojiPalette: "จานสีเอโมจิ" postForm: "แบบฟอร์มการโพสต์" +textCount: "จำนวนอักขระ" information: "เกี่ยวกับ" +chat: "แชต" +migrateOldSettings: "ย้ายข้อมูลการตั้งค่าเก่า" +migrateOldSettings_description: "โดยปกติจะทำโดยอัตโนมัติ แต่หากด้วยเหตุผลบางประการที่ไม่สามารถย้ายได้สำเร็จ สามารถสั่งย้ายด้วยตนเองได้ การตั้งค่าปัจจุบันจะถูกเขียนทับ" +compress: "บีบอัด" right: "ขวา" bottom: "ภายใต้" +top: "บน" +embed: "ฝัง" +settingsMigrating: "กำลังย้ายการตั้งค่า กรุณารอสักครู่... (สามารถย้ายด้วยตนเองภายหลังได้ที่ การตั้งค่า → อื่นๆ → ย้ายข้อมูลการตั้งค่าเก่า)" +readonly: "อ่านได้อย่างเดียว" +goToDeck: "กลับไปยังเด็ค" +federationJobs: "งานสหพันธ์" +driveAboutTip: "ในไดรฟ์จะแสดงรายการไฟล์ที่เคยอัปโหลดไว้ก่อนหน้า
\nสามารถนำมาใช้ซ้ำเมื่อแนบไฟล์ในโน้ต หรือตั้งค่าให้อัปโหลดไฟล์ล่วงหน้าเพื่อนำไปโพสต์ทีหลังได้
\nโปรดระวัง เมื่อลบไฟล์ ไฟล์นั้นจะไม่แสดงในทุกที่ที่เคยใช้ไฟล์นี้ (โน้ต, หน้าเพจ, อวตาร, แบนเนอร์ ฯลฯ)
\nสามารถสร้างโฟลเดอร์เพื่อจัดระเบียบได้" +scrollToClose: "เลื่อนเพื่อปิด" +advice: "คำแนะนำ" +realtimeMode: "โหมดเรียลไทม์" +turnItOn: "เปิดใช้งาน" +turnItOff: "ปิดใช้งาน" +emojiMute: "ปิดเสียงเอโมจิ" +emojiUnmute: "เลิกปิดเสียงเอโมจิ" +muteX: "ปิดเสียง {x}" +unmuteX: "เลิกปิดเสียง {x}" +abort: "หยุดและยกเลิก" +tip: "คำแนะนำและเคล็ดลับ" +redisplayAllTips: "แสดงคำแนะนำและเคล็ดลับทั้งหมดอีกครั้ง" +hideAllTips: "ซ่อนคำแนะนำและเคล็ดลับทั้งหมด" +defaultImageCompressionLevel: "ความละเอียดเริ่มต้นสำหรับการบีบอัดภาพ" +defaultImageCompressionLevel_description: "หากตั้งค่าต่ำ จะรักษาคุณภาพภาพได้ดีขึ้นแต่ขนาดไฟล์จะเพิ่มขึ้น
หากตั้งค่าสูง จะลดขนาดไฟล์ได้ แต่คุณภาพภาพจะลดลง" +inMinutes: "นาที" +inDays: "วัน" +safeModeEnabled: "โหมดปลอดภัยถูกเปิดใช้งาน" +pluginsAreDisabledBecauseSafeMode: "เนื่องจากโหมดปลอดภัยถูกเปิดใช้งาน ปลั๊กอินทั้งหมดจึงถูกปิดใช้งาน" +customCssIsDisabledBecauseSafeMode: "เนื่องจากโหมดปลอดภัยถูกเปิดใช้งาน CSS แบบกำหนดเองจึงไม่ได้ถูกนำมาใช้" +themeIsDefaultBecauseSafeMode: "ในระหว่างที่โหมดปลอดภัยถูกเปิดใช้งาน จะใช้ธีมเริ่มต้น เมื่อปิดโหมดปลอดภัยจะกลับคืนดังเดิม" +thankYouForTestingBeta: "ขอบคุณที่ให้ความร่วมมือในการทดสอบเวอร์ชันเบต้า!" +_order: + newest: "เรียงจากใหม่ไปเก่า" + oldest: "เรียงจากเก่าไปใหม่" _chat: + noMessagesYet: "ยังไม่มีข้อความ" + newMessage: "ข้อความใหม่" + individualChat: "แชตส่วนตัว" + individualChat_description: "สามารถแชตแบบตัวต่อตัวกับผู้ใช้ที่ระบุไว้ได้" + roomChat: "ห้องแชต" + roomChat_description: "สามารถแชตแบบกลุ่มหลายคนได้\nและสามารถแชตกับผู้ใช้ที่ไม่ได้อนุญาตแชตส่วนตัวได้ หากอีกฝ่ายยอมรับ" + createRoom: "สร้างห้อง" + inviteUserToChat: "เชิญผู้ใช้และเริ่มแชตได้เลย" + yourRooms: "ห้องที่สร้างไว้" + joiningRooms: "ห้องที่เข้าร่วมอยู่" invitations: "คำเชิญ" + noInvitations: "ไม่มีคำเชิญ" + history: "ประวัติ" noHistory: "ไม่มีประวัติ" + noRooms: "ไม่มีห้อง" + inviteUser: "เชิญผู้ใช้" + sentInvitations: "คำเชิญที่ส่งไปแล้ว" + join: "เข้าร่วม" + ignore: "ไม่สนใจ" + leave: "ออกจากห้อง" members: "สมาชิก" + searchMessages: "ค้นหาข้อความ" home: "หน้าหลัก" send: "ส่ง" + newline: "ขึ้นบรรทัดใหม่" + muteThisRoom: "ปิดเสียงห้องนี้" + deleteRoom: "ลบห้อง" + chatNotAvailableForThisAccountOrServer: "แชตไม่ได้เปิดใช้งานบนเซิร์ฟเวอร์นี้ หรือบัญชีนี้" + chatIsReadOnlyForThisAccountOrServer: "แชตบนเซิร์ฟเวอร์นี้ หรือบัญชีนี้ เป็นแบบอ่านอย่างเดียว ไม่สามารถส่งข้อความใหม่ สร้างหรือเข้าร่วมห้องแชตได้" + chatNotAvailableInOtherAccount: "บัญชีคู่สนทนาไม่สามารถใช้ฟังก์ชันแชตได้" + cannotChatWithTheUser: "ไม่สามารถเริ่มแชตกับผู้ใช้นี้ได้" + cannotChatWithTheUser_description: "แชตใช้งานไม่ได้ หรือคู่สนทนายังไม่ได้เปิดแชต" + youAreNotAMemberOfThisRoomButInvited: "คุณไม่ได้เป็นผู้เข้าร่วมห้องนี้ แต่มีคำเชิญส่งมา หากต้องการเข้าร่วม กรุณายืนยันคำเชิญ" + doYouAcceptInvitation: "ต้องการยอมรับคำเชิญหรือไม่?" + chatWithThisUser: "แชตเลย" + thisUserAllowsChatOnlyFromFollowers: "ผู้ใช้นี้รับแชตเฉพาะจากผู้ติดตามเท่านั้น" + thisUserAllowsChatOnlyFromFollowing: "ผู้ใช้นี้รับแชตเฉพาะจากผู้ที่เขาติดตามเท่านั้น" + thisUserAllowsChatOnlyFromMutualFollowing: "ผู้ใช้นี้รับแชตเฉพาะจากผู้ที่ติดตามซึ่งกันและกันทั้งสองฝ่ายเท่านั้น" + thisUserNotAllowedChatAnyone: "ผู้ใช้นี้ไม่รับแชตจากใครเลย" + chatAllowedUsers: "ผู้ที่อนุญาตให้แชตด้วย" + chatAllowedUsers_note: "ไม่ว่าจะตั้งค่ายังไง คุณยังสามารถแชตกับคนที่คุณส่งข้อความไปหาได้" + _chatAllowedUsers: + everyone: "ใครก็ได้หมด" + followers: "เฉพาะผู้ติดตามเท่านั้น" + following: "เฉพาะผู้ที่ตัวเองติดตามเท่านั้น" + mutual: "เฉพาะผู้ใช้ที่ติดตามซึ่งกันและกันทั้งสองฝ่ายเท่านั้น" + none: "ไม่อนุญาตให้ใครเลย" +_emojiPalette: + palettes: "จานสี" + enableSyncBetweenDevicesForPalettes: "เปิดใช้งานการซิงค์จานสีระหว่างอุปกรณ์" + paletteForMain: "จานสีหลักที่ใช้" + paletteForReaction: "จานสีที่ใช้ในการรีแอคชั่น" _settings: + driveBanner: "สามารถจัดการและตั้งค่าไดรฟ์ ตรวจสอบการใช้งาน และตั้งค่าการอัปโหลดไฟล์ได้" + pluginBanner: "สามารถขยายความสามารถของไคลเอนต์ด้วยปลั๊กอินได้ ติดตั้ง ตั้งค่า และจัดการปลั๊กอินแต่ละตัวได้" + notificationsBanner: "สามารถตั้งค่าประเภทและขอบเขตของการแจ้งเตือนที่รับจากเซิร์ฟเวอร์ รวมถึงการแจ้งเตือนแบบพุช" + api: "API" webhook: "Webhook" + serviceConnection: "การเชื่อมต่อกับบริการ" + serviceConnectionBanner: "สามารถจัดการและตั้งค่าโทเค็นการเข้าถึงและ Webhook เพื่อเชื่อมต่อกับแอปหรือบริการภายนอกได้" + accountData: "ข้อมูลบัญชี" + accountDataBanner: "สามารถจัดการข้อมูลบัญชีได้โดยส่งออกหรือนำเข้าไฟล์เก็บถาวร" + muteAndBlockBanner: "สามารถตั้งค่าการซ่อนเนื้อหา และจำกัดการกระทำจากผู้ใช้เฉพาะรายได้" + accessibilityBanner: "สามารถปรับแต่งรูปลักษณ์และพฤติกรรมของไคลเอนต์เพื่อให้เหมาะกับการใช้งานของตนเองมากขึ้น" + privacyBanner: "สามารถตั้งค่าความเป็นส่วนตัวของบัญชี เช่น ขอบเขตการเผยแพร่เนื้อหา ความสามารถในการค้นหา และการอนุมัติผู้ติดตาม" + securityBanner: "สามารถตั้งค่าความปลอดภัยของบัญชี เช่น รหัสผ่าน วิธีการเข้าสู่ระบบ แอปยืนยันตัวตน Passkey เป็นต้น" preferencesBanner: "คุณสามารถกำหนดค่าพฤติกรรมโดยรวมของไคลเอนต์ได้ตามความต้องการของคุณ" + appearanceBanner: "สามารถตั้งค่ารูปลักษณ์และวิธีการแสดงผลของไคลเอนต์ตามความชอบได้" + soundsBanner: "สามารถตั้งค่าเสียงที่จะเล่นบนไคลเอนต์ได้" + timelineAndNote: "ไทม์ไลน์และโน้ต" + makeEveryTextElementsSelectable: "อนุญาตให้เลือกข้อความทั้งหมดได้" + makeEveryTextElementsSelectable_description: "หากเปิดใช้งาน อาจทำให้ความสะดวกในการใช้งานลดลงในบางสถานการณ์" + useStickyIcons: "ทำให้ไอคอนเคลื่อนตามการเลื่อน" + enableHighQualityImagePlaceholders: "แสดงภาพตัวแทนคุณภาพสูง" + uiAnimations: "ภาพเคลื่อนไหวของ UI" + showNavbarSubButtons: "แสดงปุ่มรองบนแถบนำทาง" + ifOn: "เมื่อเปิดใช้งาน" + ifOff: "เมื่อปิดใช้งาน" + enableSyncThemesBetweenDevices: "ซิงค์ธีมที่ติดตั้งระหว่างอุปกรณ์" + enablePullToRefresh: "ดึงเพื่ออัปเดต" + enablePullToRefresh_description: "สำหรับเมาส์ ให้กดปุ่มล้อกลางค้างไว้แล้วลาก" + realtimeMode_description: "เชื่อมต่อกับเซิร์ฟเวอร์และอัปเดตเนื้อหาแบบเรียลไทม์ อาจทำให้ใช้ปริมาณข้อมูลและแบตเตอรี่มากขึ้นได้" + contentsUpdateFrequency: "ความถี่ในการดึงข้อมูลเนื้อหา" + contentsUpdateFrequency_description: "ยิ่งตั้งค่าสูง เนื้อหาจะอัปเดตแบบเรียลไทม์มากขึ้น แต่ประสิทธิภาพอาจลดลง และการใช้ข้อมูลกับแบตเตอรี่จะเพิ่มมากขึ้น" + contentsUpdateFrequency_description2: "เมื่อโหมดเรียลไทม์เปิดอยู่ เนื้อหาจะอัปเดตแบบเรียลไทม์โดยไม่ขึ้นกับการตั้งค่านี้" + showUrlPreview: "แสดงตัวอย่าง URL" + showAvailableReactionsFirstInNote: "แสดงรีแอคชั่นที่ใช้ได้ไว้หน้าสุด" + showPageTabBarBottom: "แสดงแท็บบาร์ของเพจที่ด้านล่าง" + _chat: + showSenderName: "แสดงชื่อผู้ส่ง" + sendOnEnter: "กด Enter เพื่อส่ง" +_preferencesProfile: + profileName: "ชื่อโปรไฟล์" + profileNameDescription: "กรุณาตั้งชื่อเพื่อระบุอุปกรณ์นี้" + profileNameDescription2: "เช่น: “คอมเครื่องหลัก”, “มือถือ” ฯลฯ" + manageProfiles: "จัดการโปรไฟล์" +_preferencesBackup: + autoBackup: "สำรองโดยอัตโนมัติ" + restoreFromBackup: "คืนค่าจากข้อมูลสำรอง" + noBackupsFoundTitle: "ไม่พบข้อมูลสำรอง" + noBackupsFoundDescription: "ไม่พบข้อมูลสำรองที่สร้างโดยอัตโนมัติ แต่หากมีข้อมูลสำรองที่บันทึกด้วยตนเอง สามารถนำเข้ามาเพื่อกู้คืนได้" + selectBackupToRestore: "กรุณาเลือกข้อมูลสำรองที่ต้องการกู้คืน" + youNeedToNameYourProfileToEnableAutoBackup: "จำเป็นต้องตั้งชื่อโปรไฟล์ก่อนจึงจะเปิดใช้งานการสำรองข้อมูลอัตโนมัติได้" + autoPreferencesBackupIsNotEnabledForThisDevice: "ยังไม่ได้เปิดใช้งานการสำรองข้อมูลอัตโนมัติบนอุปกรณ์นี้" + backupFound: "พบข้อมูลสำรองของการตั้งค่าแล้ว" _accountSettings: requireSigninToViewContents: "ต้องเข้าสู่ระบบเพื่อดูเนื้อหา" - requireSigninToViewContentsDescription1: "ต้องเข้าสู่ระบบเพื่อดูบันทึกและเนื้อหาอื่น ๆ ทั้งหมดที่คุณสร้าง คาดว่าจะมีประสิทธิผลในการป้องกันไม่ให้ข้อมูลถูกเก็บรวบรวมโดยโปรแกรมรวบรวมข้อมูล" - requireSigninToViewContentsDescription2: "นอกจากนี้ จะไม่สามารถดูจากเซิร์ฟเวอร์ที่ไม่รองรับการดูตัวอย่าง URL (OGP), การฝังในหน้าเว็บ หรือการอ้างอิงหมายเหตุได้" - requireSigninToViewContentsDescription3: "เนื้อหาที่ถูกรวมเข้ากับเซิร์ฟเวอร์ระยะไกลอาจไม่อยู่ภายใต้ข้อจำกัดเหล่านี้" + requireSigninToViewContentsDescription1: "กำหนดให้ต้องเข้าสู่ระบบก่อนจึงจะสามารถดูโน้ตหรือเนื้อหาทั้งหมดที่สร้างไว้ได้ ซึ่งช่วยป้องกันไม่ให้ข้อมูลถูกเก็บโดยบอตหรือ Crawler (โปรแกรมรวบรวมข้อมูล)" + requireSigninToViewContentsDescription2: "จะไม่สามารถแสดงผลจากเซิร์ฟเวอร์ที่ไม่รองรับการแสดงตัวอย่าง URL (OGP), การฝังในหน้าเว็บ, หรือการอ้างอิงโน้ตได้" + requireSigninToViewContentsDescription3: "เนื้อหาที่ถูกรวมผ่านสหพันธ์จากเซิร์ฟเวอร์ระยะไกลอาจไม่อยู่ภายใต้ข้อจำกัดเหล่านี้" + makeNotesFollowersOnlyBefore: "แสดงโน้ตเก่าเฉพาะกับผู้ติดตามเท่านั้น" + makeNotesFollowersOnlyBeforeDescription: "ขณะที่เปิดฟังก์ชันนี้ โน้ตที่เก่ากว่าหรือเลยเวลาที่กำหนดจะแสดงเฉพาะกับผู้ติดตามเท่านั้น หากปิดใช้งาน สถานะการเปิดเผยจะกลับไปเป็นแบบเดิม" + makeNotesHiddenBefore: "ทำให้โน้ตเก่าทั้งหมดเป็นแบบส่วนตัว" + makeNotesHiddenBeforeDescription: "ขณะที่เปิดฟังก์ชันนี้ โน้ตที่เก่ากว่าหรือเลยเวลาที่กำหนดจะแสดงเฉพาะกับตนเอง (กลายเป็นแบบส่วนตัว) หากปิดใช้งาน สถานะการเปิดเผยจะกลับไปเป็นแบบเดิม" + mayNotEffectForFederatedNotes: "โน้ตที่ถูกรวมผ่านสหพันธ์จากเซิร์ฟเวอร์ระยะไกลอาจไม่ได้รับผลจากการตั้งค่านี้" + mayNotEffectSomeSituations: "ข้อจำกัดเหล่านี้เป็นเพียงการกรองเบื้องต้น ในบางกรณี เช่น การดูจากเซิร์ฟเวอร์อื่นหรือในระหว่างการตรวจสอบโดยผู้ดูแล อาจไม่สามารถใช้งานได้" + notesHavePassedSpecifiedPeriod: "โน้ตที่เลยเวลาที่กำหนดไว้แล้ว" + notesOlderThanSpecifiedDateAndTime: "โน้ตก่อนเวลาที่กำหนดไว้" _abuseUserReport: forward: "ส่ง​ต่อ" forwardDescription: "ส่งรายงานไปยังเซิร์ฟเวอร์ระยะไกลโดยใช้บัญชีระบบที่ไม่ระบุตัวตน" resolve: "แก้ไข" accept: "ยอมรับ" reject: "ปฏิเสธ" - resolveTutorial: "ถ้าหากรายงานนี้มีเนื้อหาถูกต้อง ให้เลือก \"ยอมรับ\" เพื่อปิดเคสกรณีนี้โดยถือว่าได้รับการแก้ไขแล้ว\nถ้าหากเนื้อหาในรายงานนี้นั้นไม่ถูกต้อง ให้เลือก \"ปฏิเสธ\" เพื่อปิดเคสกรณีนี้โดยถือว่าไม่ได้รับการแก้ไข" + resolveTutorial: "ให้เลือก “ยอมรับ” หากรายงานนี้มีเนื้อหาชอบธรรม เพื่อทำเครื่องหมายว่ากรณีนี้ได้รับการแก้ไขในทางบวก\nให้เลือก “ปฏิเสธ” หากรายงานนี้มีเนื้อหาไม่สมเหตุผล เพื่อทำเครื่องหมายว่ากรณีนี้ได้รับการแก้ไขในทางลบ" _delivery: status: "สถานะการจัดส่ง" stop: "ระงับการส่ง" @@ -1335,6 +1514,7 @@ _delivery: manuallySuspended: "หยุดชั่วคราวด้วยตนเอง" goneSuspended: "เซิร์ฟเวอร์ถูกระงับเนื่องจากมีการลบเซิร์ฟเวอร์นี้" autoSuspendedForNotResponding: "เซิร์ฟเวอร์ถูกระงับเนื่องจากไม่ตอบสนอง" + softwareSuspended: "หยุดให้บริการ เนื่องจากเป็นซอฟต์แวร์ที่ถูกระงับการเผยแพร่" _bubbleGame: howToPlay: "วิธีเล่น" hold: "ถือไว้" @@ -1449,7 +1629,7 @@ _timelineDescription: _serverRules: description: "ชุดของกฎที่จะแสดงก่อนการลงทะเบียนเราขอแนะนำให้ตั้งค่าสรุปข้อกำหนดในการให้บริการ" _serverSettings: - iconUrl: "URL ไอคอน" + iconUrl: "URL ของไอคอน" appIconDescription: "ระบุไอคอนที่จะใช้เมื่อ {host} แสดงเป็นแอป" appIconUsageExample: "ตัวอย่างเช่น เมื่อถูกเพิ่มเป็น PWA หรือบุ๊กมาร์กบนหน้าจอหลักในสมาร์ทโฟน" appIconStyleRecommendation: "เนื่องจากอาจถูกครอบตัดเป็นสี่เหลี่ยมหรือวงกลม จึงแนะนำให้ใช้ภาพที่เผื่อพื้นที่รอบๆ ตัวโลโก้ไอคอนไว้" @@ -1461,9 +1641,34 @@ _serverSettings: fanoutTimelineDbFallback: "ฟอลแบ๊กกลับฐานข้อมูล" fanoutTimelineDbFallbackDescription: "เมื่อเปิดใช้งาน หากไม่ได้แคชไทม์ไลน์ ไทม์ไลน์จะฟอลแบ๊กไปยังฐานข้อมูลสำหรับการ query เพิ่มเติม การปิดใช้งานจะช่วยลดภาระของเซิร์ฟเวอร์ด้วยการกำจัดกระบวนฟอลแบ๊ก แต่มันก็จะจำกัดช่วงเวลาไทม์ไลน์ที่สามารถดึงข้อมูลได้" reactionsBufferingDescription: "เมื่อเปิดใช้งานฟังก์ชันนี้ก็จะช่วยลด latency ในการสร้างปฏิกิริยา แต่อาจจะส่งผลให้ memory footprint ของ Redis เพิ่มขึ้นนะ" + remoteNotesCleaning: "การล้างข้อมูลโพสต์จากระยะไกลโดยอัตโนมัติ" + remoteNotesCleaning_description: "เมื่อเปิดใช้งาน จะทำการล้างโพสต์จากระยะไกลเก่าที่ไม่ถูกอ้างอิง เป็นระยะ เพื่อลดการขยายตัวของฐานข้อมูล" + remoteNotesCleaningMaxProcessingDuration: "ระยะเวลาสูงสุดของการประมวลผลการล้างข้อมูล" + remoteNotesCleaningExpiryDaysForEachNotes: "จำนวนวันที่ต้องเก็บโน้ตไว้อย่างน้อย" inquiryUrl: "URL สำหรับการติดต่อสอบถาม" inquiryUrlDescription: "ระบุ URL ของหน้าเว็บที่มีแบบฟอร์มสำหรับติดต่อผู้ดูแลเซิร์ฟเวอร์ หรือข้อมูลการติดต่อของผู้ดูแลเซิร์ฟเวอร์" - thisSettingWillAutomaticallyOffWhenModeratorsInactive: "ถ้าหากไม่มีการตรวจสอบจากผู้ดูแลระบบหรือไม่มีความเคลื่อนไหวมาเป็นระยะเวลาหนึ่ง ระบบจะทำการปิดใช้งานฟังก์ชันนี้โดยอัตโนมัติ เพื่อลดความเสี่ยงในการถูกโจมตีด้วยสแปมและอื่นๆ" + openRegistration: "เปิดให้สร้างบัญชีได้" + openRegistrationWarning: "การเปิดให้ลงทะเบียนมีความเสี่ยง แนะนำให้เปิดใช้งานเฉพาะในกรณีที่สามารถตรวจสอบเซิร์ฟเวอร์อย่างสม่ำเสมอและมีระบบรับมือกับปัญหาได้ทันท่วงที" + thisSettingWillAutomaticallyOffWhenModeratorsInactive: "หากไม่พบกิจกรรมของผู้ควบคุมในช่วงระยะเวลาหนึ่ง การตั้งค่านี้จะถูกปิดโดยอัตโนมัติเพื่อป้องกันสแปม" + deliverSuspendedSoftware: "ซอฟต์แวร์ที่หยุดการเผยแพร่" + deliverSuspendedSoftwareDescription: "เนื่องจากเหตุผลด้านช่องโหว่ เป็นต้น สามารถหยุดการแจกจ่ายโดยระบุชื่อซอฟต์แวร์ของเซิร์ฟเวอร์และช่วงของเวอร์ชันได้ ข้อมูลเวอร์ชันนี้เป็นข้อมูลที่เซิร์ฟเวอร์ให้มา จึงไม่สามารถรับประกันความน่าเชื่อถือได้ สามารถใช้การระบุช่วงเวอร์ชันแบบ semver ได้ แต่ถ้าระบุเป็น >= 2024.3.1 จะไม่รวมเวอร์ชันแบบกำหนดเอง เช่น 2024.3.1-custom.0 จึงแนะนำให้ระบุเป็น >= 2024.3.1-0 ซึ่งเป็นการระบุแบบ prerelease" + singleUserMode: "โหมดผู้ใช้คนเดียว" + singleUserMode_description: "หากมีเพียงตัวเองคนเดียวที่ใช้เซิร์ฟเวอร์นี้ การเปิดใช้งานโหมดนี้จะช่วยปรับการทำงานให้เหมาะสมที่สุด" + signToActivityPubGet: "ลงนามในคำขอ GET" + signToActivityPubGet_description: "โดยปกติควรเปิดใช้งาน แต่หากพบปัญหาเกี่ยวกับการสื่อสารในสหพันธ์ การปิดใช้งานอาจช่วยแก้ไขได้ แต่ในบางกรณี เซิร์ฟเวอร์อาจไม่สามารถสื่อสารได้เลยหากปิดใช้งานนี้" + proxyRemoteFiles: "พร็อกซีไฟล์ระยะไกล" + proxyRemoteFiles_description: "เมื่อเปิดใช้งาน จะทำหน้าที่เป็นพร็อกซีสำหรับไฟล์จากระยะไกล ช่วยในการสร้างภาพขนาดย่อและปกป้องความเป็นส่วนตัวของผู้ใช้" + allowExternalApRedirect: "อนุญาตการเปลี่ยนเส้นทางการสืบค้นผ่าน ActivityPub" + allowExternalApRedirect_description: "เมื่อเปิดใช้งาน จะอนุญาตให้เซิร์ฟเวอร์อื่นสืบค้นเนื้อหาของบุคคลที่สามผ่านเซิร์ฟเวอร์นี้ได้ แต่มีความเสี่ยงที่อาจเกิดการปลอมแปลงเนื้อหา" + userGeneratedContentsVisibilityForVisitor: "ขอบเขตการเปิดเผยเนื้อหาที่ผู้ใช้สร้างต่อบุคคลที่ไม่ได้เข้าร่วม (แขก)" + userGeneratedContentsVisibilityForVisitor_description: "ช่วยป้องกันปัญหาที่อาจเกิดขึ้นจากเนื้อหาระยะไกลที่ไม่เหมาะสม ซึ่งอาจถูกเผยแพร่ออกสู่อินเทอร์เน็ตโดยไม่ตั้งใจผ่านเซิร์ฟเวอร์ของตนเอง โดยเฉพาะในกรณีที่การดูแลควบคุมไม่ทั่วถึง" + userGeneratedContentsVisibilityForVisitor_description2: "การเปิดเผยเนื้อหาทั้งหมดในเซิร์ฟเวอร์รวมทั้งเนื้อหาที่รับมาจากระยะไกลสู่สาธารณะบนอินเทอร์เน็ตโดยไม่มีข้อจำกัดใดๆ มีความเสี่ยงโดยเฉพาะอย่างยิ่งสำหรับผู้ชมที่ไม่เข้าใจลักษณะของระบบแบบกระจาย อาจทำให้เกิดความเข้าใจผิดคิดว่าเนื้อหาที่มาจากระยะไกลนั้นเป็นเนื้อหาที่สร้างขึ้นภายในเซิร์ฟเวอร์นี้ จึงควรใช้ความระมัดระวังอย่างมาก" + restartServerSetupWizardConfirm_title: "ต้องการเริ่มวิซาร์ดการตั้งค่าเซิร์ฟเวอร์ใหม่หรือไม่?" + restartServerSetupWizardConfirm_text: "การตั้งค่าบางส่วนในปัจจุบันจะถูกรีเซ็ต" + _userGeneratedContentsVisibilityForVisitor: + all: "ทั้งหมดสาธารณะ" + localOnly: "เผยแพร่เป็นสาธารณะเฉพาะเนื้อหาท้องถิ่น เนื้อหาระยะไกลให้เป็นส่วนตัว" + none: "ทั้งหมดส่วนตัว" _accountMigration: moveFrom: "ย้ายจากบัญชีอื่นมาที่บัญชีนี้" moveFromSub: "สร้างนามแฝงไปยังบัญชีอื่น" @@ -1753,13 +1958,15 @@ _role: baseRole: "แม่แบบบทบาท" useBaseValue: "ใช้ตามแม่แบบบทบาท" chooseRoleToAssign: "เลือกบทบาทที่ต้องการกำหนด" - iconUrl: "URL ไอคอน" + iconUrl: "URL ของไอคอน" asBadge: "แสดงเป็นตรา" - descriptionOfAsBadge: "เมื่อเปิดใช้งาน ไอคอนบทบาทจะปรากฏถัดจากชื่อผู้ใช้" + descriptionOfAsBadge: "หากเปิดใช้งาน จะมีไอคอนของบทบาท แสดงถัดจากชื่อผู้ใช้" isExplorable: "ค้นหาผู้ใช้ได้ง่ายขึ้นโดยดูจากบทบาท" descriptionOfIsExplorable: "เมื่อเปิดใช้งาน ไทมไลน์บทบาทนี้และสมาชิกที่มีบทบาทนี้จะเปิดเผยเป็นสาธารณะ" displayOrder: "ลำดับการแสดงผล" descriptionOfDisplayOrder: "เลขที่สูงกว่าจะแสดงบน UI ก่อน" + preserveAssignmentOnMoveAccount: "โอนสถานะการมอบหมายไปยังบัญชีที่ย้ายไป" + preserveAssignmentOnMoveAccount_description: "เมื่อเปิดใช้งาน บัญชีที่ได้รับบทบาทนี้เมื่อถูกย้ายไปบัญชีใหม่ บทบาทนี้จะถูกถ่ายทอดไปยังบัญชีปลายทางด้วย" canEditMembersByModerator: "อนุญาตให้ผู้ควบคุมแก้ไขสมาชิก" descriptionOfCanEditMembersByModerator: "เมื่อเปิดใช้ นอกเหนือจากผู้ควบคุมและผู้ดูแลระบบแล้ว จะสามารถเพิ่มถอนบทบาทนี้แก่ผู้ใช้ได้ แต่เมื่อปิดใช้ จะมีเฉพาะผู้ดูแลระบบเท่านั้นที่จะสามารถดำเนินการได้" priority: "ลำดับความสำคัญ" @@ -1779,8 +1986,9 @@ _role: canManageCustomEmojis: "จัดการเอโมจิที่กำหนดเอง" canManageAvatarDecorations: "จัดการตกแต่งอวตาร" driveCapacity: "ความจุของไดรฟ์" + maxFileSize: "ขนาดไฟล์สูงสุดที่สามารถอัปโหลดได้" alwaysMarkNsfw: "ทำเครื่องหมายไฟล์ว่าเป็น NSFW เสมอ" - canUpdateBioMedia: "อนุญาตให้ปรับปรุงไอคอนและแบนเนอร์" + canUpdateBioMedia: "อนุญาตให้เปลี่ยนไอคอนประจำตัวและแบนเนอร์" pinMax: "จํานวนสูงสุดของโน้ตที่ปักหมุดไว้" antennaMax: "จำนวนสูงสุดของเสาอากาศ" wordMuteMax: "จำนวนอักขระสูงสุดที่อนุญาตในการปิดเสียงคำ" @@ -1793,13 +2001,20 @@ _role: descriptionOfRateLimitFactor: "ยิ่งตัวเลขน้อยก็ยิ่งจำกัดน้อย ยิ่งมากก็ยิ่งเข้มงวดมากขึ้น" canHideAds: "ซ่อนโฆษณา" canSearchNotes: "การใช้การค้นหาโน้ต" + canSearchUsers: "ค้นหาผู้ใช้" canUseTranslator: "การใช้งานแปล" - avatarDecorationLimit: "จำนวนการตกแต่งไอคอนสูงสุดที่สามารถติดตั้งได้" + avatarDecorationLimit: "จำนวนของตกแต่งไอคอนสูงสุดที่สามารถติดตั้งได้" canImportAntennas: "อนุญาตให้นำเข้าเสาอากาศ" canImportBlocking: "อนุญาตให้นำเข้าการบล็อก" canImportFollowing: "อนุญาตให้นำเข้ารายการต่อไปนี้" - canImportMuting: "อนุญาตให้นำเข้าการปิดกั้น" + canImportMuting: "อนุญาตให้นำเข้าการปิดเสียง" canImportUserLists: "อนุญาตให้นำเข้ารายการ" + chatAvailability: "อนุญาตให้แชต" + uploadableFileTypes: "ประเภทไฟล์ที่สามารถอัปโหลดได้" + uploadableFileTypes_caption: "สามารถระบุ MIME type ได้ โดยใช้การขึ้นบรรทัดใหม่เพื่อแยกหลายรายการ และสามารถใช้ดอกจัน (*) เพื่อระบุแบบไวลด์การ์ดได้ (เช่น: image/*)" + uploadableFileTypes_caption2: "ไฟล์บางประเภทอาจไม่สามารถระบุชนิดได้ หากต้องการอนุญาตไฟล์ลักษณะนั้น กรุณาเพิ่ม {x} ลงในรายการที่อนุญาต" + noteDraftLimit: "จำนวนโน้ตฉบับร่างที่สามารถสร้างได้บนฝั่งเซิร์ฟเวอร์" + watermarkAvailable: "มีฟังก์ชั่นลายน้ำให้เลือกใช้" _condition: roleAssignedTo: "มอบหมายให้มีบทบาทแบบทำมือ" isLocal: "ผู้ใช้ท้องถิ่น" @@ -1959,10 +2174,12 @@ _theme: install: "ติดตั้งธีม" manage: "จัดการธีม" code: "โค้ดธีม" - description: "รายละเอียด" + copyThemeCode: "คัดลอกรหัสธีม" + description: "คำอธิบาย" installed: "{name} ได้รับการติดตั้ง" installedThemes: "ธีมที่ติดตั้ง" builtinThemes: "ธีมในตัว" + instanceTheme: "ธีมของเซิร์ฟเวอร์" alreadyInstalled: "ธีมนี้ได้รับการติดตั้งแล้ว" invalid: "รูปแบบของธีมนี้ไม่ถูกต้องนะ" make: "ทำธีม" @@ -1990,7 +2207,7 @@ _theme: fg: "ข้อความ" focus: "โฟกัส" indicator: "ตัวบ่งชี้" - panel: "แผงควบคุม" + panel: "แผง" shadow: "เงา" header: "ส่วนหัว" navBg: "พื้นหลังแถบด้านข้าง" @@ -2000,7 +2217,7 @@ _theme: link: "ลิงก์" hashtag: "แฮชแท็ก" mention: "กล่าวถึง" - mentionMe: "ได้กล่าวถึง (ฉัน)" + mentionMe: "ได้กล่าวถึงคุณ" renote: "รีโน้ต" modalBg: "พื้นหลังโมดอล" divider: "ตัวแบ่ง" @@ -2024,6 +2241,7 @@ _sfx: noteMy: "โน้ตของตัวเอง" notification: "การเเจ้งเตือน" reaction: "เมื่อเลือกรีแอคชั่น" + chatMessage: "ข้อความของแชต" _soundSettings: driveFile: "ใช้เสียงจากไดรฟ์" driveFileWarn: "เลือกไฟล์ในไดรฟ์ของคุณ" @@ -2056,6 +2274,7 @@ _time: minute: "นาที" hour: "ชั่วโมง" day: "วัน" + month: "เดือน" _2fa: alreadyRegistered: "คุณได้ลงทะเบียนอุปกรณ์ยืนยันตัวตนแบบ 2 ชั้นแล้ว" registerTOTP: "ลงทะเบียนแอพตัวตรวจสอบสิทธิ์" @@ -2066,15 +2285,15 @@ _2fa: step3: "ป้อนโทเค็นที่แอปของคุณให้มาเพื่อเสร็จสิ้นการตั้งค่า" setupCompleted: "ตั้งค่าสำเร็จแล้ว" step4: "นับจากนี้เป็นต้นไปการพยายามเข้าสู่ระบบในอนาคตนั้น อาจจะต้องขอโทเค็นในการเข้าสู่ระบบดังกล่าว" - securityKeyNotSupported: "เบราว์เซอร์ของคุณไม่รองรับคีย์ความปลอดภัยนะ" - registerTOTPBeforeKey: "กรุณาตั้งค่าแอปยืนยันตัวตนเพื่อลงทะเบียนรหัสความปลอดภัยหรือรหัสผ่าน" - securityKeyInfo: "นอกจากนี้การตรวจสอบความถูกต้องด้วยลายนิ้วมือหรือ PIN แล้ว คุณยังสามารถตั้งค่าการตรวจสอบสิทธิ์ผ่านคีย์ความปลอดภัยของฮาร์ดแวร์ที่รองรับ FIDO2 เพื่อเพิ่มความปลอดภัยให้กับบัญชีของคุณ" - registerSecurityKey: "ลงทะเบียนรหัสความปลอดภัยหรือรหัสผ่าน" + securityKeyNotSupported: "เว็บเบราว์เซอร์ที่ใช้งานอยู่ไม่รองรับ Security Key" + registerTOTPBeforeKey: "ก่อนลงทะเบียน Security Key หรือ Passkey กรุณาตั้งค่าแอปยืนยันตัวตนก่อน" + securityKeyInfo: "ลงทะเบียนกุญแจที่มาจาก WebAuthn เช่น Security Key แบบฮาร์ดแวร์ที่รองรับ FIDO2 การยืนยันตัวตนด้วยชีวมิติหรือ PIN บนอุปกรณ์ และ Passkey" + registerSecurityKey: "ลงทะเบียน Security Key หรือ Passkey" securityKeyName: "ป้อนชื่อคีย์" - tapSecurityKey: "กรุณาทำตามเบราว์เซอร์ของคุณเพื่อลงทะเบียนรหัสความปลอดภัยหรือรหัสผ่าน" - removeKey: "ลบคีย์ความปลอดภัยออก" + tapSecurityKey: "กรุณาทำตามคำแนะนำของเบราว์เซอร์เพื่อลงทะเบียน Security Key หรือ Passkey" + removeKey: "ลบ Security Key ออก" removeKeyConfirm: "ลบข้อมูลสำรอง {name} มั้ย?" - whyTOTPOnlyRenew: "ไม่สามารถลบแอปตัวรับรองความถูกต้องได้ตราบใดที่มีการลงทะเบียนคีย์ความปลอดภัยไว้แล้ว" + whyTOTPOnlyRenew: "ไม่สามารถลบแอปตัวรับรองความถูกต้องได้ตราบใดที่ยังมีการลงทะเบียน Security Key อยู่" renewTOTP: "ตั้งค่าแอปยืนยันตัวตน" renewTOTPConfirm: "วิธีการแบบนี้จะทําให้รหัสยืนยันจากแอพก่อนหน้าของคุณหยุดทํางานเลยนะ" renewTOTPOk: "ตั้งค่าคอนฟิกใหม่" @@ -2171,6 +2390,7 @@ _permissions: "read:federation": "รับข้อมูลเกี่ยวกับสหพันธ์" "write:report-abuse": "รายงานการละเมิด" "write:chat": "เขียนหรือลบข้อความแชท" + "read:chat": "อ่านแชต" _auth: shareAccessTitle: "การให้สิทธิ์แอปพลิเคชัน" shareAccess: "คุณต้องการอนุญาตให้ \"{name}\" เข้าถึงบัญชีนี้เลยมั้ย?" @@ -2179,8 +2399,11 @@ _auth: permissionAsk: "แอปพลิเคชันนี้ขอสิทธิ์ดังต่อไปนี้" pleaseGoBack: "กรุณากลับไปที่แอปพลิเคชัน" callback: "กำลังกลับไปที่แอปพลิเคชัน" + accepted: "การเข้าถึงได้รับอนุญาต" denied: "ปฏิเสธการเข้าใช้" + scopeUser: "กำลังดำเนินการในฐานะผู้ใช้ต่อไปนี้" pleaseLogin: "กรุณาเข้าสู่ระบบเพื่ออนุมัติแอปพลิเคชัน" + byClickingYouWillBeRedirectedToThisUrl: "หากอนุญาตการเข้าถึง ระบบจะเปลี่ยนเส้นทางไปยัง URL ด้านล่างโดยอัตโนมัติ" _antennaSources: all: "โน้ตทั้งหมด" homeTimeline: "โน้ตจากผู้ใช้ที่ติดตาม" @@ -2226,6 +2449,7 @@ _widgets: chooseList: "เลือกรายชื่อ" clicker: "คลิกเกอร์" birthdayFollowings: "วันเกิดผู้ใช้ในวันนี้" + chat: "แชต" _cw: hide: "ซ่อน" show: "โหลดเพิ่มเติม" @@ -2265,6 +2489,8 @@ _visibility: disableFederation: "การปิดใช้งานสหพันธ์" disableFederationDescription: "อย่าส่งข้อมูลไปยังเซิร์ฟเวอร์อื่น" _postForm: + quitInspiteOfThereAreUnuploadedFilesConfirm: "มีไฟล์ที่ยังไม่ได้อัปโหลด ต้องการละทิ้งและปิดฟอร์มหรือไม่?" + uploaderTip: "ไฟล์ยังไม่ได้อัปโหลด สามารถตั้งค่าต่างๆ ได้จากเมนูของไฟล์ เช่น การเปลี่ยนชื่อ การครอปรูป การใส่ลายน้ำ และการบีบอัด ไฟล์จะถูกอัปโหลดโดยอัตโนมัติเมื่อโพสต์โน้ต" replyPlaceholder: "ตอบกลับโน้ตนี้..." quotePlaceholder: "อ้างโน้ตนี้..." channelPlaceholder: "โพสต์ลงช่อง..." @@ -2285,7 +2511,7 @@ _profile: metadataDescription: "ใช้สิ่งเหล่านี้ คุณสามารถแสดงฟิลด์ข้อมูลเพิ่มเติมในโปรไฟล์ของคุณ" metadataLabel: "ป้ายชื่อ" metadataContent: "เนื้อหา" - changeAvatar: "เปลี่ยนอวาตาร์" + changeAvatar: "เปลี่ยนไอคอนประจำตัว" changeBanner: "เปลี่ยนแบนเนอร์" verifiedLinkDescription: "หากป้อน URL ที่มีลิงก์ไปยังโปรไฟล์ของคุณ ไอคอนการยืนยันความเป็นเจ้าของจะแสดงถัดจากฟิลด์นั้น ๆ" avatarDecorationMax: "คุณสามารถเพิ่มการตกแต่งได้สูงสุด {max}" @@ -2298,7 +2524,7 @@ _exportOrImport: clips: "คลิป" followingList: "กำลังติดตาม" muteList: "ปิดเสียง" - blockingList: "บล็อค" + blockingList: "บล็อก" userLists: "รายชื่อ" excludeMutingUsers: "ยกเว้นผู้ใช้ที่ปิดเสียง" excludeInactiveUsers: "ยกเว้นผู้ใช้ที่ไม่ได้ใช้งาน" @@ -2368,7 +2594,7 @@ _pages: featured: "เป็นที่นิยม" inspector: "ตัวตรวจสอบ" contents: "เนื้อหา" - content: "บล็อคหน้าเพจ" + content: "บล็อกหน้าเพจ" variables: "ตัวแปร" title: "หัวข้อ" url: "URL ของหน้า" @@ -2380,7 +2606,7 @@ _pages: fontSansSerif: "Sans Serif" eyeCatchingImageSet: "ตั้งค่าภาพขนาดย่อ" eyeCatchingImageRemove: "ลบภาพขนาดย่อ" - chooseBlock: "เพิ่มบล็อค" + chooseBlock: "เพิ่มบล็อก" enterSectionTitle: "ป้อนชื่อหัวข้อ" selectType: "เลือกชนิด" contentBlocks: "เนื้อหา" @@ -2416,6 +2642,7 @@ _notification: newNote: "โพสต์ใหม่" unreadAntennaNote: "เสาอากาศ {name}" roleAssigned: "ได้รับบทบาท" + chatRoomInvitationReceived: "ได้รับคำเชิญเข้าร่วมห้องแชต" emptyPushNotificationMessage: "อัปเดตการแจ้งเตือนแบบพุชแล้ว" achievementEarned: "รับความสำเร็จ" testNotification: "ทดสอบการแจ้งเตือน" @@ -2428,7 +2655,9 @@ _notification: followedBySomeUsers: "มีผู้ติดตาม {n} ราย" flushNotification: "ล้างประวัติการแจ้งเตือน" exportOfXCompleted: "การดำเนินการส่งออก {x} ได้เสร็จสิ้นลงแล้ว" - login: "มีคนล็อกอิน" + login: "มีการเข้าสู่ระบบ" + createToken: "สร้างโทเค็นการเข้าถึงแล้ว" + createTokenDescription: "หากไม่ทราบสาเหตุของคำเชิญ กรุณาลบโทเค็นการเข้าถึงผ่านทาง “{text}”" _types: all: "ทั้งหมด" note: "โน้ตใหม่" @@ -2442,9 +2671,11 @@ _notification: receiveFollowRequest: "ได้รับคำร้องขอติดตาม" followRequestAccepted: "อนุมัติให้ติดตามแล้ว" roleAssigned: "ให้บทบาท" + chatRoomInvitationReceived: "เชิญเข้าห้องแชต" achievementEarned: "ปลดล็อกความสำเร็จแล้ว" exportCompleted: "กระบวนการส่งออกข้อมูลได้เสร็จสิ้นสมบูรณ์แล้ว" login: "เข้าสู่ระบบ" + createToken: "สร้างโทเค็นการเข้าถึง" test: "ทดสอบระบบแจ้งเตือน" app: "การแจ้งเตือนจากแอปที่มีลิงก์" _actions: @@ -2454,6 +2685,9 @@ _notification: _deck: alwaysShowMainColumn: "แสดงคอลัมน์หลักเสมอ" columnAlign: "จัดแนวคอลัมน์" + columnGap: "ช่องห่างระว่างคอลัมน์" + deckMenuPosition: "ตำแหน่งเมนูเด็ค" + navbarPosition: "ตำแหน่งของแถบนำทาง" addColumn: "เพิ่มคอลัมน์" newNoteNotificationSettings: "ตั้งค่าการแจ้งเตือนเมื่อมีโน้ตใหม่" configureColumn: "ตั้งค่าคอลัมน์" @@ -2472,6 +2706,7 @@ _deck: useSimpleUiForNonRootPages: "แสดง UI ของ Root Page อย่างง่าย " usedAsMinWidthWhenFlexible: "ความกว้างขั้นต่ำนั้นจะถูกใช้งานสำหรับสิ่งนี้เมื่อเปิดใช้งานตัวเลือก \"ปรับความกว้างอัตโนมัติ\" หากเลือกเปิดใช้งานแล้ว" flexible: "ปรับความกว้างอัตโนมัติ" + enableSyncBetweenDevicesForProfiles: "เปิดใช้งานการซิงค์ข้อมูลโปรไฟล์ระหว่างอุปกรณ์" _columns: main: "หลัก" widgets: "วิดเจ็ต" @@ -2483,6 +2718,7 @@ _deck: mentions: "กล่าวถึงคุณ" direct: "ไดเร็กต์" roleTimeline: "บทบาทไทม์ไลน์" + chat: "แชต" _dialog: charactersExceeded: "คุณกำลังมีตัวอักขระเกินขีดจำกัดสูงสุดแล้วนะ! ปัจจุบันอยู่ที่ {current} จาก {max}" charactersBelow: "คุณกำลังใช้อักขระต่ำกว่าขีดจำกัดขั้นต่ำเลยนะ! ปัจจุบันอยู่ที่ {current} จาก {min}" @@ -2511,8 +2747,8 @@ _webhookSettings: abuseReport: "เมื่อมีการรายงานจากผู้ใช้" abuseReportResolved: "เมื่อมีการจัดการกับการรายงานจากผู้ใช้" userCreated: "เมื่อผู้ใช้ถูกสร้างขึ้น" - inactiveModeratorsWarning: "เมื่อผู้ดูแลระบบไม่ได้ใช้งานมานานระยะหนึ่ง" - inactiveModeratorsInvitationOnlyChanged: "เมื่อผู้ดูแลระบบที่ไม่ได้ใช้งานมานาน และเซิร์ฟเวอร์เปลี่ยนเป็นแบบเชิญเข้าร่วมเท่านั้น" + inactiveModeratorsWarning: "เมื่อผู้ควบคุมไม่มีความเคลื่อนไหวในช่วงระยะเวลาหนึ่ง" + inactiveModeratorsInvitationOnlyChanged: "เมื่อผู้ควบคุมไม่มีความเคลื่อนไหวในช่วงระยะเวลาหนึ่ง ระบบจะเปลี่ยนเป็นแบบใช้คำเชิญโดยอัตโนมัติ" deleteConfirm: "ต้องการลบ Webhook ใช่ไหม?" testRemarks: "คลิกปุ่มทางด้านขวาของสวิตช์เพื่อส่ง Webhook ทดสอบที่มีข้อมูลจำลอง" _abuseReport: @@ -2564,10 +2800,10 @@ _moderationLogTypes: createAd: "สร้างโฆษณาแล้ว" deleteAd: "ลบโฆษณาออกแล้ว" updateAd: "อัปเดตโฆษณาแล้ว" - createAvatarDecoration: "สร้างการตกแต่งไอคอนแล้ว" - updateAvatarDecoration: "อัปเดตการตกแต่งไอคอนแล้ว" - deleteAvatarDecoration: "ลบการตกแต่งไอคอนแล้ว" - unsetUserAvatar: "ลบไอคอนผู้ใช้" + createAvatarDecoration: "สร้างของตกแต่งไอคอนแล้ว" + updateAvatarDecoration: "อัปเดตของตกแต่งไอคอนแล้ว" + deleteAvatarDecoration: "ลบของตกแต่งไอคอนแล้ว" + unsetUserAvatar: "เลิกตั้งไอคอนประจำตัวแล้ว" unsetUserBanner: "ลบแบนเนอร์ผู้ใช้" createSystemWebhook: "สร้าง SystemWebhook" updateSystemWebhook: "อัปเดต SystemWebhook" @@ -2579,6 +2815,8 @@ _moderationLogTypes: deletePage: "เพจถูกลบออกไปแล้ว" deleteFlash: "Play ถูกลบออกไปแล้ว" deleteGalleryPost: "โพสต์แกลเลอรี่ถูกลบออกแล้ว" + deleteChatRoom: "ลบห้องแชต" + updateProxyAccountDescription: "อัปเดตคำอธิบายของบัญชีพร็อกซี" _fileViewer: title: "รายละเอียดไฟล์" type: "ประเภทไฟล์" @@ -2586,6 +2824,7 @@ _fileViewer: url: "URL" uploadedAt: "วันที่เข้าร่วม" attachedNotes: "โน้ตที่แนบมาด้วย" + usage: "ใช้แล้ว" thisPageCanBeSeenFromTheAuthor: "หน้าเพจนี้จะสามารถปรากฏได้โดยผู้ใช้ที่อัปโหลดไฟล์นี้เท่านั้น" _externalResourceInstaller: title: "ติดตั้งจากไซต์ภายนอก" @@ -2631,8 +2870,14 @@ _dataSaver: title: "โหลดสื่อ" description: "กันไม่ให้ภาพและวิดีโอโหลดโดยอัตโนมัติ แตะรูปภาพ/วิดีโอที่ซ่อนอยู่เพื่อโหลด" _avatar: - title: "รูปไอคอน" - description: "ระงับการเคลื่อนไหวของภาพไอคอน ภาพเคลื่อนไหวอาจมีขนาดไฟล์ใหญ่กว่าภาพปกติ ดังนั้นจึงสามารถช่วยในการลดการใช้ข้อมูล" + title: "ปิดใช้งานภาพเคลื่อนไหวของไอคอนประจำตัว" + description: "ภาพเคลื่อนไหวของไอคอนประจำตัวจะหยุดทำงาน ภาพแบบเคลื่อนไหวมักมีขนาดไฟล์ใหญ่กว่าภาพปกติ จึงช่วยลดปริมาณการใช้ข้อมูลได้มากขึ้น" + _urlPreviewThumbnail: + title: "ซ่อนภาพขนาดย่อของการแสดงตัวอย่าง URL" + description: "ภาพขนาดย่อของการตัวอย่าง URL จะไม่ถูกโหลดอีกต่อไป" + _disableUrlPreview: + title: "ปิดการใช้งานแสดงตัวอย่าง URL" + description: "ปิดฟังก์ชันแสดงตัวอย่าง URL แตกต่างจากการซ่อนเพียงภาพขนาดย่อ ฟังก์ชันนี้จะช่วยลดการโหลดข้อมูลจากลิงก์ปลายทางทั้งหมด" _code: title: "ไฮไลต์โค้ด" description: "หากใช้สัญลักษณ์ไฮไลต์โค้ดใน MFM ฯลฯ สัญลักษณ์เหล่านั้นจะไม่โหลดจนกว่าจะแตะ การไฮไลต์ไวยากรณ์(syntax)จำเป็นต้องดาวน์โหลดไฟล์คำจำกัดความของไฮไลต์สำหรับแต่ละภาษา ดังนั้นการปิดใช้งานการโหลดไฟล์เหล่านี้โดยอัตโนมัติจึงคาดว่าจะช่วยลดปริมาณข้อมูลการสื่อสารได้" @@ -2683,13 +2928,15 @@ _reversi: allowIrregularRules: "อนุญาตกฎที่ไม่ปรกติ (โหมดฟรีทุกอย่าง)" disallowIrregularRules: "ไม่อนุญาตกฎที่ไม่ปรกติ" showBoardLabels: "แสดงหมายเลขแถว/คอลัมน์บนกระดาน" - useAvatarAsStone: "ใช้รูปอวตารเป็นหมาก" + useAvatarAsStone: "ใช้ไอคอนประจำตัวเป็นหมาก" _offlineScreen: title: "ออฟไลน์ - ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ได้" header: "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ได้" _urlPreviewSetting: title: "การตั้งค่าการแสดงตัวอย่าง URL" enable: "เปิดใช้งานการแสดงตัวอย่าง URL" + allowRedirect: "อนุญาตการเปลี่ยนเส้นทางไปยังปลายทางของการแสดงตัวอย่าง" + allowRedirectDescription: "ตั้งค่าว่าจะติดตามลิงก์ที่เปลี่ยนเส้นทาง (redirect) เพื่อแสดงตัวอย่างหรือไม่ เมื่อมีการป้อน URL ที่มีการเปลี่ยนเส้นทาง หากปิดการใช้งาน จะช่วยประหยัดทรัพยากรของเซิร์ฟเวอร์ แต่จะไม่สามารถแสดงเนื้อหาจากปลายทางที่เปลี่ยนเส้นทางได้" timeout: "เวลาจำกัดในการโหลดตัวอย่าง URL (ms)" timeoutDescription: "หากเวลาที่ใช้ในการโหลดเกินค่านี้ จะไม่มีการสร้างการแสดงตัวอย่าง" maximumContentLength: "ค่าสูงสุดของ Content-Length (byte)" @@ -2710,6 +2957,62 @@ _contextMenu: app: "แอปพลิเคชัน" appWithShift: "แอปฟลิเคชันด้วยปุ่มยกแคร่ (Shift)" native: "UI ของเบราว์เซอร์" +_gridComponent: + _error: + requiredValue: "ค่านี้จำเป็นต้องกรอก" + columnTypeNotSupport: "การตรวจสอบค่าด้วย regex รองรับเฉพาะคอลัมน์ที่เป็น type:text" + patternNotMatch: "ค่านี้ไม่ตรงกับรูปแบบ {pattern}" + notUnique: "ค่านี้ต้องไม่ซ้ำกับค่าที่มีอยู่" +_roleSelectDialog: + notSelected: "ยังไม่มีการเลือก" +_customEmojisManager: + _gridCommon: + copySelectionRows: "คัดลอกแถวที่เลือกไว้" + copySelectionRanges: "คัดลือกที่เลือกไว้" + deleteSelectionRows: "ลบแถวที่เลือกไว้" + deleteSelectionRanges: "ล้างค่าช่วงที่เลือก" + searchSettings: "ตั้งค่าการค้นหา" + searchSettingCaption: "ตั้งค่าเงื่อนไขการค้นหาอย่างละเอียด" + searchLimit: "จำนวนรายการที่แสดง" + sortOrder: "ลำดับการเรียง" + registrationLogs: "ปูมการลงทะเบียน" + registrationLogsCaption: "จะแสดงปูมเมื่อมีการอัปเดตหรือลบเอโมจิ หากดำเนินการอัปเดต/ลบ หรือเปลี่ยนหน้า/รีโหลด หน้านี้ ปูมจะหายไป" + alertEmojisRegisterFailedDescription: "การอัปเดตหรือลบเอโมจิล้มเหลว กรุณาตรวจสอบรายละเอียดในปูมการลงทะเบียน" + _logs: + showSuccessLogSwitch: "แสดงปูมที่สำเร็จ" + failureLogNothing: "ไม่มีปูมความล้มเหลว" + logNothing: "ไม่มีปูม" + _remote: + selectionRowDetail: "รายละเอียดของแถวที่เลือก" + importSelectionRows: "นำเข้าแถวที่เลือก" + importSelectionRangesRows: "นำเข้าแถวในช่วงที่เลือก" + importEmojisButton: "นำเข้าเอโมจิที่ทำเครื่องหมายไว้" + confirmImportEmojisTitle: "นำเข้าเอโมจิ" + confirmImportEmojisDescription: "จะนำเข้าเอโมจิ {count} รายการที่ได้รับจากระยะไกล ทั้งนี้โปรดระมัดระวังเรื่องสิทธิ์การใช้งานเอโมจิ ดำเนินการหรือไม่?" + _local: + tabTitleList: "รายการเอโมจิที่ลงทะเบียนไว้แล้ว" + tabTitleRegister: "ลงทะเบียนเอโมจิ" + _list: + emojisNothing: "ยังไม่มีเอโมจิที่ลงทะเบียนไว้" + markAsDeleteTargetRows: "กำหนดแถวที่เลือกให้เป็นรายการสำหรับลบ" + markAsDeleteTargetRanges: "กำหนดช่วงแถวที่เลือกให้เป็นรายการสำหรับลบ" + alertUpdateEmojisNothingDescription: "ไม่มีการเปลี่ยนแปลงเอโมจิ" + alertDeleteEmojisNothingDescription: "ไม่มีเอโมจิที่อยู่ในรายการสำหรับลบ" + confirmMovePage: "ต้องการเปลี่ยนหน้าหรือไม่?" + confirmChangeView: "ต้องการเปลี่ยนการแสดงผลหรือไม่?" + confirmUpdateEmojisDescription: "จะอัปเดตเอโมจิ {count} รายการ ดำเนินการหรือไม่?" + confirmDeleteEmojisDescription: "จะลบเอโมจิที่ถูกทำเครื่องหมายไว้ {count} รายการ ดำเนินการหรือไม่?" + confirmResetDescription: "การเปลี่ยนแปลงทั้งหมดที่ทำมาจะถูกรีเซ็ต" + confirmMovePageDesciption: "มีการเปลี่ยนแปลงเอโมจิในหน้านี้ หากเปลี่ยนหน้าโดยไม่บันทึก การเปลี่ยนแปลงทั้งหมดจะถูกละทิ้ง" + dialogSelectRoleTitle: "ค้นหาบทบาทที่ตั้งค่าไว้ด้วยเอโมจิ" + _register: + uploadSettingTitle: "ตั้งค่าการอัปโหลด" + uploadSettingDescription: "สามารถกำหนดพฤติกรรมขณะอัปโหลดเอโมจิจากหน้าจอนี้ได้" + directoryToCategoryLabel: "ป้อนชื่อไดเรกทอรีเป็น \"category\"" + directoryToCategoryCaption: "เมื่อทำการลากและวางไดเรกทอรี ชื่อจะถูกป้อนเป็น \"category\"" + confirmRegisterEmojisDescription: "จะลงทะเบียนเอโมจิที่แสดงในรายการเป็นเอโมจิแบบกำหนดเองใหม่\nดำเนินการต่อหรือไม่? (เพื่อหลีกเลี่ยงภาระโหลดหนัก ระบบจะสามารถลงทะเบียนอีโมจิได้สูงสุด {count} รายการต่อครั้ง)" + confirmClearEmojisDescription: "ต้องการยกเลิกการแก้ไขและล้างรายการเอโมจิที่แสดงอยู่หรือไม่?" + confirmUploadEmojisDescription: "จะอัปโหลดไฟล์ {count} รายการที่ลากและวางไปยังไดรฟ์ ดำเนินการหรือไม่?" _embedCodeGen: title: "ปรับแต่งโค้ดฝัง" header: "แสดงส่วนหัว" @@ -2724,15 +3027,140 @@ _embedCodeGen: generateCode: "สร้างโค้ดสำหรับการฝัง" codeGenerated: "รหัสถูกสร้างขึ้นแล้ว" codeGeneratedDescription: "นำโค้ดที่สร้างแล้วไปวางในเว็บไซต์ของคุณเพื่อฝังเนื้อหา" +_selfXssPrevention: + warning: "คำเตือน" + title: "“ข้อความที่บอกให้วางบางอย่างในหน้าจอนี้” ทั้งหมดเป็นการหลอกลวง" + description1: "ถ้าวางบางอย่างที่นี่ อาจทำให้ผู้ไม่หวังดีเข้าควบคุมบัญชี หรือขโมยข้อมูลส่วนตัวได้" + description2: "ถ้าไม่เข้าใจอย่างชัดเจนว่าสิ่งที่กำลังจะวางคืออะไร %cให้หยุดการทำงานทันทีแล้วปิดหน้าต่างนี้" + description3: "ดูรายละเอียดเพิ่มเติมได้ที่นี่: {link}" +_followRequest: + recieved: "คำขอที่ได้รับ" + sent: "คำที่ส่งไป" _remoteLookupErrors: + _federationNotAllowed: + title: "ไม่สามารถสื่อสารกับเซิร์ฟเวอร์นี้ได้" + description: "การสื่อสารกับเซิร์ฟเวอร์นี้อาจถูกปิดใช้งาน หรือเซิร์ฟเวอร์นี้อาจจะได้บล็อกคุณ หรือคุณอาจจะได้บล็อกเซิร์ฟเวอร์นี้อยู่\nกรุณาติดต่อผู้ดูแลระบบเซิร์ฟเวอร์เพื่อสอบถามรายละเอียดเพิ่มเติม" + _uriInvalid: + title: "URI ไม่ถูกต้อง" + description: "มีปัญหาเกี่ยวกับ URI ที่ป้อน โปรดตรวจสอบว่าไม่มีอักขระที่ไม่สามารถใช้กับ URI" + _requestFailed: + title: "การร้องขอล้มเหลว" + description: "การสื่อสารกับเซิร์ฟเวอร์นี้ล้มเหลว เซิร์ฟเวอร์ปลายทางอาจล่ม หรืออาจป้อน URI ที่ไม่ถูกต้องหรือไม่มีอยู่" + _responseInvalid: + title: "ข้อมูลตอบสนองกลับไม่ถูกต้อง" + description: "สามารถเชื่อมต่อกับเซิร์ฟเวอร์นี้ได้ แต่ข้อมูลที่ได้รับไม่ถูกต้อง หากกำลังดึงข้อมูลจากเซิร์ฟเวอร์บุคคลที่สาม โปรดใช้ URI ที่สามารถดึงข้อมูลได้จากเซิร์ฟเวอร์ต้นทางโดยตรง" _noSuchObject: title: "ไม่พบหน้าที่ต้องการ" + description: "ไม่พบทรัพยากรที่ร้องขอ กรุณาตรวจสอบ URI อีกครั้ง" +_captcha: + verify: "กรุณาผ่าน CAPTCHA" + testSiteKeyMessage: "สามารถดูตัวอย่างได้โดยป้อนค่าทดสอบใน site key และ secret key\nดูรายละเอียดเพิ่มเติมได้ที่หน้าด้านล่างนี้" + _error: + _requestFailed: + title: "การร้องขอ CAPTCHA ล้มเหลว" + text: "โปรดลองใหม่ภายหลัง หรือ ตรวจสอบการตั้งค่าอีกครั้ง" + _verificationFailed: + title: "การยืนยัน CAPTCHA ล้มเหลว" + text: "กรุณาตรวจสอบอีกครั้งว่าการตั้งค่าถูกต้องหรือไม่" + _unknown: + title: "CAPTCHA เกิดข้อผิดพลาด" + text: "เกิดข้อผิดพลาดที่ไม่คาดคิด" +_bootErrors: + title: "การโหลดล้มเหลว" + serverError: "หากปัญหายังคงอยู่แม้ว่าจะรอสักครู่แล้วโหลดหน้าใหม่อีกครั้ง โปรดติดต่อผู้ดูแลระบบเซิร์ฟเวอร์พร้อมรหัสข้อผิดพลาดต่อไปนี้" + solution: "สิ่งต่อไปนี้อาจช่วยแก้ไขปัญหาได้" + solution1: "อัปเดตเบราว์เซอร์และระบบปฏิบัติการเป็นรุ่นล่าสุด" + solution2: "ปิดใช้งานตัวบล็อกโฆษณา" + solution3: "ล้างแคชเบราว์เซอร์" + solution4: "(Tor Browser) ตั้งค่า dom.webaudio.enabled เป็น true" + otherOption: "ตัวเลือกเพิ่มเติม" + otherOption1: "ลบการตั้งค่าและแคชของไคลเอนต์" + otherOption2: "เริ่มใช้งานไคลเอนต์แบบง่าย" + otherOption3: "เปิดเครื่องมือซ่อมแซม" + otherOption4: "เริ่มทำงาน Misskey ในโหมดปลอดภัย" _search: searchScopeAll: "ทั้งหมด" searchScopeLocal: "ท้องถิ่น" + searchScopeServer: "ระบุเซิร์ฟเวอร์" searchScopeUser: "ผู้ใช้เฉพาะ" + pleaseEnterServerHost: "กรุณากรอกโฮสต์ของเซิร์ฟเวอร์" + pleaseSelectUser: "กรุณาเลือกผู้ใช้" + serverHostPlaceholder: "ตัวอย่าง: misskey.example.com" +_serverSetupWizard: + installCompleted: "การติดตั้ง Misskey เสร็จสมบูรณ์แล้ว!" + firstCreateAccount: "ขั้นแรก ให้สร้างบัญชีผู้ดูแลระบบ" + accountCreated: "บัญชีผู้ดูแลระบบถูกสร้างขึ้นแล้ว!" + serverSetting: "การตั้งค่าเซิร์ฟเวอร์" + youCanEasilyConfigureOptimalServerSettingsWithThisWizard: "สามารถตั้งค่าเซิร์ฟเวอร์ได้อย่างง่ายดายด้วยวิซาร์ดนี้" + settingsYouMakeHereCanBeChangedLater: "สามารถเปลี่ยนแปลงการตั้งค่าเหล่านี้ในภายหลังได้" + howWillYouUseMisskey: "ต้องการใช้ Misskey อย่างไร?" + _use: + single: "เซิร์ฟเวอร์คนเดียว" + single_description: "ใช้งานเป็นเซิร์ฟเวอร์ส่วนตัวสำหรับตัวเองคนเดียว" + single_youCanCreateMultipleAccounts: "แม้จะใช้งานเป็นเซิร์ฟเวอร์ส่วนตัวสำหรับคนเดียว ก็สามารถสร้างบัญชีผู้ใช้หลายบัญชีได้ตามความจำเป็น" + group: "เซิร์ฟเวอร์กลุ่ม" + group_description: "เชิญผู้ใช้ที่เชื่อถือได้ มาเข้าร่วมใช้งานแบบหลายคน" + open: "เซิร์ฟเวอร์สาธารณะ" + open_description: "เปิดรับผู้ใช้จำนวนมากแบบไม่จำกัด" + openServerAdvice: "การเปิดรับผู้ใช้จำนวนมากมีความเสี่ยง ควรบริหารจัดการด้วยระบบดูแลที่เข้มงวดเพื่อรับมือกับปัญหาที่อาจเกิดขึ้น" + openServerAntiSpamAdvice: "เพื่อป้องกันไม่ให้เซิร์ฟเวอร์ของตนกลายเป็นแหล่งส่งสแปม ควรเปิดใช้งานฟีเจอร์ป้องกันบอต เช่น reCAPTCHA และใส่ใจเรื่องความปลอดภัยอย่างเคร่งครัด" + howManyUsersDoYouExpect: "คาดว่าจะมีผู้ใช้งานประมาณกี่คน?" + _scale: + small: "น้อยกว่า 100 คน (ขนาดเล็ก)" + medium: "เกิน 100 คน แต่น้อยกว่า 1000 คน (ขนาดกลาง)" + large: "เกิน 1000 คน (ขนาดใหญ่)" + largeScaleServerAdvice: "เซิร์ฟเวอร์ขนาดใหญ่อาจต้องการความรู้ด้านโครงสร้างพื้นฐานขั้นสูง เช่น การบาลานซ์โหลด หรือการทำสำเนาฐานข้อมูล" + doYouConnectToFediverse: "เชื่อมต่อกับ Fediverse หรือไม่?" + doYouConnectToFediverse_description1: "หากเชื่อมต่อกับเครือข่ายที่ประกอบด้วยเซิร์ฟเวอร์แบบกระจาย (Fediverse) จะสามารถแลกเปลี่ยนเนื้อหากับเซิร์ฟเวอร์อื่นๆ ได้" + doYouConnectToFediverse_description2: "การเชื่อมต่อกับ Fediverse เรียกว่า “สหพันธ์”" + youCanConfigureMoreFederationSettingsLater: "หลังจากนี้ยังสามารถตั้งค่าแบบขั้นสูง เช่น การกำหนดเซิร์ฟเวอร์ที่อนุญาตให้สหพันธ์ต่อกันได้เพิ่มเติม" + remoteContentsCleaning: "การล้างข้อมูลเนื้อหาที่ได้รับโดยอัตโนมัติ" + remoteContentsCleaning_description: "เมื่อมีการเชื่อมโยงสหพันธ์ จะได้รับเนื้อหาเป็นจำนวนมากอย่างต่อเนื่อง เมื่อเปิดใช้งานการล้างข้อมูลอัตโนมัติ จะทำการลบเนื้อหาเก่าที่ไม่ถูกอ้างอิง ไปจากเซิร์ฟเวอร์โดยอัตโนมัติ เพื่อประหยัดพื้นที่จัดเก็บข้อมูล" + adminInfo: "ข้อมูลผู้ดูแลระบ" + adminInfo_description: "ตั้งค่าข้อมูลผู้ดูแลระบบที่จะใช้รับคำถามและติดต่อ" + adminInfo_mustBeFilled: "หากเปิดใช้เซิร์ฟเวอร์สาธารณะ หรือเปิดใช้งานสหพันธ์ จะต้องกรอกข้อมูลนี้" + followingSettingsAreRecommended: "แนะนำให้ตั้งค่าตามด้านล่างนี้" + applyTheseSettings: "ใช้การตั้งค่านี้" + skipSettings: "ข้ามการตั้งค่า" + settingsCompleted: "การตั้งค่าเสร็จสมบูรณ์แล้ว!" + settingsCompleted_description: "ขอบคุณที่สละเวลามาตั้งค่า ตอนนี้เซิร์ฟเวอร์พร้อมใช้งานได้ทันที" + settingsCompleted_description2: "การตั้งค่าเซิร์ฟเวอร์อย่างละเอียดสามารถทำได้จาก “แผงควบคุม”" + donationRequest: "คำขอรับบริจาค" + _donationRequest: + text1: "Misskey เป็นซอฟต์แวร์ฟรีที่พัฒนาโดยอาสาสมัคร" + text2: "เพื่อให้การพัฒนางานนี้สามารถดำเนินต่อไปได้ในอนาคต หากไม่เป็นการรบกวน รบกวนพิจารณาร่วมสมทบทุนด้วยนะคะ" + text3: "นอกจากนี้ยังมีสิทธิพิเศษสำหรับผู้สนับสนุนอีกด้วยค่ะ" +_uploader: + editImage: "แก้ไขรูปภาพ" + compressedToX: "บีบอัดเป็น {x}" + savedXPercent: "ประหยัดไป {x}%" + abortConfirm: "มีไฟล์ที่ยังไม่ได้อัปโหลด ต้องการยกเลิกหรือไม่?" + doneConfirm: "มีไฟล์ที่ยังไม่ได้อัปโหลด ต้องการดำเนินการให้เสร็จสิ้นหรือไม่?" + maxFileSizeIsX: "ขนาดไฟล์สูงสุดที่สามารถอัปโหลดได้คือ {x}" + allowedTypes: "ประเภทไฟล์ที่สามารถอัปโหลดได้" + tip: "ยังไม่มีไฟล์ถูกอัปโหลด สามารถ ตรวจสอบ ลบชื่อไฟล์ บีบอัด หรือครอปตัดภาพ ก่อนอัปโหลดได้ในหน้านี้ เมื่อพร้อมแล้วให้กดปุ่ม “อัปโหลด” เพื่อเริ่มการอัปโหลด" +_clientPerformanceIssueTip: + title: "หากรู้สึกว่าแบตเตอรี่หมดเร็ว" + makeSureDisabledAdBlocker: "โปรดปิดการใช้งานตัวบล็อกโฆษณา" + makeSureDisabledAdBlocker_description: "ตัวบล็อกโฆษณาอาจส่งผลต่อประสิทธิภาพ โปรดตรวจสอบว่าไม่ได้เปิดใช้งานผ่านฟังก์ชันของระบบปฏิบัติการ เบราว์เซอร์ หรือส่วนเสริมใดๆ" + makeSureDisabledCustomCss: "โปรดปิดการใช้งาน CSS แบบกำหนดเอง" + makeSureDisabledCustomCss_description: "การเขียนทับสไตล์อาจส่งผลต่อประสิทธิภาพ โปรดตรวจสอบว่าไม่มี CSS แบบกำหนดเองหรือส่วนเสริมที่แก้ไขสไตล์เปิดใช้งานอยู่" + makeSureDisabledAddons: "โปรดปิดการใช้งานส่วนเสริม" + makeSureDisabledAddons_description: "ส่วนเสริมบางตัวอาจรบกวนการทำงานของไคลเอนต์และทำให้ประสิทธิภาพลดลง กรุณาลองปิดส่วนเสริมในเบราว์เซอร์แล้วตรวจสอบอีกครั้ง" +_clip: + tip: "คลิปเป็นฟังก์ชันที่สามารถรวมโน้ตเข้าด้วยกัน" +_userLists: + tip: "สามารถสร้างรายชื่อที่มีผู้ใช้ใดก็ได้ เมื่อสร้างแล้ว รายชื่อนั้นจะแสดงเป็นไทม์ไลน์ได้" +watermark: "ลายน้ำ" +defaultPreset: "พรีเซ็ตเริ่มต้น" _watermarkEditor: + tip: "สามารถเพิ่มลายน้ำ เช่น ข้อมูลเครดิต ลงในภาพได้" + quitWithoutSaveConfirm: "ต้องการออกโดยไม่บันทึกหรือไม่?" driveFileTypeWarn: "ไม่รองรับไฟล์นี้" + driveFileTypeWarnDescription: "กรุณาเลือกไฟล์ภาพ" + title: "แก้ไขลายน้ำ" + cover: "ซ้อนทับทั่วทั้งพื้นที่" + repeat: "ปูให้เต็มพื้นที่" opacity: "ความทึบแสง" scale: "ขนาด" text: "ข้อความ" @@ -2741,3 +3169,82 @@ _watermarkEditor: image: "รูปภาพ" advanced: "ขั้นสูง" angle: "แองเกิล" + stripe: "ริ้ว" + stripeWidth: "ความกว้างเส้น" + stripeFrequency: "จำนวนเส้น" + polkadot: "ลายจุด" + checker: "ช่องตาราง" + polkadotMainDotOpacity: "ความทึบของจุดหลัก" + polkadotMainDotRadius: "ขนาดของจุดหลัก" + polkadotSubDotOpacity: "ความทึบของจุดรอง" + polkadotSubDotRadius: "ขนาดของจุดรอง" + polkadotSubDotDivisions: "จำนวนจุดรอง" +_imageEffector: + title: "เอฟเฟกต์" + addEffect: "เพิ่มเอฟเฟกต์" + discardChangesConfirm: "ต้องการทิ้งการเปลี่ยนแปลงแล้วออกหรือไม่?" + nothingToConfigure: "ไม่มีอะไรให้ตั้งค่า" + _fxs: + chromaticAberration: "ความคลาดสี" + glitch: "กลิตช์" + mirror: "กระจก" + invert: "กลับสี" + grayscale: "ขาวดำเทา" + colorAdjust: "ปรับแก้สี" + colorClamp: "บีบอัดสี" + colorClampAdvanced: "บีบอัดสี (ขั้นสูง)" + distort: "บิดเบี้ยว" + threshold: "สองสี" + zoomLines: "เส้นความเข้มข้น" + stripe: "ริ้ว" + polkadot: "ลายจุด" + checker: "ช่องตาราง" + blockNoise: "บล็อกที่มีการรบกวน" + tearing: "ฉีกขาด" + _fxProps: + angle: "แองเกิล" + scale: "ขนาด" + size: "ขนาด" + color: "สี" + opacity: "ความทึบแสง" + normalize: "นอร์มัลไลซ์" + amount: "จำนวน" + lightness: "สว่าง" + contrast: "คอนทราสต์" + hue: "HUE" + brightness: "ความสว่าง" + saturation: "ความอิ่มตัว" + max: "สูงสุด" + min: "ต่ำสุด" + direction: "ทิศทาง" + phase: "ระยะ" + frequency: "ความถี่" + strength: "ความแรง" + glitchChannelShift: "ความเคลื่อน" + seed: "ซีด" + redComponent: "ส่วนสีแดง" + greenComponent: "ส่วนสีเขียว" + blueComponent: "ส่วนสีน้ำเงิน" + threshold: "เทรชโฮลด์" + centerX: "กลาง X" + centerY: "กลาง Y" + zoomLinesSmoothing: "ทำให้สมูธ" + zoomLinesSmoothingDescription: "ตั้งให้สมูธไม่สามารถใช้ร่วมกับตั้งความกว้างเส้นรวมศูนย์ได้" + zoomLinesThreshold: "ความกว้างเส้นรวมศูนย์" + zoomLinesMaskSize: "ขนาดพื้นที่ตรงกลาง" + zoomLinesBlack: "ทำให้ดำ" +drafts: "ร่าง" +_drafts: + select: "เลือกฉบับร่าง" + cannotCreateDraftAnymore: "ถึงจำนวนจำกัดที่ฉบับร่างที่สามารถสร้างได้แล้ว" + cannotCreateDraft: "ไม่สามารถสร้างฉบับร่างด้วยเนื้อหานี้ได้" + delete: "ลบฉบับร่าง" + deleteAreYouSure: "ต้องการลบฉบับร่างหรือไม่?" + noDrafts: "ไม่มีฉบับร่าง" + replyTo: "ตอบกลับ {user}" + quoteOf: "อ้างอิงถึงโน้ตของ {user}" + postTo: "โพสต์ไปยัง {channel}" + saveToDraft: "บันทึกเป็นฉบับร่าง" + restoreFromDraft: "คืนค่าจากฉบับร่าง" + restore: "กู้คืน" + listDrafts: "รายการฉบับร่าง" diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml index 2f20c02105..5ca2b18fac 100644 --- a/locales/tr-TR.yml +++ b/locales/tr-TR.yml @@ -1,150 +1,166 @@ --- _lang_: "Türkçe" -headlineMisskey: "Notlarla bağlanmış bir ağ" -introMisskey: "Açık kaynaklı bir dağıtılmış mikroblog hizmeti olan Misskey'e hoş geldiniz.\nMisskey, neler olup bittiğini paylaşmak ve herkese sizden bahsetmek için \"notlar\" oluşturmanıza olanak tanıyan, açık kaynaklı, dağıtılmış bir mikroblog hizmetidir.\nHerkesin notlarına kendi tepkilerinizi hızlıca eklemek için \"Tepkiler\" özelliğini de kullanabilirsiniz👍.\nYeni bir dünyayı keşfedin🚀." -poweredByMisskeyDescription: "name}Açık kaynak bir platform\nMisskeyDünya'nın en sunucularında biri。" -monthAndDay: "{month}Ay {day}Gün" -search: "Arama" -notifications: "Bildirim" +headlineMisskey: "Notlarla birbirine bağlı bir ağ" +introMisskey: "Hoş geldiniz! Misskey, merkezi olmayan bir açık kaynaklı mikroblog platformudur.\n“Notlar” yazarak şu anda neler olduğunu anlatabilir veya olayları başkalarıyla paylaşabilirsiniz. 📡\n“Tepkiler” ile diğer kullanıcıların notları hakkındaki duygularınızı hızlı bir şekilde ifade edebilirsiniz. 👍\nYeni bir dünya sizi bekliyor! 🚀" +poweredByMisskeyDescription: "{name}, açık kaynak platformu Misskey (kısaca “Misskey örneği” olarak anılır) tarafından desteklenen hizmetlerden biridir." +monthAndDay: "{day}.{month}." +search: "Ara" +reset: "Sıfırla" +notifications: "Bildirimler" username: "Kullanıcı Adı" password: "Şifre" -initialPasswordForSetup: "" -forgotPassword: "şifremi unuttum" -fetchingAsApObject: "從聯邦宇宙取得中..." -ok: "TAMAM" -gotIt: "Anladım" -cancel: "İptal" -noThankYou: "Hayır, teşekkürler" -enterUsername: "Kullanıcı adınızı giriniz" -renotedBy: "{user} tarafından Renotelandı" -noNotes: "Notlar mevcut değil." -noNotifications: "Bildirim bulunmuyor" +initialPasswordForSetup: "Kurulum için ilk şifre" +initialPasswordIsIncorrect: "Kurulum için ilk şifre yanlış" +initialPasswordForSetupDescription: "Misskey'i kendiniz kurduysan, yapılandırma dosyasında belirtilen şifreyi kullan.\nMisskey barındırma hizmeti veya benzeri bir hizmet kullanıyorsan, orada belirtilen şifreyi kullan.\nŞifre belirlemediysen, devam etmek için boş bırak." +forgotPassword: "Şifremi unuttum" +fetchingAsApObject: "Fediverse'den talep ediliyor..." +ok: "Tamam" +gotIt: "Anladım!" +cancel: "Vazgeç" +noThankYou: "Hayır, teşekkürler." +enterUsername: "Kullanıcı adı gir" +renotedBy: "{user} renote etti" +noNotes: "Not yok" +noNotifications: "Bildirim yok" instance: "Sunucu" settings: "Ayarlar" notificationSettings: "Bildirim Ayarları" basicSettings: "Temel Ayarlar" otherSettings: "Diğer Ayarlar" -openInWindow: "Bir pencere ile aç" +openInWindow: "Pencerede aç" profile: "Profil" -timeline: "Zaman çizelgesi" -noAccountDescription: "Bu kullanıcı henüz biyografisini yazmadı" -login: "Giriş Yap " -loggingIn: "Oturum aç" +timeline: "Pano" +noAccountDescription: "Bu kullanıcı henüz biyografisini yazmamış." +login: "Oturum Aç" +loggingIn: "Giriş Yapılıyor..." logout: "Çıkış Yap" -signup: "Kayıt Ol" -uploading: "Yükleniyor" +signup: "Kaydol" +uploading: "Yükleniyor..." save: "Kaydet" -users: "Kullanıcı" -addUser: "Kullanıcı Ekle" -favorite: "Favoriler" +users: "Kullanıcılar" +addUser: "Kullanıcı ekle" +favorite: "Favori" favorites: "Favoriler" -unfavorite: "Favorilerden Kaldır" -favorited: "Favorilerime eklendi." -alreadyFavorited: "Zaten favorilerinizde kayıtlı." -cantFavorite: "Favorilere kayıt yapılamadı" -pin: "Sabitlenmiş" -unpin: "Sabitlemeyi kaldır" +unfavorite: "Favoriden kaldır" +favorited: "Favoriye eklendi." +alreadyFavorited: "Zaten favoride" +cantFavorite: "Favoriye eklenemedi" +pin: "Profiline sabitle" +unpin: "Profilden sabitlemeyi kaldır" copyContent: "İçeriği kopyala" -copyLink: "Bağlantıyı Kopyala" -copyLinkRenote: "Turkish" +copyLink: "Link kopyala" +copyRemoteLink: "Uzak linki kopyala" +copyLinkRenote: "Renote linkini kopyala" delete: "Sil" deleteAndEdit: "Sil ve yeniden düzenle" -deleteAndEditConfirm: "Bu notu silip yeniden düzenlemek istiyor musunuz? Bu nota ilişkin tüm Tepkiler, Yeniden Notlar ve Yanıtlar da silinecektir." +deleteAndEditConfirm: "Bu notu silip yeniden düzenlemek ister misin? Bu notla ilgili tüm Tepki, Renote ve Yanıtlar da silinecek." addToList: "Listeye ekle" -addToAntenna: "Antene ekle" -sendMessage: "Mesaj Gönder" -copyRSS: "RSSKopyala" -copyUsername: "Kullanıcı Adını Kopyala" -copyUserId: "KullanıcıyıKopyala" -copyNoteId: "Kimlik notunu kopyala" -copyFileId: "Dosya ID'sini kopyala" -copyFolderId: "Klasör ID'sini kopyala" -copyProfileUrl: "Profil URL'sini kopyala" -searchUser: "Kullanıcıları ara" -reply: "yanıt" -loadMore: "Devamını yükle" -showMore: "Devamını yükle" +addToAntenna: "Anten'e ekle" +sendMessage: "Mesaj gönder" +copyRSS: "RSS kopyala" +copyUsername: "Kullanıcı adını kopyala" +copyUserId: "Kullanıcı ID'yi kopyala" +copyNoteId: "Not ID'yi kopyala" +copyFileId: "Dosya ID'yi kopyala" +copyFolderId: "Klasör ID'yi kopyala" +copyProfileUrl: "Profil URL kopyala" +searchUser: "Kullanıcı ara" +searchThisUsersNotes: "Kullanıcının notlarını ara" +reply: "Yanıtla" +loadMore: "Daha fazla yükle" +showMore: "Daha fazlasını göster" showLess: "Kapat" youGotNewFollower: "seni takip etti" receiveFollowRequest: "Takip isteği alındı" followRequestAccepted: "Takip isteği kabul edildi" mention: "Bahset" mentions: "Bahsetmeler" -directNotes: "Kişisel mesajlar" +directNotes: "Doğrudan notlar" importAndExport: "İçeri/Dışarı aktar" import: "İçeri aktar" export: "Dışa aktar" files: "Dosyalar" download: "İndir" -driveFileDeleteConfirm: "\"{name}\" dosyası silinsin mi? Dosya kullanıldığı tüm notlardan kaybolacaktır." -unfollowConfirm: "{name} takipten çıkarılsın mı?" -exportRequested: "Dışa aktarım talep ettiniz. Bu biraz zaman alabilir. İşlem bitince Sürücünüze eklenecektir." -importRequested: "Dışa aktarım talep ettiniz. Bu işlem biraz zaman alabilir." +driveFileDeleteConfirm: "“{name}” dosyasını silmek istediğinden emin misin? Bu dosyaya ekli tüm notlar da silinecek." +unfollowConfirm: "{name} kullanıcısını cidden takipden çıkmak istiyor musun?" +exportRequested: "Dışa aktarma işlemi talep ettin. Bu işlem biraz zaman alabilir. İşlem tamamlandığında Drive'ına eklenecek." +importRequested: "İçe aktarma talebinde bulundun. Bu işlem biraz zaman alabilir." lists: "Listeler" -noLists: "Liste yok" -note: "not" -notes: "notlar" -following: "takipçi" -followers: "takipçi" -followsYou: "seni takip ediyor" +noLists: "Hiç liste yok" +note: "Not" +notes: "Notlar" +following: "Takip" +followers: "Takipçi" +followsYou: "Sizi takip ediyor" createList: "Liste oluştur" -manageLists: "Yönetici Listeleri" -error: "hata" +manageLists: "Listeleri yönet" +error: "Hata" somethingHappened: "Bir hata oluştu" retry: "Tekrar dene" -pageLoadError: "Sayfa yüklenemedi." -pageLoadErrorDescription: "Bu genelde ağ veya tarayıcı ön belleği hatalarından olur. Lütfen ön belleği temizlemeyi veya birkaç dakika beklemeyi ve sayfayı yenilemeyi deneyin." -serverIsDead: "Sunucu yanıt vermiyor. Birkaç dakika sonra tekrar deneyin." -youShouldUpgradeClient: "Sayfayı görüntülemek için yenileyin." -enterListName: "Liste ismi" +pageLoadError: "Sayfa yüklenirken bir hata oluştu." +pageLoadErrorDescription: "Bu durum genellikle ağ hataları veya tarayıcının önbelleği nedeniyle oluşur. Önbelleği temizleyin ve bir süre bekledikten sonra tekrar dene." +serverIsDead: "Bu sunucu yanıt vermiyor. Lütfen bir süre bekleyin ve tekrar dene." +youShouldUpgradeClient: "Bu sayfayı görüntülemek için lütfen yenileyerek istemcini güncelle." +enterListName: "Listeye bir ad girin" privacy: "Gizlilik" -makeFollowManuallyApprove: "Takip istekleri elle onaylansın" +makeFollowManuallyApprove: "Takip istekleri onay gerektirir" defaultNoteVisibility: "Varsayılan görünürlük" -follow: "takipçi" -followRequest: "Takip isteği" +follow: "Takip et" +followRequest: "Takip isteği gönder" followRequests: "Takip istekleri" -unfollow: "takip etmeyi bırak" -followRequestPending: "Bekleyen Takip Etme Talebi" -enterEmoji: "Emoji Giriniz" -renote: "vazgeçme" -unrenote: "not alma" -renoted: "yeniden adlandırılmış" -cantRenote: "Ayrılamama" -cantReRenote: "not alabilirmiyim" -quote: "alıntı" -inChannelRenote: "Kanal içi Renote" -inChannelQuote: "Kanal içi Alıntı" -pinnedNote: "Sabitlenen" -pinned: "Sabitlenmiş" -you: "sen" -clickToShow: "Görüntülemek için tıkla" -sensitive: "Hassas içerik" +unfollow: "Takibi bırak" +followRequestPending: "Takip isteği beklemede" +enterEmoji: "Bir emoji gir" +renote: "Renote" +unrenote: "Renote geri al" +renoted: "Renote yapıldı." +renotedToX: "{name} adresine Renote" +cantRenote: "Bu not renote edilemez." +cantReRenote: "Renote yeniden Renote edilemez." +quote: "Alıntı" +inChannelRenote: "Kanal içi renote" +inChannelQuote: "Kanal içi alıntı" +renoteToChannel: "Kanala Renote" +renoteToOtherChannel: "Diğer kanala Renote\n" +pinnedNote: "Sabit not" +pinned: "Profiline sabitle" +you: "Sen" +clickToShow: "Göstermek için tıklayın" +sensitive: "Hassas" add: "Ekle" -reaction: "Tepkiler" +reaction: "Tepki" reactions: "Tepkiler" -reactionSettingDescription2: "Sıralamak için sürükleyin, silmek için tıklayın, eklemek için \"+\" tuşuna tıklayın." -rememberNoteVisibility: "Görünürlük ayarlarını hatırla" -attachCancel: "Eki sil" +emojiPicker: "Emoji seçici" +pinnedEmojisForReactionSettingDescription: "Tepki verirken sabitlenecek ve görüntülenecek emojileri ayarlayın." +pinnedEmojisSettingDescription: "Emoji seçiciyi görüntülerken sabitlenecek ve görüntülenecek emojileri ayarlayın" +emojiPickerDisplay: "Emoji seçici ekranı" +overwriteFromPinnedEmojisForReaction: "Tepki ayarlarından geçersiz kılma" +overwriteFromPinnedEmojis: "Genel ayarlardan geçersiz kılma" +reactionSettingDescription2: "Sıralamayı değiştirmek için sürükle, silmek için tıkla, eklemek için “+” tuşuna bas." +rememberNoteVisibility: "Not görünürlük ayarlarını hatırla" +attachCancel: "Eki kaldır" +deleteFile: "Dosyayı sil" markAsSensitive: "Hassas içerik olarak işaretle" unmarkAsSensitive: "Hassas içerik işaretini kaldır" enterFileName: "Dosya ismini gir" mute: "Gizle" unmute: "sesi aç" renoteMute: "sesi kapat" -renoteUnmute: "sesi açmayı iptal et" +renoteUnmute: "Renote sessiz modunu kaldır" block: "engelle" unblock: "engellemeyi kaldır" suspend: "askıya al" -unsuspend: "askıya alma" -blockConfirm: "Onayı engelle" -unblockConfirm: "engellemeyi kaldır onayla" +unsuspend: "askıya almayı kaldır" +blockConfirm: "Engeli onayla" +unblockConfirm: "Engel kaldırmayı onayla" suspendConfirm: "Hesap askıya alınsın mı?" -unsuspendConfirm: "Hesap askıdan kaldırılsın mı" +unsuspendConfirm: "Hesap askıdan kaldırılsın mı?" selectList: "Bir liste seç" editList: "Listeyi düzenle" selectChannel: "Kanal seç" selectAntenna: "Bir anten seç" editAntenna: "Anteni düzenle" +createAntenna: "Bir anten oluşturun" selectWidget: "Araç seç" editWidgets: "Araçları düzenle" editWidgetsExit: "Tamam" @@ -156,309 +172,3083 @@ emojiUrl: "Emoji URL'si" addEmoji: "Emoji ekle" settingGuide: "Önerilen ayarlar" cacheRemoteFiles: "Uzak dosyalar ön belleğe alınsın" -cacheRemoteFilesDescription: "Bu ayar açık olduğunda diğer sitelerin dosyaları doğrudan uzak sunucudan yüklenecektir. Bu ayarı kapatmak depolama kullanımını azaltacak ama küçük resimler oluşturulmadığından trafiği arttıracaktır." -youCanCleanRemoteFilesCache: "" +cacheRemoteFilesDescription: "Bu ayar açık olduğunda diğer sitelerin dosyaları doğrudan uzak sunucudan yüklenece. Bu ayarı kapatmak depolama kullanımını azaltacak ama küçük resimler oluşturulmadığından trafiği arttıracak." +youCanCleanRemoteFilesCache: "Dosya yönetimi görünümünde 🗑️ düğmesine tıklayarak önbelleği temizleyebilirsin." cacheRemoteSensitiveFiles: "Hassas uzak dosyalar ön belleğe alınsın" -cacheRemoteSensitiveFilesDescription: "Bu ayar kapalı olduğunda hassas uzak dosyalar ön belleğe alınmadan doğrudan uzak sunucudan yüklenecektir." +cacheRemoteSensitiveFilesDescription: "Bu ayar kapalı olduğunda hassas uzak dosyalar ön belleğe alınmadan doğrudan uzak sunucudan yüklenecek." flagAsBot: "Bot olarak işaretle" -flagAsBotDescription: "Bu seçeneği hesap bir program tarafından kontrol ediliyorsa işaretleyin. Bu, diğer geliştiricilerin sonsuz etkileşim zincirleri oluşturmasını engellemeye yardımcı olur ve Misskey'in iç sisteminin hesaba bir bot gibi davranmasını sağlar." +flagAsBotDescription: "Bu hesap bir program tarafından kontrol ediliyorsa bu seçeneği etkinleştir. Etkinleştirildiğinde, diğer geliştiriciler için bir işaret görevi görerek diğer botlarla sonsuz etkileşim zincirlerini önleyecek ve Misskey'in iç sistemlerini bu hesabı bir bot olarak ele alacak şekilde ayarlayacak." flagAsCat: "Kedi hesabı" flagAsCatDescription: "Kedi hesabı" -flagShowTimelineReplies: "Zaman akışında notlara gelen cevapları göster" -flagShowTimelineRepliesDescription: "Açık olduğu durumda, zaman akışında kullanıcıların başkalarına verdiği cevaplar gözükür." +flagShowTimelineReplies: "Pano'da notlara gelen cevapları göster" +flagShowTimelineRepliesDescription: "Açık olduğu durumda, Pano'da kullanıcıların başkalarına verdiği cevaplar gözükür." autoAcceptFollowed: "Takip edilen hesapların takip isteklerini kabul et" addAccount: "Hesap ekle" reloadAccountsList: "Hesap listesini güncelle" loginFailed: "Giriş başarısız oldu" showOnRemote: "Uzak sunucuda görüntüle" +continueOnRemote: "Uzak bir sunucuda devam edin" +chooseServerOnMisskeyHub: "Misskey Hub'dan bir sunucu seçin." +specifyServerHost: "Doğrudan bir sunucu ana bilgisayarı belirtin" +inputHostName: "Alan adını girin" general: "Genel" wallpaper: "Duvar kağıdı" setWallpaper: "Duvar kağıdını ayarla" -removeWallpaper: "Duvar kağıdını sil" -searchWith: "Arama: {q}" -youHaveNoLists: "Hiç listeniz yok" -followConfirm: "{name} takip edilsin mi?" -proxyAccount: "Vekil hesabı" -proxyAccountDescription: "Proxy hesabı, belirli koşullar altında kullanıcılar için uzaktan takipçi işlevi gören bir hesaptır. Örneğin, bir kullanıcı listeye bir uzak kullanıcı eklediğinde, o kullanıcıyı takip eden yerel bir kullanıcı yoksa uzak kullanıcının etkinliği örneğe teslim edilmeyecektir, dolayısıyla bunun yerine proxy hesabı takip edilecektir." -host: "Sağlayıcı" +removeWallpaper: "Duvar kağıdını kaldır" +searchWith: "Ara: {q}" +youHaveNoLists: "Hiç listeniz yok." +followConfirm: "{name} kullanıcısını takip etmek istediğinden emin misin?" +proxyAccount: "Proxy hesabı" +proxyAccountDescription: "Proxy hesabı, belirli koşullar altında kullanıcılar için uzaktan takipçi görevi gören bir hesap. Örneğin, bir kullanıcı listeye uzaktan bir kullanıcı eklediğinde, o kullanıcıyı takip eden yerel kullanıcı yoksa uzaktan kullanıcının etkinliği örneğe iletilmez, bunun yerine proxy hesabı takip eder." +host: "Host" +selectSelf: "Kendimi seç" selectUser: "Kullanıcı seç" -recipient: "Kime" -annotation: "Açıklamalar" +recipient: "Alıcı" +annotation: "Yorumlar" federation: "Federasyon" -instances: "Sunucu" -registeredAt: "Katılma tarihi" -latestRequestReceivedAt: "Alınan son talep" -latestStatus: "En son durum" +instances: "Sunucular" +registeredAt: "Kayıtlı" +latestRequestReceivedAt: "Son talep alındı" +latestStatus: "Son durum" storageUsage: "Depolama kullanımı" -charts: "Çizelgeler" +charts: "Grafikler" perHour: "Saatlik" perDay: "Günlük" -stopActivityDelivery: "Durum güncellemelerini gönderme" +stopActivityDelivery: "Etkinlik göndermeyi durdur" blockThisInstance: "Bu sunucuyu engelle" -silenceThisInstance: "" -operations: "İşlemler" -software: "Yazılımlar" +silenceThisInstance: "Bu sunucuyu sustur" +mediaSilenceThisInstance: "Medya bu sunucuyu sustursun" +operations: "Operasyonlar" +software: "Yazılım" +softwareName: "Yazılım" version: "Sürüm" -metadata: "Meta Verileri" -withNFiles: "{n} tane dosya" +metadata: "Meta veri" +withNFiles: "{n} dosya" monitor: "Monitör" jobQueue: "İşlem sırası" -cpuAndMemory: "İşlemci ve Hafıza" +cpuAndMemory: "CPU ve Bellek" network: "Ağ" disk: "Disk" instanceInfo: "Sunucu Bilgisi" statistics: "İstatistikler" -clearQueue: "Sırayı temizle" -clearQueueConfirmTitle: "Sıra silinsin mi?" -clearQueueConfirmText: "Sırada kalan hiçbir şey iletilmeyecek. Genelde bu işlem gerekli değildir." -clearCachedFiles: "Ön belleği temizle" -clearCachedFilesConfirm: "Ön belleğe alınmış tüm uzak sunucu dosyaları silinsin mi?" -blockedInstances: "Engellenen sunucular" -blockedInstancesDescription: "Engellemek istediğiniz sunucuların alan adlarını satır sonlarıyla ayırarak yazın. Yazılan sunucular bu sunucuyla iletişime geçemeyecek." -silencedInstances: "Turkısh" -silencedInstancesDescription: "" -muteAndBlock: "Susturma ve Engelleme" -mutedUsers: "Susturulan kullanıcılar" +clearQueue: "Kuyruğu temizle" +clearQueueConfirmTitle: "Kuyruğu silmek istediğinden emin misin?" +clearQueueConfirmText: "Kuyrukta kalan teslim edilmemiş notlar birleştirilmeyecek. Genellikle bu işlem gerekli değildir." +clearCachedFiles: "Önbelleği temizle" +clearCachedFilesConfirm: "Tüm önbelleğe alınmış uzak dosyaları silmek istediğinden emin misin?" +blockedInstances: "Engellenen Sunucu" +blockedInstancesDescription: "Engellemek istediğin sunucuların ana bilgisayar adlarını satır sonlarıyla ayırarak liste. Listelenen örnekler artık bu örnekle iletişim kuramayacaktır." +silencedInstances: "Susturulmuş sunucular" +silencedInstancesDescription: "Sessize almak istediğin sunucuların ana bilgisayar adlarını yeni bir satırla ayırarak listele. Listelenen sunuculara ait tüm hesaplar sessize alınmış olarak kabul edilecek ve yalnızca takip isteklerinde bulunabilecek, takip edilmedikleri takdirde yerel hesapları etiketleyemeyeceklerdir. Bu, engellenen sunucuları etkilemeyecek." +mediaSilencedInstances: "Medya susturulmuş sunucular" +mediaSilencedInstancesDescription: "Medya sessize almak istediğin sunucuların ana bilgisayar adlarını yeni bir satırla ayırarak liste. Listelenen sunuculara ait tüm hesaplar hassas hesap olarak değerlendirilecek ve özel emojiler kullanılamayacaktır. Bu durum, engellenen sunucuları etkilemeyecek." +federationAllowedHosts: "Federasyona izin verilen sunucular" +federationAllowedHostsDescription: "Federasyona izin vermek istediğiniz sunucuların ana bilgisayar adlarını satır sonlarıyla ayırın." +muteAndBlock: "Sessize Alma ve Engelleme" +mutedUsers: "Sessize alınan kullanıcılar" blockedUsers: "Engellenen kullanıcılar" noUsers: "Kullanıcı yok" editProfile: "Profili düzenle" -noteDeleteConfirm: "Bu notu silmek istediğinizden emin misiniz?" -pinLimitExceeded: "Daha fazla not sabitlenemez" -done: "Tamamlandı" +noteDeleteConfirm: "Bu notu silmek istediğinden emin misin?" +pinLimitExceeded: "Artık daha fazla not sabitleyemezsin" +done: "Tamam" +processing: "İşleniyor..." preview: "Önizleme" default: "Varsayılan" defaultValueIs: "Varsayılan: {value}" -noCustomEmojis: "Emoji bulunamadı" -noJobs: "Hiç işlem yok" -federating: "Federe ediliyor" +noCustomEmojis: "Emoji yok" +noJobs: "Hiç ş yok" +federating: "Birleştirme" blocked: "Engellenmiş" suspended: "Askıya alınmış" all: "Tümü" subscribing: "Abonelik" publishing: "Paylaşım" -notResponding: "Cevap yok" -instanceFollowing: "Sunucuda takip edenler" +notResponding: "Yanıt vermiyor" +instanceFollowing: "Sunucuda takip" instanceFollowers: "Sunucu takipçileri" -instanceUsers: "Sunucu kullanıcıları" +instanceUsers: "Bu sunucunun kullanıcıları" changePassword: "Şifreyi değiştir" security: "Güvenlik" -retypedNotMatch: "Girişler uyuşmuyor." -currentPassword: "Geçerli şifre" +retypedNotMatch: "Girişler eşleşmiyor." +currentPassword: "Mevcut şifre" newPassword: "Yeni şifre" -newPasswordRetype: "Yeni şifre (tekrar)" -attachFile: "Dosya ekle" -more: "Daha!" -featured: "Öne Çıkan" -usernameOrUserId: "Kullanıcı adı veya ID'si" +newPasswordRetype: "Yeni şifreyi tekrar girin" +attachFile: "Dosyaları ekle" +more: "Daha fazlası!" +featured: "Öne çıkan" +usernameOrUserId: "Kullanıcı adı veya ID" noSuchUser: "Kullanıcı bulunamadı" lookup: "Sorgu" announcements: "Duyurular" -imageUrl: "Görsel URL'si" +imageUrl: "Görsel URL" remove: "Sil" removed: "Silindi" -removeAreYouSure: "\"{x}\" silmek istediğinizden emin misiniz?" -deleteAreYouSure: "\"{x}\" silmek istediğinizden emin misiniz?" -resetAreYouSure: "Sıfırlansın mı?" +removeAreYouSure: "“{x}” öğesini kaldırmak istediğinizden emin misin?" +deleteAreYouSure: "“{x}” öğesini silmek istediğinizden emin misin?" +resetAreYouSure: "Cidden sıfırlansın mı?" +areYouSure: "Emin misin?" saved: "Kaydedildi" upload: "Yükle" -keepOriginalUploading: "Orijinal görseli koru" -keepOriginalUploadingDescription: "Orijinal olarak yüklenen görüntüyü olduğu gibi kaydeder. Kapatılırsa, yükleme sırasında web'de görüntülenecek bir sürüm oluşturulur." -fromDrive: "Drive Dosyasından" -fromUrl: "Bağlantıdan" -uploadFromUrl: "Bağlantıdan yükle" -uploadFromUrlDescription: "Yüklemek istediğiniz dosyanın bağlantısı" -uploadFromUrlRequested: "Yükleme talep edildi" -uploadFromUrlMayTakeTime: "Yüklemenin tamamlanması biraz süre alabilir." +keepOriginalUploading: "Orijinal görüntüyü koru" +keepOriginalUploadingDescription: "Orijinal olarak yüklenen görüntüyü olduğu gibi kaydeder. Kapalıysa, yükleme sırasında web'de görüntülenecek bir sürüm oluşturulur." +fromDrive: "Drive'den" +fromUrl: "URL'den" +uploadFromUrl: "URL'den yükle" +uploadFromUrlDescription: "Yüklemek istediğiniz dosyanın URL'si" +uploadFromUrlRequested: "Yükleme istendi" +uploadFromUrlMayTakeTime: "Yükleme işleminin tamamlanması biraz zaman alabilir." +uploadNFiles: "{n} dosya yükle" explore: "Keşfet" -messageRead: "Okundu" -noMoreHistory: "Bundan öncesi yok" -nUsersRead: "{n} kişi okudu" -agreeTo: "Kabul Ediyorum: {0}" -agree: "Kabul Et" -agreeBelow: "Aşağıdakileri kabul ederim" +messageRead: "Oku" +noMoreHistory: "Daha fazla geçmiş bilgisi yok." +startChat: "Sohbete başla" +nUsersRead: "{n} tarafından okundu" +agreeTo: "{0}'ı kabul ediyorum." +agree: "Kabul ediyorum" +agreeBelow: "Aşağıdakileri kabul ediyorum" basicNotesBeforeCreateAccount: "Önemli notlar" -termsOfService: "Şartlar ve Koşullar" +termsOfService: "Hizmet Şartları" start: "Başla" -home: "Ana sayfa" -remoteUserCaution: "Bu kullanıcı bir uzak sunucudan olduğu için alınan bilgiler tam olmayabilir." +home: "Pano" +remoteUserCaution: "Bu kullanıcı uzak bir sunucudan geldiği için, gösterilen bilgiler eksik olabilir." activity: "Etkinlik" images: "Görseller" -image: "Görseller" +image: "Görsel" birthday: "Doğum günü" yearsOld: "{age} yaşında" -registeredDate: "Kayıt tarihi" +registeredDate: "Katılma tarihi" location: "Konum" -theme: "Temalar" -themeForLightMode: "Aydınlık Tema" -themeForDarkMode: "Karanlık Tema" +theme: "Tema" +themeForLightMode: "Aydınlık Mod'da kullanılacak tema" +themeForDarkMode: "Karanlık Mod'da kullanılacak tema" light: "Aydınlık" dark: "Karanlık" -lightThemes: "Aydınlık Temalar" -darkThemes: "Karanlık Temalar" -syncDeviceDarkMode: "Sistem Koyu Modu ile senkronize et" -drive: "Sürücü" +lightThemes: "Aydınlık temalar" +darkThemes: "Karanlık temalar" +syncDeviceDarkMode: "Karanlık Modu cihaz ayarlarınızla senkronize et" +switchDarkModeManuallyWhenSyncEnabledConfirm: "\"{x}\" açık. Senkronizasyonu kapatıp modları manuel olarak değiştirmek ister misin?" +drive: "Drive" fileName: "Dosya adı" -selectFile: "Dosya seç" -selectFiles: "Dosya seç" -selectFolder: "Klasör seç" -selectFolders: "Klasör seç" +selectFile: "Dosya seçin" +selectFiles: "Dosyaları seçin" +selectFolder: "Klasör seçin" +selectFolders: "Klasörleri seçin" +fileNotSelected: "Hiç dosya seçilmedi" renameFile: "Dosyayı yeniden adlandır" folderName: "Klasör adı" -createFolder: "Klasör oluştur" -renameFolder: "Klasörü Yeniden Adlandır" -deleteFolder: "Klasörü sil" -addFile: "Dosya ekle" -emptyDrive: "Sürücü boş" +createFolder: "Bir klasör oluşturun" +renameFolder: "Bu klasörü yeniden adlandır" +deleteFolder: "Bu klasörü sil" +folder: "Dosya" +addFile: "Bir dosya ekle" +showFile: "Dosyaları göster" +emptyDrive: "Drive boş" emptyFolder: "Bu klasör boş" -unableToDelete: "Silme mümkün değil" -inputNewFileName: "Yeni dosya ismini girin" -inputNewDescription: "Yeni bir başlık gir" -inputNewFolderName: "Yeni klasör ismini girin" -circularReferenceFolder: "Hedef klasör taşınan klasörün bir alt klasörü." -hasChildFilesOrFolders: "Klasör boş olmadığından silinemiyor" -copyUrl: "URL'yi kopyala" +unableToDelete: "Silinemiyor" +inputNewFileName: "Yeni bir dosya adı girin" +inputNewDescription: "Yeni alternatif metin girin" +inputNewFolderName: "Yeni bir klasör adı girin" +circularReferenceFolder: "Hedef klasör, taşımak istediğiniz klasörün bir alt klasörü." +hasChildFilesOrFolders: "Bu klasör boş olmadığı için silinemez." +copyUrl: "URL kopyala" rename: "Yeniden adlandır" avatar: "Avatar" banner: "Banner" -displayOfSensitiveMedia: "Hassas içerik gösterimi" -whenServerDisconnected: "Sunucu bağlantısı kesildiğinde" -disconnectedFromServer: "Sunucu bağlantısı koptu" +displayOfSensitiveMedia: "Hassas ortamların görüntülenmesi" +whenServerDisconnected: "Sunucu ile bağlantı kesildiğinde" +disconnectedFromServer: "Sunucu bağlantısı kesildi" reload: "Yenile" -doNothing: "Bir şey yapma" -reloadConfirm: "Zaman akışı yenilensin mi?" +doNothing: "Yoksay" +reloadConfirm: "Panoyu yenilemek ister misin?" watch: "İzle" unwatch: "İzlemeyi bırak" accept: "Kabul et" reject: "Reddet" normal: "Normal" -instanceName: "Sunucu ismi" +instanceName: "Sunucu adı" instanceDescription: "Sunucu açıklaması" -maintainerName: "Yönetici ismi" -maintainerEmail: "Yöneticinin e-postası" -tosUrl: "Hizmet Koşulları Bağlantısı" -thisYear: "Bu yıl" -thisMonth: "Bu ay" +maintainerName: "Bakım sorumlusu" +maintainerEmail: "Bakım sorumlusu E-Posta adresi" +tosUrl: "Hizmet Şartları URL'si" +thisYear: "Yıl" +thisMonth: "Ay" today: "Bugün" -monthX: "{month} ay" +dayX: "{day}" +monthX: "{month}" +yearX: "{year}" pages: "Sayfalar" integration: "Entegrasyon" +connectService: "Bağlan" +disconnectService: "Bağlantıyı kes" +enableLocalTimeline: "Yerel Pano'yu etkinleştir" +enableGlobalTimeline: "Global Pano'yu etkinleştir" +disablingTimelinesInfo: "Yöneticiler ve Moderatörler, etkinleştirilmemiş olsalar bile her zaman tüm Pano'ya erişebilecekler." +registration: "Kaydol" +invite: "Davet et" +driveCapacityPerLocalAccount: "Yerel kullanıcı başına Drive kapasitesi" +driveCapacityPerRemoteAccount: "Uzak kullanıcı başına Drive kapasitesi" +inMb: "Megabayt cinsinden" +bannerUrl: "Banner görseli URL" +backgroundImageUrl: "Arka plan görseli URL" basicInfo: "Temel bilgiler" pinnedUsers: "Sabitlenmiş kullanıcılar" -pinnedNotes: "Sabitlenen" -manageAntennas: "Anten ayarları" +pinnedUsersDescription: "“Keşfet” sekmesinde sabitlenecek kullanıcı adlarını satır sonlarıyla ayırarak liste." +pinnedPages: "Sabitlenmiş Sayfalar" +pinnedPagesDescription: "Bu örneğin üst sayfasına sabitlemek istediğin Sayfaların yollarını satır sonlarıyla ayırarak gir." +pinnedClipId: "Sabitlenecek klibin ID" +pinnedNotes: "Sabitlenmiş notlar" +hcaptcha: "hCaptcha" +enableHcaptcha: "hCaptcha etkinleştir" +hcaptchaSiteKey: "Site anahtar" +hcaptchaSecretKey: "Gizli anahtar" +mcaptcha: "mCaptcha" +enableMcaptcha: "mCaptcha etkinleştir" +mcaptchaSiteKey: "Site anahtarı" +mcaptchaSecretKey: "Gizli anahtar" +mcaptchaInstanceUrl: "mCaptcha sunucu URL'si" +recaptcha: "reCAPTCHA" +enableRecaptcha: "reCAPTCHA etkinleştir" +recaptchaSiteKey: "Site anahtar" +recaptchaSecretKey: "Gizli anahtar" +turnstile: "Turnstile" +enableTurnstile: "Turnstile etkinleştir" +turnstileSiteKey: "Site anahtar" +turnstileSecretKey: "Gizli anahtar" +avoidMultiCaptchaConfirm: "Birden fazla Captcha sistemi kullanmak, aralarında çakışmaya neden olabilir. Şu anda etkin olan diğer Captcha sistemlerini devre dışı bırakmak ister misiniz? Etkin kalmalarını istiyorsan, iptal düğmesine bas." +antennas: "Antenler" +manageAntennas: "Antenleri Yönet" +name: "İsim" +antennaSource: "Anten kaynağı" +antennaKeywords: "Dinlenecek anahtar kelimeler" +antennaExcludeKeywords: "Hariç tutulacak anahtar kelimeler" +antennaExcludeBots: "Bot hesaplarını hariç tut" +antennaKeywordsDescription: "VE koşulu için boşluklarla, VEYA koşulu için satır sonlarıyla ayırın." +notifyAntenna: "Yeni notlar hakkında bildirimde bulunun" +withFileAntenna: "Sadece dosyalı notlar" +excludeNotesInSensitiveChannel: "Hassas kanallardan gelen notları hariç tutun" +enableServiceworker: "Tarayıcınız için Push Bildirimlerini Etkinleştir" +antennaUsersDescription: "Satır başına bir kullanıcı adı listele" +caseSensitive: "Harfe duyarlı" +withReplies: "Yanıtları ekle" +connectedTo: "Aşağıdaki hesap(lar) bağlı" +notesAndReplies: "Notlar ve yanıtlar" +withFiles: "Dosyalar dahil" +silence: "Sessize al" +silenceConfirm: "Bu kullanıcıyı susturmak istediğinden emin misin?" +unsilence: "Sessize almayı geri al" +unsilenceConfirm: "Bu kullanıcının sessize alınmasını geri almak istediğinden emin misin?" +popularUsers: "Popüler kullanıcılar" +recentlyUpdatedUsers: "Son zamanlarda aktif olan kullanıcılar" +recentlyRegisteredUsers: "Yeni katılan kullanıcılar" +recentlyDiscoveredUsers: "Yeni keşfedilen kullanıcılar" +exploreUsersCount: "{count} kullanıcı var" +exploreFediverse: "Fediverse'i keşfedin" +popularTags: "Popüler etiketler" userList: "Listeler" -resetPassword: "Şifre sıfırlama" -details: "Detaylar" -deck: "Güverte" -smtpHost: "Sağlayıcı" -smtpUser: "Kullanıcı Adı" +about: "Hakkında" +aboutMisskey: "Misskey Hakkında" +administrator: "Yönetici" +token: "Token" +2fa: "İki faktörlü kimlik doğrulama" +setupOf2fa: "İki faktörlü kimlik doğrulamayı ayarlayın" +totp: "Authenticator Uygulaması" +totpDescription: "Tek seferlik şifreleri girmek için bir kimlik doğrulama uygulaması kullanın" +moderator: "Moderatör" +moderation: "Moderasyon" +moderationNote: "Moderasyon notu" +moderationNoteDescription: "Moderatörler arasında paylaşılacak notları girebilirsin." +addModerationNote: "Moderasyon notu ekle" +moderationLogs: "Moderasyon günlükleri" +nUsersMentioned: "{n} kullanıcı bahsetti" +securityKeyAndPasskey: "Güvenlik ve geçiş anahtarları" +securityKey: "Güvenlik anahtarı" +lastUsed: "Son kullanılan" +lastUsedAt: "Son kullanım: {t}" +unregister: "Kayıttan çık" +passwordLessLogin: "Şifresiz giriş" +passwordLessLoginDescription: "Yalnızca güvenlik anahtarı veya şifre anahtarı kullanarak şifresiz oturum açmaya izin verir." +resetPassword: "Şifreyi sıfırla" +newPasswordIs: "Yeni şifre \"{password}\"" +reduceUiAnimation: "UI animasyonlarını azaltın." +share: "Paylaş" +notFound: "Bulunamadı" +notFoundDescription: "Bu URL'ye karşılık gelen sayfa bulunamadı." +uploadFolder: "Yüklemeler için varsayılan klasör" +markAsReadAllNotifications: "Tüm bildirimleri okundu olarak işaretle" +markAsReadAllUnreadNotes: "Tüm notları okundu olarak işaretle" +markAsReadAllTalkMessages: "Tüm mesajları okundu olarak işaretle" +help: "Yardım" +inputMessageHere: "Mesajınızı buraya girin" +close: "Kapat" +invites: "Davetler" +members: "Üyeler" +transfer: "Transfer" +title: "Başlık" +text: "Metin" +enable: "Etkin" +next: "Sonraki" +retype: "Tekrar girin" +noteOf: "{user} not'u" +quoteAttached: "Alıntı" +quoteQuestion: "Alıntı olarak ekle?" +attachAsFileQuestion: "Panodaki metin uzun. Metin dosyası olarak eklemek ister misin?" +onlyOneFileCanBeAttached: "Bir mesaja yalnızca bir dosya ekleyebilirsin." +signinRequired: "Devam etmeden önce lütfen kayıt olun veya giriş yapın." +signinOrContinueOnRemote: "Devam etmek için sunucunuzu taşıyın veya bu sunucuya kaydolun / giriş yapın." +invitations: "Davetler" +invitationCode: "Davet kodu" +checking: "Kontrol ediliyor..." +available: "Kullanılabilir" +unavailable: "Kullanılamaz" +usernameInvalidFormat: "Büyük ve küçük harfler, rakamlar ve alt çizgi kullanabilirsin. (a~z、A~Z、0~9)" +tooShort: "Çok kısa" +tooLong: "Çok uzun" +weakPassword: "Zayıf şifre" +normalPassword: "Ortalama şifre" +strongPassword: "Güçlü şifre" +passwordMatched: "Eşleşti" +passwordNotMatched: "Eşleşmedi" +signinWith: "{x} ile giriş yapın" +signinFailed: "Giriş yapılamıyor. Girilen kullanıcı adı veya şifre yanlış." +or: "veya" +language: "Dil" +uiLanguage: "Kullanıcı arayüzü dili" +aboutX: "{x} hakkında" +emojiStyle: "Emoji stili" +native: "Yerli" +menuStyle: "Menü stili" +style: "Stil" +drawer: "Çekmece" +popup: "Pop-up" +showNoteActionsOnlyHover: "Not eylemlerini yalnızca üzerine gelindiğinde göster" +showReactionsCount: "Notlardaki tepki sayısını gör" +noHistory: "Geçmiş mevcut değil" +signinHistory: "Giriş geçmişi" +enableAdvancedMfm: "Gelişmiş MFM'yi etkinleştir" +enableAnimatedMfm: "Animasyonlu MFM'yi etkinleştir" +doing: "İşleniyor..." +category: "Kategori" +tags: "Takma adlar" +docSource: "Bu belgenin kaynağı" +createAccount: "Hesap oluştur" +existingAccount: "Mevcut hesap" +regenerate: "Yeniden oluştur" +fontSize: "Yazı tipi boyutu" +mediaListWithOneImageAppearance: "Tek bir resim içeren medya listelerinin yüksekliği" +limitTo: "{x} ile sınırlandır" +noFollowRequests: "Bekleyen takip istekleri yok." +openImageInNewTab: "Görüntüleri yeni sekmede aç" +dashboard: "Gösterge paneli" +local: "Yerel" +remote: "Uzak" +total: "Toplam" +weekOverWeekChanges: "Geçen haftadan beri yapılan değişiklikler" +dayOverDayChanges: "Dünkü değişiklikler" +appearance: "Görünüm" +clientSettings: "İstemci Ayarları" +accountSettings: "Hesap Ayarları" +promotion: "Tanıtım" +promote: "Tanıtıldı" +numberOfDays: "Gün sayısı" +hideThisNote: "Bu notu gizle" +showFeaturedNotesInTimeline: "Pano'da öne çıkan notları göster" +objectStorage: "Nesne Depolama" +useObjectStorage: "Nesne depolamayı kullanın" +objectStorageBaseUrl: "Temel URL" +objectStorageBaseUrlDesc: "Referans olarak kullanılan URL. CDN veya Proxy kullanıyorsanız, bunların URL'sini belirtin.\nS3 için ‘https://.s3.amazonaws.com’ ve GCS veya eşdeğer hizmetler için ‘https://storage.googleapis.com/’ vb. kullanın." +objectStorageBucket: "Kova" +objectStorageBucketDesc: "Lütfen sağlayıcınızda kullanılan kova adını belirtin." +objectStoragePrefix: "Ön ek" +objectStoragePrefixDesc: "Dosyalar bu öneke sahip dizinler altında saklanacaktır." +objectStorageEndpoint: "Uç nokta" +objectStorageEndpointDesc: "AWS S3 kullanıyorsanız bu alanı boş bırakın, aksi takdirde kullandığınız hizmete bağlı olarak uç noktayı ‘’ veya ‘:’ olarak belirtin." +objectStorageRegion: "Bölge" +objectStorageRegionDesc: "'xx-east-1' gibi bir bölge belirt. Hizmetin bölgeler arasında ayrım yapmıyorsa, ‘us-east-1’ girin. AWS yapılandırma dosyalarını veya ortam değişkenlerini kullanıyorsan boş bırak." +objectStorageUseSSL: "SSL kullanın" +objectStorageUseSSLDesc: "API bağlantıları için HTTPS kullanmayacaksanız bunu kapatın." +objectStorageUseProxy: "Proxy üzerinden bağlan" +objectStorageUseProxyDesc: "API bağlantıları için Proxy kullanmayacaksanız bunu kapatın." +objectStorageSetPublicRead: "Yükleme sırasında \"genel-okuma\" ayarını yapın" +s3ForcePathStyleDesc: "s3ForcePathStyle etkinleştirilirse, kova adı URL'nin ana bilgisayar adı yerine URL yoluna eklenmelidir. Kendi kendine barındırılan bir Minio örneği gibi hizmetleri kullanırken bu ayarı etkinleştirmen gerekebilir." +serverLogs: "Sunucu log kayıtları" +deleteAll: "Tümünü sil" +showFixedPostForm: "Gönderi formunu pano üstünde görüntüle" +showFixedPostFormInChannel: "Gönderi formunu pano üstünde görüntüle (Kanallar)" +withRepliesByDefaultForNewlyFollowed: "Yeni takip edilen kullanıcıların yanıtlarını varsayılan olarak panoya dahil et" +newNoteRecived: "Yeni Not'lar var" +newNote: "Yeni Not" +sounds: "Sesler" +sound: "Ses" +notificationSoundSettings: "Bildirim sesi ayarları" +listen: "Dinle" +none: "Hiçbiri" +showInPage: "Sayfada göster" +popout: "Açılır pencere" +volume: "Ses hacmi" +masterVolume: "Ana ses seviyesi" +notUseSound: "Sesi kapat" +useSoundOnlyWhenActive: "Misskey etkin olduğunda ses çıkarılır." +details: "Ayrıntılar" +renoteDetails: "Renote detayları" +chooseEmoji: "Bir emoji seçin" +unableToProcess: "İşlem tamamlanamadı." +recentUsed: "Son kullanılan" +install: "Yükle" +uninstall: "Kaldır" +installedApps: "Yetkili Uygulamalar" +nothing: "Burada görülecek bir şey yok." +installedDate: "Yetkili" +lastUsedDate: "En son kullanıldığı tarih" +state: "Durum" +sort: "Sıralama düzeni" +ascendingOrder: "Artan" +descendingOrder: "Azalan" +scratchpad: "Not defteri" +scratchpadDescription: "Scratchpad, AiScript deneyleri için bir ortam sağlar. Misskey ile etkileşim halindeyken yazabilir, çalıştırabilir ve sonuçlarını kontrol edebilirsin." +uiInspector: "UI denetçisi" +uiInspectorDescription: "Bellekteki UI bileşeni sunucu listesini görebilirsin. UI bileşeni, Ui:C: işlevi tarafından oluşturulacak." +output: "Çıktı" +script: "Script" +disablePagesScript: "Sayfalarda AiScript'i devre dışı bırak" +updateRemoteUser: "Uzak kullanıcı bilgilerini güncelle" +unsetUserAvatar: "Avatar'ı kaldır" +unsetUserAvatarConfirm: "Avatarı silmek istediğinden emin misin?" +unsetUserBanner: "Banner'ı kaldır" +unsetUserBannerConfirm: "Banner'ı kaldırmak istediğinden emin misin?" +deleteAllFiles: "Tüm dosyaları sil" +deleteAllFilesConfirm: "Tüm dosyaları silmek istediğinden emin misin?" +removeAllFollowing: "Takip ettiğin tüm kullanıcıları takipten çıkar" +removeAllFollowingDescription: "Bu komutu çalıştırmak, {host} adresindeki tüm hesapları takipten çıkarır. Örneğin, sunucu artık mevcut değilse bu komutu çalıştırın." +userSuspended: "Bu kullanıcı askıya alınmıştır." +userSilenced: "Bu kullanıcı susturuluyor." +yourAccountSuspendedTitle: "Bu hesap askıya alınmıştır." +yourAccountSuspendedDescription: "Bu hesap, sunucunun hizmet şartlarını veya benzerlerini ihlal ettiği için askıya alınmıştır. Daha ayrıntılı bir neden öğrenmek istersen yöneticiyle iletişime geç. Lütfen yeni bir hesap oluşturma." +tokenRevoked: "Geçersiz jeton" +tokenRevokedDescription: "Bu jetonun süresi doldu. Lütfen tekrar giriş yapın." +accountDeleted: "Hesap silindi" +accountDeletedDescription: "Bu hesap silinmiş." +menu: "Menü" +divider: "Bölücü" +addItem: "Öğe Ekle" +rearrange: "Yeniden düzenle" +relays: "Röleler" +addRelay: "Röle ekle" +inboxUrl: "Gelen Kutusu URL" +addedRelays: "Eklenen Röleler" +serviceworkerInfo: "Push bildirimleri için etkinleştirilmeli." +deletedNote: "Silinen not" +invisibleNote: "Görünmez not" +enableInfiniteScroll: "Otomatik olarak daha fazlasını yükle" +visibility: "Görünürlük" +poll: "Anket" +useCw: "İçeriği gizle" +enablePlayer: "Video oynatıcıyı aç" +disablePlayer: "Video oynatıcıyı kapat" +expandTweet: "Notu genişlet" +themeEditor: "Tema düzenleyici" +description: "Açıklama" +describeFile: "Alternatif metin ekle" +enterFileDescription: "Alternatif metin girin" +author: "Yazar" +leaveConfirm: "Kaydedilmemiş değişiklikler var. Bunları silmek istiyor musunuz?" +manage: "Yönetim" +plugins: "Eklentiler" +preferencesBackups: "Tercih yedeklemeleri" +deck: "Deck" +undeck: "Güverteden Ayrıl" +useBlurEffectForModal: "Modaller için bulanıklaştırma efekti kullanın" +useFullReactionPicker: "Tam boy tepki seçiciyi kullanın" +width: "Genişlik" +height: "Yükseklik" +large: "Büyük" +medium: "Orta" +small: "Küçük" +generateAccessToken: "Erişim jetonu oluştur" +permission: "İzinler" +adminPermission: "Yönetici İzinleri" +enableAll: "Tümünü etkinleştir" +disableAll: "Tümünü devre dışı bırak" +tokenRequested: "Hesaba erişim izni ver" +pluginTokenRequestedDescription: "Bu eklenti, burada ayarlanan izinleri kullanabilecek." +notificationType: "Bildirim türü" +edit: "Düzenle" +emailServer: "E-posta sunucusu" +enableEmail: "E-posta dağıtımını etkinleştir" +emailConfigInfo: "Kayıt sırasında veya şifreni unuttuğunda E-postanı doğrulamak için kullanılır." +email: "E-Posta" +emailAddress: "E-Posta adresi" +smtpConfig: "SMTP Sunucu yapılandırması" +smtpHost: "Host" +smtpPort: "Port" +smtpUser: "Kullanıcı adı" smtpPass: "Şifre" +emptyToDisableSmtpAuth: "SMTP kimlik doğrulamasını devre dışı bırakmak için kullanıcı adı ve şifre alanlarını boş bırakın." +smtpSecure: "SMTP bağlantıları için örtük SSL/TLS kullanın" +smtpSecureInfo: "STARTTLS kullanırken bunu kapatın." +testEmail: "Test E-postası gönderimi" +wordMute: "Kelime sustur" +wordMuteDescription: "Belirtilen kelime veya kelime öbeğini içeren notları küçültün. Küçültülmüş notlar, üzerlerine tıklanarak görüntülenebilir." +hardWordMute: "Zorla kelime sustur" +showMutedWord: "Sessize alınan kelimeleri göster" +hardWordMuteDescription: "Belirtilen kelime veya kelime öbeğini içeren notları gizle. Kelime sessize alma özelliğinden farklı olarak, not tamamen görünmez hale gelir." +regexpError: "Düzenli ifade hatası" +regexpErrorDescription: "{tab} kelimesinin {line} satırındaki düzenli ifadede bir hata oluştu:" +instanceMute: "Sunucu Sessizleştirme" +userSaysSomething: "{name} bir şey söyledi." +userSaysSomethingAbout: "{name} “{word}” hakkında bir şey söyledi." +makeActive: "Etkinleştir" +display: "Ekran" +copy: "Kopyala" +copiedToClipboard: "Panoya kopyalandı" +metrics: "Metrikler" +overview: "Genel Bakış" +logs: "Günlükler" +delayed: "Gecikmeli" +database: "Veritabanı" +channel: "Kanallar" +create: "Oluştur" notificationSetting: "Bildirim ayarları" +notificationSettingDesc: "Görüntülemek istediğiniz bildirim türlerini seçin." +useGlobalSetting: "Genel ayarları kullan" +useGlobalSettingDesc: "Etkinleştirildiğinde, hesabınızın bildirim ayarları kullanılır. Devre dışı bırakıldığında, bireysel yapılandırmalar yapılabilir." +other: "Diğer" +regenerateLoginToken: "Giriş jetonunu yeniden oluştur" +regenerateLoginTokenDescription: "Giriş sırasında dahili olarak kullanılan jetonu yeniden oluşturur. Normalde bu işlem gerekli değildir. Yeniden oluşturulursa, tüm cihazlar oturumu kapatılır." +theKeywordWhenSearchingForCustomEmoji: "Bu, kendi emojilerini ararken kullanılan anahtar kelimedir." +setMultipleBySeparatingWithSpace: "Birden fazla girişi boşluklarla ayırın." +fileIdOrUrl: "Dosya ID veya URL" +behavior: "Davranış" +sample: "Örnek" +abuseReports: "Raporlar" +reportAbuse: "Rapor" +reportAbuseRenote: "Raporu yeniden gönder" +reportAbuseOf: "{name} raporu" +fillAbuseReportDescription: "Bu raporla ilgili ayrıntıları lütfen doldur. Belirli bir notla ilgiliyse, lütfen URL'sini de ekle." +abuseReported: "Raporunuz gönderildi. Çok teşekkür ederiz." +reporter: "Raporlayan" +reporteeOrigin: "Bildirim Kaynağı" +reporterOrigin: "Bildirenin Kaynağı" +send: "Gönder" +openInNewTab: "Yeni sekmede aç" +openInSideView: "Yan görünümde aç" +defaultNavigationBehaviour: "Varsayılan gezinme davranışı" +editTheseSettingsMayBreakAccount: "Bu ayarları düzenlemek hesabınıza zarar verebilir." instanceTicker: "Notların sunucu bilgileri" +waitingFor: "{x} bekleniyor" +random: "Rastgele" +system: "Sistem" +switchUi: "UI değiştir" +desktop: "Masaüstü " +clip: "Klip" +createNew: "Yeni oluştur" +optional: "Opsiyonel" +createNewClip: "Klip oluştur" +unclip: "Klip kaldır" +confirmToUnclipAlreadyClippedNote: "Bu not zaten “{name}” klibinin bir parçası. Bu klipten silmek ister misin?" +public: "Herkese açık" +private: "Özel" +i18nInfo: "Misskey, gönüllüler tarafından çeşitli dillere çevrilmektedir. {link} adresinden yardımcı olabilirsin." +manageAccessTokens: "Acces Tokens yönet" +accountInfo: "Hesap bilgileri" +notesCount: "Not sayısı" +repliesCount: "Yanıt sayısı" +renotesCount: "Renote sayısı" +repliedCount: "Alınan yanıt sayısı" +renotedCount: "Alınan Renote sayısı" +followingCount: "Takip sayısı" +followersCount: "Takipçi sayısı" +sentReactionsCount: "Tepki sayısı" +receivedReactionsCount: "Alınan tepki sayısı" +pollVotesCount: "Anket oy sayısı" +pollVotedCount: "Alınan anket oy sayısı" +yes: "Evet" +no: "Hayır" +driveFilesCount: "Drive dosya sayısı" +driveUsage: "Drive alanı kullanımı" +noCrawle: "Tarayıcı indekslemesini reddet" noCrawleDescription: "Arama motorlarından profilinde, notlarında, sayfalarında vb. dolaşılmamasını ve dizine eklememesini talep et." -clearCache: "Ön belleği temizle" +lockedAccountInfo: "Notunuzun görünürlüğünü “Yalnızca takipçiler” olarak ayarlamadığınız sürece, takipçilerin manuel olarak onaylanmasını gerektirse bile notlarınız herkes tarafından görülebilir." +alwaysMarkSensitive: "Varsayılan olarak hassas olarak işaretle" +loadRawImages: "Küçük resimleri göstermek yerine orijinal resimleri yükle" +disableShowingAnimatedImages: "Animasyonlu görüntüleri oynatmayın" +highlightSensitiveMedia: "Hassas medyayı vurgulayın" +verificationEmailSent: "Doğrulama e-postası gönderildi. Doğrulamayı tamamlamak için e-postadaki bağlantıyı takip edin." +notSet: "Ayarlı değil" +emailVerified: "E-posta adresi doğrulandı." +noteFavoritesCount: "Favori not sayısı" +pageLikesCount: "Beğenilen sayfa sayısı" +pageLikedCount: "Alınan sayfa beğen sayısı" +contact: "İletişim" +useSystemFont: "Sistemin varsayılan yazı tipini kullanın" +clips: "Klipler" +experimentalFeatures: "Deneysel özellikler" +experimental: "Deneysel" +thisIsExperimentalFeature: "Bu deneysel bir özellik. İşlevselliği değişebilir ve amaçlandığı gibi çalışmayabilir." +developer: "Geliştirici" +makeExplorable: "Hesabı “Keşfet” bölümünde görünür hale getir" +makeExplorableDescription: "Bunu kapatırsanız, hesabınız “Keşfet” bölümünde görünmez." +duplicate: "Çoğalt" +left: "Sol" +center: "Merkez" +wide: "Geniş" +narrow: "Dar" +reloadToApplySetting: "Bu ayar, sayfa yeniden yüklendikten sonra geçerli olacaktır. Şimdi yeniden yüklemek ister misin?" +needReloadToApply: "Bunun yansıtılması için yeniden yükleme yapılması gerekir." +needToRestartServerToApply: "Değişikliğin yansıtılması için Misskey'in yeniden başlatılması gerekir." +showTitlebar: "Başlık çubuğunu göster" +clearCache: "Önbellek temizle" onlineUsersCount: "{n} kullanıcı çevrim içi" +nUsers: "{n} Kullanıcı" +nNotes: "{n} Not" +sendErrorReports: "Hata raporları gönder" +sendErrorReportsDescription: "Etkinleştirildiğinde, bir sorun oluştuğunda ayrıntılı hata bilgileri Misskey ile paylaşılacak ve bu da Misskey'in kalitesinin iyileştirilmesine yardımcı olacak.\nBu bilgiler arasında işletim sisteminizin sürümü, kullandığınız tarayıcı, Misskey'deki faaliyetlerin vb. yer alacaktır." +myTheme: "Benim temam" +backgroundColor: "Arka plan rengi" +accentColor: "Vurgu rengi" +textColor: "Metin rengi" +saveAs: "Farklı kaydet" +advanced: "Gelişmiş" +advancedSettings: "Gelişmiş ayarlar" +value: "Değer" +createdAt: "Oluşturuldu" +updatedAt: "Güncellendi" +saveConfirm: "Değişiklikleri kaydetmek ister misin?" +deleteConfirm: "Cidden silmek istiyor musunuz?" +invalidValue: "Geçersiz değer." +registry: "Kayıt Defteri" +closeAccount: "Hesabı kapat" +currentVersion: "Şu anki sürüm" +latestVersion: "En yeni sürüm" +youAreRunningUpToDateClient: "İstemci yazılımınızın en yeni sürümünü kullanıyorsunuz." +newVersionOfClientAvailable: "İstemcinin daha yeni bir sürümü var." +usageAmount: "Kullanım" +capacity: "Kapasite" +inUse: "Kullanılıyor" +editCode: "Kodu düzenle" +apply: "Uygula" +receiveAnnouncementFromInstance: "Bu sunucudan bildirimler alın" +emailNotification: "E-posta bildirimi" +publish: "Yayınla" +inChannelSearch: "Kanalda ara" +useReactionPickerForContextMenu: "Sağ tıklama ile tepki seçiciyi aç" +typingUsers: "{users} yazıyor..." +jumpToSpecifiedDate: "Belirli bir tarihe atla" +showingPastTimeline: "Şu anda eski bir Pano görüntüleniyor." +clear: "Temizle" +markAllAsRead: "Tümünü okundu olarak işaretle" +goBack: "Geri" +unlikeConfirm: "Cidden beğenini kaldırmak mı istiyorsun?" +fullView: "Tam görünüm" +quitFullView: "Tam ekranı kapat" +addDescription: "Açıklama ekle" +userPagePinTip: "Bireysel notların menüsünden “Profiline sabitle” seçeneğini seçerek notları burada görüntüleyebilirsin." +notSpecifiedMentionWarning: "Bu notta, alıcılar arasında yer almayan kullanıcılar hakkında bilgiler bulunmaktadır." +info: "Hakkında" +userInfo: "Kullanıcı hakkında" +unknown: "Bilinmiyor" +onlineStatus: "Çevrimiçi durumu" +hideOnlineStatus: "Çevrimiçi durumunu gizle" +hideOnlineStatusDescription: "Çevrimiçi durumunuzu gizlemek, arama gibi bazı özelliklerin kullanışlılığını azaltır." +online: "Online" +active: "Aktif" +offline: "Offline" +notRecommended: "Tavsiye edilmez" +botProtection: "Bot Koruması" +instanceBlocking: "Blocked/Silenced Instances" +selectAccount: "Hesap seç" +switchAccount: "Hesap değiştir" +enabled: "Aktif" +disabled: "Devre Dışı" +quickAction: "Hızlı eylemler" user: "Kullanıcı" -global: "Küresel" +administration: "Yönetim" +accounts: "Hesaplar" +switch: "Anahtar" +noMaintainerInformationWarning: "Bakımcı bilgileri yapılandırılmamıştır." +noInquiryUrlWarning: "Sorgu URL'si ayarlanmadı" +noBotProtectionWarning: "Bot koruması yapılandırılmamıştır." +configure: "Yeniden Yapılandır" +postToGallery: "Yeni galeri gönderisi oluştur" +postToHashtag: "Bu hashtag'e gönder" +gallery: "Galeri" +recentPosts: "Son gönderiler" +popularPosts: "Popüler gönderiler" +shareWithNote: "Notla paylaş" +ads: "Reklamlar" +expiration: "Son tarih" +startingperiod: "Başla" +memo: "Hatırlatıcı" +priority: "Öncelik" +high: "Yüksek" +middle: "Orta" +low: "Düşük" +emailNotConfiguredWarning: "E-posta adresi ayarlanmamış." +ratio: "Oran" +previewNoteText: "Önizlemeyi göster" +customCss: "Özel CSS" +customCssWarn: "Bu ayar, yalnızca ne işe yaradığını biliyorsanız kullanılmalıdır. Yanlış değerler girilmesi, istemcinin normal şekilde çalışmamasına neden olabilir." +global: "Global" squareAvatars: "Kare avatarlar" +sent: "Gönderilen" +received: "Alınan" +searchResult: "Arama sonuçları" +hashtags: "Hashtag'ler" +troubleshooting: "Sorun Giderme" +useBlurEffect: "UI'da bulanıklık efektleri kullanın" +learnMore: "Daha fazla bilgi edinin" +misskeyUpdated: "Misskey güncellendi!" +whatIsNew: "Değişiklikleri göster" +translate: "Çevir" +translatedFrom: "{x}'ten çevrilmiştir." +accountDeletionInProgress: "Hesap silme işlemi şu anda devam ediyor." +usernameInfo: "Bu sunucudaki diğer hesaplardan hesabını ayıran bir isim. Alfabe (a~z, A~Z), rakamlar (0~9) veya alt çizgi (_) kullanabilirsin. Kullanıcı adları daha sonra değiştirilemez." +aiChanMode: "Ai Modu" +devMode: "Geliştirici modu" +keepCw: "İçerik uyarılarını sakla" +pubSub: "Yayın/Abonelik Hesapları" +lastCommunication: "Son iletişim" +resolved: "Çözülmüş" +unresolved: "Çözülmemiş" +breakFollow: "Takipçiyi kaldır" +breakFollowConfirm: "Bu takipçiyi ciddden silmek istiyor musun?" +itsOn: "Etkin" +itsOff: "Devre Dışı" +on: "Açık" +off: "Kapalı" +emailRequiredForSignup: "Kayıt için E-posta adresi gereklidir." +unread: "Okunmamış" +filter: "Filtre" +controlPanel: "Kontrol Paneli" +manageAccounts: "Hesapları Yönet" +makeReactionsPublic: "Tepki geçmişini herkese açık olarak ayarla" +makeReactionsPublicDescription: "Bu, geçmişteki tüm tepkilerinin listesini herkese açık hale getirecek." +classic: "Klasik" +muteThread: "Konuyu sessize al" +unmuteThread: "Konuyu sessizden çıkar" +followingVisibility: "Takip edilenlerin görünürlüğü" +followersVisibility: "Takipçilerin görünürlüğü" +continueThread: "Konunun devamını görüntüle" +deleteAccountConfirm: "Bu, hesabını geri dönüşü olmayan bir şekilde silecek. Devam etmek istiyor musun?" +incorrectPassword: "Yanlış şifre." +incorrectTotp: "Tek kullanımlık şifre yanlış veya süresi dolmuş." +voteConfirm: "\"{choice}\" için oyunuzu onaylıyor musunuz?" +hide: "Gizle" +useDrawerReactionPickerForMobile: "Mobil cihazlarda tepki seçiciyi çekmece olarak göster" +welcomeBackWithName: "Hoş geldin, {name}" +clickToFinishEmailVerification: "E-posta doğrulamasını tamamlamak için lütfen [{ok}] düğmesine tıklayın." +overridedDeviceKind: "Cihaz türü" +smartphone: "Akıllı telefon" +tablet: "Tablet" +auto: "Otomatik" +themeColor: "Örnek Ticker Rengi" +size: "Boyut" +numberOfColumn: "Sütun sayısı" searchByGoogle: "Arama" +instanceDefaultLightTheme: "Sunucu genelinde varsayılan açık tema" +instanceDefaultDarkTheme: "Sunucu genelinde varsayılan koyu tema" +instanceDefaultThemeDescription: "Tema kodunu nesne biçiminde girin." +mutePeriod: "Sessiz kalma süresi" +period: "Zaman sınırı" +indefinitely: "Kalıcı olarak" +tenMinutes: "10 dakika" +oneHour: "1 saat" +oneDay: "1 gün" +oneWeek: "1 hafta" +oneMonth: "1 ay" +threeMonths: "3 ay" +oneYear: "1 yıl" +threeDays: "3 gün" +reflectMayTakeTime: "Bunun yansıtılması biraz zaman alabilir." +failedToFetchAccountInformation: "Hesap bilgileri alınamadı" +rateLimitExceeded: "Hız sınırı aşıldı" +cropImage: "Görüntüyü kırp" +cropImageAsk: "Bu görüntüyü kırpmak ister misin?" +cropYes: "Kırp" +cropNo: "Olduğu gibi kullanın" file: "Dosyalar" +recentNHours: "Son {n} saat" +recentNDays: "Son {n} gün" +noEmailServerWarning: "E-posta sunucusu yapılandırılmamış." +thereIsUnresolvedAbuseReportWarning: "Çözülmemiş raporlar var." +recommended: "Önerilen" +check: "Kontrol" +driveCapOverrideLabel: "Bu kullanıcının Drive kapasitesini değiştir" +driveCapOverrideCaption: "Kapasiteyi varsayılan değere sıfırlamak için 0 veya daha düşük bir değer girin." +requireAdminForView: "Bunu görüntülemek için yönetici hesabıyla oturum açmanız gerekir." +isSystemAccount: "Sistem tarafından oluşturulan ve otomatik olarak işletilen bir hesap." +typeToConfirm: "Onaylamak için lütfen {x} girin." +deleteAccount: "Hesabı sil" +document: "Dokümantasyon" +numberOfPageCache: "Önbelleğe alınmış sayfa sayısı" +numberOfPageCacheDescription: "Bu sayıyı artırmak, kullanıcının cihazında daha fazla bellek kullanımı nedeniyle daha fazla yük oluşturmakla birlikte, kullanıcının rahatlığını artıracaktır." +logoutConfirm: "Çıkmak istediğinden emin misin?" +logoutWillClearClientData: "Oturumu kapatmak, tarayıcıdan istemcinin ayarlarını siler. Tekrar oturum açtığında ayarları geri yükleyebilmek için, ayarlarının otomatik yedeklenmesini etkinleştirmen gerekir." +lastActiveDate: "Son kullanımı" +statusbar: "Durum çubuğu" +pleaseSelect: "Bir seçenek seçin" +reverse: "Tersine" +colored: "Renkli" +refreshInterval: "Güncelleme aralığı" +label: "Etiket" +type: "Tür" +speed: "Hız" +slow: "Yavaş" +fast: "Hızlı" +sensitiveMediaDetection: "Hassas ortamların tespiti" +localOnly: "Yalnızca yerel" +remoteOnly: "Sadece uzaktan" +failedToUpload: "Yükleme başarısız" +cannotUploadBecauseInappropriate: "Bu dosya, dosyanın bazı kısımlarının uygunsuz olabileceği tespit edildiği için yüklenemiyor." +cannotUploadBecauseNoFreeSpace: "Drive kapasitesi yetersiz olduğu için yükleme başarısız oldu." +cannotUploadBecauseExceedsFileSizeLimit: "Bu dosya, dosya boyutu sınırını aştığı için yüklenemiyor." +cannotUploadBecauseUnallowedFileType: "Yetkisiz dosya türü nedeniyle yükleme yapılamıyor." +beta: "Beta" +enableAutoSensitive: "Otomatik olarak hassas olarak işaretleme" +enableAutoSensitiveDescription: "Mümkün olduğunda, Makine Öğrenimi yoluyla hassas ortamların otomatik olarak algılanmasını ve işaretlenmesini sağlar. Bu seçenek devre dışı bırakılmış olsa bile, örnek genelinde etkinleştirilebilir." +activeEmailValidationDescription: "E-posta adreslerinin daha sıkı bir şekilde doğrulanmasını sağlar. Bu, tek kullanımlık adreslerin kontrol edilmesini ve adresin gerçekten iletişim kurulabilir olup olmadığının kontrol edilmesini içerir. İşaretlenmediğinde, yalnızca e-postanın biçimi doğrulanır." +navbar: "Gezinti çubuğu" +shuffle: "Karıştır" +account: "Hesap" +move: "Taşı" pushNotification: "Push bildirimleri" subscribePushNotification: "Push bildirimlerini etkinleştir" unsubscribePushNotification: "Push bildirimlerini kapat" pushNotificationAlreadySubscribed: "Push bildirimleri zaten açık" pushNotificationNotSupported: "Push bildirimleri sunucu veya tarayıcı tarafından desteklenmiyor" +sendPushNotificationReadMessage: "Okunduktan sonra push bildirimlerini silin" +sendPushNotificationReadMessageCaption: "Bu, cihazınızın güç tüketimini artırabilir." +windowMaximize: "Maksimize et" +windowMinimize: "Minimize et" +windowRestore: "Geri yükle" +caption: "Alternatif metin" +loggedInAsBot: "Şu anda bot olarak oturum açmış durumdasınız." +tools: "Araçlar" +cannotLoad: "Yüklenemiyor" +numberOfProfileView: "Profil görüntülemeleri" +like: "Beğen" +unlike: "Beğenme" +numberOfLikes: "Beğeniler" +show: "Göster" +neverShow: "Bir daha gösterme" +remindMeLater: "Belki daha sonra" +didYouLikeMisskey: "Misskey'i sevdin mi?" +pleaseDonate: "{host} ücretsiz yazılım Misskey kullanmaktadır. Misskey'in geliştirilmesinin devam edebilmesi için bağışlarınızı çok takdir ederiz!" +correspondingSourceIsAvailable: "İlgili kaynak kodu {anchor} adresinde mevcuttur." +roles: "Roller" +role: "Rol" noRole: "Rol bulunamadı" +normalUser: "Normal kullanıcı" +undefined: "Tanımlanmamış" +assign: "Atama" +unassign: "Atamayı kaldır" color: "Renk" +manageCustomEmojis: "Özel Emojileri Yönet" +manageAvatarDecorations: "Avatar süslerini yönet" +youCannotCreateAnymore: "Oluşturma sınırına ulaştınız." +cannotPerformTemporary: "Geçici olarak kullanılamıyor" +cannotPerformTemporaryDescription: "Bu işlem, yürütme sınırını aştığı için geçici olarak gerçekleştirilememekte. Lütfen bir süre bekle ve tekrar dene." +invalidParamError: "Geçersiz parametreler" +invalidParamErrorDescription: "İstek parametreleri geçersiz. Bu durum genellikle bir hata nedeniyle oluşur, ancak boyut sınırlarını aşan girdiler veya benzer nedenlerden de kaynaklanabilir." +permissionDeniedError: "İşlem reddedildi" +permissionDeniedErrorDescription: "Bu hesap bu işlemi gerçekleştirmek için gerekli izne sahip değildir." +preset: "Ön ayar" +selectFromPresets: "Ön ayarlardan seçim yapın" +custom: "Özel" +achievements: "Başarılar" +gotInvalidResponseError: "Geçersiz sunucu yanıtı" +gotInvalidResponseErrorDescription: "Sunucu erişilemez durumda olabilir veya bakım çalışması yapılmaktadır. Lütfen daha sonra tekrar dene." +thisPostMayBeAnnoying: "Bu not başkalarını rahatsız edebilir." +thisPostMayBeAnnoyingHome: "Ana panoya gönder" +thisPostMayBeAnnoyingCancel: "İptal" +thisPostMayBeAnnoyingIgnore: "Yine de gönder" +collapseRenotes: "Daha önce görüntülenen Renote'lari kısaltılmış olarak göster" +collapseRenotesDescription: "Zaten yanıtladığın veya renote aldığın notları kapat." +internalServerError: "İç Sunucu Hatası" +internalServerErrorDescription: "Sunucu beklenmedik bir hatayla karşılaştı." +copyErrorInfo: "Hata ayrıntılarını kopyala" +joinThisServer: "Kaydol" +exploreOtherServers: "Diğer sunucuları keşfet" +letsLookAtTimeline: "Pano'ya bir göz atın" +disableFederationConfirm: "Federasyonu cidden devre dışı bırakmak istiyor musun?" +disableFederationConfirmWarn: "Federasyondan ayrılsa bile, aksi belirtilmedikçe gönderiler herkese açık olmaya devam edecek. Genellikle bunu yapmanız gerekmez." +disableFederationOk: "Devre Dışı" +invitationRequiredToRegister: "Bu etkinlik davetle katılımlıdır. Geçerli bir davet kodu girerek kaydolmanız gerekir." +emailNotSupported: "Bu sunucu, E-Posta göndermeyi desteklemiyor." +postToTheChannel: "Kanalına gönder" +cannotBeChangedLater: "Bu daha sonra değiştirilemez." +reactionAcceptance: "Tepki Kabulü" +likeOnly: "Sadece beğeniler" +likeOnlyForRemote: "Tüm (Yalnızca uzak sunucu için beğeniler)" +nonSensitiveOnly: "Hassas olmayanlar için" +nonSensitiveOnlyForLocalLikeOnlyForRemote: "Yalnızca hassas olmayanlar (Yalnızca uzaktan beğeniler)" +rolesAssignedToMe: "Bana atanan roller" +resetPasswordConfirm: "Şifreni gerçekten sıfırlamak istiyor musun?" +sensitiveWords: "Hassas kelimeler" +sensitiveWordsDescription: "Yapılandırılan kelimelerden herhangi birini içeren tüm notların görünürlüğü otomatik olarak “Ana Sayfa” olarak ayarlanacaktır. Satır sonları ile ayırarak birden fazla not listeleyebilirsin." +sensitiveWordsDescription2: "Boşluk kullanmak AND ifadeleri oluşturur ve anahtar kelimeleri eğik çizgi ile çevrelemek bunları düzenli ifadeye dönüştürür." +prohibitedWords: "Yasaklanmış kelimeler" +prohibitedWordsDescription: "Belirlenen kelime(ler)i içeren bir not göndermeye çalışıldığında hata verir. Birden fazla kelime, yeni satırla ayrılmış olarak ayarlanabilir." +prohibitedWordsDescription2: "Boşluk kullanmak AND ifadeleri oluşturur ve anahtar kelimeleri eğik çizgi ile çevrelemek bunları düzenli ifadeye dönüştürür." +hiddenTags: "Gizli hashtag'ler" +hiddenTagsDescription: "Trend listesinde gösterilmeyecek etiketleri seçin.\nSatırlarla birden fazla etiket kaydedilebilir." +notesSearchNotAvailable: "Not arama özelliği kullanılamıyor." +usersSearchNotAvailable: "Kullanıcı araması mevcut değildir." +license: "Lisans" +unfavoriteConfirm: "Cidden favorilerden kaldırmak istiyor musunuz?" +myClips: "Kliplerim" +drivecleaner: "Drive Temizleyici" +retryAllQueuesNow: "Tüm kuyrukları yeniden çalıştırmayı deneyin" +retryAllQueuesConfirmTitle: "Cidden hepsini tekrar denemek istiyor musunuz?" +retryAllQueuesConfirmText: "Bu, sunucu yükünü geçici olarak artıracaktır." +enableChartsForRemoteUser: "Uzak kullanıcı veri grafikleri oluşturun" +enableChartsForFederatedInstances: "Uzak sunucu veri grafikleri oluşturun" +enableStatsForFederatedInstances: "Uzak sunucu istatistiklerini alın" +showClipButtonInNoteFooter: "Not eylem menüsüne “Klip” ekle" +reactionsDisplaySize: "Tepki ekran boyutu" +limitWidthOfReaction: "Tepkilerin maksimum genişliğini sınırla ve bunları küçültülmüş boyutta görüntüle." +noteIdOrUrl: "Not ID veya URL" +video: "Video" +videos: "Videolar" +audio: "Ses" +audioFiles: "Ses Dosyası" +dataSaver: "Veri Tasarrufu" +accountMigration: "Hesap Taşıma" +accountMoved: "Bu kullanıcı yeni bir hesaba taşındı:" +accountMovedShort: "Bu hesap taşınmıştır." +operationForbidden: "İşlem yasak" +forceShowAds: "Her zaman reklamları göster" addMemo: "Kısa not ekle" +editMemo: "Kısa not düzenle" +reactionsList: "Tepkiler" +renotesList: "Renote'lar" +notificationDisplay: "Bildirimler" +leftTop: "Sol üst" +rightTop: "Sağ üst" +leftBottom: "Sol alt" +rightBottom: "Sağ alt" +stackAxis: "Yığınlama yönü" +vertical: "Dikey" +horizontal: "Yatay" +position: "Pozisyon" +serverRules: "Sunucu kuralları" +pleaseConfirmBelowBeforeSignup: "Bu sunucuya kaydolmak için aşağıdakileri gözden geçirip kabul etmelisin:" +pleaseAgreeAllToContinue: "Devam etmek için yukarıdaki tüm alanları kabul etmelisin." +continue: "Devam et" +preservedUsernames: "Rezerve edilmiş kullanıcı adları" +preservedUsernamesDescription: "Rezervasyon yapmak için kullanıcı adlarını satır sonlarıyla ayırarak listele. Bu kullanıcı adları normal hesap oluşturma sırasında kullanılamaz hale gelir, ancak yöneticiler tarafından manuel olarak hesap oluşturmak için kullanılabilir. Bu kullanıcı adlarını kullanan mevcut hesaplar etkilenmez." +createNoteFromTheFile: "Bu dosyadan not oluşturun" +archive: "Arşiv" +archived: "Arşivle" +unarchive: "Arşivden çıkar" +channelArchiveConfirmTitle: "Cidden {name} arşivlemek mi istiyorsun?" +channelArchiveConfirmDescription: "Arşivlenmiş bir kanal artık kanal listesinde veya arama sonuçlarında görünmeyecektir. Ayrıca, bu kanala yeni gönderiler eklenemeyecek." +thisChannelArchived: "Bu kanal arşivlenmiş." +displayOfNote: "Not ekranı" +initialAccountSetting: "Profil ayarları" +youFollowing: "Takip edildi" +preventAiLearning: "Makine Öğreniminde (Üretken Ai) kullanımını reddet" +preventAiLearningDescription: "Tarayıcılardan, makine öğrenimi (Tahminsel / Üretken Ai) veri kümelerinde yayınlanan metin veya görsel materyalleri vb. kullanmamalarını talep eder. Bu, ilgili içeriğe “noai” HTML-Response bayrağı eklenerek gerçekleştirilir. Ancak, bu bayrakla tam bir önleme sağlanamaz, çünkü bu bayrak basitçe göz ardı edilebilir." +options: "Seçenekler" +specifyUser: "Belirli kullanıcı" +lookupConfirm: "Yukarı bakmak ister misin?" +openTagPageConfirm: "Bir hashtag sayfası açmak ister misin?" +specifyHost: "Belirli ana bilgisayar" +failedToPreviewUrl: "Önizleme yapılamadı" +update: "Güncelle" +rolesThatCanBeUsedThisEmojiAsReaction: "Bu emojiyi tepki olarak kullanabileceğin roller" +rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "Herhangi bir rol belirtilmezse, herkes bu emojiyi tepki olarak kullanabilir." +rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Bu roller herkese açık olmalıdır." +cancelReactionConfirm: "Tepkini cidden silmek istiyor musun?" +changeReactionConfirm: "Tepkini cidden değiştirmek istiyor musun?" +later: "Daha sonra" +goToMisskey: "Misskey'e" +additionalEmojiDictionary: "Ek emoji sözlükleri" +installed: "Yüklendi" +branding: "Markalaşma" +enableServerMachineStats: "Sunucu donanım istatistiklerini yayınla" +enableIdenticonGeneration: "Kullanıcı identicon oluşturmayı etkinleştir" +turnOffToImprovePerformance: "Devre dışı bırakma, daha yüksek performansa yol açabilir." +createInviteCode: "Davet Kodu oluştur" +createWithOptions: "Seçeneklerle oluştur" +createCount: "Davet sayısı" +inviteCodeCreated: "Davet oluşturuldu" +inviteLimitExceeded: "Oluşturulabilecek davetiyelerin maksimum sayısına ulaştın." +createLimitRemaining: "{limit} Davet limiti kaldı" +inviteLimitResetCycle: "Bu limit {time} tarihinde {limit} değerine sıfırlanacaktır." +expirationDate: "Son kullanma tarihi" +noExpirationDate: "Son kullanma tarihi yok" +inviteCodeUsedAt: "Kullanılan davet kodu" +registeredUserUsingInviteCode: "Kullanılan davet" +waitingForMailAuth: "E-Posta doğrulama beklemede" +inviteCodeCreator: "Davet oluşturuldu" +usedAt: "Kullanıldığı yer" +unused: "Kullanılmamış" +used: "Kullanılmış" +expired: "Süresi dolmuş" +doYouAgree: "Katılıyor musunuz?" +beSureToReadThisAsItIsImportant: "Lütfen bu önemli bilgileri okuyun." +iHaveReadXCarefullyAndAgree: "“{x}” metnini okudum ve kabul ediyorum." +dialog: "Diyalog" icon: "Avatar" -replies: "yanıt" -renotes: "vazgeçme" +forYou: "Senin için" +currentAnnouncements: "Güncel duyurular" +pastAnnouncements: "Geçmiş duyurular" +youHaveUnreadAnnouncements: "Okunmamış duyurular var." +useSecurityKey: "Güvenlik anahtarını veya şifreni kullanmak için lütfen tarayıcının veya cihazının talimatlarını izle." +replies: "Yanıtla" +renotes: "Renote'lar" +loadReplies: "Yanıtları göster" +loadConversation: "Konuşmayı göster" +pinnedList: "Sabitlenmiş liste" +keepScreenOn: "Ekranı açık tut" +verifiedLink: "Bağlantı sahipliği doğrulanmıştır." +notifyNotes: "Yeni notlar hakkında bildirimde bulun" +unnotifyNotes: "Yeni notlar hakkında bildirim almayı durdur" +authentication: "Kimlik doğrulama" +authenticationRequiredToContinue: "Devam etmek için lütfen kimlik doğrulaması yapın." +dateAndTime: "Zaman damgası" +showRenotes: "Renote'ları göster" +edited: "Düzenlendi" +notificationRecieveConfig: "Bildirim Ayarları" +mutualFollow: "Karşılıklı takip" +followingOrFollower: "Takip eden veya takipçi" +fileAttachedOnly: "Yalnızca dosya içeren notlar" +showRepliesToOthersInTimeline: "Pano'da diğer kişilere verilen yanıtları göster" +hideRepliesToOthersInTimeline: "Pano'dan diğer kişilerin yanıtlarını gizle" +showRepliesToOthersInTimelineAll: "Pano'da takip ettiğin herkesin diğerlerine verdiği yanıtları göster" +hideRepliesToOthersInTimelineAll: "Pano'da takip ettiğin herkesten diğer kişilere verilen yanıtları gizle" +confirmShowRepliesAll: "Bu işlem geri alınamaz. Takip ettiğin herkesin yanıtlarını panoda diğer kullanıcılara göstermek istiyor musun?" +confirmHideRepliesAll: "Bu işlem geri alınamaz. Şu anda takip ettiğin tüm kullanıcıların yanıtlarını panoda cidden göstermeyecek misin?" +externalServices: "Dış Hizmetler" +sourceCode: "Kaynak kodu" +sourceCodeIsNotYetProvided: "Kaynak kodu henüz mevcut değildir. Bu sorunu gidermek için yöneticiyle iletişime geçin." +repositoryUrl: "Depo URL'si" +repositoryUrlDescription: "Misskey'i olduğu gibi kullanıyorsanız (kaynak kodunda herhangi bir değişiklik yapmadan), https://github.com/misskey-dev/misskey adresini girin." +repositoryUrlOrTarballRequired: "Bir depo yayınlamadıysanız, bunun yerine bir tarball sağlamalısınız. Daha fazla bilgi için .config/example.yml dosyasına bakın." +feedback: "Feedback" +feedbackUrl: "Geri Bildirim URL'si" +impressum: "Yayıncı Bilgileri" +impressumUrl: "Yayıncı Bilgileri URL'si" +impressumDescription: "Almanya gibi bazı ülkelerde, ticari web sitelerinde işletmeci iletişim bilgilerinin (Yayıncı) yer alması yasal olarak zorunludur." +privacyPolicy: "Gizlilik Politikası" +privacyPolicyUrl: "Gizlilik Politikası URL'si" +tosAndPrivacyPolicy: "Hizmet Şartları ve Gizlilik Politikası" +avatarDecorations: "Avatar süsleri" +attach: "Ek" +detach: "Kaldır" +detachAll: "Tümünü Kaldır" +angle: "Açı" +flip: "Çevir" +showAvatarDecorations: "Avatar süslerini göster" +releaseToRefresh: "Yenilemek için serbest bırak" +refreshing: "Yenileniyor..." +pullDownToRefresh: "Yenilemek için aşağı çekin" +useGroupedNotifications: "Gruplandırılmış bildirimleri göster" +emailVerificationFailedError: "E-posta adresi doğrulanırken bir sorun oluştu. Bağlantının geçerlilik süresi dolmuş olabilir." +cwNotationRequired: "“İçeriği gizle” seçeneği etkinleştirilirse, bir açıklama sağlanmalı." +doReaction: "Tepki ekle" +code: "Kod" +reloadRequiredToApplySettings: "Ayarları uygulamak için yeniden yükleme gereklidir." +remainingN: "Kalan: {n}" +overwriteContentConfirm: "Mevcut içeriği üzerine yazmak istediğinden emin misin?" +seasonalScreenEffect: "Mevsimsel Ekran Efekti" +decorate: "Süsle" +addMfmFunction: "MFM ekle" +enableQuickAddMfmFunction: "Gelişmiş MFM seçiciyi göster" +bubbleGame: "Kabarcık Oyunu" +sfx: "Ses Efektleri" +soundWillBePlayed: "Ses çalınacaktır" +showReplay: "Tekrarı izle" +replay: "Tekrar oynat" +replaying: "Tekrar gösteriliyor" +endReplay: "Tekrardan çık" +copyReplayData: "Tekrar oynatma verilerini kopyala" +ranking: "Sıralama" +lastNDays: "Son {n} gün" +backToTitle: "Başlığa geri dön" +hemisphere: "Yaşadığınız yer" +withSensitive: "Hassas dosyalara notlar ekle" +userSaysSomethingSensitive: "{name} tarafından gönderilen mesaj hassas içerik barındırmaktadır." +enableHorizontalSwipe: "Kaydırarak sekmeler arasında geçiş yapın" +loading: "Yükleniyor" +surrender: "İptal" +gameRetry: "Tekrar dene" +notUsePleaseLeaveBlank: "Kullanılmıyorsa boş bırakın." +useTotp: "Tek Kullanımlık Şifreyi Girin" +useBackupCode: "Yedek kodları kullanın" +launchApp: "Uygulamayı başlatın" +useNativeUIForVideoAudioPlayer: "Video ve ses oynatımı için tarayıcı kullanıcı arayüzünü kullan" +keepOriginalFilename: "Orijinal dosya adını koru" +keepOriginalFilenameDescription: "Bu ayarı kapatırsan, dosya yüklediğinde dosya adları otomatik olarak rastgele bir dizeyle değiştirilecek." +noDescription: "Açıklama yok" +alwaysConfirmFollow: "Takip ederken her zaman onaylayın" +inquiry: "İletişim" +tryAgain: "Lütfen daha sonra tekrar dene." +confirmWhenRevealingSensitiveMedia: "Confirm when revealing sensitive media" +sensitiveMediaRevealConfirm: "Bu hassas bir medya olabilir. Açıklamakta emin misin?" +createdLists: "Oluşturulan listeler" +createdAntennas: "Oluşturulan antenler" +fromX: "{x}'den" +genEmbedCode: "Gömme kodu oluştur" +noteOfThisUser: "Bu kullanıcının notları" +clipNoteLimitExceeded: "Bu klibe daha fazla not eklenemez." +performance: "Başarım" +modified: "Değiştirilmiş" +discard: "At" +thereAreNChanges: "{n} değişiklik var." +signinWithPasskey: "Geçiş Anahtarı ile giriş yapın" +unknownWebAuthnKey: "Bilinmeyen Geçiş Anahtarı" +passkeyVerificationFailed: "Geçiş Anahtarı doğrulama başarısız oldu." +passkeyVerificationSucceededButPasswordlessLoginDisabled: "Geçiş anahtarı doğrulaması başarılı oldu ancak şifresiz oturum açma devre dışıdır." +messageToFollower: "Takipçilere mesaj" +target: "Hedef" +testCaptchaWarning: "Bu işlev CAPTCHA testi amacıyla tasarlanmıştır.\nÜretim ortamında kullanmayın." +prohibitedWordsForNameOfUser: "Kullanıcı adları için yasaklanmış kelimeler" +prohibitedWordsForNameOfUserDescription: "Bu listedeki dizilerden herhangi biri kullanıcının adında yer alıyorsa, ad reddedilecektir. Moderatör ayrıcalıklarına sahip kullanıcılar bu kısıtlamadan etkilenmez." +yourNameContainsProhibitedWords: "Adınız yasaklanmış kelimeler içeriyor" +yourNameContainsProhibitedWordsDescription: "Bu adı kullanmak istiyorsan, lütfen sunucu yöneticinizle iletişime geç." +thisContentsAreMarkedAsSigninRequiredByAuthor: "Yazar tarafından görüntülemek için oturum açma gerektirir." +lockdown: "Karantina" +pleaseSelectAccount: "Hesap seçin" +availableRoles: "Mevcut roller" +acknowledgeNotesAndEnable: "Önlemleri anladıktan sonra açın." +federationSpecified: "Bu sunucu, beyaz liste federasyonunda çalıştırılmaktadır. Yönetici tarafından belirlenen sunucular dışında diğer sunucularla etkileşim kurmak yasaktır." +federationDisabled: "Bu sunucuda federasyon devre dışıdır. Diğer sunuculardaki kullanıcılarla etkileşim kuramazsınız." +draft: "Taslaklar" +confirmOnReact: "Tepki verirken onaylayın" +reactAreYouSure: "“{emoji}” tepkisini eklemek ister misin?" +markAsSensitiveConfirm: "Bu medyayı hassas olarak ayarlamak ister misin?" +unmarkAsSensitiveConfirm: "Bu medya için hassas işaretini kaldırmak ister misin?" +preferences: "Tercihler" +accessibility: "Erişilebilirlik" +preferencesProfile: "Tercihler profili" +copyPreferenceId: "Tercih ID kopyala" +resetToDefaultValue: "Varsayılana dön" +overrideByAccount: "Hesap tarafından geçersiz kılma" +untitled: "İsimsiz" +noName: "İsim yok" +skip: "Atla" +restore: "Geri yükle" +syncBetweenDevices: "Cihazlar arasında senkronizasyon" +preferenceSyncConflictTitle: "Yapılandırılan değer sunucuda mevcuttur." +preferenceSyncConflictText: "Senkronizasyon etkin ayarlar, değerlerini sunucuya kaydeder. Ancak, sunucuda mevcut değerler bulunmaktadır. Hangi değerleri üzerine yazmak istersin?" +preferenceSyncConflictChoiceMerge: "Birleştir" +preferenceSyncConflictChoiceServer: "Sunucuda yapılandırılan değer" +preferenceSyncConflictChoiceDevice: "Cihazda yapılandırılan değer" +preferenceSyncConflictChoiceCancel: "Senkronizasyonu etkinleştirmeyi iptal et" +paste: "Yapıştır" +emojiPalette: "Emoji paleti" +postForm: "Gönderim formu" +textCount: "Karakter sayısı" +information: "Hakkında" +chat: "Sohbet" +migrateOldSettings: "Eski istemci ayarlarını taşıma" +migrateOldSettings_description: "Bu işlem otomatik olarak yapılmalıdır, ancak herhangi bir nedenle geçiş başarısız olursa, geçiş işlemini manuel olarak kendin başlatabilirsin. Mevcut yapılandırma bilgileri üzerine yazılacaktır." +compress: "Sıkıştır" +right: "Sağ" +bottom: "Alt" +top: "Üst" +embed: "Göm" +settingsMigrating: "Ayarlar taşınıyor, lütfen bir dakika bekle... (Daha sonra Ayarlar→Diğerler→Eski ayarları taşı seçeneğine giderek manuel olarak da taşıyabilirsin)" +readonly: "Sadece okuma" +goToDeck: "Güverteye Dön" +federationJobs: "Federasyon İşleri" +driveAboutTip: "Drive'da, geçmişte yüklediğin dosyaların bir listesi görüntülenir.
\nBu dosyaları notlara eklerken yeniden kullanabilir veya daha sonra paylaşmak üzere önceden yükleyebilirsin.
\nBir dosyayı silerken dikkatli ol, çünkü kullanıldığı her yerde (notlar, sayfalar, avatarlar, afişler vb.) mevcut olmayacakt.
\nAyrıca dosyalarını düzenlemek için klasörler oluşturabilirsin." +scrollToClose: "Kaydırarak kapatın" +advice: "Tavsiye" +realtimeMode: "Gerçek zamanlı mod" +turnItOn: "Aç" +turnItOff: "Kapat" +emojiMute: "Emoji ses kapat" +emojiUnmute: "Emoji ses aç" +muteX: "Sessiz {x}" +unmuteX: "Sesi aç {x}" +abort: "İptal" +tip: "İpucu & Püf Nokta" +redisplayAllTips: "Tüm “İpucu & Püf Nokta” tekrar göster" +hideAllTips: "Tüm “İpucu & Püf Nokta” gizle" +defaultImageCompressionLevel: "Varsayılan görüntü sıkıştırma düzeyi" +defaultImageCompressionLevel_description: "Düşük seviye görüntü kalitesini korur ancak dosya boyutunu artırır.
Yüksek seviye dosya boyutunu azaltır ancak görüntü kalitesini düşürür." +inMinutes: "Dakika(lar)" +inDays: "Gün(ler)" +safeModeEnabled: "Güvenli mod etkinleştirildi" +pluginsAreDisabledBecauseSafeMode: "Güvenli mod etkinleştirildiği için tüm eklentiler devre dışı bırakılmıştır." +customCssIsDisabledBecauseSafeMode: "Güvenli mod etkin olduğu için özel CSS uygulanmıyor." +themeIsDefaultBecauseSafeMode: "Güvenli mod etkinken, varsayılan tema kullanılır. Güvenli modu devre dışı bırakmak bu değişiklikleri geri alır." +thankYouForTestingBeta: "Beta sürümünü test ettiğin için teşekkür ederiz!" +_order: + newest: "Önce yeni" + oldest: "Önce eski" _chat: - home: "Ana sayfa" + noMessagesYet: "Henüz mesaj yok" + newMessage: "Yeni mesaj" + individualChat: "Özel Sohbet" + individualChat_description: "Başka bir kişiyle özel sohbet edin." + roomChat: "Sohbet Odası" + roomChat_description: "Birden fazla kişinin katılabileceği bir sohbet odası.\nÖzel sohbetlere izin vermeyen kişileri de davet edebilirsin, ancak davetini kabul etmeleri gerekir." + createRoom: "Oda Oluştur" + inviteUserToChat: "Kullanıcıları sohbete davet edin" + yourRooms: "Oluşturulan odalar" + joiningRooms: "Katıldığı odalar" + invitations: "Davet" + noInvitations: "Davet yok" + history: "Tarih" + noHistory: "Geçmiş bilgisi mevcut değil" + noRooms: "Oda bulunamadı" + inviteUser: "Kullanıcıları Davet Et" + sentInvitations: "Gönderilen Davetler" + join: "Katıl" + ignore: "Yoksay" + leave: "Odadan çık" + members: "Üyeler" + searchMessages: "Mesajları ara" + home: "Ana Sayfa" + send: "Gönder" + newline: "Yeni satır" + muteThisRoom: "Sessiz oda" + deleteRoom: "Odayı sil" + chatNotAvailableForThisAccountOrServer: "Bu sunucuda veya bu hesapta sohbet özelliği etkin değildir." + chatIsReadOnlyForThisAccountOrServer: "Bu sunucuda veya bu hesapta sohbet okunur modundadır. Yeni mesaj yazamaz veya sohbet odası oluşturamaz/katılamazsınız." + chatNotAvailableInOtherAccount: "Sohbet işlevi diğer kullanıcı için devre dışı bırakılmıştır." + cannotChatWithTheUser: "Bu kullanıcıyla sohbet başlatılamıyor." + cannotChatWithTheUser_description: "Sohbet kullanılamıyor veya karşı taraf sohbeti etkinleştirmedi." + youAreNotAMemberOfThisRoomButInvited: "Bu odanın katılımcısı değilsin, ancak bir davet aldın. Lütfen daveti kabul ederek katıl." + doYouAcceptInvitation: "Daveti kabul ediyor musunuz?" + chatWithThisUser: "Kullanıcıyla sohbet et" + thisUserAllowsChatOnlyFromFollowers: "Bu kullanıcı yalnızca takipçilerinden gelen sohbetleri kabul eder." + thisUserAllowsChatOnlyFromFollowing: "Bu kullanıcı, yalnızca takip ettiği kullanıcılardan gelen sohbetleri kabul eder." + thisUserAllowsChatOnlyFromMutualFollowing: "Bu kullanıcı, yalnızca karşılıklı takip eden kullanıcıların sohbetlerini kabul eder." + thisUserNotAllowedChatAnyone: "Bu kullanıcı kimseyle sohbet etmiyor." + chatAllowedUsers: "Sohbet etmesine izin verilecek kişiler" + chatAllowedUsers_note: "Bu ayardan bağımsız olarak, sohbet mesajı gönderdiğin herkesle sohbet edebilirsin." + _chatAllowedUsers: + everyone: "Herkes" + followers: "Sadece takipçilerin" + following: "Only users you are following" + mutual: "Sadece takiplerin" + none: "Kimse" +_emojiPalette: + palettes: "Palet" + enableSyncBetweenDevicesForPalettes: "Cihazlar arasında palet senkronizasyonunu etkinleştir" + paletteForMain: "Ana palet" + paletteForReaction: "Reaksiyon paleti" +_settings: + driveBanner: "Drive'ı yönetebilir ve yapılandırabilir, kullanımı kontrol edebilir ve dosya yükleme ayarlarını yapılandırabilirsin." + pluginBanner: "Eklentilerle istemci özelliklerini genişletebilirsin. Eklentileri yükleyebilir, ayrı ayrı yapılandırabilir ve yönetebilirsin." + notificationsBanner: "Sunucudan gelen bildirimlerin türlerini ve kapsamını ve push bildirimlerini yapılandırabilirsin." + api: "API" + webhook: "Webhook" + serviceConnection: "Hizmet entegrasyonu" + serviceConnectionBanner: "Dış uygulamalar veya hizmetlerle entegrasyon sağlamak için erişim belirteçlerini ve Webhook'ları yönetin ve yapılandırın." + accountData: "Hesap verileri" + accountDataBanner: "Hesap verilerini yönetmek için dışa ve içe aktarma." + muteAndBlockBanner: "İçeriği gizlemek ve belirli kullanıcıların eylemlerini kısıtlamak için ayarları yapılandırabilir ve yönetebilirsin." + accessibilityBanner: "İstemci, görünüm ve davranışları açısından en iyi şekilde kullanılmak üzere kişiselleştirilebilir ve ayarlanabilir." + privacyBanner: "Hesap gizliliği ile ilgili ayarları, örneğin içerik görünürlüğü, bulunabilirlik ve takip onayı gibi ayarları yapılandırabilirsin." + securityBanner: "Şifre, oturum açma yöntemleri, kimlik doğrulama uygulamaları ve Passkeys gibi hesap güvenliği ile ilgili ayarları yapılandırabilirsin." + preferencesBanner: "İstediğin şekilde istemcinin genel davranışını yapılandırabilirsin." + appearanceBanner: "İstemcinin görünüm ve ekran ayarlarını tercihlerini göre yapılandırabilirsin." + soundsBanner: "İstemcide oynatma için ses ayarlarını yapılandırabilirsin." + timelineAndNote: "Pano ve not" + makeEveryTextElementsSelectable: "Tüm metin öğelerini seçilebilir hale getir" + makeEveryTextElementsSelectable_description: "Bunu etkinleştirmek bazı durumlarda kullanılabilirliği azaltabilir." + useStickyIcons: "Kaydırma sırasında simgeleri takip et" + enableHighQualityImagePlaceholders: "Yüksek kaliteli görüntüler için yer tutucuları göster" + uiAnimations: "UI Animasyonları" + showNavbarSubButtons: "Navigasyon çubuğunda alt düğmeleri göster" + ifOn: "Açıkken" + ifOff: "Kapalıyken" + enableSyncThemesBetweenDevices: "Yüklü temaları cihazlar arasında senkronize edin" + enablePullToRefresh: "Yenilemek için çekin" + enablePullToRefresh_description: "Fareyi kullanırken, kaydırma tekerleğini basılı tutarken sürükle." + realtimeMode_description: "Sunucu ile bağlantı kurar ve içeriği gerçek zamanlı olarak günceller. Bu, trafik ve bellek tüketimini artırabilir." + contentsUpdateFrequency: "İçerik erişim sıklığı" + contentsUpdateFrequency_description: "Değer ne kadar yüksek olursa içerik o kadar sık güncellenir, ancak bu durum performansı düşürür ve trafik ile bellek tüketimini artırır." + contentsUpdateFrequency_description2: "Gerçek zamanlı mod açık olduğunda, bu ayardan bağımsız olarak içerik gerçek zamanlı olarak güncellenir." + showUrlPreview: "URL önizlemesi" + showAvailableReactionsFirstInNote: "Mevcut tepkileri en üstte göster." + showPageTabBarBottom: "Sayfa sekme çubuğunu aşağıda göster" + _chat: + showSenderName: "Gönderenin adını göster" + sendOnEnter: "Enter tuşuna basarak gönderin" +_preferencesProfile: + profileName: "Profil adı" + profileNameDescription: "Bu cihazı tanımlayan bir ad belirle." + profileNameDescription2: "Örnek: “Ana bilgisayar”, “Akıllı telefon”" + manageProfiles: "Profilleri Yönet" +_preferencesBackup: + autoBackup: "Otomatik yedekleme" + restoreFromBackup: "Yedeklemeden geri yükle" + noBackupsFoundTitle: "Yedekleme bulunamadı" + noBackupsFoundDescription: "Otomatik olarak oluşturulan yedekleme bulunamadı, ancak manuel olarak bir yedekleme dosyası kaydettiysen, bunu içe aktarabilir ve geri yükleyebilirsin." + selectBackupToRestore: "Geri yüklemek için bir yedekleme seçin" + youNeedToNameYourProfileToEnableAutoBackup: "Otomatik yedeklemeyi etkinleştirmek için bir profil adı ayarlanmalıdır." + autoPreferencesBackupIsNotEnabledForThisDevice: "Bu cihazda ayarların otomatik yedeklemesi etkinleştirilmemiş." + backupFound: "Ayarların yedeği bulundu" +_accountSettings: + requireSigninToViewContents: "İçeriği görüntülemek için oturum açmanız gerekir." + requireSigninToViewContentsDescription1: "Oluşturduğun tüm notları ve diğer içeriği görüntülemek için oturum açman gerekir. Bu, tarayıcıların bilgilerini toplamasına engel olacaktır." + requireSigninToViewContentsDescription2: "İçerik, URL önizlemelerinde (OGP), web sayfalarına gömülü olarak veya not alıntıları desteklemeyen sunucularda görüntülenmeyecek." + requireSigninToViewContentsDescription3: "Bu kısıtlamalar, diğer uzak sunuculardan gelen birleştirilmiş içerik için geçerli olmayabilir." + makeNotesFollowersOnlyBefore: "Geçmiş notların yalnızca takipçilere gösterilmesini sağlayın" + makeNotesFollowersOnlyBeforeDescription: "Bu özellik etkinleştirildiğinde, yalnızca takipçiler belirlenen tarih ve saatten sonra veya belirlenen süre boyunca görünür olan notları görebilir. Bu özellik devre dışı bırakıldığında, notun yayın durumu da geri yüklenir." + makeNotesHiddenBefore: "Geçmiş notları gizli yap" + makeNotesHiddenBeforeDescription: "Bu özellik etkinleştirildiğinde, belirlenen tarih ve saatten geçmiş olan veya yalnızca sizin görebildiğiniz notlar. Bu özellik devre dışı bırakıldığında, notun yayın durumu da geri yüklenecek." + mayNotEffectForFederatedNotes: "Uzak sunucuya bağlı notlar etkilenmeyebilir." + mayNotEffectSomeSituations: "Bu kısıtlamalar basitleştirilmiştir. Uzaktaki bir sunucuda görüntüleme veya moderasyon sırasında gibi bazı durumlarda geçerli olmayabilir." + notesHavePassedSpecifiedPeriod: "Belirtilen sürenin geçtiğini unutmayın." + notesOlderThanSpecifiedDateAndTime: "Belirtilen tarih ve saatten önceki notlar" +_abuseUserReport: + forward: "İleri" + forwardDescription: "Raporu, anonim bir sistem hesabı olarak uzak bir sunucuya iletin." + resolve: "Çözüm" + accept: "Kabul et" + reject: "Reddet" + resolveTutorial: "Raporun içeriği meşruysa, “Kabul Et” seçeneğini seçerek sorunu çözülmüş olarak işaretle.\nRaporun içeriği meşru değilse, “Reddet” seçeneğini seçerek raporu yok say." _delivery: - stop: "Askıya alınmış" + status: "Teslimat durumu" + stop: "Askıya al" + resume: "Teslimat özgeçmişi" _type: none: "Paylaşım" + manuallySuspended: "Manuel olarak askıya alınmış" + goneSuspended: "Sunucu, sunucunun silinmesi nedeniyle askıya alınmıştır." + autoSuspendedForNotResponding: "Sunucu yanıt vermediği için askıya alınmıştır." + softwareSuspended: "Bu yazılım artık dağıtılmadığı için askıya alınmıştır." +_bubbleGame: + howToPlay: "Nasıl oynanır" + hold: "Tut" + _score: + score: "Skor" + scoreYen: "Kazanılan para miktarı" + highScore: "Yüksek puan" + maxChain: "Maksimum zincir sayısı" + yen: "{yen} Yen" + estimatedQty: "{qty} Adet" + scoreSweets: "{onigiriQtyWithUnit} Onigiri" + _howToPlay: + section1: "Konumu ayarlayın ve nesneyi kutuya bırakın." + section2: "Aynı türden iki nesne birbirine dokunduğunda, farklı bir nesneye dönüşür ve puan kazanırsınız." + section3: "Kutu dolduğunda oyun biter. Kutuyu doldurmadan nesneleri birleştirerek yüksek puan almaya çalış!" +_announcement: + forExistingUsers: "Sadece mevcut kullanıcılar" + forExistingUsersDescription: "Bu duyuru, etkinleştirildiğinde yalnızca yayınlandığı anda mevcut olan kullanıcılara gösterilecek. Devre dışı bırakıldığında, yayınlandıktan sonra yeni kaydolan kullanıcılar da bu duyuruyu görecek." + needConfirmationToRead: "Ayrı okuma onayı gerektirir" + needConfirmationToReadDescription: "Etkinleştirildiğinde, bu duyuruyu okundu olarak işaretlemek için ayrı bir onay mesajı görüntülenir. Bu duyuru, “Tümünü okundu olarak işaretle” işlevinden de hariç tutulur." + end: "Arşiv duyurusu" + tooManyActiveAnnouncementDescription: "Çok fazla aktif duyuru olması kullanıcı deneyimini kötüleştirebilir. Artık geçerliliğini yitirmiş duyuruları arşivlemeyi düşün." + readConfirmTitle: "Okundu olarak işaretle?" + readConfirmText: "Bu, “{title}” içeriğini okundu olarak işaretleyecek." + shouldNotBeUsedToPresentPermanentInfo: "Duyuruları, uzun vadede geçerli olacak bilgiler için değil, güncel ve zaman sınırlı bilgileri yayınlamak için kullanmak en iyisidir." + dialogAnnouncementUxWarn: "Aynı anda iki veya daha fazla diyalog tarzı bildirim olması, kullanıcı deneyimini önemli ölçüde etkileyebilir, bu nedenle lütfen bunları dikkatli kullanın." + silence: "Bildirim yok" + silenceDescription: "Bu seçeneği etkinleştirdiğinde, bu duyurunun bildirimi atlanacak ve kullanıcı bunu okumak zorunda kalmayacak." +_initialAccountSetting: + accountCreated: "Hesabınız başarıyla oluşturuldu!" + letsStartAccountSetup: "Şimdi hesabını oluşturalım." + letsFillYourProfile: "Önce profilini oluşturalım." + profileSetting: "Profil ayarları" + privacySetting: "Gizlilik ayarları" + theseSettingsCanEditLater: "Bu ayarları daha sonra istediğin zaman değiştirebilirsin." + youCanEditMoreSettingsInSettingsPageLater: "“Ayarlar” sayfasından yapılandırabileceğin daha birçok ayar bulunmaktadır. Daha sonra mutlaka ziyaret et." + followUsers: "İlgilendiğiniz bazı kullanıcıları takip ederek zaman akışını oluşturmaya çalış." + pushNotificationDescription: "Push bildirimlerini etkinleştirdiğinde, {name} adresinden gelen bildirimleri doğrudan cihazınıza alabilirsin." + initialAccountSettingCompleted: "Profil kurulumu tamamlandı!" + haveFun: "{name} ile iyi eğlenceler!" + youCanContinueTutorial: "{name} (Misskey) öğreticisine geçebilir veya buradan kurulumu sonlandırıp hemen kullanabilirsin." + startTutorial: "Öğreticiye başla" + skipAreYouSure: "Profil kurulumunu cidden atlamak mı istiyorsun?" + laterAreYouSure: "Profil ayarlarını cidden daha sonra mı yapacaksın?" +_initialTutorial: + launchTutorial: "Öğreticiyi izle" + title: "Öğretici" + wellDone: "Tebrikler!" + skipAreYouSure: "Öğreticiyi kapatmak mı istiyorsunuz?" + _landing: + title: "Öğreticiye hoş geldin" + description: "Burada, Misskey'i kullanmanın temellerini ve özelliklerini öğrenebilirsin." + _note: + title: "Not nedir?" + description: "Misskey'deki gönderiler “Notlar” olarak adlandırılır. Notlar panoda kronolojik olarak düzenlenir ve gerçek zamanlı olarak güncellenir." + reply: "Bir mesaja yanıt vermek için bu düğmeye tıklayın. Yanıtlara yanıt vermek de mümkündür, böylece konuşma bir konu başlığı gibi devam eder." + renote: "Bu notu kendi panonda paylaşabilirsin. Ayrıca yorumlarınla birlikte alıntı da yapabilirsin." + reaction: "Not'a tepkiler ekleyebilirsin. Daha fazla ayrıntı bir sonraki sayfada açıklanacak." + menu: "Not ayrıntılarını görüntüleyebilir, bağlantıları kopyalayabilir ve çeşitli diğer işlemleri gerçekleştirebilirsin." + _reaction: + title: "Reaksiyonlar nedir?" + description: "Notlara çeşitli emojilerle tepki verilebilir. Tepkiler, sadece bir ‘beğeni’ ile ifade edilemeyen nüansları ifade etmeni sağlar." + letsTryReacting: "Notun üzerindeki ‘+’ düğmesine tıklayarak tepkiler eklenebilir. Bu örnek nota tepki verin!" + reactToContinue: "Devam etmek için bir tepki ekle." + reactNotification: "Biri notunuza tepki verdiğinde gerçek zamanlı bildirimler alacaksınız." + reactDone: "“-” düğmesine basarak bir tepkiyi geri alabilirsin." + _timeline: + title: "Pano Kavramı" + description1: "Misskey, kullanıma göre birden fazla Pano sunar (Bazı Pano'lar sunucunun politikalarına bağlı olarak kullanılamayabilir)." + home: "Takip ettiğin hesapların notlarını görüntüleyebilirsin." + local: "Bu sunucudaki tüm kullanıcıların notlarını görüntüleyebilirsin." + social: "Ev ve Yerel Pano'dan notlar görüntülenecek." + global: "Bağlı tüm sunuculardan gelen notları görüntüleyebilirsin." + description2: "Ekranın üst kısmındaki Pano'lar arasında istediğin zaman geçiş yapabilirsin." + description3: "Ayrıca, Liste Pano ve Kanal Pano da bulunmaktadır. Daha fazla ayrıntı için lütfen {link} adresine bakın." + _postNote: + title: "Not Yayınlama Ayarları" + description1: "Misskey'de not yayınlarken çeşitli seçenekler mevcuttur. Yayınlama formu şu şekildedir." + _visibility: + description: "Notunu kimlerin görüntüleyebileceğini sınırlayabilirsin." + public: "Notunuz tüm kullanıcılar tarafından görülebilir olacaktır." + home: "Yalnızca Ana zaman akışında herkese açık. Profilinizi ziyaret edenler, takipçilerin ve yeniden notlar aracılığıyla bunu görebilirler." + followers: "Sadece takipçiler tarafından görülebilir. Sadece takipçiler görebilir, başkaları göremez ve başkaları tarafından yeniden not edilemez." + direct: "Yalnızca belirli kullanıcılar tarafından görülebilir ve alıcıya bildirim gönderilir. Doğrudan mesajlaşma yerine alternatif olarak kullanılabilir." + doNotSendConfidencialOnDirect1: "Hassas bilgileri gönderirken dikkatli olun!" + doNotSendConfidencialOnDirect2: "Sunucu yöneticileri yazdıklarınızı görebilir. Güvenilir olmayan sunuculardaki kullanıcılara doğrudan not gönderirken hassas bilgilere dikkat edin." + localOnly: "Bu bayrakla yayınlamak, notu diğer sunuculara aktarmaz. Diğer sunuculardaki kullanıcılar, yukarıdaki görüntüleme ayarlarından bağımsız olarak bu notları doğrudan görüntüleyemezler." + _cw: + title: "İçerik Uyarısı" + description: "Gövde yerine, “Yorumlar” alanına yazılan içerik görüntülenecek. “Devamını oku” düğmesine basıldığında gövde görüntülenecek." + _exampleNote: + cw: "Bu kesinlikle sizi acıktıracak!" + note: "Az önce çikolata kaplı bir donut yedim 🍩😋" + useCases: "Bu, sunucu kurallarına uyulurken, gerekli notlar için veya spoiler veya hassas metinlerin kendi kendine kısıtlanması için kullanılır." + _howToMakeAttachmentsSensitive: + title: "Ekleri Hassas Olarak İşaretleme" + description: "Sunucu kuralları gereği gerekli olan veya bozulmaması gereken ekler için “hassas” bayrağı ekle." + tryThisFile: "Bu forma ekli resmi hassas olarak işaretlemeyi dene!" + _exampleNote: + note: "Oops, natto kapağını açarken berbat ettim..." + method: "Bir eki hassas olarak işaretlemek için, dosya küçük resmini tıklayın, menüyü açın ve “Hassas Olarak İşaretle” seçeneğini tıklayın." + sensitiveSucceeded: "Dosya eklerken, lütfen sunucu kurallarına uygun olarak hassasiyet ayarlarını yapın." + doItToContinue: "Devam etmek için ek dosyayı hassas olarak işaretle." + _done: + title: "Eğitimi tamamladınız! 🎉" + description: "Burada tanıtılan işlevler sadece küçük bir kısmıdır. Misskey'i kullanma konusunda daha ayrıntılı bilgi için lütfen şu kaynağa bakın: {link}." +_timelineDescription: + home: "Ana Pano'da, takip ettiğin hesapların notlarını görebilirsin." + local: "Yerel Pano'da, bu sunucudaki tüm kullanıcıların notlarını görebilirsin." + social: "Pano, Sosyal Pano ve Yerel Pano'dan gelen notları görüntüler." + global: "Global Pano'da, bağlı tüm sunuculardan gelen notları görebilirsin." +_serverRules: + description: "Kayıt öncesinde gösterilecek bir dizi kural. Hizmet Şartlarının özetini belirlemen önerilir." +_serverSettings: + iconUrl: "Simge URL'si" + appIconDescription: " {host} bir uygulama olarak görüntülendiğinde kullanılacak simgeyi belirtir." + appIconUsageExample: "Örneğin, PWA olarak veya bir telefonda ana ekran yer imi olarak görüntülendiğinde" + appIconStyleRecommendation: "Simge kare veya daire şeklinde kırpılabileceğinden, içeriğin etrafında renkli kenar boşluğu bulunan bir simge kullanılması önerilir." + appIconResolutionMustBe: "Minimum çözünürlük {resolution}'tür." + manifestJsonOverride: "manifest.json Geçersiz Kılma" + shortName: "Kısa ad" + shortNameDescription: "Resmi adın uzun olması durumunda görüntülenebilen, örneğin adının kısaltması." + fanoutTimelineDescription: "Etkinleştirildiğinde Pano alma performansını büyük ölçüde artırır ve veritabanı yükünü azaltır. Bunun karşılığında Redis'in bellek kullanımı artacaktır. Sunucu belleği düşükse veya sunucu kararsızsa bunu devre dışı bırakmayı düşün." + fanoutTimelineDbFallback: "Veritabanına geri dön" + fanoutTimelineDbFallbackDescription: "Etkinleştirildiğinde, Pano önbelleğe alınmamışsa ek sorgular için veritabanına geri döner. Bu özelliği devre dışı bırakmak, geri dönüş sürecini ortadan kaldırarak sunucu yükünü daha da azaltır, ancak alınabilecek panoların aralığını sınırlar." + reactionsBufferingDescription: "Etkinleştirildiğinde, reaksiyon oluşturma sırasında performans büyük ölçüde artacak ve veritabanı üzerindeki yük azalacaktır. Ancak, Redis bellek kullanımı artacakt." + remoteNotesCleaning: "Uzak notların otomatik olarak temizlenmesi" + remoteNotesCleaning_description: "Etkinleştirildiğinde, kullanılmayan ve güncelliğini yitirmiş uzak notlar, veritabanının şişmesini önlemek için periyodik olarak temizlenecek." + remoteNotesCleaningMaxProcessingDuration: "Maksimum temizleme işlem süresi" + remoteNotesCleaningExpiryDaysForEachNotes: "Notları saklamak için minimum gün sayısı" + inquiryUrl: "Sorgu URL'si" + inquiryUrlDescription: "Sorgu formu için sunucu yöneticisine bir URL veya iletişim bilgileri için bir web sayfası belirtin." + openRegistration: "Hesap oluşturmayı açık hale getir" + openRegistrationWarning: "Kayıt açma işlemi riskler içerir. Sunucuyu sürekli olarak izleyen ve herhangi bir sorun durumunda hemen müdahale edebilen bir sistemin varsa, bu işlemi etkinleştirmen önerilir." + thisSettingWillAutomaticallyOffWhenModeratorsInactive: "Bir süre boyunca moderatör etkinliği algılanmazsa, spam'ı önlemek için bu ayar otomatik olarak kapatılır." + deliverSuspendedSoftware: "Askıya Alınan Yazılım" + deliverSuspendedSoftwareDescription: "Güvenlik açığı veya diğer nedenlerle sunucunun yazılımının belirli bir isim ve sürüm aralığı için teslimatı durdurabilirsiniz. Bu sürüm bilgileri sunucu tarafından sağlanır ve güvenilirliği garanti edilmez. Sürümü belirtmek için semver aralığı belirtilebilir, ancak >= 2024.3.1 belirtildiğinde 2024.3.1-custom.0 gibi özel sürümler dahil edilmez, bu nedenle >= 2024.3.1-0 gibi ön sürüm belirtimi kullanılması önerilir." + singleUserMode: "Tek kullanıcı modu" + singleUserMode_description: "Bu sunucunun tek kullanıcısıysanız, bu modu etkinleştirerek performansını optimize edebilirsin." + signToActivityPubGet: "ActivityPub GET isteklerini imzalayın" + signToActivityPubGet_description: "Normalde bu özellik etkinleştirilmiş olmalıdır. Bu özelliği devre dışı bırakmak federasyonla ilgili sorunları iyileştirebilir, ancak diğer yandan bazı diğer sunuculara yönelik federasyonu devre dışı bırakabilir." + proxyRemoteFiles: "Proxy uzak dosyalar" + proxyRemoteFiles_description: "Etkinleştirildiğinde, sunucu uzak dosyaları proxy olarak kullanır ve sunar. Bu, resim küçük resimleri oluşturmak ve kullanıcı gizliliğini korumak için kullanışlıdır." + allowExternalApRedirect: "ActivityPub aracılığıyla yapılan sorgular için yönlendirmelere izin ver" + allowExternalApRedirect_description: "Etkinleştirildiğinde, diğer sunucular bu sunucu aracılığıyla üçüncü taraf içeriğini sorgulayabilir, ancak bu durum içerik sahteciliğine yol açabilir." + userGeneratedContentsVisibilityForVisitor: "Kullanıcılar tarafından oluşturulan içeriğin misafirlere görünürlüğü" + userGeneratedContentsVisibilityForVisitor_description: "Bu, uygunsuz ve iyi denetlenmemiş uzaktaki içeriğin kendi sunucunuz aracılığıyla istemeden internette yayınlanmasını önlemek için yararlıdır." + userGeneratedContentsVisibilityForVisitor_description2: "Sunucu tarafından alınan uzak içerik dahil olmak üzere sunucudaki tüm içeriği koşulsuz olarak İnternet'e yayınlamak risklidir. Bu, içeriğin dağıtılmış yapısından haberdar olmayan misafirler için özellikle önemlidir, çünkü onlar yanlışlıkla uzak içeriğin bile sunucudaki kullanıcılar tarafından oluşturulan içerik olduğunu düşünebilirler." + restartServerSetupWizardConfirm_title: "Sunucu kurulum sihirbazını yeniden başlatmak ister misin?" + restartServerSetupWizardConfirm_text: "Bazı mevcut ayarlar sıfırlanacaktır." + entrancePageStyle: "Giriş sayfası stili" + showTimelineForVisitor: "Panoyu göster" + showActivitiesForVisitor: "Aktiviteleri göster" + _userGeneratedContentsVisibilityForVisitor: + all: "Her şey halka açıktır." + localOnly: "Yalnızca yerel içerik yayınlanır, uzak içerik gizli tutulur." + none: "Her şey gizlidir." +_accountMigration: + moveFrom: "Başka bir hesabı bu hesaba taşıyın" + moveFromSub: "Başka bir hesaba takma ad oluşturun" + moveFromLabel: "Orijinal Hesap #{n}" + moveFromDescription: "Bu hesaptan taşınacak hesap için bir takma ad oluşturmalısınız.\nTaşınacak hesabı aşağıdaki biçimde girin: @username@server.example.com\nTakma adı silmek için alanı boş bırakın (önerilmez)." + moveTo: "Bu hesabı başka bir hesaba taşıyın" + moveToLabel: "Taşınacak hesap:" + moveCannotBeUndone: "Hesap taşıma işlemi geri alınamaz." + moveAccountDescription: "Bu işlem, hesabını farklı bir hesaba taşıyacaktır.\n・Bu hesabın takipçileri otomatik olarak yeni hesaba taşınacak.\n・Bu hesap, şu anda takip ettiği tüm kullanıcıları takipten çıkaracak.\n・Bu hesapta yeni notlar vb. oluşturamayacaksın.\n\nTakipçilerin taşınması otomatik olarak gerçekleşirken, takip ettiğin kullanıcıların listesini taşımak için bazı adımları manuel olarak hazırlaman gerekir. Bunu yapmak için, ayarlar menüsünden takipçilerini dışa aktar ve daha sonra yeni hesaba içe aktar. Aynı prosedür, listelerinin yanı sıra sessize aldığın ve engellediğin kullanıcılar için de geçerli.\n\n(Bu açıklama Misskey v13.12.0 ve sonraki sürümler için geçerlidir. Mastodon gibi diğer ActivityPub yazılımları farklı şekilde çalışabilir.)" + moveAccountHowTo: "Geçiş yapmak için, önce taşınacak hesapta bu hesap için bir takma ad oluşturun.\nTakma adı oluşturduktan sonra, taşınacak hesabı aşağıdaki biçimde girin: @username@server.example.com" + startMigration: "Taşın" + migrationConfirm: "Bu hesabı {account} hesabına gerçekten taşımak istiyor musun? Bu işlem başlatıldıktan sonra durdurulamaz veya geri alınamaz ve bu hesabı artık orijinal haliyle kullanamazsın." + movedAndCannotBeUndone: "\nBu hesap taşınmıştır.\nTaşıma işlemi geri alınamaz." + postMigrationNote: "Bu hesap, geçiş işlemi tamamlandıktan 24 saat sonra şu anda takip ettiği tüm hesapları takipten çıkaracak.\nHem takipçi sayısı hem de takip edilenler sayısı sıfır olacak. Takipçilerinin bu hesabın yalnızca takipçilere açık gönderilerini görememesi durumunu önlemek için, takipçilerin bu hesabı takip etmeye devam edecek." + movedTo: "Yeni hesap:" +_achievements: + earnedAt: "Şurada açıldı" + _types: + _notes1: + title: "msky'ımı kuruyorum" + description: "İlk notunuzu yayınlayın" + flavor: "Misskey ile iyi vakit geçirin!" + _notes10: + title: "Bazı notlar" + description: "10 not gönder" + _notes100: + title: "Çok sayıda not" + description: "100 notu yayınla" + _notes500: + title: "Notlarla kaplı" + description: "500 notu yayınla" + _notes1000: + title: "Notlardan oluşan bir dağ" + description: "1.000 not yayınla" + _notes5000: + title: "Taşan notlar" + description: "5.000 not yayınla" + _notes10000: + title: "Süper not" + description: "10.000 not yayınla" + _notes20000: + title: "Daha... fazla... not... lazım..." + description: "20.000 not yayınla" + _notes30000: + title: "Notlar notlar notlar!" + description: "30.000 not yayınla" + _notes40000: + title: "Not fabrikası" + description: "40.000 not yayınla" + _notes50000: + title: "Notların gezegeni" + description: "50.000 not yayınla" + _notes60000: + title: "Not kuasar" + description: "60.000 not yayınla" + _notes70000: + title: "Not kara deliği" + description: "70.000 not yayınla" + _notes80000: + title: "Not galaksisi" + description: "80.000 not yayınla" + _notes90000: + title: "Not evreni" + description: "90.000 not yayınla" + _notes100000: + title: "TÜM NOTLARINIZ BİZE AİTTİR" + description: "100.000 yayınlanmış not" + flavor: "Gerçekten söyleyecek çok şeyin var." + _login3: + title: "Başlangıç I" + description: "Log in for a total of 3 days" + flavor: "Toplam 3 gün boyunca oturum açın" + _login7: + title: "Başlangıç II" + description: "Toplam 7 gün boyunca oturum açın" + flavor: "Henüz işlerin nasıl yürüdüğünü anladığını hissediyor musun?" + _login15: + title: "Başlangıç III" + description: "Toplam 15 gün boyunca oturum açın" + _login30: + title: "Misskist I" + description: "Toplam 30 gün boyunca oturum açın" + _login60: + title: "Misskist II" + description: "Toplam 60 gün boyunca oturum açın" + _login100: + title: "Misskist III" + description: "Toplam 100 gün boyunca oturum açın" + flavor: "Şiddetli Misskist" + _login200: + title: "Düzenli I" + description: "Toplam 200 gün boyunca oturum açın." + _login300: + title: "Düzenli II" + description: "Toplam 300 gün boyunca oturum açın" + _login400: + title: "Düzenli III" + description: "Toplam 400 gün boyunca oturum açın" + _login500: + title: "Uzman I" + description: "Toplam 500 gün boyunca oturum açın" + flavor: "Arkadaşlar, sık sık not almayı sevdiğim söylenir." + _login600: + title: "Uzman II" + description: "Toplam 600 gün boyunca oturum açın" + _login700: + title: "Uzman III" + description: "Toplam 700 gün boyunca oturum açın" + _login800: + title: "Notların Ustası I" + description: "Toplam 800 gün boyunca oturum açın" + _login900: + title: "Notların Ustası II" + description: "Toplam 900 gün boyunca oturum açın" + _login1000: + title: "Notların Ustası III" + description: "Toplam 1.000 gün boyunca oturum açın." + flavor: "Misskey'i kullandığınız için teşekkür ederiz!" + _noteClipped1: + title: "Kesinlikle... kesmeliyim..." + description: "İlk notunu ekle" + _noteFavorited1: + title: "Yıldız gözlemcisi" + description: "İlk notunu favorilerine ekle" + _myNoteFavorited1: + title: "Yıldızları Arayış" + description: "Başka birinin notlarınızdan birini favorilerine eklemesini sağlayın" + _profileFilled: + title: "İyi hazırlanmış" + description: "Profilini oluştur" + _markedAsCat: + title: "Ben bir kediyim." + description: "Hesabını kedi olarak işaretle" + flavor: "Sana daha sonra bir isim vereceğim." + _following1: + title: "İlk kullanıcınızı takip edin" + description: "Bir kullanıcıyı takip et" + _following10: + title: "Devam et... devam et..." + description: "10 kullanıcıyı takip et" + _following50: + title: "Bir sürü arkadaş" + description: "50 hesabı takip et" + _following100: + title: "100 Arkadaş" + description: "100 hesabı takip et" + _following300: + title: "Arkadaş yüklemesi" + description: "300 hesabı takip et" + _followers1: + title: "İlk takipçi" + description: "1 takipçi kazanın" + _followers10: + title: "Beni takip edin!" + description: "10 takipçi kazanın" + _followers50: + title: "Kalabalıklar halinde gelmek" + description: "50 takipçi kazanın" + _followers100: + title: "Popüler" + description: "100 takipçi kazanın" + _followers300: + title: "Lütfen tek sıra halinde dizilin." + description: "300 takipçi kazanın" + _followers500: + title: "Radyo Kulesi" + description: "500 takipçi kazanın" + _followers1000: + title: "Etkileyici" + description: "1.000 takipçi kazanın" + _collectAchievements30: + title: "Başarı Koleksiyoncusu" + description: "30 başarı kazan" + _viewAchievements3min: + title: "Beğeniler Başarılar" + description: "Likes Achievements" + _iLoveMisskey: + title: "Misskey'i seviyorum" + description: "“I ❤ #Misskey” yazısını paylaş" + flavor: "Misskey geliştirme ekibi desteğin için çok teşekkür eder!" + _foundTreasure: + title: "Hazine Avı" + description: "Gizli hazineyi buldunuz." + _client30min: + title: "Kısa mola" + description: "Misskey'i en az 30 dakika açık tutun." + _client60min: + title: "Misskey'de “Miss” yok" + description: "Misskey'i en az 60 dakika açık tutun." + _noteDeletedWithin1min: + title: "Nevermind" + description: "Boş ver" + _postedAtLateNight: + title: "Gececi" + description: "Gece geç saatlerde bir not yayınlayın" + flavor: "Yatma vakti geldi." + _postedAt0min0sec: + title: "Konuşan Saat" + description: "00:00'da bir not yayınlayın." + flavor: "Tık tık tık, güm!" + _selfQuote: + title: "Öz Referans" + description: "Kendi notunuzu alıntı yapın" + _htl20npm: + title: "Akış Panosu" + description: "Ev zaman çizelgenizin hızı 20 npm'yi (dakika başına not sayısı) aşıyor mu?" + _viewInstanceChart: + title: "Analist" + description: "Sunucunun grafiklerini görüntüle" + _outputHelloWorldOnScratchpad: + title: "Merhaba, dünya!" + description: "Scratchpad'de “hello world” yazdırın." + _open3windows: + title: "Çoklu Pencere" + description: "Aynı anda en az 3 pencere açık olsun." + _driveFolderCircularReference: + title: "Döngüsel Referans" + description: "Drive'da yinelemeli olarak iç içe geçmiş bir klasör oluşturmaya çalış." + _reactWithoutRead: + title: "Cidden okudun mu?" + description: "100 karakterden uzun bir notun yayınlanmasından itibaren 3 saniye içinde yanıt verin." + _clickedClickHere: + title: "Buraya tıklayın" + description: "Buraya tıkladınız" + _justPlainLucky: + title: "Sadece Şanslı" + description: "Her 10 saniyede bir %0,005 olasılıkla elde edilme şansı vardır." + _setNameToSyuilo: + title: "Tanrı Kompleksi" + description: "Adınızı “syuilo” olarak ayarlayın." + _passedSinceAccountCreated1: + title: "Birinci Yıl Dönümü" + description: "Hesabınızın oluşturulmasından bu yana bir yıl geçti." + _passedSinceAccountCreated2: + title: "İki Yıllık Yıldönümü" + description: "Hesabınızın oluşturulmasından bu yana iki yıl geçti." + _passedSinceAccountCreated3: + title: "Üçüncü Yıl Dönümü" + description: "Hesabınızın oluşturulmasından bu yana üç yıl geçti." + _loggedInOnBirthday: + title: "Doğum günün kutlu olsun" + description: "Doğum gününüzde giriş yapın" + _loggedInOnNewYearsDay: + title: "Yeni yılınız kutlu olsun!" + description: "Yılın ilk gününde oturum açıldı" + flavor: "Bu sunucuda bir başka harika yıla" + _cookieClicked: + title: "Çerezleri tıklayarak oynanan bir oyun" + description: "Çerezi tıkladı" + flavor: "Wait, are you on the correct website?" + _brainDiver: + title: "Brain Diver" + description: "Brain Diver bağlantısını paylaşın" + flavor: "Misskey-Misskey La-Tu-Ma" + _smashTestNotificationButton: + title: "Test taşması" + description: "Bildirim testini çok kısa bir süre içinde tekrar tekrar tetikle." + _tutorialCompleted: + title: "Misskey Temel Kurs Diploması" + description: "Eğitim tamamlandı" + _bubbleGameExplodingHead: + title: "🤯" + description: "Kabarcık oyunundaki en büyük nesne" + _bubbleGameDoubleExplodingHead: + title: "Çift🤯" + description: "Aynı anda balon oyunundaki en büyük iki nesne" + flavor: "Öğle yemeği kutunu şöyle doldurabilirsin 🤯 🤯 biraz." +_role: + new: "Yeni rol" + edit: "Rolü düzenle" + name: "Rol adı" + description: "Rol tanımı" + permission: "Rol izinleri" + descriptionOfPermission: "Moderators temel moderasyon işlemlerini gerçekleştirebilir.\nAdministrators örneğin tüm ayarlarını değiştirebilir." + assignTarget: "Görev türü" + descriptionOfAssignTarget: "Bu rolün parçası olan ve olmayan kişileri manuel olarak değiştirmek için manuel.\nKullanıcıların bir koşula bağlı olarak bu role otomatik olarak atanmasını ve bu rolden çıkarılmasını sağlamak için koşullu." + manual: "Kılavuz" + manualRoles: "Manuel roller" + conditional: "Koşullu" + conditionalRoles: "Koşullu roller" + condition: "Durum" + isConditionalRole: "Bu, koşullu bir roldür." + isPublic: "Kamu rolü" + descriptionOfIsPublic: "Bu rol, atanan kullanıcıların profillerinde görüntülenecek." + options: "Seçenekler" + policies: "Politikalar" + baseRole: "Rol şablonu" + useBaseValue: "Rol şablonu değerini kullan" + chooseRoleToAssign: "Atamak istediğin rolü seç" + iconUrl: "Simge URL'si" + asBadge: "Rozet olarak göster" + descriptionOfAsBadge: "This role's icon will be displayed next to the username of users with this role if turned on." + isExplorable: "Rolü keşfedilebilir hale getir" + descriptionOfIsExplorable: "Bu rolün panosu ve bu role sahip kullanıcıların listesi, etkinleştirilirse kamuya açık hale getirilecek." + displayOrder: "Pozisyon" + descriptionOfDisplayOrder: "Sayı ne kadar yüksekse, UI pozisyonu da o kadar yüksek olur." + preserveAssignmentOnMoveAccount: "Geçiş sırasında rol atamalarını koruyun" + preserveAssignmentOnMoveAccount_description: "Etkinleştirildiğinde, bu rol, bu role sahip bir hesap taşındığında hedef hesaba aktarılacak." + canEditMembersByModerator: "Moderatörlerin bu rol için üye listesini düzenlemesine izin ver" + descriptionOfCanEditMembersByModerator: "Etkinleştirildiğinde, moderatörler ve yöneticiler bu role kullanıcıları atayabilir ve atamalarını kaldırabilir. Devre dışı bırakıldığında, yalnızca yöneticiler kullanıcıları atayabilir." + priority: "Öncelik" + _priority: + low: "Düşük" + middle: "Orta" + high: "Yüksek" + _options: + gtlAvailable: "Global Pano'yu görüntüleyebilir" + ltlAvailable: "Yerel panoyu görüntüleyebilir" + canPublicNote: "Halka açık notlar gönderebilir" + mentionMax: "Bir notta maksimum bahsetme sayısı" + canInvite: "Sunucu davet kodları oluşturabilir" + inviteLimit: "Davet sınırı" + inviteLimitCycle: "Davet sınırı bekleme süresi" + inviteExpirationTime: "Davet süresi dolma aralığı" + canManageCustomEmojis: "Özel emojileri yönetebilir" + canManageAvatarDecorations: "Avatar süslerini yönet" + driveCapacity: "Drive kapasitesi" + maxFileSize: "Yükleyebileceğin maksimum dosya boyutu" + alwaysMarkNsfw: "Dosyaları her zaman NSFW olarak işaretle" + canUpdateBioMedia: "Bir simge veya banner görüntüsünü düzenleyebilir" + pinMax: "Sabitlenmiş notların maksimum sayısı" + antennaMax: "Maksimum anten sayısı" + wordMuteMax: "Kelime sessizlerinde izin verilen maksimum karakter sayısı" + webhookMax: "Maksimum Webhook sayısı" + clipMax: "Maksimum klip sayısı" + noteEachClipsMax: "Bir klip içindeki maksimum not sayısı" + userListMax: "Maksimum kullanıcı listesi sayısı" + userEachUserListsMax: "Kullanıcı listesindeki maksimum kullanıcı sayısı" + rateLimitFactor: "Hız Sınırı" + descriptionOfRateLimitFactor: "Daha düşük oran sınırları daha az kısıtlayıcıdır, daha yüksek olanlar ise daha kısıtlayıcıdır." + canHideAds: "Reklamları gizleyebilir" + canSearchNotes: "Not arama kullanımı" + canSearchUsers: "Kullanıcı arama" + canUseTranslator: "Çevirmen kullanımı" + avatarDecorationLimit: "Maksimum avatar süsü sayısı" + canImportAntennas: "Antenlerin içe aktarılmasına izin ver" + canImportBlocking: "Engellemeyi içe aktarmaya izin ver" + canImportFollowing: "Aşağıdakilerin içe aktarılmasına izin ver" + canImportMuting: "Sessize alma özelliğini içe aktarmaya izin ver" + canImportUserLists: "Listelerin içe aktarılmasına izin ver" + chatAvailability: "Sohbeti İzin Ver" + uploadableFileTypes: "Yüklenebilir dosya türleri" + uploadableFileTypes_caption: "İzin verilen MIME/dosya türlerini belirtir. Birden fazla MIME türü, yeni bir satırla ayırarak belirtilebilir ve joker karakterler yıldız işareti (*) ile belirtilebilir. (örneğin, image/*)" + uploadableFileTypes_caption2: "Bazı dosya türleri algılanamayabilir. Bu tür dosyalara izin vermek için, spesifikasyona {x} ekle." + noteDraftLimit: "Sunucu notlarının olası taslak sayısı" + watermarkAvailable: "Filigran işlevinin kullanılabilirliği" + _condition: + roleAssignedTo: "Manuel rollere atanmış" + isLocal: "Yerel kullanıcı" + isRemote: "Uzak kullanıcı" + isCat: "Kedi Kullanıcıları" + isBot: "Bot Kullanıcıları" + isSuspended: "Askıya alınmış kullanıcı" + isLocked: "Özel hesaplar" + isExplorable: "“Hesabı bulunabilir hale getir” özelliğini etkili bir şekilde kullanan kullanıcı" + createdLessThan: "Hesap oluşturulduktan sonra X'ten az zaman geçti." + createdMoreThan: "Hesap oluşturulmasından bu yana X'ten fazla zaman geçti." + followersLessThanOrEq: "X veya daha az takipçisi var" + followersMoreThanOrEq: "X veya daha fazla takipçisi var" + followingLessThanOrEq: "X veya daha az sayıda hesabı takip ediyor" + followingMoreThanOrEq: "X veya daha fazla hesabı takip ediyor" + notesLessThanOrEq: "Gönderi sayısı şundan az/eşit" + notesMoreThanOrEq: "Gönderi sayısı şundan büyük/eşit" + and: "Koşul-AND" + or: "Koşul-QR" + not: "Koşul-NOT" +_sensitiveMediaDetection: + description: "Makine öğrenimi yoluyla hassas medyayı otomatik olarak tanıyarak sunucu moderasyonunun yükünü azaltır. Bu, sunucu üzerindeki yükü biraz artıracaktır." + sensitivity: "Algılama hassasiyeti" + sensitivityDescription: "Hassasiyeti azaltmak, yanlış algılamaların (yanlış pozitifler) azalmasına neden olurken, hassasiyeti artırmak ise algılamaların kaçırılmasının (yanlış negatifler) azalmasına neden olur." + setSensitiveFlagAutomatically: "Hassas olarak işaretle" + setSensitiveFlagAutomaticallyDescription: "Bu seçenek kapatılsa bile, dahili algılama sonuçları korunacaktır." + analyzeVideos: "Videoların analizini etkinleştir" + analyzeVideosDescription: "Görüntülerin yanı sıra videoları da analiz eder. Bu, sunucu üzerindeki yükü biraz artıracaktır." +_emailUnavailable: + used: "Bu E-Posta adresi zaten kullanılıyor." + format: "Bu E-Posta adresinin biçimi geçersizdir." + disposable: "Tek kullanımlık E-Posta adresleri kullanılamaz." + mx: "Bu E-Posta sunucusu geçersizdir." + smtp: "Bu E-Posta sunucusu yanıt vermiyor." + banned: "Bu E-Posta adresiyle kayıt olamazsınız." +_ffVisibility: + public: "Bu e-posta adresiyle kayıt olamazsınız." + followers: "Sadece takipçiler tarafından görülebilir" + private: "Özel" +_signup: + almostThere: "Neredeyse vardık" + emailAddressInfo: "Lütfen E-Posta adresini gir. Bu adres kamuya açık hale getirilmeyecek." + emailSent: "Onay e-postası E-Posta adresine ({email}) gönderilmiştir. Hesap oluşturma işlemini tamamlamak için e-postadaki bağlantıya tıkla." _accountDelete: - started: "Silme işlemi başlatıldı" + accountDelete: "Hesabı sil" + mayTakeTime: "Hesap silme işlemi kaynak yoğun bir işlem olduğundan, oluşturduğun içerik miktarına ve yüklediğin dosya sayısına bağlı olarak tamamlanması biraz zaman alabilir." + sendEmail: "Hesap silme işlemi tamamlandıktan sonra, bu hesaba kayıtlı E-Posta adresine bir e-posta gönderilecek." + requestAccountDelete: "Hesap silme talebi" + started: "Silme işlemi başlatıldı." + inProgress: "Silme işlemi şu anda devam ediyor." +_ad: + back: "Geri" + reduceFrequencyOfThisAd: "Bu reklamı daha az göster" + hide: "Gizle" + timezoneinfo: "Haftanın günü, sunucunun saat diliminden belirlenir." + adsSettings: "Reklam ayarları" + notesPerOneAd: "Gerçek zamanlı güncelleme reklam yerleşim aralığı (Reklam başına notlar)" + setZeroToDisable: "Bu değeri 0 olarak ayarlayarak gerçek zamanlı güncelleme reklamlarını devre dışı bırakın." + adsTooClose: "Mevcut reklam aralığı çok düşük olduğu için kullanıcı deneyimini önemli ölçüde kötüleştirebilir." +_forgotPassword: + enterEmail: "Kayıt olurken kullandığın E-Posta adresini gir. Şifreni sıfırlayabileceğin bir bağlantı bu adrese gönderilecek." + ifNoEmail: "Kayıt sırasında E-Posta kullanmadıysanız, lütfen bunun yerine sunucu yöneticisiyle iletişime geçin." + contactAdmin: "This instance does not support using email addresses, please contact the instance administrator to reset your password instead." +_gallery: + my: "Benim Galerim" + liked: "Beğenilen Gönderiler" + like: "Beğen" + unlike: "Benzerlerini kaldır" _email: _follow: - title: "seni takip etti" + title: "Yeni bir takipçin var." + _receiveFollowRequest: + title: "Bir takip isteği aldınız." +_plugin: + install: "Eklentileri yükle takip isteği aldınız" + installWarn: "Güvenilir olmayan eklentileri yükleme." + manage: "Eklentileri yönet" + viewSource: "Kaynak görüntüle" + viewLog: "Günlüğü göster" +_preferencesBackups: + list: "Created backups" + saveNew: "Yeni yedeklemeyi kaydet" + loadFile: "Dosyadan yükle" + apply: "Bu cihaza başvur" + save: "Değişiklikleri kaydet" + inputName: "Lütfen bu yedekleme için bir ad girin." + cannotSave: "Kaydetme başarısız oldu" + nameAlreadyExists: "“{name}” adlı bir yedekleme zaten mevcut. Lütfen farklı bir ad girin." + applyConfirm: "Bu cihaza “{name}” yedeklemesini cidden uygulamak istiyor musun? Bu cihazın mevcut ayarları üzerine yazılacaktır." + saveConfirm: "Yedeklemeyi {name} olarak kaydedin?" + deleteConfirm: "{name} yedeklemesini silmek ister misin?" + renameConfirm: "Bu yedeğin adını “{old}” den “{new}” ye değiştirmek ister misin?" + noBackups: "Yedekleme mevcut değil. “Yeni yedekleme oluştur” seçeneğini kullanarak bu sunucudaki istemci ayarlarınızı yedekleyebilirsin." + createdAt: "Oluşturulma tarihi: {date} {time}" + updatedAt: "Güncelleme tarihi: {date} {time}" + cannotLoad: "Yükleme başarısız" + invalidFile: "Geçersiz dosya biçimi" +_registry: + scope: "Kapsam" + key: "Anahtar" + keys: "Anahtarlar" + domain: "Alan adı" + createKey: "Anahtar oluştur" +_aboutMisskey: + about: "Misskey, 2014 yılından beri syuilo tarafından geliştirilen açık kaynaklı bir yazılımdır." + contributors: "Başlıca katkıda bulunanlar" + allContributors: "Tüm katkıda bulunanlar" + source: "Kaynak kodu" + original: "Orijinal" + thisIsModifiedVersion: "{name} orijinal Misskey'in değiştirilmiş bir sürümünü kullanır." + translation: "Misskey'i çevir" + donate: "Misskey'e bağış yapın" + morePatrons: "Burada adı geçmeyen diğer birçok yardımseverin desteğine de teşekkür ederiz. Teşekkürler! 🥰" + patrons: "Müşteriler" + projectMembers: "Proje üyeleri" +_displayOfSensitiveMedia: + respect: "Hassas olarak işaretlenmiş medyayı gizle" + ignore: "Hassas olarak işaretlenmiş medya görüntüleme" + force: "Hide all media" +_instanceTicker: + none: "Asla gösterme" + remote: "Uzak kullanıcılar için göster" + always: "Her zaman göster" +_serverDisconnectedBehavior: + reload: "Otomatik olarak yeniden yükle" + dialog: "Otomatik olarak yeniden yükle" + quiet: "Göze batmayan uyarı göster" +_channel: + create: "Kanal oluştur" + edit: "Kanalı düzenle" + setBanner: "Afiş ayarla" + removeBanner: "Afişi kaldır" + featured: "Trend olan" + owned: "Sahip olunan" + following: "Takip edildi" + usersCount: "{n} Katılımcılar" + notesCount: "{n} Notlar" + nameAndDescription: "Adı ve açıklaması" + nameOnly: "Sadece isim" + allowRenoteToExternal: "Kanal dışında yeniden not alma ve alıntı yapmaya izin ver" +_menuDisplay: + sideFull: "Yan" + sideIcon: "Yan (Simgeler)" + top: "En üst" + hide: "Gizle" +_wordMute: + muteWords: "Sessiz kelimeler" + muteWordsDescription: "AND koşulu için boşluklarla, OR koşulu için satır sonlarıyla ayırın." + muteWordsDescription2: "Surround keywords with slashes to use regular expressions." +_instanceMute: + instanceMuteDescription: "Bu, listelenen sunuculardan gelen tüm notları/yeniden notları sessize alır, sessize alınan bir sunucudan bir kullanıcıya yanıt veren kullanıcıların notları da dahil olmak üzere." + instanceMuteDescription2: "Yeni satırlarla ayırın" + title: "Listelenen sunuculardan notları gizler." + heading: "Sessize alınacak sunucuların listesi" _theme: + explore: "Temaları Keşfedin" + install: "Bir tema yükle" + manage: "Temaları yönet" + code: "Tema kodu" + copyThemeCode: "Tema kodunu kopyala" + description: "Açıklama" + installed: "{name} kuruldu" + installedThemes: "Yüklü temalar" + builtinThemes: "Yerleşik temalar" + instanceTheme: "Sunucu teması" + alreadyInstalled: "Bu tema zaten yüklenmiş." + invalid: "Bu temanın biçimi geçersizdir." + make: "Bir tema oluşturun" + base: "Base" + addConstant: "Sabit ekle" + constant: "Sabit" + defaultValue: "Varsayılan değer" color: "Renk" + refProp: "Bir özelliği referans al" + refConst: "Sabiti referans al" + key: "Anahtar" + func: "İşlevler" + funcKind: "İşlev türü" + argument: "Tartışma" + basedProp: "Referans verilen mülk" + alpha: "Opaklık" + darken: "Koyulaştır" + lighten: "Hafiflet" + inputConstantName: "Bu sabit için bir ad girin" + importInfo: "Buraya tema kodunu girersen, onu tema düzenleyicisine aktarabilirsin." + deleteConstantConfirm: "{const} sabitini cidden silmek istiyor musun?" keys: - mention: "Bahset" - renote: "vazgeçme" + accent: "Aksan" + bg: "Arka plan" + fg: "Metin" + focus: "Odak" + indicator: "Gösterge" + panel: "Panel" + shadow: "Gölge" + header: "Başlık" + navBg: "Kenar çubuğu arka planı" + navFg: "Kenar çubuğu metni" + navActive: "Kenar çubuğu metni (Etkin)" + navIndicator: "Kenar çubuğu göstergesi" + link: "Link" + hashtag: "Hashtag" + mention: "Bahsetmeler" + mentionMe: "Bahsetmeler (Ben)" + renote: "Renote" + modalBg: "Modal arka plan" + divider: "Bölücü" + scrollbarHandle: "Kaydırma çubuğu" + scrollbarHandleHover: "Kaydırma çubuğu (Fareyi üzerine getir)" + dateLabelFg: "Tarih etiketi metni" + infoBg: "Bilgi arka planı" + infoFg: "Bilgi metni" + infoWarnBg: "Uyarı arka planı" + infoWarnFg: "Uyarı metni" + toastBg: "Bildirim arka planı" + toastFg: "Bildirim metni" + buttonBg: "Düğme arka planı" + buttonHoverBg: "Button background (Hover)" + inputBorder: "Giriş alanı kenarlığı" + badge: "Rozet" + messageBg: "Sohbet arka planı" + fgHighlighted: "Vurgulanan Metin" _sfx: - note: "notlar" - notification: "Bildirim" + note: "Yeni not" + noteMy: "Kendi notu" + notification: "Bildirimler" + reaction: "Reaksiyon seçimi hakkında" + chatMessage: "Sohbet Mesajları" +_soundSettings: + driveFile: "Drive'da bir ses dosyası kullanın." + driveFileWarn: "Drive'dan bir ses dosyası seçin." + driveFileTypeWarn: "Bu dosya desteklenmiyor" + driveFileTypeWarnDescription: "Bir ses dosyası seçin" + driveFileDurationWarn: "Ses kaydı çok uzun." + driveFileDurationWarnDescription: "Uzun sesli mesajlar Misskey'in kullanımını engelleyebilir. Devam etmek istiyor musunuz?" + driveFileError: "Ses yüklenemedi. Lütfen ayarları değiştir." +_ago: + future: "Gelecek" + justNow: "Şimdi" + secondsAgo: "{n} sn" + minutesAgo: "{n} dk" + hoursAgo: "{n} sa" + daysAgo: "{n} gün" + weeksAgo: "{n} hafta" + monthsAgo: "{n} ay" + yearsAgo: "{n} yıl" + invalid: "Geçersiz" +_timeIn: + seconds: "{n} saniye içinde" + minutes: "{n} dakika içinde" + hours: "{n} saat içinde" + days: "{n} gün içinde" + weeks: "{n} hafta içinde" + months: "{n} ay içinde" + years: "{n} yıl içinde" +_time: + second: "Saniye(ler)" + minute: "Dakika(lar)" + hour: "Saat(ler)" + day: "Gün(ler)" + month: "Ay" _2fa: - renewTOTPCancel: "Hayır, teşekkürler" + alreadyRegistered: "2fa kimlik doğrulama cihazını zaten kaydettin." + registerTOTP: "Kimlik doğrulama uygulamasını kaydet" + step1: "Öncelikle, cihazınıza bir kimlik doğrulama uygulaması (örneğin {a} veya {b}) yükleyin." + step2: "Ardından, bu ekranda görüntülenen QR kodunu tarayın." + step2Uri: "Masaüstü programı kullanıyorsanız aşağıdaki URI'yi girin" + step3Title: "Doğrulama kodunu girin" + step3: "Uygulamanız tarafından sağlanan kimlik doğrulama kodunu (token) girerek kurulumu tamamlayın." + setupCompleted: "Kurulum tamamlandı" + step4: "Bundan sonra, gelecekteki tüm oturum açma girişimlerinde bu tür bir oturum açma jetonu istenecek." + securityKeyNotSupported: "Tarayıcınız güvenlik anahtarlarını desteklemiyor." + registerTOTPBeforeKey: "Güvenlik veya geçiş anahtarını kaydetmek için bir kimlik doğrulama uygulaması kurun." + securityKeyInfo: "Parmak izi veya PIN kimlik doğrulamasının yanı sıra, hesabını daha da güvenli hale getirmek için FIDO2'yi destekleyen donanım güvenlik anahtarları aracılığıyla kimlik doğrulama da ayarlayabilirsin." + registerSecurityKey: "Güvenlik veya geçiş anahtarını kaydedin" + securityKeyName: "Bir anahtar adı girin" + tapSecurityKey: "Güvenlik veya geçiş anahtarını kaydetmek için lütfen tarayıcınızı takip edin." + removeKey: "Güvenlik anahtarını kaldır" + removeKeyConfirm: "{name} anahtarını cidden silmek istiyor musun?" + whyTOTPOnlyRenew: "Güvenlik anahtarı kayıtlı olduğu sürece kimlik doğrulama uygulaması kaldırılamaz." + renewTOTP: "Kimlik doğrulama uygulamasını yeniden yapılandırın" + renewTOTPConfirm: "Bu, önceki uygulamanızdaki doğrulama kodlarının çalışmamasına neden olacaktır." + renewTOTPOk: "Yeniden yapılandır" + renewTOTPCancel: "İptal" + checkBackupCodesBeforeCloseThisWizard: "Bu pencereyi kapatmadan önce, lütfen aşağıdaki yedek kodları not edin." + backupCodes: "Yedek kodlar" + backupCodesDescription: "İki faktörlü kimlik doğrulama uygulamasını kullanamaz hale gelmen durumunda, bu kodları kullanarak hesabınıza erişebilirsin. Her kod yalnızca bir kez kullanılabilir. Lütfen bu kodları güvenli bir yerde sakla." + backupCodeUsedWarning: "Yedek kod kullanıldı. Artık kullanamıyorsanız, lütfen iki faktörlü kimlik doğrulamayı mümkün olan en kısa sürede yeniden yapılandırın." + backupCodesExhaustedWarning: "Tüm yedek kodlar kullanıldı. İki faktörlü kimlik doğrulama uygulamana erişimini kaybedersen, bu hesaba erişemezsin. Lütfen iki faktörlü kimlik doğrulamayı yeniden yapılandır." + moreDetailedGuideHere: "İşte ayrıntılı kılavuz" _permissions: - "read:blocks": "Engellenen hesapları gör" - "write:blocks": "Engellenen hesap listesini düzenle" + "read:account": "Hesap bilgilerini gör" + "write:account": "Hesap bilgilerini düzenle" + "read:blocks": "Engellenen kullanıcıların listesini görüntüle" + "write:blocks": "Engellenen kullanıcılar listeni düzenle" + "read:drive": "Drive dosyalarına ve klasörlerine eriş" + "write:drive": "Drive dosyalarını ve klasörlerini düzenle veya sil" + "read:favorites": "Favoriler listeni görüntüle" + "write:favorites": "Favoriler listeni düzenle" + "read:following": "Takip ettiğin kişilerle ilgili bilgileri görüntüle" + "write:following": "Diğer hesapları takip et veya takipten çıkar" + "read:messaging": "Sohbetlerini görüntüle" + "write:messaging": "Sohbet mesajlarını oluşturun veya silin" + "read:mutes": "Sessize alınan kullanıcıların listesini görüntüle" + "write:mutes": "Sessize alınan kullanıcıların listesini düzenle" + "write:notes": "Notlar oluşturun veya silin" + "read:notifications": "Bildirimlerini görüntüle" + "write:notifications": "Bildirimlerini yönet" + "read:reactions": "Tepkilerini görüntüle" + "write:reactions": "Tepkilerini düzenle" + "write:votes": "Ankete oy verin" + "read:pages": "Sayfalarını görüntüle" + "write:pages": "Sayfalarını düzenle veya sil" + "read:page-likes": "Beğenilen sayfaların listesini görüntüle" + "write:page-likes": "Beğenilen sayfaların listesini düzenle" + "read:user-groups": "Kullanıcı gruplarını görüntüle" + "write:user-groups": "Kullanıcı gruplarını düzenle veya sil" + "read:channels": "Kanallarını görüntüle" + "write:channels": "Kanallarını düzenle" + "read:gallery": "Galeriyi görüntüle" + "write:gallery": "Galeri düzenle" + "read:gallery-likes": "Beğendiğin galeri gönderilerinin listesini görüntüle" + "write:gallery-likes": "Beğendiğin galeri gönderilerinin listesini düzenle" + "read:flash": "Oynat" + "write:flash": "Oyunları Düzenle" + "read:flash-likes": "Beğenilen Oyunların listesini görüntüle" + "write:flash-likes": "Beğenilen Oyunlar listesini düzenle" + "read:admin:abuse-user-reports": "Kullanıcı raporlarını görüntüle" + "write:admin:delete-account": "Kullanıcı hesabını sil" + "write:admin:delete-all-files-of-a-user": "Bir kullanıcının tüm dosyalarını sil" + "read:admin:index-stats": "Veritabanı dizin istatistiklerini görüntüle" + "read:admin:table-stats": "Veritabanı tablosu istatistiklerini görüntüle" + "read:admin:user-ips": "Kullanıcı IP adreslerini görüntüle" + "read:admin:meta": "Sunucu meta verilerini görüntüle" + "write:admin:reset-password": "Kullanıcı şifresini sıfırla" + "write:admin:resolve-abuse-user-report": "Kullanıcı raporunu çözme" + "write:admin:send-email": "E-Posta gönder" + "read:admin:server-info": "Sunucu bilgilerini görüntüle" + "read:admin:show-moderation-log": "Moderasyon günlüğünü görüntüle" + "read:admin:show-user": "Özel kullanıcı bilgilerini görüntüle" + "write:admin:suspend-user": "Kullanıcıyı askıya al" + "write:admin:unset-user-avatar": "Kullanıcı avatarını kaldır" + "write:admin:unset-user-banner": "Kullanıcı afişini kaldır" + "write:admin:unsuspend-user": "Kullanıcı askıya alma işlemini kaldır" + "write:admin:meta": "Sunucu meta verilerini yönetme" + "write:admin:user-note": "Moderasyon notunu yönet" + "write:admin:roles": "Rolleri yönet" + "read:admin:roles": "Rolü görüntüle" + "write:admin:relays": "Röleleri yönetme" + "read:admin:relays": "Röleleri görüntüle" + "write:admin:invite-codes": "Davet kodlarını yönet" + "read:admin:invite-codes": "Davet kodlarını görüntüle" + "write:admin:announcements": "Duyuruları yönet" + "read:admin:announcements": "Duyuruları görüntüle" + "write:admin:avatar-decorations": "Avatar süslerini yönetebilir" + "read:admin:avatar-decorations": "Avatar süslerini görüntüle" + "write:admin:federation": "Federasyon verilerini yönetme" + "write:admin:account": "Kullanıcı hesabını yönet" + "read:admin:account": "Kullanıcı hesabını görüntüle" + "write:admin:emoji": "Emoji'leri yönet" + "read:admin:emoji": "Emojiyi görüntüle" + "write:admin:queue": "İş kuyruğunu yönet" + "read:admin:queue": "İş kuyruğu bilgilerini görüntüle" + "write:admin:promo": "Promosyon notlarını yönet" + "write:admin:drive": "Kullanıcı Drive'ını yönet" + "read:admin:drive": "Kullanıcı Drive bilgilerini görüntüle" + "read:admin:stream": "Yönetici için WebSocket API'sını kullanın" + "write:admin:ad": "Reklamları yönet" + "read:admin:ad": "Reklamları görüntüle" + "write:invite-codes": "Davet kodları oluşturun" + "read:invite-codes": "Davet kodlarını alın" + "write:clip-favorite": "Favorilere eklenen klipleri yönet" + "read:clip-favorite": "Favorilere eklenen klipleri görüntüle" + "read:federation": "Federasyon verilerini alın" + "write:report-abuse": "İhlali bildir" + "write:chat": "Sohbet mesajlarını oluşturun veya silin" + "read:chat": "Sohbeti Gözat" +_auth: + shareAccessTitle: "Uygulama izinlerinin verilmesi" + shareAccess: "“{name}”nin bu hesaba erişmesine izin vermek ister misiniz?" + shareAccessAsk: "Bu uygulamanın hesabınıza erişmesine izin vermek istediğinden emin misin?" + permission: "{name} aşağıdaki izinleri talep etmektedir." + permissionAsk: "Bu uygulama aşağıdaki izinleri talep etmektedir" + pleaseGoBack: "Lütfen uygulamaya geri dönün." + callback: "Uygulamaya geri dönmek" + accepted: "Erişim izni verildi" + denied: "Erişim reddedildi" + scopeUser: "Aşağıdaki kullanıcı olarak çalıştırın" + pleaseLogin: "Uygulamaları yetkilendirmek için lütfen giriş yapın." + byClickingYouWillBeRedirectedToThisUrl: "Erişim izni verildiğinde, otomatik olarak aşağıdaki URL'ye yönlendirileceksin." +_antennaSources: + all: "Tüm notlar" + homeTimeline: "Takip edilen kullanıcıların notları" + users: "Belirli kullanıcılardan gelen notlar" + userList: "Belirtilen kullanıcı listesinden notlar" + userBlacklist: "Bir veya daha fazla belirli kullanıcıya ait olanlar hariç tüm notlar" +_weekday: + sunday: "Pazar" + monday: "Pazartesi" + tuesday: "Salı" + wednesday: "Çarşamba" + thursday: "Perşembe" + friday: "Cuma" + saturday: "Cumartesi" _widgets: profile: "Profil" instanceInfo: "Sunucu Bilgisi" - notifications: "Bildirim" - timeline: "Zaman çizelgesi" + memo: "Yapışkan notlar" + notifications: "Bildirimler" + timeline: "Pano" calendar: "Takvim" + trends: "Trend olan" clock: "Saat" + rss: "RSS okuyucu" + rssTicker: "RSS-Ticker" activity: "Etkinlik" + photos: "Fotoğraflar" + digitalClock: "Dijital saat" + unixClock: "UNIX saati" federation: "Federasyon" - jobQueue: "İşlem sırası" + instanceCloud: "Bulut sunucu" + postForm: "Gönderim formu" + slideshow: "Slayt gösterisi" + button: "Düğme" + onlineUsers: "Çevrimiçi kullanıcılar" + jobQueue: "İş Kuyruğu" + serverMetric: "Sunucu ölçümleri" + aiscript: "AiScript konsolu" + aiscriptApp: "AiScript Uygulaması" + aichan: "Ai" + userList: "Kullanıcı listesi" _userList: - chooseList: "Bir liste seç" + chooseList: "Bir liste seçin" + clicker: "Tıklayıcı" + birthdayFollowings: "Bugünün Doğum Günleri" + chat: "Sohbet" _cw: - show: "Devamını yükle" + hide: "Gizle" + show: "İçeriği göster" + chars: "{count} karakter" + files: "{count} dosya(lar)" _poll: - vote: "Oy kullan" + noOnlyOneChoice: "En az iki seçenek gereklidir." + choiceN: "Seçim {n}" + noMore: "Daha fazla seçenek ekleyemezsin." + canMultipleVote: "Birden fazla seçenek seçilmesine izin ver" + expiration: "Anketi sonlandır" + infinite: "Asla" + at: "Şurada bitir..." + after: "Sonrasında bitir..." + deadlineDate: "Bitiş tarihi" + deadlineTime: "Zaman" + duration: "Süre" + votesCount: "{n} oy" + totalVotes: "Toplam {n} oy" + vote: "Oy ver" + showResult: "Sonuçları görüntüle" + voted: "Oylandı" + closed: "Sona erdi" + remainingDays: "{d} gün {h} saat kaldı" + remainingHours: "{h} saat {m} dakika kaldı" + remainingMinutes: "{m} dakika {s} saniye kaldı" + remainingSeconds: "{s} saniye kaldı" _visibility: - publicDescription: "Herkese açık" - home: "Ana sayfa" - followers: "takipçi" + public: "Halka açık" + publicDescription: "Notunuz tüm kullanıcılar tarafından görülebilir olacaktır." + home: "Pano" + homeDescription: "Yalnızca ana panoya gönder" + followers: "Takipçiler" + followersDescription: "Sadece takipçilerine görünür hale getir" + specified: "Doğrudan" + specifiedDescription: "Yalnızca belirli kullanıcılar için görünür hale getir" + disableFederation: "Federasyon olmadan" + disableFederationDescription: "Diğer sunuculara aktarma" +_postForm: + quitInspiteOfThereAreUnuploadedFilesConfirm: "Yüklenmemiş dosyalar var, bunları silip formu kapatmak ister misin?" + uploaderTip: "Dosya henüz yüklenmemiş. Dosya menüsünden dosyayı yeniden adlandırabilir, görüntüleri kırpabilir, filigran ekleyebilir ve dosyayı sıkıştırabilir veya sıkıştırmayı kaldırabilirsin. Notu yayınladığında dosyalar otomatik olarak yüklenir." + replyPlaceholder: "Bu notu yanıtla..." + quotePlaceholder: "Bu notu alıntı yap..." + channelPlaceholder: "Bir kanala gönder..." + _placeholders: + a: "Ne yapıyorsun?" + b: "Çevrende neler oluyor?" + c: "Aklında ne var?" + d: "Ne söylemek istiyorsun?" + e: "Yazmaya başlayın..." + f: "Yazmanızı bekliyoruz..." _profile: - username: "Kullanıcı Adı" + name: "Ad" + username: "Kullanıcı adı" + description: "Biyografi" + youCanIncludeHashtags: "Biyografinize hashtag'ler de ekleyebilirsiniz." + metadata: "Ek Bilgiler" + metadataEdit: "Ek bilgileri düzenle" + metadataDescription: "Bunları kullanarak profilinde ek bilgi alanları görüntüleyebilirsin." + metadataLabel: "Etiket" + metadataContent: "İçerik" + changeAvatar: "Avatar değiştir" + changeBanner: "Banner değiştir" + verifiedLinkDescription: "Buraya profiline bağlantı içeren bir URL girerek, alanın yanında bir sahiplik doğrulama simgesi görüntülenebilir." + avatarDecorationMax: "En fazla {max} süs ekleyebilirsin." + followedMessage: "Takip edildiğinizde gönderilen mesaj" + followedMessageDescription: "Abonelerin seni takip ettiklerinde görüntülenmesini istediğin kısa bir mesaj ayarlayabilirsin." + followedMessageDescriptionForLockedAccount: "Takip isteklerinin onay gerektirmesini ayarladıysan, bir takip isteğini kabul ettiğinde bu mesaj görüntülenir." _exportOrImport: - followingList: "takipçi" - muteList: "Gizle" - blockingList: "engelle" - userLists: "Listeler" + allNotes: "Tüm notlar" + favoritedNotes: "Favori notlar" + clips: "Klip" + followingList: "Takip edilen kullanıcılar" + muteList: "Sessize alınan kullanıcılar" + blockingList: "Engellenen kullanıcılar" + userLists: "Kullanıcı listeleri" + excludeMutingUsers: "Sessize alınan kullanıcıları hariç tut" + excludeInactiveUsers: "Etkin olmayan kullanıcıları hariç tut" + withReplies: "İçe aktarılan kullanıcıların yanıtlarını panoya dahil edin" _charts: federation: "Federasyon" + apRequest: "Talepler" + usersIncDec: "Kullanıcı sayısındaki fark" + usersTotal: "Toplam kullanıcı sayısı" + activeUsers: "Aktif kullanıcılar" + notesIncDec: "Not sayısındaki fark" + localNotesIncDec: "Yerel notaların sayısındaki fark" + remoteNotesIncDec: "Uzak notların sayısındaki fark" + notesTotal: "Toplam not sayısı" + filesIncDec: "Dosya sayısındaki fark" + filesTotal: "Toplam dosya sayısı" + storageUsageIncDec: "Depolama kullanımı farkı" + storageUsageTotal: "Total storage usage" +_instanceCharts: + requests: "Talepler" + users: "Kullanıcı sayısındaki fark" + usersTotal: "Toplam kullanıcı sayısı" + notes: "Not sayısındaki fark" + notesTotal: "Toplam not sayısı" + ff: "Takip / Takipçi sayısı farkı" + ffTotal: "Takip / Takipçi toplam sayısı" + cacheSize: "Önbellek boyutundaki fark" + cacheSizeTotal: "Önbelleğin toplam boyutu" + files: "Dosya sayısındaki fark" + filesTotal: "Toplam dosya sayısı" _timelines: - home: "Ana sayfa" - global: "Küresel" + home: "Pano" + local: "Yerel" + social: "Sosyal" + global: "Global" +_play: + new: "Oyun Oluştur" + edit: "Düzenle Oynat" + created: "Oyun oluşturuldu" + updated: "Düzenlenmiş oynat" + deleted: "Oyun silindi" + pageSetting: "Oyun ayarları" + editThisPage: "Bu Oyunu Düzenle" + viewSource: "Kaynak görüntüle" + my: "Oyunlarım" + liked: "Beğenilen Oyunlar" + featured: "Popüler" + title: "Başlık" + script: "Senaryo" + summary: "Açıklama" + visibilityDescription: "Özel olarak ayarlamak, profilinde görünmeyeceği anlamına gelir, ancak URL'ye sahip olan herkes yine de erişebilir." _pages: + newPage: "Yeni bir Sayfa oluşturun" + editPage: "Bu sayfayı düzenle" + readPage: "Bu Sayfanın Kaynağını Görüntüleme" + pageSetting: "Sayfa ayarları" + nameAlreadyExists: "Belirtilen Sayfa URL'si zaten mevcut." + invalidNameTitle: "Belirtilen Sayfa URL geçersiz" + invalidNameText: "Sayfa başlığının boş olmadığından emin olun." + editThisPage: "Bu sayfayı düzenle" + viewSource: "Kaynak görüntüle" + viewPage: "Sayfalarını görüntüle" + like: "Beğen" + unlike: "Benzerlerini kaldır" + my: "Benzerlerini kaldır" + liked: "Beğenilen Sayfalar" + featured: "Popüler" + inspector: "Müfettiş" + contents: "İçindekiler" + content: "Sayfa bloğu" + variables: "Değişkenler" + title: "Başlık" + url: "Sayfa URL'si" + summary: "Sayfa özeti" + alignCenter: "Merkez öğeleri" + hideTitleWhenPinned: "Profiline sabitlendiğinde sayfa başlığını gizle" + font: "Yazı tipi" + fontSerif: "Serif" + fontSansSerif: "Sans Serif" + eyeCatchingImageSet: "Küçük resmi ayarla" + eyeCatchingImageRemove: "Küçük resmi sil" + chooseBlock: "Blok ekle" + enterSectionTitle: "Bölüm başlığını girin" + selectType: "Bir tür seçin" + contentBlocks: "İçerik" + inputBlocks: "Giriş" + specialBlocks: "Özel" blocks: + text: "Metin" + textarea: "Metin alanı" + section: "Bölüm" image: "Görseller" + button: "Düğme" + dynamic: "Dinamik Bloklar" + dynamicDescription: "Bu blok kaldırılmıştır. Bundan sonra lütfen {play} kullanın." + note: "Gömülü not" + _note: + id: "Not Kimliği" + idDescription: "Alternatif olarak notun URL buraya yapıştırabilirsin." + detailed: "Ayrıntılı görünüm" +_relayStatus: + requesting: "Beklemede" + accepted: "Accepted" + rejected: "Reddedildi" _notification: + fileUploaded: "Dosya başarıyla yüklendi" + youGotMention: "{name} sizden bahsetti." + youGotReply: "{name} size yanıt verdi" + youGotQuote: "{name} sizden alıntı yaptı" + youRenoted: "{name}'den Renote" youWereFollowed: "seni takip etti" + youReceivedFollowRequest: "Bir takip isteği aldınız." + yourFollowRequestAccepted: "Takip isteğin kabul edildi." + pollEnded: "Anket sonuçları açıklandı." + newNote: "Yeni not" unreadAntennaNote: "{name} anteni" + roleAssigned: "Verilen rol" + chatRoomInvitationReceived: "Sohbet odasına davet edildin." + emptyPushNotificationMessage: "Push bildirimleri güncellendi" + achievementEarned: "Achievement unlocked" + testNotification: "Test bildirimi" + checkNotificationBehavior: "Bildirim görünümünü kontrol edin" + sendTestNotification: "Test bildirimi gönder" + notificationWillBeDisplayedLikeThis: "Bildirimler şöyle görünür" + reactedBySomeUsers: "{n} kullanıcı tepki gösterdi" + likedBySomeUsers: "{n} kullanıcı notunuzu beğendi." + renotedBySomeUsers: "{n} kullanıcıdan gelen hatırlatma" + followedBySomeUsers: "{n} kullanıcı tarafından takip ediliyor" + flushNotification: "Bildirimleri temizle" + exportOfXCompleted: "{x} ihracatı tamamlandı." + login: "Biri oturum açtı" + createToken: "Bir erişim jetonu oluşturuldu." + createTokenDescription: "Eğer bilmiyorsanız, “{text}” aracılığıyla erişim jetonunu silin." _types: - follow: "takipçi" - mention: "Bahset" - renote: "vazgeçme" - quote: "alıntı" - reaction: "Tepkiler" - receiveFollowRequest: "Takip isteği alındı" - followRequestAccepted: "Takip isteği kabul edildi" - login: "Giriş Yap " + all: "Tümü" + note: "Yeni notlar" + follow: "Yeni takipçiler" + mention: "Bahsetmeler" + reply: "Yanıtlar" + renote: "Renote" + quote: "Alıntılar" + reaction: "Tepki" + pollEnded: "Anketler sona eriyor" + receiveFollowRequest: "Takip istekleri alındı" + followRequestAccepted: "Kabul edilen takip istekleri" + roleAssigned: "Verilen rol" + chatRoomInvitationReceived: "Sohbet odasına davet edildi" + achievementEarned: "Başarı kilidi açıldı" + exportCompleted: "İhracat işlemi tamamlandı." + login: "Oturum Aç" + createToken: "Erişim jetonu oluştur" + test: "Bildirim testi" + app: "Bağlı uygulamalardan gelen bildirimler" _actions: - reply: "yanıt" - renote: "vazgeçme" + followBack: "seni takip ettim" + reply: "Yanıtla" + renote: "Renote" _deck: - configureColumn: "Sütun seçenekleri" + alwaysShowMainColumn: "Ana sütunu her zaman göster" + columnAlign: "Sütunları hizala" + columnGap: "Sütunlar arasındaki kenar boşluğu" + deckMenuPosition: "Sütunlar arasındaki kenar boşluğu" + navbarPosition: "Gezinti çubuğu konumu" + addColumn: "Sütun ekle" + newNoteNotificationSettings: "Notification setting for new notes" + configureColumn: "Sütun ayarları" + swapLeft: "Sol sütunla değiştir" + swapRight: "Sağ sütunla değiştir" + swapUp: "Yukarıdaki sütunla değiştir" + swapDown: "Aşağıdaki sütunla değiştir" + stackLeft: "Sol sütunda yığın" + popRight: "Sağdaki pop sütunu" + profile: "Profil" + newProfile: "Yeni profil" + deleteProfile: "Profili sil" + introduction: "Sütunları serbestçe düzenleyerek size en uygun arayüzü oluşturun!" + introduction2: "Ekranın sağındaki + işaretine tıklayarak istediğin zaman yeni sütunlar ekleyebilirsin." + widgetsIntroduction: "Lütfen sütun menüsünden “Widget'ları düzenle” seçeneğini seç ve bir widget ekle." + useSimpleUiForNonRootPages: "Gezinilen sayfalar için basit kullanıcı arayüzü kullanın" + usedAsMinWidthWhenFlexible: "“Otomatik genişlik ayarı” seçeneği etkinleştirildiğinde, bunun için minimum genişlik kullanılacak." + flexible: "Otomatik genişlik ayarı" + enableSyncBetweenDevicesForProfiles: "Cihazlar arasında profil bilgilerinin senkronizasyonunu etkinleştir" _columns: - notifications: "Bildirim" - tl: "Zaman çizelgesi" - list: "Listeler" + main: "Ana" + widgets: "Widget'lar" + notifications: "Bildirimler" + tl: "Pano" + antenna: "Antenler" + list: "Liste" + channel: "Kanal" mentions: "Bahsetmeler" + direct: "Doğrudan notlar" + roleTimeline: "Rol Pano" + chat: "Sohbet" +_dialog: + charactersExceeded: "Maksimum karakter sınırını aştınız! Şu anda {current} karakterde {max} karakterlik sınırın {current} karakterinde bulunuyorsunuz." + charactersBelow: "You're below the minimum character limit! Currently at {current} of {min}." +_disabledTimeline: + title: "Pano devre dışı bırakıldı" + description: "Mevcut rollerinle bu Pano kullanılamaz." +_drivecleaner: + orderBySizeDesc: "Azalan Dosya Boyutları" + orderByCreatedAtAsc: "Yükselen Tarihler" +_webhookSettings: + createWebhook: "Webhook oluştur" + modifyWebhook: "Webhook'u değiştir" + name: "Webhook'u değiştir" + secret: "Gizli" + trigger: "Tetikleyici" + active: "Etkin" + _events: + follow: "Bir kullanıcıyı takip ederken" + followed: "Takip edildiğinde" + note: "Not gönderirken" + reply: "Yanıt alındığında" + renote: "Yeniden not edildiğinde" + reaction: "Tepki aldığınızda" + mention: "Bahsedildiğinde" + _systemEvents: + abuseReport: "Yeni bir rapor alındığında" + abuseReportResolved: "Çözüldüğünde rapor" + userCreated: "Kullanıcı oluşturulduğunda" + inactiveModeratorsWarning: "Moderatörler bir süredir aktif olmadıklarında" + inactiveModeratorsInvitationOnlyChanged: "Bir moderatör bir süre aktif olmadığında ve sunucu davetle erişilebilir hale getirildiğinde" + deleteConfirm: "Webhook'u silmek istediğinden emin misin?" + testRemarks: "Anahtarın sağındaki düğmeyi tıklayarak sahte verilerle bir test Webhook gönderin." +_abuseReport: + _notificationRecipient: + createRecipient: "Raporlar için alıcı ekle" + modifyRecipient: "Raporlar için alıcıyı düzenle" + recipientType: "Bildirim türü" + _recipientType: + mail: "E-Posta" + webhook: "Webhook" + _captions: + mail: "Raporları aldığınızda, E-Postayı moderatörlerin e-posta adreslerine gönderin." + webhook: "Raporları aldığınızda veya çözdüğünüzde Sistem Webhook'una bir bildirim gönderin." + keywords: "Anahtar kelimeler" + notifiedUser: "Bildirilecek kullanıcılar" + notifiedWebhook: "Kullanılacak webhook" + deleteConfirm: "Bildirim alıcısını silmek istediğinden emin misin?" _moderationLogTypes: - suspend: "askıya al" - resetPassword: "Şifre sıfırlama" + createRole: "Rol oluşturuldu" + deleteRole: "Rol silindi" + updateRole: "Rol güncellendi" + assignRole: "Rol atandı" + unassignRole: "Görevinden alınmış" + suspend: "Askıya alınmış" + unsuspend: "Askıya alınmamış" + addCustomEmoji: "Özel emoji eklendi" + updateCustomEmoji: "Özel emoji güncellendi" + deleteCustomEmoji: "Özel emoji silindi" + updateServerSettings: "Sunucu ayarları güncellendi" + updateUserNote: "Moderasyon notu güncellendi" + deleteDriveFile: "Dosya silindi" + deleteNote: "Not silindi" + createGlobalAnnouncement: "Global duyuru oluşturuldu" + createUserAnnouncement: "Kullanıcı duyurusu oluşturuldu" + updateGlobalAnnouncement: "Global duyuru güncellendi" + updateUserAnnouncement: "Kullanıcı duyurusu güncellendi" + deleteGlobalAnnouncement: "Global duyuru silindi" + deleteUserAnnouncement: "Kullanıcı duyurusu silindi" + resetPassword: "Şifreyi sıfırla" + suspendRemoteInstance: "Uzak sunucu askıya alındı" + unsuspendRemoteInstance: "Uzak sunucu askıya alınmadı" + updateRemoteInstanceNote: "Uzak sunucular için güncellenmiş moderasyon notu" + markSensitiveDriveFile: "Hassas olarak işaretlenmiş dosya" + unmarkSensitiveDriveFile: "Dosya hassas olarak işaretlenmemiş" + resolveAbuseReport: "Rapor çözüldü" + forwardAbuseReport: "Rapor iletildi" + updateAbuseReportNote: "Güncellenen raporun moderasyon notu" + createInvitation: "Davet oluşturuldu" + createAd: "Reklam oluşturuldu" + deleteAd: "Reklam silindi" + updateAd: "Reklam güncellendi" + createAvatarDecoration: "Avatar dekorasyonu oluşturuldu" + updateAvatarDecoration: "Avatar dekorasyonu güncellendi" + deleteAvatarDecoration: "Avatar süsü silindi" + unsetUserAvatar: "Kullanıcı avatarı ayarlanmamış" + unsetUserBanner: "Kullanıcı başlığı ayarlanmamış" + createSystemWebhook: "Sistem Webhook oluşturuldu" + updateSystemWebhook: "Sistem Webhook güncellendi" + deleteSystemWebhook: "Sistem Webhook silindi" + createAbuseReportNotificationRecipient: "Oluşturulan raporlar için alıcı" + updateAbuseReportNotificationRecipient: "Raporlar için alıcı güncellendi" + deleteAbuseReportNotificationRecipient: "Silinen raporlar için alıcı" + deleteAccount: "Hesap silindi" + deletePage: "Sayfa silindi" + deleteFlash: "Oyun silindi" + deleteGalleryPost: "Galeri gönderisi silindi" + deleteChatRoom: "Deleted Chat Room" + updateProxyAccountDescription: "Proxy hesabının açıklamasını güncelle" +_fileViewer: + title: "Dosya ayrıntıları" + type: "Dosya türü" + size: "Dosya boyutu" + url: "URL" + uploadedAt: "Yüklendiği tarih" + attachedNotes: "Ekli notlar" + usage: "Kullanılmış" + thisPageCanBeSeenFromTheAuthor: "Bu sayfa, bu dosyayı yükleyen kullanıcı tarafından görülebilir." +_externalResourceInstaller: + title: "Harici siteden yükle" + checkVendorBeforeInstall: "Yüklemeden önce bu kaynağın dağıtımcısının güvenilir olduğundan emin olun." + _plugin: + title: "Bu eklentiyi yüklemek ister misin?" + _theme: + title: "Bu temayı yüklemek ister misin?" + _meta: + base: "Temel renk şeması" + _vendorInfo: + title: "Dağıtıcı bilgileri" + endpoint: "Referans uç nokta" + hashVerify: "Hash doğrulama" + _errors: + _invalidParams: + title: "Geçersiz parametreler" + description: "Harici bir siteden veri yüklemek için yeterli bilgi yok. Lütfen girdiğin URL'yi kontrol et." + _resourceTypeNotSupported: + title: "Bu harici kaynak desteklenmemektedir." + description: "Bu harici kaynağın türü desteklenmemektedir. Lütfen site yöneticisiyle iletişime geç." + _failedToFetch: + title: "Veriler alınamadı" + fetchErrorDescription: "Harici siteyle iletişim sırasında bir hata oluştu. Tekrar denemen sorunu çözmezse, lütfen site yöneticisine başvur." + parseErrorDescription: "Harici siteden yüklenen veriler işlenirken bir hata oluştu. Lütfen site yöneticisiyle iletişime geçin." + _hashUnmatched: + title: "Veri doğrulama başarısız oldu" + description: "Alınan verilerin bütünlüğünü doğrularken bir hata oluştu. Güvenlik önlemi olarak, kurulum devam edemez. Lütfen site yöneticisiyle iletişime geçin." + _pluginParseFailed: + title: "AiScript Hatası" + description: "İstenen veriler başarıyla alındı, ancak AiScript ayrıştırma sırasında bir hata oluştu. Lütfen eklenti yazarına başvurun. Hata ayrıntıları Javascript konsolunda görüntülenebilir." + _pluginInstallFailed: + title: "Eklenti kurulumu başarısız oldu" + description: "Eklenti yükleme sırasında bir sorun oluştu. Lütfen tekrar dene. Hata ayrıntıları Javascript konsolunda görüntülenebilir." + _themeParseFailed: + title: "Tema ayrıştırma başarısız oldu" + description: "İstenen veriler başarıyla alındı, ancak tema ayrıştırma sırasında bir hata oluştu. Lütfen tema yazarıyla iletişime geçin. Hata ayrıntıları Javascript konsolunda görüntülenebilir." + _themeInstallFailed: + title: "Tema yüklenemedi" + description: "Tema yükleme sırasında bir sorun oluştu. Lütfen tekrar dene. Hata ayrıntıları Javascript konsolunda görüntülenebilir." +_dataSaver: + _media: + title: "Medya yükleniyor" + description: "Görüntülerin/videoların otomatik olarak yüklenmesini engeller. Gizli görüntüler/videolar dokunulduğunda yüklenir." + _avatar: + title: "Avatar resmi" + description: "Avatar görüntüsünün animasyonunu durdurun. Animasyonlu görüntüler normal görüntülere göre dosya boyutu açısından daha büyük olabilir ve bu da veri trafiğinde daha fazla azalmaya yol açabilir." + _urlPreviewThumbnail: + title: "URL önizleme küçük resimlerini gizle" + description: "URL önizleme küçük resimleri artık yüklenmeyecek." + _disableUrlPreview: + title: "URL önizlemesini devre dışı bırak" + description: "URL önizleme işlevini devre dışı bırakır. Küçük resimler aksine, bu işlev bağlantılı bilgilerin kendisinin yüklenmesini azaltır." + _code: + title: "Kod vurgulama" + description: "MFM vb. programlarda kod vurgulama notasyonları kullanılıyorsa, bunlar dokunulana kadar yüklenmez. Sözdizimi vurgulama, her programlama dili için vurgu tanım dosyalarının indirilmesini gerektirir. Bu nedenle, bu dosyaların otomatik olarak yüklenmesinin devre dışı bırakılması, iletişim verisi miktarını azaltması beklenir." +_hemisphere: + N: "Kuzey Yarımküre" + S: "Güney Yarımküre" + caption: "Bazı istemci ayarlarında mevsimi belirlemek için kullanılır." +_reversi: + reversi: "Tersine çevirme" + gameSettings: "Oyun ayarları" + chooseBoard: "Bir tahta seçin" + blackOrWhite: "Siyah/Beyaz" + blackIs: "{name} siyah oynuyor." + rules: "Kurallar" + thisGameIsStartedSoon: "Oyun kısa süre içinde başlayacak." + waitingForOther: "Rakibin sırasını bekle" + waitingForMe: "Sıranı bekliyorsun" + waitingBoth: "Hazır olun" + ready: "Hazır" + cancelReady: "Hazır değil" + opponentTurn: "Rakibin sırası" + myTurn: "Sıra sende" + turnOf: "Sıra {name}'de." + pastTurnOf: "{name}'nin sırası" + surrender: "Teslimiyet" + surrendered: "Teslim oldu" + timeout: "Zaman doldu" + drawn: "Çiz" + won: "{name} kazandı" + black: "Siyah" + white: "Beyaz" + total: "Toplam" + turnCount: "{count} döndür" + myGames: "Benim turlarım" + allGames: "Tüm turlar" + ended: "Sona erdi" + playing: "Şu anda oynatılıyor" + isLlotheo: "Taş sayısı daha az olan kazanır (Llotheo)" + loopedMap: "Döngüsel harita" + canPutEverywhere: "Fayanslar her yere yerleştirilebilir." + timeLimitForEachTurn: "Sıra için zaman sınırı" + freeMatch: "Ücretsiz Eşleştirme" + lookingForPlayer: "Rakip aranıyor..." + gameCanceled: "Oyun iptal edildi." + shareToTlTheGameWhenStart: "Oyun başlatıldığında panoda paylaş" + iStartedAGame: "Oyun başladı! #MisskeyReversi" + opponentHasSettingsChanged: "Rakip ayarlarını değiştirmiş." + allowIrregularRules: "Düzensiz kurallar (tamamen ücretsiz)" + disallowIrregularRules: "Düzensiz kurallar yok" + showBoardLabels: "Tahtada satır ve sütun numaralarını göster" + useAvatarAsStone: "Taşları kullanıcı avatarlarına dönüştürün" +_offlineScreen: + title: "Çevrimdışı - sunucuya bağlanılamıyor" + header: "Sunucuya bağlanılamıyor" +_urlPreviewSetting: + title: "URL önizleme ayarları" + enable: "URL önizlemesini etkinleştir" + allowRedirect: "URL önizleme yönlendirmesine izin ver" + allowRedirectDescription: "Bir URL'de yönlendirme ayarlanmışsa, bu özelliği etkinleştirerek yönlendirmeyi takip edebilir ve yönlendirilen içeriğin önizlemesini görüntüleyebilirsin. Bu özelliği devre dışı bırakmak sunucu kaynaklarından tasarruf sağlar, ancak yönlendirilen içerik görüntülenmez." + timeout: "Önizleme alırken zaman aşımı (ms)" + timeoutDescription: "Önizlemeyi almak bu değerden daha uzun sürerse, önizleme oluşturulmaz." + maximumContentLength: "Maksimum İçerik Uzunluğu (bayt)" + maximumContentLengthDescription: "Content-Length bu değerden yüksekse, önizleme oluşturulmaz." + requireContentLength: "Yalnızca Content-Length değerini alabiliyorsanız önizlemeyi oluşturun." + requireContentLengthDescription: "Diğer sunucu Content-Length değerini döndürmezse, önizleme oluşturulmaz." + userAgent: "Kullanıcı Aracısı" + userAgentDescription: "Önizlemeleri alırken kullanılacak Kullanıcı Aracısını ayarlar. Boş bırakılırsa, varsayılan Kullanıcı Aracısı kullanılır." + summaryProxy: "Önizlemeler oluşturan proxy uç noktaları" + summaryProxyDescription: "Misskey'in kendisi değil, Summaly Proxy kullanarak önizlemeler oluştur." + summaryProxyDescription2: "Aşağıdaki parametreler, sorgu dizesi olarak proxy'ye bağlanır. Proxy bunları desteklemiyorsa, değerler yok sayılır." +_mediaControls: + pip: "Resim içinde resim" + playbackRate: "Oynatma Hızı" + loop: "Döngüsel oynatma" +_contextMenu: + title: "Bağlam menüsü" + app: "Uygulama" + appWithShift: "Shift tuşuyla uygulama" + native: "Doğal" +_gridComponent: + _error: + requiredValue: "Bu değer gereklidir." + columnTypeNotSupport: "Düzenli ifade ile doğrulama yalnızca type:text sütunları için desteklenir." + patternNotMatch: "Bu değer {pattern} içindeki desenle eşleşmiyor." + notUnique: "Bu değer benzersiz olmalıdır." +_roleSelectDialog: + notSelected: "Seçilmedi" +_customEmojisManager: + _gridCommon: + copySelectionRows: "Seçili satırları kopyala" + copySelectionRanges: "Seçimi kopyala" + deleteSelectionRows: "Seçili satırları sil" + deleteSelectionRanges: "Seçimdeki satırları sil" + searchSettings: "Arama ayarları" + searchSettingCaption: "Ayrıntılı arama kriterleri belirle." + searchLimit: "Sonuç sayısı" + sortOrder: "Sıralama düzeni" + registrationLogs: "Kayıt günlüğü" + registrationLogsCaption: "Emojileri güncellerken veya silerken günlükler görüntülenecek. Güncelleme veya silme işleminden sonra, yeni bir sayfaya geçildiğinde veya yeniden yüklendiğinde günlükler kaybolacak." + alertEmojisRegisterFailedDescription: "Emojileri güncelleyemedi veya silemedi. Ayrıntılar için kayıt günlüğünü kontrol edin." + _logs: + showSuccessLogSwitch: "Başarı günlüğünü göster" + failureLogNothing: "Hata günlüğü yoktur." + logNothing: "Günlük kaydı yok." + _remote: + selectionRowDetail: "Seçilen satırın ayrıntıları" + importSelectionRows: "Seçilen satırları içe aktar" + importSelectionRangesRows: "Seçimdeki satırları içe aktar" + importEmojisButton: "Kontrol edilen Emojileri içe aktar" + confirmImportEmojisTitle: "Emoji'leri İçe Aktar" + confirmImportEmojisDescription: "Uzak sunucudan alınan {count} Emoji(ler)i içe aktarın. Emoji lisansına dikkat edin. Devam etmek istediğinizden emin misiniz?" + _local: + tabTitleList: "Kayıtlı emojiler" + tabTitleRegister: "Emoji kaydı" + _list: + emojisNothing: "Kayıtlı Emoji yok." + markAsDeleteTargetRows: "Silinecek hedef olarak seçilen satırları işaretle" + markAsDeleteTargetRanges: "Seçimdeki satırları silinecek hedef olarak işaretle" + alertUpdateEmojisNothingDescription: "Güncellenmiş Emoji yok." + alertDeleteEmojisNothingDescription: "Silinecek Emoji yok." + confirmMovePage: "Sayfaları taşımak ister misin?" + confirmChangeView: "Görüntüleme şeklini değiştirmek ister misn?" + confirmUpdateEmojisDescription: "{count} Emoji'yi güncelle. Devam etmek istediğinden emin misin?" + confirmDeleteEmojisDescription: "İşaretli {count} Emoji(leri) silin. Devam etmek istediğinden emin misin?" + confirmResetDescription: "Şimdiye kadar yapılan tüm değişiklikler geri alınacaktır." + confirmMovePageDesciption: "Bu sayfadaki Emojilerde değişiklikler yapılmış.\nSayfayı kaydetmeden terk ederseniz, bu sayfada yapılan tüm değişiklikler silinecek." + dialogSelectRoleTitle: "Emojilerde rol setine göre arama yapın" + _register: + uploadSettingTitle: "Yükleme ayarları" + uploadSettingDescription: "Bu ekranda, Emoji yüklerken davranışı yapılandırabilirsin." + directoryToCategoryLabel: "“Kategori” alanına dizin adını girin." + directoryToCategoryCaption: "Bir dizini sürükleyip bıraktığınızda, “kategori” alanına dizin adını girin." + confirmRegisterEmojisDescription: "Listeden Emojileri yeni özel Emojiler olarak kaydet. Devam etmek istediğinden emin misin? (Aşırı yüklemeyi önlemek için, tek bir işlemde yalnızca {count} Emoji kaydedilebilir)" + confirmClearEmojisDescription: "Düzenlemeleri sil ve listeden Emojileri temizle. Devam etmek istediğinden emin misiniz?" + confirmUploadEmojisDescription: "Drive'a sürüklenip bırakılan {count} dosyayı yükle. Devam etmek istediğinden emin misin?" +_embedCodeGen: + title: "Gömme kodunu özelleştir" + header: "Başlığı göster" + autoload: "Otomatik olarak daha fazlasını yükle (kullanımdan kaldırıldı)" + maxHeight: "Maksimum yükseklik" + maxHeightDescription: "0 olarak ayarlandığında maksimum yükseklik ayarı devre dışı bırakılır. Widget'ın dikey olarak genişlemeye devam etmesini önlemek için bir değer belirt." + maxHeightWarn: "Maksimum yükseklik sınırı devre dışıdır (0). Bu istenmeyen bir durumsa, maksimum yüksekliği bir değer olarak ayarla." + previewIsNotActual: "Ekran, önizleme ekranında görüntülenen aralığı aştığı için gerçek gömme işleminden farklıdır." + rounded: "Yuvarlak hale getir" + border: "Dış çerçeveye kenarlık ekle" + applyToPreview: "Önizlemeye başvur" + generateCode: "Gömme kodu oluştur" + codeGenerated: "Kod oluşturuldu" + codeGeneratedDescription: "Oluşturulan kodu web sitene yapıştırarak içeriği göm." +_selfXssPrevention: + warning: "UYARI" + title: "“Bu ekrana bir şey yapıştırın” tamamen bir aldatmaca." + description1: "Buraya bir şey yapıştırırsan, kötü niyetli bir kullanıcı hesabını ele geçirebilir veya kişisel bilgilerini çalabilir." + description2: "Yapıştırmaya çalıştığınız şeyi tam olarak anlamıyorsanız, %c hemen çalışmayı bırakın ve bu pencereyi kapatın." + description3: "Daha fazla bilgi için lütfen buraya bakın. {link}" +_followRequest: + recieved: "Talep alındı" + sent: "İstek gönderildi" +_remoteLookupErrors: + _federationNotAllowed: + title: "Bu sunucuyla iletişim kurulamıyor" + description: "Bu sunucu ile iletişim devre dışı bırakılmış olabilir veya bu sunucu engellenmiş olabilir.\nLütfen sunucu yöneticisi ile iletişime geçin." + _uriInvalid: + title: "URI geçersiz" + description: "Girdiğin URI ile ilgili bir sorun var. Lütfen URI'da kullanılamayan karakterler girip girmediğini kontrol et." + _requestFailed: + title: "İstek başarısız oldu" + description: "Bu sunucuyla iletişim kurulamadı. Sunucu kapalı olabilir. Ayrıca, geçersiz veya mevcut olmayan bir URI girmediğinizden emin ol." + _responseInvalid: + title: "Yanıt geçersiz" + description: "Bu sunucuyla iletişim kurabildi, ancak elde edilen veriler yanlıştı." + _noSuchObject: + title: "Bulunamadı" + description: "İstenen kaynak bulunamadı, lütfen URI'yi tekrar kontrol edin." +_captcha: + verify: "Lütfen CAPTCHA'yı doğrulayın" + testSiteKeyMessage: "Site ve gizli anahtarlar için test değerlerini girerek önizlemeyi kontrol edebilirsin.\nAyrıntılar için lütfen aşağıdaki sayfaya bak." + _error: + _requestFailed: + title: "CAPTCHA isteği başarısız oldu" + text: "Lütfen bir süre sonra tekrar çalıştırın veya ayarları tekrar kontrol edin." + _verificationFailed: + title: "CAPTCHA doğrulaması başarısız oldu" + text: "Ayarların doğru olup olmadığını lütfen tekrar kontrol edin." + _unknown: + title: "CAPTCHA hatası" + text: "Beklenmedik bir hata oluştu." +_bootErrors: + title: "Yükleme başarısız" + serverError: "Bir süre bekledikten ve yeniden yükledikten sonra sorun devam ederse, lütfen aşağıdaki Hata ID ile sunucu yöneticisine başvurun." + solution: "Aşağıdakiler sorunu çözebilir." + solution1: "Tarayıcını ve işletim sistemini en son sürüme güncelle." + solution2: "Reklam engelleyiciyi devre dışı bırak" + solution3: "Tarayıcı önbelleğini temizle" + solution4: "Tor Tarayıcı için dom.webaudio.enabled değerini true olarak ayarlayın." + otherOption: "Diğer seçenekler" + otherOption1: "İstemci ayarlarını ve önbelleği sil" + otherOption2: "Basit istemciyi başlatın" + otherOption3: "Onarım aracını başlatın" + otherOption4: "Misskey'i güvenli modda başlatın" _search: searchScopeAll: "Tümü" + searchScopeLocal: "Yerel" + searchScopeServer: "Spesifik sunucu" + searchScopeUser: "Spesifik kullanıcı" + pleaseEnterServerHost: "Sunucu ana bilgisayarını girin" + pleaseSelectUser: "Kullanıcı seç" + serverHostPlaceholder: "Örnek: misskey.example.com" +_serverSetupWizard: + installCompleted: "Misskey kurulumu tamamlandı!" + firstCreateAccount: "Başlamak için bir yönetici hesabı oluşturun." + accountCreated: "Yönetici hesabı oluşturuldu!" + serverSetting: "Sunucu Ayarları" + youCanEasilyConfigureOptimalServerSettingsWithThisWizard: "Bu sihirbaz, sunucu ayarlarını yapılandırmayı kolaylaştırır." + settingsYouMakeHereCanBeChangedLater: "Bu sihirbaz aracılığıyla değiştirilen ayarlar daha sonra yeniden düzenlenebilir." + howWillYouUseMisskey: "Misskey'i nasıl kullanacaksınız?" + _use: + single: "Tek Kullanıcı Sunucusu" + single_description: "Kendi sunucunuz olarak tek başına kullanın." + single_youCanCreateMultipleAccounts: "Tek kullanıcı sunucusu olarak çalıştırıldığında bile, gerektiğinde birden fazla hesap oluşturulabilir." + group: "Grup sunucusu" + group_description: "Diğer güvenilir kullanıcıları birden fazla kullanıcıyla birlikte kullanmaya davet edin." + open: "Genel sunucu" + open_description: "Herkesin kayıt olmasına izin verin." + openServerAdvice: "Çok sayıda bilinmeyen kullanıcıyı kabul etmek risklidir. Herhangi bir sorunu çözmek için güvenilir bir moderasyon sistemi kullanmanızı öneririz." + openServerAntiSpamAdvice: "Sunucunuzun spam için bir basamak haline gelmesini önlemek için, reCAPTCHA gibi anti-bot işlevlerini etkinleştirerek güvenliğe de özen göstermelisin." + howManyUsersDoYouExpect: "Kaç kullanıcı bekliyorsunuz?" + _scale: + small: "100'den az (küçük ölçekli)" + medium: "100'den fazla ve 1000'den az kullanıcı (orta büyüklükte)" + large: "1000'den fazla (Büyük ölçekli)" + largeScaleServerAdvice: "Büyük sunucular, yük dengeleme ve veritabanı çoğaltma gibi gelişmiş altyapı bilgisi gerektirebilir." + doYouConnectToFediverse: "Fediverse'e bağlanmak ister misin?" + doYouConnectToFediverse_description1: "Dağıtılmış sunucular ağına (Fediverse) bağlandığında, içerik diğer sunucularla paylaşılabilir." + doYouConnectToFediverse_description2: "Fediverse ile bağlantı kurmak “federasyon” olarak da adlandırılır." + youCanConfigureMoreFederationSettingsLater: "Birleştirilmiş sunucuları belirtme gibi gelişmiş ayarlar daha sonra yapılandırılabilir." + remoteContentsCleaning: "Alınan içeriklerin otomatik olarak temizlenmesi" + remoteContentsCleaning_description: "Federasyon, sürekli içerik akışına neden olabilir. Otomatik temizleme özelliğini etkinleştirmek, depolama alanından tasarruf etmek için sunucudan eski ve referanslanmamış içeriği kaldıracak." + adminInfo: "Yönetici bilgileri" + adminInfo_description: "Sorguları almak için kullanılan yönetici bilgilerini ayarlar." + adminInfo_mustBeFilled: "Genel sunucu veya federasyon açıksa girilmelidir." + followingSettingsAreRecommended: "Aşağıdaki ayarlar önerilir" + applyTheseSettings: "Bu ayarları uygulayın" + skipSettings: "Ayarları atla" + settingsCompleted: "Kurulum tamamlandı!" + settingsCompleted_description: "Zaman ayırdığınız için teşekkür ederiz. Artık her şey hazır olduğuna göre, sunucuyu hemen kullanmaya başlayabilirsin." + settingsCompleted_description2: "Sunucu ayarları “Kontrol Paneli”nden değiştirilebilir." + donationRequest: "Bağış Talebi" + _donationRequest: + text1: "Misskey, gönüllüler tarafından geliştirilen ücretsiz bir yazılımdır." + text2: "Bu yazılımı gelecekte de geliştirmeye devam edebilmemiz için desteğini rica ederiz." + text3: "Destekçilere özel avantajlar da var!" +_uploader: + editImage: "Resmi Düzenle" + compressedToX: "{x} boyutuna sıkıştırıldı" + savedXPercent: "{x}% tasarruf" + abortConfirm: "Bazı dosyalar yüklenmedi, iptal etmek ister misin?" + doneConfirm: "Bazı dosyalar yüklenmedi, yine de devam etmek istiyor musun?" + maxFileSizeIsX: "Yükleyebileceğin maksimum dosya boyutu {x}" + allowedTypes: "Yüklenebilir dosya türleri" + tip: "Dosya henüz yüklenmediğinden, bu iletişim kutusu yüklemeden önce dosyayı onaylamanıza, yeniden adlandırmana, sıkıştırmana ve kırpmana olanak tanır. Hazır olduğunda, “Yükle” düğmesine basarak yüklemeyi başlatabilirsin." +_clientPerformanceIssueTip: + title: "Performans ipuçları" + makeSureDisabledAdBlocker: "Reklam engelleyicini devre dışı bırak" + makeSureDisabledAdBlocker_description: "Reklam engelleyiciler performansı etkileyebilir, lütfen sisteminde veya tarayıcının özelliklerinde/uzantılarında reklam engelleyicilerin etkinleştirilmediğinden emin ol." + makeSureDisabledCustomCss: "Özel CSS'yi devre dışı bırak" + makeSureDisabledCustomCss_description: "Stil geçersiz kılma, performansı etkileyebilir. Stil geçersiz kılan özel CSS veya uzantıların etkinleştirilmediğinden emin ol." + makeSureDisabledAddons: "Uzantıları devre dışı bırak" + makeSureDisabledAddons_description: "Bazı uzantılar istemci davranışını engelleyebilir ve performansı etkileyebilir. Lütfen tarayıcı uzantılarınızı devre dışı bırakın ve durumun düzelip düzelmediğini kontrol edin." +_clip: + tip: "Klip, notları gruplandırmanıza olanak tanıyan bir özelliktir." +_userLists: + tip: "Listeler, oluşturulurken belirttiğin herhangi bir kullanıcıyı içerebilir. Oluşturulan liste, yalnızca belirtilen kullanıcıları gösteren bir pano olarak görüntülenebilir." +watermark: "Filigran" +defaultPreset: "Varsayılan Ön Ayar" _watermarkEditor: + tip: "Kredi bilgileri gibi bir filigran görüntüye eklenebilir." + quitWithoutSaveConfirm: "Kaydedilmemiş değişiklikleri silmek ister misin?" + driveFileTypeWarn: "Bu dosya desteklenmiyor" + driveFileTypeWarnDescription: "Bir görüntü dosyası seçin" + title: "Filigranı Düzenle" + cover: "Her şeyi örtün" + repeat: "her yere yayılmış" + opacity: "Opaklık" + scale: "Boyut" + text: "Metin" + position: "Pozisyon" + type: "Tür" image: "Görseller" + advanced: "Gelişmiş" + angle: "Açı" + stripe: "Çizgiler" + stripeWidth: "Çizgi genişliği" + stripeFrequency: "Satır sayısı" + polkadot: "Nokta deseni" + checker: "Kontrolcü" + polkadotMainDotOpacity: "Ana noktanın opaklığı" + polkadotMainDotRadius: "Ana noktanın boyutu" + polkadotSubDotOpacity: "İkincil noktanın opaklığı" + polkadotSubDotRadius: "İkincil noktanın boyutu" + polkadotSubDotDivisions: "Alt nokta sayısı." +_imageEffector: + title: "Effektler" + addEffect: "Efektler Ekle" + discardChangesConfirm: "Cidden çıkmak istiyor musun? Kaydedilmemiş değişikliklerin var." + nothingToConfigure: "Yapılandırılabilir seçenekler mevcut değildir." + _fxs: + chromaticAberration: "Renk Sapması" + glitch: "Bozulma" + mirror: "Ayna" + invert: "Renkleri Ters Çevir" + grayscale: "Gri tonlama" + colorAdjust: "Renk Düzeltme" + colorClamp: "Renk Sıkıştırma" + colorClampAdvanced: "Renk Sıkıştırma (Gelişmiş)" + distort: "Bozulma" + threshold: "Binarize" + zoomLines: "Doymuş hatlar" + stripe: "Çizgiler" + polkadot: "Nokta deseni" + checker: "Denetleyici" + blockNoise: "Gürültüyü Engelle" + tearing: "Yırtılma" + _fxProps: + angle: "Açı" + scale: "Boyut" + size: "Boyut" + color: "Renk" + opacity: "Opaklık" + normalize: "Normalize" + amount: "Miktar" + lightness: "Hafiflet" + contrast: "Kontrast" + hue: "Hue" + brightness: "Parlaklık" + saturation: "Doygunluk" + max: "Maksimum" + min: "Minimum" + direction: "Yön" + phase: "Aşama" + frequency: "Sıklık" + strength: "Güç" + glitchChannelShift: "Kanal değişimi" + seed: "Tohum değeri" + redComponent: "Kırmızı bileşen" + greenComponent: "Yeşil bileşen" + blueComponent: "Mavi bileşen" + threshold: "Eşik" + centerX: "Merkez X" + centerY: "Merkez Y" + zoomLinesSmoothing: "Düzeltme" + zoomLinesSmoothingDescription: "Düzeltme ve yakınlaştırma çizgi genişliği birlikte kullanılamaz." + zoomLinesThreshold: "Zoom çizgi genişliği" + zoomLinesMaskSize: "Merkez çapı" + zoomLinesBlack: "Siyah yap" +drafts: "Taslaklar" +_drafts: + select: "Taslak Seç" + cannotCreateDraftAnymore: "Oluşturulabilecek taslak sayısı aşılmıştır." + cannotCreateDraft: "Bu içerikle taslak oluşturamazsınız." + delete: "Taslak Sil" + deleteAreYouSure: "Taslağı silmek ister misin?" + noDrafts: "Taslak yok" + replyTo: "{user} notunu yanıtla" + quoteOf: "{user} notuna alıntı" + postTo: "{channel}'a gönder" + saveToDraft: "Taslak olarak kaydet" + restoreFromDraft: "Taslaktan geri yükle" + restore: "Geri yükle" + listDrafts: "Taslaklar Listesi" diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index 0c7308a016..46d9ab95ff 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -8,6 +8,9 @@ search: "Пошук" notifications: "Сповіщення" username: "Ім'я користувача" password: "Пароль" +initialPasswordForSetup: "Початковий пароль для налаштування" +initialPasswordIsIncorrect: "Початковий пароль для налаштування неправильний" +initialPasswordForSetupDescription: "Використайте пароль, вказаний у конфігураційному файлі, якщо ви встановлювали Misskey власноруч.\nЯкщо використовуєте сервіси хостингу Misskey, використайте наданий пароль.\nЯкщо ви не маєте паролю, лишіть порожнім щоб продовжити. " forgotPassword: "Я забув пароль" fetchingAsApObject: "Отримуємо з федіверсу..." ok: "OK" @@ -45,6 +48,7 @@ pin: "Закріпити" unpin: "Відкріпити" copyContent: "Скопіювати контент" copyLink: "Скопіювати посилання" +copyRemoteLink: "Копіювати віддалене посилання" delete: "Видалити" deleteAndEdit: "Видалити й редагувати" deleteAndEditConfirm: "Ви впевнені, що хочете видалити цю нотатку та відредагувати її? Ви втратите всі реакції, поширення та відповіді на неї." @@ -57,6 +61,7 @@ copyUserId: "Копіювати ID користувача" copyNoteId: "блокнот ID користувача" copyFileId: "Скопіювати ідентифікатор файлу." searchUser: "Пошук користувачів" +searchThisUsersNotes: "Пошук нотаток користувача" reply: "Відповісти" loadMore: "Показати більше" showMore: "Показати більше" @@ -105,9 +110,11 @@ enterEmoji: "Введіть емодзі" renote: "Поширити" unrenote: "Відміна поширення" renoted: "Поширити запис." +renotedToX: "Поширено до {name}" cantRenote: "Неможливо поширити." cantReRenote: "Поширення не можливо поширити." quote: "Цитата" +inChannelRenote: "Поширено у канал" pinnedNote: "Закріплений запис" pinned: "Закріпити" you: "Ви" @@ -116,6 +123,7 @@ sensitive: "NSFW" add: "Додати" reaction: "Реакції" reactions: "Реакції" +emojiPicker: "Вибір реакції" reactionSettingDescription2: "Перемістити щоб змінити порядок, Клацнути мишою щоб видалити, Натиснути \"+\" щоб додати." rememberNoteVisibility: "Пам’ятати параметри видимісті" attachCancel: "Видалити вкладення" @@ -289,7 +297,9 @@ folderName: "Ім'я теки" createFolder: "Створити теку" renameFolder: "Перейменувати теку" deleteFolder: "Видалити теку" +folder: "Тека" addFile: "Додати файл" +showFile: "Показати файл" emptyDrive: "Диск порожній" emptyFolder: "Тека порожня" unableToDelete: "Видалення неможливе" @@ -302,6 +312,7 @@ copyUrl: "Копіювати URL" rename: "Перейменувати" avatar: "Аватар" banner: "Банер" +displayOfSensitiveMedia: "Показ чутливого медіа" whenServerDisconnected: "Коли зв’язок із сервером втрачено" disconnectedFromServer: "Зв’язок із сервером було перервано" reload: "Оновити" @@ -348,8 +359,11 @@ hcaptcha: "hCaptcha" enableHcaptcha: "Увімкнути hCaptcha" hcaptchaSiteKey: "Ключ сайту" hcaptchaSecretKey: "Секретний ключ" +mcaptcha: "MCaptcha" +enableMcaptcha: "Увімкнути MCaptcha" mcaptchaSiteKey: "Ключ сайту" mcaptchaSecretKey: "Секретний ключ" +mcaptchaInstanceUrl: "Посилання на сервер MCaptcha" recaptcha: "reCAPTCHA" enableRecaptcha: "Увімкнути reCAPTCHA" recaptchaSiteKey: "Ключ сайту" @@ -905,6 +919,8 @@ flip: "Перевернути" lastNDays: "Останні {n} днів" postForm: "Створення нотатки" information: "Інформація" +inMinutes: "х" +inDays: "д" _chat: invitations: "Запросити" noHistory: "Історія порожня" @@ -1632,3 +1648,10 @@ _watermarkEditor: type: "Тип" image: "Зображення" advanced: "Розширені" +_imageEffector: + _fxProps: + scale: "Розмір" + size: "Розмір" + color: "Колір" + opacity: "Непрозорість" + lightness: "Яскравість" diff --git a/locales/uz-UZ.yml b/locales/uz-UZ.yml index b8375302ba..cfa2e26fd5 100644 --- a/locales/uz-UZ.yml +++ b/locales/uz-UZ.yml @@ -903,7 +903,7 @@ _theme: header: "Sarlavha" navBg: "Yon panel foni" navFg: "Yon panel matni" - mention: "Murojat" + mention: "Eslatib o'tish" renote: "Qayta qayd etish" divider: "Ajratrmoq" fgHighlighted: "Belgilangan matn" @@ -1045,7 +1045,7 @@ _notification: _types: all: "Barchasi" follow: "Obuna bo‘lish" - mention: "Murojat" + mention: "Eslatib o'tish" renote: "Qayta qayd etish" quote: "Iqtibos keltirish" reaction: "Reaktsiyalar" @@ -1102,3 +1102,7 @@ _watermarkEditor: type: "turi" image: "Rasmlar" advanced: "Murakkab" +_imageEffector: + _fxProps: + color: "Rang" + lightness: "Yoritish" diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index 47ea715058..cb2f37bed7 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -1196,7 +1196,6 @@ showAvatarDecorations: "Hiển thị trang trí ảnh đại diện" releaseToRefresh: "Thả để làm mới" refreshing: "Đang làm mới" pullDownToRefresh: "Kéo xuống để làm mới" -signupPendingError: "Đã xảy ra sự cố khi xác minh địa chỉ email của bạn. Liên kết có thể đã hết hạn." cwNotationRequired: "Nếu \"Ẩn nội dung\" được bật thì cần phải có chú thích." decorate: "Trang trí" lastNDays: "{n} ngày trước" @@ -1221,6 +1220,8 @@ information: "Giới thiệu" chat: "Trò chuyện" migrateOldSettings: "Di chuyển cài đặt cũ" migrateOldSettings_description: "Thông thường, quá trình này diễn ra tự động, nhưng nếu vì lý do nào đó mà quá trình di chuyển không thành công, bạn có thể kích hoạt thủ công quy trình di chuyển, quá trình này sẽ ghi đè lên thông tin cấu hình hiện tại của bạn." +inMinutes: "phút" +inDays: "ngày" _chat: invitations: "Mời" noHistory: "Không có dữ liệu" @@ -2089,3 +2090,11 @@ _watermarkEditor: image: "Hình ảnh" advanced: "Nâng cao" angle: "Góc" +_imageEffector: + _fxProps: + angle: "Góc" + scale: "Kích thước" + size: "Kích thước" + color: "Màu sắc" + opacity: "Độ trong suốt" + lightness: "Độ sáng" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index c49be193f2..6c62c80f0d 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -1054,6 +1054,7 @@ permissionDeniedError: "操作被拒绝" permissionDeniedErrorDescription: "本账户没有执行该操作的权限。" preset: "预设值" selectFromPresets: "从预设值中选择" +custom: "自定义" achievements: "成就" gotInvalidResponseError: "服务器无应答" gotInvalidResponseErrorDescription: "您的网络连接可能出现了问题, 或是远程服务器暂时不可用. 请稍后重试。" @@ -1092,6 +1093,7 @@ prohibitedWordsDescription2: "AND 条件用空格分隔,正则表达式用斜 hiddenTags: "隐藏标签" hiddenTagsDescription: "设定的标签将不会在时间线上显示。可使用换行来设置多个标签。" notesSearchNotAvailable: "帖子检索不可用" +usersSearchNotAvailable: "用户检索不可用" license: "许可信息" unfavoriteConfirm: "确定要取消收藏吗?" myClips: "我的便签" @@ -1143,7 +1145,7 @@ channelArchiveConfirmTitle: "要将 {name} 归档吗?" channelArchiveConfirmDescription: "归档后,在频道列表与搜索结果中不会显示,也无法发布新的贴文。" thisChannelArchived: "该频道已被归档。" displayOfNote: "显示帖子" -initialAccountSetting: "初始设置" +initialAccountSetting: "初始设定" youFollowing: "正在关注" preventAiLearning: "拒绝接受生成式 AI 的学习" preventAiLearningDescription: "要求文章生成 AI 或图像生成 AI 不能够以发布的帖子和图像等内容作为学习对象。这是通过在 HTML 响应中包含 noai 标志来实现的,这不能完全阻止 AI 学习你的发布内容,并不是所有 AI 都会遵守这类请求。" @@ -1243,7 +1245,7 @@ releaseToRefresh: "松开以刷新" refreshing: "刷新中" pullDownToRefresh: "下拉以刷新" useGroupedNotifications: "分组显示通知" -signupPendingError: "确认电子邮件时出现错误。链接可能已过期。" +emailVerificationFailedError: "确认电子邮件时出现错误。链接可能已过期。" cwNotationRequired: "在启用「隐藏内容」时必须输入注释" doReaction: "回应" code: "代码" @@ -1318,7 +1320,7 @@ confirmOnReact: "发送回应前需要确认" reactAreYouSure: "要用「{emoji}」进行回应吗?" markAsSensitiveConfirm: "要将此媒体标记为敏感吗?" unmarkAsSensitiveConfirm: "要将此媒体解除敏感标记吗?" -preferences: "设置" +preferences: "偏好设置" accessibility: "辅助功能" preferencesProfile: "设置的配置" copyPreferenceId: "复制设置 ID" @@ -1368,6 +1370,13 @@ redisplayAllTips: "重新显示所有的提示和技巧" hideAllTips: "隐藏所有的提示和技巧" defaultImageCompressionLevel: "默认图像压缩等级" defaultImageCompressionLevel_description: "较低的等级可以保持画质,但会增加文件大小。
较高的等级可以减少文件大小,但相对应的画质将会降低。" +inMinutes: "分" +inDays: "日" +safeModeEnabled: "已启用安全模式" +pluginsAreDisabledBecauseSafeMode: "因启用了安全模式,所有插件均已被禁用。" +customCssIsDisabledBecauseSafeMode: "因启用了安全模式,无法应用自定义 CSS。" +themeIsDefaultBecauseSafeMode: "启用安全模式时将使用默认主题。关闭安全模式后将还原。" +thankYouForTestingBeta: "感谢您协助测试 beta 版!" _order: newest: "从新到旧" oldest: "从旧到新" @@ -1459,6 +1468,7 @@ _settings: contentsUpdateFrequency_description2: "当实时模式开启时,无论此设置如何,内容都会实时更新。" showUrlPreview: "显示 URL 预览" showAvailableReactionsFirstInNote: "在顶部显示可用的回应" + showPageTabBarBottom: "在下方显示页面标签栏" _chat: showSenderName: "显示发送者的名字" sendOnEnter: "回车键发送" @@ -1536,7 +1546,7 @@ _announcement: silenceDescription: "开启后,此条公告将不会发送通知,也不强制用户阅读。" _initialAccountSetting: accountCreated: "账户创建完成了!" - letsStartAccountSetup: "来进行帐户的初始设置吧。" + letsStartAccountSetup: "马上来进行账户的初始设定吧。" letsFillYourProfile: "首先,来设定你的个人档案吧!" profileSetting: "个人资料设置" privacySetting: "隐私设置" @@ -1548,7 +1558,7 @@ _initialAccountSetting: haveFun: "希望 {name} 在这里玩得开心!" youCanContinueTutorial: "您可以继续了解 {name}(Misskey) 的使用教程,也可以在此停止教程并立即开始使用它。\n" startTutorial: "开始教学" - skipAreYouSure: "要跳过初始设置吗?" + skipAreYouSure: "要跳过初始设定吗?" laterAreYouSure: "要稍后再进行初始设定吗?" _initialTutorial: launchTutorial: "观看教学" @@ -1632,6 +1642,10 @@ _serverSettings: fanoutTimelineDbFallback: "回退到数据库" fanoutTimelineDbFallbackDescription: "当启用时,若时间线未被缓存,则将额外查询数据库。禁用该功能可通过不执行回退处理进一步减少服务器负载,但会限制可检索的时间线范围。" reactionsBufferingDescription: "开启时可显著提高发送回应时的性能,及减轻数据库负荷。但 Redis 的内存用量会相应增加。" + remoteNotesCleaning: "自动清理远程投稿" + remoteNotesCleaning_description: "启用后,将自动清理已无法找到的旧的远程投稿,可减缓数据库的增长。" + remoteNotesCleaningMaxProcessingDuration: "最长清理持续时间" + remoteNotesCleaningExpiryDaysForEachNotes: "最短帖子保留期限" inquiryUrl: "联络地址" inquiryUrlDescription: "用来指定诸如向服务运营商咨询的论坛地址,或记载了运营商联系方式之类的网页地址。" openRegistration: "开放注册" @@ -1650,6 +1664,11 @@ _serverSettings: userGeneratedContentsVisibilityForVisitor: "用户生成内容对非用户的可见性" userGeneratedContentsVisibilityForVisitor_description: "对于防止难以审核的不适当的远程内容等,通过自己的服务器无意中在互联网上公开等问题很有用。" userGeneratedContentsVisibilityForVisitor_description2: "包含服务器接收到的远程内容在内,无条件将服务器上的所有内容公开在互联网上存在风险。特别是对去中心化的特性不是很了解的访问者有可能将远程服务器上的内容误认为是在此服务器内生成的,需要特别留意。" + restartServerSetupWizardConfirm_title: "要重新开始服务器初始设定向导吗?" + restartServerSetupWizardConfirm_text: "现有的部分设定将重置。" + entrancePageStyle: "入口页面样式" + showTimelineForVisitor: "显示时间线" + showActivitiesForVisitor: "显示活动" _userGeneratedContentsVisibilityForVisitor: all: "全部公开" localOnly: "仅公开本地内容,隐藏远程内容" @@ -1927,7 +1946,7 @@ _role: name: "角色名称" description: "角色描述" permission: "角色权限" - descriptionOfPermission: "监察员可以执行基本地审核操作。\n管理员可以更改服务器的所有设置。" + descriptionOfPermission: "监察员可以执行基本的审核操作。\n管理员可以更改实例的所有设置。" assignTarget: "授权对象" descriptionOfAssignTarget: "手动指手动选择谁被包括在这个角色中。\n符合条件指设置条件以自动包括符合条件的用户。" manual: "手动" @@ -1986,6 +2005,7 @@ _role: descriptionOfRateLimitFactor: "值越小限制越少,值越大限制越多。" canHideAds: "可以隐藏广告" canSearchNotes: "是否可以搜索帖子" + canSearchUsers: "使用用户检索" canUseTranslator: "使用翻译功能" avatarDecorationLimit: "可添加头像挂件的最大个数" canImportAntennas: "允许导入天线" @@ -1998,6 +2018,7 @@ _role: uploadableFileTypes_caption: "指定 MIME 类型。可用换行指定多个类型,也可以用星号(*)作为通配符。(如 image/*)" uploadableFileTypes_caption2: "文件根据文件的不同,可能无法判断其类型。若要允许此类文件,请在指定中添加 {x}。" noteDraftLimit: "可在服务器上创建多少草稿" + watermarkAvailable: "能否使用水印功能" _condition: roleAssignedTo: "已分配给手动角色" isLocal: "是本地用户" @@ -2257,6 +2278,7 @@ _time: minute: "分" hour: "小时" day: "日" + month: "个月" _2fa: alreadyRegistered: "此设备已被注册" registerTOTP: "开始设置验证器" @@ -2806,6 +2828,7 @@ _fileViewer: url: "URL" uploadedAt: "添加日期" attachedNotes: "附加到的帖子" + usage: "使用" thisPageCanBeSeenFromTheAuthor: "此页只能被该文件的上传者查看。" _externalResourceInstaller: title: "从外部站点安装" @@ -3058,6 +3081,7 @@ _bootErrors: otherOption1: "清除客户端设定与缓存" otherOption2: "使用简易客户端" otherOption3: "启动修复工具" + otherOption4: "以安全模式启动 Misskey" _search: searchScopeAll: "全部" searchScopeLocal: "本地" @@ -3094,6 +3118,8 @@ _serverSetupWizard: doYouConnectToFediverse_description1: "若加入由分散性服务器所构成的网络(Fediverse),将能与其它服务器交换内容。" doYouConnectToFediverse_description2: "加入 Fediverse 在这里被称为「联合」。" youCanConfigureMoreFederationSettingsLater: "可在之后进行如哪些服务器可以进行联合等高级设置。" + remoteContentsCleaning: "自动清理传入内容" + remoteContentsCleaning_description: "加入联合后,服务器将持续接收大量内容。打开自动清理后,将自动删除无法找到的旧内容,可节省存储空间。" adminInfo: "管理员信息" adminInfo_description: "设置用于接受询问的管理员信息。" adminInfo_mustBeFilled: "开放服务器或开启了联合的情况下必须输入。" @@ -3146,10 +3172,10 @@ _watermarkEditor: type: "类型" image: "图片" advanced: "高级" + angle: "角度" stripe: "条纹" stripeWidth: "线条宽度" stripeFrequency: "线条数量" - angle: "角度" polkadot: "波点" checker: "检查" polkadotMainDotOpacity: "主波点的不透明度" @@ -3161,6 +3187,7 @@ _imageEffector: title: "效果" addEffect: "添加效果" discardChangesConfirm: "丢弃当前设置并退出?" + nothingToConfigure: "还没有设置" _fxs: chromaticAberration: "色差" glitch: "故障" @@ -3178,11 +3205,43 @@ _imageEffector: checker: "检查" blockNoise: "块状噪点" tearing: "撕裂" + _fxProps: + angle: "角度" + scale: "大小" + size: "大小" + color: "颜色" + opacity: "不透明度" + normalize: "标准化" + amount: "数量" + lightness: "浅色" + contrast: "对比度" + hue: "色调" + brightness: "亮度" + saturation: "饱和度" + max: "最大值" + min: "最小值" + direction: "方向" + phase: "相位" + frequency: "频率" + strength: "强度" + glitchChannelShift: "错位" + seed: "种子" + redComponent: "红色成分" + greenComponent: "绿色成分" + blueComponent: "蓝色成分" + threshold: "阈值" + centerX: "中心 X " + centerY: "中心 Y" + zoomLinesSmoothing: "平滑" + zoomLinesSmoothingDescription: "平滑和集中线宽度设置不能同时使用。" + zoomLinesThreshold: "集中线宽度" + zoomLinesMaskSize: "中心直径" + zoomLinesBlack: "变成黑色" drafts: "草稿" _drafts: select: "选择草稿" cannotCreateDraftAnymore: "已超过可创建的草稿数量。" - cannotCreateDraftOfRenote: "无法创建转帖草稿。" + cannotCreateDraft: "此内容无法创建草稿。" delete: "删除草稿" deleteAreYouSure: "要删除草稿吗?" noDrafts: "没有草稿" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 617e8ef0c7..65b7f9bfba 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -638,7 +638,7 @@ inboxUrl: "收件夾 URL" addedRelays: "已加入的中繼器" serviceworkerInfo: "如要使用推播通知,需要啟用此選項並設定金鑰。" deletedNote: "已刪除的貼文" -invisibleNote: "私人貼文" +invisibleNote: "私密的貼文" enableInfiniteScroll: "啟用自動滾動頁面模式" visibility: "可見性" poll: "票選活動" @@ -1054,6 +1054,7 @@ permissionDeniedError: "操作被拒絕" permissionDeniedErrorDescription: "此帳戶沒有執行這個操作的權限。" preset: "預設值" selectFromPresets: "從預設值中選擇" +custom: "自訂" achievements: "成就" gotInvalidResponseError: "伺服器的回應無效" gotInvalidResponseErrorDescription: "伺服器可能已關閉或者在維護中,請稍後再試。" @@ -1092,6 +1093,7 @@ prohibitedWordsDescription2: "空格代表「以及」(AND),斜線包圍 hiddenTags: "隱藏標籤" hiddenTagsDescription: "設定的標籤不會在趨勢中顯示,換行可以設定多個標籤。" notesSearchNotAvailable: "無法使用搜尋貼文功能。" +usersSearchNotAvailable: "無法使用使用者搜尋功能。" license: "授權" unfavoriteConfirm: "要取消收錄我的最愛嗎?" myClips: "我的摘錄" @@ -1243,7 +1245,7 @@ releaseToRefresh: "放開以更新內容" refreshing: "載入更新中" pullDownToRefresh: "往下拉來更新內容" useGroupedNotifications: "分組顯示通知訊息" -signupPendingError: "驗證您的電子郵件地址時出現問題。連結可能已過期。" +emailVerificationFailedError: "驗證您的電子郵件地址時出現問題。連結可能已過期。" cwNotationRequired: "如果開啟「隱藏內容」,則需要註解說明。" doReaction: "做出反應" code: "程式碼" @@ -1368,6 +1370,13 @@ redisplayAllTips: "重新顯示所有「提示與技巧」" hideAllTips: "隱藏所有「提示與技巧」" defaultImageCompressionLevel: "預設的影像壓縮程度" defaultImageCompressionLevel_description: "低的話可以保留畫質,但是會增加檔案的大小。
高的話可以減少檔案大小,但是會降低畫質。" +inMinutes: "分鐘" +inDays: "日" +safeModeEnabled: "啟用安全模式" +pluginsAreDisabledBecauseSafeMode: "由於啟用安全模式,所有的外掛都被停用。" +customCssIsDisabledBecauseSafeMode: "由於啟用安全模式,所有的客製 CSS 都被停用。" +themeIsDefaultBecauseSafeMode: "在安全模式啟用期間將使用預設主題。關閉安全模式後會恢復原本的設定。" +thankYouForTestingBeta: "感謝您協助驗證 beta 版!" _order: newest: "最新的在前" oldest: "最舊的在前" @@ -1459,6 +1468,7 @@ _settings: contentsUpdateFrequency_description2: "當即時模式開啟時,不論此設定為何,內容都會即時更新。" showUrlPreview: "顯示網址預覽" showAvailableReactionsFirstInNote: "將可用的反應顯示在頂部" + showPageTabBarBottom: "在底部顯示頁面的標籤列" _chat: showSenderName: "顯示發送者的名稱" sendOnEnter: "按下 Enter 發送訊息" @@ -1543,7 +1553,7 @@ _initialAccountSetting: theseSettingsCanEditLater: "這裡的設定可以在之後變更。" youCanEditMoreSettingsInSettingsPageLater: "除此之外,還可以在「設定」頁面進行各種設定。之後請確認看看。" followUsers: "為了構築時間軸,試著追隨您感興趣的使用者吧。" - pushNotificationDescription: "啟用推送通知後,就可以在裝置上接收來自{name}的通知了。" + pushNotificationDescription: "啟用推送通知後,就可以在裝置上接收來自 {name} 的通知了。" initialAccountSettingCompleted: "初始設定完成了!" haveFun: "盡情享受{name}吧!" youCanContinueTutorial: "您可以繼續學習如何使用{name}(Misskey),也可以就此打住,立即開始使用。" @@ -1632,6 +1642,10 @@ _serverSettings: fanoutTimelineDbFallback: "資料庫的回退" fanoutTimelineDbFallbackDescription: "若啟用,在時間軸沒有快取的情況下將執行回退處理以額外查詢資料庫。若停用,可以透過不執行回退處理來進一步減少伺服器的負荷,但會限制可取得的時間軸範圍。" reactionsBufferingDescription: "啟用時,可以顯著提高建立反應時的效能並減少資料庫的負載。 但是,Redis 記憶體使用量會增加。" + remoteNotesCleaning: "自動清除遠端發佈內容" + remoteNotesCleaning_description: "啟用後,系統會定期清理未被參照的舊遠端貼文,以抑制資料庫的膨脹。" + remoteNotesCleaningMaxProcessingDuration: "清理作業的最長持續時間" + remoteNotesCleaningExpiryDaysForEachNotes: "貼文最短保留天數" inquiryUrl: "聯絡表單網址" inquiryUrlDescription: "指定伺服器運營者的聯絡表單網址,或包含運營者聯絡資訊網頁的網址。" openRegistration: "允許建立帳戶" @@ -1650,6 +1664,11 @@ _serverSettings: userGeneratedContentsVisibilityForVisitor: "使用者建立的內容對訪客的公開範圍" userGeneratedContentsVisibilityForVisitor_description: "這有助於防止一些問題的發生,例如未經適當審核的不適當遠端內容無意中透過您自己的伺服器發佈到網際網路上。" userGeneratedContentsVisibilityForVisitor_description2: "包括伺服器接收到的遠端內容在內,無條件地將伺服器內所有內容公開到網際網路上是具有風險的。特別是對於不了解分散式架構特性的瀏覽者來說,他們可能會誤以為這些遠端內容是由該伺服器所創建的,因此需要特別留意。" + restartServerSetupWizardConfirm_title: "要重新執行伺服器的初始設定精靈嗎?" + restartServerSetupWizardConfirm_text: "當前的部分設定將會被重設。" + entrancePageStyle: "入口頁面的樣式" + showTimelineForVisitor: "顯示時間軸" + showActivitiesForVisitor: "顯示活動" _userGeneratedContentsVisibilityForVisitor: all: "全部公開\n" localOnly: "僅公開本地內容,遠端內容則不公開\n" @@ -1986,6 +2005,7 @@ _role: descriptionOfRateLimitFactor: "值越小限制越少,值越大限制越多。" canHideAds: "不顯示廣告" canSearchNotes: "可否搜尋貼文" + canSearchUsers: "可使用使用者搜尋功能" canUseTranslator: "使用翻譯功能" avatarDecorationLimit: "頭像可掛上的最大裝飾數量" canImportAntennas: "允許匯入天線" @@ -1998,6 +2018,7 @@ _role: uploadableFileTypes_caption: "請指定 MIME 類型。可以用換行區隔多個類型,也可以使用星號(*)作為萬用字元進行指定。(例如:image/*)\n" uploadableFileTypes_caption2: "有些檔案可能無法判斷其類型。若要允許這類檔案,請在指定中加入 {x}。" noteDraftLimit: "伺服器端可建立的貼文草稿數量上限\n" + watermarkAvailable: "浮水印功能是否可用" _condition: roleAssignedTo: "手動指派角色完成" isLocal: "本地使用者" @@ -2257,6 +2278,7 @@ _time: minute: "分鐘" hour: "小時" day: "日" + month: "個月" _2fa: alreadyRegistered: "此裝置已被註冊過了" registerTOTP: "開始設定驗證應用程式" @@ -2806,6 +2828,7 @@ _fileViewer: url: "URL" uploadedAt: "加入日期" attachedNotes: "含有附件的貼文" + usage: "使用情況" thisPageCanBeSeenFromTheAuthor: "本頁面僅限上傳了這個檔案的使用者可以檢視。" _externalResourceInstaller: title: "從外部網站安裝" @@ -3058,6 +3081,7 @@ _bootErrors: otherOption1: "刪除用戶端設定和快取" otherOption2: "啟動簡易用戶端" otherOption3: "啟動修復工具" + otherOption4: "以安全模式啟動 Misskey" _search: searchScopeAll: "全部" searchScopeLocal: "本地" @@ -3094,6 +3118,8 @@ _serverSetupWizard: doYouConnectToFediverse_description1: "連接到由分散型伺服器構成的網絡(聯邦宇宙)後,您可以與其他伺服器進行內容的互相交流。\n" doYouConnectToFediverse_description2: "連接到聯邦宇宙被稱為「聯邦」。\n" youCanConfigureMoreFederationSettingsLater: "您可以在稍後進行更高級的設定,例如指定可以聯繫的伺服器等。\n" + remoteContentsCleaning: "自動清理接收的內容" + remoteContentsCleaning_description: "進行聯邦後,會持續接收大量內容。啟用自動清理功能後,系統會自動從伺服器中刪除未被參照的過時內容,以節省儲存空間。" adminInfo: "管理員資訊" adminInfo_description: "設定用於接收查詢的管理者資訊。\n" adminInfo_mustBeFilled: "當設置為開放伺服器或啟用聯邦時,必須填寫此資訊。\n" @@ -3146,10 +3172,10 @@ _watermarkEditor: type: "類型" image: "圖片" advanced: "進階" + angle: "角度" stripe: "條紋" stripeWidth: "線條寬度" stripeFrequency: "線條數量" - angle: "角度" polkadot: "波卡圓點" checker: "棋盤格" polkadotMainDotOpacity: "主圓點的不透明度" @@ -3161,6 +3187,7 @@ _imageEffector: title: "特效" addEffect: "新增特效" discardChangesConfirm: "捨棄更改並退出嗎?" + nothingToConfigure: "無可設定的項目" _fxs: chromaticAberration: "色差" glitch: "異常雜訊效果" @@ -3178,11 +3205,43 @@ _imageEffector: checker: "棋盤格" blockNoise: "阻擋雜訊" tearing: "撕裂" + _fxProps: + angle: "角度" + scale: "大小" + size: "大小" + color: "顏色" + opacity: "透明度" + normalize: "正規化" + amount: "數量" + lightness: "亮度" + contrast: "對比度" + hue: "色相" + brightness: "亮度" + saturation: "彩度" + max: "最大值" + min: "最小值" + direction: "方向" + phase: "相位" + frequency: "頻率" + strength: "強度" + glitchChannelShift: "偏移" + seed: "種子值" + redComponent: "紅色成分" + greenComponent: "綠色成分" + blueComponent: "青色成分" + threshold: "閾值" + centerX: "X中心座標" + centerY: "Y中心座標" + zoomLinesSmoothing: "平滑化" + zoomLinesSmoothingDescription: "平滑化與集中線寬度設定不能同時使用。" + zoomLinesThreshold: "集中線的寬度" + zoomLinesMaskSize: "中心直徑" + zoomLinesBlack: "變成黑色" drafts: "草稿\n" _drafts: select: "選擇草槁" cannotCreateDraftAnymore: "已超出可建立的草稿數量上限。\n" - cannotCreateDraftOfRenote: "無法建立轉發的草稿。\n" + cannotCreateDraft: "無法以此內容建立草稿。\n" delete: "刪除草稿" deleteAreYouSure: "確定要刪除草稿嗎?\n" noDrafts: "沒有草稿。\n" diff --git a/package.json b/package.json index c0ec1ed876..faec340213 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "misskey", - "version": "2025.6.4-alpha.3", + "version": "2025.9.1-alpha.1", "codename": "nasubi", "repository": { "type": "git", "url": "https://github.com/misskey-dev/misskey.git" }, - "packageManager": "pnpm@10.12.1", + "packageManager": "pnpm@10.16.0", "workspaces": [ "packages/frontend-shared", "packages/frontend", @@ -27,6 +27,7 @@ "build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json --no-build && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api", "start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js", "start:test": "ncp ./.github/misskey/test.yml ./.config/test.yml && cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js", + "cli": "cd packages/backend && pnpm cli", "init": "pnpm migrate", "migrate": "cd packages/backend && pnpm migrate", "revert": "cd packages/backend && pnpm revert", @@ -52,30 +53,31 @@ "lodash": "4.17.21" }, "dependencies": { - "cssnano": "7.0.7", - "esbuild": "0.25.5", + "cssnano": "7.1.1", + "esbuild": "0.25.9", "execa": "9.6.0", "fast-glob": "3.3.3", - "glob": "11.0.2", + "glob": "11.0.3", "ignore-walk": "7.0.0", "js-yaml": "4.1.0", - "postcss": "8.5.4", + "postcss": "8.5.6", "tar": "7.4.3", - "terser": "5.42.0", - "typescript": "5.8.3" + "terser": "5.44.0", + "typescript": "5.9.2" }, "devDependencies": { "@misskey-dev/eslint-plugin": "2.1.0", - "@types/node": "22.15.31", - "@typescript-eslint/eslint-plugin": "8.34.0", - "@typescript-eslint/parser": "8.34.0", + "@types/js-yaml": "4.0.9", + "@types/node": "22.18.1", + "@typescript-eslint/eslint-plugin": "8.42.0", + "@typescript-eslint/parser": "8.42.0", "cross-env": "7.0.3", - "cypress": "14.4.1", - "eslint": "9.28.0", - "globals": "16.2.0", + "cypress": "14.5.4", + "eslint": "9.35.0", + "globals": "16.3.0", "ncp": "2.0.0", - "pnpm": "10.12.1", - "start-server-and-test": "2.0.12" + "pnpm": "10.16.0", + "start-server-and-test": "2.1.0" }, "optionalDependencies": { "@tensorflow/tfjs-core": "4.22.0" @@ -83,6 +85,9 @@ "pnpm": { "overrides": { "@aiscript-dev/aiscript-languageserver": "-" + }, + "patchedDependencies": { + "typeorm": "patches/typeorm.patch" } } } diff --git a/packages/backend/migration/1750729939704-FixAvatarUrl.js b/packages/backend/migration/1750729939704-FixAvatarUrl.js new file mode 100644 index 0000000000..81a598136b --- /dev/null +++ b/packages/backend/migration/1750729939704-FixAvatarUrl.js @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ +export class FixAvatarUrl1750729939704 { + name = 'FixAvatarUrl1750729939704' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" ALTER COLUMN "avatarUrl" TYPE character varying(1024)`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" ALTER COLUMN "avatarUrl" TYPE character varying(512)`); + } +} diff --git a/packages/backend/migration/1752502434151-no-action-on-draft-relation.js b/packages/backend/migration/1752502434151-no-action-on-draft-relation.js new file mode 100644 index 0000000000..e3c63b79c7 --- /dev/null +++ b/packages/backend/migration/1752502434151-no-action-on-draft-relation.js @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class NoActionOnDraftRelation1752502434151 { + name = 'NoActionOnDraftRelation1752502434151' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_NOTE_DRAFT_CHANNEL_ID"`); + await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_NOTE_DRAFT_USER_ID"`); + await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_NOTE_DRAFT_RENOTE_ID"`); + await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_NOTE_DRAFT_REPLY_ID"`); + await queryRunner.query(`ALTER TABLE "note_draft" ADD CONSTRAINT "FK_e4983f28b4b18b03491536052f5" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_e4983f28b4b18b03491536052f5"`); + await queryRunner.query(`ALTER TABLE "note_draft" ADD CONSTRAINT "FK_NOTE_DRAFT_REPLY_ID" FOREIGN KEY ("replyId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note_draft" ADD CONSTRAINT "FK_NOTE_DRAFT_RENOTE_ID" FOREIGN KEY ("renoteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note_draft" ADD CONSTRAINT "FK_NOTE_DRAFT_USER_ID" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note_draft" ADD CONSTRAINT "FK_NOTE_DRAFT_CHANNEL_ID" FOREIGN KEY ("channelId") REFERENCES "channel"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } +} diff --git a/packages/backend/migration/1752509043847-migration-cleanup.js b/packages/backend/migration/1752509043847-migration-cleanup.js new file mode 100644 index 0000000000..450e22af0c --- /dev/null +++ b/packages/backend/migration/1752509043847-migration-cleanup.js @@ -0,0 +1,79 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class MigrationCleanup1752509043847 { + name = 'MigrationCleanup1752509043847' + + async up(queryRunner) { + // 1745378064470-composite-note-index.js created a index ON "note" ("userId", "id" DESC) as IDX_724b311e6f883751f261ebe378 but should be named IDX_a6f649630f55af3888e5a42919 + await queryRunner.query(`ALTER INDEX "IDX_724b311e6f883751f261ebe378" RENAME TO "IDX_a6f649630f55af3888e5a42919"`); + + // 1713656541000-abuse-report-notification.js generated system_webhook with hand-written SQL with CURRENT_TIMESTAMP as the default value, but its representation in TypeORM is `now()` + // see https://github.com/typeorm/typeorm/blob/f351757a15b9d2bd9d4222c69dcfd2316f46b5d1/src/driver/postgres/PostgresDriver.ts#L1575 + await queryRunner.query(`ALTER TABLE "system_webhook" ALTER COLUMN "updatedAt" SET DEFAULT now()`); + + // 1702718871541-ffVisibility.js defined a enum type "user_profile_followersVisibility_enum" but it should be "user_profile_followersvisibility_enum" (lowercase 'v') in typeorm + await queryRunner.query(`ALTER TYPE "public"."user_profile_followersVisibility_enum" RENAME TO "user_profile_followersvisibility_enum"`); + + // 1713656541000-abuse-report-notification.js generated abuse_report_notification_recipient with hand-written SQL with CURRENT_TIMESTAMP as the default value, but its representation in TypeORM is `now()` + await queryRunner.query(`ALTER TABLE "abuse_report_notification_recipient" ALTER COLUMN "updatedAt" SET DEFAULT now()`); + + // 1690796169261-play-visibility.js added visibility column to flash table but it forgot to set NOT NULL constraint + await queryRunner.query(`ALTER TABLE "flash" ALTER COLUMN "visibility" SET NOT NULL`); + + // 1736686850345-createNoteDraft.js created note_draft with hand-written SQL but several types and comments are not correctly defined + await queryRunner.query(`CREATE TYPE "public"."note_draft_visibility_enum" AS ENUM('public', 'home', 'followers', 'specified')`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "id" SET DATA TYPE character varying(32)`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "replyId" SET DATA TYPE character varying(32)`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "renoteId" SET DATA TYPE character varying(32)`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "userId" SET DATA TYPE character varying(32)`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "channelId" SET DATA TYPE character varying(32)`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "fileIds" SET DATA TYPE character varying(32) array`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibleUserIds" SET DATA TYPE character varying(32) array`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibility" SET DATA TYPE "public"."note_draft_visibility_enum" USING visibility::note_draft_visibility_enum`); + await queryRunner.query(`COMMENT ON COLUMN "note_draft"."replyId" IS 'The ID of reply target.'`); + await queryRunner.query(`COMMENT ON COLUMN "note_draft"."renoteId" IS 'The ID of renote target.'`); + await queryRunner.query(`COMMENT ON COLUMN "note_draft"."userId" IS 'The ID of author.'`); + await queryRunner.query(`COMMENT ON COLUMN "note_draft"."channelId" IS 'The ID of source channel.'`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "fileIds" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "hasPoll" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "pollChoices" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "pollMultiple" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "localOnly" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibleUserIds" SET NOT NULL`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibleUserIds" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "localOnly" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "pollMultiple" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "pollChoices" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "hasPoll" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "fileIds" DROP NOT NULL`); + await queryRunner.query(`COMMENT ON COLUMN "note_draft"."channelId" IS NULL`); + await queryRunner.query(`COMMENT ON COLUMN "note_draft"."userId" IS 'The ID of author.'`); + await queryRunner.query(`COMMENT ON COLUMN "note_draft"."renoteId" IS NULL`); + await queryRunner.query(`COMMENT ON COLUMN "note_draft"."replyId" IS NULL`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibility" SET DATA TYPE varchar`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibleUserIds" SET DATA TYPE varchar[]`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "fileIds" SET DATA TYPE varchar[]`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "channelId" SET DATA TYPE varchar`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "userId" SET DATA TYPE varchar`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "renoteId" SET DATA TYPE varchar`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "replyId" SET DATA TYPE varchar`); + await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "id" SET DATA TYPE varchar`); + await queryRunner.query(`DROP TYPE "public"."note_draft_visibility_enum"`); + + await queryRunner.query(`ALTER TABLE "flash" ALTER COLUMN "visibility" DROP NOT NULL`); + + await queryRunner.query(`ALTER TABLE "abuse_report_notification_recipient" ALTER COLUMN "updatedAt" SET DEFAULT CURRENT_TIMESTAMP`); + + await queryRunner.query(`ALTER TYPE "public"."user_profile_followersvisibility_enum" RENAME TO "user_profile_followersVisibility_enum"`); + + await queryRunner.query(`ALTER TABLE "system_webhook" ALTER COLUMN "updatedAt" SET DEFAULT CURRENT_TIMESTAMP`); + + await queryRunner.query(`ALTER INDEX "IDX_a6f649630f55af3888e5a42919" RENAME TO "IDX_724b311e6f883751f261ebe378"`); + } +} diff --git a/packages/backend/migration/1753863104203-remoteNotesCleaning.js b/packages/backend/migration/1753863104203-remoteNotesCleaning.js new file mode 100644 index 0000000000..8250d8d0df --- /dev/null +++ b/packages/backend/migration/1753863104203-remoteNotesCleaning.js @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class RemoteNotesCleaning1753863104203 { + name = 'RemoteNotesCleaning1753863104203' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "enableRemoteNotesCleaning" boolean NOT NULL DEFAULT false`); + await queryRunner.query('ALTER TABLE "meta" ADD "remoteNotesCleaningMaxProcessingDurationInMinutes" integer NOT NULL DEFAULT \'60\''); + await queryRunner.query('ALTER TABLE "meta" ADD "remoteNotesCleaningExpiryDaysForEachNotes" integer NOT NULL DEFAULT \'90\''); + } + + async down(queryRunner) { + await queryRunner.query('ALTER TABLE "meta" DROP COLUMN "remoteNotesCleaningExpiryDaysForEachNotes"'); + await queryRunner.query('ALTER TABLE "meta" DROP COLUMN "remoteNotesCleaningMaxProcessingDurationInMinutes"'); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableRemoteNotesCleaning"`); + } +} diff --git a/packages/backend/migration/1753868431598-remove_note_constraints.js b/packages/backend/migration/1753868431598-remove_note_constraints.js new file mode 100644 index 0000000000..29540cf9de --- /dev/null +++ b/packages/backend/migration/1753868431598-remove_note_constraints.js @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class RemoveNoteConstraints1753868431598 { + name = 'RemoveNoteConstraints1753868431598' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "FK_52ccc804d7c69037d558bac4c96"`); + await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "FK_17cb3553c700a4985dff5a30ff5"`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_17cb3553c700a4985dff5a30ff5" FOREIGN KEY ("replyId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_52ccc804d7c69037d558bac4c96" FOREIGN KEY ("renoteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } +} diff --git a/packages/backend/migration/1754019326356-tweakDefaultFederationSettings.js b/packages/backend/migration/1754019326356-tweakDefaultFederationSettings.js new file mode 100644 index 0000000000..1051b18e1b --- /dev/null +++ b/packages/backend/migration/1754019326356-tweakDefaultFederationSettings.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class TweakDefaultFederationSettings1754019326356 { + name = 'TweakDefaultFederationSettings1754019326356' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "federation" SET DEFAULT 'none'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "federation" SET DEFAULT 'all'`); + } +} diff --git a/packages/backend/migration/1755168347001-PageCountInNote.js b/packages/backend/migration/1755168347001-PageCountInNote.js new file mode 100644 index 0000000000..9f1894ab2f --- /dev/null +++ b/packages/backend/migration/1755168347001-PageCountInNote.js @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class PageCountInNote1755168347001 { + name = 'PageCountInNote1755168347001' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "note" ADD "pageCount" smallint NOT NULL DEFAULT '0'`); + + // Update existing notes + // block_list CTE collects all page blocks on the pages including child blocks in the section blocks. + // The clipped_notes CTE counts how many distinct pages each note block is referenced in. + // Finally, we update the note table with the count of pages for each referenced note. + await queryRunner.query(` + WITH RECURSIVE block_list AS ( + ( + SELECT + page.id as page_id, + block as block + FROM page + CROSS JOIN LATERAL jsonb_array_elements(page.content) block + WHERE block->>'type' = 'note' OR block->>'type' = 'section' + ) + UNION ALL + ( + SELECT + block_list.page_id, + child_block AS block + FROM LATERAL ( + SELECT page_id, block + FROM block_list + WHERE block_list.block->>'type' = 'section' + ) block_list + CROSS JOIN LATERAL jsonb_array_elements(block_list.block->'children') child_block + WHERE child_block->>'type' = 'note' OR child_block->>'type' = 'section' + ) + ), + clipped_notes AS ( + SELECT + (block->>'note') AS note_id, + COUNT(distinct block_list.page_id) AS count + FROM block_list + WHERE block_list.block->>'type' = 'note' + GROUP BY block->>'note' + ) + UPDATE note + SET "pageCount" = clipped_notes.count + FROM clipped_notes + WHERE note.id = clipped_notes.note_id; + `); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "pageCount"`); + } +} diff --git a/packages/backend/migration/1755574887486-entrancePageStyle.js b/packages/backend/migration/1755574887486-entrancePageStyle.js new file mode 100644 index 0000000000..ba40764b94 --- /dev/null +++ b/packages/backend/migration/1755574887486-entrancePageStyle.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class EntrancePageStyle1755574887486 { + name = 'EntrancePageStyle1755574887486' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "clientOptions" jsonb NOT NULL DEFAULT '{}'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "clientOptions"`); + } +} diff --git a/packages/backend/migration/1756062689648-NonCascadingPageEyeCatching.js b/packages/backend/migration/1756062689648-NonCascadingPageEyeCatching.js new file mode 100644 index 0000000000..8554cc4304 --- /dev/null +++ b/packages/backend/migration/1756062689648-NonCascadingPageEyeCatching.js @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class NonCascadingPageEyeCatching1756062689648 { + name = 'NonCascadingPageEyeCatching1756062689648' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "page" DROP CONSTRAINT "FK_a9ca79ad939bf06066b81c9d3aa"`); + await queryRunner.query(`ALTER TABLE "page" ADD CONSTRAINT "FK_a9ca79ad939bf06066b81c9d3aa" FOREIGN KEY ("eyeCatchingImageId") REFERENCES "drive_file"("id") ON DELETE SET NULL ON UPDATE NO ACTION`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "page" DROP CONSTRAINT "FK_a9ca79ad939bf06066b81c9d3aa"`); + await queryRunner.query(`ALTER TABLE "page" ADD CONSTRAINT "FK_a9ca79ad939bf06066b81c9d3aa" FOREIGN KEY ("eyeCatchingImageId") REFERENCES "drive_file"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } +} diff --git a/packages/backend/migration/1757823175259-sensitive-ad.js b/packages/backend/migration/1757823175259-sensitive-ad.js new file mode 100644 index 0000000000..46f0f270ab --- /dev/null +++ b/packages/backend/migration/1757823175259-sensitive-ad.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class SensitiveAd1757823175259 { + name = 'SensitiveAd1757823175259' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "ad" ADD "isSensitive" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "ad" DROP COLUMN "isSensitive"`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index 2173ce71a5..5114912769 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -4,13 +4,14 @@ "private": true, "type": "module", "engines": { - "node": "^20.10.0 || ^22.0.0" + "node": "^22.15.0" }, "scripts": { "start": "node ./built/boot/entry.js", "start:test": "cross-env NODE_ENV=test node ./built/boot/entry.js", "migrate": "pnpm typeorm migration:run -d ormconfig.js", "revert": "pnpm typeorm migration:revert -d ormconfig.js", + "cli": "node ./built/boot/cli.js", "check:connect": "node ./scripts/check_connect.js", "build": "swc src -d built -D --strip-leading-paths", "build:test": "swc test-server -d built-test -D --config-file test-server/.swcrc --strip-leading-paths", @@ -33,21 +34,22 @@ "test:fed": "pnpm jest:fed", "test-and-coverage": "pnpm jest-and-coverage", "test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e", + "check-migrations": "node scripts/check_migrations_clean.js", "generate-api-json": "node ./scripts/generate_api_json.js" }, "optionalDependencies": { "@swc/core-android-arm64": "1.3.11", - "@swc/core-darwin-arm64": "1.12.0", - "@swc/core-darwin-x64": "1.12.0", + "@swc/core-darwin-arm64": "1.13.5", + "@swc/core-darwin-x64": "1.13.5", "@swc/core-freebsd-x64": "1.3.11", - "@swc/core-linux-arm-gnueabihf": "1.12.0", - "@swc/core-linux-arm64-gnu": "1.12.0", - "@swc/core-linux-arm64-musl": "1.12.0", - "@swc/core-linux-x64-gnu": "1.12.0", - "@swc/core-linux-x64-musl": "1.12.0", - "@swc/core-win32-arm64-msvc": "1.12.0", - "@swc/core-win32-ia32-msvc": "1.12.0", - "@swc/core-win32-x64-msvc": "1.12.0", + "@swc/core-linux-arm-gnueabihf": "1.13.5", + "@swc/core-linux-arm64-gnu": "1.13.5", + "@swc/core-linux-arm64-musl": "1.13.5", + "@swc/core-linux-x64-gnu": "1.13.5", + "@swc/core-linux-x64-musl": "1.13.5", + "@swc/core-win32-arm64-msvc": "1.13.5", + "@swc/core-win32-ia32-msvc": "1.13.5", + "@swc/core-win32-x64-msvc": "1.13.5", "@tensorflow/tfjs": "4.22.0", "@tensorflow/tfjs-node": "4.22.0", "bufferutil": "4.0.9", @@ -67,32 +69,32 @@ "utf-8-validate": "6.0.5" }, "dependencies": { - "@aws-sdk/client-s3": "3.826.0", - "@aws-sdk/lib-storage": "3.826.0", - "@discordapp/twemoji": "15.1.0", + "@aws-sdk/client-s3": "3.883.0", + "@aws-sdk/lib-storage": "3.883.0", + "@discordapp/twemoji": "16.0.1", "@fastify/accepts": "5.0.2", "@fastify/cookie": "11.0.2", "@fastify/cors": "10.1.0", "@fastify/express": "4.0.2", "@fastify/http-proxy": "10.0.2", - "@fastify/multipart": "9.0.3", + "@fastify/multipart": "9.2.1", "@fastify/static": "8.2.0", "@fastify/view": "10.0.2", "@misskey-dev/sharp-read-bmp": "1.2.0", - "@misskey-dev/summaly": "5.2.1", - "@napi-rs/canvas": "0.1.71", - "@nestjs/common": "11.1.3", - "@nestjs/core": "11.1.3", - "@nestjs/testing": "11.1.3", + "@misskey-dev/summaly": "5.2.3", + "@napi-rs/canvas": "0.1.79", + "@nestjs/common": "11.1.6", + "@nestjs/core": "11.1.6", + "@nestjs/testing": "11.1.6", "@peertube/http-signature": "1.7.0", "@sentry/node": "8.55.0", "@sentry/profiling-node": "8.55.0", "@simplewebauthn/server": "12.0.0", "@sinonjs/fake-timers": "11.3.1", "@smithy/node-http-handler": "2.5.0", - "@swc/cli": "0.7.7", - "@swc/core": "1.12.0", - "@twemoji/parser": "15.1.1", + "@swc/cli": "0.7.8", + "@swc/core": "1.13.5", + "@twemoji/parser": "16.0.0", "@types/redis-info": "3.0.3", "accepts": "1.3.8", "ajv": "8.17.1", @@ -101,10 +103,10 @@ "bcryptjs": "2.4.3", "blurhash": "2.0.5", "body-parser": "1.20.3", - "bullmq": "5.53.2", + "bullmq": "5.58.5", "cacheable-lookup": "7.0.0", "cbor": "9.0.2", - "chalk": "5.4.1", + "chalk": "5.6.0", "chalk-template": "1.1.0", "chokidar": "4.0.3", "cli-highlight": "2.1.11", @@ -112,18 +114,18 @@ "content-disposition": "0.5.4", "date-fns": "2.30.0", "deep-email-validator": "0.1.21", - "fastify": "5.3.3", + "fastify": "5.6.0", "fastify-raw-body": "5.0.0", "feed": "4.2.2", "file-type": "19.6.0", "fluent-ffmpeg": "2.1.3", - "form-data": "4.0.3", - "got": "14.4.7", + "form-data": "4.0.4", + "got": "14.4.8", "happy-dom": "16.8.1", "hpagent": "1.2.0", "htmlescape": "1.1.1", "http-link-header": "1.1.3", - "ioredis": "5.6.1", + "ioredis": "5.7.0", "ip-cidr": "4.0.2", "ipaddr.js": "2.2.0", "is-svg": "5.1.0", @@ -133,13 +135,13 @@ "jsonld": "8.3.3", "jsrsasign": "11.1.0", "juice": "11.0.1", - "meilisearch": "0.51.0", - "mfm-js": "0.24.0", - "microformats-parser": "2.0.3", + "meilisearch": "0.52.0", + "mfm-js": "0.25.0", + "microformats-parser": "2.0.4", "mime-types": "2.1.35", "misskey-js": "workspace:*", "misskey-reversi": "workspace:*", - "ms": "3.0.0-canary.1", + "ms": "3.0.0-canary.202508261828", "nanoid": "5.1.5", "nested-property": "4.0.0", "node-fetch": "3.3.2", @@ -149,9 +151,9 @@ "oauth2orize": "1.12.0", "oauth2orize-pkce": "0.1.2", "os-utils": "0.0.14", - "otpauth": "9.4.0", + "otpauth": "9.4.1", "parse5": "7.3.0", - "pg": "8.16.0", + "pg": "8.16.3", "pkce-challenge": "4.1.0", "probe-image-size": "7.2.3", "promise-limit": "2.7.0", @@ -173,25 +175,25 @@ "slacc": "0.0.10", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", - "systeminformation": "5.27.1", + "systeminformation": "5.27.8", "tinycolor2": "1.6.0", - "tmp": "0.2.3", + "tmp": "0.2.5", "tsc-alias": "1.8.16", "tsconfig-paths": "4.2.0", - "typeorm": "0.3.24", - "typescript": "5.8.3", + "typeorm": "0.3.26", + "typescript": "5.9.2", "ulid": "2.4.0", "vary": "1.1.2", "web-push": "3.6.7", - "ws": "8.18.2", + "ws": "8.18.3", "xev": "3.0.2" }, "devDependencies": { "@jest/globals": "29.7.0", - "@nestjs/platform-express": "10.4.19", - "@sentry/vue": "9.28.0", + "@nestjs/platform-express": "10.4.20", + "@sentry/vue": "9.46.0", "@simplewebauthn/types": "12.0.0", - "@swc/jest": "0.2.38", + "@swc/jest": "0.2.39", "@types/accepts": "1.3.7", "@types/archiver": "6.0.3", "@types/bcryptjs": "2.4.6", @@ -208,19 +210,19 @@ "@types/jsrsasign": "10.5.15", "@types/mime-types": "2.1.4", "@types/ms": "0.7.34", - "@types/node": "22.15.31", - "@types/nodemailer": "6.4.17", + "@types/node": "22.18.1", + "@types/nodemailer": "6.4.19", "@types/oauth": "0.9.6", "@types/oauth2orize": "1.11.5", "@types/oauth2orize-pkce": "0.1.2", - "@types/pg": "8.15.4", + "@types/pg": "8.15.5", "@types/pug": "2.0.10", "@types/qrcode": "1.5.5", "@types/random-seed": "0.3.5", "@types/ratelimiter": "3.4.6", "@types/rename": "1.0.7", "@types/sanitize-html": "2.16.0", - "@types/semver": "7.7.0", + "@types/semver": "7.7.1", "@types/simple-oauth2": "5.0.7", "@types/sinonjs__fake-timers": "8.1.5", "@types/supertest": "6.0.3", @@ -229,11 +231,11 @@ "@types/vary": "1.1.3", "@types/web-push": "3.6.4", "@types/ws": "8.18.1", - "@typescript-eslint/eslint-plugin": "8.34.0", - "@typescript-eslint/parser": "8.34.0", + "@typescript-eslint/eslint-plugin": "8.42.0", + "@typescript-eslint/parser": "8.42.0", "aws-sdk-client-mock": "4.1.0", "cross-env": "7.0.3", - "eslint-plugin-import": "2.31.0", + "eslint-plugin-import": "2.32.0", "execa": "8.0.1", "fkill": "9.0.0", "jest": "29.7.0", @@ -241,6 +243,6 @@ "nodemon": "3.1.10", "pid-port": "1.0.2", "simple-oauth2": "5.1.0", - "supertest": "7.1.1" + "supertest": "7.1.4" } } diff --git a/packages/backend/scripts/check_migrations_clean.js b/packages/backend/scripts/check_migrations_clean.js new file mode 100644 index 0000000000..ce67b1cd81 --- /dev/null +++ b/packages/backend/scripts/check_migrations_clean.js @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +// This script checks if the database migrations has been generated correctly. + +import dataSource from '../ormconfig.js'; + +await dataSource.initialize(); + +const sqlInMemory = await dataSource.driver.createSchemaBuilder().log(); + +if (sqlInMemory.upQueries.length > 0 || sqlInMemory.downQueries.length > 0) { + console.error('There are several pending migrations. Please make sure you have generated the migrations correctly, or configured entities class correctly.'); + for (const query of sqlInMemory.upQueries) { + console.error(`- ${query.query}`); + } + for (const query of sqlInMemory.downQueries) { + console.error(`- ${query.query}`); + } + process.exit(1); +} else { + console.log('All migrations are clean.'); + process.exit(0); +} diff --git a/packages/backend/scripts/dev.mjs b/packages/backend/scripts/dev.mjs index a3e0558abd..023eb7eae6 100644 --- a/packages/backend/scripts/dev.mjs +++ b/packages/backend/scripts/dev.mjs @@ -42,7 +42,7 @@ async function killProc() { './node_modules/nodemon/bin/nodemon.js', [ '-w', 'src', - '-e', 'ts,js,mjs,cjs,json', + '-e', 'ts,js,mjs,cjs,json,pug', '--exec', 'pnpm', 'run', 'build', ], { diff --git a/packages/backend/src/boot/cli.ts b/packages/backend/src/boot/cli.ts new file mode 100644 index 0000000000..a5618f8152 --- /dev/null +++ b/packages/backend/src/boot/cli.ts @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import 'reflect-metadata'; +import { EventEmitter } from 'node:events'; +import { NestFactory } from '@nestjs/core'; +import { CommandModule } from '@/cli/CommandModule.js'; +import { NestLogger } from '@/NestLogger.js'; +import { CommandService } from '@/cli/CommandService.js'; + +process.title = 'Misskey Cli'; + +Error.stackTraceLimit = Infinity; +EventEmitter.defaultMaxListeners = 128; + +const app = await NestFactory.createApplicationContext(CommandModule, { + logger: new NestLogger(), +}); + +const commandService = app.get(CommandService); + +const command = process.argv[2] ?? 'help'; + +switch (command) { + case 'help': { + console.log('Available commands:'); + console.log(' help - Displays this help message'); + console.log(' reset-captcha - Resets the captcha'); + break; + } + case 'ping': { + await commandService.ping(); + break; + } + case 'reset-captcha': { + await commandService.resetCaptcha(); + console.log('Captcha has been reset.'); + break; + } + default: { + console.error(`Unrecognized command: ${command}`); + console.error('Use "help" to see available commands.'); + process.exit(1); + } +} + +process.exit(0); diff --git a/packages/backend/src/cli/CommandModule.ts b/packages/backend/src/cli/CommandModule.ts new file mode 100644 index 0000000000..f4b1d25c18 --- /dev/null +++ b/packages/backend/src/cli/CommandModule.ts @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Module } from '@nestjs/common'; +import { CoreModule } from '@/core/CoreModule.js'; +import { GlobalModule } from '@/GlobalModule.js'; +import { CommandService } from './CommandService.js'; + +@Module({ + imports: [ + GlobalModule, + CoreModule, + ], + providers: [ + CommandService, + ], + exports: [ + CommandService, + ], +}) +export class CommandModule {} diff --git a/packages/backend/src/cli/CommandService.ts b/packages/backend/src/cli/CommandService.ts new file mode 100644 index 0000000000..cdb2a9f382 --- /dev/null +++ b/packages/backend/src/cli/CommandService.ts @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import type { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; +import type Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; +import { MetaService } from '@/core/MetaService.js'; + +@Injectable() +export class CommandService { + private logger: Logger; + + constructor( + @Inject(DI.config) + private config: Config, + + private metaService: MetaService, + ) { + } + + @bindThis + public async ping() { + console.log('pong'); + } + + @bindThis + public async resetCaptcha() { + await this.metaService.update({ + enableHcaptcha: false, + hcaptchaSiteKey: null, + hcaptchaSecretKey: null, + enableMcaptcha: false, + mcaptchaSitekey: null, + mcaptchaSecretKey: null, + mcaptchaInstanceUrl: null, + enableRecaptcha: false, + recaptchaSiteKey: null, + recaptchaSecretKey: null, + enableTurnstile: false, + turnstileSiteKey: null, + turnstileSecretKey: null, + enableTestcaptcha: false, + }); + } +} diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 9031096745..fdf6fe18e2 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -7,6 +7,7 @@ import * as fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, resolve } from 'node:path'; import * as yaml from 'js-yaml'; +import { type FastifyServerOptions } from 'fastify'; import type * as Sentry from '@sentry/node'; import type * as SentryVue from '@sentry/vue'; import type { RedisOptions } from 'ioredis'; @@ -27,6 +28,7 @@ type Source = { url?: string; port?: number; socket?: string; + trustProxy?: FastifyServerOptions['trustProxy']; chmodSocket?: string; disableHsts?: boolean; db: { @@ -118,6 +120,7 @@ export type Config = { url: string; port: number; socket: string | undefined; + trustProxy: FastifyServerOptions['trustProxy']; chmodSocket: string | undefined; disableHsts: boolean | undefined; db: { @@ -184,9 +187,9 @@ export type Config = { authUrl: string; driveUrl: string; userAgent: string; - frontendEntry: string; + frontendEntry: { file: string | null }; frontendManifestExists: boolean; - frontendEmbedEntry: string; + frontendEmbedEntry: { file: string | null }; frontendEmbedManifestExists: boolean; mediaProxy: string; externalMediaProxyEnabled: boolean; @@ -235,10 +238,10 @@ export function loadConfig(): Config { const frontendEmbedManifestExists = fs.existsSync(_dirname + '/../../../built/_frontend_embed_vite_/manifest.json'); const frontendManifest = frontendManifestExists ? JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_frontend_vite_/manifest.json`, 'utf-8')) - : { 'src/_boot_.ts': { file: 'src/_boot_.ts' } }; + : { 'src/_boot_.ts': { file: null } }; const frontendEmbedManifest = frontendEmbedManifestExists ? JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_frontend_embed_vite_/manifest.json`, 'utf-8')) - : { 'src/boot.ts': { file: 'src/boot.ts' } }; + : { 'src/boot.ts': { file: null } }; const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source; @@ -266,6 +269,7 @@ export function loadConfig(): Config { url: url.origin, port: config.port ?? parseInt(process.env.PORT ?? '', 10), socket: config.socket, + trustProxy: config.trustProxy, chmodSocket: config.chmodSocket, disableHsts: config.disableHsts, host, diff --git a/packages/backend/src/core/AiService.ts b/packages/backend/src/core/AiService.ts index 248a9b8979..23ab8082ed 100644 --- a/packages/backend/src/core/AiService.ts +++ b/packages/backend/src/core/AiService.ts @@ -29,7 +29,7 @@ export class AiService { } @bindThis - public async detectSensitive(path: string): Promise { + public async detectSensitive(source: string | Buffer): Promise { try { if (isSupportedCpu === undefined) { isSupportedCpu = await this.computeIsSupportedCpu(); @@ -51,7 +51,7 @@ export class AiService { }); } - const buffer = await fs.promises.readFile(path); + const buffer = source instanceof Buffer ? source : await fs.promises.readFile(source); const image = await tf.node.decodeImage(buffer, 3) as any; try { const predictions = await this.model.classify(image); diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index 0c0c5d3a39..a30bff0fe4 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -78,6 +78,7 @@ import { ChannelFollowingService } from './ChannelFollowingService.js'; import { ChatService } from './ChatService.js'; import { RegistryApiService } from './RegistryApiService.js'; import { ReversiService } from './ReversiService.js'; +import { PageService } from './PageService.js'; import { ChartLoggerService } from './chart/ChartLoggerService.js'; import FederationChart from './chart/charts/federation.js'; @@ -227,6 +228,7 @@ const $ChannelFollowingService: Provider = { provide: 'ChannelFollowingService', const $ChatService: Provider = { provide: 'ChatService', useExisting: ChatService }; const $RegistryApiService: Provider = { provide: 'RegistryApiService', useExisting: RegistryApiService }; const $ReversiService: Provider = { provide: 'ReversiService', useExisting: ReversiService }; +const $PageService: Provider = { provide: 'PageService', useExisting: PageService }; const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService }; const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart }; @@ -379,6 +381,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting ChatService, RegistryApiService, ReversiService, + PageService, ChartLoggerService, FederationChart, @@ -527,6 +530,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $ChatService, $RegistryApiService, $ReversiService, + $PageService, $ChartLoggerService, $FederationChart, @@ -676,6 +680,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting ChatService, RegistryApiService, ReversiService, + PageService, FederationChart, NotesChart, @@ -822,6 +827,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $ChatService, $RegistryApiService, $ReversiService, + $PageService, $FederationChart, $NotesChart, diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts index 45d7ea11e4..c7be0f7843 100644 --- a/packages/backend/src/core/EmailService.ts +++ b/packages/backend/src/core/EmailService.ts @@ -145,7 +145,10 @@ export class EmailService { try { // TODO: htmlサニタイズ const info = await transporter.sendMail({ - from: this.meta.email!, + from: this.meta.name ? { + name: this.meta.name, + address: this.meta.email!, + } : this.meta.email!, to: to, subject: subject, text: text, diff --git a/packages/backend/src/core/FanoutTimelineEndpointService.ts b/packages/backend/src/core/FanoutTimelineEndpointService.ts index 97b617096a..94c5691bf4 100644 --- a/packages/backend/src/core/FanoutTimelineEndpointService.ts +++ b/packages/backend/src/core/FanoutTimelineEndpointService.ts @@ -20,6 +20,8 @@ import { CacheService } from '@/core/CacheService.js'; import { isReply } from '@/misc/is-reply.js'; import { isInstanceMuted } from '@/misc/is-instance-muted.js'; +type NoteFilter = (note: MiNote) => boolean; + type TimelineOptions = { untilId: string | null, sinceId: string | null, @@ -28,7 +30,7 @@ type TimelineOptions = { me?: { id: MiUser['id'] } | undefined | null, useDbFallback: boolean, redisTimelines: FanoutTimelineName[], - noteFilter?: (note: MiNote) => boolean, + noteFilter?: NoteFilter, alwaysIncludeMyNotes?: boolean; ignoreAuthorFromBlock?: boolean; ignoreAuthorFromMute?: boolean; @@ -79,7 +81,7 @@ export class FanoutTimelineEndpointService { const shouldFallbackToDb = noteIds.length === 0 || ps.sinceId != null && ps.sinceId < oldestNoteId; if (!shouldFallbackToDb) { - let filter = ps.noteFilter ?? (_note => true); + let filter = ps.noteFilter ?? (_note => true) as NoteFilter; if (ps.alwaysIncludeMyNotes && ps.me) { const me = ps.me; @@ -145,15 +147,11 @@ export class FanoutTimelineEndpointService { { const parentFilter = filter; filter = (note) => { - const noteJoined = note as MiNote & { - renoteUser: MiUser | null; - replyUser: MiUser | null; - }; if (!ps.ignoreAuthorFromUserSuspension) { if (note.user!.isSuspended) return false; } - if (note.userId !== note.renoteUserId && noteJoined.renoteUser?.isSuspended) return false; - if (note.userId !== note.replyUserId && noteJoined.replyUser?.isSuspended) return false; + if (note.userId !== note.renoteUserId && note.renote?.user?.isSuspended) return false; + if (note.userId !== note.replyUserId && note.reply?.user?.isSuspended) return false; return parentFilter(note); }; @@ -200,7 +198,7 @@ export class FanoutTimelineEndpointService { return await ps.dbFallback(ps.untilId, ps.sinceId, ps.limit); } - private async getAndFilterFromDb(noteIds: string[], noteFilter: (note: MiNote) => boolean, idCompare: (a: string, b: string) => number): Promise { + private async getAndFilterFromDb(noteIds: string[], noteFilter: NoteFilter, idCompare: (a: string, b: string) => number): Promise { const query = this.notesRepository.createQueryBuilder('note') .where('note.id IN (:...noteIds)', { noteIds: noteIds }) .innerJoinAndSelect('note.user', 'user') diff --git a/packages/backend/src/core/FileInfoService.ts b/packages/backend/src/core/FileInfoService.ts index 6250d4d3a1..62a7d24afb 100644 --- a/packages/backend/src/core/FileInfoService.ts +++ b/packages/backend/src/core/FileInfoService.ts @@ -21,6 +21,7 @@ import { LoggerService } from '@/core/LoggerService.js'; import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import type { PredictionType } from 'nsfwjs'; +import { isMimeImage } from '@/misc/is-mime-image.js'; export type FileInfo = { size: number; @@ -204,16 +205,7 @@ export class FileInfoService { return [sensitive, porn]; } - if ([ - 'image/jpeg', - 'image/png', - 'image/webp', - ].includes(mime)) { - const result = await this.aiService.detectSensitive(source); - if (result) { - [sensitive, porn] = judgePrediction(result); - } - } else if (analyzeVideo && (mime === 'image/apng' || mime.startsWith('video/'))) { + if (analyzeVideo && (mime === 'image/apng' || mime.startsWith('video/'))) { const [outDir, disposeOutDir] = await createTempDir(); try { const command = FFmpeg() @@ -281,6 +273,23 @@ export class FileInfoService { } finally { disposeOutDir(); } + } else if (isMimeImage(mime, 'sharp-convertible-image-with-bmp')) { + /* + * tfjs-node は限られた画像形式しか受け付けないため、sharp で PNG に変換する + * せっかくなので内部処理で使われる最大サイズの299x299に事前にリサイズする + */ + const png = await (await sharpBmp(source, mime)) + .resize(299, 299, { + withoutEnlargement: false, + }) + .rotate() + .flatten({ background: { r: 119, g: 119, b: 119 } }) // 透過部分を18%グレーで塗りつぶす + .png() + .toBuffer(); + const result = await this.aiService.detectSensitive(png); + if (result) { + [sensitive, porn] = judgePrediction(result); + } } return [sensitive, porn]; diff --git a/packages/backend/src/core/FlashService.ts b/packages/backend/src/core/FlashService.ts index 2a98225382..8caffe9e45 100644 --- a/packages/backend/src/core/FlashService.ts +++ b/packages/backend/src/core/FlashService.ts @@ -4,8 +4,11 @@ */ import { Inject, Injectable } from '@nestjs/common'; +import { Brackets } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import { type FlashsRepository } from '@/models/_.js'; +import { type FlashLikesRepository, MiUser, type FlashsRepository } from '@/models/_.js'; +import { QueryService } from '@/core/QueryService.js'; +import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; /** * MisskeyPlay関係のService @@ -15,6 +18,11 @@ export class FlashService { constructor( @Inject(DI.flashsRepository) private flashRepository: FlashsRepository, + + @Inject(DI.flashLikesRepository) + private flashLikesRepository: FlashLikesRepository, + + private queryService: QueryService, ) { } @@ -37,4 +45,43 @@ export class FlashService { return await builder.getMany(); } + + public async myLikes(meId: MiUser['id'], opts: { sinceId?: string, untilId?: string, sinceDate?: number, untilDate?: number, limit?: number, search?: string | null }) { + const query = this.queryService.makePaginationQuery(this.flashLikesRepository.createQueryBuilder('like'), opts.sinceId, opts.untilId, opts.sinceDate, opts.untilDate) + .andWhere('like.userId = :meId', { meId }) + .leftJoinAndSelect('like.flash', 'flash'); + + if (opts.search != null) { + for (const word of opts.search.trim().split(' ')) { + query.andWhere(new Brackets(qb => { + qb.orWhere('flash.title ILIKE :search', { search: `%${sqlLikeEscape(word)}%` }); + qb.orWhere('flash.summary ILIKE :search', { search: `%${sqlLikeEscape(word)}%` }); + })); + } + } + + const likes = await query + .limit(opts.limit) + .getMany(); + + return likes; + } + + public async search(searchQuery: string, opts: { sinceId?: string, untilId?: string, sinceDate?: number, untilDate?: number, limit?: number }) { + const query = this.queryService.makePaginationQuery(this.flashRepository.createQueryBuilder('flash'), opts.sinceId, opts.untilId, opts.sinceDate, opts.untilDate) + .andWhere('flash.visibility = \'public\''); + + for (const word of searchQuery.trim().split(' ')) { + query.andWhere(new Brackets(qb => { + qb.orWhere('flash.title ILIKE :search', { search: `%${sqlLikeEscape(word)}%` }); + qb.orWhere('flash.summary ILIKE :search', { search: `%${sqlLikeEscape(word)}%` }); + })); + } + + const result = await query + .limit(opts.limit) + .getMany(); + + return result; + } } diff --git a/packages/backend/src/core/HttpRequestService.ts b/packages/backend/src/core/HttpRequestService.ts index 3ddfe52045..f7973cbb66 100644 --- a/packages/backend/src/core/HttpRequestService.ts +++ b/packages/backend/src/core/HttpRequestService.ts @@ -6,6 +6,7 @@ import * as http from 'node:http'; import * as https from 'node:https'; import * as net from 'node:net'; +import * as stream from 'node:stream'; import ipaddr from 'ipaddr.js'; import CacheableLookup from 'cacheable-lookup'; import fetch from 'node-fetch'; @@ -26,12 +27,6 @@ export type HttpRequestSendOptions = { validators?: ((res: Response) => void)[]; }; -declare module 'node:http' { - interface Agent { - createConnection(options: net.NetConnectOpts, callback?: (err: unknown, stream: net.Socket) => void): net.Socket; - } -} - class HttpRequestServiceAgent extends http.Agent { constructor( private config: Config, @@ -41,11 +36,11 @@ class HttpRequestServiceAgent extends http.Agent { } @bindThis - public createConnection(options: net.NetConnectOpts, callback?: (err: unknown, stream: net.Socket) => void): net.Socket { + public createConnection(options: http.ClientRequestArgs, callback?: (err: Error | null, stream: stream.Duplex) => void): stream.Duplex { const socket = super.createConnection(options, callback) .on('connect', () => { - const address = socket.remoteAddress; - if (process.env.NODE_ENV === 'production') { + if (socket instanceof net.Socket && process.env.NODE_ENV === 'production') { + const address = socket.remoteAddress; if (address && ipaddr.isValid(address)) { if (this.isPrivateIp(address)) { socket.destroy(new Error(`Blocked address: ${address}`)); @@ -80,11 +75,11 @@ class HttpsRequestServiceAgent extends https.Agent { } @bindThis - public createConnection(options: net.NetConnectOpts, callback?: (err: unknown, stream: net.Socket) => void): net.Socket { + public createConnection(options: http.ClientRequestArgs, callback?: (err: Error | null, stream: stream.Duplex) => void): stream.Duplex { const socket = super.createConnection(options, callback) .on('connect', () => { - const address = socket.remoteAddress; - if (process.env.NODE_ENV === 'production') { + if (socket instanceof net.Socket && process.env.NODE_ENV === 'production') { + const address = socket.remoteAddress; if (address && ipaddr.isValid(address)) { if (this.isPrivateIp(address)) { socket.destroy(new Error(`Blocked address: ${address}`)); diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 469426f87e..1eefcfa054 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -421,7 +421,7 @@ export class NoteCreateService implements OnApplicationShutdown { emojis, userId: user.id, localOnly: data.localOnly!, - reactionAcceptance: data.reactionAcceptance, + reactionAcceptance: data.reactionAcceptance ?? null, visibility: data.visibility as any, visibleUserIds: data.visibility === 'specified' ? data.visibleUsers @@ -483,7 +483,11 @@ export class NoteCreateService implements OnApplicationShutdown { await this.notesRepository.insert(insert); } - return insert; + return { + ...insert, + reply: data.reply ?? null, + renote: data.renote ?? null, + }; } catch (e) { // duplicate key error if (isDuplicateKeyValueError(e)) { diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts index e394506a44..af1f0eda9a 100644 --- a/packages/backend/src/core/NoteDeleteService.ts +++ b/packages/backend/src/core/NoteDeleteService.ts @@ -62,7 +62,6 @@ export class NoteDeleteService { */ async delete(user: { id: MiUser['id']; uri: MiUser['uri']; host: MiUser['host']; isBot: MiUser['isBot']; }, note: MiNote, quiet = false, deleter?: MiUser) { const deletedAt = new Date(); - const cascadingNotes = await this.findCascadingNotes(note); if (note.replyId) { await this.notesRepository.decrement({ id: note.replyId }, 'repliesCount', 1); @@ -90,15 +89,6 @@ export class NoteDeleteService { this.deliverToConcerned(user, note, content); } - - // also deliver delete activity to cascaded notes - const federatedLocalCascadingNotes = (cascadingNotes).filter(note => !note.localOnly && note.userHost == null); // filter out local-only notes - for (const cascadingNote of federatedLocalCascadingNotes) { - if (!cascadingNote.user) continue; - if (!this.userEntityService.isLocalUser(cascadingNote.user)) continue; - const content = this.apRendererService.addContext(this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${cascadingNote.id}`), cascadingNote.user)); - this.deliverToConcerned(cascadingNote.user, cascadingNote, content); - } //#endregion this.notesChart.update(note, false); @@ -118,9 +108,6 @@ export class NoteDeleteService { } } - for (const cascadingNote of cascadingNotes) { - this.searchService.unindexNote(cascadingNote); - } this.searchService.unindexNote(note); await this.notesRepository.delete({ @@ -140,29 +127,6 @@ export class NoteDeleteService { } } - @bindThis - private async findCascadingNotes(note: MiNote): Promise { - const recursive = async (noteId: string): Promise => { - const query = this.notesRepository.createQueryBuilder('note') - .where('note.replyId = :noteId', { noteId }) - .orWhere(new Brackets(q => { - q.where('note.renoteId = :noteId', { noteId }) - .andWhere('note.text IS NOT NULL'); - })) - .leftJoinAndSelect('note.user', 'user'); - const replies = await query.getMany(); - - return [ - replies, - ...await Promise.all(replies.map(reply => recursive(reply.id))), - ].flat(); - }; - - const cascadingNotes: MiNote[] = await recursive(note.id); - - return cascadingNotes; - } - @bindThis private async getMentionedRemoteUsers(note: MiNote) { const where = [] as any[]; diff --git a/packages/backend/src/core/PageService.ts b/packages/backend/src/core/PageService.ts new file mode 100644 index 0000000000..7f0e5c7ccc --- /dev/null +++ b/packages/backend/src/core/PageService.ts @@ -0,0 +1,223 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource, In, Not } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import { + type NotesRepository, + MiPage, + type PagesRepository, + MiDriveFile, + type UsersRepository, + MiNote, +} from '@/models/_.js'; +import { bindThis } from '@/decorators.js'; +import { RoleService } from '@/core/RoleService.js'; +import { IdService } from '@/core/IdService.js'; +import type { MiUser } from '@/models/User.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; + +export interface PageBody { + title: string; + name: string; + summary: string | null; + content: Array>; + variables: Array>; + script: string; + eyeCatchingImage?: MiDriveFile | null; + font: string; + alignCenter: boolean; + hideTitleWhenPinned: boolean; +} + +@Injectable() +export class PageService { + constructor( + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private roleService: RoleService, + private moderationLogService: ModerationLogService, + private idService: IdService, + ) { + } + + @bindThis + public async create( + me: MiUser, + body: PageBody, + ): Promise { + await this.pagesRepository.findBy({ + userId: me.id, + name: body.name, + }).then(result => { + if (result.length > 0) { + throw new IdentifiableError('1a79e38e-3d83-4423-845b-a9d83ff93b61'); + } + }); + + const page = await this.pagesRepository.insertOne(new MiPage({ + id: this.idService.gen(), + updatedAt: new Date(), + title: body.title, + name: body.name, + summary: body.summary, + content: body.content, + variables: body.variables, + script: body.script, + eyeCatchingImageId: body.eyeCatchingImage ? body.eyeCatchingImage.id : null, + userId: me.id, + visibility: 'public', + alignCenter: body.alignCenter, + hideTitleWhenPinned: body.hideTitleWhenPinned, + font: body.font, + })); + + const referencedNotes = this.collectReferencedNotes(page.content); + if (referencedNotes.length > 0) { + await this.notesRepository.increment({ id: In(referencedNotes) }, 'pageCount', 1); + } + + return page; + } + + @bindThis + public async update( + me: MiUser, + pageId: MiPage['id'], + body: Partial, + ): Promise { + await this.db.transaction(async (transaction) => { + const page = await transaction.findOne(MiPage, { + where: { + id: pageId, + }, + lock: { mode: 'for_no_key_update' }, + }); + + if (page == null) { + throw new IdentifiableError('66aefd3c-fdb2-4a71-85ae-cc18bea85d3f'); + } + if (page.userId !== me.id) { + throw new IdentifiableError('d0017699-8256-46f1-aed4-bc03bed73616'); + } + + if (body.name != null) { + await transaction.findBy(MiPage, { + id: Not(pageId), + userId: me.id, + name: body.name, + }).then(result => { + if (result.length > 0) { + throw new IdentifiableError('d05bfe24-24b6-4ea2-a3ec-87cc9bf4daa4'); + } + }); + } + + await transaction.update(MiPage, page.id, { + updatedAt: new Date(), + title: body.title, + name: body.name, + summary: body.summary === undefined ? page.summary : body.summary, + content: body.content, + variables: body.variables, + script: body.script, + alignCenter: body.alignCenter, + hideTitleWhenPinned: body.hideTitleWhenPinned, + font: body.font, + eyeCatchingImageId: body.eyeCatchingImage === undefined ? undefined : (body.eyeCatchingImage?.id ?? null), + }); + + console.log("page.content", page.content); + + if (body.content != null) { + const beforeReferencedNotes = this.collectReferencedNotes(page.content); + const afterReferencedNotes = this.collectReferencedNotes(body.content); + + const removedNotes = beforeReferencedNotes.filter(noteId => !afterReferencedNotes.includes(noteId)); + const addedNotes = afterReferencedNotes.filter(noteId => !beforeReferencedNotes.includes(noteId)); + + if (removedNotes.length > 0) { + await transaction.decrement(MiNote, { id: In(removedNotes) }, 'pageCount', 1); + } + if (addedNotes.length > 0) { + await transaction.increment(MiNote, { id: In(addedNotes) }, 'pageCount', 1); + } + } + }); + } + + @bindThis + public async delete(me: MiUser, pageId: MiPage['id']): Promise { + await this.db.transaction(async (transaction) => { + const page = await transaction.findOne(MiPage, { + where: { + id: pageId, + }, + lock: { mode: 'pessimistic_write' }, // same lock level as DELETE + }); + + if (page == null) { + throw new IdentifiableError('66aefd3c-fdb2-4a71-85ae-cc18bea85d3f'); + } + + if (!await this.roleService.isModerator(me) && page.userId !== me.id) { + throw new IdentifiableError('d0017699-8256-46f1-aed4-bc03bed73616'); + } + + await transaction.delete(MiPage, page.id); + + if (page.userId !== me.id) { + const user = await this.usersRepository.findOneByOrFail({ id: page.userId }); + this.moderationLogService.log(me, 'deletePage', { + pageId: page.id, + pageUserId: page.userId, + pageUserUsername: user.username, + page, + }); + } + + const referencedNotes = this.collectReferencedNotes(page.content); + if (referencedNotes.length > 0) { + await transaction.decrement(MiNote, { id: In(referencedNotes) }, 'pageCount', 1); + } + }); + } + + collectReferencedNotes(content: MiPage['content']): string[] { + const referencingNotes = new Set(); + const recursiveCollect = (content: unknown[]) => { + for (const contentElement of content) { + if (typeof contentElement === 'object' + && contentElement !== null + && 'type' in contentElement) { + if (contentElement.type === 'note' + && 'note' in contentElement + && typeof contentElement.note === 'string') { + referencingNotes.add(contentElement.note); + } + if (contentElement.type === 'section' + && 'children' in contentElement + && Array.isArray(contentElement.children)) { + recursiveCollect(contentElement.children); + } + } + } + }; + recursiveCollect(content); + return [...referencingNotes]; + } +} diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts index d398e83230..49f93ad108 100644 --- a/packages/backend/src/core/QueryService.ts +++ b/packages/backend/src/core/QueryService.ts @@ -360,7 +360,7 @@ export class QueryService { public generateSuspendedUserQueryForNote(q: SelectQueryBuilder, excludeAuthor?: boolean): void { if (excludeAuthor) { const brakets = (user: string) => new Brackets(qb => qb - .where(`note.${user}Id IS NULL`) + .where(`${user}.id IS NULL`) // そもそもreplyやrenoteではない、もしくはleftjoinなどでuserが存在しなかった場合を考慮 .orWhere(`user.id = ${user}.id`) .orWhere(`${user}.isSuspended = FALSE`)); q @@ -368,7 +368,7 @@ export class QueryService { .andWhere(brakets('renoteUser')); } else { const brakets = (user: string) => new Brackets(qb => qb - .where(`note.${user}Id IS NULL`) + .where(`${user}.id IS NULL`) // そもそもreplyやrenoteではない、もしくはleftjoinなどでuserが存在しなかった場合を考慮 .orWhere(`${user}.isSuspended = FALSE`)); q .andWhere('user.isSuspended = FALSE') diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 04bbc7e38a..2d0e7b5d83 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -17,6 +17,7 @@ import { bindThis } from '@/decorators.js'; import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js'; import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js'; import { type SystemWebhookPayload } from '@/core/SystemWebhookService.js'; +import type { Packed } from '@/misc/json-schema.js'; import { type UserWebhookPayload } from './UserWebhookService.js'; import type { DbJobData, @@ -39,7 +40,6 @@ import type { } from './QueueModule.js'; import type httpSignature from '@peertube/http-signature'; import type * as Bull from 'bullmq'; -import type { Packed } from '@/misc/json-schema.js'; export const QUEUE_TYPES = [ 'system', @@ -53,6 +53,37 @@ export const QUEUE_TYPES = [ 'systemWebhookDeliver', ] as const; +const REPEATABLE_SYSTEM_JOB_DEF = [{ + name: 'tickCharts', + pattern: '55 * * * *', +}, { + name: 'resyncCharts', + pattern: '0 0 * * *', +}, { + name: 'cleanCharts', + pattern: '0 0 * * *', +}, { + name: 'aggregateRetention', + pattern: '0 0 * * *', +}, { + name: 'clean', + pattern: '0 0 * * *', +}, { + name: 'checkExpiredMutings', + pattern: '*/5 * * * *', +}, { + name: 'bakeBufferedReactions', + pattern: '0 0 * * *', +}, { + name: 'checkModeratorsActivity', + // 毎時30分に起動 + pattern: '30 * * * *', +}, { + name: 'cleanRemoteNotes', + // 毎日午前4時に起動(最も人の少ない時間帯) + pattern: '0 4 * * *', +}]; + @Injectable() export class QueueService { constructor( @@ -69,61 +100,31 @@ export class QueueService { @Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue, @Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue, ) { - this.systemQueue.add('tickCharts', { - }, { - repeat: { pattern: '55 * * * *' }, - removeOnComplete: 10, - removeOnFail: 30, - }); + for (const def of REPEATABLE_SYSTEM_JOB_DEF) { + this.systemQueue.upsertJobScheduler(def.name, { + pattern: def.pattern, + immediately: false, + }, { + name: def.name, + opts: { + // 期限ではなくcountで設定したいが、ジョブごとではなくキュー全体でカウントされるため、高頻度で実行されるジョブによって低頻度で実行されるジョブのログが消えることになる + removeOnComplete: { + age: 3600 * 24 * 7, // keep up to 7 days + }, + removeOnFail: { + age: 3600 * 24 * 7, // keep up to 7 days + }, + }, + }); + } - this.systemQueue.add('resyncCharts', { - }, { - repeat: { pattern: '0 0 * * *' }, - removeOnComplete: 10, - removeOnFail: 30, - }); - - this.systemQueue.add('cleanCharts', { - }, { - repeat: { pattern: '0 0 * * *' }, - removeOnComplete: 10, - removeOnFail: 30, - }); - - this.systemQueue.add('aggregateRetention', { - }, { - repeat: { pattern: '0 0 * * *' }, - removeOnComplete: 10, - removeOnFail: 30, - }); - - this.systemQueue.add('clean', { - }, { - repeat: { pattern: '0 0 * * *' }, - removeOnComplete: 10, - removeOnFail: 30, - }); - - this.systemQueue.add('checkExpiredMutings', { - }, { - repeat: { pattern: '*/5 * * * *' }, - removeOnComplete: 10, - removeOnFail: 30, - }); - - this.systemQueue.add('bakeBufferedReactions', { - }, { - repeat: { pattern: '0 0 * * *' }, - removeOnComplete: 10, - removeOnFail: 30, - }); - - this.systemQueue.add('checkModeratorsActivity', { - }, { - // 毎時30分に起動 - repeat: { pattern: '30 * * * *' }, - removeOnComplete: 10, - removeOnFail: 30, + // 古いバージョンで作成され現在使われなくなったrepeatableジョブをクリーンアップ + this.systemQueue.getJobSchedulers().then(schedulers => { + for (const scheduler of schedulers) { + if (!REPEATABLE_SYSTEM_JOB_DEF.some(def => def.name === scheduler.key)) { + this.systemQueue.removeJobScheduler(scheduler.key); + } + } }); } @@ -755,8 +756,8 @@ export class QueueService { @bindThis public async queueRetryJob(queueType: typeof QUEUE_TYPES[number], jobId: string) { const queue = this.getQueue(queueType); - const job: Bull.Job | null = await queue.getJob(jobId); - if (job) { + const job = await queue.getJob(jobId); + if (job != null) { if (job.finishedOn != null) { await job.retry(); } else { @@ -768,8 +769,8 @@ export class QueueService { @bindThis public async queueRemoveJob(queueType: typeof QUEUE_TYPES[number], jobId: string) { const queue = this.getQueue(queueType); - const job: Bull.Job | null = await queue.getJob(jobId); - if (job) { + const job = await queue.getJob(jobId); + if (job != null) { await job.remove(); } } @@ -802,14 +803,21 @@ export class QueueService { @bindThis public async queueGetJob(queueType: typeof QUEUE_TYPES[number], jobId: string) { const queue = this.getQueue(queueType); - const job: Bull.Job | null = await queue.getJob(jobId); - if (job) { + const job = await queue.getJob(jobId); + if (job != null) { return this.packJobData(job); } else { throw new Error(`Job not found: ${jobId}`); } } + @bindThis + public async queueGetJobLogs(queueType: typeof QUEUE_TYPES[number], jobId: string) { + const queue = this.getQueue(queueType); + const result = await queue.getJobLogs(jobId); + return result.logs; + } + @bindThis public async queueGetJobs(queueType: typeof QUEUE_TYPES[number], jobTypes: JobType[], search?: string) { const RETURN_LIMIT = 100; diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 314f7e221a..9e1d9cc370 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -31,6 +31,7 @@ import { FanoutTimelineService } from '@/core/FanoutTimelineService.js'; import { NotificationService } from '@/core/NotificationService.js'; import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common'; +// misskey-js の rolePolicies と同期すべし export type RolePolicies = { gtlAvailable: boolean; ltlAvailable: boolean; @@ -43,6 +44,7 @@ export type RolePolicies = { canManageCustomEmojis: boolean; canManageAvatarDecorations: boolean; canSearchNotes: boolean; + canSearchUsers: boolean; canUseTranslator: boolean; canHideAds: boolean; driveCapacityMb: number; @@ -67,6 +69,7 @@ export type RolePolicies = { chatAvailability: 'available' | 'readonly' | 'unavailable'; uploadableFileTypes: string[]; noteDraftLimit: number; + watermarkAvailable: boolean; }; export const DEFAULT_POLICIES: RolePolicies = { @@ -81,6 +84,7 @@ export const DEFAULT_POLICIES: RolePolicies = { canManageCustomEmojis: false, canManageAvatarDecorations: false, canSearchNotes: false, + canSearchUsers: true, canUseTranslator: true, canHideAds: false, driveCapacityMb: 100, @@ -97,20 +101,22 @@ export const DEFAULT_POLICIES: RolePolicies = { userEachUserListsLimit: 50, rateLimitFactor: 1, avatarDecorationLimit: 1, - canImportAntennas: true, - canImportBlocking: true, - canImportFollowing: true, - canImportMuting: true, - canImportUserLists: true, + canImportAntennas: false, + canImportBlocking: false, + canImportFollowing: false, + canImportMuting: false, + canImportUserLists: false, chatAvailability: 'available', uploadableFileTypes: [ 'text/plain', + 'text/csv', 'application/json', 'image/*', 'video/*', 'audio/*', ], noteDraftLimit: 10, + watermarkAvailable: true, }; @Injectable() @@ -400,6 +406,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { canManageCustomEmojis: calc('canManageCustomEmojis', vs => vs.some(v => v === true)), canManageAvatarDecorations: calc('canManageAvatarDecorations', vs => vs.some(v => v === true)), canSearchNotes: calc('canSearchNotes', vs => vs.some(v => v === true)), + canSearchUsers: calc('canSearchUsers', vs => vs.some(v => v === true)), canUseTranslator: calc('canUseTranslator', vs => vs.some(v => v === true)), canHideAds: calc('canHideAds', vs => vs.some(v => v === true)), driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)), @@ -433,6 +440,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { return [...set]; }), noteDraftLimit: calc('noteDraftLimit', vs => Math.max(...vs)), + watermarkAvailable: calc('watermarkAvailable', vs => vs.some(v => v === true)), }; } diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index 643a5f525d..71dc718916 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -227,9 +227,9 @@ export class SearchService { if (opts.host) { if (opts.host === '.') { - query.andWhere('user.host IS NULL'); + query.andWhere('note.userHost IS NULL'); } else { - query.andWhere('user.host = :host', { host: opts.host }); + query.andWhere('note.userHost = :host', { host: opts.host }); } } diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index 5462cb0b13..a85da62b86 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -93,6 +93,11 @@ export class SignupService { if (isPreserved) { throw new Error('USED_USERNAME'); } + + const hasProhibitedWords = this.utilityService.isKeyWordIncluded(username.toLowerCase(), this.meta.prohibitedWordsForNameOfUser); + if (hasProhibitedWords) { + throw new Error('USED_USERNAME'); + } } const keyPair = await new Promise((res, rej) => diff --git a/packages/backend/src/core/WebhookTestService.ts b/packages/backend/src/core/WebhookTestService.ts index 9cf985b688..6714bda9a9 100644 --- a/packages/backend/src/core/WebhookTestService.ts +++ b/packages/backend/src/core/WebhookTestService.ts @@ -85,6 +85,7 @@ function generateDummyNote(override?: Partial): MiNote { renoteCount: 10, repliesCount: 5, clippedCount: 0, + pageCount: 0, reactions: {}, visibility: 'public', uri: null, @@ -243,7 +244,6 @@ export class WebhookTestService { case 'reaction': return; default: { - // eslint-disable-next-line @typescript-eslint/no-unused-vars const _exhaustiveAssertion: never = params.type; return; } @@ -326,7 +326,6 @@ export class WebhookTestService { break; } default: { - // eslint-disable-next-line @typescript-eslint/no-unused-vars const _exhaustiveAssertion: never = params.type; return; } @@ -411,7 +410,7 @@ export class WebhookTestService { name: user.name, username: user.username, host: user.host, - avatarUrl: user.avatarId == null ? null : user.avatarUrl, + avatarUrl: (user.avatarId == null ? null : user.avatarUrl) ?? '', avatarBlurhash: user.avatarId == null ? null : user.avatarBlurhash, avatarDecorations: user.avatarDecorations.map(it => ({ id: it.id, diff --git a/packages/backend/src/core/entities/ChatEntityService.ts b/packages/backend/src/core/entities/ChatEntityService.ts index 6bce2413fd..cfa983e766 100644 --- a/packages/backend/src/core/entities/ChatEntityService.ts +++ b/packages/backend/src/core/entities/ChatEntityService.ts @@ -54,12 +54,13 @@ export class ChatEntityService { const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src }); - const reactions: { user: Packed<'UserLite'>; reaction: string; }[] = []; + // userは削除されている可能性があるのでnull許容 + const reactions: { user: Packed<'UserLite'> | null; reaction: string; }[] = []; for (const record of message.reactions) { const [userId, reaction] = record.split('/'); reactions.push({ - user: packedUsers?.get(userId) ?? await this.userEntityService.pack(userId), + user: packedUsers?.get(userId) ?? await this.userEntityService.pack(userId).catch(() => null), reaction, }); } @@ -76,7 +77,7 @@ export class ChatEntityService { toRoom: message.toRoomId ? (packedRooms?.get(message.toRoomId) ?? await this.packRoom(message.toRoom ?? message.toRoomId, me)) : undefined, fileId: message.fileId, file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null, - reactions, + reactions: reactions.filter((r): r is { user: Packed<'UserLite'>; reaction: string; } => r.user != null), }; } @@ -108,6 +109,7 @@ export class ChatEntityService { } } + // TODO: packedUsersに削除されたユーザーもnullとして含める const [packedUsers, packedFiles, packedRooms] = await Promise.all([ this.userEntityService.packMany(users, me) .then(users => new Map(users.map(u => [u.id, u]))), @@ -183,12 +185,13 @@ export class ChatEntityService { const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src }); - const reactions: { user: Packed<'UserLite'>; reaction: string; }[] = []; + // userは削除されている可能性があるのでnull許容 + const reactions: { user: Packed<'UserLite'> | null; reaction: string; }[] = []; for (const record of message.reactions) { const [userId, reaction] = record.split('/'); reactions.push({ - user: packedUsers?.get(userId) ?? await this.userEntityService.pack(userId), + user: packedUsers?.get(userId) ?? await this.userEntityService.pack(userId).catch(() => null), reaction, }); } @@ -202,7 +205,7 @@ export class ChatEntityService { toRoomId: message.toRoomId!, fileId: message.fileId, file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null, - reactions, + reactions: reactions.filter((r): r is { user: Packed<'UserLite'>; reaction: string; } => r.user != null), }; } diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts index 02783dc450..2da614a120 100644 --- a/packages/backend/src/core/entities/MetaEntityService.ts +++ b/packages/backend/src/core/entities/MetaEntityService.ts @@ -109,6 +109,7 @@ export class MetaEntityService { maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, defaultLightTheme, defaultDarkTheme, + clientOptions: instance.clientOptions, ads: ads.map(ad => ({ id: ad.id, url: ad.url, @@ -116,6 +117,7 @@ export class MetaEntityService { ratio: ad.ratio, imageUrl: ad.imageUrl, dayOfWeek: ad.dayOfWeek, + isSensitive: ad.isSensitive ? true : undefined, })), notesPerOneAd: instance.notesPerOneAd, enableEmail: instance.enableEmail, diff --git a/packages/backend/src/core/entities/NoteDraftEntityService.ts b/packages/backend/src/core/entities/NoteDraftEntityService.ts index 26455029d5..3ef8cdaa12 100644 --- a/packages/backend/src/core/entities/NoteDraftEntityService.ts +++ b/packages/backend/src/core/entities/NoteDraftEntityService.ts @@ -5,6 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; +import { EntityNotFoundError } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { Packed } from '@/misc/json-schema.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; @@ -90,6 +91,17 @@ export class NoteDraftEntityService implements OnModuleInit { const packedFiles = options?._hint_?.packedFiles; const packedUsers = options?._hint_?.packedUsers; + async function nullIfEntityNotFound(promise: Promise): Promise { + try { + return await promise; + } catch (err) { + if (err instanceof EntityNotFoundError) { + return null; + } + throw err; + } + } + const packed: Packed<'NoteDraft'> = await awaitAll({ id: noteDraft.id, createdAt: this.idService.parse(noteDraft.id).date.toISOString(), @@ -117,15 +129,15 @@ export class NoteDraftEntityService implements OnModuleInit { } : undefined, ...(opts.detail ? { - reply: noteDraft.replyId ? this.noteEntityService.pack(noteDraft.replyId, me, { + reply: noteDraft.replyId ? nullIfEntityNotFound(this.noteEntityService.pack(noteDraft.replyId, me, { detail: false, skipHide: opts.skipHide, - }) : undefined, + })) : undefined, - renote: noteDraft.renoteId ? this.noteEntityService.pack(noteDraft.renoteId, me, { + renote: noteDraft.renoteId ? nullIfEntityNotFound(this.noteEntityService.pack(noteDraft.renoteId, me, { detail: true, skipHide: opts.skipHide, - }) : undefined, + })) : undefined, poll: noteDraft.hasPoll ? { choices: noteDraft.pollChoices, diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 92caad908c..6871ba2c72 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import { In } from 'typeorm'; +import { EntityNotFoundError, In } from 'typeorm'; import { ModuleRef } from '@nestjs/core'; import { DI } from '@/di-symbols.js'; import type { Packed } from '@/misc/json-schema.js'; @@ -46,6 +46,17 @@ function getAppearNoteIds(notes: MiNote[]): Set { return appearNoteIds; } +async function nullIfEntityNotFound(promise: Promise): Promise { + try { + return await promise; + } catch (err) { + if (err instanceof EntityNotFoundError) { + return null; + } + throw err; + } +} + @Injectable() export class NoteEntityService implements OnModuleInit { private userEntityService: UserEntityService; @@ -436,19 +447,21 @@ export class NoteEntityService implements OnModuleInit { ...(opts.detail ? { clippedCount: note.clippedCount, - reply: note.replyId ? this.pack(note.reply ?? note.replyId, me, { + // そもそもJOINしていない場合はundefined、JOINしたけど存在していなかった場合はnullで区別される + reply: (note.replyId && note.reply === null) ? null : note.replyId ? nullIfEntityNotFound(this.pack(note.reply ?? note.replyId, me, { detail: false, skipHide: opts.skipHide, withReactionAndUserPairCache: opts.withReactionAndUserPairCache, _hint_: options?._hint_, - }) : undefined, + })) : undefined, - renote: note.renoteId ? this.pack(note.renote ?? note.renoteId, me, { + // そもそもJOINしていない場合はundefined、JOINしたけど存在していなかった場合はnullで区別される + renote: (note.renoteId && note.renote === null) ? null : note.renoteId ? nullIfEntityNotFound(this.pack(note.renote ?? note.renoteId, me, { detail: true, skipHide: opts.skipHide, withReactionAndUserPairCache: opts.withReactionAndUserPairCache, _hint_: options?._hint_, - }) : undefined, + })) : undefined, poll: note.hasPoll ? this.populatePoll(note, meId) : undefined, @@ -591,7 +604,7 @@ export class NoteEntityService implements OnModuleInit { private findNoteOrFail(id: string): Promise { return this.notesRepository.findOneOrFail({ where: { id }, - relations: ['user'], + relations: ['user', 'renote', 'reply'], }); } diff --git a/packages/backend/src/core/entities/NoteReactionEntityService.ts b/packages/backend/src/core/entities/NoteReactionEntityService.ts index 46ec13704c..54ce4d472a 100644 --- a/packages/backend/src/core/entities/NoteReactionEntityService.ts +++ b/packages/backend/src/core/entities/NoteReactionEntityService.ts @@ -49,15 +49,12 @@ export class NoteReactionEntityService implements OnModuleInit { public async pack( src: MiNoteReaction['id'] | MiNoteReaction, me?: { id: MiUser['id'] } | null | undefined, - options?: { - withNote: boolean; - }, + options?: object, hints?: { packedUser?: Packed<'UserLite'> }, ): Promise> { const opts = Object.assign({ - withNote: false, }, options); const reaction = typeof src === 'object' ? src : await this.noteReactionsRepository.findOneByOrFail({ id: src }); @@ -67,9 +64,6 @@ export class NoteReactionEntityService implements OnModuleInit { createdAt: this.idService.parse(reaction.id).date.toISOString(), user: hints?.packedUser ?? await this.userEntityService.pack(reaction.user ?? reaction.userId, me), type: this.reactionService.convertLegacyReaction(reaction.reaction), - ...(opts.withNote ? { - note: await this.noteEntityService.pack(reaction.note ?? reaction.noteId, me), - } : {}), }; } @@ -77,16 +71,50 @@ export class NoteReactionEntityService implements OnModuleInit { public async packMany( reactions: MiNoteReaction[], me?: { id: MiUser['id'] } | null | undefined, - options?: { - withNote: boolean; - }, + options?: object, ): Promise[]> { const opts = Object.assign({ - withNote: false, }, options); const _users = reactions.map(({ user, userId }) => user ?? userId); const _userMap = await this.userEntityService.packMany(_users, me) .then(users => new Map(users.map(u => [u.id, u]))); return Promise.all(reactions.map(reaction => this.pack(reaction, me, opts, { packedUser: _userMap.get(reaction.userId) }))); } + + @bindThis + public async packWithNote( + src: MiNoteReaction['id'] | MiNoteReaction, + me?: { id: MiUser['id'] } | null | undefined, + options?: object, + hints?: { + packedUser?: Packed<'UserLite'> + }, + ): Promise> { + const opts = Object.assign({ + }, options); + + const reaction = typeof src === 'object' ? src : await this.noteReactionsRepository.findOneByOrFail({ id: src }); + + return { + id: reaction.id, + createdAt: this.idService.parse(reaction.id).date.toISOString(), + user: hints?.packedUser ?? await this.userEntityService.pack(reaction.user ?? reaction.userId, me), + type: this.reactionService.convertLegacyReaction(reaction.reaction), + note: await this.noteEntityService.pack(reaction.note ?? reaction.noteId, me), + }; + } + + @bindThis + public async packManyWithNote( + reactions: MiNoteReaction[], + me?: { id: MiUser['id'] } | null | undefined, + options?: object, + ): Promise[]> { + const opts = Object.assign({ + }, options); + const _users = reactions.map(({ user, userId }) => user ?? userId); + const _userMap = await this.userEntityService.packMany(_users, me) + .then(users => new Map(users.map(u => [u.id, u]))); + return Promise.all(reactions.map(reaction => this.packWithNote(reaction, me, opts, { packedUser: _userMap.get(reaction.userId) }))); + } } diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index d4769d24d4..47021359e1 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -471,8 +471,8 @@ export class UserEntityService implements OnModuleInit { (profile.followersVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount : null; - const isModerator = isMe && isDetailed ? this.roleService.isModerator(user) : null; - const isAdmin = isMe && isDetailed ? this.roleService.isAdministrator(user) : null; + const isModerator = isMe && isDetailed ? this.roleService.isModerator(user) : undefined; + const isAdmin = isMe && isDetailed ? this.roleService.isAdministrator(user) : undefined; const unreadAnnouncements = isMe && isDetailed ? (await this.announcementService.getUnreadAnnouncements(user)).map((announcement) => ({ createdAt: this.idService.parse(announcement.id).date.toISOString(), @@ -481,6 +481,7 @@ export class UserEntityService implements OnModuleInit { const notificationsInfo = isMe && isDetailed ? await this.getNotificationsInfo(user.id) : null; + // TODO: 例えば avatarUrl: true など間違った型を設定しても型エラーにならないのをどうにかする(ジェネリクス使わない方法で実装するしかなさそう?) const packed = { id: user.id, name: user.name, diff --git a/packages/backend/src/misc/emoji-regex.ts b/packages/backend/src/misc/emoji-regex.ts index 6d03b433ba..fabbdc335f 100644 --- a/packages/backend/src/misc/emoji-regex.ts +++ b/packages/backend/src/misc/emoji-regex.ts @@ -4,6 +4,6 @@ */ // taken from @twemoji/parser/dist/lib/regex.js -const twemojiRegex = /(?:\ud83d\udc68\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffc-\udfff]|\ud83e\uddd1\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffb\udffd-\udfff]|\ud83e\uddd1\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffb\udffc\udffe\udfff]|\ud83e\uddd1\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffb-\udffd\udfff]|\ud83e\uddd1\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffb-\udffe]|\ud83d\udc68\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffc-\udfff]|\ud83d\udc68\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffd-\udfff]|\ud83d\udc68\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffc\udffe\udfff]|\ud83d\udc68\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffd\udfff]|\ud83d\udc68\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffe]|\ud83d\udc69\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffc-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffc-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffd-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb\udffd-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffc\udffe\udfff]|\ud83d\udc69\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb\udffc\udffe\udfff]|\ud83d\udc69\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffd\udfff]|\ud83d\udc69\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb-\udffd\udfff]|\ud83d\udc69\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffe]|\ud83d\udc69\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb-\udffe]|\ud83e\uddd1\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffc-\udfff]|\ud83e\uddd1\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffb\udffd-\udfff]|\ud83e\uddd1\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffb\udffc\udffe\udfff]|\ud83e\uddd1\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffb-\udffd\udfff]|\ud83e\uddd1\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffb-\udffe]|\ud83e\uddd1\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68|\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d[\udc68\udc69]|\ud83e\udef1\ud83c\udffb\u200d\ud83e\udef2\ud83c[\udffc-\udfff]|\ud83e\udef1\ud83c\udffc\u200d\ud83e\udef2\ud83c[\udffb\udffd-\udfff]|\ud83e\udef1\ud83c\udffd\u200d\ud83e\udef2\ud83c[\udffb\udffc\udffe\udfff]|\ud83e\udef1\ud83c\udffe\u200d\ud83e\udef2\ud83c[\udffb-\udffd\udfff]|\ud83e\udef1\ud83c\udfff\u200d\ud83e\udef2\ud83c[\udffb-\udffe]|\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc68|\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d[\udc68\udc69]|\ud83e\uddd1\u200d\ud83e\udd1d\u200d\ud83e\uddd1|\ud83d\udc6b\ud83c[\udffb-\udfff]|\ud83d\udc6c\ud83c[\udffb-\udfff]|\ud83d\udc6d\ud83c[\udffb-\udfff]|\ud83d\udc8f\ud83c[\udffb-\udfff]|\ud83d\udc91\ud83c[\udffb-\udfff]|\ud83e\udd1d\ud83c[\udffb-\udfff]|\ud83d[\udc6b-\udc6d\udc8f\udc91]|\ud83e\udd1d)|(?:\ud83d[\udc68\udc69]|\ud83e\uddd1)(?:\ud83c[\udffb-\udfff])?\u200d(?:\u2695\ufe0f|\u2696\ufe0f|\u2708\ufe0f|\ud83c[\udf3e\udf73\udf7c\udf84\udf93\udfa4\udfa8\udfeb\udfed]|\ud83d[\udcbb\udcbc\udd27\udd2c\ude80\ude92]|\ud83e[\uddaf-\uddb3\uddbc\uddbd])|(?:\ud83c[\udfcb\udfcc]|\ud83d[\udd74\udd75]|\u26f9)((?:\ud83c[\udffb-\udfff]|\ufe0f)\u200d[\u2640\u2642]\ufe0f)|(?:\ud83c[\udfc3\udfc4\udfca]|\ud83d[\udc6e\udc70\udc71\udc73\udc77\udc81\udc82\udc86\udc87\ude45-\ude47\ude4b\ude4d\ude4e\udea3\udeb4-\udeb6]|\ud83e[\udd26\udd35\udd37-\udd39\udd3d\udd3e\uddb8\uddb9\uddcd-\uddcf\uddd4\uddd6-\udddd])(?:\ud83c[\udffb-\udfff])?\u200d[\u2640\u2642]\ufe0f|(?:\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83c\udff3\ufe0f\u200d\u26a7\ufe0f|\ud83c\udff3\ufe0f\u200d\ud83c\udf08|\ud83d\ude36\u200d\ud83c\udf2b\ufe0f|\u2764\ufe0f\u200d\ud83d\udd25|\u2764\ufe0f\u200d\ud83e\ude79|\ud83c\udff4\u200d\u2620\ufe0f|\ud83d\udc15\u200d\ud83e\uddba|\ud83d\udc3b\u200d\u2744\ufe0f|\ud83d\udc41\u200d\ud83d\udde8|\ud83d\udc68\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83d\udc6f\u200d\u2640\ufe0f|\ud83d\udc6f\u200d\u2642\ufe0f|\ud83d\ude2e\u200d\ud83d\udca8|\ud83d\ude35\u200d\ud83d\udcab|\ud83e\udd3c\u200d\u2640\ufe0f|\ud83e\udd3c\u200d\u2642\ufe0f|\ud83e\uddde\u200d\u2640\ufe0f|\ud83e\uddde\u200d\u2642\ufe0f|\ud83e\udddf\u200d\u2640\ufe0f|\ud83e\udddf\u200d\u2642\ufe0f|\ud83d\udc08\u200d\u2b1b|\ud83d\udc26\u200d\u2b1b)|[#*0-9]\ufe0f?\u20e3|(?:[©®\u2122\u265f]\ufe0f)|(?:\ud83c[\udc04\udd70\udd71\udd7e\udd7f\ude02\ude1a\ude2f\ude37\udf21\udf24-\udf2c\udf36\udf7d\udf96\udf97\udf99-\udf9b\udf9e\udf9f\udfcd\udfce\udfd4-\udfdf\udff3\udff5\udff7]|\ud83d[\udc3f\udc41\udcfd\udd49\udd4a\udd6f\udd70\udd73\udd76-\udd79\udd87\udd8a-\udd8d\udda5\udda8\uddb1\uddb2\uddbc\uddc2-\uddc4\uddd1-\uddd3\udddc-\uddde\udde1\udde3\udde8\uddef\uddf3\uddfa\udecb\udecd-\udecf\udee0-\udee5\udee9\udef0\udef3]|[\u203c\u2049\u2139\u2194-\u2199\u21a9\u21aa\u231a\u231b\u2328\u23cf\u23ed-\u23ef\u23f1\u23f2\u23f8-\u23fa\u24c2\u25aa\u25ab\u25b6\u25c0\u25fb-\u25fe\u2600-\u2604\u260e\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262a\u262e\u262f\u2638-\u263a\u2640\u2642\u2648-\u2653\u2660\u2663\u2665\u2666\u2668\u267b\u267f\u2692-\u2697\u2699\u269b\u269c\u26a0\u26a1\u26a7\u26aa\u26ab\u26b0\u26b1\u26bd\u26be\u26c4\u26c5\u26c8\u26cf\u26d1\u26d3\u26d4\u26e9\u26ea\u26f0-\u26f5\u26f8\u26fa\u26fd\u2702\u2708\u2709\u270f\u2712\u2714\u2716\u271d\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u2764\u27a1\u2934\u2935\u2b05-\u2b07\u2b1b\u2b1c\u2b50\u2b55\u3030\u303d\u3297\u3299])(?:\ufe0f|(?!\ufe0e))|(?:(?:\ud83c[\udfcb\udfcc]|\ud83d[\udd74\udd75\udd90]|\ud83e\udef0|[\u261d\u26f7\u26f9\u270c\u270d])(?:\ufe0f|(?!\ufe0e))|(?:\ud83c[\udf85\udfc2-\udfc4\udfc7\udfca]|\ud83d[\udc42\udc43\udc46-\udc50\udc66-\udc69\udc6e\udc70-\udc78\udc7c\udc81-\udc83\udc85-\udc87\udcaa\udd7a\udd95\udd96\ude45-\ude47\ude4b-\ude4f\udea3\udeb4-\udeb6\udec0\udecc]|\ud83e[\udd0c\udd0f\udd18-\udd1c\udd1e\udd1f\udd26\udd30-\udd39\udd3d\udd3e\udd77\uddb5\uddb6\uddb8\uddb9\uddbb\uddcd-\uddcf\uddd1-\udddd\udec3-\udec5\udef1-\udef8]|[\u270a\u270b]))(?:\ud83c[\udffb-\udfff])?|(?:\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f|\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc73\udb40\udc63\udb40\udc74\udb40\udc7f|\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f|\ud83c\udde6\ud83c[\udde8-\uddec\uddee\uddf1\uddf2\uddf4\uddf6-\uddfa\uddfc\uddfd\uddff]|\ud83c\udde7\ud83c[\udde6\udde7\udde9-\uddef\uddf1-\uddf4\uddf6-\uddf9\uddfb\uddfc\uddfe\uddff]|\ud83c\udde8\ud83c[\udde6\udde8\udde9\uddeb-\uddee\uddf0-\uddf5\uddf7\uddfa-\uddff]|\ud83c\udde9\ud83c[\uddea\uddec\uddef\uddf0\uddf2\uddf4\uddff]|\ud83c\uddea\ud83c[\udde6\udde8\uddea\uddec\udded\uddf7-\uddfa]|\ud83c\uddeb\ud83c[\uddee-\uddf0\uddf2\uddf4\uddf7]|\ud83c\uddec\ud83c[\udde6\udde7\udde9-\uddee\uddf1-\uddf3\uddf5-\uddfa\uddfc\uddfe]|\ud83c\udded\ud83c[\uddf0\uddf2\uddf3\uddf7\uddf9\uddfa]|\ud83c\uddee\ud83c[\udde8-\uddea\uddf1-\uddf4\uddf6-\uddf9]|\ud83c\uddef\ud83c[\uddea\uddf2\uddf4\uddf5]|\ud83c\uddf0\ud83c[\uddea\uddec-\uddee\uddf2\uddf3\uddf5\uddf7\uddfc\uddfe\uddff]|\ud83c\uddf1\ud83c[\udde6-\udde8\uddee\uddf0\uddf7-\uddfb\uddfe]|\ud83c\uddf2\ud83c[\udde6\udde8-\udded\uddf0-\uddff]|\ud83c\uddf3\ud83c[\udde6\udde8\uddea-\uddec\uddee\uddf1\uddf4\uddf5\uddf7\uddfa\uddff]|\ud83c\uddf4\ud83c\uddf2|\ud83c\uddf5\ud83c[\udde6\uddea-\udded\uddf0-\uddf3\uddf7-\uddf9\uddfc\uddfe]|\ud83c\uddf6\ud83c\udde6|\ud83c\uddf7\ud83c[\uddea\uddf4\uddf8\uddfa\uddfc]|\ud83c\uddf8\ud83c[\udde6-\uddea\uddec-\uddf4\uddf7-\uddf9\uddfb\uddfd-\uddff]|\ud83c\uddf9\ud83c[\udde6\udde8\udde9\uddeb-\udded\uddef-\uddf4\uddf7\uddf9\uddfb\uddfc\uddff]|\ud83c\uddfa\ud83c[\udde6\uddec\uddf2\uddf3\uddf8\uddfe\uddff]|\ud83c\uddfb\ud83c[\udde6\udde8\uddea\uddec\uddee\uddf3\uddfa]|\ud83c\uddfc\ud83c[\uddeb\uddf8]|\ud83c\uddfd\ud83c\uddf0|\ud83c\uddfe\ud83c[\uddea\uddf9]|\ud83c\uddff\ud83c[\udde6\uddf2\uddfc]|\ud83c[\udccf\udd8e\udd91-\udd9a\udde6-\uddff\ude01\ude32-\ude36\ude38-\ude3a\ude50\ude51\udf00-\udf20\udf2d-\udf35\udf37-\udf7c\udf7e-\udf84\udf86-\udf93\udfa0-\udfc1\udfc5\udfc6\udfc8\udfc9\udfcf-\udfd3\udfe0-\udff0\udff4\udff8-\udfff]|\ud83d[\udc00-\udc3e\udc40\udc44\udc45\udc51-\udc65\udc6a\udc6f\udc79-\udc7b\udc7d-\udc80\udc84\udc88-\udc8e\udc90\udc92-\udca9\udcab-\udcfc\udcff-\udd3d\udd4b-\udd4e\udd50-\udd67\udda4\uddfb-\ude44\ude48-\ude4a\ude80-\udea2\udea4-\udeb3\udeb7-\udebf\udec1-\udec5\uded0-\uded2\uded5-\uded7\udedc-\udedf\udeeb\udeec\udef4-\udefc\udfe0-\udfeb\udff0]|\ud83e[\udd0d\udd0e\udd10-\udd17\udd20-\udd25\udd27-\udd2f\udd3a\udd3c\udd3f-\udd45\udd47-\udd76\udd78-\uddb4\uddb7\uddba\uddbc-\uddcc\uddd0\uddde-\uddff\ude70-\ude7c\ude80-\ude88\ude90-\udebd\udebf-\udec2\udece-\udedb\udee0-\udee8]|[\u23e9-\u23ec\u23f0\u23f3\u267e\u26ce\u2705\u2728\u274c\u274e\u2753-\u2755\u2795-\u2797\u27b0\u27bf\ue50a])|\ufe0f/g; +const twemojiRegex = /(?:\ud83d\udc68\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffc-\udfff]|\ud83e\uddd1\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffb\udffd-\udfff]|\ud83e\uddd1\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffb\udffc\udffe\udfff]|\ud83e\uddd1\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffb-\udffd\udfff]|\ud83e\uddd1\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffb-\udffe]|\ud83d\udc68\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffc-\udfff]|\ud83d\udc68\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffd-\udfff]|\ud83d\udc68\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffc\udffe\udfff]|\ud83d\udc68\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffd\udfff]|\ud83d\udc68\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffe]|\ud83d\udc69\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffc-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffc-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffd-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb\udffd-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffc\udffe\udfff]|\ud83d\udc69\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb\udffc\udffe\udfff]|\ud83d\udc69\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffd\udfff]|\ud83d\udc69\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb-\udffd\udfff]|\ud83d\udc69\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffe]|\ud83d\udc69\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb-\udffe]|\ud83e\uddd1\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffc-\udfff]|\ud83e\uddd1\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffb\udffd-\udfff]|\ud83e\uddd1\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffb\udffc\udffe\udfff]|\ud83e\uddd1\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffb-\udffd\udfff]|\ud83e\uddd1\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffb-\udffe]|\ud83e\uddd1\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68|\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d[\udc68\udc69]|\ud83e\udef1\ud83c\udffb\u200d\ud83e\udef2\ud83c[\udffc-\udfff]|\ud83e\udef1\ud83c\udffc\u200d\ud83e\udef2\ud83c[\udffb\udffd-\udfff]|\ud83e\udef1\ud83c\udffd\u200d\ud83e\udef2\ud83c[\udffb\udffc\udffe\udfff]|\ud83e\udef1\ud83c\udffe\u200d\ud83e\udef2\ud83c[\udffb-\udffd\udfff]|\ud83e\udef1\ud83c\udfff\u200d\ud83e\udef2\ud83c[\udffb-\udffe]|\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc68|\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d[\udc68\udc69]|\ud83e\uddd1\u200d\ud83e\udd1d\u200d\ud83e\uddd1|\ud83d\udc6b\ud83c[\udffb-\udfff]|\ud83d\udc6c\ud83c[\udffb-\udfff]|\ud83d\udc6d\ud83c[\udffb-\udfff]|\ud83d\udc8f\ud83c[\udffb-\udfff]|\ud83d\udc91\ud83c[\udffb-\udfff]|\ud83e\udd1d\ud83c[\udffb-\udfff]|\ud83d[\udc6b-\udc6d\udc8f\udc91]|\ud83e\udd1d)|(?:\ud83d[\udc68\udc69]|\ud83e\uddd1)(?:\ud83c[\udffb-\udfff])?\u200d(?:\u2695\ufe0f|\u2696\ufe0f|\u2708\ufe0f|\ud83c[\udf3e\udf73\udf7c\udf84\udf93\udfa4\udfa8\udfeb\udfed]|\ud83d[\udcbb\udcbc\udd27\udd2c\ude80\ude92]|\ud83e[\uddaf-\uddb3\uddbc\uddbd])(?:\u200d\u27a1\ufe0f)?|(?:\ud83c[\udfcb\udfcc]|\ud83d[\udd74\udd75]|\u26f9)((?:\ud83c[\udffb-\udfff]|\ufe0f)\u200d[\u2640\u2642]\ufe0f(?:\u200d\u27a1\ufe0f)?)|(?:\ud83c[\udfc3\udfc4\udfca]|\ud83d[\udc6e\udc70\udc71\udc73\udc77\udc81\udc82\udc86\udc87\ude45-\ude47\ude4b\ude4d\ude4e\udea3\udeb4-\udeb6]|\ud83e[\udd26\udd35\udd37-\udd39\udd3d\udd3e\uddb8\uddb9\uddcd-\uddcf\uddd4\uddd6-\udddd])(?:\ud83c[\udffb-\udfff])?\u200d[\u2640\u2642]\ufe0f(?:\u200d\u27a1\ufe0f)?|(?:\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83e\uddd1\u200d\ud83e\uddd1\u200d\ud83e\uddd2\u200d\ud83e\uddd2|\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83e\uddd1\u200d\ud83e\uddd1\u200d\ud83e\uddd2|\ud83e\uddd1\u200d\ud83e\uddd2\u200d\ud83e\uddd2|\ud83c\udff3\ufe0f\u200d\u26a7\ufe0f|\ud83c\udff3\ufe0f\u200d\ud83c\udf08|\ud83d\ude36\u200d\ud83c\udf2b\ufe0f|\u26d3\ufe0f\u200d\ud83d\udca5|\u2764\ufe0f\u200d\ud83d\udd25|\u2764\ufe0f\u200d\ud83e\ude79|\ud83c\udf44\u200d\ud83d\udfeb|\ud83c\udf4b\u200d\ud83d\udfe9|\ud83c\udff4\u200d\u2620\ufe0f|\ud83d\udc15\u200d\ud83e\uddba|\ud83d\udc26\u200d\ud83d\udd25|\ud83d\udc3b\u200d\u2744\ufe0f|\ud83d\udc41\u200d\ud83d\udde8|\ud83d\udc68\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83d\udc6f\u200d\u2640\ufe0f|\ud83d\udc6f\u200d\u2642\ufe0f|\ud83d\ude2e\u200d\ud83d\udca8|\ud83d\ude35\u200d\ud83d\udcab|\ud83d\ude42\u200d\u2194\ufe0f|\ud83d\ude42\u200d\u2195\ufe0f|\ud83e\udd3c\u200d\u2640\ufe0f|\ud83e\udd3c\u200d\u2642\ufe0f|\ud83e\uddd1\u200d\ud83e\uddd2|\ud83e\uddde\u200d\u2640\ufe0f|\ud83e\uddde\u200d\u2642\ufe0f|\ud83e\udddf\u200d\u2640\ufe0f|\ud83e\udddf\u200d\u2642\ufe0f|\ud83d\udc08\u200d\u2b1b|\ud83d\udc26\u200d\u2b1b)|[#*0-9]\ufe0f?\u20e3|(?:[©®\u2122\u265f]\ufe0f)|(?:\ud83c[\udc04\udd70\udd71\udd7e\udd7f\ude02\ude1a\ude2f\ude37\udf21\udf24-\udf2c\udf36\udf7d\udf96\udf97\udf99-\udf9b\udf9e\udf9f\udfcd\udfce\udfd4-\udfdf\udff3\udff5\udff7]|\ud83d[\udc3f\udc41\udcfd\udd49\udd4a\udd6f\udd70\udd73\udd76-\udd79\udd87\udd8a-\udd8d\udda5\udda8\uddb1\uddb2\uddbc\uddc2-\uddc4\uddd1-\uddd3\udddc-\uddde\udde1\udde3\udde8\uddef\uddf3\uddfa\udecb\udecd-\udecf\udee0-\udee5\udee9\udef0\udef3]|[\u203c\u2049\u2139\u2194-\u2199\u21a9\u21aa\u231a\u231b\u2328\u23cf\u23ed-\u23ef\u23f1\u23f2\u23f8-\u23fa\u24c2\u25aa\u25ab\u25b6\u25c0\u25fb-\u25fe\u2600-\u2604\u260e\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262a\u262e\u262f\u2638-\u263a\u2640\u2642\u2648-\u2653\u2660\u2663\u2665\u2666\u2668\u267b\u267f\u2692-\u2697\u2699\u269b\u269c\u26a0\u26a1\u26a7\u26aa\u26ab\u26b0\u26b1\u26bd\u26be\u26c4\u26c5\u26c8\u26cf\u26d1\u26d3\u26d4\u26e9\u26ea\u26f0-\u26f5\u26f8\u26fa\u26fd\u2702\u2708\u2709\u270f\u2712\u2714\u2716\u271d\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u2764\u27a1\u2934\u2935\u2b05-\u2b07\u2b1b\u2b1c\u2b50\u2b55\u3030\u303d\u3297\u3299])(?:\ufe0f|(?!\ufe0e))|(?:(?:\ud83c[\udfcb\udfcc]|\ud83d[\udd74\udd75\udd90]|\ud83e\udef0|[\u261d\u26f7\u26f9\u270c\u270d])(?:\ufe0f|(?!\ufe0e))|(?:\ud83c\udfc3|\ud83d\udeb6|\ud83e\uddce)(?:\ud83c[\udffb-\udfff])?(?:\u200d\u27a1\ufe0f)?|(?:\ud83c[\udf85\udfc2\udfc4\udfc7\udfca]|\ud83d[\udc42\udc43\udc46-\udc50\udc66-\udc69\udc6e\udc70-\udc78\udc7c\udc81-\udc83\udc85-\udc87\udcaa\udd7a\udd95\udd96\ude45-\ude47\ude4b-\ude4f\udea3\udeb4\udeb5\udec0\udecc]|\ud83e[\udd0c\udd0f\udd18-\udd1c\udd1e\udd1f\udd26\udd30-\udd39\udd3d\udd3e\udd77\uddb5\uddb6\uddb8\uddb9\uddbb\uddcd\uddcf\uddd1-\udddd\udec3-\udec5\udef1-\udef8]|[\u270a\u270b]))(?:\ud83c[\udffb-\udfff])?|(?:\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f|\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc73\udb40\udc63\udb40\udc74\udb40\udc7f|\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f|\ud83c\udde6\ud83c[\udde8-\uddec\uddee\uddf1\uddf2\uddf4\uddf6-\uddfa\uddfc\uddfd\uddff]|\ud83c\udde7\ud83c[\udde6\udde7\udde9-\uddef\uddf1-\uddf4\uddf6-\uddf9\uddfb\uddfc\uddfe\uddff]|\ud83c\udde8\ud83c[\udde6\udde8\udde9\uddeb-\uddee\uddf0-\uddf7\uddfa-\uddff]|\ud83c\udde9\ud83c[\uddea\uddec\uddef\uddf0\uddf2\uddf4\uddff]|\ud83c\uddea\ud83c[\udde6\udde8\uddea\uddec\udded\uddf7-\uddfa]|\ud83c\uddeb\ud83c[\uddee-\uddf0\uddf2\uddf4\uddf7]|\ud83c\uddec\ud83c[\udde6\udde7\udde9-\uddee\uddf1-\uddf3\uddf5-\uddfa\uddfc\uddfe]|\ud83c\udded\ud83c[\uddf0\uddf2\uddf3\uddf7\uddf9\uddfa]|\ud83c\uddee\ud83c[\udde8-\uddea\uddf1-\uddf4\uddf6-\uddf9]|\ud83c\uddef\ud83c[\uddea\uddf2\uddf4\uddf5]|\ud83c\uddf0\ud83c[\uddea\uddec-\uddee\uddf2\uddf3\uddf5\uddf7\uddfc\uddfe\uddff]|\ud83c\uddf1\ud83c[\udde6-\udde8\uddee\uddf0\uddf7-\uddfb\uddfe]|\ud83c\uddf2\ud83c[\udde6\udde8-\udded\uddf0-\uddff]|\ud83c\uddf3\ud83c[\udde6\udde8\uddea-\uddec\uddee\uddf1\uddf4\uddf5\uddf7\uddfa\uddff]|\ud83c\uddf4\ud83c\uddf2|\ud83c\uddf5\ud83c[\udde6\uddea-\udded\uddf0-\uddf3\uddf7-\uddf9\uddfc\uddfe]|\ud83c\uddf6\ud83c\udde6|\ud83c\uddf7\ud83c[\uddea\uddf4\uddf8\uddfa\uddfc]|\ud83c\uddf8\ud83c[\udde6-\uddea\uddec-\uddf4\uddf7-\uddf9\uddfb\uddfd-\uddff]|\ud83c\uddf9\ud83c[\udde6\udde8\udde9\uddeb-\udded\uddef-\uddf4\uddf7\uddf9\uddfb\uddfc\uddff]|\ud83c\uddfa\ud83c[\udde6\uddec\uddf2\uddf3\uddf8\uddfe\uddff]|\ud83c\uddfb\ud83c[\udde6\udde8\uddea\uddec\uddee\uddf3\uddfa]|\ud83c\uddfc\ud83c[\uddeb\uddf8]|\ud83c\uddfd\ud83c\uddf0|\ud83c\uddfe\ud83c[\uddea\uddf9]|\ud83c\uddff\ud83c[\udde6\uddf2\uddfc]|\ud83c[\udccf\udd8e\udd91-\udd9a\udde6-\uddff\ude01\ude32-\ude36\ude38-\ude3a\ude50\ude51\udf00-\udf20\udf2d-\udf35\udf37-\udf7c\udf7e-\udf84\udf86-\udf93\udfa0-\udfc1\udfc5\udfc6\udfc8\udfc9\udfcf-\udfd3\udfe0-\udff0\udff4\udff8-\udfff]|\ud83d[\udc00-\udc3e\udc40\udc44\udc45\udc51-\udc65\udc6a\udc6f\udc79-\udc7b\udc7d-\udc80\udc84\udc88-\udc8e\udc90\udc92-\udca9\udcab-\udcfc\udcff-\udd3d\udd4b-\udd4e\udd50-\udd67\udda4\uddfb-\ude44\ude48-\ude4a\ude80-\udea2\udea4-\udeb3\udeb7-\udebf\udec1-\udec5\uded0-\uded2\uded5-\uded7\udedc-\udedf\udeeb\udeec\udef4-\udefc\udfe0-\udfeb\udff0]|\ud83e[\udd0d\udd0e\udd10-\udd17\udd20-\udd25\udd27-\udd2f\udd3a\udd3c\udd3f-\udd45\udd47-\udd76\udd78-\uddb4\uddb7\uddba\uddbc-\uddcc\uddd0\uddde-\uddff\ude70-\ude7c\ude80-\ude89\ude8f-\udec2\udec6\udece-\udedc\udedf-\udee9]|[\u23e9-\u23ec\u23f0\u23f3\u267e\u26ce\u2705\u2728\u274c\u274e\u2753-\u2755\u2795-\u2797\u27b0\u27bf\ue50a])|\ufe0f/g; export const emojiRegex = new RegExp(`(${twemojiRegex.source})`); diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts index ed47edff9b..ed7d5bfc3a 100644 --- a/packages/backend/src/misc/json-schema.ts +++ b/packages/backend/src/misc/json-schema.ts @@ -22,7 +22,7 @@ import { packedFollowingSchema } from '@/models/json-schema/following.js'; import { packedMutingSchema } from '@/models/json-schema/muting.js'; import { packedRenoteMutingSchema } from '@/models/json-schema/renote-muting.js'; import { packedBlockingSchema } from '@/models/json-schema/blocking.js'; -import { packedNoteReactionSchema } from '@/models/json-schema/note-reaction.js'; +import { packedNoteReactionSchema, packedNoteReactionWithNoteSchema } from '@/models/json-schema/note-reaction.js'; import { packedHashtagSchema } from '@/models/json-schema/hashtag.js'; import { packedInviteCodeSchema } from '@/models/json-schema/invite-code.js'; import { packedPageBlockSchema, packedPageSchema } from '@/models/json-schema/page.js'; @@ -65,6 +65,7 @@ import { packedMetaDetailedSchema, packedMetaLiteSchema, } from '@/models/json-schema/meta.js'; +import { packedUserWebhookSchema } from '@/models/json-schema/user-webhook.js'; import { packedSystemWebhookSchema } from '@/models/json-schema/system-webhook.js'; import { packedAbuseReportNotificationRecipientSchema } from '@/models/json-schema/abuse-report-notification-recipient.js'; import { packedChatMessageSchema, packedChatMessageLiteSchema, packedChatMessageLiteForRoomSchema, packedChatMessageLiteFor1on1Schema } from '@/models/json-schema/chat-message.js'; @@ -92,6 +93,7 @@ export const refs = { Note: packedNoteSchema, NoteDraft: packedNoteDraftSchema, NoteReaction: packedNoteReactionSchema, + NoteReactionWithNote: packedNoteReactionWithNoteSchema, NoteFavorite: packedNoteFavoriteSchema, Notification: packedNotificationSchema, DriveFile: packedDriveFileSchema, @@ -133,6 +135,7 @@ export const refs = { MetaLite: packedMetaLiteSchema, MetaDetailedOnly: packedMetaDetailedOnlySchema, MetaDetailed: packedMetaDetailedSchema, + UserWebhook: packedUserWebhookSchema, SystemWebhook: packedSystemWebhookSchema, AbuseReportNotificationRecipient: packedAbuseReportNotificationRecipientSchema, ChatMessage: packedChatMessageSchema, diff --git a/packages/backend/src/models/AbuseReportNotificationRecipient.ts b/packages/backend/src/models/AbuseReportNotificationRecipient.ts index fbff880afc..17ec6abed5 100644 --- a/packages/backend/src/models/AbuseReportNotificationRecipient.ts +++ b/packages/backend/src/models/AbuseReportNotificationRecipient.ts @@ -22,7 +22,7 @@ export class MiAbuseReportNotificationRecipient { /** * 有効かどうか. */ - @Index() + @Index('IDX_abuse_report_notification_recipient_isActive') @Column('boolean', { default: true, }) @@ -47,7 +47,7 @@ export class MiAbuseReportNotificationRecipient { /** * 通知方法. */ - @Index() + @Index('IDX_abuse_report_notification_recipient_method') @Column('varchar', { length: 64, }) @@ -56,10 +56,11 @@ export class MiAbuseReportNotificationRecipient { /** * 通知先のユーザID. */ - @Index() + @Index('IDX_abuse_report_notification_recipient_userId') @Column({ ...id(), nullable: true, + default: null, }) public userId: MiUser['id'] | null; @@ -75,17 +76,20 @@ export class MiAbuseReportNotificationRecipient { /** * 通知先のユーザプロフィール. */ - @ManyToOne(type => MiUserProfile, {}) + @ManyToOne(type => MiUserProfile, { + onDelete: 'CASCADE', + }) @JoinColumn({ name: 'userId', referencedColumnName: 'userId', foreignKeyConstraintName: 'FK_abuse_report_notification_recipient_userId2' }) public userProfile: MiUserProfile | null; /** * 通知先のシステムWebhookId. */ - @Index() + @Index('IDX_abuse_report_notification_recipient_systemWebhookId') @Column({ ...id(), nullable: true, + default: null, }) public systemWebhookId: string | null; @@ -95,6 +99,6 @@ export class MiAbuseReportNotificationRecipient { @ManyToOne(type => MiSystemWebhook, { onDelete: 'CASCADE', }) - @JoinColumn() + @JoinColumn({ name: 'systemWebhookId', referencedColumnName: 'id', foreignKeyConstraintName: 'FK_abuse_report_notification_recipient_systemWebhookId' }) public systemWebhook: MiSystemWebhook | null; } diff --git a/packages/backend/src/models/Ad.ts b/packages/backend/src/models/Ad.ts index 108e991c70..0d402fcbe8 100644 --- a/packages/backend/src/models/Ad.ts +++ b/packages/backend/src/models/Ad.ts @@ -54,10 +54,17 @@ export class MiAd { length: 8192, nullable: false, }) public memo: string; + @Column('integer', { default: 0, nullable: false, }) public dayOfWeek: number; + + @Column('boolean', { + default: false, + }) + public isSensitive: boolean; + constructor(data: Partial) { if (data == null) return; diff --git a/packages/backend/src/models/Emoji.ts b/packages/backend/src/models/Emoji.ts index d62b6e9f6f..8dff8fd153 100644 --- a/packages/backend/src/models/Emoji.ts +++ b/packages/backend/src/models/Emoji.ts @@ -8,6 +8,7 @@ import { id } from './util/id.js'; @Entity('emoji') @Index(['name', 'host'], { unique: true }) +@Index('IDX_EMOJI_ROLE_IDS', { synchronize: false }) // GIN for roleIdsThatCanBeUsedThisEmojiAsReaction in production export class MiEmoji { @PrimaryColumn(id()) public id: string; @@ -32,6 +33,7 @@ export class MiEmoji { @Column('varchar', { length: 128, nullable: true, }) + @Index('IDX_EMOJI_CATEGORY') public category: string | null; @Column('varchar', { diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 3ee6190d45..f8021a7a84 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -59,7 +59,7 @@ export class MiMeta { public maintainerEmail: string | null; @Column('boolean', { - default: false, + default: true, }) public disableRegistration: boolean; @@ -570,7 +570,7 @@ export class MiMeta { public bannedEmailDomains: string[]; @Column('varchar', { - length: 1024, array: true, default: '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }', + length: 1024, array: true, default: ['admin', 'administrator', 'root', 'system', 'maintainer', 'host', 'mod', 'moderator', 'owner', 'superuser', 'staff', 'auth', 'i', 'me', 'everyone', 'all', 'mention', 'mentions', 'example', 'user', 'users', 'account', 'accounts', 'official', 'help', 'helps', 'support', 'supports', 'info', 'information', 'informations', 'announce', 'announces', 'announcement', 'announcements', 'notice', 'notification', 'notifications', 'dev', 'developer', 'developers', 'tech', 'misskey'], }) public preservedUsernames: string[]; @@ -635,7 +635,7 @@ export class MiMeta { public urlPreviewMaximumContentLength: number; @Column('boolean', { - default: true, + default: false, }) public urlPreviewRequireContentLength: boolean; @@ -648,12 +648,13 @@ export class MiMeta { @Column('varchar', { length: 1024, nullable: true, + default: null, }) public urlPreviewUserAgent: string | null; @Column('varchar', { length: 128, - default: 'all', + default: 'none', }) public federation: 'all' | 'specified' | 'none'; @@ -700,6 +701,26 @@ export class MiMeta { default: true, }) public allowExternalApRedirect: boolean; + + @Column('boolean', { + default: false, + }) + public enableRemoteNotesCleaning: boolean; + + @Column('integer', { + default: 60, // minutes + }) + public remoteNotesCleaningMaxProcessingDurationInMinutes: number; + + @Column('integer', { + default: 90, // days + }) + public remoteNotesCleaningExpiryDaysForEachNotes: number; + + @Column('jsonb', { + default: { }, + }) + public clientOptions: Record; } export type SoftwareSuspension = { diff --git a/packages/backend/src/models/Note.ts b/packages/backend/src/models/Note.ts index 0560ee17c0..26d5c1d535 100644 --- a/packages/backend/src/models/Note.ts +++ b/packages/backend/src/models/Note.ts @@ -20,7 +20,8 @@ import type { MiDriveFile } from './DriveFile.js'; // You should not use `@Index({ concurrent: true })` decorator because database initialization for test will fail // because it will always run CREATE INDEX in transaction based on decorators. // Not appending `{ concurrent: true }` to `@Index` will not cause any problem in production, -@Index(['userId', 'id']) + +@Index(['userId', 'id']) // Note: this index is ("userId", "id" DESC) in production, but not in test. @Entity('note') export class MiNote { @PrimaryColumn(id()) @@ -35,7 +36,7 @@ export class MiNote { public replyId: MiNote['id'] | null; @ManyToOne(type => MiNote, { - onDelete: 'CASCADE', + createForeignKeyConstraints: false, }) @JoinColumn() public reply: MiNote | null; @@ -49,7 +50,7 @@ export class MiNote { public renoteId: MiNote['id'] | null; @ManyToOne(type => MiNote, { - onDelete: 'CASCADE', + createForeignKeyConstraints: false, }) @JoinColumn() public renote: MiNote | null; @@ -113,6 +114,13 @@ export class MiNote { }) public clippedCount: number; + // The number of note page blocks referencing this note. + // This column is used by Remote Note Cleaning and manually updated rather than automatically with triggers. + @Column('smallint', { + default: 0, + }) + public pageCount: number; + @Column('jsonb', { default: {}, }) diff --git a/packages/backend/src/models/NoteDraft.ts b/packages/backend/src/models/NoteDraft.ts index edae254bb8..6483748bc2 100644 --- a/packages/backend/src/models/NoteDraft.ts +++ b/packages/backend/src/models/NoteDraft.ts @@ -12,11 +12,13 @@ import { MiNote } from './Note.js'; import type { MiDriveFile } from './DriveFile.js'; @Entity('note_draft') +@Index('IDX_NOTE_DRAFT_FILE_IDS', { synchronize: false }) // GIN for fileIds in production +@Index('IDX_NOTE_DRAFT_VISIBLE_USER_IDS', { synchronize: false }) // GIN for visibleUserIds in production export class MiNoteDraft { @PrimaryColumn(id()) public id: string; - @Index() + @Index('IDX_NOTE_DRAFT_REPLY_ID') @Column({ ...id(), nullable: true, @@ -24,13 +26,14 @@ export class MiNoteDraft { }) public replyId: MiNote['id'] | null; + // There is a possibility that replyId is not null but reply is null when the reply note is deleted. @ManyToOne(type => MiNote, { - onDelete: 'CASCADE', + createForeignKeyConstraints: false, }) @JoinColumn() public reply: MiNote | null; - @Index() + @Index('IDX_NOTE_DRAFT_RENOTE_ID') @Column({ ...id(), nullable: true, @@ -38,8 +41,9 @@ export class MiNoteDraft { }) public renoteId: MiNote['id'] | null; + // There is a possibility that renoteId is not null but renote is null when the renote note is deleted. @ManyToOne(type => MiNote, { - onDelete: 'CASCADE', + createForeignKeyConstraints: false, }) @JoinColumn() public renote: MiNote | null; @@ -55,7 +59,7 @@ export class MiNoteDraft { }) public cw: string | null; - @Index() + @Index('IDX_NOTE_DRAFT_USER_ID') @Column({ ...id(), comment: 'The ID of author.', @@ -106,7 +110,7 @@ export class MiNoteDraft { }) public hashtag: string | null; - @Index() + @Index('IDX_NOTE_DRAFT_CHANNEL_ID') @Column({ ...id(), nullable: true, @@ -114,8 +118,10 @@ export class MiNoteDraft { }) public channelId: MiChannel['id'] | null; + // There is a possibility that channelId is not null but channel is null when the channel is deleted. + // (deleting channel is not implemented so it's not happening now but may happen in the future) @ManyToOne(type => MiChannel, { - onDelete: 'CASCADE', + createForeignKeyConstraints: false, }) @JoinColumn() public channel: MiChannel | null; diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts index 5764a307b0..0b4eeb3455 100644 --- a/packages/backend/src/models/Notification.ts +++ b/packages/backend/src/models/Notification.ts @@ -10,6 +10,7 @@ import { MiAccessToken } from './AccessToken.js'; import { MiRole } from './Role.js'; import { MiDriveFile } from './DriveFile.js'; +// misskey-js の notificationTypes と同期すべし export type MiNotification = { type: 'note'; id: string; diff --git a/packages/backend/src/models/Page.ts b/packages/backend/src/models/Page.ts index 0b59e7a92c..d112a66c04 100644 --- a/packages/backend/src/models/Page.ts +++ b/packages/backend/src/models/Page.ts @@ -69,7 +69,7 @@ export class MiPage { public eyeCatchingImageId: MiDriveFile['id'] | null; @ManyToOne(type => MiDriveFile, { - onDelete: 'CASCADE', + onDelete: 'SET NULL', }) @JoinColumn() public eyeCatchingImage: MiDriveFile | null; diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts index baf4eefdf1..a6e9edcf5f 100644 --- a/packages/backend/src/models/User.ts +++ b/packages/backend/src/models/User.ts @@ -120,7 +120,7 @@ export class MiUser { // avatarId が null になったとしてもこれが null でない可能性があるため、このフィールドを使うときは avatarId の non-null チェックをすること @Column('varchar', { - length: 512, nullable: true, + length: 1024, nullable: true, }) public avatarUrl: string | null; diff --git a/packages/backend/src/models/UserProfile.ts b/packages/backend/src/models/UserProfile.ts index c4c1fa5ec9..501b539210 100644 --- a/packages/backend/src/models/UserProfile.ts +++ b/packages/backend/src/models/UserProfile.ts @@ -29,7 +29,7 @@ export class MiUserProfile { }) public location: string | null; - @Index() + // Note: There's index named IDX_de22cd2b445eee31ae51cdbe99 for SUBSTR("birthday", 6, 5) @Column('char', { length: 10, nullable: true, comment: 'The birthday (YYYY-MM-DD) of the User.', diff --git a/packages/backend/src/models/json-schema/ad.ts b/packages/backend/src/models/json-schema/ad.ts index b01b39a38b..d88ac23894 100644 --- a/packages/backend/src/models/json-schema/ad.ts +++ b/packages/backend/src/models/json-schema/ad.ts @@ -60,5 +60,10 @@ export const packedAdSchema = { optional: false, nullable: false, }, + isSensitive: { + type: 'boolean', + optional: false, + nullable: false, + }, }, } as const; diff --git a/packages/backend/src/models/json-schema/flash.ts b/packages/backend/src/models/json-schema/flash.ts index 42b2172409..d50200e6e9 100644 --- a/packages/backend/src/models/json-schema/flash.ts +++ b/packages/backend/src/models/json-schema/flash.ts @@ -51,7 +51,7 @@ export const packedFlashSchema = { }, likedCount: { type: 'number', - optional: false, nullable: true, + optional: false, nullable: false, }, isLiked: { type: 'boolean', diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts index 2cd7620af0..a0e7d490b3 100644 --- a/packages/backend/src/models/json-schema/meta.ts +++ b/packages/backend/src/models/json-schema/meta.ts @@ -71,6 +71,10 @@ export const packedMetaLiteSchema = { type: 'string', optional: false, nullable: true, }, + clientOptions: { + type: 'object', + optional: false, nullable: false, + }, disableRegistration: { type: 'boolean', optional: false, nullable: false, @@ -191,6 +195,10 @@ export const packedMetaLiteSchema = { type: 'integer', optional: false, nullable: false, }, + isSensitive: { + type: 'boolean', + optional: true, nullable: false, + }, }, }, }, diff --git a/packages/backend/src/models/json-schema/note-draft.ts b/packages/backend/src/models/json-schema/note-draft.ts index 20c56d0795..504b263a6d 100644 --- a/packages/backend/src/models/json-schema/note-draft.ts +++ b/packages/backend/src/models/json-schema/note-draft.ts @@ -51,11 +51,13 @@ export const packedNoteDraftSchema = { type: 'object', optional: true, nullable: true, ref: 'Note', + description: 'The reply target note contents if exists. If the reply target has been deleted since the draft was created, this will be null while replyId is not null.', }, renote: { type: 'object', optional: true, nullable: true, ref: 'Note', + description: 'The renote target note contents if exists. If the renote target has been deleted since the draft was created, this will be null while renoteId is not null.', }, visibility: { type: 'string', diff --git a/packages/backend/src/models/json-schema/note-reaction.ts b/packages/backend/src/models/json-schema/note-reaction.ts index 95658ace1f..04c9f34232 100644 --- a/packages/backend/src/models/json-schema/note-reaction.ts +++ b/packages/backend/src/models/json-schema/note-reaction.ts @@ -10,7 +10,6 @@ export const packedNoteReactionSchema = { type: 'string', optional: false, nullable: false, format: 'id', - example: 'xxxxxxxxxx', }, createdAt: { type: 'string', @@ -28,3 +27,33 @@ export const packedNoteReactionSchema = { }, }, } as const; + +export const packedNoteReactionWithNoteSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + user: { + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', + }, + type: { + type: 'string', + optional: false, nullable: false, + }, + note: { + type: 'object', + optional: false, nullable: false, + ref: 'Note', + }, + }, +} as const; diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts index a3f679129d..0b9234cb81 100644 --- a/packages/backend/src/models/json-schema/role.ts +++ b/packages/backend/src/models/json-schema/role.ts @@ -212,6 +212,10 @@ export const packedRolePoliciesSchema = { type: 'boolean', optional: false, nullable: false, }, + canSearchUsers: { + type: 'boolean', + optional: false, nullable: false, + }, canUseTranslator: { type: 'boolean', optional: false, nullable: false, @@ -313,6 +317,10 @@ export const packedRolePoliciesSchema = { type: 'integer', optional: false, nullable: false, }, + watermarkAvailable: { + type: 'boolean', + optional: false, nullable: false, + }, }, } as const; diff --git a/packages/backend/src/models/json-schema/user-webhook.ts b/packages/backend/src/models/json-schema/user-webhook.ts new file mode 100644 index 0000000000..8ea0991716 --- /dev/null +++ b/packages/backend/src/models/json-schema/user-webhook.ts @@ -0,0 +1,55 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { webhookEventTypes } from '@/models/Webhook.js'; + +export const packedUserWebhookSchema = { + type: 'object', + properties: { + id: { + type: 'string', + format: 'id', + optional: false, nullable: false, + }, + userId: { + type: 'string', + format: 'id', + optional: false, nullable: false, + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + on: { + type: 'array', + items: { + type: 'string', + optional: false, nullable: false, + enum: webhookEventTypes, + }, + }, + url: { + type: 'string', + optional: false, nullable: false, + }, + secret: { + type: 'string', + optional: false, nullable: false, + }, + active: { + type: 'boolean', + optional: false, nullable: false, + }, + latestSentAt: { + type: 'string', + format: 'date-time', + optional: false, nullable: true, + }, + latestStatus: { + type: 'integer', + optional: false, nullable: true, + }, + }, +} as const; diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index 2b5f706ff9..c507d8d5c6 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -65,7 +65,7 @@ export const packedUserLiteSchema = { avatarUrl: { type: 'string', format: 'url', - nullable: true, optional: false, + nullable: false, optional: false, }, avatarBlurhash: { type: 'string', @@ -465,11 +465,11 @@ export const packedMeDetailedOnlySchema = { }, isModerator: { type: 'boolean', - nullable: true, optional: false, + nullable: false, optional: false, }, isAdmin: { type: 'boolean', - nullable: true, optional: false, + nullable: false, optional: false, }, injectFeaturedNote: { type: 'boolean', @@ -591,7 +591,7 @@ export const packedMeDetailedOnlySchema = { }, mutedInstances: { type: 'array', - nullable: true, optional: false, + nullable: false, optional: false, items: { type: 'string', nullable: false, optional: false, diff --git a/packages/backend/src/queue/QueueProcessorModule.ts b/packages/backend/src/queue/QueueProcessorModule.ts index 9044285bf6..e01414cd53 100644 --- a/packages/backend/src/queue/QueueProcessorModule.ts +++ b/packages/backend/src/queue/QueueProcessorModule.ts @@ -6,7 +6,6 @@ import { Module } from '@nestjs/common'; import { CoreModule } from '@/core/CoreModule.js'; import { GlobalModule } from '@/GlobalModule.js'; -import { CheckModeratorsActivityProcessorService } from '@/queue/processors/CheckModeratorsActivityProcessorService.js'; import { QueueLoggerService } from './QueueLoggerService.js'; import { QueueProcessorService } from './QueueProcessorService.js'; import { DeliverProcessorService } from './processors/DeliverProcessorService.js'; @@ -18,6 +17,8 @@ import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMu import { BakeBufferedReactionsProcessorService } from './processors/BakeBufferedReactionsProcessorService.js'; import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js'; import { CleanProcessorService } from './processors/CleanProcessorService.js'; +import { CheckModeratorsActivityProcessorService } from './processors/CheckModeratorsActivityProcessorService.js'; +import { CleanRemoteNotesProcessorService } from './processors/CleanRemoteNotesProcessorService.js'; import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js'; import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js'; import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js'; @@ -83,6 +84,7 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor AggregateRetentionProcessorService, CheckExpiredMutingsProcessorService, CheckModeratorsActivityProcessorService, + CleanRemoteNotesProcessorService, QueueProcessorService, ], exports: [ diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index c98ebcdcd9..7b64182754 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -43,6 +43,7 @@ import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMu import { BakeBufferedReactionsProcessorService } from './processors/BakeBufferedReactionsProcessorService.js'; import { CleanProcessorService } from './processors/CleanProcessorService.js'; import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js'; +import { CleanRemoteNotesProcessorService } from './processors/CleanRemoteNotesProcessorService.js'; import { QueueLoggerService } from './QueueLoggerService.js'; import { QUEUE, baseWorkerOptions } from './const.js'; @@ -123,6 +124,7 @@ export class QueueProcessorService implements OnApplicationShutdown { private bakeBufferedReactionsProcessorService: BakeBufferedReactionsProcessorService, private checkModeratorsActivityProcessorService: CheckModeratorsActivityProcessorService, private cleanProcessorService: CleanProcessorService, + private cleanRemoteNotesProcessorService: CleanRemoteNotesProcessorService, ) { this.logger = this.queueLoggerService.logger; @@ -164,6 +166,7 @@ export class QueueProcessorService implements OnApplicationShutdown { case 'bakeBufferedReactions': return this.bakeBufferedReactionsProcessorService.process(); case 'checkModeratorsActivity': return this.checkModeratorsActivityProcessorService.process(); case 'clean': return this.cleanProcessorService.process(); + case 'cleanRemoteNotes': return this.cleanRemoteNotesProcessorService.process(job); default: throw new Error(`unrecognized job type ${job.name} for system`); } }; diff --git a/packages/backend/src/queue/processors/CleanRemoteNotesProcessorService.ts b/packages/backend/src/queue/processors/CleanRemoteNotesProcessorService.ts new file mode 100644 index 0000000000..36c34c753c --- /dev/null +++ b/packages/backend/src/queue/processors/CleanRemoteNotesProcessorService.ts @@ -0,0 +1,289 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { setTimeout } from 'node:timers/promises'; +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource, IsNull, LessThan, QueryFailedError, Not } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { MiMeta, MiNote, NotesRepository } from '@/models/_.js'; +import type Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; +import { QueueLoggerService } from '../QueueLoggerService.js'; +import type * as Bull from 'bullmq'; + +@Injectable() +export class CleanRemoteNotesProcessorService { + private logger: Logger; + + constructor( + @Inject(DI.meta) + private meta: MiMeta, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.db) + private db: DataSource, + + private idService: IdService, + private queueLoggerService: QueueLoggerService, + ) { + this.logger = this.queueLoggerService.logger.createSubLogger('clean-remote-notes'); + } + + @bindThis + private computeProgress(minId: string, maxId: string, cursorLeft: string) { + const minTs = this.idService.parse(minId).date.getTime(); + const maxTs = this.idService.parse(maxId).date.getTime(); + const cursorTs = this.idService.parse(cursorLeft).date.getTime(); + + return ((cursorTs - minTs) / (maxTs - minTs)) * 100; + } + + @bindThis + public async process(job: Bull.Job>): Promise<{ + deletedCount: number; + oldest: number | null; + newest: number | null; + skipped: boolean; + transientErrors: number; + }> { + if (!this.meta.enableRemoteNotesCleaning) { + this.logger.info('Remote notes cleaning is disabled, skipping...'); + return { + deletedCount: 0, + oldest: null, + newest: null, + skipped: true, + transientErrors: 0, + }; + } + + this.logger.info('cleaning remote notes...'); + + const maxDuration = this.meta.remoteNotesCleaningMaxProcessingDurationInMinutes * 60 * 1000; // Convert minutes to milliseconds + const startAt = Date.now(); + + //#region queries + // The date limit for the newest note to be considered for deletion. + // All notes newer than this limit will always be retained. + const newestLimit = this.idService.gen(Date.now() - (1000 * 60 * 60 * 24 * this.meta.remoteNotesCleaningExpiryDaysForEachNotes)); + + // The condition for removing the notes. + // The note must be: + // - old enough (older than the newestLimit) + // - a remote note (userHost is not null). + // - not have clipped + // - not have pinned on the user profile + // - not has been favorite by any user + const removalCriteria = [ + 'note."id" < :newestLimit', + 'note."clippedCount" = 0', + 'note."pageCount" = 0', + 'note."userHost" IS NOT NULL', + 'NOT EXISTS (SELECT 1 FROM user_note_pining WHERE "noteId" = note."id")', + 'NOT EXISTS (SELECT 1 FROM note_favorite WHERE "noteId" = note."id")', + 'NOT EXISTS (SELECT 1 FROM note_reaction INNER JOIN "user" ON note_reaction."userId" = "user".id WHERE note_reaction."noteId" = note."id" AND "user"."host" IS NULL)', + ].join(' AND '); + + const minId = (await this.notesRepository.createQueryBuilder('note') + .select('MIN(note.id)', 'minId') + .where({ + id: LessThan(newestLimit), + userHost: Not(IsNull()), + replyId: IsNull(), + renoteId: IsNull(), + }) + .getRawOne<{ minId?: MiNote['id'] }>())?.minId; + + if (!minId) { + this.logger.info('No notes can possibly be deleted, skipping...'); + return { + deletedCount: 0, + oldest: null, + newest: null, + skipped: false, + transientErrors: 0, + }; + } + + // start with a conservative limit and adjust it based on the query duration + const minimumLimit = 10; + let currentLimit = 100; + let cursorLeft = '0'; + + const candidateNotesCteName = 'candidate_notes'; + + // tree walk down all root notes, short-circuit when the first unremovable note is found + const candidateNotesQueryBase = this.notesRepository.createQueryBuilder('note') + .select('note."id"', 'id') + .addSelect('note."replyId"', 'replyId') + .addSelect('note."renoteId"', 'renoteId') + .addSelect('note."id"', 'rootId') + .addSelect('TRUE', 'isRemovable') + .addSelect('TRUE', 'isBase') + .where('note."id" > :cursorLeft') + .andWhere(removalCriteria) + .andWhere({ replyId: IsNull(), renoteId: IsNull() }); + + const candidateNotesQueryInductive = this.notesRepository.createQueryBuilder('note') + .select('note.id', 'id') + .addSelect('note."replyId"', 'replyId') + .addSelect('note."renoteId"', 'renoteId') + .addSelect('parent."rootId"', 'rootId') + .addSelect(removalCriteria, 'isRemovable') + .addSelect('FALSE', 'isBase') + .innerJoin(candidateNotesCteName, 'parent', 'parent."id" = note."replyId" OR parent."id" = note."renoteId"') + .where('parent."isRemovable" = TRUE'); + + // A note tree can be deleted if there are no unremovable rows with the same rootId. + // + // `candidate_notes` will have the following structure after recursive query (some columns omitted): + // After performing a LEFT JOIN with `candidate_notes` as `unremovable`, + // the note tree containing unremovable notes will be anti-joined. + // For removable rows, the `unremovable` columns will have `NULL` values. + // | id | rootId | isRemovable | + // |-----|--------|-------------| + // | aaa | aaa | TRUE | + // | bbb | aaa | FALSE | + // | ccc | aaa | FALSE | + // | ddd | ddd | TRUE | + // | eee | ddd | TRUE | + // | fff | fff | TRUE | + // | ggg | ggg | FALSE | + // + const candidateNotesQuery = this.db.createQueryBuilder() + .select(`"${candidateNotesCteName}"."id"`, 'id') + .addSelect('unremovable."id" IS NULL', 'isRemovable') + .addSelect(`BOOL_OR("${candidateNotesCteName}"."isBase")`, 'isBase') + .addCommonTableExpression( + `((SELECT "base".* FROM (${candidateNotesQueryBase.orderBy('note.id', 'ASC').limit(currentLimit).getQuery()}) AS "base") UNION ${candidateNotesQueryInductive.getQuery()})`, + candidateNotesCteName, + { recursive: true }, + ) + .from(candidateNotesCteName, candidateNotesCteName) + .leftJoin(candidateNotesCteName, 'unremovable', `unremovable."rootId" = "${candidateNotesCteName}"."rootId" AND unremovable."isRemovable" = FALSE`) + .groupBy(`"${candidateNotesCteName}"."id"`) + .addGroupBy('unremovable."id" IS NULL'); + + const stats = { + deletedCount: 0, + oldest: null as number | null, + newest: null as number | null, + }; + + let lowThroughputWarned = false; + let transientErrors = 0; + for (;;) { + //#region check time + const batchBeginAt = Date.now(); + + const elapsed = batchBeginAt - startAt; + + const progress = this.computeProgress(minId, newestLimit, cursorLeft > minId ? cursorLeft : minId); + + if (elapsed >= maxDuration) { + job.log(`Reached maximum duration of ${maxDuration}ms, stopping... (last cursor: ${cursorLeft}, final progress ${progress}%)`); + job.updateProgress(100); + break; + } + + const wallClockUsage = elapsed / maxDuration; + if (wallClockUsage > 0.5 && progress < 50 && !lowThroughputWarned) { + const msg = `Not projected to finish in time! (wall clock usage ${wallClockUsage * 100}% at ${progress}%, current limit ${currentLimit})`; + this.logger.warn(msg); + job.log(msg); + lowThroughputWarned = true; + } + job.updateProgress(progress); + //#endregion + + const queryBegin = performance.now(); + let noteIds = null; + + try { + noteIds = await candidateNotesQuery.setParameters( + { newestLimit, cursorLeft }, + ).getRawMany<{ id: MiNote['id'], isRemovable: boolean, isBase: boolean }>(); + } catch (e) { + if (currentLimit > minimumLimit && e instanceof QueryFailedError && e.driverError?.code === '57014') { + // Statement timeout (maybe suddenly hit a large note tree), reduce the limit and try again + // continuous failures will eventually converge to currentLimit == minimumLimit and then throw + currentLimit = Math.max(minimumLimit, Math.floor(currentLimit * 0.25)); + continue; + } + throw e; + } + + if (noteIds.length === 0) { + job.log('No more notes to clean.'); + break; + } + + const queryDuration = performance.now() - queryBegin; + // try to adjust such that each query takes about 1~5 seconds and reasonable NodeJS heap so the task stays responsive + // this should not oscillate.. + if (queryDuration > 5000 || noteIds.length > 5000) { + currentLimit = Math.floor(currentLimit * 0.5); + } else if (queryDuration < 1000 && noteIds.length < 1000) { + currentLimit = Math.floor(currentLimit * 1.5); + } + // clamp to a sane range + currentLimit = Math.min(Math.max(currentLimit, minimumLimit), 5000); + + const deletableNoteIds = noteIds.filter(result => result.isRemovable).map(result => result.id); + if (deletableNoteIds.length > 0) { + try { + await this.notesRepository.delete(deletableNoteIds); + + for (const id of deletableNoteIds) { + const t = this.idService.parse(id).date.getTime(); + if (stats.oldest === null || t < stats.oldest) { + stats.oldest = t; + } + if (stats.newest === null || t > stats.newest) { + stats.newest = t; + } + } + + stats.deletedCount += deletableNoteIds.length; + } catch (e) { + // check for integrity violation errors (class 23) that might have occurred between the check and the delete + // we can safely continue to the next batch + if (e instanceof QueryFailedError && e.driverError?.code?.startsWith('23')) { + transientErrors++; + job.log(`Error deleting notes: ${e} (transient race condition?)`); + } else { + throw e; + } + } + } + + cursorLeft = noteIds.filter(result => result.isBase).reduce((max, { id }) => id > max ? id : max, cursorLeft); + + job.log(`Deleted ${noteIds.length} notes; ${Date.now() - batchBeginAt}ms`); + + if (process.env.NODE_ENV !== 'test') { + await setTimeout(Math.min(1000 * 5, queryDuration)); // Wait a moment to avoid overwhelming the db + } + }; + + if (transientErrors > 0) { + const msg = `${transientErrors} transient errors occurred while cleaning remote notes. You may need a second pass to complete the cleaning.`; + this.logger.warn(msg); + job.log(msg); + } + this.logger.succ('cleaning of remote notes completed.'); + + return { + deletedCount: stats.deletedCount, + oldest: stats.oldest, + newest: stats.newest, + skipped: false, + transientErrors, + }; + } +} diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts index 14a53e0c42..b643c2a6d0 100644 --- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import type { DriveFilesRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; @@ -14,6 +14,7 @@ import type { MiNote } from '@/models/Note.js'; import { EmailService } from '@/core/EmailService.js'; import { bindThis } from '@/decorators.js'; import { SearchService } from '@/core/SearchService.js'; +import { PageService } from '@/core/PageService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbUserDeleteJobData } from '../types.js'; @@ -35,7 +36,11 @@ export class DeleteAccountProcessorService { @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, + private driveService: DriveService, + private pageService: PageService, private emailService: EmailService, private queueLoggerService: QueueLoggerService, private searchService: SearchService, @@ -112,6 +117,28 @@ export class DeleteAccountProcessorService { this.logger.succ('All of files deleted'); } + { + // delete pages. Necessary for decrementing pageCount of notes. + while (true) { + const pages = await this.pagesRepository.find({ + where: { + userId: user.id, + }, + take: 100, + order: { + id: 1, + }, + }); + + if (pages.length === 0) { + break; + } + for (const page of pages) { + await this.pageService.delete(user, page.id); + } + } + } + { // Send email notification const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); if (profile.email && profile.emailVerified) { diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index 23c085ee27..1286b4dad6 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -75,7 +75,7 @@ export class ServerService implements OnApplicationShutdown { @bindThis public async launch(): Promise { const fastify = Fastify({ - trustProxy: true, + trustProxy: this.config.trustProxy ?? true, logger: false, }); this.#fastify = fastify; @@ -238,30 +238,6 @@ export class ServerService implements OnApplicationShutdown { } }); - fastify.get<{ Params: { code: string } }>('/verify-email/:code', async (request, reply) => { - const profile = await this.userProfilesRepository.findOneBy({ - emailVerifyCode: request.params.code, - }); - - if (profile != null) { - await this.userProfilesRepository.update({ userId: profile.userId }, { - emailVerified: true, - emailVerifyCode: null, - }); - - this.globalEventService.publishMainStream(profile.userId, 'meUpdated', await this.userEntityService.pack(profile.userId, { id: profile.userId }, { - schema: 'MeDetailed', - includeSecrets: true, - })); - - reply.code(200).send('Verification succeeded! メールアドレスの認証に成功しました。'); - return; - } else { - reply.code(404).send('Verification failed. Please try again. メールアドレスの認証に失敗しました。もう一度お試しください'); - return; - } - }); - fastify.register(this.clientServerService.createServer); this.streamingApiServerService.attach(fastify.server); diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts index 32818003ad..57d74ef2b1 100644 --- a/packages/backend/src/server/api/ApiServerService.ts +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -176,6 +176,17 @@ export class ApiServerService { } }); + fastify.all('/clear-browser-cache', (request, reply) => { + if (['GET', 'POST'].includes(request.method)) { + reply.header('Clear-Site-Data', '"cache", "prefetchCache", "prerenderCache", "executionContexts"'); + reply.code(204); + reply.send(); + } else { + reply.code(405); + reply.send(); + } + }); + // Make sure any unknown path under /api returns HTTP 404 Not Found, // because otherwise ClientServerService will return the base client HTML // page with HTTP 200. diff --git a/packages/backend/src/server/api/GetterService.ts b/packages/backend/src/server/api/GetterService.ts index 444e6db744..8f4213dfb6 100644 --- a/packages/backend/src/server/api/GetterService.ts +++ b/packages/backend/src/server/api/GetterService.ts @@ -40,8 +40,8 @@ export class GetterService { } @bindThis - public async getNoteWithUser(noteId: MiNote['id']) { - const note = await this.notesRepository.findOne({ where: { id: noteId }, relations: ['user'] }); + public async getNoteWithRelations(noteId: MiNote['id']) { + const note = await this.notesRepository.findOne({ where: { id: noteId }, relations: ['user', 'reply', 'renote', 'reply.user', 'renote.user'] }); if (note == null) { throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.'); diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index 3ec5e5d3e6..53336a087d 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -129,7 +129,8 @@ export class SignupApiService { let ticket: MiRegistrationTicket | null = null; - if (this.meta.disableRegistration) { + // テスト時はこの機構は障害となるため無効にする + if (process.env.NODE_ENV !== 'test' && this.meta.disableRegistration) { if (invitationCode == null || typeof invitationCode !== 'string') { reply.code(400); return; diff --git a/packages/backend/src/server/api/endpoint-list.ts b/packages/backend/src/server/api/endpoint-list.ts index f7b2fad341..f4aa07875d 100644 --- a/packages/backend/src/server/api/endpoint-list.ts +++ b/packages/backend/src/server/api/endpoint-list.ts @@ -70,6 +70,7 @@ export * as 'admin/queue/inbox-delayed' from './endpoints/admin/queue/inbox-dela export * as 'admin/queue/retry-job' from './endpoints/admin/queue/retry-job.js'; export * as 'admin/queue/remove-job' from './endpoints/admin/queue/remove-job.js'; export * as 'admin/queue/show-job' from './endpoints/admin/queue/show-job.js'; +export * as 'admin/queue/show-job-logs' from './endpoints/admin/queue/show-job-logs.js'; export * as 'admin/queue/promote-jobs' from './endpoints/admin/queue/promote-jobs.js'; export * as 'admin/queue/jobs' from './endpoints/admin/queue/jobs.js'; export * as 'admin/queue/stats' from './endpoints/admin/queue/stats.js'; @@ -168,6 +169,7 @@ export * as 'clips/update' from './endpoints/clips/update.js'; export * as 'drive' from './endpoints/drive.js'; export * as 'drive/files' from './endpoints/drive/files.js'; export * as 'drive/files/attached-notes' from './endpoints/drive/files/attached-notes.js'; +export * as 'drive/files/attached-chat-messages' from './endpoints/drive/files/attached-chat-messages.js'; export * as 'drive/files/check-existence' from './endpoints/drive/files/check-existence.js'; export * as 'drive/files/create' from './endpoints/drive/files/create.js'; export * as 'drive/files/delete' from './endpoints/drive/files/delete.js'; @@ -208,6 +210,7 @@ export * as 'flash/my-likes' from './endpoints/flash/my-likes.js'; export * as 'flash/show' from './endpoints/flash/show.js'; export * as 'flash/unlike' from './endpoints/flash/unlike.js'; export * as 'flash/update' from './endpoints/flash/update.js'; +export * as 'flash/search' from './endpoints/flash/search.js'; export * as 'following/create' from './endpoints/following/create.js'; export * as 'following/delete' from './endpoints/following/delete.js'; export * as 'following/invalidate' from './endpoints/following/invalidate.js'; @@ -409,6 +412,7 @@ export * as 'users/search' from './endpoints/users/search.js'; export * as 'users/search-by-username-and-host' from './endpoints/users/search-by-username-and-host.js'; export * as 'users/show' from './endpoints/users/show.js'; export * as 'users/update-memo' from './endpoints/users/update-memo.js'; +export * as 'verify-email' from './endpoints/verify-email.js'; export * as 'chat/messages/create-to-user' from './endpoints/chat/messages/create-to-user.js'; export * as 'chat/messages/create-to-room' from './endpoints/chat/messages/create-to-room.js'; export * as 'chat/messages/delete' from './endpoints/chat/messages/delete.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts index 06047b58a6..6606202118 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -34,13 +34,22 @@ export const meta = { res: { type: 'object', optional: false, nullable: false, - ref: 'MeDetailed', - properties: { - token: { - type: 'string', - optional: false, nullable: false, + allOf: [ + { + type: 'object', + ref: 'MeDetailed', }, - }, + { + type: 'object', + optional: false, nullable: false, + properties: { + token: { + type: 'string', + optional: false, nullable: false, + }, + }, + } + ], }, } as const; diff --git a/packages/backend/src/server/api/endpoints/admin/ad/create.ts b/packages/backend/src/server/api/endpoints/admin/ad/create.ts index 955154f4fb..01697ae185 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/create.ts @@ -36,6 +36,7 @@ export const paramDef = { startsAt: { type: 'integer' }, imageUrl: { type: 'string', minLength: 1 }, dayOfWeek: { type: 'integer' }, + isSensitive: { type: 'boolean' }, }, required: ['url', 'memo', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt', 'imageUrl', 'dayOfWeek'], } as const; @@ -55,6 +56,7 @@ export default class extends Endpoint { // eslint- expiresAt: new Date(ps.expiresAt), startsAt: new Date(ps.startsAt), dayOfWeek: ps.dayOfWeek, + isSensitive: ps.isSensitive, url: ps.url, imageUrl: ps.imageUrl, priority: ps.priority, @@ -73,6 +75,7 @@ export default class extends Endpoint { // eslint- expiresAt: ad.expiresAt.toISOString(), startsAt: ad.startsAt.toISOString(), dayOfWeek: ad.dayOfWeek, + isSensitive: ad.isSensitive, url: ad.url, imageUrl: ad.imageUrl, priority: ad.priority, diff --git a/packages/backend/src/server/api/endpoints/admin/ad/list.ts b/packages/backend/src/server/api/endpoints/admin/ad/list.ts index 4f897d98e4..f67cad5bd2 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/list.ts @@ -63,6 +63,7 @@ export default class extends Endpoint { // eslint- expiresAt: ad.expiresAt.toISOString(), startsAt: ad.startsAt.toISOString(), dayOfWeek: ad.dayOfWeek, + isSensitive: ad.isSensitive, url: ad.url, imageUrl: ad.imageUrl, memo: ad.memo, diff --git a/packages/backend/src/server/api/endpoints/admin/ad/update.ts b/packages/backend/src/server/api/endpoints/admin/ad/update.ts index 4e3d731aca..a3d9aaddc6 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/update.ts @@ -39,6 +39,7 @@ export const paramDef = { expiresAt: { type: 'integer' }, startsAt: { type: 'integer' }, dayOfWeek: { type: 'integer' }, + isSensitive: { type: 'boolean' }, }, required: ['id'], } as const; @@ -66,6 +67,7 @@ export default class extends Endpoint { // eslint- expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : undefined, startsAt: ps.startsAt ? new Date(ps.startsAt) : undefined, dayOfWeek: ps.dayOfWeek, + isSensitive: ps.isSensitive, }); const updatedAd = await this.adsRepository.findOneByOrFail({ id: ad.id }); diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts index 81a788de2b..804bd5d9b9 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -49,6 +49,34 @@ export const meta = { type: 'string', optional: false, nullable: false, }, + icon: { + type: 'string', + optional: false, nullable: true, + }, + display: { + type: 'string', + optional: false, nullable: false, + }, + isActive: { + type: 'boolean', + optional: false, nullable: false, + }, + forExistingUsers: { + type: 'boolean', + optional: false, nullable: false, + }, + silence: { + type: 'boolean', + optional: false, nullable: false, + }, + needConfirmationToRead: { + type: 'boolean', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: true, + }, imageUrl: { type: 'string', optional: false, nullable: true, diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts index b84a5c73f9..e7a70d0762 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts @@ -157,6 +157,22 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + maybeSensitive: { + type: 'boolean', + optional: false, nullable: false, + }, + maybePorn: { + type: 'boolean', + optional: false, nullable: false, + }, + requestIp: { + type: 'string', + optional: false, nullable: true, + }, + requestHeaders: { + type: 'object', + optional: false, nullable: true, + }, }, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 924163afbb..21099c0a8c 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -223,10 +223,12 @@ export const meta = { sensitiveMediaDetection: { type: 'string', optional: false, nullable: false, + enum: ['none', 'all', 'local', 'remote'], }, sensitiveMediaDetectionSensitivity: { type: 'string', optional: false, nullable: false, + enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'], }, setSensitiveFlagAutomatically: { type: 'boolean', @@ -425,6 +427,10 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + clientOptions: { + type: 'object', + optional: false, nullable: false, + }, description: { type: 'string', optional: false, nullable: true, @@ -469,6 +475,10 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + feedbackUrl: { + type: 'string', + optional: false, nullable: true, + }, summalyProxy: { type: 'string', optional: false, nullable: true, @@ -571,6 +581,18 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + enableRemoteNotesCleaning: { + type: 'boolean', + optional: false, nullable: false, + }, + remoteNotesCleaningExpiryDaysForEachNotes: { + type: 'number', + optional: false, nullable: false, + }, + remoteNotesCleaningMaxProcessingDurationInMinutes: { + type: 'number', + optional: false, nullable: false, + }, }, }, } as const; @@ -638,6 +660,7 @@ export default class extends Endpoint { // eslint- logoImageUrl: instance.logoImageUrl, defaultLightTheme: instance.defaultLightTheme, defaultDarkTheme: instance.defaultDarkTheme, + clientOptions: instance.clientOptions, enableEmail: instance.enableEmail, enableServiceWorker: instance.enableServiceWorker, translatorAvailable: instance.deeplAuthKey != null, @@ -722,6 +745,9 @@ export default class extends Endpoint { // eslint- proxyRemoteFiles: instance.proxyRemoteFiles, signToActivityPubGet: instance.signToActivityPubGet, allowExternalApRedirect: instance.allowExternalApRedirect, + enableRemoteNotesCleaning: instance.enableRemoteNotesCleaning, + remoteNotesCleaningExpiryDaysForEachNotes: instance.remoteNotesCleaningExpiryDaysForEachNotes, + remoteNotesCleaningMaxProcessingDurationInMinutes: instance.remoteNotesCleaningMaxProcessingDurationInMinutes, }; }); } diff --git a/packages/backend/src/server/api/endpoints/admin/queue/show-job-logs.ts b/packages/backend/src/server/api/endpoints/admin/queue/show-job-logs.ts new file mode 100644 index 0000000000..b9292ed12a --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/queue/show-job-logs.ts @@ -0,0 +1,45 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + kind: 'read:admin:queue', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + optional: false, nullable: false, + type: 'string', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + queue: { type: 'string', enum: QUEUE_TYPES }, + jobId: { type: 'string' }, + }, + required: ['queue', 'jobId'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + return this.queueService.queueGetJobLogs(ps.queue, ps.jobId); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/system-webhook/create.ts b/packages/backend/src/server/api/endpoints/admin/system-webhook/create.ts index 28071e7a33..93d293ed41 100644 --- a/packages/backend/src/server/api/endpoints/admin/system-webhook/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/system-webhook/create.ts @@ -48,8 +48,8 @@ export const paramDef = { }, secret: { type: 'string', - minLength: 1, maxLength: 1024, + default: '', }, }, required: [ @@ -57,7 +57,6 @@ export const paramDef = { 'name', 'on', 'url', - 'secret', ], } as const; diff --git a/packages/backend/src/server/api/endpoints/admin/system-webhook/update.ts b/packages/backend/src/server/api/endpoints/admin/system-webhook/update.ts index 8d68bb8f87..e021806398 100644 --- a/packages/backend/src/server/api/endpoints/admin/system-webhook/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/system-webhook/update.ts @@ -52,8 +52,8 @@ export const paramDef = { }, secret: { type: 'string', - minLength: 1, maxLength: 1024, + default: '', }, }, required: [ @@ -62,7 +62,6 @@ export const paramDef = { 'name', 'on', 'url', - 'secret', ], } as const; diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 578aa2b662..a1a2a99d6e 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -67,6 +67,7 @@ export const paramDef = { description: { type: 'string', nullable: true }, defaultLightTheme: { type: 'string', nullable: true }, defaultDarkTheme: { type: 'string', nullable: true }, + clientOptions: { type: 'object', nullable: false }, cacheRemoteFiles: { type: 'boolean' }, cacheRemoteSensitiveFiles: { type: 'boolean' }, emailRequiredForSignup: { type: 'boolean' }, @@ -205,6 +206,9 @@ export const paramDef = { proxyRemoteFiles: { type: 'boolean' }, signToActivityPubGet: { type: 'boolean' }, allowExternalApRedirect: { type: 'boolean' }, + enableRemoteNotesCleaning: { type: 'boolean' }, + remoteNotesCleaningExpiryDaysForEachNotes: { type: 'number' }, + remoteNotesCleaningMaxProcessingDurationInMinutes: { type: 'number' }, }, required: [], } as const; @@ -323,6 +327,10 @@ export default class extends Endpoint { // eslint- set.defaultDarkTheme = ps.defaultDarkTheme; } + if (ps.clientOptions !== undefined) { + set.clientOptions = ps.clientOptions; + } + if (ps.cacheRemoteFiles !== undefined) { set.cacheRemoteFiles = ps.cacheRemoteFiles; } @@ -723,6 +731,18 @@ export default class extends Endpoint { // eslint- set.allowExternalApRedirect = ps.allowExternalApRedirect; } + if (ps.enableRemoteNotesCleaning !== undefined) { + set.enableRemoteNotesCleaning = ps.enableRemoteNotesCleaning; + } + + if (ps.remoteNotesCleaningExpiryDaysForEachNotes !== undefined) { + set.remoteNotesCleaningExpiryDaysForEachNotes = ps.remoteNotesCleaningExpiryDaysForEachNotes; + } + + if (ps.remoteNotesCleaningMaxProcessingDurationInMinutes !== undefined) { + set.remoteNotesCleaningMaxProcessingDurationInMinutes = ps.remoteNotesCleaningMaxProcessingDurationInMinutes; + } + const before = await this.metaService.fetch(true); await this.metaService.update(set); diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 4afed7dc5c..fe48e7497a 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -18,9 +18,9 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; -import { ApiError } from '../../error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['federation'], diff --git a/packages/backend/src/server/api/endpoints/chat/read-all.ts b/packages/backend/src/server/api/endpoints/chat/read-all.ts index 2ed9497eef..e2d9601aa6 100644 --- a/packages/backend/src/server/api/endpoints/chat/read-all.ts +++ b/packages/backend/src/server/api/endpoints/chat/read-all.ts @@ -32,6 +32,8 @@ export default class extends Endpoint { // eslint- private chatService: ChatService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'read'); + await this.chatService.readAllChatMessages(me.id); }); } diff --git a/packages/backend/src/server/api/endpoints/clips/list.ts b/packages/backend/src/server/api/endpoints/clips/list.ts index 2e4a3ff820..af20ea9f8d 100644 --- a/packages/backend/src/server/api/endpoints/clips/list.ts +++ b/packages/backend/src/server/api/endpoints/clips/list.ts @@ -5,6 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; import type { ClipsRepository } from '@/models/_.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -29,7 +30,13 @@ export const meta = { export const paramDef = { type: 'object', - properties: {}, + properties: { + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, + }, required: [], } as const; @@ -39,12 +46,14 @@ export default class extends Endpoint { // eslint- @Inject(DI.clipsRepository) private clipsRepository: ClipsRepository, + private queryService: QueryService, private clipEntityService: ClipEntityService, ) { super(meta, paramDef, async (ps, me) => { - const clips = await this.clipsRepository.findBy({ - userId: me.id, - }); + const query = this.queryService.makePaginationQuery(this.clipsRepository.createQueryBuilder('clip'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('clip.userId = :userId', { userId: me.id }); + + const clips = await query.limit(ps.limit).getMany(); return await this.clipEntityService.packMany(clips, me); }); diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts index ecd0afc386..c4260fd87c 100644 --- a/packages/backend/src/server/api/endpoints/clips/notes.ts +++ b/packages/backend/src/server/api/endpoints/clips/notes.ts @@ -101,7 +101,7 @@ export default class extends Endpoint { // eslint- } if (ps.search != null) { - for (const word of ps.search!.trim().split(' ')) { + for (const word of ps.search.trim().split(' ')) { query.andWhere(new Brackets(qb => { qb.orWhere('note.text ILIKE :search', { search: `%${sqlLikeEscape(word)}%` }); qb.orWhere('note.cw ILIKE :search', { search: `%${sqlLikeEscape(word)}%` }); diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-chat-messages.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-chat-messages.ts new file mode 100644 index 0000000000..b34ac4abd1 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/drive/files/attached-chat-messages.ts @@ -0,0 +1,93 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { DriveFilesRepository, ChatMessagesRepository } from '@/models/_.js'; +import { QueryService } from '@/core/QueryService.js'; +import { DI } from '@/di-symbols.js'; +import { RoleService } from '@/core/RoleService.js'; +import { ChatEntityService } from '@/core/entities/ChatEntityService.js'; +import { ChatService } from '@/core/ChatService.js'; +import { ApiError } from '../../../error.js'; + +export const meta = { + tags: ['drive', 'chat'], + + requireCredential: true, + + kind: 'read:drive', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'ChatMessage', + }, + }, + + errors: { + noSuchFile: { + message: 'No such file.', + code: 'NO_SUCH_FILE', + id: '485ce26d-f5d2-4313-9783-e689d131eafb', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + fileId: { type: 'string', format: 'misskey:id' }, + }, + required: ['fileId'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + @Inject(DI.chatMessagesRepository) + private chatMessagesRepository: ChatMessagesRepository, + + private chatService: ChatService, + private chatEntityService: ChatEntityService, + private queryService: QueryService, + private roleService: RoleService, + ) { + super(meta, paramDef, async (ps, me) => { + const isModerator = await this.roleService.isModerator(me); + + if (!isModerator) { + await this.chatService.checkChatAvailability(me.id, 'read'); + } + + const file = await this.driveFilesRepository.findOneBy({ + id: ps.fileId, + userId: isModerator ? undefined : me.id, + }); + + if (file == null) { + throw new ApiError(meta.errors.noSuchFile); + } + + const query = this.queryService.makePaginationQuery(this.chatMessagesRepository.createQueryBuilder('message'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate); + query.andWhere('message.fileId = :fileId', { fileId: file.id }); + + const messages = await query.limit(ps.limit).getMany(); + + return await this.chatEntityService.packMessagesDetailed(messages, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/flash/my-likes.ts b/packages/backend/src/server/api/endpoints/flash/my-likes.ts index c1a197214c..ff9d6c3264 100644 --- a/packages/backend/src/server/api/endpoints/flash/my-likes.ts +++ b/packages/backend/src/server/api/endpoints/flash/my-likes.ts @@ -5,10 +5,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { FlashLikesRepository } from '@/models/_.js'; -import { QueryService } from '@/core/QueryService.js'; import { FlashLikeEntityService } from '@/core/entities/FlashLikeEntityService.js'; import { DI } from '@/di-symbols.js'; +import { FlashService } from '@/core/FlashService.js'; export const meta = { tags: ['account', 'flash'], @@ -46,6 +45,7 @@ export const paramDef = { untilId: { type: 'string', format: 'misskey:id' }, sinceDate: { type: 'integer' }, untilDate: { type: 'integer' }, + search: { type: 'string', minLength: 1, maxLength: 100, nullable: true }, }, required: [], } as const; @@ -53,20 +53,18 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.flashLikesRepository) - private flashLikesRepository: FlashLikesRepository, - private flashLikeEntityService: FlashLikeEntityService, - private queryService: QueryService, + private flashService: FlashService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.flashLikesRepository.createQueryBuilder('like'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) - .andWhere('like.userId = :meId', { meId: me.id }) - .leftJoinAndSelect('like.flash', 'flash'); - - const likes = await query - .limit(ps.limit) - .getMany(); + const likes = await this.flashService.myLikes(me.id, { + sinceId: ps.sinceId, + untilId: ps.untilId, + sinceDate: ps.sinceDate, + untilDate: ps.untilDate, + limit: ps.limit, + search: ps.search, + }); return this.flashLikeEntityService.packMany(likes, me); }); diff --git a/packages/backend/src/server/api/endpoints/flash/search.ts b/packages/backend/src/server/api/endpoints/flash/search.ts new file mode 100644 index 0000000000..36948bb7b4 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/flash/search.ts @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { FlashEntityService } from '@/core/entities/FlashEntityService.js'; +import { DI } from '@/di-symbols.js'; +import { FlashService } from '@/core/FlashService.js'; + +export const meta = { + tags: ['flash'], + + requireCredential: false, + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'Flash', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + query: { type: 'string', minLength: 1, maxLength: 100 }, + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, + limit: { type: 'integer', minimum: 1, maximum: 100, default: 5 }, + }, + required: ['query'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private flashService: FlashService, + private flashEntityService: FlashEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const result = await this.flashService.search(ps.query, { + sinceId: ps.sinceId, + untilId: ps.untilId, + sinceDate: ps.sinceDate, + untilDate: ps.untilDate, + limit: ps.limit, + }); + + return await this.flashEntityService.packMany(result, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/flash/update.ts b/packages/backend/src/server/api/endpoints/flash/update.ts index e378669f0a..8696c6f6e8 100644 --- a/packages/backend/src/server/api/endpoints/flash/update.ts +++ b/packages/backend/src/server/api/endpoints/flash/update.ts @@ -73,8 +73,8 @@ export default class extends Endpoint { // eslint- updatedAt: new Date(), ...Object.fromEntries( Object.entries(ps).filter( - ([key, val]) => (key !== 'flashId') && Object.hasOwn(paramDef.properties, key) - ) + ([key, val]) => (key !== 'flashId') && Object.hasOwn(paramDef.properties, key), + ), ), }); }); diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts index 055b5cc061..523d81ac73 100644 --- a/packages/backend/src/server/api/endpoints/i/apps.ts +++ b/packages/backend/src/server/api/endpoints/i/apps.ts @@ -46,6 +46,14 @@ export const meta = { type: 'string', }, }, + iconUrl: { + type: 'string', + optional: true, nullable: true, + }, + description: { + type: 'string', + optional: true, nullable: true, + }, }, }, }, @@ -88,6 +96,8 @@ export default class extends Endpoint { // eslint- createdAt: this.idService.parse(token.id).date.toISOString(), lastUsedAt: token.lastUsedAt?.toISOString(), permission: token.app ? token.app.permission : token.permission, + iconUrl: token.iconUrl, + description: token.description ?? token.app?.description ?? null, }))); }); } diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts index 394c178f2a..8a3ba9e026 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts @@ -21,29 +21,7 @@ export const meta = { type: 'array', items: { type: 'object', - properties: { - id: { - type: 'string', - format: 'misskey:id', - }, - userId: { - type: 'string', - format: 'misskey:id', - }, - name: { type: 'string' }, - on: { - type: 'array', - items: { - type: 'string', - enum: webhookEventTypes, - }, - }, - url: { type: 'string' }, - secret: { type: 'string' }, - active: { type: 'boolean' }, - latestSentAt: { type: 'string', format: 'date-time', nullable: true }, - latestStatus: { type: 'integer', nullable: true }, - }, + ref: 'UserWebhook', }, }, } as const; @@ -65,19 +43,17 @@ export default class extends Endpoint { // eslint- userId: me.id, }); - return webhooks.map(webhook => ( - { - id: webhook.id, - userId: webhook.userId, - name: webhook.name, - on: webhook.on, - url: webhook.url, - secret: webhook.secret, - active: webhook.active, - latestSentAt: webhook.latestSentAt ? webhook.latestSentAt.toISOString() : null, - latestStatus: webhook.latestStatus, - } - )); + return webhooks.map(webhook => ({ + id: webhook.id, + userId: webhook.userId, + name: webhook.name, + on: webhook.on, + url: webhook.url, + secret: webhook.secret, + active: webhook.active, + latestSentAt: webhook.latestSentAt ? webhook.latestSentAt.toISOString() : null, + latestStatus: webhook.latestStatus, + })); }); } } diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts index 4a0c09ff0c..1c19081c98 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts @@ -28,29 +28,7 @@ export const meta = { res: { type: 'object', - properties: { - id: { - type: 'string', - format: 'misskey:id', - }, - userId: { - type: 'string', - format: 'misskey:id', - }, - name: { type: 'string' }, - on: { - type: 'array', - items: { - type: 'string', - enum: webhookEventTypes, - }, - }, - url: { type: 'string' }, - secret: { type: 'string' }, - active: { type: 'boolean' }, - latestSentAt: { type: 'string', format: 'date-time', nullable: true }, - latestStatus: { type: 'integer', nullable: true }, - }, + ref: 'UserWebhook', }, } as const; diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 253a360815..7caea8eedc 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -269,7 +269,10 @@ export default class extends Endpoint { // eslint- let renote: MiNote | null = null; if (ps.renoteId != null) { // Fetch renote to note - renote = await this.notesRepository.findOneBy({ id: ps.renoteId }); + renote = await this.notesRepository.findOne({ + where: { id: ps.renoteId }, + relations: ['user', 'renote', 'reply'], + }); if (renote == null) { throw new ApiError(meta.errors.noSuchRenoteTarget); @@ -315,7 +318,10 @@ export default class extends Endpoint { // eslint- let reply: MiNote | null = null; if (ps.replyId != null) { // Fetch reply - reply = await this.notesRepository.findOneBy({ id: ps.replyId }); + reply = await this.notesRepository.findOne({ + where: { id: ps.replyId }, + relations: ['user'], + }); if (reply == null) { throw new ApiError(meta.errors.noSuchReplyTarget); diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index 1c73edf08e..7fa8004209 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -91,6 +91,7 @@ export default class extends Endpoint { // eslint- qb.orWhere(new Brackets(qb => { qb.where('note.text IS NOT NULL'); qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); })); })); } diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts index 05ffdc1f97..e775bdb7fd 100644 --- a/packages/backend/src/server/api/endpoints/notes/mentions.ts +++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts @@ -66,7 +66,7 @@ export default class extends Endpoint { // eslint- .orWhere(':meIdAsList <@ note.visibleUserIds'); })) // Avoid scanning primary key index - .orderBy('CONCAT(note.id)', 'DESC') + .orderBy('CONCAT(note.id)', (ps.sinceDate || ps.sinceId) ? 'ASC' : 'DESC') .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts index b93c73b0c5..a41de25ddf 100644 --- a/packages/backend/src/server/api/endpoints/notes/show.ts +++ b/packages/backend/src/server/api/endpoints/notes/show.ts @@ -29,10 +29,16 @@ export const meta = { id: '24fcbfc6-2e37-42b6-8388-c29b3861a08d', }, - signinRequired: { - message: 'Signin required.', - code: 'SIGNIN_REQUIRED', - id: '8e75455b-738c-471d-9f80-62693f33372e', + contentRestrictedByUser: { + message: 'Content restricted by user. Please sign in to view.', + code: 'CONTENT_RESTRICTED_BY_USER', + id: 'fbcc002d-37d9-4944-a6b0-d9e29f2d33ab', + }, + + contentRestrictedByServer: { + message: 'Content restricted by server settings. Please sign in to view.', + code: 'CONTENT_RESTRICTED_BY_SERVER', + id: '145f88d2-b03d-4087-8143-a78928883c4b', }, }, } as const; @@ -55,21 +61,21 @@ export default class extends Endpoint { // eslint- private getterService: GetterService, ) { super(meta, paramDef, async (ps, me) => { - const note = await this.getterService.getNoteWithUser(ps.noteId).catch(err => { + const note = await this.getterService.getNoteWithRelations(ps.noteId).catch(err => { if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); throw err; }); if (note.user!.requireSigninToViewContents && me == null) { - throw new ApiError(meta.errors.signinRequired); + throw new ApiError(meta.errors.contentRestrictedByUser); } if (this.serverSettings.ugcVisibilityForVisitor === 'none' && me == null) { - throw new ApiError(meta.errors.signinRequired); + throw new ApiError(meta.errors.contentRestrictedByServer); } if (this.serverSettings.ugcVisibilityForVisitor === 'local' && note.userHost != null && me == null) { - throw new ApiError(meta.errors.signinRequired); + throw new ApiError(meta.errors.contentRestrictedByServer); } return await this.noteEntityService.pack(note, me, { diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index c76cca1518..eeeb797efc 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -237,7 +237,14 @@ export default class extends Endpoint { // eslint- } if (ps.withRenotes === false) { - query.andWhere('note.renoteId IS NULL'); + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere(new Brackets(qb => { + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + })); } //#endregion diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index 614cd9204d..42e80c6ae1 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -223,6 +223,7 @@ export default class extends Endpoint { // eslint- qb.orWhere(new Brackets(qb => { qb.orWhere('note.text IS NOT NULL'); qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); })); })); } diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index 6de5fe3d44..96bc2a953a 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -5,12 +5,13 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; -import type { DriveFilesRepository, PagesRepository } from '@/models/_.js'; -import { IdService } from '@/core/IdService.js'; -import { MiPage, pageNameSchema } from '@/models/Page.js'; +import type { DriveFilesRepository, MiDriveFile, PagesRepository } from '@/models/_.js'; +import { pageNameSchema } from '@/models/Page.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; import { DI } from '@/di-symbols.js'; +import { PageService } from '@/core/PageService.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -77,11 +78,11 @@ export default class extends Endpoint { // eslint- @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, + private pageService: PageService, private pageEntityService: PageEntityService, - private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { - let eyeCatchingImage = null; + let eyeCatchingImage: MiDriveFile | null = null; if (ps.eyeCatchingImageId != null) { eyeCatchingImage = await this.driveFilesRepository.findOneBy({ id: ps.eyeCatchingImageId, @@ -102,24 +103,20 @@ export default class extends Endpoint { // eslint- } }); - const page = await this.pagesRepository.insertOne(new MiPage({ - id: this.idService.gen(), - updatedAt: new Date(), - title: ps.title, - name: ps.name, - summary: ps.summary, - content: ps.content, - variables: ps.variables, - script: ps.script, - eyeCatchingImageId: eyeCatchingImage ? eyeCatchingImage.id : null, - userId: me.id, - visibility: 'public', - alignCenter: ps.alignCenter, - hideTitleWhenPinned: ps.hideTitleWhenPinned, - font: ps.font, - })); + try { + const page = await this.pageService.create(me, { + ...ps, + eyeCatchingImage, + summary: ps.summary ?? null, + }); - return await this.pageEntityService.pack(page); + return await this.pageEntityService.pack(page); + } catch (err) { + if (err instanceof IdentifiableError && err.id === '1a79e38e-3d83-4423-845b-a9d83ff93b61') { + throw new ApiError(meta.errors.nameAlreadyExists); + } + throw err; + } }); } } diff --git a/packages/backend/src/server/api/endpoints/pages/delete.ts b/packages/backend/src/server/api/endpoints/pages/delete.ts index f2bc946788..a33868552d 100644 --- a/packages/backend/src/server/api/endpoints/pages/delete.ts +++ b/packages/backend/src/server/api/endpoints/pages/delete.ts @@ -4,12 +4,14 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { PagesRepository, UsersRepository } from '@/models/_.js'; +import type { MiDriveFile, PagesRepository, UsersRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../error.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { PageService } from '@/core/PageService.js'; export const meta = { tags: ['pages'], @@ -44,36 +46,17 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.pagesRepository) - private pagesRepository: PagesRepository, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - private moderationLogService: ModerationLogService, - private roleService: RoleService, + private pageService: PageService, ) { super(meta, paramDef, async (ps, me) => { - const page = await this.pagesRepository.findOneBy({ id: ps.pageId }); - - if (page == null) { - throw new ApiError(meta.errors.noSuchPage); - } - - if (!await this.roleService.isModerator(me) && page.userId !== me.id) { - throw new ApiError(meta.errors.accessDenied); - } - - await this.pagesRepository.delete(page.id); - - if (page.userId !== me.id) { - const user = await this.usersRepository.findOneByOrFail({ id: page.userId }); - this.moderationLogService.log(me, 'deletePage', { - pageId: page.id, - pageUserId: page.userId, - pageUserUsername: user.username, - page, - }); + try { + await this.pageService.delete(me, ps.pageId); + } catch (err) { + if (err instanceof IdentifiableError) { + if (err.id === '66aefd3c-fdb2-4a71-85ae-cc18bea85d3f') throw new ApiError(meta.errors.noSuchPage); + if (err.id === 'd0017699-8256-46f1-aed4-bc03bed73616') throw new ApiError(meta.errors.accessDenied); + } + throw err; } }); } diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts index a6aeb6002e..6fa5c1d75c 100644 --- a/packages/backend/src/server/api/endpoints/pages/update.ts +++ b/packages/backend/src/server/api/endpoints/pages/update.ts @@ -4,13 +4,14 @@ */ import ms from 'ms'; -import { Not } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { PagesRepository, DriveFilesRepository } from '@/models/_.js'; +import type { DriveFilesRepository, MiDriveFile } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; import { pageNameSchema } from '@/models/Page.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { PageService } from '@/core/PageService.js'; export const meta = { tags: ['pages'], @@ -75,57 +76,37 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.pagesRepository) - private pagesRepository: PagesRepository, - @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, + + private pageService: PageService, ) { super(meta, paramDef, async (ps, me) => { - const page = await this.pagesRepository.findOneBy({ id: ps.pageId }); - if (page == null) { - throw new ApiError(meta.errors.noSuchPage); - } - if (page.userId !== me.id) { - throw new ApiError(meta.errors.accessDenied); - } + try { + let eyeCatchingImage: MiDriveFile | null | undefined | string = ps.eyeCatchingImageId; + if (eyeCatchingImage != null) { + eyeCatchingImage = await this.driveFilesRepository.findOneBy({ + id: eyeCatchingImage, + userId: me.id, + }); - if (ps.eyeCatchingImageId != null) { - const eyeCatchingImage = await this.driveFilesRepository.findOneBy({ - id: ps.eyeCatchingImageId, - userId: me.id, - }); - - if (eyeCatchingImage == null) { - throw new ApiError(meta.errors.noSuchFile); - } - } - - if (ps.name != null) { - await this.pagesRepository.findBy({ - id: Not(ps.pageId), - userId: me.id, - name: ps.name, - }).then(result => { - if (result.length > 0) { - throw new ApiError(meta.errors.nameAlreadyExists); + if (eyeCatchingImage == null) { + throw new ApiError(meta.errors.noSuchFile); } - }); - } + } - await this.pagesRepository.update(page.id, { - updatedAt: new Date(), - title: ps.title, - name: ps.name, - summary: ps.summary === undefined ? page.summary : ps.summary, - content: ps.content, - variables: ps.variables, - script: ps.script, - alignCenter: ps.alignCenter, - hideTitleWhenPinned: ps.hideTitleWhenPinned, - font: ps.font, - eyeCatchingImageId: ps.eyeCatchingImageId, - }); + await this.pageService.update(me, ps.pageId, { + ...ps, + eyeCatchingImage, + }); + } catch (err) { + if (err instanceof IdentifiableError) { + if (err.id === '66aefd3c-fdb2-4a71-85ae-cc18bea85d3f') throw new ApiError(meta.errors.noSuchPage); + if (err.id === 'd0017699-8256-46f1-aed4-bc03bed73616') throw new ApiError(meta.errors.accessDenied); + if (err.id === 'd05bfe24-24b6-4ea2-a3ec-87cc9bf4daa4') throw new ApiError(meta.errors.nameAlreadyExists); + } + throw err; + } }); } } diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts index 8756801fe4..c6d477a92f 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -22,7 +22,26 @@ export const meta = { res: { type: 'object', optional: false, nullable: false, - ref: 'UserList', + allOf: [ + { + type: 'object', + ref: 'UserList', + }, + { + type: 'object', + optional: false, nullable: false, + properties: { + likedCount: { + type: 'number', + optional: true, nullable: false, + }, + isLiked: { + type: 'boolean', + optional: true, nullable: false, + }, + }, + }, + ], }, errors: { diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index d6f1ecd8ed..d84a191f7a 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -28,7 +28,7 @@ export const meta = { items: { type: 'object', optional: false, nullable: false, - ref: 'NoteReaction', + ref: 'NoteReactionWithNote', }, }, @@ -120,7 +120,7 @@ export default class extends Endpoint { // eslint- return true; }); - return await this.noteReactionEntityService.packMany(reactions, me, { withNote: true }); + return await this.noteReactionEntityService.packManyWithNote(reactions, me); }); } } diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts index 5d36847e03..c422286152 100644 --- a/packages/backend/src/server/api/endpoints/users/search.ts +++ b/packages/backend/src/server/api/endpoints/users/search.ts @@ -13,6 +13,7 @@ export const meta = { tags: ['users'], requireCredential: false, + requiredRolePolicy: 'canSearchUsers', description: 'Search for users.', diff --git a/packages/backend/src/server/api/endpoints/verify-email.ts b/packages/backend/src/server/api/endpoints/verify-email.ts new file mode 100644 index 0000000000..e069ed59f2 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/verify-email.ts @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { UserProfilesRepository } from '@/models/_.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { ApiError } from '../error.js'; + +export const meta = { + requireCredential: false, + + tags: ['account'], + + errors: { + noSuchCode: { + message: 'No such code.', + code: 'NO_SUCH_CODE', + id: '97c1f576-e4b8-4b8a-a6dc-9cb65e7f6f85', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + code: { type: 'string' }, + }, + required: ['code'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + private userEntityService: UserEntityService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps) => { + const profile = await this.userProfilesRepository.findOneBy({ + emailVerifyCode: ps.code, + }); + + if (profile == null) { + throw new ApiError(meta.errors.noSuchCode); + } + + await this.userProfilesRepository.update({ userId: profile.userId }, { + emailVerified: true, + emailVerifyCode: null, + }); + + this.globalEventService.publishMainStream(profile.userId, 'meUpdated', await this.userEntityService.pack(profile.userId, { id: profile.userId }, { + schema: 'MeDetailed', + includeSecrets: true, + })); + }); + } +} + diff --git a/packages/backend/src/server/api/stream/Connection.ts b/packages/backend/src/server/api/stream/Connection.ts index c9801d8314..8e28ab263b 100644 --- a/packages/backend/src/server/api/stream/Connection.ts +++ b/packages/backend/src/server/api/stream/Connection.ts @@ -32,7 +32,6 @@ export default class Connection { public subscriber: StreamEventEmitter; private channels: Channel[] = []; private subscribingNotes: Partial> = {}; - private cachedNotes: Packed<'Note'>[] = []; public userProfile: MiUserProfile | null = null; public following: Record | undefined> = {}; public followingChannels: Set = new Set(); @@ -132,26 +131,6 @@ export default class Connection { this.sendMessageToWs(data.type, data.body); } - @bindThis - public cacheNote(note: Packed<'Note'>) { - const add = (note: Packed<'Note'>) => { - const existIndex = this.cachedNotes.findIndex(n => n.id === note.id); - if (existIndex > -1) { - this.cachedNotes[existIndex] = note; - return; - } - - this.cachedNotes.unshift(note); - if (this.cachedNotes.length > 32) { - this.cachedNotes.splice(32); - } - }; - - add(note); - if (note.reply) add(note.reply); - if (note.renote) add(note.renote); - } - @bindThis private onReadNotification(payload: JsonValue | undefined) { this.notificationService.readAllNotification(this.user!.id); diff --git a/packages/backend/src/server/api/stream/channels/antenna.ts b/packages/backend/src/server/api/stream/channels/antenna.ts index 53dc7f18b6..e08562fdf9 100644 --- a/packages/backend/src/server/api/stream/channels/antenna.ts +++ b/packages/backend/src/server/api/stream/channels/antenna.ts @@ -43,8 +43,6 @@ class AntennaChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; - this.connection.cacheNote(note); - this.send('note', note); } else { this.send(data.type, data.body); diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index 7108e0cd6e..ac79c31854 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -49,8 +49,6 @@ class ChannelChannel extends Channel { } } - this.connection.cacheNote(note); - this.send('note', note); } diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts index 795980821b..d7c781ad12 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -65,8 +65,6 @@ class GlobalTimelineChannel extends Channel { } } - this.connection.cacheNote(note); - this.send('note', note); } diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts index 8105f15cb1..c911d63642 100644 --- a/packages/backend/src/server/api/stream/channels/hashtag.ts +++ b/packages/backend/src/server/api/stream/channels/hashtag.ts @@ -53,8 +53,6 @@ class HashtagChannel extends Channel { } } - this.connection.cacheNote(note); - this.send('note', note); } diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index 66644ed58c..157d9fc279 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -86,8 +86,6 @@ class HomeTimelineChannel extends Channel { } } - this.connection.cacheNote(note); - this.send('note', note); } diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index 5681311493..db5b4576be 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -100,8 +100,6 @@ class HybridTimelineChannel extends Channel { } } - this.connection.cacheNote(note); - this.send('note', note); } diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts index 2984e18774..3d7ed6acdb 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -75,8 +75,6 @@ class LocalTimelineChannel extends Channel { } } - this.connection.cacheNote(note); - this.send('note', note); } diff --git a/packages/backend/src/server/api/stream/channels/main.ts b/packages/backend/src/server/api/stream/channels/main.ts index 863d7f4c4e..525f24c105 100644 --- a/packages/backend/src/server/api/stream/channels/main.ts +++ b/packages/backend/src/server/api/stream/channels/main.ts @@ -39,7 +39,6 @@ class MainChannel extends Channel { const note = await this.noteEntityService.pack(data.body.note.id, this.user, { detail: true, }); - this.connection.cacheNote(note); data.body.note = note; } break; @@ -52,7 +51,6 @@ class MainChannel extends Channel { const note = await this.noteEntityService.pack(data.body.id, this.user, { detail: true, }); - this.connection.cacheNote(note); data.body = note; } break; diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index 4f38351e94..5bfd8fa68c 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -118,8 +118,6 @@ class UserListChannel extends Channel { } } - this.connection.cacheNote(note); - this.send('note', note); } diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 8ca61a497d..3cd83efa1a 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -20,17 +20,6 @@ import type { Config } from '@/config.js'; import { getNoteSummary } from '@/misc/get-note-summary.js'; import { DI } from '@/di-symbols.js'; import * as Acct from '@/misc/acct.js'; -import type { - DbQueue, - DeliverQueue, - EndedPollNotificationQueue, - InboxQueue, - ObjectStorageQueue, - RelationshipQueue, - SystemQueue, - UserWebhookDeliverQueue, - SystemWebhookDeliverQueue, -} from '@/core/QueueModule.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; @@ -129,16 +118,6 @@ export class ClientServerService { private feedService: FeedService, private roleService: RoleService, private clientLoggerService: ClientLoggerService, - - @Inject('queue:system') public systemQueue: SystemQueue, - @Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue, - @Inject('queue:deliver') public deliverQueue: DeliverQueue, - @Inject('queue:inbox') public inboxQueue: InboxQueue, - @Inject('queue:db') public dbQueue: DbQueue, - @Inject('queue:relationship') public relationshipQueue: RelationshipQueue, - @Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue, - @Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue, - @Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue, ) { //this.createServer = this.createServer.bind(this); } @@ -188,6 +167,10 @@ export class ClientServerService { 'url': 'url', }, }, + 'shortcuts': [{ + 'name': 'Safemode', + 'url': '/?safemode=true', + }], }; manifest = { @@ -218,6 +201,8 @@ export class ClientServerService { @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { + const configUrl = new URL(this.config.url); + fastify.register(fastifyView, { root: _dirname + '/views', engine: { @@ -256,7 +241,6 @@ export class ClientServerService { done(); }); } else { - const configUrl = new URL(this.config.url); const urlOriginWithoutPort = configUrl.origin.replace(/:\d+$/, ''); const port = (process.env.VITE_PORT ?? '5173'); @@ -580,7 +564,7 @@ export class ClientServerService { id: request.params.note, visibility: In(['public', 'home']), }, - relations: ['user'], + relations: ['user', 'reply', 'renote'], }); if ( @@ -821,8 +805,11 @@ export class ClientServerService { fastify.get<{ Params: { note: string; } }>('/embed/notes/:note', async (request, reply) => { reply.removeHeader('X-Frame-Options'); - const note = await this.notesRepository.findOneBy({ - id: request.params.note, + const note = await this.notesRepository.findOne({ + where: { + id: request.params.note, + }, + relations: ['user', 'reply', 'renote'], }); if (note == null) return; @@ -901,6 +888,22 @@ export class ClientServerService { [, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/'); fastify.get('/flush', async (request, reply) => { + let sendHeader = true; + + if (request.headers['origin']) { + const originURL = new URL(request.headers['origin']); + if (originURL.protocol !== 'https:') { // Clear-Site-Data only supports https + sendHeader = false; + } + if (originURL.host !== configUrl.host) { + sendHeader = false; + } + } + + if (sendHeader) { + reply.header('Clear-Site-Data', '"*"'); + } + reply.header('Set-Cookie', 'http-flush-failed=1; Path=/flush; Max-Age=60'); return await reply.view('flush'); }); diff --git a/packages/backend/src/server/web/boot.embed.js b/packages/backend/src/server/web/boot.embed.js index 9de1275380..022ff064ad 100644 --- a/packages/backend/src/server/web/boot.embed.js +++ b/packages/backend/src/server/web/boot.embed.js @@ -32,61 +32,30 @@ } //#region Detect language & fetch translations - if (!localStorage.hasOwnProperty('locale')) { - const supportedLangs = LANGS; - let lang = localStorage.getItem('lang'); - if (lang == null || !supportedLangs.includes(lang)) { - if (supportedLangs.includes(navigator.language)) { - lang = navigator.language; - } else { - lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); - - // Fallback - if (lang == null) lang = 'en-US'; - } - } - - const metaRes = await window.fetch('/api/meta', { - method: 'POST', - body: JSON.stringify({}), - credentials: 'omit', - cache: 'no-cache', - headers: { - 'Content-Type': 'application/json', - }, - }); - if (metaRes.status !== 200) { - renderError('META_FETCH'); - return; - } - const meta = await metaRes.json(); - const v = meta.version; - if (v == null) { - renderError('META_FETCH_V'); - return; - } - - // for https://github.com/misskey-dev/misskey/issues/10202 - if (lang == null || lang.toString == null || lang.toString() === 'null') { - console.error('invalid lang value detected!!!', typeof lang, lang); - lang = 'en-US'; - } - - const localRes = await window.fetch(`/assets/locales/${lang}.${v}.json`); - if (localRes.status === 200) { - localStorage.setItem('lang', lang); - localStorage.setItem('locale', await localRes.text()); - localStorage.setItem('localeVersion', v); + const supportedLangs = LANGS; + /** @type { string } */ + let lang = localStorage.getItem('lang'); + if (lang == null || !supportedLangs.includes(lang)) { + if (supportedLangs.includes(navigator.language)) { + lang = navigator.language; } else { - renderError('LOCALE_FETCH'); - return; + lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); + + // Fallback + if (lang == null) lang = 'en-US'; } } + + // for https://github.com/misskey-dev/misskey/issues/10202 + if (lang == null || lang.toString == null || lang.toString() === 'null') { + console.error('invalid lang value detected!!!', typeof lang, lang); + lang = 'en-US'; + } //#endregion //#region Script async function importAppScript() { - await import(`/embed_vite/${CLIENT_ENTRY}`) + await import(CLIENT_ENTRY ? `/embed_vite/${CLIENT_ENTRY.replace('scripts', lang)}` : '/embed_vite/src/_boot_.ts') .catch(async e => { console.error(e); renderError('APP_IMPORT'); @@ -115,10 +84,26 @@ await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve)); } - const locale = JSON.parse(localStorage.getItem('locale') || '{}'); + let messages = null; + const bootloaderLocales = localStorage.getItem('bootloaderLocales'); + if (bootloaderLocales) { + messages = JSON.parse(bootloaderLocales); + } + if (!messages) { + // older version of misskey does not store bootloaderLocales, stores locale as a whole + const legacyLocale = localStorage.getItem('locale'); + if (legacyLocale) { + const parsed = JSON.parse(legacyLocale); + messages = { + ...(parsed._bootErrors ?? {}), + reload: parsed.reload, + }; + } + } + if (!messages) messages = {}; - const title = locale?._bootErrors?.title || 'Failed to initialize Misskey'; - const reload = locale?.reload || 'Reload'; + const title = messages?.title || 'Failed to initialize Misskey'; + const reload = messages?.reload || 'Reload'; document.body.innerHTML = `
${title}
diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index 24794cbf2a..ab4b158287 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -13,7 +13,7 @@ }; window.onunhandledrejection = (e) => { console.error(e); - renderError('SOMETHING_HAPPENED_IN_PROMISE', e); + renderError('SOMETHING_HAPPENED_IN_PROMISE', e.reason || e); }; let forceError = localStorage.getItem('forceError'); @@ -22,62 +22,31 @@ return; } - //#region Detect language & fetch translations - if (!localStorage.hasOwnProperty('locale')) { - const supportedLangs = LANGS; - let lang = localStorage.getItem('lang'); - if (lang == null || !supportedLangs.includes(lang)) { - if (supportedLangs.includes(navigator.language)) { - lang = navigator.language; - } else { - lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); - - // Fallback - if (lang == null) lang = 'en-US'; - } - } - - const metaRes = await window.fetch('/api/meta', { - method: 'POST', - body: JSON.stringify({}), - credentials: 'omit', - cache: 'no-cache', - headers: { - 'Content-Type': 'application/json', - }, - }); - if (metaRes.status !== 200) { - renderError('META_FETCH'); - return; - } - const meta = await metaRes.json(); - const v = meta.version; - if (v == null) { - renderError('META_FETCH_V'); - return; - } - - // for https://github.com/misskey-dev/misskey/issues/10202 - if (lang == null || lang.toString == null || lang.toString() === 'null') { - console.error('invalid lang value detected!!!', typeof lang, lang); - lang = 'en-US'; - } - - const localRes = await window.fetch(`/assets/locales/${lang}.${v}.json`); - if (localRes.status === 200) { - localStorage.setItem('lang', lang); - localStorage.setItem('locale', await localRes.text()); - localStorage.setItem('localeVersion', v); + //#region Detect language + const supportedLangs = LANGS; + /** @type { string } */ + let lang = localStorage.getItem('lang'); + if (lang == null || !supportedLangs.includes(lang)) { + if (supportedLangs.includes(navigator.language)) { + lang = navigator.language; } else { - renderError('LOCALE_FETCH'); - return; + lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); + + // Fallback + if (lang == null) lang = 'en-US'; } } + + // for https://github.com/misskey-dev/misskey/issues/10202 + if (lang == null || lang.toString == null || lang.toString() === 'null') { + console.error('invalid lang value detected!!!', typeof lang, lang); + lang = 'en-US'; + } //#endregion //#region Script async function importAppScript() { - await import(`/vite/${CLIENT_ENTRY}`) + await import(CLIENT_ENTRY ? `/vite/${CLIENT_ENTRY.replace('scripts', lang)}` : '/vite/src/_boot_.ts') .catch(async e => { console.error(e); renderError('APP_IMPORT', e); @@ -94,23 +63,37 @@ } //#endregion - //#region Theme - const theme = localStorage.getItem('theme'); - if (theme) { - for (const [k, v] of Object.entries(JSON.parse(theme))) { - document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString()); + let isSafeMode = (localStorage.getItem('isSafeMode') === 'true'); - // HTMLの theme-color 適用 - if (k === 'htmlThemeColor') { - for (const tag of document.head.children) { - if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') { - tag.setAttribute('content', v); - break; + if (!isSafeMode) { + const urlParams = new URLSearchParams(window.location.search); + + if (urlParams.has('safemode') && urlParams.get('safemode') === 'true') { + localStorage.setItem('isSafeMode', 'true'); + isSafeMode = true; + } + } + + //#region Theme + if (!isSafeMode) { + const theme = localStorage.getItem('theme'); + if (theme) { + for (const [k, v] of Object.entries(JSON.parse(theme))) { + document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString()); + + // HTMLの theme-color 適用 + if (k === 'htmlThemeColor') { + for (const tag of document.head.children) { + if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') { + tag.setAttribute('content', v); + break; + } } } } } } + const colorScheme = localStorage.getItem('colorScheme'); if (colorScheme) { document.documentElement.style.setProperty('color-scheme', colorScheme); @@ -127,11 +110,13 @@ document.documentElement.classList.add('useSystemFont'); } - const customCss = localStorage.getItem('customCss'); - if (customCss && customCss.length > 0) { - const style = document.createElement('style'); - style.innerHTML = customCss; - document.head.appendChild(style); + if (!isSafeMode) { + const customCss = localStorage.getItem('customCss'); + if (customCss && customCss.length > 0) { + const style = document.createElement('style'); + style.innerHTML = customCss; + document.head.appendChild(style); + } } async function addStyle(styleText) { @@ -146,9 +131,25 @@ await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve)); } - const locale = JSON.parse(localStorage.getItem('locale') || '{}'); + let messages = null; + const bootloaderLocales = localStorage.getItem('bootloaderLocales'); + if (bootloaderLocales) { + messages = JSON.parse(bootloaderLocales); + } + if (!messages) { + // older version of misskey does not store bootloaderLocales, stores locale as a whole + const legacyLocale = localStorage.getItem('locale'); + if (legacyLocale) { + const parsed = JSON.parse(legacyLocale); + messages = { + ...(parsed._bootErrors ?? {}), + reload: parsed.reload, + }; + } + } + if (!messages) messages = {}; - const messages = Object.assign({ + messages = Object.assign({ title: 'Failed to initialize Misskey', solution: 'The following actions may solve the problem.', solution1: 'Update your os and browser', @@ -159,8 +160,12 @@ otherOption1: 'Clear preferences and cache', otherOption2: 'Start the simple client', otherOption3: 'Start the repair tool', - }, locale?._bootErrors || {}); - const reload = locale?.reload || 'Reload'; + otherOption4: 'Start Misskey in safe mode', + reload: 'Reload', + }, messages); + + const safeModeUrl = new URL(window.location.href); + safeModeUrl.searchParams.set('safemode', 'true'); let errorsElement = document.getElementById('errors'); @@ -173,7 +178,7 @@

${messages.title}

${messages.solution}

${messages.solution1}

@@ -182,6 +187,12 @@

${messages.solution4}

${messages.otherOption} + + + +