merge: version 2024.9.0 (!675)

View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/675

Approved-by: Julia <julia@insertdomain.name>
Approved-by: Marie <github@yuugi.dev>
This commit is contained in:
dakkar 2024-10-18 21:14:49 +00:00
commit ba17776b19
677 changed files with 24067 additions and 10325 deletions

View file

@ -0,0 +1,211 @@
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Misskey configuration
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ┌─────┐
#───┘ URL └─────────────────────────────────────────────────────
# Final accessible URL seen by a user.
url: 'http://misskey.local'
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# URL SETTINGS AFTER THAT!
# ┌───────────────────────┐
#───┘ Port and TLS settings └───────────────────────────────────
#
# Misskey requires a reverse proxy to support HTTPS connections.
#
# +----- https://example.tld/ ------------+
# +------+ |+-------------+ +----------------+|
# | User | ---> || Proxy (443) | ---> | Misskey (3000) ||
# +------+ |+-------------+ +----------------+|
# +---------------------------------------+
#
# You need to set up a reverse proxy. (e.g. nginx)
# An encrypted connection with HTTPS is highly recommended
# because tokens may be transferred in GET requests.
# The port that your Misskey server should listen on.
port: 61812
# ┌──────────────────────────┐
#───┘ PostgreSQL configuration └────────────────────────────────
db:
host: db
port: 5432
# Database name
db: misskey
# Auth
user: postgres
pass: postgres
# Whether disable Caching queries
#disableCache: true
# Extra Connection options
#extra:
# ssl: true
dbReplications: false
# You can configure any number of replicas here
#dbSlaves:
# -
# host:
# port:
# db:
# user:
# pass:
# -
# host:
# port:
# db:
# user:
# pass:
# ┌─────────────────────┐
#───┘ Redis configuration └─────────────────────────────────────
redis:
host: redis
port: 6379
#family: 0 # 0=Both, 4=IPv4, 6=IPv6
#pass: example-pass
#prefix: example-prefix
#db: 1
#redisForPubsub:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
#redisForJobQueue:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
#redisForTimelines:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
#redisForReactions:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────
#meilisearch:
# host: meilisearch
# port: 7700
# apiKey: ''
# ssl: true
# index: ''
# ┌───────────────┐
#───┘ ID generation └───────────────────────────────────────────
# You can select the ID generation method.
# You don't usually need to change this setting, but you can
# change it according to your preferences.
# Available methods:
# aid ... Short, Millisecond accuracy
# aidx ... Millisecond accuracy
# meid ... Similar to ObjectID, Millisecond accuracy
# ulid ... Millisecond accuracy
# objectid ... This is left for backward compatibility
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# ID SETTINGS AFTER THAT!
id: 'aidx'
# ┌────────────────┐
#───┘ Error tracking └──────────────────────────────────────────
# Sentry is available for error tracking.
# See the Sentry documentation for more details on options.
#sentryForBackend:
# enableNodeProfiling: true
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
#sentryForFrontend:
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
# Whether disable HSTS
#disableHsts: true
# Number of worker processes
#clusterLimit: 1
# Job concurrency per worker
# deliverJobConcurrency: 128
# inboxJobConcurrency: 16
# Job rate limiter
# deliverJobPerSec: 128
# inboxJobPerSec: 32
# Job attempts
# deliverJobMaxAttempts: 12
# inboxJobMaxAttempts: 8
# IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4
# Proxy for HTTP/HTTPS
#proxy: http://127.0.0.1:3128
proxyBypassHosts:
- api.deepl.com
- api-free.deepl.com
- www.recaptcha.net
- hcaptcha.com
- challenges.cloudflare.com
# Proxy for SMTP/SMTPS
#proxySmtp: http://127.0.0.1:3128 # use HTTP/1.1 CONNECT
#proxySmtp: socks4://127.0.0.1:1080 # use SOCKS4
#proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5
# Media Proxy
#mediaProxy: https://example.com/proxy
# Proxy remote files (default: true)
proxyRemoteFiles: true
# Sign to ActivityPub GET request (default: true)
signToActivityPubGet: true
allowedPrivateNetworks: [
'127.0.0.1/32'
]
# Upload or download file size limits (bytes)
#maxFileSize: 262144000

View file

@ -163,6 +163,14 @@ redis:
# #prefix: example-prefix # #prefix: example-prefix
# #db: 1 # #db: 1
#redisForReactions:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# ┌───────────────────────────┐ # ┌───────────────────────────┐
#───┘ MeiliSearch configuration └───────────────────────────── #───┘ MeiliSearch configuration └─────────────────────────────

View file

@ -172,6 +172,16 @@ redis:
# # You can specify more ioredis options... # # You can specify more ioredis options...
# #username: example-username # #username: example-username
#redisForReactions:
# host: localhost
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# # You can specify more ioredis options...
# #username: example-username
# ┌───────────────────────────┐ # ┌───────────────────────────┐
#───┘ MeiliSearch configuration └───────────────────────────── #───┘ MeiliSearch configuration └─────────────────────────────

View file

@ -103,6 +103,14 @@ redis:
# #prefix: example-prefix # #prefix: example-prefix
# #db: 1 # #db: 1
#redisForReactions:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# ┌───────────────────────────┐ # ┌───────────────────────────┐
#───┘ MeiliSearch configuration └───────────────────────────── #───┘ MeiliSearch configuration └─────────────────────────────

View file

@ -3,6 +3,8 @@
set -xe set -xe
sudo chown node node_modules sudo chown node node_modules
sudo apt-get update
sudo apt-get -y install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2 libxtst6 xauth xvfb
git config --global --add safe.directory /workspace git config --global --add safe.directory /workspace
git submodule update --init git submodule update --init
corepack install corepack install
@ -12,3 +14,4 @@ pnpm install --frozen-lockfile
cp .devcontainer/devcontainer.yml .config/default.yml cp .devcontainer/devcontainer.yml .config/default.yml
pnpm build pnpm build
pnpm migrate pnpm migrate
pnpm exec cypress install

10
.gitignore vendored
View file

@ -38,6 +38,7 @@ coverage
!/.config/example.yml !/.config/example.yml
!/.config/docker_example.yml !/.config/docker_example.yml
!/.config/docker_example.env !/.config/docker_example.env
!/.config/cypress-devcontainer.yml
docker-compose.yml docker-compose.yml
compose.yml compose.yml
.devcontainer/compose.yml .devcontainer/compose.yml
@ -47,6 +48,7 @@ compose.yml
/build /build
built built
built-test built-test
js-built
/data /data
/.cache-loader /.cache-loader
/db /db
@ -66,8 +68,9 @@ temp
tsdoc-metadata.json tsdoc-metadata.json
misskey-assets misskey-assets
# Sharkey # Vite temporary files
/packages/megalodon/lib vite.config.js.timestamp-*
vite.config.ts.timestamp-*
# blender backups # blender backups
*.blend1 *.blend1
@ -78,3 +81,6 @@ misskey-assets
# VSCode addon # VSCode addon
.favorites.json .favorites.json
# Sharkey
/packages/megalodon/lib

View file

@ -20,9 +20,9 @@ testCommit:
- pnpm install --frozen-lockfile - pnpm install --frozen-lockfile
- pnpm run build - pnpm run build
- pnpm run migrate - pnpm run migrate
- pnpm run --filter='!megalodon' --workspace-concurrency=1 test - pnpm run --filter='!megalodon' test
- pnpm run --filter=backend lint - pnpm run --filter=backend --filter=misskey-js lint
- pnpm run --filter=frontend eslint - pnpm run --filter=frontend --filter=frontend-embed eslint
cache: cache:
key: test key: test
policy: pull-push policy: pull-push

View file

@ -1,3 +1,56 @@
## 2024.9.0
### General
- Feat: ノート単体・ユーザーのノート・クリップのノートの埋め込み機能
- 埋め込みコードやウェブサイトへの実装方法の詳細は https://misskey-hub.net/docs/for-users/features/embed/ をご覧ください
- Feat: パスキーでログインボタンを実装 (#14574)
- Feat: フォローされた際のメッセージを設定できるように
- Feat: 連合をホワイトリスト制にできるように
- Feat: UserWebhookとSystemWebhookのテスト送信機能を追加 (#14445)
- Feat: モデレーターはユーザーにかかわらずファイルが添付されているノートを検索できるように
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/680)
- Feat: データエクスポートが完了した際に通知を発行するように
- Enhance: ユーザーによるコンテンツインポートの可否をロールポリシーで制御できるように
- Enhance: 依存関係の更新
- Enhance: l10nの更新
### Client
- Enhance: サイズ制限を超過するファイルをアップロードしようとした際にエラーを出すように
- Enhance: アイコンデコレーション管理画面にプレビューを追加
- Enhance: コントロールパネル内のファイル一覧でセンシティブなファイルを区別しやすく
- Enhance: ScratchpadにUIインスペクターを追加
- Enhance: Play編集画面の項目の並びを少しリデザイン
- Enhance: 各種メニューをドロワー表示するかどうか設定可能に
- Enhance: AiScriptのMk:C:containerのオプションに`borderStyle`と`borderRadius`を追加
- Enhance: CWでも絵文字をクリックしてメニューを表示できるように
- Fix: サーバーメトリクスが2つ以上あるとリロード直後の表示がおかしくなる問題を修正
- Fix: コントロールパネル内のAp requests内のチャートの表示がおかしかった問題を修正
- Fix: 月の違う同じ日はセパレータが表示されないのを修正
- Fix: タッチ画面でレンジスライダーを操作するとツールチップが複数表示される問題を修正
(Cherry-picked from https://github.com/taiyme/misskey/pull/265)
- Fix: 縦横比が極端なカスタム絵文字を表示する際にレイアウトが崩れる箇所があるのを修正
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/725)
- Fix: 設定変更時のリロード確認ダイアログが複数個表示されることがある問題を修正
- Fix: ファイルの詳細ページのファイルの説明で改行が正しく表示されない問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/bde6bb0bd2e8b0d027e724d2acdb8ae0585a8110)
- Fix: 一部画面のページネーションが動作しにくくなっていたのを修正 ( #12766 , #11449 )
### Server
- Feat: Misskey® Reactions Boost Technology™ (RBT)により、リアクションの作成負荷を低減することが可能に
- Fix: アンテナの書き込み時にキーワードが与えられなかった場合のエラーをApiErrorとして投げるように
- この変更により、公式フロントエンドでは入力の不備が内部エラーとして報告される代わりに一般的なエラーダイアログで報告されます
- Fix: ファイルがサイズの制限を超えてアップロードされた際にエラーを返さなかった問題を修正
- Fix: 外部ページを解析する際に、ページに紐づけられた関連リソースも読み込まれてしまう問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/26e0412fbb91447c37e8fb06ffb0487346063bb8)
- Fix: Continue importing from file if single emoji import fails
- Fix: `Retry-After`ヘッダーが送信されなかった問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/8a982c61c01909e7540ff1be9f019df07c3f0624)
- Fix: サーバーサイドのDOM解析完了時にリソースを開放するように
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/634)
- Fix: `<link rel="alternate">`を追って照会するのはOKレスポンスが返却された場合のみに
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/633)
- Fix: メールにスタイルが適用されていなかった問題を修正
## 2024.8.0 ## 2024.8.0
### General ### General

View file

@ -574,6 +574,26 @@ marginはそのコンポーネントを使う側が設定する
### indexというファイル名を使うな ### indexというファイル名を使うな
ESMではディレクトリインポートは廃止されているのと、ディレクトリインポートせずともファイル名が index だと何故か一部のライブラリ?でディレクトリインポートだと見做されてエラーになる ESMではディレクトリインポートは廃止されているのと、ディレクトリインポートせずともファイル名が index だと何故か一部のライブラリ?でディレクトリインポートだと見做されてエラーになる
## CSS Recipe
### Lighten CSS vars
``` css
color: hsl(from var(--accent) h s calc(l + 10));
```
### Darken CSS vars
``` css
color: hsl(from var(--accent) h s calc(l - 10));
```
### Add alpha to CSS vars
``` css
color: color(from var(--accent) srgb r g b / 0.5);
```
## Merging from Misskey into Sharkey ## Merging from Misskey into Sharkey
Make sure you have both remotes in the same clone (`git remote add misskey Make sure you have both remotes in the same clone (`git remote add misskey
@ -591,15 +611,11 @@ seems to do a decent job)
*after that commit*, do all the extra work, on the same branch: *after that commit*, do all the extra work, on the same branch:
* copy all changes (commit after each step): * copy all changes (commit after each step):
* in `packages/backend/src/core/NoteCreateService.ts`, from `create` to
`import` (and vice versa if `git` got confused!)
* in * in
`packages/backend/src/core/activitypub/models/ApNoteService.ts`, `packages/backend/src/core/activitypub/models/ApNoteService.ts`,
from `createNote` to `updateNote` from `createNote` to `updateNote`
* from `packages/backend/src/core/NoteCreateService.ts` to * from `packages/backend/src/core/NoteCreateService.ts` to
`packages/backend/src/core/NoteEditService.vue` `packages/backend/src/core/NoteEditService.vue`
* in `packages/backend/src/core/activitypub/models/ApNoteService.ts`,
from `createNote` to `updateNote`
* from `packages/backend/src/server/api/endpoints/notes/create.ts` * from `packages/backend/src/server/api/endpoints/notes/create.ts`
to `packages/backend/src/server/api/endpoints/notes/edit.ts` to `packages/backend/src/server/api/endpoints/notes/edit.ts`
* from `packages/frontend/src/components/MkNote*.vue` to * from `packages/frontend/src/components/MkNote*.vue` to
@ -615,9 +631,9 @@ seems to do a decent job)
* check the changes against our `develop` (`git diff develop`) and * check the changes against our `develop` (`git diff develop`) and
against Misskey (`git diff misskey/develop`) against Misskey (`git diff misskey/develop`)
* re-generate `misskey-js` (`pnpm build-misskey-js-with-types`) and commit * re-generate `misskey-js` (`pnpm build-misskey-js-with-types`) and commit
* build the frontend: `rm -rf built/; NODE_ENV=development pnpm --filter=frontend * build the frontend: `rm -rf built/; NODE_ENV=development pnpm
build` (the `development` tells it to keep some of the original --filter=frontend --filter=frontend-embed build` (the `development`
filenames in the built files) tells it to keep some of the original filenames in the built files)
* make sure there aren't any new `ti-*` classes (Tabler Icons), and * make sure there aren't any new `ti-*` classes (Tabler Icons), and
replace them with appropriate `ph-*` ones (Phosphor Icons): replace them with appropriate `ph-*` ones (Phosphor Icons):
`grep -rP '["'\'']ti[ -](?!fw)' -- built/` should show you what to change. `grep -rP '["'\'']ti[ -](?!fw)' -- built/` should show you what to change.

View file

@ -23,9 +23,10 @@ RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
RUN pnpm build RUN pnpm build
RUN node scripts/trim-deps.mjs RUN node scripts/trim-deps.mjs
RUN mv packages/frontend/assets sharkey-assets RUN mv packages/frontend/assets sharkey-assets
RUN mv packages/frontend-embed/assets sharkey-embed-assets
RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \ RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
pnpm prune pnpm prune
RUN rm -r node_modules packages/frontend packages/sw RUN rm -r node_modules packages/frontend packages/frontend-shared packages/frontend-embed packages/sw
RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \ RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
pnpm i --prod --frozen-lockfile --aggregate-output pnpm i --prod --frozen-lockfile --aggregate-output
RUN rm -rf .git RUN rm -rf .git
@ -64,6 +65,7 @@ COPY --chown=sharkey:sharkey --from=build /sharkey/packages/megalodon/lib ./pack
COPY --chown=sharkey:sharkey --from=build /sharkey/fluent-emojis ./fluent-emojis COPY --chown=sharkey:sharkey --from=build /sharkey/fluent-emojis ./fluent-emojis
COPY --chown=sharkey:sharkey --from=build /sharkey/tossface-emojis/dist ./tossface-emojis/dist COPY --chown=sharkey:sharkey --from=build /sharkey/tossface-emojis/dist ./tossface-emojis/dist
COPY --chown=sharkey:sharkey --from=build /sharkey/sharkey-assets ./packages/frontend/assets COPY --chown=sharkey:sharkey --from=build /sharkey/sharkey-assets ./packages/frontend/assets
COPY --chown=sharkey:sharkey --from=build /sharkey/sharkey-embed-assets ./packages/frontend-embed/assets
COPY --chown=sharkey:sharkey pnpm-workspace.yaml ./pnpm-workspace.yaml COPY --chown=sharkey:sharkey pnpm-workspace.yaml ./pnpm-workspace.yaml
COPY --chown=sharkey:sharkey packages/backend/package.json ./packages/backend/package.json COPY --chown=sharkey:sharkey packages/backend/package.json ./packages/backend/package.json

View file

@ -124,6 +124,14 @@ redis:
# #prefix: example-prefix # #prefix: example-prefix
# #db: 1 # #db: 1
#redisForReactions:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# ┌───────────────────────────┐ # ┌───────────────────────────┐
#───┘ MeiliSearch configuration └───────────────────────────── #───┘ MeiliSearch configuration └─────────────────────────────

View file

@ -1,4 +1,4 @@
files: files:
- source: /locales/ja-JP.yml - source: /sharkey-locales/en-US.yml
translation: /locales/%locale%.yml translation: /sharkey-locales/%locale%.yml
update_option: update_as_unapproved update_option: update_as_unapproved

41
idea/MkDisableSection.vue Normal file
View file

@ -0,0 +1,41 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="[$style.root]">
<div :inert="disabled" :class="[{ [$style.disabled]: disabled }]">
<slot></slot>
</div>
<div v-if="disabled" :class="[$style.cover]"></div>
</div>
</template>
<script lang="ts" setup>
defineProps<{
disabled?: boolean;
}>();
</script>
<style lang="scss" module>
.root {
position: relative;
}
.disabled {
opacity: 0.7;
}
.cover {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
cursor: not-allowed;
--color: color(from var(--error) srgb r g b / 0.25);
background-size: auto auto;
background-image: repeating-linear-gradient(135deg, transparent, transparent 10px, var(--color) 4px, var(--color) 14px);
}
</style>

1
idea/README.md Normal file
View file

@ -0,0 +1 @@
使われなくなったけど消すのは勿体ない(将来使えるかもしれない)コードを入れておくとこ

View file

@ -626,10 +626,7 @@ abuseReported: "أُرسل البلاغ، شكرًا لك"
reporter: "المُبلّغ" reporter: "المُبلّغ"
reporteeOrigin: "أصل البلاغ" reporteeOrigin: "أصل البلاغ"
reporterOrigin: "أصل المُبلّغ" reporterOrigin: "أصل المُبلّغ"
forwardReport: "وجّه البلاغ إلى المثيل البعيد"
forwardReportIsAnonymous: "في المثيل البعيد سيظهر المبلّغ كحساب مجهول."
send: "أرسل" send: "أرسل"
abuseMarkAsResolved: "علّم البلاغ كمحلول"
openInNewTab: "افتح في لسان جديد" openInNewTab: "افتح في لسان جديد"
defaultNavigationBehaviour: "سلوك الملاحة الافتراضي" defaultNavigationBehaviour: "سلوك الملاحة الافتراضي"
editTheseSettingsMayBreakAccount: "تعديل هذه الإعدادات قد يسبب عطبًا لحسابك" editTheseSettingsMayBreakAccount: "تعديل هذه الإعدادات قد يسبب عطبًا لحسابك"
@ -1533,6 +1530,7 @@ _notification:
reaction: "التفاعل" reaction: "التفاعل"
receiveFollowRequest: "طلبات المتابعة" receiveFollowRequest: "طلبات المتابعة"
followRequestAccepted: "طلبات المتابعة المقبولة" followRequestAccepted: "طلبات المتابعة المقبولة"
login: "لِج"
app: "إشعارات التطبيقات المرتبطة" app: "إشعارات التطبيقات المرتبطة"
_actions: _actions:
followBack: "تابعك بالمثل" followBack: "تابعك بالمثل"

View file

@ -451,7 +451,6 @@ or: "অথবা"
language: "ভাষা" language: "ভাষা"
uiLanguage: "UI এর ভাষা" uiLanguage: "UI এর ভাষা"
aboutX: "{x} সম্পর্কে" aboutX: "{x} সম্পর্কে"
disableDrawer: "ড্রয়ার মেনু প্রদর্শন করবেন না"
noHistory: "কোনো ইতিহাস নেই" noHistory: "কোনো ইতিহাস নেই"
signinHistory: "প্রবেশ করার ইতিহাস" signinHistory: "প্রবেশ করার ইতিহাস"
doing: "প্রক্রিয়া করছে..." doing: "প্রক্রিয়া করছে..."
@ -625,10 +624,7 @@ abuseReported: "আপনার অভিযোগটি দাখিল কর
reporter: "অভিযোগকারী" reporter: "অভিযোগকারী"
reporteeOrigin: "অভিযোগটির উৎস" reporteeOrigin: "অভিযোগটির উৎস"
reporterOrigin: "অভিযোগকারীর উৎস" reporterOrigin: "অভিযোগকারীর উৎস"
forwardReport: "রিমোট ইন্সত্যান্সে অভিযোগটি পাঠান"
forwardReportIsAnonymous: "আপনার তথ্য রিমোট ইন্সত্যান্সে পাঠানো হবে না এবং একটি বেনামী সিস্টেম অ্যাকাউন্ট হিসাবে প্রদর্শিত হবে।"
send: "পাঠান" send: "পাঠান"
abuseMarkAsResolved: "অভিযোগটিকে সমাধাকৃত হিসাবে চিহ্নিত করুন"
openInNewTab: "নতুন ট্যাবে খুলুন" openInNewTab: "নতুন ট্যাবে খুলুন"
openInSideView: "সাইড ভিউতে খুলুন" openInSideView: "সাইড ভিউতে খুলুন"
defaultNavigationBehaviour: "ডিফল্ট নেভিগেশন" defaultNavigationBehaviour: "ডিফল্ট নেভিগেশন"
@ -1314,6 +1310,7 @@ _notification:
pollEnded: "পোল শেষ" pollEnded: "পোল শেষ"
receiveFollowRequest: "প্রাপ্ত অনুসরণের অনুরোধসমূহ" receiveFollowRequest: "প্রাপ্ত অনুসরণের অনুরোধসমূহ"
followRequestAccepted: "গৃহীত অনুসরণের অনুরোধসমূহ" followRequestAccepted: "গৃহীত অনুসরণের অনুরোধসমূহ"
login: "প্রবেশ করুন"
app: "লিঙ্ক করা অ্যাপ থেকে বিজ্ঞপ্তি" app: "লিঙ্ক করা অ্যাপ থেকে বিজ্ঞপ্তি"
_actions: _actions:
followBack: "ফলো ব্যাক করেছে" followBack: "ফলো ব্যাক করেছে"

View file

@ -8,6 +8,8 @@ search: "Cercar"
notifications: "Notificacions" notifications: "Notificacions"
username: "Nom d'usuari" username: "Nom d'usuari"
password: "Contrasenya" password: "Contrasenya"
initialPasswordForSetup: "Contrasenya inicial per la configuració inicial"
initialPasswordIsIncorrect: "La contrasenya no és correcta."
forgotPassword: "Contrasenya oblidada" forgotPassword: "Contrasenya oblidada"
fetchingAsApObject: "Cercant en el Fediverse..." fetchingAsApObject: "Cercant en el Fediverse..."
ok: "OK" ok: "OK"
@ -60,6 +62,7 @@ copyFileId: "Copiar ID d'arxiu"
copyFolderId: "Copiar ID de carpeta" copyFolderId: "Copiar ID de carpeta"
copyProfileUrl: "Copiar URL del perfil" copyProfileUrl: "Copiar URL del perfil"
searchUser: "Cercar un usuari" searchUser: "Cercar un usuari"
searchThisUsersNotes: "Cerca les publicacions de l'usuari"
reply: "Respondre" reply: "Respondre"
loadMore: "Carregar més" loadMore: "Carregar més"
showMore: "Veure més" showMore: "Veure més"
@ -108,11 +111,14 @@ enterEmoji: "Introduir un emoji"
renote: "Impulsa" renote: "Impulsa"
unrenote: "Anul·la l'impuls" unrenote: "Anul·la l'impuls"
renoted: "S'ha impulsat" renoted: "S'ha impulsat"
renotedToX: "Impulsat per {name}."
cantRenote: "No es pot impulsar aquesta publicació" cantRenote: "No es pot impulsar aquesta publicació"
cantReRenote: "No es pot impulsar l'impuls." cantReRenote: "No es pot impulsar l'impuls."
quote: "Cita" quote: "Cita"
inChannelRenote: "Renotar només al Canal" inChannelRenote: "Renotar només al Canal"
inChannelQuote: "Citar només al Canal" inChannelQuote: "Citar només al Canal"
renoteToChannel: "Impulsa a un canal"
renoteToOtherChannel: "Impulsa a un altre canal"
pinnedNote: "Nota fixada" pinnedNote: "Nota fixada"
pinned: "Fixar al perfil" pinned: "Fixar al perfil"
you: "Tu" you: "Tu"
@ -151,6 +157,7 @@ editList: "Editar llista"
selectChannel: "Selecciona un canal" selectChannel: "Selecciona un canal"
selectAntenna: "Tria una antena" selectAntenna: "Tria una antena"
editAntenna: "Modificar antena" editAntenna: "Modificar antena"
createAntenna: "Crea una antena"
selectWidget: "Triar un giny" selectWidget: "Triar un giny"
editWidgets: "Editar ginys" editWidgets: "Editar ginys"
editWidgetsExit: "Fet" editWidgetsExit: "Fet"
@ -177,6 +184,10 @@ addAccount: "Afegeix un compte"
reloadAccountsList: "Recarregar la llista de contactes" reloadAccountsList: "Recarregar la llista de contactes"
loginFailed: "S'ha produït un error al accedir." loginFailed: "S'ha produït un error al accedir."
showOnRemote: "Navega més en el perfil original" showOnRemote: "Navega més en el perfil original"
continueOnRemote: "Veure perfil original"
chooseServerOnMisskeyHub: "Escull un servidor des del Hub de Misskey"
specifyServerHost: "Especifica un servidor directament"
inputHostName: "Introdueix el domini"
general: "General" general: "General"
wallpaper: "Fons de Pantalla" wallpaper: "Fons de Pantalla"
setWallpaper: "Defineix el fons de pantalla" setWallpaper: "Defineix el fons de pantalla"
@ -187,6 +198,7 @@ followConfirm: "Estàs segur que vols deixar de seguir {name}?"
proxyAccount: "Compte de proxy" proxyAccount: "Compte de proxy"
proxyAccountDescription: "Un compte proxy és un compte que actua com a seguidor remot per als usuaris en determinades condicions. Per exemple, quan un usuari afegeix un usuari remot a la llista, l'activitat de l'usuari remot no es lliurarà al servidor si cap usuari local segueix aquest usuari, de manera que el compte proxy el seguirà." proxyAccountDescription: "Un compte proxy és un compte que actua com a seguidor remot per als usuaris en determinades condicions. Per exemple, quan un usuari afegeix un usuari remot a la llista, l'activitat de l'usuari remot no es lliurarà al servidor si cap usuari local segueix aquest usuari, de manera que el compte proxy el seguirà."
host: "Amfitrió" host: "Amfitrió"
selectSelf: "Escollir manualment"
selectUser: "Selecciona usuari/a" selectUser: "Selecciona usuari/a"
recipient: "Destinatari" recipient: "Destinatari"
annotation: "Comentaris" annotation: "Comentaris"
@ -202,6 +214,7 @@ perDay: "Per dia"
stopActivityDelivery: "Deixa d'enviar activitats" stopActivityDelivery: "Deixa d'enviar activitats"
blockThisInstance: "Deixa d'enviar activitats" blockThisInstance: "Deixa d'enviar activitats"
silenceThisInstance: "Silencia aquesta instància " silenceThisInstance: "Silencia aquesta instància "
mediaSilenceThisInstance: "Silenciar els arxius d'aquesta instància "
operations: "Accions" operations: "Accions"
software: "Programari" software: "Programari"
version: "Versió" version: "Versió"
@ -223,6 +236,10 @@ blockedInstances: "Instàncies bloquejades"
blockedInstancesDescription: "Llista els enllaços d'amfitrió de les instàncies que vols bloquejar separades per un salt de pàgina. Les instàncies llistades no podran comunicar-se amb aquesta instància." blockedInstancesDescription: "Llista els enllaços d'amfitrió de les instàncies que vols bloquejar separades per un salt de pàgina. Les instàncies llistades no podran comunicar-se amb aquesta instància."
silencedInstances: "Instàncies silenciades" silencedInstances: "Instàncies silenciades"
silencedInstancesDescription: "Llista els enllaços d'amfitrió de les instàncies que vols silenciar. Tots els comptes de les instàncies llistades s'establiran com silenciades i només podran fer sol·licitacions de seguiment, i no podran mencionar als comptes locals si no els segueixen. Això no afectarà les instàncies bloquejades." silencedInstancesDescription: "Llista els enllaços d'amfitrió de les instàncies que vols silenciar. Tots els comptes de les instàncies llistades s'establiran com silenciades i només podran fer sol·licitacions de seguiment, i no podran mencionar als comptes locals si no els segueixen. Això no afectarà les instàncies bloquejades."
mediaSilencedInstances: "Instàncies amb els arxius silenciats"
mediaSilencedInstancesDescription: "Llista els noms dels servidors que vulguis silenciar els arxius, un servidor per línia. Tots els comptes que pertanyin als servidors llistats seran tractats com sensibles i no podran fer servir emojis personalitzats. Això no tindrà efecte sobre els servidors blocats."
federationAllowedHosts: "Llista de servidors federats"
federationAllowedHostsDescription: "Llista dels servidors amb els quals es federa."
muteAndBlock: "Silencia i bloca" muteAndBlock: "Silencia i bloca"
mutedUsers: "Usuaris silenciats" mutedUsers: "Usuaris silenciats"
blockedUsers: "Usuaris bloquejats" blockedUsers: "Usuaris bloquejats"
@ -313,6 +330,7 @@ selectFile: "Selecciona fitxers"
selectFiles: "Selecciona fitxers" selectFiles: "Selecciona fitxers"
selectFolder: "Selecció de carpeta" selectFolder: "Selecció de carpeta"
selectFolders: "Selecció de carpeta" selectFolders: "Selecció de carpeta"
fileNotSelected: "Cap fitxer seleccionat"
renameFile: "Canvia el nom del fitxer" renameFile: "Canvia el nom del fitxer"
folderName: "Nom de la carpeta" folderName: "Nom de la carpeta"
createFolder: "Crea una carpeta" createFolder: "Crea una carpeta"
@ -320,6 +338,7 @@ renameFolder: "Canvia el nom de la carpeta"
deleteFolder: "Elimina la carpeta" deleteFolder: "Elimina la carpeta"
folder: "Carpeta " folder: "Carpeta "
addFile: "Afegeix un fitxer" addFile: "Afegeix un fitxer"
showFile: "Mostrar fitxer"
emptyDrive: "La teva unitat és buida" emptyDrive: "La teva unitat és buida"
emptyFolder: "La carpeta està buida" emptyFolder: "La carpeta està buida"
unableToDelete: "No es pot eliminar" unableToDelete: "No es pot eliminar"
@ -468,10 +487,12 @@ retype: "Torneu a introduir-la"
noteOf: "Publicació de: {user}" noteOf: "Publicació de: {user}"
quoteAttached: "Frase adjunta" quoteAttached: "Frase adjunta"
quoteQuestion: "Vols annexar-la com a cita?" quoteQuestion: "Vols annexar-la com a cita?"
attachAsFileQuestion: "El text copiat és massa llarg. Vols adjuntar-lo com un fitxer de text?"
noMessagesYet: "Encara no hi ha missatges" noMessagesYet: "Encara no hi ha missatges"
newMessageExists: "Has rebut un nou missatge" newMessageExists: "Has rebut un nou missatge"
onlyOneFileCanBeAttached: "Només pots adjuntar un fitxer a un missatge" onlyOneFileCanBeAttached: "Només pots adjuntar un fitxer a un missatge"
signinRequired: "Si us plau, Registra't o inicia la sessió abans de continuar" signinRequired: "Si us plau, Registra't o inicia la sessió abans de continuar"
signinOrContinueOnRemote: "Per continuar necessites moure el teu servidor o registrar-te / iniciar sessió en aquest servidor."
invitations: "Convida" invitations: "Convida"
invitationCode: "Codi d'invitació" invitationCode: "Codi d'invitació"
checking: "Comprovació en curs..." checking: "Comprovació en curs..."
@ -493,7 +514,10 @@ uiLanguage: "Idioma de l'interfície"
aboutX: "Respecte a {x}" aboutX: "Respecte a {x}"
emojiStyle: "Estil d'emoji" emojiStyle: "Estil d'emoji"
native: "Nadiu" native: "Nadiu"
disableDrawer: "No mostrar els menús en calaixos" menuStyle: "Estil de menú"
style: "Estil"
drawer: "Calaix"
popup: "Emergent"
showNoteActionsOnlyHover: "Només mostra accions de la nota en passar amb el cursor" showNoteActionsOnlyHover: "Només mostra accions de la nota en passar amb el cursor"
showReactionsCount: "Mostra el nombre de reaccions a les publicacions" showReactionsCount: "Mostra el nombre de reaccions a les publicacions"
noHistory: "No hi ha un registre previ" noHistory: "No hi ha un registre previ"
@ -543,7 +567,7 @@ objectStorageUseSSLDesc: "Desactiva'l si no tens pensat fer servir HTTPS per les
objectStorageUseProxy: "Connectar-se mitjançant un Proxy" objectStorageUseProxy: "Connectar-se mitjançant un Proxy"
objectStorageUseProxyDesc: "Desactiva'l si no faràs servir un Proxy per les connexions de l'API" objectStorageUseProxyDesc: "Desactiva'l si no faràs servir un Proxy per les connexions de l'API"
objectStorageSetPublicRead: "Configurar les pujades com públiques " objectStorageSetPublicRead: "Configurar les pujades com públiques "
s3ForcePathStyleDesc: "Si s3ForcePathStyle es troba activat el nom del dipòsit s'ha d'incloure a l'adreça URL en comtes del nom del host. Potser que necessitis activar-ho quan facis servir, per exemple, Minio a un servidor propi." s3ForcePathStyleDesc: "Si s3ForcePathStyle es troba activat el nom del cubell s'haurà d'especificar com a part de l'adreça URL en comptes del nom del servidor. Podria ser que necessitis activar aquesta opció quan facis servir serveis com ara l'allotjament a un servidor propi."
serverLogs: "Registres del servidor" serverLogs: "Registres del servidor"
deleteAll: "Elimina-ho tot" deleteAll: "Elimina-ho tot"
showFixedPostForm: "Mostrar el formulari per escriure a l'inici de la línia de temps" showFixedPostForm: "Mostrar el formulari per escriure a l'inici de la línia de temps"
@ -576,6 +600,8 @@ ascendingOrder: "Ascendent"
descendingOrder: "Descendent" descendingOrder: "Descendent"
scratchpad: "Bloc de proves" scratchpad: "Bloc de proves"
scratchpadDescription: "El bloc de proves proporciona un entorn experimental per AiScript. Pot escriure i verificar els resultats que interactuen amb Misskey." scratchpadDescription: "El bloc de proves proporciona un entorn experimental per AiScript. Pot escriure i verificar els resultats que interactuen amb Misskey."
uiInspector: "Inspector de la interfície"
uiInspectorDescription: "Podeu visualitzar una llista d'elements UI presents en la memòria. Els components de la interfície d'usuari són generats per les funcions Ui:C:."
output: "Sortida" output: "Sortida"
script: "Script" script: "Script"
disablePagesScript: "Desactivar AiScript a les pàgines " disablePagesScript: "Desactivar AiScript a les pàgines "
@ -692,10 +718,7 @@ abuseReported: "La teva denúncia s'ha enviat. Moltes gràcies."
reporter: "Denunciant " reporter: "Denunciant "
reporteeOrigin: "Origen de la denúncia " reporteeOrigin: "Origen de la denúncia "
reporterOrigin: "Origen del denunciant" reporterOrigin: "Origen del denunciant"
forwardReport: "Transferir la denúncia a una instància remota"
forwardReportIsAnonymous: "En lloc del teu compte, es farà servir un compte anònim com a denunciant al servidor remot."
send: "Envia" send: "Envia"
abuseMarkAsResolved: "Marca la denúncia com a resolta"
openInNewTab: "Obre a una pestanya nova" openInNewTab: "Obre a una pestanya nova"
openInSideView: "Obre a una vista lateral" openInSideView: "Obre a una vista lateral"
defaultNavigationBehaviour: "Navegació per defecte" defaultNavigationBehaviour: "Navegació per defecte"
@ -832,6 +855,7 @@ administration: "Administració"
accounts: "Comptes" accounts: "Comptes"
switch: "Canvia" switch: "Canvia"
noMaintainerInformationWarning: "La informació de l'administrador no s'ha configurat" noMaintainerInformationWarning: "La informació de l'administrador no s'ha configurat"
noInquiryUrlWarning: "No s'ha desat l'URL de consulta."
noBotProtectionWarning: "La protecció contra bots no s'ha configurat." noBotProtectionWarning: "La protecció contra bots no s'ha configurat."
configure: "Configurar" configure: "Configurar"
postToGallery: "Crear una nova publicació a la galeria" postToGallery: "Crear una nova publicació a la galeria"
@ -896,6 +920,7 @@ followersVisibility: "Visibilitat dels seguidors"
continueThread: "Veure la continuació del fil" continueThread: "Veure la continuació del fil"
deleteAccountConfirm: "Això eliminarà el teu compte irreversiblement. Procedir?" deleteAccountConfirm: "Això eliminarà el teu compte irreversiblement. Procedir?"
incorrectPassword: "Contrasenya incorrecta." incorrectPassword: "Contrasenya incorrecta."
incorrectTotp: "La contrasenya no és correcta, o ha caducat."
voteConfirm: "Confirma el teu vot \"{choice}\"" voteConfirm: "Confirma el teu vot \"{choice}\""
hide: "Amagar" hide: "Amagar"
useDrawerReactionPickerForMobile: "Mostrar el selector de reaccions com un calaix al mòbil " useDrawerReactionPickerForMobile: "Mostrar el selector de reaccions com un calaix al mòbil "
@ -1021,6 +1046,7 @@ thisPostMayBeAnnoyingHome: "Publicar a la línia de temps d'Inici"
thisPostMayBeAnnoyingCancel: "Cancel·lar " thisPostMayBeAnnoyingCancel: "Cancel·lar "
thisPostMayBeAnnoyingIgnore: "Publicar de totes maneres" thisPostMayBeAnnoyingIgnore: "Publicar de totes maneres"
collapseRenotes: "Col·lapsar les renotes que ja has vist" collapseRenotes: "Col·lapsar les renotes que ja has vist"
collapseRenotesDescription: "Col·lapse les notes a les quals ja has reaccionat o que ja has renotat"
internalServerError: "Error intern del servidor" internalServerError: "Error intern del servidor"
internalServerErrorDescription: "El servidor ha fallat de manera inexplicable." internalServerErrorDescription: "El servidor ha fallat de manera inexplicable."
copyErrorInfo: "Copiar la informació de l'error " copyErrorInfo: "Copiar la informació de l'error "
@ -1094,6 +1120,8 @@ preservedUsernames: "Noms d'usuaris reservats"
preservedUsernamesDescription: "Llistat de noms d'usuaris que no es poden fer servir separats per salts de linia. Aquests noms d'usuaris no estaran disponibles quan es creï un compte d'usuari normal, però els administradors els poden fer servir per crear comptes manualment. Per altre banda els comptes ja creats amb aquests noms d'usuari no es veure'n afectats." preservedUsernamesDescription: "Llistat de noms d'usuaris que no es poden fer servir separats per salts de linia. Aquests noms d'usuaris no estaran disponibles quan es creï un compte d'usuari normal, però els administradors els poden fer servir per crear comptes manualment. Per altre banda els comptes ja creats amb aquests noms d'usuari no es veure'n afectats."
createNoteFromTheFile: "Compon una nota des d'aquest fitxer" createNoteFromTheFile: "Compon una nota des d'aquest fitxer"
archive: "Arxiu" archive: "Arxiu"
archived: "Arxivat"
unarchive: "Desarxivar"
channelArchiveConfirmTitle: "Vols arxivar {name}?" channelArchiveConfirmTitle: "Vols arxivar {name}?"
channelArchiveConfirmDescription: "Un Canal arxivat no apareixerà a la llista de canals o als resultats de cerca. Tampoc es poden afegir noves entrades." channelArchiveConfirmDescription: "Un Canal arxivat no apareixerà a la llista de canals o als resultats de cerca. Tampoc es poden afegir noves entrades."
thisChannelArchived: "Aquest Canal ha sigut arxivat." thisChannelArchived: "Aquest Canal ha sigut arxivat."
@ -1104,6 +1132,9 @@ preventAiLearning: "Descartar l'ús d'aprenentatge automàtic (IA Generativa)"
preventAiLearningDescription: "Demanar els indexadors no fer servir els texts, imatges, etc. en cap conjunt de dades per alimentar l'aprenentatge automàtic (IA Predictiva/ Generativa). Això s'aconsegueix afegint la etiqueta \"noai\" com a resposta HTML al contingut corresponent. Prevenir aquest ús totalment pot ser que no sigui aconseguit, ja que molts indexadors poden obviar aquesta etiqueta." preventAiLearningDescription: "Demanar els indexadors no fer servir els texts, imatges, etc. en cap conjunt de dades per alimentar l'aprenentatge automàtic (IA Predictiva/ Generativa). Això s'aconsegueix afegint la etiqueta \"noai\" com a resposta HTML al contingut corresponent. Prevenir aquest ús totalment pot ser que no sigui aconseguit, ja que molts indexadors poden obviar aquesta etiqueta."
options: "Opcions" options: "Opcions"
specifyUser: "Especificar usuari" specifyUser: "Especificar usuari"
lookupConfirm: "Vols fer una cerca?"
openTagPageConfirm: "Vols obrir una pàgina d'etiquetes?"
specifyHost: "Especifica un servidor"
failedToPreviewUrl: "Vista prèvia no disponible" failedToPreviewUrl: "Vista prèvia no disponible"
update: "Actualitzar" update: "Actualitzar"
rolesThatCanBeUsedThisEmojiAsReaction: "Rols que poden fer servir aquest emoji com a reacció " rolesThatCanBeUsedThisEmojiAsReaction: "Rols que poden fer servir aquest emoji com a reacció "
@ -1146,7 +1177,7 @@ currentAnnouncements: "Informes actuals"
pastAnnouncements: "Informes passats" pastAnnouncements: "Informes passats"
youHaveUnreadAnnouncements: "Tens informes per llegir." youHaveUnreadAnnouncements: "Tens informes per llegir."
useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey." useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey."
replies: "Respostes" replies: "Respondre"
renotes: "Impulsa" renotes: "Impulsa"
loadReplies: "Mostrar les respostes" loadReplies: "Mostrar les respostes"
loadConversation: "Mostrar la conversació " loadConversation: "Mostrar la conversació "
@ -1172,7 +1203,10 @@ confirmShowRepliesAll: "Aquesta opció no té marxa enrere. Vols mostrar les tev
confirmHideRepliesAll: "Aquesta opció no té marxa enrere. Vols ocultar les teves respostes a tots els usuaris que segueixes a la línia de temps?" confirmHideRepliesAll: "Aquesta opció no té marxa enrere. Vols ocultar les teves respostes a tots els usuaris que segueixes a la línia de temps?"
externalServices: "Serveis externs" externalServices: "Serveis externs"
sourceCode: "Codi font" sourceCode: "Codi font"
sourceCodeIsNotYetProvided: "El codi font encara no es troba disponible. Contacta amb l'administrador per solucionar aquest problema."
repositoryUrl: "URL del repositori" repositoryUrl: "URL del repositori"
repositoryUrlDescription: "Si estàs fent servir Misskey tal com és (sense cap canvi al codi font), introdueix https://github.com/misskey-dev/misskey"
repositoryUrlOrTarballRequired: "Si no ofereixes cap repositori, publica un fitxer tarball. Dona una ullada a .config/example.yml per a més informació."
feedback: "Opinió" feedback: "Opinió"
feedbackUrl: "URL per a opinar" feedbackUrl: "URL per a opinar"
impressum: "Impressum" impressum: "Impressum"
@ -1211,6 +1245,7 @@ showReplay: "Veure reproducció"
replay: "Reproduir" replay: "Reproduir"
replaying: "Reproduint" replaying: "Reproduint"
endReplay: "Tanca la redifusió" endReplay: "Tanca la redifusió"
copyReplayData: "Copia les dades de la resposta"
ranking: "Classificació" ranking: "Classificació"
lastNDays: "Últims {n} dies" lastNDays: "Últims {n} dies"
backToTitle: "Torna al títol" backToTitle: "Torna al títol"
@ -1224,12 +1259,51 @@ gameRetry: "Torna a provar"
notUsePleaseLeaveBlank: "Si no voleu usar-ho, deixeu-ho en blanc" notUsePleaseLeaveBlank: "Si no voleu usar-ho, deixeu-ho en blanc"
useTotp: "Usa una contrasenya d'un sol ús" useTotp: "Usa una contrasenya d'un sol ús"
useBackupCode: "Usa un codi de recuperació" useBackupCode: "Usa un codi de recuperació"
launchApp: "Inicia l'aplicació "
useNativeUIForVideoAudioPlayer: "Fes servir la UI del navegador quan reprodueixis vídeo i àudio "
keepOriginalFilename: "Desa el nom del fitxer original"
keepOriginalFilenameDescription: "Si desactives aquesta opció els noms dels fitxers se substituiran per una cadena aleatòria quan carreguis nous fitxers de forma automàtica."
noDescription: "No hi ha una descripció "
alwaysConfirmFollow: "Confirma sempre els seguiments"
inquiry: "Contacte"
tryAgain: "Intenta-ho més tard."
confirmWhenRevealingSensitiveMedia: "Confirmació quan revelis contingut sensible "
sensitiveMediaRevealConfirm: "Aquest contingut potser sensible. Segur que ho vols revelar?"
createdLists: "Llistes creades "
createdAntennas: "Antenes creades"
fromX: "De {x}"
genEmbedCode: "Obtenir el codi per incrustar"
noteOfThisUser: "Notes d'aquest usuari"
clipNoteLimitExceeded: "No es poden afegir més notes a aquest clip."
performance: "Rendiment"
modified: "Modificat"
discard: "Descarta"
thereAreNChanges: "Hi ha(n) {n} canvi(s)"
signinWithPasskey: "Inicia sessió amb Passkey"
unknownWebAuthnKey: "Passkey desconeguda"
passkeyVerificationFailed: "La verificació a fallat"
passkeyVerificationSucceededButPasswordlessLoginDisabled: "La verificació de la passkey a estat correcta, però s'ha deshabilitat l'inici de sessió sense contrasenya."
messageToFollower: "Missatge als meus seguidors"
_delivery: _delivery:
status: "Estat d'entrega "
stop: "Suspés" stop: "Suspés"
resume: "Torna a enviar"
_type: _type:
none: "S'està publicant" none: "S'està publicant"
manuallySuspended: "Suspendre manualment"
goneSuspended: "Servidor suspès perquè el servidor s'ha esborrat"
autoSuspendedForNotResponding: "Servidor suspès perquè el servidor no respon"
_bubbleGame: _bubbleGame:
howToPlay: "Com es juga" howToPlay: "Com es juga"
hold: "Mantenir"
_score:
score: "Puntuació "
scoreYen: "Diners guanyats"
highScore: "Millor puntuació "
maxChain: "Nombre màxim de combos"
yen: "{yen}Ien"
estimatedQty: "{qty}peces"
scoreSweets: "{onigiriQtyWithUnit}ongiris"
_howToPlay: _howToPlay:
section1: "Ajusta la posició i deixa caure l'objecte dintre la caixa." section1: "Ajusta la posició i deixa caure l'objecte dintre la caixa."
section2: "Quan dos objectes del mateix tipus es toquen, canviaran en un objecte diferent i guanyares punts." section2: "Quan dos objectes del mateix tipus es toquen, canviaran en un objecte diferent i guanyares punts."
@ -1281,10 +1355,10 @@ _initialTutorial:
_reaction: _reaction:
title: "Què són les Reaccions?" title: "Què són les Reaccions?"
description: "Es poden reaccionar a les Notes amb diferents emoticones. Les reaccions et permeten expressar matisos que hi són més enllà d'un simple m'agrada." description: "Es poden reaccionar a les Notes amb diferents emoticones. Les reaccions et permeten expressar matisos que hi són més enllà d'un simple m'agrada."
letsTryReacting: "Es poden afegir reaccions fent clic al botó '{reaction}'. Prova reaccionant a aquesta nota!" letsTryReacting: "Es poden afegir reaccions fent clic al botó '+'. Prova reaccionant a aquesta nota!"
reactToContinue: "Afegeix una reacció per continuar." reactToContinue: "Afegeix una reacció per continuar."
reactNotification: "Rebràs notificacions en temps real quan un usuari reaccioni a les teves notes." reactNotification: "Rebràs notificacions en temps real quan un usuari reaccioni a les teves notes."
reactDone: "Pots desfer una reacció fent clic al botó '{undo}'." reactDone: "Pots desfer una reacció fent clic al botó '-'."
_timeline: _timeline:
title: "El concepte de les línies de temps" title: "El concepte de les línies de temps"
description1: "Misskey mostra diferents línies de temps basades en l'ús (algunes poden no estar disponibles depenent de la política del servidor)" description1: "Misskey mostra diferents línies de temps basades en l'ús (algunes poden no estar disponibles depenent de la política del servidor)"
@ -1344,6 +1418,9 @@ _serverSettings:
fanoutTimelineDescription: "Quan es troba activat millora bastant el rendiment quan es recuperen les línies de temps i redueix la carrega de la base de dades. Com a contrapunt, l'ús de memòria de Redis es veurà incrementada. Considera d'estabilitat aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes de inestabilitat." fanoutTimelineDescription: "Quan es troba activat millora bastant el rendiment quan es recuperen les línies de temps i redueix la carrega de la base de dades. Com a contrapunt, l'ús de memòria de Redis es veurà incrementada. Considera d'estabilitat aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes de inestabilitat."
fanoutTimelineDbFallback: "Carregar de la base de dades" 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." 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."
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ó."
_accountMigration: _accountMigration:
moveFrom: "Migrar un altre compte a aquest" moveFrom: "Migrar un altre compte a aquest"
moveFromSub: "Crear un àlies per un altre compte" moveFromSub: "Crear un àlies per un altre compte"
@ -1651,6 +1728,7 @@ _role:
gtlAvailable: "Pot veure la línia de temps global" gtlAvailable: "Pot veure la línia de temps global"
ltlAvailable: "Pot veure la línia de temps local" ltlAvailable: "Pot veure la línia de temps local"
canPublicNote: "Pot enviar notes públiques" canPublicNote: "Pot enviar notes públiques"
mentionMax: "Nombre màxim de mencions a una nota"
canInvite: "Pot crear invitacions a la instància " canInvite: "Pot crear invitacions a la instància "
inviteLimit: "Límit d'invitacions " inviteLimit: "Límit d'invitacions "
inviteLimitCycle: "Temps de refresc de les invitacions" inviteLimitCycle: "Temps de refresc de les invitacions"
@ -1659,6 +1737,7 @@ _role:
canManageAvatarDecorations: "Gestiona les decoracions dels avatars " canManageAvatarDecorations: "Gestiona les decoracions dels avatars "
driveCapacity: "Capacitat del disc" driveCapacity: "Capacitat del disc"
alwaysMarkNsfw: "Marca sempre els fitxers com a sensibles" alwaysMarkNsfw: "Marca sempre els fitxers com a sensibles"
canUpdateBioMedia: "Permet l'edició d'una icona o un bàner"
pinMax: "Nombre màxim de notes fixades" pinMax: "Nombre màxim de notes fixades"
antennaMax: "Nombre màxim d'antenes" antennaMax: "Nombre màxim d'antenes"
wordMuteMax: "Nombre màxim de caràcters permesos a les paraules silenciades" wordMuteMax: "Nombre màxim de caràcters permesos a les paraules silenciades"
@ -1673,9 +1752,20 @@ _role:
canSearchNotes: "Pot cercar notes" canSearchNotes: "Pot cercar notes"
canUseTranslator: "Pot fer servir el traductor" canUseTranslator: "Pot fer servir el traductor"
avatarDecorationLimit: "Nombre màxim de decoracions que es poden aplicar els avatars" avatarDecorationLimit: "Nombre màxim de decoracions que es poden aplicar els avatars"
canImportAntennas: "Autoritza la importació d'antenes "
canImportBlocking: "Autoritza la importació de bloquejats"
canImportFollowing: "Autoritza la importació de seguidors"
canImportMuting: "Autoritza la importació de silenciats"
canImportUserLists: "Autoritza la importació de llistes d'usuaris "
_condition: _condition:
roleAssignedTo: "Assignat a rols manuals"
isLocal: "Usuari local" isLocal: "Usuari local"
isRemote: "Usuari remot" isRemote: "Usuari remot"
isCat: "Usuaris gats"
isBot: "Usuaris bots"
isSuspended: "Usuari suspès"
isLocked: "Comptes privats"
isExplorable: "Fes que el compte aparegui a les cerques"
createdLessThan: "Han passat menys de X a passat des de la creació del compte" createdLessThan: "Han passat menys de X a passat des de la creació del compte"
createdMoreThan: "Han passat més de X des de la creació del compte" createdMoreThan: "Han passat més de X des de la creació del compte"
followersLessThanOrEq: "Té menys de X seguidors" followersLessThanOrEq: "Té menys de X seguidors"
@ -1745,6 +1835,7 @@ _plugin:
installWarn: "Si us plau, no instal·lis afegits que no siguin de confiança." installWarn: "Si us plau, no instal·lis afegits que no siguin de confiança."
manage: "Gestionar els afegits" manage: "Gestionar els afegits"
viewSource: "Veure l'origen " viewSource: "Veure l'origen "
viewLog: "Mostra el registre"
_preferencesBackups: _preferencesBackups:
list: "Llista de còpies de seguretat" list: "Llista de còpies de seguretat"
saveNew: "Fer una còpia de seguretat nova" saveNew: "Fer una còpia de seguretat nova"
@ -1774,6 +1865,8 @@ _aboutMisskey:
contributors: "Col·laboradors principals" contributors: "Col·laboradors principals"
allContributors: "Tots els col·laboradors " allContributors: "Tots els col·laboradors "
source: "Codi font" source: "Codi font"
original: "Original"
thisIsModifiedVersion: "En {name} fa servir una versió modificada de Misskey."
translation: "Tradueix Misskey" translation: "Tradueix Misskey"
donate: "Fes un donatiu a Misskey" donate: "Fes un donatiu a Misskey"
morePatrons: "També agraïm el suport d'altres col·laboradors que no surten en aquesta llista. Gràcies! 🥰" morePatrons: "També agraïm el suport d'altres col·laboradors que no surten en aquesta llista. Gràcies! 🥰"
@ -1901,6 +1994,7 @@ _soundSettings:
driveFileTypeWarnDescription: "Seleccionar un fitxer d'àudio " driveFileTypeWarnDescription: "Seleccionar un fitxer d'àudio "
driveFileDurationWarn: "L'àudio és massa llarg" driveFileDurationWarn: "L'àudio és massa llarg"
driveFileDurationWarnDescription: "Els àudios molt llargs pot interrompre l'ús de Misskey. Vols continuar?" driveFileDurationWarnDescription: "Els àudios molt llargs pot interrompre l'ús de Misskey. Vols continuar?"
driveFileError: "El so no es pot carregar. Canvia la configuració"
_ago: _ago:
future: "Futur " future: "Futur "
justNow: "Ara mateix" justNow: "Ara mateix"
@ -1953,6 +2047,7 @@ _2fa:
backupCodesDescription: "Si l'aplicació d'autenticació no es pot utilitzar, es pot accedir al compte utilitzant els següents codis de còpia de seguretat. Assegura't de mantenir aquests codis en un lloc segur. Cada codi es pot utilitzar només una vegada." backupCodesDescription: "Si l'aplicació d'autenticació no es pot utilitzar, es pot accedir al compte utilitzant els següents codis de còpia de seguretat. Assegura't de mantenir aquests codis en un lloc segur. Cada codi es pot utilitzar només una vegada."
backupCodeUsedWarning: "Es va utilitzar un codi de còpia de seguretat. Si l'aplicació de certificació està disponible, reconfigura l'aplicació d'autenticació tan aviat com sigui possible." backupCodeUsedWarning: "Es va utilitzar un codi de còpia de seguretat. Si l'aplicació de certificació està disponible, reconfigura l'aplicació d'autenticació tan aviat com sigui possible."
backupCodesExhaustedWarning: "Es van utilitzar tots els codis de còpia de seguretat. Si no es pot utilitzar l'aplicació d'autenticació, ja no es pot accedir al compte. Torna a registrar l'aplicació d'autenticació." backupCodesExhaustedWarning: "Es van utilitzar tots els codis de còpia de seguretat. Si no es pot utilitzar l'aplicació d'autenticació, ja no es pot accedir al compte. Torna a registrar l'aplicació d'autenticació."
moreDetailedGuideHere: "Aquí tens una guia al detall"
_permissions: _permissions:
"read:account": "Veure la informació del compte." "read:account": "Veure la informació del compte."
"write:account": "Editar la informació del compte." "write:account": "Editar la informació del compte."
@ -2026,22 +2121,73 @@ _permissions:
"read:admin:emoji": "Veure emojis" "read:admin:emoji": "Veure emojis"
"write:admin:queue": "Gestionar la cua de feines" "write:admin:queue": "Gestionar la cua de feines"
"read:admin:queue": "Veure la cua de feines" "read:admin:queue": "Veure la cua de feines"
"write:admin:promo": "Gestiona les notes promocionals"
"write:admin:drive": "Gestiona el disc de l'usuari"
"read:admin:drive": "Veure la informació del disc de l'usuari"
"read:admin:stream": "Fes servir l'API sobre Websocket per l'administració"
"write:admin:ad": "Gestiona la publicitat"
"read:admin:ad": "Veure anuncis"
"write:invite-codes": "Crear codis d'invitació"
"read:invite-codes": "Obtenir codis d'invitació"
"write:clip-favorite": "Gestionar els clips favorits"
"read:clip-favorite": "Veure clips favorits"
"read:federation": "Veure dades de federació"
"write:report-abuse": "Informar d'un abús"
_auth:
shareAccessTitle: "Concedeix permisos a l'aplicació"
shareAccess: "Vols que {name} pugui accedir al vostre compte?"
shareAccessAsk: "Segur que vols que aquesta aplicació pugui accedir al vostre compte?"
permission: "{name} demana els següents permisos"
permissionAsk: "Aquesta aplicació demana els següents permisos"
pleaseGoBack: "Si us plau, torna a l'aplicació"
callback: "Tornant a l'aplicació"
denied: "Accés denegat"
pleaseLogin: "Si us plau, identificat per autoritzar l'aplicació."
_antennaSources: _antennaSources:
all: "Totes les publicacions" all: "Totes les publicacions"
homeTimeline: "Publicacions dels usuaris seguits" homeTimeline: "Publicacions dels usuaris seguits"
users: "Publicacions d'usuaris específics" users: "Publicacions d'usuaris específics"
userList: "Publicacions d'una llista d'usuaris" userList: "Publicacions d'una llista d'usuaris"
userBlacklist: "Totes les notes excepte les d'un o alguns usuaris especificats"
_weekday:
sunday: "Diumenge"
monday: "Dilluns"
tuesday: "Dimarts"
wednesday: "Dimecres"
thursday: "Dijous"
friday: "Divendres"
saturday: "Dissabte"
_widgets: _widgets:
profile: "Perfil" profile: "Perfil"
instanceInfo: "Informació del fitxer d'instal·lació" instanceInfo: "Informació del fitxer d'instal·lació"
memo: "Notes adhesives"
notifications: "Notificacions" notifications: "Notificacions"
timeline: "Línia de temps" timeline: "Línia de temps"
calendar: "Calendari"
trends: "Tendència"
clock: "Rellotge"
rss: "Lector RSS"
rssTicker: "RSS ticker"
activity: "Activitat" activity: "Activitat"
photos: "Fotografies"
digitalClock: "Rellotge digital"
unixClock: "Rellotge UNIX"
federation: "Federació" federation: "Federació"
instanceCloud: "Núvol d'instàncies"
postForm: "Formulari de publicació"
slideshow: "Presentació"
button: "Botó " button: "Botó "
onlineUsers: "Usuaris actius"
jobQueue: "Cua de tasques" jobQueue: "Cua de tasques"
serverMetric: "Mètriques del servidor"
aiscript: "Consola AiScript"
aiscriptApp: "Aplicació AiScript"
aichan: "Ai"
userList: "Llistat d'usuaris"
_userList: _userList:
chooseList: "Tria una llista" chooseList: "Tria una llista"
clicker: "Clicker"
birthdayFollowings: "Usuaris que fan l'aniversari avui"
_cw: _cw:
hide: "Amagar" hide: "Amagar"
show: "Carregar més" show: "Carregar més"
@ -2105,27 +2251,79 @@ _profile:
changeBanner: "Canviar el bàner " changeBanner: "Canviar el bàner "
verifiedLinkDescription: "Escrivint una adreça URL que enllaci a aquest perfil, una icona de propietat verificada es mostrarà al costat del camp." verifiedLinkDescription: "Escrivint una adreça URL que enllaci a aquest perfil, una icona de propietat verificada es mostrarà al costat del camp."
avatarDecorationMax: "Pot afegir un màxim de {max} decoracions." avatarDecorationMax: "Pot afegir un màxim de {max} decoracions."
followedMessage: "Missatge als nous seguidors"
followedMessageDescription: "Es pot configurar un missatge curt que es mostra a l'altra persona quan comença a seguir-te."
followedMessageDescriptionForLockedAccount: "Si comencen a seguir-te es mostra un missatge de quan es permet aquesta sol·licitud. "
_exportOrImport: _exportOrImport:
allNotes: "Totes les publicacions" allNotes: "Totes les publicacions"
favoritedNotes: "Notes preferides"
clips: "Retalls" clips: "Retalls"
followingList: "Seguint" followingList: "Seguint"
muteList: "Silencia" muteList: "Silencia"
blockingList: "Bloqueja" blockingList: "Bloqueja"
userLists: "Llistes" userLists: "Llistes"
excludeMutingUsers: "Exclou usuaris silenciats"
excludeInactiveUsers: "Exclou usuaris inactius"
withReplies: "Inclou a la línia de temps les respostes d'usuaris importats"
_charts: _charts:
federation: "Federació" federation: "Federació"
apRequest: "Peticions"
usersIncDec: "Diferència entre el nombre d'usuaris"
usersTotal: "Nombre total d'usuaris"
activeUsers: "Usuaris actius"
notesIncDec: "Diferència entre el nombre de notes"
localNotesIncDec: "Diferencia en el nombre de notes locals"
remoteNotesIncDec: "Diferencia en el nombre de notes remotes"
notesTotal: "Nombre total de notes"
filesIncDec: "Diferencia en el nombre de fitxers"
filesTotal: "Nombre total de fitxers"
storageUsageIncDec: "Diferencia en l'emmagatzematge usat"
storageUsageTotal: "Emmagatzematge usat"
_instanceCharts:
requests: "Peticions"
users: "Diferència entre el nombre d'usuaris"
usersTotal: "Usuaris totals acumulats"
notes: "Diferència entre el nombre de notes"
notesTotal: "Notes totals acumulades"
ff: "Diferència en nombre d'usuaris seguits / seguidors"
ffTotal: "Nombre total acumulat d'usuaris seguits / seguidors"
cacheSize: "Diferència a la mida de la memòria cau"
cacheSizeTotal: "Total acumulat de la mida de la memòria cau"
files: "Diferència al nombre d'arxius"
filesTotal: "Nombre acumulatiu de fitxers"
_timelines: _timelines:
home: "Inici" home: "Inici"
local: "Local" local: "Local"
social: "Social" social: "Social"
global: "Global" global: "Global"
_play: _play:
new: "Crear un guió"
edit: "Editar guió"
created: "Guió creat"
updated: "Guió editat"
deleted: "Guió esborrat"
pageSetting: "Configuració del guió"
editThisPage: "Edita aquest guió"
viewSource: "Veure l'origen " viewSource: "Veure l'origen "
my: "Els meus guions"
liked: "Guions que m'han agradat"
featured: "Popular" featured: "Popular"
title: "Títol " title: "Títol "
script: "Script" script: "Script"
summary: "Descripció" summary: "Descripció"
visibilityDescription: ""
_pages: _pages:
newPage: "pa"
editPage: "Editar la pàgina"
readPage: "Veure el codi font d'aquesta pàgina"
created: "La pàgina ha sigut creada correctament"
updated: "La pàgina s'ha editat correctament"
deleted: "La pàgina s'ha esborrat sense problemes"
pageSetting: "Configuració de la pàgina"
nameAlreadyExists: "L'adreça URL de la pàgina ja existeix"
invalidNameTitle: "L'adreça URL de la pàgina no és vàlida"
invalidNameText: "Assegurat que el títol de la pàgina no és buit"
editThisPage: "Editar la pàgina"
viewSource: "Veure l'origen " viewSource: "Veure l'origen "
viewPage: "Veure les teves pàgines " viewPage: "Veure les teves pàgines "
like: "M'agrada " like: "M'agrada "
@ -2148,6 +2346,7 @@ _pages:
eyeCatchingImageSet: "Escull una miniatura" eyeCatchingImageSet: "Escull una miniatura"
eyeCatchingImageRemove: "Esborrar la miniatura" eyeCatchingImageRemove: "Esborrar la miniatura"
chooseBlock: "Afegeix un bloc" chooseBlock: "Afegeix un bloc"
enterSectionTitle: "Escriu el títol de la secció"
selectType: "Seleccionar tipus" selectType: "Seleccionar tipus"
contentBlocks: "Contingut" contentBlocks: "Contingut"
inputBlocks: "Entrada " inputBlocks: "Entrada "
@ -2158,6 +2357,8 @@ _pages:
section: "Secció " section: "Secció "
image: "Imatges" image: "Imatges"
button: "Botó " button: "Botó "
dynamic: "Blocs dinàmics"
dynamicDescription: "Aquest bloc és antic. Ara en endavant fes servir {play}"
note: "Incorporar una Nota" note: "Incorporar una Nota"
_note: _note:
id: "ID de la publicació" id: "ID de la publicació"
@ -2187,29 +2388,54 @@ _notification:
sendTestNotification: "Enviar notificació de prova" sendTestNotification: "Enviar notificació de prova"
notificationWillBeDisplayedLikeThis: "Les notificacions és veure'n així " notificationWillBeDisplayedLikeThis: "Les notificacions és veure'n així "
reactedBySomeUsers: "Han reaccionat {n} usuaris" reactedBySomeUsers: "Han reaccionat {n} usuaris"
likedBySomeUsers: "A {n} usuaris els hi agrada la teva nota"
renotedBySomeUsers: "L'han impulsat {n} usuaris" renotedBySomeUsers: "L'han impulsat {n} usuaris"
followedBySomeUsers: "Et segueixen {n} usuaris"
flushNotification: "Netejar notificacions"
exportOfXCompleted: "Completada l'exportació de {n}"
_types: _types:
all: "Tots" all: "Tots"
note: "Notes noves"
follow: "Seguint" follow: "Seguint"
mention: "Menció" mention: "Menció"
reply: "Respostes"
renote: "Renotar" renote: "Renotar"
quote: "Citar" quote: "Citar"
reaction: "Reaccions" reaction: "Reaccions"
pollEnded: "Enquesta terminada"
receiveFollowRequest: "Rebuda una petició de seguiment"
followRequestAccepted: "Petició de seguiment acceptada"
roleAssigned: "Rol donat"
achievementEarned: "Assoliment desbloquejat"
exportCompleted: "Exportació completada"
login: "Iniciar sessió"
test: "Prova la notificació"
app: "Notificacions d'aplicacions"
_actions: _actions:
followBack: "t'ha seguit també" followBack: "t'ha seguit també"
reply: "Respondre" reply: "Respondre"
renote: "Renotar" renote: "Renotar"
_deck: _deck:
alwaysShowMainColumn: "Mostrar sempre la columna principal"
columnAlign: "Alinea les columnes" columnAlign: "Alinea les columnes"
addColumn: "Afig una columna" addColumn: "Afig una columna"
newNoteNotificationSettings: "Configuració de notificacions per a notes noves"
configureColumn: "Configuració de columnes"
swapLeft: "Mou a lesquerra" swapLeft: "Mou a lesquerra"
swapRight: "Mou a la dreta" swapRight: "Mou a la dreta"
swapUp: "Mou cap amunt" swapUp: "Mou cap amunt"
swapDown: "Mou cap avall" swapDown: "Mou cap avall"
stackLeft: "Pila a la columna esquerra"
popRight: "Col·loca a la dreta" popRight: "Col·loca a la dreta"
profile: "Perfil" profile: "Perfil"
newProfile: "Perfil nou" newProfile: "Perfil nou"
deleteProfile: "Elimina el perfil" deleteProfile: "Elimina el perfil"
introduction: "Crea la interfície perfecta posant les columnes allà on vulguis!"
introduction2: "Fes clic al botó + de la dreta per afegir noves columnes sempre que vulguis."
widgetsIntroduction: "Selecciona \"Editar ginys\" a la columna del menú i afegeix un."
useSimpleUiForNonRootPages: "Usa una interfície senzilla per a les pàgines navegades"
usedAsMinWidthWhenFlexible: "L'amplada mínima es farà servir quan \"Ajust automàtic de l'amplada\" estigui activat"
flexible: "Ajust automàtic de l'amplada"
_columns: _columns:
main: "Principal" main: "Principal"
widgets: "Ginys" widgets: "Ginys"
@ -2220,18 +2446,77 @@ _deck:
channel: "Canals" channel: "Canals"
mentions: "Mencions" mentions: "Mencions"
direct: "Publicacions directes" direct: "Publicacions directes"
roleTimeline: "Línia de temps dels rols"
_dialog:
charactersExceeded: "Has arribat al màxim de caràcters! Actualment és {current} de {max}"
charactersBelow: "Ets per sota del mínim de caràcters! Actualment és {current} de {min}"
_disabledTimeline:
title: "Línia de tems desactivada"
description: "No pots fer servir aquesta línia de temps amb els teus rols actuals."
_drivecleaner:
orderBySizeDesc: "Mida del fitxer descendent"
orderByCreatedAtAsc: "Data ascendent"
_webhookSettings: _webhookSettings:
createWebhook: "Crear un Webhook"
modifyWebhook: "Modificar un Webhook"
name: "Nom" name: "Nom"
secret: "Secret"
trigger: "Activador"
active: "Activat" active: "Activat"
_events:
follow: "Quan se segueix a un usuari"
followed: "Quan et segueixen"
note: "Quan es publica una nota"
reply: "Quan es rep una resposta"
renote: "Quan es renoti"
reaction: "Quan es rep una reacció "
mention: "Quan et mencionen"
_systemEvents:
abuseReport: "Quan reps un nou informe de moderació "
abuseReportResolved: "Quan resols un informe de moderació "
userCreated: "Quan es crea un usuari"
deleteConfirm: "Segur que vols esborrar el webhook?"
testRemarks: "Si feu clic al botó a la dreta de l'interruptor, podeu enviar un webhook de prova amb dades dummy."
_abuseReport: _abuseReport:
_notificationRecipient: _notificationRecipient:
createRecipient: "Afegeix un destinatari a l'informe de moderació "
modifyRecipient: "Editar un destinatari en l'informe de moderació "
recipientType: "Tipus de notificació "
_recipientType: _recipientType:
mail: "Correu electrònic" mail: "Correu electrònic"
webhook: "Webhook"
_captions:
mail: "Enviar un correu electrònic a tots els moderadors quan es rep un informe de moderació "
webhook: "Enviar una notificació al SystemWebhook quan es rebi o es resolgui un informe de moderació "
keywords: "Paraules clau"
notifiedUser: "Usuaris que s'han de notificar "
notifiedWebhook: "Webhook que s'ha de fer servir"
deleteConfirm: "Segur que vols esborrar el destinatari de l'informe de moderació?"
_moderationLogTypes: _moderationLogTypes:
createRole: "Rol creat"
deleteRole: "Rol esborrat"
updateRole: "Rol actualitzat"
assignRole: "Assignat al rol"
unassignRole: "Esborrat del rol"
suspend: "Suspèn" suspend: "Suspèn"
unsuspend: "Suspensió treta"
addCustomEmoji: "Afegit emoji personalitzat"
updateCustomEmoji: "Actualitzat emoji personalitzat"
deleteCustomEmoji: "Esborrat emoji personalitzat"
updateServerSettings: "Configuració del servidor actualitzada"
updateUserNote: "Nota de moderació actualitzada"
deleteDriveFile: "Fitxer esborrat"
deleteNote: "Nota esborrada"
createGlobalAnnouncement: "Anunci global creat"
createUserAnnouncement: "Anunci individual creat"
updateGlobalAnnouncement: "Anunci global actualitzat"
updateUserAnnouncement: "Anunci individual actualitzat "
deleteGlobalAnnouncement: "Anunci global esborrat"
deleteUserAnnouncement: "Anunci individual esborrat "
resetPassword: "Restableix la contrasenya" resetPassword: "Restableix la contrasenya"
suspendRemoteInstance: "Servidor remot suspès " suspendRemoteInstance: "Servidor remot suspès "
unsuspendRemoteInstance: "S'ha tret la suspensió del servidor remot" unsuspendRemoteInstance: "S'ha tret la suspensió del servidor remot"
updateRemoteInstanceNote: "Nota de moderació de la instància remota actualitzada"
markSensitiveDriveFile: "Fitxer marcat com a sensible" markSensitiveDriveFile: "Fitxer marcat com a sensible"
unmarkSensitiveDriveFile: "S'ha tret la marca de sensible del fitxer" unmarkSensitiveDriveFile: "S'ha tret la marca de sensible del fitxer"
resolveAbuseReport: "Informe resolt" resolveAbuseReport: "Informe resolt"
@ -2244,6 +2529,16 @@ _moderationLogTypes:
deleteAvatarDecoration: "S'ha esborrat la decoració de l'avatar " deleteAvatarDecoration: "S'ha esborrat la decoració de l'avatar "
unsetUserAvatar: "Esborrar l'avatar d'aquest usuari" unsetUserAvatar: "Esborrar l'avatar d'aquest usuari"
unsetUserBanner: "Esborrar el bàner d'aquest usuari" unsetUserBanner: "Esborrar el bàner d'aquest usuari"
createSystemWebhook: "Crear un SystemWebhook"
updateSystemWebhook: "Actualitzar SystemWebhook"
deleteSystemWebhook: "Esborrar SystemWebhook"
createAbuseReportNotificationRecipient: "Crear un destinatari per l'informe de moderació "
updateAbuseReportNotificationRecipient: "Actualitzar destinatari per l'informe de moderació "
deleteAbuseReportNotificationRecipient: "Esborrar destinatari de l'informe de moderació "
deleteAccount: "Esborrar el compte "
deletePage: "Esborrar la pàgina"
deleteFlash: "Esborrar el guió"
deleteGalleryPost: "Esborrar la publicació de la galeria"
_fileViewer: _fileViewer:
title: "Detall del fitxer" title: "Detall del fitxer"
type: "Tipus de fitxer" type: "Tipus de fitxer"
@ -2270,5 +2565,54 @@ _externalResourceInstaller:
_errors: _errors:
_invalidParams: _invalidParams:
title: "Paràmetres no vàlids " title: "Paràmetres no vàlids "
description: "No hi ha suficient informació per carregar les dades del lloc extern. Confirma l'URL que hi ha escrita."
_resourceTypeNotSupported:
title: "El recurs extern no està suportat."
description: "Aquesta mena de recurs no està suportat. Contacta amb l'administrador."
_failedToFetch:
title: "Ha fallat l'obtenció de dades"
fetchErrorDescription: "Ha aparegut un error comunicant-se amb el lloc extern. Si després d'intentar-ho un altre cop no es resol, contacta amb l'administrador."
parseErrorDescription: "Ha aparegut un error processant les dades carregades del lloc extern. Contacta amb l'administrador."
_hashUnmatched:
title: "Ha fallat la verificació de les dades"
description: "Ha aparegut un error verificant les dades obtingudes. Com a mesura de seguretat la instal·lació no pot continuar. Contacta amb l'administrador."
_pluginParseFailed:
title: "Error d'AiScript"
description: "Les dades sol·licitades s'han obtingut correctament, però hem trobat un error durant el processament d'AiScript. Contacta amb l'autor de l'afegit. Detalls de l'error es pot veure a la consola JavaScript."
_pluginInstallFailed:
title: "La instal·lació de l'afegit a fallat"
description: "Ha aparegut un error durant la instal·lació de l'afegit. Intenta-ho una altra vegada. El detall de l'error es pot veure a la consola JavaScript."
_themeParseFailed:
title: "Ha fallat el processament del tema"
description: "Les dades sol·licitades s'han obtingut correctament, però hem trobat un error durant el processament del tema. Contacta amb l'autor de l'afegit. Detalls de l'error es pot veure a la consola JavaScript."
_themeInstallFailed:
title: "La instal·lació del tema a fallat"
description: "Ha aparegut un error durant la instal·lació del tema. Intenta-ho una altra vegada. El detall de l'error es pot veure a la consola JavaScript."
_dataSaver:
_media:
title: "Carregant multimèdia "
description: "Desactiva la càrrega automàtica d'imatges i vídeos. Les imatges i els vídeos amagats es carregaran quan es faci clic a sobre."
_avatar:
title: "Avatars animats"
description: "Detenir l'animació dels avatars animats. Les imatges animades solen tenir un pes més gran que les imatges normals, reduint el tràfic disponible."
_urlPreview:
title: "Miniatures vista prèvia de l'URL"
description: "Les imatges en miniatura que serveixen com a vista prèvia de les URLs no es tornaran a carregar."
_code:
title: "Ressaltat del codi "
_reversi: _reversi:
total: "Total" total: "Total"
_embedCodeGen:
title: "Personalitza el codi per incrustar"
header: "Mostrar la capçalera"
autoload: "Carregar automàticament (no recomanat)"
maxHeight: "Alçada màxima"
maxHeightDescription: "0 anul·la la configuració màxima. Per evitar que continuï creixent verticalment, especifiqui qualsevol valor."
maxHeightWarn: "El límit màxim d'alçada és nul (0). Si això no és un canvi previst, estableix el màxim d'alçada a un cert valor."
previewIsNotActual: "La visualització és diferent de la que es mostra quan s'implanta."
rounded: "Angle recte"
border: "Afegeix un marc al contenidor"
applyToPreview: "Aplica a la vista prèvia"
generateCode: "Crea el codi per incrustar"
codeGenerated: "Codi generat"
codeGeneratedDescription: "Si us plau, enganxeu el codi generat al lloc web."

View file

@ -471,7 +471,6 @@ uiLanguage: "Jazyk uživatelského rozhraní"
aboutX: "O {x}" aboutX: "O {x}"
emojiStyle: "Styl emoji" emojiStyle: "Styl emoji"
native: "Výchozí" native: "Výchozí"
disableDrawer: "Nepoužívat šuplíkové menu"
showNoteActionsOnlyHover: "Zobrazit akce poznámky jenom při naběhnutí myši" showNoteActionsOnlyHover: "Zobrazit akce poznámky jenom při naběhnutí myši"
noHistory: "Žádná historie" noHistory: "Žádná historie"
signinHistory: "Historie přihlášení" signinHistory: "Historie přihlášení"
@ -658,10 +657,7 @@ abuseReported: "Nahlášení bylo odesláno. Děkujeme převelice."
reporter: "Nahlásil" reporter: "Nahlásil"
reporteeOrigin: "Původ nahlášení" reporteeOrigin: "Původ nahlášení"
reporterOrigin: "Původ nahlasovače" reporterOrigin: "Původ nahlasovače"
forwardReport: "Přeposlat nahlášení do vzdálené instance"
forwardReportIsAnonymous: "Místo vašeho účtu se ve vzdálené instanci zobrazí anonymní systémový účet jako nahlašovač."
send: "Odeslat" send: "Odeslat"
abuseMarkAsResolved: "Označit nahlášení jako vyřešené"
openInNewTab: "Otevřít v nové kartě" openInNewTab: "Otevřít v nové kartě"
openInSideView: "Otevřít v bočním panelu" openInSideView: "Otevřít v bočním panelu"
defaultNavigationBehaviour: "Výchozí chování navigace" defaultNavigationBehaviour: "Výchozí chování navigace"
@ -1093,7 +1089,7 @@ doYouAgree: "Souhlasíte?"
beSureToReadThisAsItIsImportant: "Přečtěte si prosím tyto důležité informace." beSureToReadThisAsItIsImportant: "Přečtěte si prosím tyto důležité informace."
iHaveReadXCarefullyAndAgree: "Přečetl jsem si text \"{x}\" a souhlasím s ním." iHaveReadXCarefullyAndAgree: "Přečetl jsem si text \"{x}\" a souhlasím s ním."
icon: "Avatar" icon: "Avatar"
replies: "Odpovědi" replies: "Odpovědět"
renotes: "Přeposlat" renotes: "Přeposlat"
sourceCode: "Zdrojový kód" sourceCode: "Zdrojový kód"
flip: "Otočit" flip: "Otočit"
@ -1963,6 +1959,7 @@ _notification:
receiveFollowRequest: "Obdržené žádosti o sledování" receiveFollowRequest: "Obdržené žádosti o sledování"
followRequestAccepted: "Přijaté žádosti o sledování" followRequestAccepted: "Přijaté žádosti o sledování"
achievementEarned: "Úspěch odemčen" achievementEarned: "Úspěch odemčen"
login: "Přihlásit se"
app: "Oznámení z propojených aplikací" app: "Oznámení z propojených aplikací"
_actions: _actions:
followBack: "vás začal sledovat zpět" followBack: "vás začal sledovat zpět"

View file

@ -1,8 +1,8 @@
--- ---
_lang_: "Deutsch" _lang_: "Deutsch"
headlineMisskey: "Ein durch Notizen verbundenes Netzwerk" headlineMisskey: "Ein durch Notizen verbundenes Netzwerk"
introMisskey: "Willkommen! Sharkey ist eine dezentralisierte Open-Source Microblogging-Platform.\nVerfasse „Notizen“ um mitzuteilen, was gerade passiert oder um Ereignisse mit anderen zu teilen. 📡\nMit „Reaktionen“ kannst du außerdem schnell deine Gefühle über Notizen anderer Benutzer zum Ausdruck bringen. 👍\nEine neue Welt wartet auf dich! 🚀" introMisskey: "Willkommen! Misskey ist eine dezentralisierte Open-Source Microblogging-Platform.\nVerfasse „Notizen“ um mitzuteilen, was gerade passiert oder um Ereignisse mit anderen zu teilen. 📡\nMit „Reaktionen“ kannst du außerdem schnell deine Gefühle über Notizen anderer Benutzer zum Ausdruck bringen. 👍\nEine neue Welt wartet auf dich! 🚀"
poweredByMisskeyDescription: "{name} ist einer der durch die Open-Source-Plattform <b>Sharkey</b> betriebenen Dienste die auf Misskey basiert ist (meist als \"Misskey-Instanz\" bezeichnet)." poweredByMisskeyDescription: "{name} ist einer der durch die Open-Source-Plattform <b>Misskey</b> betriebenen Dienste."
monthAndDay: "{day}.{month}." monthAndDay: "{day}.{month}."
search: "Suchen" search: "Suchen"
notifications: "Benachrichtigungen" notifications: "Benachrichtigungen"
@ -15,7 +15,7 @@ gotIt: "Verstanden!"
cancel: "Abbrechen" cancel: "Abbrechen"
noThankYou: "Nein, danke" noThankYou: "Nein, danke"
enterUsername: "Benutzername eingeben" enterUsername: "Benutzername eingeben"
renotedBy: "Geboostet von {user}" renotedBy: "Renote von {user}"
noNotes: "Keine Notizen gefunden" noNotes: "Keine Notizen gefunden"
noNotifications: "Keine Benachrichtigungen gefunden" noNotifications: "Keine Benachrichtigungen gefunden"
instance: "Instanz" instance: "Instanz"
@ -105,13 +105,13 @@ followRequests: "Follow-Anfragen"
unfollow: "Entfolgen" unfollow: "Entfolgen"
followRequestPending: "Follow-Anfrage ausstehend" followRequestPending: "Follow-Anfrage ausstehend"
enterEmoji: "Gib ein Emoji ein" enterEmoji: "Gib ein Emoji ein"
renote: "Boost" renote: "Renote"
unrenote: "Boost zurücknehmen" unrenote: "Renote zurücknehmen"
renoted: "Boost getätigt." renoted: "Renote getätigt."
cantRenote: "Boosten dieses Beitrags nicht möglich." cantRenote: "Renote dieses Beitrags nicht möglich."
cantReRenote: "Boosten von einen Boost nicht möglich." cantReRenote: "Renote einer Renote nicht möglich."
quote: "Zitieren" quote: "Zitieren"
inChannelRenote: "Kanal-interner Boost" inChannelRenote: "Kanal-interner Renote"
inChannelQuote: "Kanal-internes Zitat" inChannelQuote: "Kanal-internes Zitat"
pinnedNote: "Angeheftete Notiz" pinnedNote: "Angeheftete Notiz"
pinned: "Angeheftet" pinned: "Angeheftet"
@ -166,7 +166,7 @@ youCanCleanRemoteFilesCache: "Klicke auf den 🗑️-Knopf der Dateiverwaltungsa
cacheRemoteSensitiveFiles: "Sensitive Dateien von fremden Instanzen im Cache speichern" cacheRemoteSensitiveFiles: "Sensitive Dateien von fremden Instanzen im Cache speichern"
cacheRemoteSensitiveFilesDescription: "Ist diese Einstellung deaktiviert, so werden sensitive Dateien fremder Instanzen direkt von dort ohne Zwischenspeicherung geladen." cacheRemoteSensitiveFilesDescription: "Ist diese Einstellung deaktiviert, so werden sensitive Dateien fremder Instanzen direkt von dort ohne Zwischenspeicherung geladen."
flagAsBot: "Als Bot markieren" flagAsBot: "Als Bot markieren"
flagAsBotDescription: "Aktiviere diese Option, falls dieses Benutzerkonto durch ein Programm gesteuert wird. Falls aktiviert, agiert es als Flag für andere Entwickler zur Verhinderung von endlosen Kettenreaktionen mit anderen Bots und lässt Sharkeys interne Systeme dieses Benutzerkonto als Bot behandeln." flagAsBotDescription: "Aktiviere diese Option, falls dieses Benutzerkonto durch ein Programm gesteuert wird. Falls aktiviert, agiert es als Flag für andere Entwickler zur Verhinderung von endlosen Kettenreaktionen mit anderen Bots und lässt Misskeys interne Systeme dieses Benutzerkonto als Bot behandeln."
flagAsCat: "Als Katze markieren" flagAsCat: "Als Katze markieren"
flagAsCatDescription: "Aktiviere diese Option, um dieses Benutzerkonto als Katze zu markieren." flagAsCatDescription: "Aktiviere diese Option, um dieses Benutzerkonto als Katze zu markieren."
flagShowTimelineReplies: "Antworten in der Chronik anzeigen" flagShowTimelineReplies: "Antworten in der Chronik anzeigen"
@ -229,7 +229,7 @@ noUsers: "Keine Benutzer gefunden"
editProfile: "Profil bearbeiten" editProfile: "Profil bearbeiten"
noteDeleteConfirm: "Möchtest du diese Notiz wirklich löschen?" noteDeleteConfirm: "Möchtest du diese Notiz wirklich löschen?"
pinLimitExceeded: "Du kannst nicht noch mehr Notizen anheften." pinLimitExceeded: "Du kannst nicht noch mehr Notizen anheften."
intro: "Sharkey ist installiert! Lass uns nun ein Administratorkonto einrichten." intro: "Misskey ist installiert! Lass uns nun ein Administratorkonto einrichten."
done: "Fertig" done: "Fertig"
processing: "In Bearbeitung …" processing: "In Bearbeitung …"
preview: "Vorschau" preview: "Vorschau"
@ -422,7 +422,7 @@ exploreFediverse: "Das Fediverse erkunden"
popularTags: "Beliebte Schlagwörter" popularTags: "Beliebte Schlagwörter"
userList: "Liste" userList: "Liste"
about: "Über" about: "Über"
aboutMisskey: "Über Sharkey" aboutMisskey: "Über Misskey"
administrator: "Administrator" administrator: "Administrator"
token: "Token" token: "Token"
2fa: "Zwei-Faktor-Authentifizierung" 2fa: "Zwei-Faktor-Authentifizierung"
@ -491,7 +491,6 @@ uiLanguage: "Sprache der Benutzeroberfläche"
aboutX: "Über {x}" aboutX: "Über {x}"
emojiStyle: "Emoji-Stil" emojiStyle: "Emoji-Stil"
native: "Nativ" native: "Nativ"
disableDrawer: "Keine ausfahrbaren Menüs verwenden"
showNoteActionsOnlyHover: "Notizmenü nur bei Mouseover anzeigen" showNoteActionsOnlyHover: "Notizmenü nur bei Mouseover anzeigen"
noHistory: "Kein Verlauf gefunden" noHistory: "Kein Verlauf gefunden"
signinHistory: "Anmeldungsverlauf" signinHistory: "Anmeldungsverlauf"
@ -572,7 +571,7 @@ sort: "Sortieren"
ascendingOrder: "Aufsteigende Reihenfolge" ascendingOrder: "Aufsteigende Reihenfolge"
descendingOrder: "Absteigende Reihenfolge" descendingOrder: "Absteigende Reihenfolge"
scratchpad: "Testumgebung" scratchpad: "Testumgebung"
scratchpadDescription: "Die Testumgebung bietet einen Bereich für AiScript-Experimente. Dort kannst du AiScript schreiben, ausführen sowie dessen Auswirkungen auf Sharkey überprüfen." scratchpadDescription: "Die Testumgebung bietet einen Bereich für AiScript-Experimente. Dort kannst du AiScript schreiben, ausführen sowie dessen Auswirkungen auf Misskey überprüfen."
output: "Ausgabe" output: "Ausgabe"
script: "Skript" script: "Skript"
disablePagesScript: "AiScript auf Seiten deaktivieren" disablePagesScript: "AiScript auf Seiten deaktivieren"
@ -687,10 +686,7 @@ abuseReported: "Deine Meldung wurde versendet. Vielen Dank."
reporter: "Melder" reporter: "Melder"
reporteeOrigin: "Herkunft des Gemeldeten" reporteeOrigin: "Herkunft des Gemeldeten"
reporterOrigin: "Herkunft des Meldenden" reporterOrigin: "Herkunft des Meldenden"
forwardReport: "Meldung an fremde Instanz weiterleiten"
forwardReportIsAnonymous: "Anstatt deines Benutzerkontos wird bei der fremden Instanz ein anonymes Systemkonto als Melder angezeigt."
send: "Senden" send: "Senden"
abuseMarkAsResolved: "Meldung als gelöst markieren"
openInNewTab: "In neuem Tab öffnen" openInNewTab: "In neuem Tab öffnen"
openInSideView: "In Seitenansicht öffnen" openInSideView: "In Seitenansicht öffnen"
defaultNavigationBehaviour: "Standardnavigationsverhalten" defaultNavigationBehaviour: "Standardnavigationsverhalten"
@ -709,14 +705,14 @@ unclip: "Aus Clip entfernen"
confirmToUnclipAlreadyClippedNote: "Diese Notiz ist bereits im \"{name}\" Clip enthalten. Möchtest du sie aus diesem Clip entfernen?" confirmToUnclipAlreadyClippedNote: "Diese Notiz ist bereits im \"{name}\" Clip enthalten. Möchtest du sie aus diesem Clip entfernen?"
public: "Öffentlich" public: "Öffentlich"
private: "Privat" private: "Privat"
i18nInfo: "Sharkey wird durch freiwillige Helfer in viele verschiedene Sprachen übersetzt. Auf {link} kannst du mithelfen." i18nInfo: "Misskey wird durch freiwillige Helfer in viele verschiedene Sprachen übersetzt. Auf {link} kannst du mithelfen."
manageAccessTokens: "Zugriffstokens verwalten" manageAccessTokens: "Zugriffstokens verwalten"
accountInfo: "Benutzerkonto-Informationen" accountInfo: "Benutzerkonto-Informationen"
notesCount: "Anzahl der Notizen" notesCount: "Anzahl der Notizen"
repliesCount: "Anzahl gesendeter Antworten" repliesCount: "Anzahl gesendeter Antworten"
renotesCount: "Anzahl getätigter Renotes" renotesCount: "Anzahl getätigter Renotes"
repliedCount: "Anzahl erhaltener Antworten" repliedCount: "Anzahl erhaltener Antworten"
renotedCount: "Anzahl erhaltener Boosts" renotedCount: "Anzahl erhaltener Renotes"
followingCount: "Anzahl gefolgter Benutzer" followingCount: "Anzahl gefolgter Benutzer"
followersCount: "Anzahl an Followern" followersCount: "Anzahl an Followern"
sentReactionsCount: "Anzahl gesendeter Reaktionen" sentReactionsCount: "Anzahl gesendeter Reaktionen"
@ -763,7 +759,7 @@ onlineUsersCount: "{n} Benutzer sind online"
nUsers: "{n} Benutzer" nUsers: "{n} Benutzer"
nNotes: "{n} Notizen" nNotes: "{n} Notizen"
sendErrorReports: "Fehlerberichte senden" sendErrorReports: "Fehlerberichte senden"
sendErrorReportsDescription: "Ist diese Option aktiviert, so werden beim Auftreten von Fehlern detaillierte Fehlerinformationen an Sharkey weitergegeben, was zur Verbesserung der Qualität von Sharkey beiträgt.\nEnthalten in diesen Informationen sind u.a. die Version deines Betriebssystems, welchen Browser du verwendest und ein Verlauf deiner Aktivitäten innerhalb Sharkey." sendErrorReportsDescription: "Ist diese Option aktiviert, so werden beim Auftreten von Fehlern detaillierte Fehlerinformationen an Misskey weitergegeben, was zur Verbesserung der Qualität von Misskey beiträgt.\nEnthalten in diesen Informationen sind u.a. die Version deines Betriebssystems, welchen Browser du verwendest und ein Verlauf deiner Aktivitäten innerhalb Misskey."
myTheme: "Mein Farbschema" myTheme: "Mein Farbschema"
backgroundColor: "Hintergrundfarbe" backgroundColor: "Hintergrundfarbe"
accentColor: "Akzentfarbe" accentColor: "Akzentfarbe"
@ -857,7 +853,7 @@ hashtags: "Hashtags"
troubleshooting: "Problembehandlung" troubleshooting: "Problembehandlung"
useBlurEffect: "Weichzeichnungseffekt in der Benutzeroberfläche verwenden" useBlurEffect: "Weichzeichnungseffekt in der Benutzeroberfläche verwenden"
learnMore: "Mehr erfahren" learnMore: "Mehr erfahren"
misskeyUpdated: "Sharkey wurde aktualisiert!" misskeyUpdated: "Misskey wurde aktualisiert!"
whatIsNew: "Änderungen anzeigen" whatIsNew: "Änderungen anzeigen"
translate: "Übersetzen" translate: "Übersetzen"
translatedFrom: "Aus {x} übersetzt" translatedFrom: "Aus {x} übersetzt"
@ -984,9 +980,8 @@ numberOfLikes: "\"Gefällt mir\"-Anzahl"
show: "Anzeigen" show: "Anzeigen"
neverShow: "Nicht wieder anzeigen" neverShow: "Nicht wieder anzeigen"
remindMeLater: "Vielleicht später" remindMeLater: "Vielleicht später"
didYouLikeMisskey: "Gefällt dir Sharkey?" didYouLikeMisskey: "Gefällt dir Misskey?"
pleaseDonate: "Sharkey ist die kostenlose Software, die von {host} verwendet wird. Wir würden uns über Spenden freuen, damit dessen Entwicklung weitergeführt werden kann!" pleaseDonate: "Misskey ist die kostenlose Software, die von {host} verwendet wird. Wir würden uns über Spenden freuen, damit dessen Entwicklung weitergeführt werden kann!"
pleaseDonateInstance: "Du kannst {host} auch direkt unterstützen, indem du an deine Instanz Administration spendest."
roles: "Rollen" roles: "Rollen"
role: "Rolle" role: "Rolle"
noRole: "Rolle nicht gefunden" noRole: "Rolle nicht gefunden"
@ -1006,7 +1001,7 @@ permissionDeniedError: "Aktion verweigert"
permissionDeniedErrorDescription: "Dieses Benutzerkonto besitzt nicht die Berechtigung, um diese Aktion auszuführen." permissionDeniedErrorDescription: "Dieses Benutzerkonto besitzt nicht die Berechtigung, um diese Aktion auszuführen."
preset: "Vorlage" preset: "Vorlage"
selectFromPresets: "Aus Vorlagen wählen" selectFromPresets: "Aus Vorlagen wählen"
achievements: "Erfolge" achievements: "Errungenschaften"
gotInvalidResponseError: "Ungültige Antwort des Servers" gotInvalidResponseError: "Ungültige Antwort des Servers"
gotInvalidResponseErrorDescription: "Eventuell ist der Server momentan nicht erreichbar oder untergeht Wartungsarbeiten. Bitte versuche es später noch einmal." gotInvalidResponseErrorDescription: "Eventuell ist der Server momentan nicht erreichbar oder untergeht Wartungsarbeiten. Bitte versuche es später noch einmal."
thisPostMayBeAnnoying: "Dieser Beitrag stört eventuell andere Benutzer." thisPostMayBeAnnoying: "Dieser Beitrag stört eventuell andere Benutzer."
@ -1101,7 +1096,7 @@ rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Diese Rollen müssen öffe
cancelReactionConfirm: "Möchtest du deine Reaktion wirklich löschen?" cancelReactionConfirm: "Möchtest du deine Reaktion wirklich löschen?"
changeReactionConfirm: "Möchtest du deine Reaktion wirklich ändern?" changeReactionConfirm: "Möchtest du deine Reaktion wirklich ändern?"
later: "Später" later: "Später"
goToMisskey: "Zu Sharkey" goToMisskey: "Zu Misskey"
additionalEmojiDictionary: "Zusätzliche Emoji-Wörterbücher" additionalEmojiDictionary: "Zusätzliche Emoji-Wörterbücher"
installed: "Installiert" installed: "Installiert"
branding: "Branding" branding: "Branding"
@ -1166,8 +1161,6 @@ impressumDescription: "In manchen Ländern, wie Deutschland und dessen Umgebung,
privacyPolicy: "Datenschutzerklärung" privacyPolicy: "Datenschutzerklärung"
privacyPolicyUrl: "Datenschutzerklärungs-URL" privacyPolicyUrl: "Datenschutzerklärungs-URL"
tosAndPrivacyPolicy: "Nutzungsbedingungen und Datenschutzerklärung" tosAndPrivacyPolicy: "Nutzungsbedingungen und Datenschutzerklärung"
donation: "Spenden"
donationUrl: "Spenden-URL"
avatarDecorations: "Profilbilddekoration" avatarDecorations: "Profilbilddekoration"
attach: "Anbringen" attach: "Anbringen"
detach: "Entfernen" detach: "Entfernen"
@ -1271,7 +1264,7 @@ _accountMigration:
moveTo: "Dieses Konto zu einem neuen migrieren" moveTo: "Dieses Konto zu einem neuen migrieren"
moveToLabel: "Umzugsziel:" moveToLabel: "Umzugsziel:"
moveCannotBeUndone: "Die Migration eines Benutzerkontos ist unwiderruflich." moveCannotBeUndone: "Die Migration eines Benutzerkontos ist unwiderruflich."
moveAccountDescription: "Hierdurch wird dein Konto zu einem anderen migriert.\n ・Follower von diesem Konto werden automatisch auf das neue Konto migriert\n ・Dieses Konto wird allen Nutzern, denen es derzeit folgt, nicht mehr folgen\n ・Mit diesem Konto können keine neuen Notizen usw. erstellt werden\n\nWährend die Migration der Follower automatisch erfolgt, muss die Migration der Konten, denen du folgst, manuell vorbereitet werden. Exportiere hierzu die Liste der gefolgten Nutzer über das Einstellungsmenu, und importiere diese Liste im neuen Konto. Das gleiche Verfahren gilt für erstellte Listen und stummgeschaltete oder blockierte Nutzer.\n\n(Diese Erklärung gilt für Sharkey v13.12.0 oder später. Die Funktionsweise andere ActivityPub-Software, beispielsweise Mastodon, kann hiervon abweichen.)" moveAccountDescription: "Hierdurch wird dein Konto zu einem anderen migriert.\n ・Follower von diesem Konto werden automatisch auf das neue Konto migriert\n ・Dieses Konto wird allen Nutzern, denen es derzeit folgt, nicht mehr folgen\n ・Mit diesem Konto können keine neuen Notizen usw. erstellt werden\n\nWährend die Migration der Follower automatisch erfolgt, muss die Migration der Konten, denen du folgst, manuell vorbereitet werden. Exportiere hierzu die Liste der gefolgten Nutzer über das Einstellungsmenu, und importiere diese Liste im neuen Konto. Das gleiche Verfahren gilt für erstellte Listen und stummgeschaltete oder blockierte Nutzer.\n\n(Diese Erklärung gilt für Misskey v13.12.0 oder später. Die Funktionsweise andere ActivityPub-Software, beispielsweise Mastodon, kann hiervon abweichen.)"
moveAccountHowTo: "Um ein Konto zu migrieren, erstelle zuerst auf dem Umzugsziel einen Alias für dieses Konto.\nGib dann das Umzugsziel in folgendem Format ein: @username@server.example.com" moveAccountHowTo: "Um ein Konto zu migrieren, erstelle zuerst auf dem Umzugsziel einen Alias für dieses Konto.\nGib dann das Umzugsziel in folgendem Format ein: @username@server.example.com"
startMigration: "Migrieren" startMigration: "Migrieren"
migrationConfirm: "Dieses Konto wirklich zu {account} umziehen? Sobald der Umzug beginnt, kann er nicht rückgängig gemacht werden, und dieses Konto nicht wieder im ursprünglichen Zustand verwendet werden." migrationConfirm: "Dieses Konto wirklich zu {account} umziehen? Sobald der Umzug beginnt, kann er nicht rückgängig gemacht werden, und dieses Konto nicht wieder im ursprünglichen Zustand verwendet werden."
@ -1282,26 +1275,26 @@ _achievements:
earnedAt: "Freigeschaltet am" earnedAt: "Freigeschaltet am"
_types: _types:
_notes1: _notes1:
title: "Hallo Sharkey!" title: "Hallo Misskey!"
description: "Sende deine erste Notiz" description: "Sende deine erste Notiz"
flavor: "Hab eine schöne Zeit mit Sharkey!" flavor: "Hab eine schöne Zeit mit Misskey!"
_notes10: _notes10:
title: "Notizblog" title: "Ein paar Notizen"
description: "10 Notizen gesendet" description: "10 Notizen gesendet"
_notes100: _notes100:
title: "Schreiberling" title: "Viele Notizen"
description: "100 Notizen gesendet" description: "100 Notizen gesendet"
_notes500: _notes500:
title: "Autor" title: "Überschüttet mit Notizen"
description: "500 Notizen gesendet" description: "500 Notizen gesendet"
_notes1000: _notes1000:
title: "Bestseller" title: "Berg an Notizen"
description: "1.000 Notizen gesendet" description: "1.000 Notizen gesendet"
_notes5000: _notes5000:
title: "Dicker Wälzer" title: "Überquellende Notizen"
description: "5.000 Notizen gesendet" description: "5.000 Notizen gesendet"
_notes10000: _notes10000:
title: "Verlagshaus" title: "Supernotiz"
description: "10.000 Notizen gesendet" description: "10.000 Notizen gesendet"
_notes20000: _notes20000:
title: "Brauche... mehr... Notizen..." title: "Brauche... mehr... Notizen..."
@ -1328,28 +1321,28 @@ _achievements:
title: "Notizversum" title: "Notizversum"
description: "90.000 Notizen gesendet" description: "90.000 Notizen gesendet"
_notes100000: _notes100000:
title: "ALL YOUR NOTES ARE BELONG TO US" title: "ALL YOUR NOTE ARE BELONG TO US"
description: "100.000 Notizen gesendet" description: "100.000 Notizen gesendet"
flavor: "Du hast wirklich viel zu sagen." flavor: "Du hast wirklich viel zu sagen."
_login3: _login3:
title: "Neuling " title: "Anfänger "
description: "An 3 Tagen eingeloggt" description: "An 3 Tagen eingeloggt"
flavor: "Nenn' mich ab heute Misskist" flavor: "Nenn' mich ab heute Misskist"
_login7: _login7:
title: "Neuling Ⅱ" title: "Anfänger Ⅱ"
description: "An 7 Tagen eingeloggt" description: "An 7 Tagen eingeloggt"
flavor: "Na, eingewöht?" flavor: "Na, eingewöht?"
_login15: _login15:
title: "Neuling Ⅲ" title: "Anfänger Ⅲ"
description: "An 15 Tagen eingeloggt" description: "An 15 Tagen eingeloggt"
_login30: _login30:
title: "Fedizen " title: "Misskist "
description: "An 30 Tagen eingeloggt" description: "An 30 Tagen eingeloggt"
_login60: _login60:
title: "Fedizen Ⅱ" title: "Misskist Ⅱ"
description: "An 60 Tagen eingeloggt" description: "An 60 Tagen eingeloggt"
_login100: _login100:
title: "Fedizen Ⅲ" title: "Misskist Ⅲ"
description: "An 100 Tagen eingeloggt" description: "An 100 Tagen eingeloggt"
flavor: "Violent Misskist" flavor: "Violent Misskist"
_login200: _login200:
@ -1362,14 +1355,14 @@ _achievements:
title: "Stammbesucher Ⅲ" title: "Stammbesucher Ⅲ"
description: "An 400 Tagen eingeloggt" description: "An 400 Tagen eingeloggt"
_login500: _login500:
title: "Alter Hase " title: "Veteran "
description: "An 500 Tagen eingeloggt" description: "An 500 Tagen eingeloggt"
flavor: "Meine Kameraden, ich liebe sie, die Notizen." flavor: "Meine Kameraden, ich liebe sie, die Notizen."
_login600: _login600:
title: "Alter Hase Ⅱ" title: "Veteran Ⅱ"
description: "An 600 Tagen eingeloggt" description: "An 600 Tagen eingeloggt"
_login700: _login700:
title: "Alter Hase Ⅲ" title: "Veteran Ⅲ"
description: "An 700 Tagen eingeloggt" description: "An 700 Tagen eingeloggt"
_login800: _login800:
title: "Meister der Notizen " title: "Meister der Notizen "
@ -1378,39 +1371,39 @@ _achievements:
title: "Meister der Notizen Ⅱ" title: "Meister der Notizen Ⅱ"
description: "An 900 Tagen eingeloggt" description: "An 900 Tagen eingeloggt"
_login1000: _login1000:
title: "Wie die Zeit vergeht" title: "Meister der Notizen Ⅲ"
description: "An 1000 Tagen eingeloggt" description: "An 1000 Tagen eingeloggt"
flavor: "Danke, dass du Sharkey nutzt!" flavor: "Danke, dass du Misskey nutzt!"
_noteClipped1: _noteClipped1:
title: "Das merk ich mir" title: "Muss... clippen..."
description: "Die erste Notiz geclippt" description: "Die erste Notiz geclippt"
_noteFavorited1: _noteFavorited1:
title: "Sternengucker" title: "Sternengucker"
description: "Eine Notiz als Favorit markiert" description: "Eine Notiz als Favorit markiert"
_myNoteFavorited1: _myNoteFavorited1:
title: "Hilfreich" title: "Sternensucher"
description: "Ein anderer Benutzer hat eine deiner Notizen als Favoriten markiert" description: "Ein anderer Benutzer hat eine deiner Notizen als Favoriten markiert"
_profileFilled: _profileFilled:
title: "Perfekte Vorbereitung" title: "Perfekte Vorbereitung"
description: "Fülle dein Profil aus" description: "Fülle dein Profil aus"
_markedAsCat: _markedAsCat:
title: "Das Königreich der Katzen" title: "Ich der Kater"
description: "Markiere dein Profil als Katze" description: "Markiere dein Konto als Katze"
flavor: "Einen Namen bekommst du später~ " flavor: "Einen Namen bekommst du später. "
_following1: _following1:
title: "Immer auf dem neusten Stand" title: "Das Folgen beginnt"
description: "Du folgst deiner ersten Person" description: "Du folgst deiner ersten Person"
_following10: _following10:
title: "Folge ihnen... folge ihnen..." title: "Folge ihnen... folge ihnen..."
description: "Du folgst über 10 Leuten" description: "Du folgst über 10 Leuten"
_following50: _following50:
title: "Lieblingsposter" title: "Viele Freunde"
description: "Du folgst über 50 Leuten" description: "Du folgst über 50 Leuten"
_following100: _following100:
title: "Die Top 100" title: "100 Freunde"
description: "Du folgst über 100 Leuten" description: "Du folgst über 100 Leuten"
_following300: _following300:
title: "Folgen, folgen, folgen!" title: "Freundeüberschuss"
description: "Du folgst über 300 Leuten" description: "Du folgst über 300 Leuten"
_followers1: _followers1:
title: "Der erste Follower" title: "Der erste Follower"
@ -1425,7 +1418,7 @@ _achievements:
title: "Beliebt" title: "Beliebt"
description: "Die Anzahl deiner Follower hat 100 überschritten" description: "Die Anzahl deiner Follower hat 100 überschritten"
_followers300: _followers300:
title: "Teil des Schiffs, Teil der Crew" title: "Eine geordnete Reihe, bitte!"
description: "Die Anzahl deiner Follower hat 300 überschritten" description: "Die Anzahl deiner Follower hat 300 überschritten"
_followers500: _followers500:
title: "Funkmast" title: "Funkmast"
@ -1434,31 +1427,31 @@ _achievements:
title: "Influencer" title: "Influencer"
description: "Die Anzahl deiner Follower hat 1000 überschritten" description: "Die Anzahl deiner Follower hat 1000 überschritten"
_collectAchievements30: _collectAchievements30:
title: "Sammler der Erfolge" title: "Sammler der Errungenschaften"
description: "Schalte 30 Errungenschaften frei" description: "Schalte 30 Errungenschaften frei"
_viewAchievements3min: _viewAchievements3min:
title: "Ausstellung" title: "Fan von Errungenschaften"
description: "Schau dir die Liste deiner Errungenschaften für mindestens 3 Minuten an" description: "Schau dir die Liste deiner Errungenschaften für mindestens 3 Minuten an"
_iLoveMisskey: _iLoveMisskey:
title: "I Love Sharkey" title: "I Love Misskey"
description: "Sende \"I ❤ #Sharkey\"" description: "Sende \"I ❤ #Misskey\""
flavor: "Danke, dass du Sharkey verwendest! - vom Entwicklerteam" flavor: "Danke, dass du Misskey verwendest! - vom Entwicklerteam"
_foundTreasure: _foundTreasure:
title: "Schatzsuche" title: "Schatzsuche"
description: "Du hast einen verborgenen Schatz gefunden" description: "Du hast einen verborgenen Schatz gefunden"
_client30min: _client30min:
title: "Kurze Pause" title: "Kurze Pause"
description: "Habe Sharkey für mindestens 30 Minuten geöffnet" description: "Habe Misskey für mindestens 30 Minuten geöffnet"
_client60min: _client60min:
title: "Munter mit Sharkey" title: "Munter mit Misskey"
description: "Habe Sharkey für mindestens 60 Minuten geöffnet" description: "Habe Misskey für mindestens 60 Minuten geöffnet"
_noteDeletedWithin1min: _noteDeletedWithin1min:
title: "Katze auf der Tastatur" title: "Ups"
description: "Lösche eine Notiz innerhalb von 1 Minute nachdem sie gesendet wurde" description: "Lösche eine Notiz innerhalb von 1 Minute nachdem sie gesendet wurde"
_postedAtLateNight: _postedAtLateNight:
title: "Nachtaktiv" title: "Nachtaktiv"
description: "Sende mitten in der Nacht eine Notiz" description: "Sende mitten in der Nacht eine Notiz"
flavor: "Echte Katzen sind nunmal nachtaktiv~" flavor: "Geh bald schlafen."
_postedAt0min0sec: _postedAt0min0sec:
title: "Zeitansage" title: "Zeitansage"
description: "Sende um 00:00 eine Notiz" description: "Sende um 00:00 eine Notiz"
@ -1473,7 +1466,7 @@ _achievements:
title: "Analyst" title: "Analyst"
description: "Schau dir die Messwerte der Instanz an" description: "Schau dir die Messwerte der Instanz an"
_outputHelloWorldOnScratchpad: _outputHelloWorldOnScratchpad:
title: "Junghacker" title: "Hallo Welt!"
description: "Gib \"hello world\" in der Testumgebung aus" description: "Gib \"hello world\" in der Testumgebung aus"
_open3windows: _open3windows:
title: "Splitscreen" title: "Splitscreen"
@ -1482,25 +1475,25 @@ _achievements:
title: "Zyklischer Verweis" title: "Zyklischer Verweis"
description: "Versuche, in Drive einen Zirkelbezug von Ordnern herzustellen" description: "Versuche, in Drive einen Zirkelbezug von Ordnern herzustellen"
_reactWithoutRead: _reactWithoutRead:
title: "Erst reagieren, dann lesen" title: "Hast du das wirklich gelesen?"
description: "Reagiere auf eine Notiz mit mindestens 100 Zeichen innerhalb von 3 Sekunden der Erstellung der Notiz" description: "Reagiere auf eine Notiz mit mindestens 100 Zeichen innerhalb von 3 Sekunden der Erstellung der Notiz"
_clickedClickHere: _clickedClickHere:
title: "Klicke hier" title: "Klicke hier"
description: "Du hast hier geklickt" description: "Du hast hier geklickt"
_justPlainLucky: _justPlainLucky:
title: "Glück 100" title: "Pures Glück"
description: "Kann alle 10 Sekunden mit einer Warscheinlichkeit von 0.005% erhalten werden" description: "Kann alle 10 Sekunden mit einer Warscheinlichkeit von 0.005% erhalten werden"
_setNameToSyuilo: _setNameToSyuilo:
title: "Gottkomplex" title: "Gottkomplex"
description: "Setze deinen Namen auf \"syuilo\"" description: "Setze deinen Namen auf \"syuilo\""
_passedSinceAccountCreated1: _passedSinceAccountCreated1:
title: "Davon erzähle ich meinen Enkeln" title: "Einjahresjubiläum"
description: "Seit der Erstellung deines Kontos ist 1 Jahr vergangen" description: "Seit der Erstellung deines Kontos ist 1 Jahr vergangen"
_passedSinceAccountCreated2: _passedSinceAccountCreated2:
title: "Diese Software hat mich zur Katze gemacht" title: "Zweijahresjubiläum"
description: "Seit der Erstellung deines Kontos sind 2 Jahre vergangen" description: "Seit der Erstellung deines Kontos sind 2 Jahre vergangen"
_passedSinceAccountCreated3: _passedSinceAccountCreated3:
title: "Goldene Zeit" title: "Dreijahresjubiläum"
description: "Seit der Erstellung deines Kontos sind 3 Jahre vergangen" description: "Seit der Erstellung deines Kontos sind 3 Jahre vergangen"
_loggedInOnBirthday: _loggedInOnBirthday:
title: "Alles Gute Zum Geburtstag" title: "Alles Gute Zum Geburtstag"
@ -1508,15 +1501,15 @@ _achievements:
_loggedInOnNewYearsDay: _loggedInOnNewYearsDay:
title: "Frohes Neujahr" title: "Frohes Neujahr"
description: "Logge dich am Neujahrstag ein" description: "Logge dich am Neujahrstag ein"
flavor: "Auf ein weiteres tolles Jahr in diesem Universum!" flavor: "Auf ein weiteres tolles Jahr in dieser Instanz"
_cookieClicked: _cookieClicked:
title: "Kreise klicken für Omas" title: "Ein Spiel, in dem du auf einen Keks klickst"
description: "Den Keks geklickt" description: "Den Keks geklickt"
flavor: "Bist du hier richtig?" flavor: "Bist du hier richtig?"
_brainDiver: _brainDiver:
title: "Brain Diver" title: "Brain Diver"
description: "Sende den Link zu Brain Diver" description: "Sende den Link zu Brain Diver"
flavor: "Sharkey-Sharkey La-Tu-Ma" flavor: "Misskey-Misskey La-Tu-Ma"
_smashTestNotificationButton: _smashTestNotificationButton:
title: "Testüberfluss" title: "Testüberfluss"
description: "Betätige den Benachrichtigungstest mehrfach innerhalb einer extrem kurzen Zeitspanne" description: "Betätige den Benachrichtigungstest mehrfach innerhalb einer extrem kurzen Zeitspanne"
@ -1681,12 +1674,12 @@ _registry:
domain: "Domain" domain: "Domain"
createKey: "Schlüssel erstellen" createKey: "Schlüssel erstellen"
_aboutMisskey: _aboutMisskey:
about: "Sharkey ist Open-Source-Software basiert auf Misskey welche von syuilo seit 2014 entwickelt wird." about: "Misskey ist Open-Source-Software, welche von syuilo seit 2014 entwickelt wird."
contributors: "Hauptmitwirkende" contributors: "Hauptmitwirkende"
allContributors: "Alle Mitwirkenden" allContributors: "Alle Mitwirkenden"
source: "Quellcode" source: "Quellcode"
translation: "Sharkey übersetzen" translation: "Misskey übersetzen"
donate: "An Sharkey spenden" donate: "An Misskey spenden"
morePatrons: "Wir schätzen ebenso die Unterstützung vieler anderer hier nicht gelisteter Personen sehr. Danke! 🥰" morePatrons: "Wir schätzen ebenso die Unterstützung vieler anderer hier nicht gelisteter Personen sehr. Danke! 🥰"
patrons: "UnterstützerInnen" patrons: "UnterstützerInnen"
projectMembers: "Projektmitglieder" projectMembers: "Projektmitglieder"
@ -2116,7 +2109,7 @@ _notification:
youGotMention: "{name} hat dich erwähnt" youGotMention: "{name} hat dich erwähnt"
youGotReply: "{name} hat dir geantwortet" youGotReply: "{name} hat dir geantwortet"
youGotQuote: "{name} hat dich zitiert" youGotQuote: "{name} hat dich zitiert"
youRenoted: "Boost deiner Notiz von {name}" youRenoted: "Renote deiner Notiz von {name}"
youWereFollowed: "ist dir gefolgt" youWereFollowed: "ist dir gefolgt"
youReceivedFollowRequest: "Du hast eine Follow-Anfrage erhalten" youReceivedFollowRequest: "Du hast eine Follow-Anfrage erhalten"
yourFollowRequestAccepted: "Deine Follow-Anfrage wurde akzeptiert" yourFollowRequestAccepted: "Deine Follow-Anfrage wurde akzeptiert"
@ -2145,6 +2138,7 @@ _notification:
receiveFollowRequest: "Erhaltene Follow-Anfragen" receiveFollowRequest: "Erhaltene Follow-Anfragen"
followRequestAccepted: "Akzeptierte Follow-Anfragen" followRequestAccepted: "Akzeptierte Follow-Anfragen"
achievementEarned: "Errungenschaft freigeschaltet" achievementEarned: "Errungenschaft freigeschaltet"
login: "Anmelden"
app: "Benachrichtigungen von Apps" app: "Benachrichtigungen von Apps"
_actions: _actions:
followBack: "folgt dir nun auch" followBack: "folgt dir nun auch"

View file

@ -378,6 +378,7 @@ _notification:
renote: "Κοινοποίηση σημειώματος" renote: "Κοινοποίηση σημειώματος"
quote: "Παράθεση" quote: "Παράθεση"
reaction: "Αντιδράσεις" reaction: "Αντιδράσεις"
login: "Σύνδεση"
_actions: _actions:
reply: "Απάντηση" reply: "Απάντηση"
renote: "Κοινοποίηση σημειώματος" renote: "Κοινοποίηση σημειώματος"

File diff suppressed because it is too large Load diff

View file

@ -109,11 +109,14 @@ enterEmoji: "Ingresar emojis"
renote: "Renotar" renote: "Renotar"
unrenote: "Quitar renota" unrenote: "Quitar renota"
renoted: "Renotado" renoted: "Renotado"
renotedToX: "{name} usuarios han renotado。"
cantRenote: "No se puede renotar este post" cantRenote: "No se puede renotar este post"
cantReRenote: "No se puede renotar una renota" cantReRenote: "No se puede renotar una renota"
quote: "Citar" quote: "Citar"
inChannelRenote: "Renota sólo del canal" inChannelRenote: "Renota sólo del canal"
inChannelQuote: "Cita sólo del canal" inChannelQuote: "Cita sólo del canal"
renoteToChannel: "Renotar a otro canal"
renoteToOtherChannel: "Renotar a otro canal"
pinnedNote: "Nota fijada" pinnedNote: "Nota fijada"
pinned: "Fijar al perfil" pinned: "Fijar al perfil"
you: "Tú" you: "Tú"
@ -152,6 +155,7 @@ editList: "Editar lista"
selectChannel: "Seleccionar canal" selectChannel: "Seleccionar canal"
selectAntenna: "Seleccionar antena" selectAntenna: "Seleccionar antena"
editAntenna: "Editar antena" editAntenna: "Editar antena"
createAntenna: "Crear una antena"
selectWidget: "Seleccionar widget" selectWidget: "Seleccionar widget"
editWidgets: "Editar widgets" editWidgets: "Editar widgets"
editWidgetsExit: "Terminar edición" editWidgetsExit: "Terminar edición"
@ -178,6 +182,10 @@ addAccount: "Agregar Cuenta"
reloadAccountsList: "Recargar lista de cuentas" reloadAccountsList: "Recargar lista de cuentas"
loginFailed: "Error al iniciar sesión." loginFailed: "Error al iniciar sesión."
showOnRemote: "Ver en una instancia remota" showOnRemote: "Ver en una instancia remota"
continueOnRemote: "Ver en una instancia remota"
chooseServerOnMisskeyHub: "Elegir un servidor en Misskey Hub"
specifyServerHost: "Especifica una instancia directamente"
inputHostName: "Introduzca el dominio"
general: "General" general: "General"
wallpaper: "Fondo de pantalla" wallpaper: "Fondo de pantalla"
setWallpaper: "Establecer fondo de pantalla" setWallpaper: "Establecer fondo de pantalla"
@ -406,7 +414,7 @@ antennaKeywordsDescription: "Separar con espacios es una declaración AND, separ
notifyAntenna: "Notificar nueva nota" notifyAntenna: "Notificar nueva nota"
withFileAntenna: "Sólo notas con archivos adjuntados" withFileAntenna: "Sólo notas con archivos adjuntados"
enableServiceworker: "Activar ServiceWorker" enableServiceworker: "Activar ServiceWorker"
antennaUsersDescription: "Elegir nombres de usuarios separados por una linea nueva. Utilice \"*@instance.com\" para especificar todos los usuarios de una instancia." antennaUsersDescription: "Elegir nombres de usuarios separados por una linea nueva"
caseSensitive: "Distinguir mayúsculas de minúsculas" caseSensitive: "Distinguir mayúsculas de minúsculas"
withReplies: "Incluir respuestas" withReplies: "Incluir respuestas"
connectedTo: "Estas cuentas están conectadas" connectedTo: "Estas cuentas están conectadas"
@ -494,7 +502,6 @@ uiLanguage: "Idioma de visualización de la interfaz"
aboutX: "Acerca de {x}" aboutX: "Acerca de {x}"
emojiStyle: "Estilo de emoji" emojiStyle: "Estilo de emoji"
native: "Nativo" native: "Nativo"
disableDrawer: "No mostrar los menús en cajones"
showNoteActionsOnlyHover: "Mostrar acciones de la nota sólo al pasar el cursor" showNoteActionsOnlyHover: "Mostrar acciones de la nota sólo al pasar el cursor"
showReactionsCount: "Mostrar el número de reacciones en las notas" showReactionsCount: "Mostrar el número de reacciones en las notas"
noHistory: "No hay datos en el historial" noHistory: "No hay datos en el historial"
@ -693,10 +700,7 @@ abuseReported: "Se ha enviado el reporte. Muchas gracias."
reporter: "Reportador" reporter: "Reportador"
reporteeOrigin: "Reportar a" reporteeOrigin: "Reportar a"
reporterOrigin: "Origen del reporte" reporterOrigin: "Origen del reporte"
forwardReport: "Transferir un informe a una instancia remota"
forwardReportIsAnonymous: "No puede ver su información de la instancia remota y aparecerá como una cuenta anónima del sistema"
send: "Enviar" send: "Enviar"
abuseMarkAsResolved: "Marcar reporte como resuelto"
openInNewTab: "Abrir en una Nueva Pestaña" openInNewTab: "Abrir en una Nueva Pestaña"
openInSideView: "Abrir en una vista al costado" openInSideView: "Abrir en una vista al costado"
defaultNavigationBehaviour: "Navegación por defecto" defaultNavigationBehaviour: "Navegación por defecto"
@ -1095,6 +1099,8 @@ preservedUsernames: "Nombre de usuario reservado"
preservedUsernamesDescription: "La lista de nombres de usuario para reservar tienen que separarse con saltos de línea.\nEstos estarán indisponibles durante la creación de cuentas, pero pueden ser usados para que los administradores puedan crear esas cuentas manualmente. Las cuentas existentes con esos nombres de usuario no se verán afectadas." preservedUsernamesDescription: "La lista de nombres de usuario para reservar tienen que separarse con saltos de línea.\nEstos estarán indisponibles durante la creación de cuentas, pero pueden ser usados para que los administradores puedan crear esas cuentas manualmente. Las cuentas existentes con esos nombres de usuario no se verán afectadas."
createNoteFromTheFile: "Componer una nota desde éste archivo" createNoteFromTheFile: "Componer una nota desde éste archivo"
archive: "Archivo" archive: "Archivo"
archived: "Archivado"
unarchive: "Desarchivar"
channelArchiveConfirmTitle: "¿Seguro de archivar {name}?" 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." 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." thisChannelArchived: "El canal ha sido archivado."
@ -1147,8 +1153,8 @@ currentAnnouncements: "Anuncios actuales"
pastAnnouncements: "Anuncios anteriores" pastAnnouncements: "Anuncios anteriores"
youHaveUnreadAnnouncements: "Hay anuncios sin leer" youHaveUnreadAnnouncements: "Hay anuncios sin leer"
useSecurityKey: "Por favor, sigue las instrucciones de tu dispositivo o navegador para usar tu clave de seguridad o tu clave de paso." useSecurityKey: "Por favor, sigue las instrucciones de tu dispositivo o navegador para usar tu clave de seguridad o tu clave de paso."
replies: "Respuestas" replies: "Responder"
renotes: "Renotas" renotes: "Renotar"
loadReplies: "Ver respuestas" loadReplies: "Ver respuestas"
loadConversation: "Ver conversación" loadConversation: "Ver conversación"
pinnedList: "Lista fijada" pinnedList: "Lista fijada"
@ -1300,10 +1306,10 @@ _initialTutorial:
_reaction: _reaction:
title: "¿Qué son las reacciones?" title: "¿Qué son las reacciones?"
description: "Se puede reaccionar a las Notas con diferentes emojis. Las reacciones te permiten expresar matices que no se pueden transmitir con un simple 'me gusta'." description: "Se puede reaccionar a las Notas con diferentes emojis. Las reacciones te permiten expresar matices que no se pueden transmitir con un simple 'me gusta'."
letsTryReacting: "Puedes añadir reacciones pulsando en el botón '{reaction}' de la nota. ¡Intenta reaccionar a esta nota de ejemplo!" letsTryReacting: "Puedes añadir reacciones pulsando en el botón '+' de la nota. ¡Intenta reaccionar a esta nota de ejemplo!"
reactToContinue: "Añade una reacción para continuar." reactToContinue: "Añade una reacción para continuar."
reactNotification: "Recibirás notificaciones en tiempo real cuando alguien reaccione a tu nota." reactNotification: "Recibirás notificaciones en tiempo real cuando alguien reaccione a tu nota."
reactDone: "Puedes deshacer una reacción pulsando en el botón '{undo}'." reactDone: "Puedes deshacer una reacción pulsando en el botón '-'."
_timeline: _timeline:
title: "El concepto de Línea de tiempo" title: "El concepto de Línea de tiempo"
description1: "Misskey proporciona múltiples líneas de tiempo basadas en su uso (algunas pueden no estar disponibles dependiendo de las políticas de la instancia)." description1: "Misskey proporciona múltiples líneas de tiempo basadas en su uso (algunas pueden no estar disponibles dependiendo de las políticas de la instancia)."
@ -2149,7 +2155,6 @@ _poll:
remainingHours: "Quedan {h} horas y {m} minutos para que finalice" remainingHours: "Quedan {h} horas y {m} minutos para que finalice"
remainingMinutes: "Quedan {m} minutos y {s} segundos para que finalice" remainingMinutes: "Quedan {m} minutos y {s} segundos para que finalice"
remainingSeconds: "Quedan {s} segundos para que finalice" remainingSeconds: "Quedan {s} segundos para que finalice"
multiple: "Opciones múltiples"
_visibility: _visibility:
public: "Público" public: "Público"
publicDescription: "Visible para todos los usuarios" publicDescription: "Visible para todos los usuarios"
@ -2335,6 +2340,7 @@ _notification:
followRequestAccepted: "El seguimiento fue aceptado" followRequestAccepted: "El seguimiento fue aceptado"
roleAssigned: "Rol asignado" roleAssigned: "Rol asignado"
achievementEarned: "Logro desbloqueado" achievementEarned: "Logro desbloqueado"
login: "Iniciar sesión"
app: "Notificaciones desde aplicaciones" app: "Notificaciones desde aplicaciones"
_actions: _actions:
followBack: "Te sigue de vuelta" followBack: "Te sigue de vuelta"
@ -2397,6 +2403,8 @@ _abuseReport:
_notificationRecipient: _notificationRecipient:
_recipientType: _recipientType:
mail: "Correo" mail: "Correo"
webhook: "Webhook"
keywords: "Palabras Clave"
_moderationLogTypes: _moderationLogTypes:
createRole: "Rol creado" createRole: "Rol creado"
deleteRole: "Rol eliminado" deleteRole: "Rol eliminado"
@ -2500,6 +2508,7 @@ _hemisphere:
S: "Hemisferio sur" S: "Hemisferio sur"
_reversi: _reversi:
reversi: "Reversi" reversi: "Reversi"
rules: "Reglas"
won: "{name} ha ganado" won: "{name} ha ganado"
total: "Total" total: "Total"
_urlPreviewSetting: _urlPreviewSetting:

View file

@ -493,7 +493,6 @@ uiLanguage: "Langue daffichage de linterface"
aboutX: "À propos de {x}" aboutX: "À propos de {x}"
emojiStyle: "Style des émojis" emojiStyle: "Style des émojis"
native: "Natif" native: "Natif"
disableDrawer: "Les menus ne s'affichent pas dans le tiroir"
showNoteActionsOnlyHover: "Afficher les actions de note uniquement au survol" showNoteActionsOnlyHover: "Afficher les actions de note uniquement au survol"
showReactionsCount: "Afficher le nombre de réactions des notes" showReactionsCount: "Afficher le nombre de réactions des notes"
noHistory: "Pas d'historique" noHistory: "Pas d'historique"
@ -692,10 +691,7 @@ abuseReported: "Le rapport est envoyé. Merci."
reporter: "Signalé par" reporter: "Signalé par"
reporteeOrigin: "Origine du signalement" reporteeOrigin: "Origine du signalement"
reporterOrigin: "Signalé par" reporterOrigin: "Signalé par"
forwardReport: "Transférer le signalement à linstance distante"
forwardReportIsAnonymous: "L'instance distante ne sera pas en mesure de voir vos informations et apparaîtra comme un compte anonyme du système."
send: "Envoyer" send: "Envoyer"
abuseMarkAsResolved: "Marquer le signalement comme résolu"
openInNewTab: "Ouvrir dans un nouvel onglet" openInNewTab: "Ouvrir dans un nouvel onglet"
openInSideView: "Ouvrir en vue latérale" openInSideView: "Ouvrir en vue latérale"
defaultNavigationBehaviour: "Navigation par défaut" defaultNavigationBehaviour: "Navigation par défaut"
@ -1283,10 +1279,10 @@ _initialTutorial:
_reaction: _reaction:
title: "Qu'est-ce que les réactions ?" title: "Qu'est-ce que les réactions ?"
description: "Vous pouvez ajouter des « réactions » aux notes. Les réactions vous permettent d'exprimer à l'aise des nuances qui ne peuvent pas être exprimées par des mentions j'aime." description: "Vous pouvez ajouter des « réactions » aux notes. Les réactions vous permettent d'exprimer à l'aise des nuances qui ne peuvent pas être exprimées par des mentions j'aime."
letsTryReacting: "Des réactions peuvent être ajoutées en cliquant sur le bouton « {reaction} » de la note. Essayez d'ajouter une réaction à cet exemple de note !" letsTryReacting: "Des réactions peuvent être ajoutées en cliquant sur le bouton « + » de la note. Essayez d'ajouter une réaction à cet exemple de note !"
reactToContinue: "Ajoutez une réaction pour procéder." reactToContinue: "Ajoutez une réaction pour procéder."
reactNotification: "Vous recevez des notifications en temps réel lorsque quelqu'un réagit à votre note." reactNotification: "Vous recevez des notifications en temps réel lorsque quelqu'un réagit à votre note."
reactDone: "Vous pouvez annuler la réaction en cliquant sur le bouton « {undo} » ." reactDone: "Vous pouvez annuler la réaction en cliquant sur le bouton « - » ."
_timeline: _timeline:
title: "Fonctionnement des fils" title: "Fonctionnement des fils"
description1: "Misskey offre plusieurs fils selon l'usage (certains peuvent être désactivés par le serveur)." description1: "Misskey offre plusieurs fils selon l'usage (certains peuvent être désactivés par le serveur)."
@ -1350,7 +1346,7 @@ _achievements:
earnedAt: "Date d'obtention" earnedAt: "Date d'obtention"
_types: _types:
_notes1: _notes1:
title: "Je viens tout juste de configurer mon shonk" title: "Je viens tout juste de configurer mon msky"
description: "Publiez votre première note" description: "Publiez votre première note"
flavor: "Passez un bon moment avec Misskey !" flavor: "Passez un bon moment avec Misskey !"
_notes10: _notes10:
@ -2038,6 +2034,7 @@ _notification:
followRequestAccepted: "Demande d'abonnement acceptée" followRequestAccepted: "Demande d'abonnement acceptée"
roleAssigned: "Rôle reçu" roleAssigned: "Rôle reçu"
achievementEarned: "Déverrouillage d'accomplissement" achievementEarned: "Déverrouillage d'accomplissement"
login: "Se connecter"
app: "Notifications provenant des apps" app: "Notifications provenant des apps"
_actions: _actions:
followBack: "Suivre" followBack: "Suivre"

View file

@ -3,6 +3,7 @@ import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path'; import { dirname } from 'node:path';
import * as yaml from 'js-yaml'; import * as yaml from 'js-yaml';
import ts from 'typescript'; import ts from 'typescript';
import { merge } from './index.js';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);
@ -56,7 +57,10 @@ function createMembers(record) {
} }
export default function generateDTS() { export default function generateDTS() {
const locale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8')); const sharkeyLocale = yaml.load(fs.readFileSync(`${__dirname}/../sharkey-locales/en-US.yml`, 'utf-8'));
const misskeyLocale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8'));
const locale = merge(misskeyLocale, sharkeyLocale);
const members = createMembers(locale); const members = createMembers(locale);
const elements = [ const elements = [
ts.factory.createVariableStatement( ts.factory.createVariableStatement(

View file

@ -96,6 +96,7 @@ _notification:
renote: "Renote" renote: "Renote"
quote: "Idézet" quote: "Idézet"
reaction: "Reakciók" reaction: "Reakciók"
login: "Bejelentkezés"
_actions: _actions:
renote: "Renote" renote: "Renote"
_deck: _deck:

View file

@ -60,6 +60,7 @@ copyFileId: "Salin Berkas"
copyFolderId: "Salin Folder" copyFolderId: "Salin Folder"
copyProfileUrl: "Salin Alamat Web Profil" copyProfileUrl: "Salin Alamat Web Profil"
searchUser: "Cari pengguna" searchUser: "Cari pengguna"
searchThisUsersNotes: "Mencari catatan pengguna"
reply: "Balas" reply: "Balas"
loadMore: "Selebihnya" loadMore: "Selebihnya"
showMore: "Selebihnya" showMore: "Selebihnya"
@ -154,6 +155,7 @@ editList: "Sunting daftar"
selectChannel: "Pilih kanal" selectChannel: "Pilih kanal"
selectAntenna: "Pilih Antena" selectAntenna: "Pilih Antena"
editAntenna: "Sunting antena" editAntenna: "Sunting antena"
createAntenna: "Membuat antena."
selectWidget: "Pilih gawit" selectWidget: "Pilih gawit"
editWidgets: "Sunting gawit" editWidgets: "Sunting gawit"
editWidgetsExit: "Selesai" editWidgetsExit: "Selesai"
@ -502,7 +504,6 @@ uiLanguage: "Bahasa antarmuka pengguna"
aboutX: "Tentang {x}" aboutX: "Tentang {x}"
emojiStyle: "Gaya emoji" emojiStyle: "Gaya emoji"
native: "Native" native: "Native"
disableDrawer: "Jangan gunakan menu bergaya laci"
showNoteActionsOnlyHover: "Hanya tampilkan aksi catatan saat ditunjuk" showNoteActionsOnlyHover: "Hanya tampilkan aksi catatan saat ditunjuk"
showReactionsCount: "Lihat jumlah reaksi dalam catatan" showReactionsCount: "Lihat jumlah reaksi dalam catatan"
noHistory: "Tidak ada riwayat" noHistory: "Tidak ada riwayat"
@ -701,10 +702,7 @@ abuseReported: "Laporan kamu telah dikirimkan. Terima kasih."
reporter: "Pelapor" reporter: "Pelapor"
reporteeOrigin: "Yang dilaporkan" reporteeOrigin: "Yang dilaporkan"
reporterOrigin: "Pelapor" reporterOrigin: "Pelapor"
forwardReport: "Teruskan laporan ke instansi luar"
forwardReportIsAnonymous: "Untuk melindungi privasi akun kamu, akun anonim dari sistem akan digunakan sebagai pelapor pada instansi luar."
send: "Kirim" send: "Kirim"
abuseMarkAsResolved: "Tandai laporan sebagai selesai"
openInNewTab: "Buka di tab baru" openInNewTab: "Buka di tab baru"
openInSideView: "Buka di tampilan samping" openInSideView: "Buka di tampilan samping"
defaultNavigationBehaviour: "Navigasi bawaan" defaultNavigationBehaviour: "Navigasi bawaan"
@ -1155,7 +1153,7 @@ currentAnnouncements: "Pengumuman Saat Ini"
pastAnnouncements: "Pengumuman Terdahulu" pastAnnouncements: "Pengumuman Terdahulu"
youHaveUnreadAnnouncements: "Terdapat pengumuman yang belum dibaca" youHaveUnreadAnnouncements: "Terdapat pengumuman yang belum dibaca"
useSecurityKey: "Mohon ikuti instruksi peramban atau perangkat kamu untuk menggunakan kunci pengaman atau passkey." useSecurityKey: "Mohon ikuti instruksi peramban atau perangkat kamu untuk menggunakan kunci pengaman atau passkey."
replies: "Balasan" replies: "Balas"
renotes: "Renote" renotes: "Renote"
loadReplies: "Tampilkan balasan" loadReplies: "Tampilkan balasan"
loadConversation: "Tampilkan percakapan" loadConversation: "Tampilkan percakapan"
@ -1316,10 +1314,10 @@ _initialTutorial:
_reaction: _reaction:
title: "Apa itu Reaksi?" title: "Apa itu Reaksi?"
description: "Catatan dapat direaksi dengan berbagai emoji. Reaksi memperbolehkan kamu untuk mengekspresikan nuansa yang tidak dapat disampaikan hanya dengan sebuah \"suka\"." description: "Catatan dapat direaksi dengan berbagai emoji. Reaksi memperbolehkan kamu untuk mengekspresikan nuansa yang tidak dapat disampaikan hanya dengan sebuah \"suka\"."
letsTryReacting: "Reaksi dapat ditambahkan dengan mengklik tombol '{reaction}' pada catatan. Coba lakukan mereaksi contoh catatan ini!" letsTryReacting: "Reaksi dapat ditambahkan dengan mengklik tombol '+' pada catatan. Coba lakukan mereaksi contoh catatan ini!"
reactToContinue: "Tambahkan reaksi untuk melanjutkan." reactToContinue: "Tambahkan reaksi untuk melanjutkan."
reactNotification: "Kamu akan menerima notifikasi real0time ketika seseorang mereaksi catatan kamu." reactNotification: "Kamu akan menerima notifikasi real0time ketika seseorang mereaksi catatan kamu."
reactDone: "Kamu dapat mengurungkan reaksi dengan menekan tombol '{undo}'." reactDone: "Kamu dapat mengurungkan reaksi dengan menekan tombol '-'."
_timeline: _timeline:
title: "Konsep Lini Masa" title: "Konsep Lini Masa"
description1: "Misskey menyediakan berbagai lini masa sesuai dengan penggunaan (beberapa mungkin tidak tersedia karena bergantung dengan kebijakan peladen)." description1: "Misskey menyediakan berbagai lini masa sesuai dengan penggunaan (beberapa mungkin tidak tersedia karena bergantung dengan kebijakan peladen)."
@ -2353,6 +2351,7 @@ _notification:
followRequestAccepted: "Permintaan mengikuti disetujui" followRequestAccepted: "Permintaan mengikuti disetujui"
roleAssigned: "Peran Diberikan" roleAssigned: "Peran Diberikan"
achievementEarned: "Pencapaian didapatkan" achievementEarned: "Pencapaian didapatkan"
login: "Masuk"
app: "Notifikasi dari aplikasi tertaut" app: "Notifikasi dari aplikasi tertaut"
_actions: _actions:
followBack: "Ikuti Kembali" followBack: "Ikuti Kembali"

2103
locales/index.d.ts vendored

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,7 @@
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import * as yaml from 'js-yaml'; import * as yaml from 'js-yaml';
const merge = (...args) => args.reduce((a, c) => ({ export const merge = (...args) => args.reduce((a, c) => ({
...a, ...a,
...c, ...c,
...Object.entries(a) ...Object.entries(a)
@ -60,7 +60,10 @@ export function build() {
// https://github.com/vitest-dev/vitest/issues/3988#issuecomment-1686599577 // https://github.com/vitest-dev/vitest/issues/3988#issuecomment-1686599577
// https://github.com/misskey-dev/misskey/pull/14057#issuecomment-2192833785 // https://github.com/misskey-dev/misskey/pull/14057#issuecomment-2192833785
const metaUrl = import.meta.url; const metaUrl = import.meta.url;
const locales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(new URL(`${c}.yml`, metaUrl), 'utf-8'))) || {}, a), {}); const sharkeyLocales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(new URL(`../sharkey-locales/${c}.yml`, metaUrl), 'utf-8'))) || {}, a), {});
const misskeyLocales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(new URL(`${c}.yml`, metaUrl), 'utf-8'))) || {}, a), {});
// merge sharkey and misskey's locales. the second argument (sharkey) overwrites the first argument (misskey).
const locales = merge(misskeyLocales, sharkeyLocales);
// 空文字列が入ることがあり、フォールバックが動作しなくなるのでプロパティごと消す // 空文字列が入ることがあり、フォールバックが動作しなくなるのでプロパティごと消す
const removeEmpty = (obj) => { const removeEmpty = (obj) => {

View file

@ -1,6 +1,6 @@
--- ---
_lang_: "Italiano" _lang_: "Italiano"
headlineMisskey: "Rete collegata tramite note" headlineMisskey: "Rete collegata tramite Note"
introMisskey: "Eccoci! Misskey è un servizio di microblogging decentralizzato, libero e aperto. \n\n📡 Puoi pubblicare «Note» per condividere ciò che sta succedendo o per dire a tutti qualcosa su di te. \n\n👍 Puoi reagire inviando emoji rapidi alle «Note» provenienti da altri profili nel Fediverso.\n\n🚀 Esplora un nuovo mondo insieme a noi!" introMisskey: "Eccoci! Misskey è un servizio di microblogging decentralizzato, libero e aperto. \n\n📡 Puoi pubblicare «Note» per condividere ciò che sta succedendo o per dire a tutti qualcosa su di te. \n\n👍 Puoi reagire inviando emoji rapidi alle «Note» provenienti da altri profili nel Fediverso.\n\n🚀 Esplora un nuovo mondo insieme a noi!"
poweredByMisskeyDescription: "{name} è uno dei servizi (chiamati istanze) che utilizzano la piattaforma open source <b>Misskey</b>." poweredByMisskeyDescription: "{name} è uno dei servizi (chiamati istanze) che utilizzano la piattaforma open source <b>Misskey</b>."
monthAndDay: "{day}/{month}" monthAndDay: "{day}/{month}"
@ -8,6 +8,9 @@ search: "Cerca"
notifications: "Notifiche" notifications: "Notifiche"
username: "Nome utente" username: "Nome utente"
password: "Password" password: "Password"
initialPasswordForSetup: "Password iniziale, per avviare le impostazioni"
initialPasswordIsIncorrect: "Password iniziale, sbagliata."
initialPasswordForSetupDescription: "Se hai installato Misskey di persona, usa la password che hai indicato nel file di configurazione.\nSe stai utilizzando un servizio di hosting Misskey, usa la password fornita dal gestore.\nSe non hai una password preimpostata, lascia il campo vuoto e continua."
forgotPassword: "Hai dimenticato la password?" forgotPassword: "Hai dimenticato la password?"
fetchingAsApObject: "Recuperando dal Fediverso..." fetchingAsApObject: "Recuperando dal Fediverso..."
ok: "OK" ok: "OK"
@ -54,13 +57,13 @@ addToAntenna: "Aggiungi all'antenna"
sendMessage: "Invia messaggio" sendMessage: "Invia messaggio"
copyRSS: "Copia RSS" copyRSS: "Copia RSS"
copyUsername: "Copia nome utente" copyUsername: "Copia nome utente"
openRemoteProfile: "Apri profilo remoto"
copyUserId: "Copia ID del profilo" copyUserId: "Copia ID del profilo"
copyNoteId: "Copia ID della Nota" copyNoteId: "Copia ID della Nota"
copyFileId: "Copia ID del file" copyFileId: "Copia ID del file"
copyFolderId: "Copia ID della cartella" copyFolderId: "Copia ID della cartella"
copyProfileUrl: "Copia URL del profilo" copyProfileUrl: "Copia URL del profilo"
searchUser: "Cerca profilo" searchUser: "Cerca profilo"
searchThisUsersNotes: "Cerca le sue Note"
reply: "Rispondi" reply: "Rispondi"
loadMore: "Mostra di più" loadMore: "Mostra di più"
showMore: "Espandi" showMore: "Espandi"
@ -155,6 +158,7 @@ editList: "Modifica Lista"
selectChannel: "Seleziona canale" selectChannel: "Seleziona canale"
selectAntenna: "Scegli un'antenna" selectAntenna: "Scegli un'antenna"
editAntenna: "Modifica Antenna" editAntenna: "Modifica Antenna"
createAntenna: "Crea Antenna"
selectWidget: "Seleziona il riquadro" selectWidget: "Seleziona il riquadro"
editWidgets: "Modifica i riquadri" editWidgets: "Modifica i riquadri"
editWidgetsExit: "Conferma le modifiche" editWidgetsExit: "Conferma le modifiche"
@ -195,6 +199,7 @@ followConfirm: "Vuoi seguire {name}?"
proxyAccount: "Profilo proxy" proxyAccount: "Profilo proxy"
proxyAccountDescription: "Un profilo proxy funziona come follower per i profili remoti, sotto certe condizioni. Ad esempio, quando un profilo locale ne inserisce uno remoto in una lista (senza seguirlo), se nessun altro segue quel profilo remoto, le attività non possono essere distribuite. Dunque, il profilo proxy le seguirà per tutti." proxyAccountDescription: "Un profilo proxy funziona come follower per i profili remoti, sotto certe condizioni. Ad esempio, quando un profilo locale ne inserisce uno remoto in una lista (senza seguirlo), se nessun altro segue quel profilo remoto, le attività non possono essere distribuite. Dunque, il profilo proxy le seguirà per tutti."
host: "Host" host: "Host"
selectSelf: "Segli me"
selectUser: "Seleziona profilo" selectUser: "Seleziona profilo"
recipient: "Destinatario" recipient: "Destinatario"
annotation: "Annotazione preventiva" annotation: "Annotazione preventiva"
@ -210,6 +215,7 @@ perDay: "giornaliero"
stopActivityDelivery: "Interrompi la distribuzione di attività" stopActivityDelivery: "Interrompi la distribuzione di attività"
blockThisInstance: "Bloccare l'istanza" blockThisInstance: "Bloccare l'istanza"
silenceThisInstance: "Silenziare l'istanza" silenceThisInstance: "Silenziare l'istanza"
mediaSilenceThisInstance: "Silenzia i media dell'istanza"
operations: "Operazioni" operations: "Operazioni"
software: "Software" software: "Software"
version: "Versione" version: "Versione"
@ -231,6 +237,10 @@ blockedInstances: "Istanze bloccate"
blockedInstancesDescription: "Elenca le istanze che vuoi bloccare, una per riga. Esse non potranno più interagire con la tua istanza." blockedInstancesDescription: "Elenca le istanze che vuoi bloccare, una per riga. Esse non potranno più interagire con la tua istanza."
silencedInstances: "Istanze silenziate" silencedInstances: "Istanze silenziate"
silencedInstancesDescription: "Elenca i nomi host delle istanze che vuoi silenziare. Tutti i profili nelle istanze silenziate vengono trattati come tali. Possono solo inviare richieste di follow e menzionare soltanto i profili locali che seguono. Le istanze bloccate non sono interessate." silencedInstancesDescription: "Elenca i nomi host delle istanze che vuoi silenziare. Tutti i profili nelle istanze silenziate vengono trattati come tali. Possono solo inviare richieste di follow e menzionare soltanto i profili locali che seguono. Le istanze bloccate non sono interessate."
mediaSilencedInstances: "Istanze coi media silenziati"
mediaSilencedInstancesDescription: "Elenca i nomi host delle istanze di cui vuoi silenziare i media, uno per riga. Tutti gli allegati dei profili nelle istanze silenziate per via degli allegati espliciti, verranno impostati come tali, le emoji personalizzate non saranno disponibili. Le istanze bloccate sono escluse."
federationAllowedHosts: "Server a cui consentire la federazione"
federationAllowedHostsDescription: "Indica gli host dei server a cui è consentita la federazione, uno per ogni linea."
muteAndBlock: "Silenziare e bloccare" muteAndBlock: "Silenziare e bloccare"
mutedUsers: "Profili silenziati" mutedUsers: "Profili silenziati"
blockedUsers: "Profili bloccati" blockedUsers: "Profili bloccati"
@ -329,6 +339,7 @@ renameFolder: "Rinomina cartella"
deleteFolder: "Elimina cartella" deleteFolder: "Elimina cartella"
folder: "Cartella" folder: "Cartella"
addFile: "Allega" addFile: "Allega"
showFile: "Visualizza file"
emptyDrive: "Il Drive è vuoto" emptyDrive: "Il Drive è vuoto"
emptyFolder: "La cartella è vuota" emptyFolder: "La cartella è vuota"
unableToDelete: "Eliminazione impossibile" unableToDelete: "Eliminazione impossibile"
@ -450,7 +461,7 @@ securityKeyAndPasskey: "Chiave di sicurezza e accesso"
securityKey: "Chiave di sicurezza" securityKey: "Chiave di sicurezza"
lastUsed: "Ultima attività" lastUsed: "Ultima attività"
lastUsedAt: "Uso più recente: {t}" lastUsedAt: "Uso più recente: {t}"
unregister: "Annulla l'iscrizione" unregister: "Rimuovi autenticazione a due fattori (2FA/MFA)"
passwordLessLogin: "Accedi senza password" passwordLessLogin: "Accedi senza password"
passwordLessLoginDescription: "Accedi senza password, usando la chiave di sicurezza" passwordLessLoginDescription: "Accedi senza password, usando la chiave di sicurezza"
resetPassword: "Ripristina la password" resetPassword: "Ripristina la password"
@ -504,7 +515,10 @@ uiLanguage: "Lingua di visualizzazione dell'interfaccia"
aboutX: "Informazioni su {x}" aboutX: "Informazioni su {x}"
emojiStyle: "Stile emoji" emojiStyle: "Stile emoji"
native: "Nativo" native: "Nativo"
disableDrawer: "Non mostrare il menù sul drawer" menuStyle: "Stile menu"
style: "Stile"
drawer: "Drawer"
popup: "Popup"
showNoteActionsOnlyHover: "Mostra le azioni delle Note solo al passaggio del mouse" showNoteActionsOnlyHover: "Mostra le azioni delle Note solo al passaggio del mouse"
showReactionsCount: "Visualizza il numero di reazioni su una nota" showReactionsCount: "Visualizza il numero di reazioni su una nota"
noHistory: "Nessuna cronologia" noHistory: "Nessuna cronologia"
@ -560,7 +574,7 @@ deleteAll: "Cancella cronologia"
showFixedPostForm: "Visualizzare la finestra di pubblicazione in cima alla timeline" showFixedPostForm: "Visualizzare la finestra di pubblicazione in cima alla timeline"
showFixedPostFormInChannel: "Per i canali, mostra il modulo di pubblicazione in cima alla timeline" showFixedPostFormInChannel: "Per i canali, mostra il modulo di pubblicazione in cima alla timeline"
withRepliesByDefaultForNewlyFollowed: "Quando segui nuovi profili, includi le risposte in TL come impostazione predefinita" withRepliesByDefaultForNewlyFollowed: "Quando segui nuovi profili, includi le risposte in TL come impostazione predefinita"
newNoteRecived: "Nuove note da leggere" newNoteRecived: "Nuove Note da leggere"
sounds: "Impostazioni suoni" sounds: "Impostazioni suoni"
sound: "Suono" sound: "Suono"
listen: "Ascolta" listen: "Ascolta"
@ -587,6 +601,8 @@ ascendingOrder: "Aumenta"
descendingOrder: "Diminuisce" descendingOrder: "Diminuisce"
scratchpad: "ScratchPad" scratchpad: "ScratchPad"
scratchpadDescription: "Lo Scratchpad offre un ambiente per esperimenti di AiScript. È possibile scrivere, eseguire e confermare i risultati dell'interazione del codice con Misskey." scratchpadDescription: "Lo Scratchpad offre un ambiente per esperimenti di AiScript. È possibile scrivere, eseguire e confermare i risultati dell'interazione del codice con Misskey."
uiInspector: "UI Inspector"
uiInspectorDescription: "Puoi visualizzare un elenco di elementi UI presenti in memoria. I componenti dell'interfaccia utente vengono generati dalle funzioni Ui:C:."
output: "Uscita" output: "Uscita"
script: "Script" script: "Script"
disablePagesScript: "Disabilita AiScript nelle pagine" disablePagesScript: "Disabilita AiScript nelle pagine"
@ -703,10 +719,7 @@ abuseReported: "La segnalazione è stata inviata. Grazie."
reporter: "il corrispondente" reporter: "il corrispondente"
reporteeOrigin: "Segnalazione a" reporteeOrigin: "Segnalazione a"
reporterOrigin: "Segnalazione da" reporterOrigin: "Segnalazione da"
forwardReport: "Inoltro di un report a un'istanza remota."
forwardReportIsAnonymous: "L'istanza remota non vedrà le tue informazioni, apparirai come profilo di sistema, anonimo."
send: "Inviare" send: "Inviare"
abuseMarkAsResolved: "Risolvi segnalazione"
openInNewTab: "Apri in una nuova scheda" openInNewTab: "Apri in una nuova scheda"
openInSideView: "Apri in vista laterale" openInSideView: "Apri in vista laterale"
defaultNavigationBehaviour: "Navigazione preimpostata" defaultNavigationBehaviour: "Navigazione preimpostata"
@ -765,8 +778,6 @@ thisIsExperimentalFeature: "Questa è una funzionalità sperimentale. Potrebbe e
developer: "Sviluppatore" developer: "Sviluppatore"
makeExplorable: "Profilo visibile pubblicamente nella pagina \"Esplora\"" makeExplorable: "Profilo visibile pubblicamente nella pagina \"Esplora\""
makeExplorableDescription: "Disabilitando questa opzione, il tuo profilo non verrà elencato nella pagina \"Esplora\"." makeExplorableDescription: "Disabilitando questa opzione, il tuo profilo non verrà elencato nella pagina \"Esplora\"."
makeIndexable: "Non indicizzare le note pubbliche"
makeIndexableDescription: "Le tue note pubbliche non saranno cercabili"
showGapBetweenNotesInTimeline: "Mostrare un intervallo tra le note sulla timeline" showGapBetweenNotesInTimeline: "Mostrare un intervallo tra le note sulla timeline"
duplicate: "Duplica" duplicate: "Duplica"
left: "Sinistra" left: "Sinistra"
@ -910,6 +921,7 @@ followersVisibility: "Visibilità dei profili che ti seguono"
continueThread: "Altre conversazioni" continueThread: "Altre conversazioni"
deleteAccountConfirm: "Così verrà eliminato il profilo. Vuoi procedere?" deleteAccountConfirm: "Così verrà eliminato il profilo. Vuoi procedere?"
incorrectPassword: "La password è errata." incorrectPassword: "La password è errata."
incorrectTotp: "Il codice OTP è sbagliato, oppure scaduto."
voteConfirm: "Votare per「{choice}」?" voteConfirm: "Votare per「{choice}」?"
hide: "Nascondere" hide: "Nascondere"
useDrawerReactionPickerForMobile: "Mostra sul drawer da dispositivo mobile" useDrawerReactionPickerForMobile: "Mostra sul drawer da dispositivo mobile"
@ -1001,7 +1013,6 @@ cannotLoad: "Caricamento impossibile"
numberOfProfileView: "Visualizzazioni profilo" numberOfProfileView: "Visualizzazioni profilo"
like: "Mi piace!" like: "Mi piace!"
unlike: "Non mi piace" unlike: "Non mi piace"
defaultLike: "Emoji predefinita per \"mi piace\""
numberOfLikes: "Numero di Like" numberOfLikes: "Numero di Like"
show: "Visualizza" show: "Visualizza"
neverShow: "Non mostrare più" neverShow: "Non mostrare più"
@ -1110,6 +1121,8 @@ preservedUsernames: "Nomi utente riservati"
preservedUsernamesDescription: "Elenca, uno per linea, i nomi utente che non possono essere registrati durante la creazione del profilo. La restrizione non si applica agli amministratori. Inoltre, i profili già registrati sono esenti." preservedUsernamesDescription: "Elenca, uno per linea, i nomi utente che non possono essere registrati durante la creazione del profilo. La restrizione non si applica agli amministratori. Inoltre, i profili già registrati sono esenti."
createNoteFromTheFile: "Crea Nota da questo file" createNoteFromTheFile: "Crea Nota da questo file"
archive: "Archivio" archive: "Archivio"
archived: "Archiviato"
unarchive: "Annulla archiviazione"
channelArchiveConfirmTitle: "Vuoi davvero archiviare {name}?" channelArchiveConfirmTitle: "Vuoi davvero archiviare {name}?"
channelArchiveConfirmDescription: "Un canale archiviato non compare nell'elenco canali, nemmeno nei risultati di ricerca. Non può ricevere nemmeno nuove Note." channelArchiveConfirmDescription: "Un canale archiviato non compare nell'elenco canali, nemmeno nei risultati di ricerca. Non può ricevere nemmeno nuove Note."
thisChannelArchived: "Questo canale è stato archiviato." thisChannelArchived: "Questo canale è stato archiviato."
@ -1120,6 +1133,9 @@ preventAiLearning: "Impedisci l'apprendimento della IA"
preventAiLearningDescription: "Aggiungendo il campo \"noai\" alla risposta HTML, si indica ai Robot esterni di non usare testi e allegati per addestrare sistemi di Machine Learning (IA predittiva/generativa). Anche se è impossibile sapere se la richiesta venga onorata o semplicemente ignorata." preventAiLearningDescription: "Aggiungendo il campo \"noai\" alla risposta HTML, si indica ai Robot esterni di non usare testi e allegati per addestrare sistemi di Machine Learning (IA predittiva/generativa). Anche se è impossibile sapere se la richiesta venga onorata o semplicemente ignorata."
options: "Opzioni del ruolo" options: "Opzioni del ruolo"
specifyUser: "Profilo specifico" specifyUser: "Profilo specifico"
lookupConfirm: "Vuoi davvero richiedere informazioni?"
openTagPageConfirm: "Vuoi davvero aprire la pagina dell'hashtag?"
specifyHost: "Specifica l'host"
failedToPreviewUrl: "Anteprima non disponibile" failedToPreviewUrl: "Anteprima non disponibile"
update: "Aggiorna" update: "Aggiorna"
rolesThatCanBeUsedThisEmojiAsReaction: "Ruoli che possono usare questa emoji come reazione" rolesThatCanBeUsedThisEmojiAsReaction: "Ruoli che possono usare questa emoji come reazione"
@ -1254,10 +1270,25 @@ inquiry: "Contattaci"
tryAgain: "Per favore riprova" tryAgain: "Per favore riprova"
confirmWhenRevealingSensitiveMedia: "Richiedi conferma prima di mostrare gli allegati espliciti" confirmWhenRevealingSensitiveMedia: "Richiedi conferma prima di mostrare gli allegati espliciti"
sensitiveMediaRevealConfirm: "Questo allegato è esplicito, vuoi vederlo?" sensitiveMediaRevealConfirm: "Questo allegato è esplicito, vuoi vederlo?"
createdLists: "Liste create"
createdAntennas: "Antenne create"
fromX: "Da {x}"
genEmbedCode: "Ottieni il codice di incorporamento"
noteOfThisUser: "Elenco di Note di questo profilo"
clipNoteLimitExceeded: "Non è possibile aggiungere ulteriori Note a questa Clip."
performance: "Prestazioni"
modified: "Modificato"
discard: "Scarta"
thereAreNChanges: "Ci sono {n} cambiamenti"
signinWithPasskey: "Accedi con passkey"
unknownWebAuthnKey: "Questa è una passkey sconosciuta."
passkeyVerificationFailed: "La verifica della passkey non è riuscita."
passkeyVerificationSucceededButPasswordlessLoginDisabled: "La verifica della passkey è riuscita, ma l'accesso senza password è disabilitato."
messageToFollower: "Messaggio ai follower"
_delivery: _delivery:
status: "Stato della distribuzione di attività" status: "Stato della consegna"
stop: "Sospendi la distribuzione di attività" stop: "Sospensione"
resume: "Riprendi la distribuzione di attività" resume: "Riprendi la consegna"
_type: _type:
none: "Pubblicazione" none: "Pubblicazione"
manuallySuspended: "Sospesa manualmente" manuallySuspended: "Sospesa manualmente"
@ -1308,7 +1339,7 @@ _initialAccountSetting:
skipAreYouSure: "Vuoi davvero saltare la configurazione iniziale?" skipAreYouSure: "Vuoi davvero saltare la configurazione iniziale?"
laterAreYouSure: "Vuoi davvero rimandare la configurazione iniziale?" laterAreYouSure: "Vuoi davvero rimandare la configurazione iniziale?"
_initialTutorial: _initialTutorial:
launchTutorial: "Guarda il tutorial" launchTutorial: "Inizia il tutorial"
title: "Tutorial" title: "Tutorial"
wellDone: "Ottimo lavoro!" wellDone: "Ottimo lavoro!"
skipAreYouSure: "Vuoi davvero interrompere il tutorial?" skipAreYouSure: "Vuoi davvero interrompere il tutorial?"
@ -1318,17 +1349,17 @@ _initialTutorial:
_note: _note:
title: "Cosa sono le Note?" title: "Cosa sono le Note?"
description: "Gli status su Misskey sono chiamati \"Note\". Le Note sono elencate in ordine cronologico nelle timeline e vengono aggiornate in tempo reale." description: "Gli status su Misskey sono chiamati \"Note\". Le Note sono elencate in ordine cronologico nelle timeline e vengono aggiornate in tempo reale."
reply: "Puoi rispondere alle Note. Puoi anche rispondere alle risposte e continuare i dialoghi come un conversazioni." reply: "Puoi rispondere alle Note, alle altre risposte e dialogare in conversazioni."
renote: "Puoi ri-condividere le Note, facendole rifluire sulla Timeline. Puoi anche aggiungere testo e citare altri profili." renote: "Puoi ri-condividere le Note, ritorneranno sulla Timeline. Aggiungendo del testo, scriverai una Citazione."
reaction: "Puoi aggiungere una reazione. Nella pagina successiva spiegheremo i dettagli." reaction: "Puoi aggiungere una reazione. Nella pagina successiva ti spiego come."
menu: "Puoi svolgere varie attività, come visualizzare i dettagli delle Note o copiare i collegamenti." menu: "Per altre attività, ad esempio, vedere i dettagli delle Note o copiare i collegamenti."
_reaction: _reaction:
title: "Cosa sono le Reazioni?" title: "Cosa sono le Reazioni?"
description: "Puoi reagire alle Note. Le sensazioni che non si riescono a trasmettere con i \"Mi piace\" si possono esprimere facilmente inviando una reazione." description: "Reazioni alle Note. Le sensazioni che non si possono descrivere con \"Mi piace\" si esprimono facilmente con le reazioni."
letsTryReacting: "Puoi aggiungere una Reazione cliccando il bottone \"{reaction}\" della relativa Nota. Prova ad aggiungerne una a questa Nota di esempio!" letsTryReacting: "Puoi aggiungere una Reazione cliccando il bottone \"+\" (più) della relativa Nota. Prova ad aggiungerne una a questa Nota di esempio!"
reactToContinue: "Aggiungere la Reazione ti consentirà di procedere col tutorial." reactToContinue: "Aggiungere la Reazione ti consentirà di procedere col tutorial."
reactNotification: "Quando qualcuno reagisce alle tue Note, ricevi una notifica in tempo reale." reactNotification: "Quando qualcuno reagisce alle tue Note, ricevi una notifica in tempo reale."
reactDone: "Annulla la tua Reazione premendo il bottone \"{undo}\"" reactDone: "Annulla la tua Reazione premendo il bottone \"ー\" (meno)"
_timeline: _timeline:
title: "Come funziona la Timeline" title: "Come funziona la Timeline"
description1: "Misskey fornisce alcune Timeline (sequenze cronologiche di Note). Una di queste potrebbe essere stata disattivata dagli amministratori." description1: "Misskey fornisce alcune Timeline (sequenze cronologiche di Note). Una di queste potrebbe essere stata disattivata dagli amministratori."
@ -1337,7 +1368,7 @@ _initialTutorial:
social: "sia le Note della Timeline Home che quelle della Timeline Locale, insieme!" social: "sia le Note della Timeline Home che quelle della Timeline Locale, insieme!"
global: "le Note da pubblicate da tutte le altre istanze federate con la nostra." global: "le Note da pubblicate da tutte le altre istanze federate con la nostra."
description2: "Nella parte superiore dello schermo, puoi scegliere una Timeline o l'altra in qualsiasi momento." description2: "Nella parte superiore dello schermo, puoi scegliere una Timeline o l'altra in qualsiasi momento."
description3: "Ci sono anche sequenze temporali di elenchi, sequenze temporali di canali, ecc. Per ulteriori dettagli, consultare il {link}.\nPuoi vedere anche Timeline delle liste di profili (se ne hai create), canali, ecc... Per i dettagli, visita {link}." description3: "Ci sono anche sequenze temporali di elenchi, sequenze temporali di canali, ecc. Per ulteriori dettagli, consultare la {link}.\nPuoi vedere anche Timeline delle liste di profili (se ne hai create), canali, ecc... Per i dettagli, c'è la {link}."
_postNote: _postNote:
title: "La Nota e le sue impostazioni" title: "La Nota e le sue impostazioni"
description1: "Quando scrivi una Nota su Misskey, hai a disposizione varie opzioni. Il modulo di invio è simile a questo." description1: "Quando scrivi una Nota su Misskey, hai a disposizione varie opzioni. Il modulo di invio è simile a questo."
@ -1388,6 +1419,7 @@ _serverSettings:
fanoutTimelineDescription: "Attivando questa funzionalità migliori notevolmente la capacità delle Timeline di collezionare Note, riducendo il carico sul database. Tuttavia, aumenterà l'impiego di memoria RAM per Redis. Disattiva se il tuo server ha poca RAM o la funzionalità è irregolare." fanoutTimelineDescription: "Attivando questa funzionalità migliori notevolmente la capacità delle Timeline di collezionare Note, riducendo il carico sul database. Tuttavia, aumenterà l'impiego di memoria RAM per Redis. Disattiva se il tuo server ha poca RAM o la funzionalità è irregolare."
fanoutTimelineDbFallback: "Elaborazione dati alternativa" 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." 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."
inquiryUrl: "URL di contatto" inquiryUrl: "URL di contatto"
inquiryUrlDescription: "Specificare l'URL al modulo di contatto, oppure le informazioni con i dati di contatto dell'amministrazione." inquiryUrlDescription: "Specificare l'URL al modulo di contatto, oppure le informazioni con i dati di contatto dell'amministrazione."
_accountMigration: _accountMigration:
@ -1721,6 +1753,11 @@ _role:
canSearchNotes: "Ricercare nelle Note" canSearchNotes: "Ricercare nelle Note"
canUseTranslator: "Tradurre le Note" canUseTranslator: "Tradurre le Note"
avatarDecorationLimit: "Numero massimo di decorazioni foto profilo installabili" avatarDecorationLimit: "Numero massimo di decorazioni foto profilo installabili"
canImportAntennas: "Può importare Antenne"
canImportBlocking: "Può importare Blocchi"
canImportFollowing: "Può importare Following"
canImportMuting: "Può importare Silenziati"
canImportUserLists: "Può importare liste di Profili"
_condition: _condition:
roleAssignedTo: "Assegnato a ruoli manualmente" roleAssignedTo: "Assegnato a ruoli manualmente"
isLocal: "Profilo locale" isLocal: "Profilo locale"
@ -1848,7 +1885,6 @@ _serverDisconnectedBehavior:
reload: "Ricarica automaticamente" reload: "Ricarica automaticamente"
dialog: "Apri avviso in finestra" dialog: "Apri avviso in finestra"
quiet: "Visualizza avviso in modo discreto" quiet: "Visualizza avviso in modo discreto"
disabled: "Non visualizzare l'avviso"
_channel: _channel:
create: "Nuovo canale" create: "Nuovo canale"
edit: "Gerisci canale" edit: "Gerisci canale"
@ -1959,6 +1995,7 @@ _soundSettings:
driveFileTypeWarnDescription: "Per favore, scegli un file di tipo audio" driveFileTypeWarnDescription: "Per favore, scegli un file di tipo audio"
driveFileDurationWarn: "La durata dell'audio è troppo lunga" driveFileDurationWarn: "La durata dell'audio è troppo lunga"
driveFileDurationWarnDescription: "Scegliere un audio lungo potrebbe interferire con l'uso di Misskey. Vuoi continuare lo stesso?" driveFileDurationWarnDescription: "Scegliere un audio lungo potrebbe interferire con l'uso di Misskey. Vuoi continuare lo stesso?"
driveFileError: "Impossibile caricare l'audio. Si prega di modificare le impostazioni"
_ago: _ago:
future: "Futuro" future: "Futuro"
justNow: "Adesso" justNow: "Adesso"
@ -2215,6 +2252,9 @@ _profile:
changeBanner: "Cambia intestazione" changeBanner: "Cambia intestazione"
verifiedLinkDescription: "Puoi verificare il tuo profilo mostrando una icona. Devi inserire la URL alla pagina che contiene un link al tuo profilo." verifiedLinkDescription: "Puoi verificare il tuo profilo mostrando una icona. Devi inserire la URL alla pagina che contiene un link al tuo profilo."
avatarDecorationMax: "Puoi aggiungere fino a {max} decorazioni." avatarDecorationMax: "Puoi aggiungere fino a {max} decorazioni."
followedMessage: "Messaggio, quando qualcuno ti segue"
followedMessageDescription: "Puoi impostare un breve messaggio da mostrare agli altri profili quando ti seguono."
followedMessageDescriptionForLockedAccount: "Quando approvi una richiesta di follow, verrà visualizzato questo testo."
_exportOrImport: _exportOrImport:
allNotes: "Tutte le note" allNotes: "Tutte le note"
favoritedNotes: "Note preferite" favoritedNotes: "Note preferite"
@ -2307,6 +2347,7 @@ _pages:
eyeCatchingImageSet: "Imposta un'immagine attraente" eyeCatchingImageSet: "Imposta un'immagine attraente"
eyeCatchingImageRemove: "Elimina immagine attraente" eyeCatchingImageRemove: "Elimina immagine attraente"
chooseBlock: "Aggiungi blocco" chooseBlock: "Aggiungi blocco"
enterSectionTitle: "Inserisci il titolo della sezione"
selectType: "Seleziona tipo" selectType: "Seleziona tipo"
contentBlocks: "Contenuto" contentBlocks: "Contenuto"
inputBlocks: "Blocchi di input" inputBlocks: "Blocchi di input"
@ -2352,6 +2393,8 @@ _notification:
renotedBySomeUsers: "{n} Rinota" renotedBySomeUsers: "{n} Rinota"
followedBySomeUsers: "{n} follower" followedBySomeUsers: "{n} follower"
flushNotification: "Azzera le notifiche" flushNotification: "Azzera le notifiche"
exportOfXCompleted: "Abbiamo completato l'esportazione di {x}"
login: "Autenticazione avvenuta"
_types: _types:
all: "Tutto" all: "Tutto"
note: "Nuove Note" note: "Nuove Note"
@ -2366,6 +2409,9 @@ _notification:
followRequestAccepted: "Richiesta di follow accettata" followRequestAccepted: "Richiesta di follow accettata"
roleAssigned: "Ruolo concesso" roleAssigned: "Ruolo concesso"
achievementEarned: "Risultato raggiunto" achievementEarned: "Risultato raggiunto"
exportCompleted: "Esportazione completata"
login: "Accedi"
test: "Prova la notifica"
app: "Notifiche da applicazioni" app: "Notifiche da applicazioni"
_actions: _actions:
followBack: "Segui" followBack: "Segui"
@ -2417,6 +2463,7 @@ _webhookSettings:
modifyWebhook: "Modifica Webhook" modifyWebhook: "Modifica Webhook"
name: "Nome" name: "Nome"
secret: "Segreto" secret: "Segreto"
trigger: "Trigger"
active: "Attivo" active: "Attivo"
_events: _events:
follow: "Quando segui un profilo" follow: "Quando segui un profilo"
@ -2429,7 +2476,9 @@ _webhookSettings:
_systemEvents: _systemEvents:
abuseReport: "Quando arriva una segnalazione" abuseReport: "Quando arriva una segnalazione"
abuseReportResolved: "Quando una segnalazione è risolta" abuseReportResolved: "Quando una segnalazione è risolta"
userCreated: "Quando viene creato un profilo"
deleteConfirm: "Vuoi davvero eliminare il Webhook?" deleteConfirm: "Vuoi davvero eliminare il Webhook?"
testRemarks: "Clicca il bottone a destra dell'interruttore, per provare l'invio di un webhook con dati fittizi."
_abuseReport: _abuseReport:
_notificationRecipient: _notificationRecipient:
createRecipient: "Aggiungi destinatario della segnalazione" createRecipient: "Aggiungi destinatario della segnalazione"
@ -2488,6 +2537,10 @@ _moderationLogTypes:
createAbuseReportNotificationRecipient: "Crea destinatario per le notifiche di segnalazioni" createAbuseReportNotificationRecipient: "Crea destinatario per le notifiche di segnalazioni"
updateAbuseReportNotificationRecipient: "Aggiorna destinatario notifiche di segnalazioni" updateAbuseReportNotificationRecipient: "Aggiorna destinatario notifiche di segnalazioni"
deleteAbuseReportNotificationRecipient: "Elimina 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"
_fileViewer: _fileViewer:
title: "Dettagli del file" title: "Dettagli del file"
type: "Tipo di file" type: "Tipo di file"
@ -2619,3 +2672,22 @@ _mediaControls:
pip: "Sovraimpressione" pip: "Sovraimpressione"
playbackRate: "Velocità di riproduzione" playbackRate: "Velocità di riproduzione"
loop: "Ripetizione infinita" loop: "Ripetizione infinita"
_contextMenu:
title: "Menu contestuale"
app: "Applicazione"
appWithShift: "Applicazione Shift+Tasto"
native: "Interfaccia utente del browser"
_embedCodeGen:
title: "Personalizza il codice di incorporamento"
header: "Mostra la testata"
autoload: "Carica automaticamente di più (sconsigliato)"
maxHeight: "Altezza massima"
maxHeightDescription: "Specifica un valore per evitare che continui a crescere verticalmente. Il valore 0 disabilita il limite d'altezza."
maxHeightWarn: "L'altezza massima è disabilitata (0). Se l'effetto è indesiderato, prova a impostare l'altezza massima a un valore specifico."
previewIsNotActual: "Poiché supera l'intervallo che può essere visualizzato in anteprima, la visualizzazione vera e propria sarà diversa quando effettivamente incorporata."
rounded: "Bordo arrotondato"
border: "Aggiungi un bordo al contenitore"
applyToPreview: "Applica all'anteprima"
generateCode: "Crea il codice di incorporamento"
codeGenerated: "Codice generato"
codeGeneratedDescription: "Incolla il codice appena generato sul tuo sito web."

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
--- ---
_lang_: "日本語 (関西弁)" _lang_: "日本語 (関西弁)"
headlineMisskey: "ノートでつながるネットワーク" headlineMisskey: "ノートでつながるネットワーク"
introMisskey: "ようお越し!Sharkeyは、オープンソースの分散型マイクロブログサービスやねん。\n「ート」を作って、いま起こっとることを共有したり、あんたについて皆に発信しよう📡\n「ツッコミ」機能で、皆のートに素早く反応を追加したりもできるで✌\nほな、新しい世界を探検しよか🚀" introMisskey: "ようお越し!Misskeyは、オープンソースの分散型マイクロブログサービスやねん。\n「ート」を作って、いま起こっとることを共有したり、あんたについて皆に発信しよう📡\n「ツッコミ」機能で、皆のートに素早く反応を追加したりもできるで✌\nほな、新しい世界を探検しよか🚀"
poweredByMisskeyDescription: "{name}は、オープンソースのプラットフォーム<b>Sharkey</b>のサーバーのひとつなんやで。" poweredByMisskeyDescription: "{name}は、オープンソースのプラットフォーム<b>Misskey</b>のサーバーのひとつなんやで。"
monthAndDay: "{month}月 {day}日" monthAndDay: "{month}月 {day}日"
search: "探す" search: "探す"
notifications: "通知" notifications: "通知"
@ -15,7 +15,7 @@ gotIt: "ほい"
cancel: "やめとく" cancel: "やめとく"
noThankYou: "やめとく" noThankYou: "やめとく"
enterUsername: "ユーザー名を入れてや" enterUsername: "ユーザー名を入れてや"
renotedBy: "{user}がブーストしたで" renotedBy: "{user}がリノートしたで"
noNotes: "ノートはあらへん" noNotes: "ノートはあらへん"
noNotifications: "通知はあらへん" noNotifications: "通知はあらへん"
instance: "サーバー" instance: "サーバー"
@ -45,10 +45,10 @@ pin: "ピン留めしとく"
unpin: "やっぱピン留めせん" unpin: "やっぱピン留めせん"
copyContent: "内容をコピー" copyContent: "内容をコピー"
copyLink: "リンクをコピー" copyLink: "リンクをコピー"
copyLinkRenote: "ブーストのリンクをコピーするで?" copyLinkRenote: "リノートのリンクをコピーするで?"
delete: "ほかす" delete: "ほかす"
deleteAndEdit: "ほかして直す" deleteAndEdit: "ほかして直す"
deleteAndEditConfirm: "このノートをほかしてもっかい直す?このノートへのツッコミ、ブースト、返信も全部消えるんやけどそれでもええん?" deleteAndEditConfirm: "このノートをほかしてもっかい直す?このノートへのツッコミ、リノート、返信も全部消えるんやけどそれでもええん?"
addToList: "リストに入れたる" addToList: "リストに入れたる"
addToAntenna: "アンテナに入れる" addToAntenna: "アンテナに入れる"
sendMessage: "メッセージを送る" sendMessage: "メッセージを送る"
@ -113,7 +113,7 @@ renotedToX: "{name}にリノートしたで"
cantRenote: "この投稿はリノートできへんっぽい。" cantRenote: "この投稿はリノートできへんっぽい。"
cantReRenote: "リノート自体はリノートできへんで。" cantReRenote: "リノート自体はリノートできへんで。"
quote: "引用" quote: "引用"
inChannelRenote: "チャンネルの中でブースト" inChannelRenote: "チャンネルの中でリノート"
inChannelQuote: "チャンネル内引用" inChannelQuote: "チャンネル内引用"
renoteToChannel: "チャンネルにリノート" renoteToChannel: "チャンネルにリノート"
renoteToOtherChannel: "他のチャンネルにリノート" renoteToOtherChannel: "他のチャンネルにリノート"
@ -140,8 +140,8 @@ unmarkAsSensitive: "そこまでアカンことないやろ"
enterFileName: "ファイル名を入れてや" enterFileName: "ファイル名を入れてや"
mute: "ミュート" mute: "ミュート"
unmute: "ミュートやめたる" unmute: "ミュートやめたる"
renoteMute: "ブーストは見いひん" renoteMute: "リノートは見いひん"
renoteUnmute: "ブーストもやっぱ見るわ" renoteUnmute: "リノートもやっぱ見るわ"
block: "ブロック" block: "ブロック"
unblock: "ブロックやめたる" unblock: "ブロックやめたる"
suspend: "凍結" suspend: "凍結"
@ -172,11 +172,9 @@ youCanCleanRemoteFilesCache: "ファイル管理にある🗑️ボタンでキ
cacheRemoteSensitiveFiles: "リモートのきわどいファイルをキャッシュに突っ込む" cacheRemoteSensitiveFiles: "リモートのきわどいファイルをキャッシュに突っ込む"
cacheRemoteSensitiveFilesDescription: "この設定を切ると、リモートのきわどいファイルはキャッシュせず直でリンクするようになるで。" cacheRemoteSensitiveFilesDescription: "この設定を切ると、リモートのきわどいファイルはキャッシュせず直でリンクするようになるで。"
flagAsBot: "Botにするで" flagAsBot: "Botにするで"
flagAsBotDescription: "もしこのアカウントをプログラム使うて運用するんやったら、このフラグをオンにしてや。オンにすれば、反応がバーッて連鎖せんように開発者が使うたり、Sharkeyのシステム上での扱いがBotに合ったもんになるからな。" flagAsBotDescription: "もしこのアカウントをプログラム使うて運用するんやったら、このフラグをオンにしてや。オンにすれば、反応がバーッて連鎖せんように開発者が使うたり、Misskeyのシステム上での扱いがBotに合ったもんになるからな。"
flagAsCat: "猫や。かわええな。" flagAsCat: "猫や。かわええな。"
flagAsCatDescription: "ネコになりたいんならこれつけとき。" flagAsCatDescription: "ネコになりたいんならこれつけとき。"
flagSpeakAsCat: "猫語で話すで"
flagSpeakAsCatDescription: "有効にすると、あなたの投稿の 「な」を「にゃ」にするでー。"
flagShowTimelineReplies: "タイムラインにノートへの返信を表示するで" flagShowTimelineReplies: "タイムラインにノートへの返信を表示するで"
flagShowTimelineRepliesDescription: "オンにしたら、タイムラインにユーザーのノートの他にもそのユーザーの他のノートへの返信を表示するで。" flagShowTimelineRepliesDescription: "オンにしたら、タイムラインにユーザーのノートの他にもそのユーザーの他のノートへの返信を表示するで。"
autoAcceptFollowed: "フォローしとるユーザーからのフォローリクエストを勝手に許可しとく" autoAcceptFollowed: "フォローしとるユーザーからのフォローリクエストを勝手に許可しとく"
@ -245,7 +243,7 @@ noUsers: "ユーザーはおらん"
editProfile: "プロフィールをいじる" editProfile: "プロフィールをいじる"
noteDeleteConfirm: "このノートをほかしてええか?" noteDeleteConfirm: "このノートをほかしてええか?"
pinLimitExceeded: "これ以上ピン留めできひん" pinLimitExceeded: "これ以上ピン留めできひん"
intro: "Sharkeyのインストールが完了したで管理者アカウントを作ってや。" intro: "Misskeyのインストールが完了したで管理者アカウントを作ってや。"
done: "でけた" done: "でけた"
processing: "処理しとる" processing: "処理しとる"
preview: "プレビュー" preview: "プレビュー"
@ -440,7 +438,7 @@ exploreFediverse: "Fediverseを探ってみる"
popularTags: "人気のタグ" popularTags: "人気のタグ"
userList: "リスト" userList: "リスト"
about: "情報" about: "情報"
aboutMisskey: "Sharkeyってなんや" aboutMisskey: "Misskeyってなんや"
administrator: "管理者" administrator: "管理者"
token: "確認コード" token: "確認コード"
2fa: "二要素認証" 2fa: "二要素認証"
@ -511,7 +509,6 @@ uiLanguage: "UIの表示言語"
aboutX: "{x}について" aboutX: "{x}について"
emojiStyle: "絵文字のスタイル" emojiStyle: "絵文字のスタイル"
native: "ネイティブ" native: "ネイティブ"
disableDrawer: "メニューをドロワーで表示せえへん"
showNoteActionsOnlyHover: "ノートの操作部をホバー時のみ表示するで" showNoteActionsOnlyHover: "ノートの操作部をホバー時のみ表示するで"
showReactionsCount: "ノートのリアクション数を表示する" showReactionsCount: "ノートのリアクション数を表示する"
noHistory: "履歴はないわ。" noHistory: "履歴はないわ。"
@ -577,7 +574,7 @@ popout: "ポップアウト"
volume: "やかましさ" volume: "やかましさ"
masterVolume: "全体のやかましさ" masterVolume: "全体のやかましさ"
notUseSound: "音出さへん" notUseSound: "音出さへん"
useSoundOnlyWhenActive: "Sharkeyがアクティブなときだけ音出す" useSoundOnlyWhenActive: "Misskeyがアクティブなときだけ音出す"
details: "もっと" details: "もっと"
chooseEmoji: "絵文字を選ぶ" chooseEmoji: "絵文字を選ぶ"
unableToProcess: "なんか奥の方で詰まってもうた" unableToProcess: "なんか奥の方で詰まってもうた"
@ -593,7 +590,7 @@ sort: "仕分ける"
ascendingOrder: "小さい順" ascendingOrder: "小さい順"
descendingOrder: "大きい順" descendingOrder: "大きい順"
scratchpad: "スクラッチパッド" scratchpad: "スクラッチパッド"
scratchpadDescription: "スクラッチパッドではAiScriptを色々試すことができるんや。Sharkeyに対して色々できるコードを書いて動かしてみたり、結果を見たりできるで。" scratchpadDescription: "スクラッチパッドではAiScriptを色々試すことができるんや。Misskeyに対して色々できるコードを書いて動かしてみたり、結果を見たりできるで。"
output: "出力" output: "出力"
script: "スクリプト" script: "スクリプト"
disablePagesScript: "Pagesのスクリプトを無効にしてや" disablePagesScript: "Pagesのスクリプトを無効にしてや"
@ -703,17 +700,14 @@ behavior: "動作"
sample: "サンプル" sample: "サンプル"
abuseReports: "通報" abuseReports: "通報"
reportAbuse: "通報" reportAbuse: "通報"
reportAbuseRenote: "ブースト苦情だすで?" reportAbuseRenote: "リノート苦情だすで?"
reportAbuseOf: "{name}を通報する" reportAbuseOf: "{name}を通報する"
fillAbuseReportDescription: "細かい通報理由を書いてなー。対象ートがある時はそのURLも書いといてなー。" fillAbuseReportDescription: "細かい通報理由を書いてなー。対象ートがある時はそのURLも書いといてなー。"
abuseReported: "無事内容が送信されたみたいやで。おおきに〜。" abuseReported: "無事内容が送信されたみたいやで。おおきに〜。"
reporter: "通報者" reporter: "通報者"
reporteeOrigin: "通報先" reporteeOrigin: "通報先"
reporterOrigin: "通報元" reporterOrigin: "通報元"
forwardReport: "リモートサーバーに通報を転送するで"
forwardReportIsAnonymous: "リモートサーバーからはあんたの情報は見えんなって、匿名のシステムアカウントとして表示されるで。"
send: "送信" send: "送信"
abuseMarkAsResolved: "対応したで"
openInNewTab: "新しいタブで開く" openInNewTab: "新しいタブで開く"
openInSideView: "サイドビューで開く" openInSideView: "サイドビューで開く"
defaultNavigationBehaviour: "デフォルトのナビゲーション" defaultNavigationBehaviour: "デフォルトのナビゲーション"
@ -732,14 +726,14 @@ unclip: "クリップやめとく"
confirmToUnclipAlreadyClippedNote: "このノートはもう「{name}」に含まれとるで。ノート、このクリップから外そか?" confirmToUnclipAlreadyClippedNote: "このノートはもう「{name}」に含まれとるで。ノート、このクリップから外そか?"
public: "パブリック" public: "パブリック"
private: "非公開" private: "非公開"
i18nInfo: "Sharkeyは有志がいろんな言語に訳しとるで。{link}で翻訳に協力したってやー。" i18nInfo: "Misskeyは有志がいろんな言語に訳しとるで。{link}で翻訳に協力したってやー。"
manageAccessTokens: "アクセストークンの管理" manageAccessTokens: "アクセストークンの管理"
accountInfo: "アカウント情報" accountInfo: "アカウント情報"
notesCount: "ノートの数やで" notesCount: "ノートの数やで"
repliesCount: "返信した数やで" repliesCount: "返信した数やで"
renotesCount: "ブーストした数やで" renotesCount: "リノートした数やで"
repliedCount: "返信された数やで" repliedCount: "返信された数やで"
renotedCount: "ブーストされた数やで" renotedCount: "リノートされた数やで"
followingCount: "フォロー数やで" followingCount: "フォロー数やで"
followersCount: "フォロワー数やで" followersCount: "フォロワー数やで"
sentReactionsCount: "ツッコんだ数" sentReactionsCount: "ツッコんだ数"
@ -786,7 +780,7 @@ onlineUsersCount: "{n}人が起きとるで"
nUsers: "{n}ユーザー" nUsers: "{n}ユーザー"
nNotes: "{n}ノート" nNotes: "{n}ノート"
sendErrorReports: "エラーリポートを送る" sendErrorReports: "エラーリポートを送る"
sendErrorReportsDescription: "オンにしたら、なんか変なことが起きたとき、詳しいのが全部Sharkeyに送られて、ソフトウェアをもっと良うするで。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴なんかが含まれるな。" sendErrorReportsDescription: "オンにしたら、なんか変なことが起きたとき、詳しいのが全部Misskeyに送られて、ソフトウェアをもっと良うするで。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴なんかが含まれるな。"
myTheme: "マイテーマ" myTheme: "マイテーマ"
backgroundColor: "背景" backgroundColor: "背景"
accentColor: "アクセント" accentColor: "アクセント"
@ -881,7 +875,7 @@ hashtags: "ハッシュタグ"
troubleshooting: "トラブルシューティング" troubleshooting: "トラブルシューティング"
useBlurEffect: "UIにぼかし効果を使うで" useBlurEffect: "UIにぼかし効果を使うで"
learnMore: "詳しく" learnMore: "詳しく"
misskeyUpdated: "Sharkeyが更新されたで\nモデレーターの人らに感謝せなあかんで" misskeyUpdated: "Misskeyが更新されたで\nモデレーターの人らに感謝せなあかんで"
whatIsNew: "更新情報を見るで" whatIsNew: "更新情報を見るで"
translate: "翻訳" translate: "翻訳"
translatedFrom: "{x}から翻訳するで" translatedFrom: "{x}から翻訳するで"
@ -1010,8 +1004,8 @@ numberOfLikes: "いいね数"
show: "表示" show: "表示"
neverShow: "今後表示しない" neverShow: "今後表示しない"
remindMeLater: "また後で" remindMeLater: "また後で"
didYouLikeMisskey: "Sharkey気に入ってくれた" didYouLikeMisskey: "Misskey気に入ってくれた"
pleaseDonate: "Sharkeyは{host}が使うとる無料のソフトウェアやで。これからも開発を続けれるように、寄付したってな~。" pleaseDonate: "Misskeyは{host}が使うとる無料のソフトウェアやで。これからも開発を続けれるように、寄付したってな~。"
correspondingSourceIsAvailable: "{anchor}" correspondingSourceIsAvailable: "{anchor}"
roles: "ロール" roles: "ロール"
role: "ロール" role: "ロール"
@ -1039,8 +1033,8 @@ thisPostMayBeAnnoying: "この投稿は迷惑かもしらんで。"
thisPostMayBeAnnoyingHome: "ホームに投稿" thisPostMayBeAnnoyingHome: "ホームに投稿"
thisPostMayBeAnnoyingCancel: "やめとく" thisPostMayBeAnnoyingCancel: "やめとく"
thisPostMayBeAnnoyingIgnore: "このまま投稿" thisPostMayBeAnnoyingIgnore: "このまま投稿"
collapseRenotes: "見たことあるブーストは飛ばして表示するで" collapseRenotes: "見たことあるリノートは飛ばして表示するで"
collapseRenotesDescription: "リアクションやブーストをしたことがあるノートをたたんで表示するで。" collapseRenotesDescription: "リアクションやリノートをしたことがあるノートをたたんで表示するで。"
internalServerError: "サーバー内部エラー" internalServerError: "サーバー内部エラー"
internalServerErrorDescription: "サーバーでなんか変なこと起こっとるわ。" internalServerErrorDescription: "サーバーでなんか変なこと起こっとるわ。"
copyErrorInfo: "エラー情報をコピるで" copyErrorInfo: "エラー情報をコピるで"
@ -1096,7 +1090,7 @@ forceShowAds: "いっつも広告を映す"
addMemo: "メモを足す" addMemo: "メモを足す"
editMemo: "メモをいらう" editMemo: "メモをいらう"
reactionsList: "ツッコミ一覧" reactionsList: "ツッコミ一覧"
renotesList: "ブースト一覧" renotesList: "リノート一覧"
notificationDisplay: "通知見せる" notificationDisplay: "通知見せる"
leftTop: "左上" leftTop: "左上"
rightTop: "右上" rightTop: "右上"
@ -1137,7 +1131,7 @@ rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "ロールは公開ロー
cancelReactionConfirm: "ツッコむんをやっぱやめるか?" cancelReactionConfirm: "ツッコむんをやっぱやめるか?"
changeReactionConfirm: "ツッコミを別のに変えるか?" changeReactionConfirm: "ツッコミを別のに変えるか?"
later: "あとで" later: "あとで"
goToMisskey: "Sharkeyへ" goToMisskey: "Misskeyへ"
additionalEmojiDictionary: "絵文字の追加辞書" additionalEmojiDictionary: "絵文字の追加辞書"
installed: "インストールしとる" installed: "インストールしとる"
branding: "ブランディング" branding: "ブランディング"
@ -1172,7 +1166,7 @@ pastAnnouncements: "過去のお知らせやで"
youHaveUnreadAnnouncements: "あんたまだこのお知らせ読んどらんやろ。" youHaveUnreadAnnouncements: "あんたまだこのお知らせ読んどらんやろ。"
useSecurityKey: "ブラウザまたはデバイスの言う通りに、セキュリティキーまたはパスキーを使ってや。" useSecurityKey: "ブラウザまたはデバイスの言う通りに、セキュリティキーまたはパスキーを使ってや。"
replies: "返事" replies: "返事"
renotes: "ブースト" renotes: "リノート"
loadReplies: "返信を見るで" loadReplies: "返信を見るで"
loadConversation: "会話を見るで" loadConversation: "会話を見るで"
pinnedList: "ピン留めしはったリスト" pinnedList: "ピン留めしはったリスト"
@ -1183,7 +1177,7 @@ unnotifyNotes: "投稿の通知やめる"
authentication: "認証" authentication: "認証"
authenticationRequiredToContinue: "続けるんなら認証してや。" authenticationRequiredToContinue: "続けるんなら認証してや。"
dateAndTime: "日時" dateAndTime: "日時"
showRenotes: "ブースト出す" showRenotes: "リノート出す"
edited: "いじったやつ" edited: "いじったやつ"
notificationRecieveConfig: "通知もらうかの設定" notificationRecieveConfig: "通知もらうかの設定"
mutualFollow: "お互いフォローしてんで" mutualFollow: "お互いフォローしてんで"
@ -1314,7 +1308,7 @@ _initialAccountSetting:
pushNotificationDescription: "プッシュ通知を有効にすると{name}の通知をあんたのデバイスで受け取れるで。" pushNotificationDescription: "プッシュ通知を有効にすると{name}の通知をあんたのデバイスで受け取れるで。"
initialAccountSettingCompleted: "初期設定終わりや!" initialAccountSettingCompleted: "初期設定終わりや!"
haveFun: "{name}、楽しんでな~" haveFun: "{name}、楽しんでな~"
youCanContinueTutorial: "こんまま{name}(Sharkey)の使い方のチュートリアルにも行けるけど、ここでやめてすぐに使い始めてもええで。" youCanContinueTutorial: "こんまま{name}(Misskey)の使い方のチュートリアルにも行けるけど、ここでやめてすぐに使い始めてもええで。"
startTutorial: "チュートリアルはじめる" startTutorial: "チュートリアルはじめる"
skipAreYouSure: "初期設定飛ばすか?" skipAreYouSure: "初期設定飛ばすか?"
laterAreYouSure: "初期設定あとでやり直すん?" laterAreYouSure: "初期設定あとでやり直すん?"
@ -1325,10 +1319,10 @@ _initialTutorial:
skipAreYouSure: "チュートリアルやめるか?" skipAreYouSure: "チュートリアルやめるか?"
_landing: _landing:
title: "チュートリアルによう来たな" title: "チュートリアルによう来たな"
description: "ここでは、Sharkeyのカンタンな使い方とか機能を確かめれんで。" description: "ここでは、Misskeyのカンタンな使い方とか機能を確かめれんで。"
_note: _note:
title: "ノートってなんや?" title: "ノートってなんや?"
description: "Sharkeyでの投稿は「ート」って呼ばれてんで。ートは順々にタイムラインに載ってて、リアルタイムで新しくなってってんで。" description: "Misskeyでの投稿は「ート」って呼ばれてんで。ートは順々にタイムラインに載ってて、リアルタイムで新しくなってってんで。"
reply: "返信もできるで。返信の返信もできるから、スレッドっぽく会話をそのまま続けれもするで。" reply: "返信もできるで。返信の返信もできるから、スレッドっぽく会話をそのまま続けれもするで。"
renote: "そのノートを自分のタイムラインに流して共有できるで。テキスト入れて引用してもええな。" renote: "そのノートを自分のタイムラインに流して共有できるで。テキスト入れて引用してもええな。"
reaction: "ツッコミをつけることもできるで。細かいことは次のページや。" reaction: "ツッコミをつけることもできるで。細かいことは次のページや。"
@ -1336,13 +1330,13 @@ _initialTutorial:
_reaction: _reaction:
title: "ツッコミってなんや?" title: "ツッコミってなんや?"
description: "ノートには「ツッコミ」できんねん。「いいね」とか何言っとるかわからんし、簡単に表現できるのはええことやん?" description: "ノートには「ツッコミ」できんねん。「いいね」とか何言っとるかわからんし、簡単に表現できるのはええことやん?"
letsTryReacting: "ノートの「{reaction}」ボタンでツッコめるわ。試しに下のノートにツッコんでみ。" letsTryReacting: "ノートの「」ボタンでツッコめるわ。試しに下のノートにツッコんでみ。"
reactToContinue: "ツッコんだら進めるようになるで。" reactToContinue: "ツッコんだら進めるようになるで。"
reactNotification: "あんたのノートが誰かにツッコまれたら、すぐ通知するで。" reactNotification: "あんたのノートが誰かにツッコまれたら、すぐ通知するで。"
reactDone: "「{undo}」ボタンでツッコミやめれるで。" reactDone: "「」ボタンでツッコミやめれるで。"
_timeline: _timeline:
title: "タイムラインのしくみ" title: "タイムラインのしくみ"
description1: "Sharkeyには、いろいろタイムラインがあんでただ、サーバーによっては無効化されてるところもあるな。" description1: "Misskeyには、いろいろタイムラインがあんでただ、サーバーによっては無効化されてるところもあるな。"
home: "あんたがフォローしてるアカウントの投稿が見れんねん。" home: "あんたがフォローしてるアカウントの投稿が見れんねん。"
local: "このサーバーの中におる全員の投稿が見れるで。" local: "このサーバーの中におる全員の投稿が見れるで。"
social: "ホームタイムラインの投稿もローカルタイムラインのも一緒に見れるで。" social: "ホームタイムラインの投稿もローカルタイムラインのも一緒に見れるで。"
@ -1351,12 +1345,12 @@ _initialTutorial:
description3: "その他にも、リストタイムラインとかチャンネルタイムラインとかがあんねん。詳しいのは{link}を見とき。" description3: "その他にも、リストタイムラインとかチャンネルタイムラインとかがあんねん。詳しいのは{link}を見とき。"
_postNote: _postNote:
title: "ノートの投稿設定" title: "ノートの投稿設定"
description1: "Sharkeyにートを投稿するとき、いろんなオプションが付けれるで。投稿画面はこんな感じや。" description1: "Misskeyにートを投稿するとき、いろんなオプションが付けれるで。投稿画面はこんな感じや。"
_visibility: _visibility:
description: "ノートを見れる相手を制限できるわ。" description: "ノートを見れる相手を制限できるわ。"
public: "みんなに見せるで。" public: "みんなに見せるで。"
home: "ホームタイムラインにだけ見せるで。フォロワーとか、プロフィールを見に来た人、ブーストからも見れるから、実質は全員見れるけどな。あんまし広がりにくいってことや。" home: "ホームタイムラインにだけ見せるで。フォロワーとか、プロフィールを見に来た人、リノートからも見れるから、実質は全員見れるけどな。あんまし広がりにくいってことや。"
followers: "フォロワーにだけ見せるで。自分以外はブーストできへんし、フォロワー以外は絶対に見れへん。" followers: "フォロワーにだけ見せるで。自分以外はリノートできへんし、フォロワー以外は絶対に見れへん。"
direct: "指定した人にだけ公開されて、ついでに通知も送るで。ダイレクトメールの代わりとして使ってな。" direct: "指定した人にだけ公開されて、ついでに通知も送るで。ダイレクトメールの代わりとして使ってな。"
doNotSendConfidencialOnDirect1: "機密情報を送るときは十分注意せえよ。" doNotSendConfidencialOnDirect1: "機密情報を送るときは十分注意せえよ。"
doNotSendConfidencialOnDirect2: "送信先のサーバーの管理者は投稿内容が見れるから、信用できへんサーバーのひとにダイレクト投稿するときには、めっちゃ用心しとくんやで。" doNotSendConfidencialOnDirect2: "送信先のサーバーの管理者は投稿内容が見れるから、信用できへんサーバーのひとにダイレクト投稿するときには、めっちゃ用心しとくんやで。"
@ -1422,7 +1416,7 @@ _achievements:
_notes1: _notes1:
title: "まいど!" title: "まいど!"
description: "初めてノート投稿したった" description: "初めてノート投稿したった"
flavor: "Sharkeyを楽しんでな" flavor: "Misskeyを楽しんでな"
_notes10: _notes10:
title: "ノートの天保山" title: "ノートの天保山"
description: "ートを10回投稿した" description: "ートを10回投稿した"
@ -1518,7 +1512,7 @@ _achievements:
_login1000: _login1000:
title: "ノートマイスターⅢ" title: "ノートマイスターⅢ"
description: "通算1,000日ログインした" description: "通算1,000日ログインした"
flavor: "Sharkeyようさん使てもろておおきにな" flavor: "Misskeyようさん使てもろておおきにな"
_noteClipped1: _noteClipped1:
title: "アカンどれもクリップしたいわ" title: "アカンどれもクリップしたいわ"
description: "初めてノートをクリップした" description: "初めてノートをクリップした"
@ -1578,9 +1572,9 @@ _achievements:
title: "実績好き" title: "実績好き"
description: "実績一覧を3分以上眺め続けた" description: "実績一覧を3分以上眺め続けた"
_iLoveMisskey: _iLoveMisskey:
title: "Sharkey好きやねん" title: "Misskey好きやねん"
description: "\"I ❤ #Sharkey\"を投稿した" description: "\"I ❤ #Misskey\"を投稿した"
flavor: "Sharkeyを使ってくれておおきにな by 開発チーム" flavor: "Misskeyを使ってくれておおきにな by 開発チーム"
_foundTreasure: _foundTreasure:
title: "なんでも鑑定団" title: "なんでも鑑定団"
description: "隠されたお宝を発見した" description: "隠されたお宝を発見した"
@ -1588,7 +1582,7 @@ _achievements:
title: "ねんね" title: "ねんね"
description: "クライアントを起動してから30分以上経過した" description: "クライアントを起動してから30分以上経過した"
_client60min: _client60min:
title: "Sharkeyの見過ぎや" title: "Misskeyの見過ぎや"
description: "クライアント付けてから1時間経ってもうたで。" description: "クライアント付けてから1時間経ってもうたで。"
_noteDeletedWithin1min: _noteDeletedWithin1min:
title: "*おおっと*" title: "*おおっと*"
@ -1659,7 +1653,7 @@ _achievements:
title: "心配性" title: "心配性"
description: "通知のテストしすぎやって" description: "通知のテストしすぎやって"
_tutorialCompleted: _tutorialCompleted:
title: "Sharkeyひよっこ講座 修了証" title: "Misskeyひよっこ講座 修了証"
description: "チュートリアル全部やった" description: "チュートリアル全部やった"
_bubbleGameExplodingHead: _bubbleGameExplodingHead:
title: "🤯" title: "🤯"
@ -1836,14 +1830,14 @@ _registry:
domain: "ドメイン" domain: "ドメイン"
createKey: "キーを作る" createKey: "キーを作る"
_aboutMisskey: _aboutMisskey:
about: "Sharkeyは、Misskeyをベースにしたオープンソースなソフトウェアや。" about: "Misskeyはsyuiloが2014年からずっと作ってはる、オープンソースなソフトウェアや。"
contributors: "主な貢献者" contributors: "主な貢献者"
allContributors: "全ての貢献者" allContributors: "全ての貢献者"
source: "ソースコード" source: "ソースコード"
original: "オリジナル" original: "オリジナル"
thisIsModifiedVersion: "{name}はオリジナルのSharkeyをいじったバージョンをつこうてるで。" thisIsModifiedVersion: "{name}はオリジナルのMisskeyをいじったバージョンをつこうてるで。"
translation: "Sharkeyを翻訳" translation: "Misskeyを翻訳"
donate: "Sharkeyに寄付" donate: "Misskeyに寄付"
morePatrons: "他にもぎょうさんの人からサポートしてもろてんねん。ほんまおおきに🥰" morePatrons: "他にもぎょうさんの人からサポートしてもろてんねん。ほんまおおきに🥰"
patrons: "支援者" patrons: "支援者"
projectMembers: "" projectMembers: ""
@ -1871,7 +1865,7 @@ _channel:
notesCount: "{n}こ投稿があるで" notesCount: "{n}こ投稿があるで"
nameAndDescription: "名前と説明" nameAndDescription: "名前と説明"
nameOnly: "名前だけ" nameOnly: "名前だけ"
allowRenoteToExternal: "チャンネルの外にブーストできるようにする" allowRenoteToExternal: "チャンネルの外にリノートできるようにする"
_menuDisplay: _menuDisplay:
sideFull: "横" sideFull: "横"
sideIcon: "横(アイコン)" sideIcon: "横(アイコン)"
@ -1968,7 +1962,7 @@ _soundSettings:
driveFileTypeWarn: "このファイルは対応しとらへん" driveFileTypeWarn: "このファイルは対応しとらへん"
driveFileTypeWarnDescription: "音声ファイルを選びや" driveFileTypeWarnDescription: "音声ファイルを選びや"
driveFileDurationWarn: "音が長すぎるわ" driveFileDurationWarn: "音が長すぎるわ"
driveFileDurationWarnDescription: "長い音使うたらSharkey使うのに良うないかもしれへんで。それでもええか" driveFileDurationWarnDescription: "長い音使うたらMisskey使うのに良うないかもしれへんで。それでもええか"
driveFileError: "音声が読み込めへんかったで。設定を変更せえや" driveFileError: "音声が読み込めへんかったで。設定を変更せえや"
_ago: _ago:
future: "未来" future: "未来"
@ -2344,7 +2338,7 @@ _notification:
youGotMention: "{name}からのメンション" youGotMention: "{name}からのメンション"
youGotReply: "{name}からのリプライ" youGotReply: "{name}からのリプライ"
youGotQuote: "{name}による引用" youGotQuote: "{name}による引用"
youRenoted: "{name}がブーストしたみたいやで" youRenoted: "{name}がリノートしたみたいやで"
youWereFollowed: "フォローされたで" youWereFollowed: "フォローされたで"
youReceivedFollowRequest: "フォロー許可してほしいみたいやな" youReceivedFollowRequest: "フォロー許可してほしいみたいやな"
yourFollowRequestAccepted: "フォローさせてもろたで" yourFollowRequestAccepted: "フォローさせてもろたで"
@ -2360,7 +2354,7 @@ _notification:
notificationWillBeDisplayedLikeThis: "通知はこのように表示されるで" notificationWillBeDisplayedLikeThis: "通知はこのように表示されるで"
reactedBySomeUsers: "{n}人がツッコんだで" reactedBySomeUsers: "{n}人がツッコんだで"
likedBySomeUsers: "{n}人がいいねしたで" likedBySomeUsers: "{n}人がいいねしたで"
renotedBySomeUsers: "{n}人がブーストしたで" renotedBySomeUsers: "{n}人がリノートしたで"
followedBySomeUsers: "{n}人にフォローされたで" followedBySomeUsers: "{n}人にフォローされたで"
flushNotification: "通知の履歴をリセットする" flushNotification: "通知の履歴をリセットする"
_types: _types:
@ -2377,6 +2371,7 @@ _notification:
followRequestAccepted: "フォローが受理されたで" followRequestAccepted: "フォローが受理されたで"
roleAssigned: "ロールが付与された" roleAssigned: "ロールが付与された"
achievementEarned: "実績の獲得" achievementEarned: "実績の獲得"
login: "ログイン"
app: "連携アプリからの通知や" app: "連携アプリからの通知や"
_actions: _actions:
followBack: "フォローバック" followBack: "フォローバック"
@ -2435,7 +2430,7 @@ _webhookSettings:
followed: "フォローもらったとき~!" followed: "フォローもらったとき~!"
note: "ノートを投稿したとき~!" note: "ノートを投稿したとき~!"
reply: "返信があるとき~!" reply: "返信があるとき~!"
renote: "ブーストされるとき~!" renote: "リノートされるとき~!"
reaction: "ツッコまれたとき~!" reaction: "ツッコまれたとき~!"
mention: "メンションがあるとき~!" mention: "メンションがあるとき~!"
_systemEvents: _systemEvents:

View file

@ -77,6 +77,8 @@ _profile:
username: "ಬಳಕೆಹೆಸರು" username: "ಬಳಕೆಹೆಸರು"
_notification: _notification:
youWereFollowed: "ಹಿಂಬಾಲಿಸಿದರು" youWereFollowed: "ಹಿಂಬಾಲಿಸಿದರು"
_types:
login: "ಪ್ರವೇಶ"
_actions: _actions:
reply: "ಉತ್ತರಿಸು" reply: "ಉತ್ತರಿಸು"
_deck: _deck:

View file

@ -476,7 +476,6 @@ uiLanguage: "UI 표시 언어"
aboutX: "{x}에 대해서" aboutX: "{x}에 대해서"
emojiStyle: "이모지 모양" emojiStyle: "이모지 모양"
native: "기본" native: "기본"
disableDrawer: "드로어 메뉴 쓰지 않기"
showNoteActionsOnlyHover: "마우스 올맀을 때만 노트 액션 버턴 보이기" showNoteActionsOnlyHover: "마우스 올맀을 때만 노트 액션 버턴 보이기"
noHistory: "기록이 없십니다" noHistory: "기록이 없십니다"
signinHistory: "로그인 기록" signinHistory: "로그인 기록"
@ -583,6 +582,9 @@ describeFile: "캡션 옇기"
enterFileDescription: "캡션 서기" enterFileDescription: "캡션 서기"
author: "맨던 사람" author: "맨던 사람"
manage: "간리" manage: "간리"
large: "커게"
medium: "엔갆게"
small: "쪼맪게"
emailServer: "전자우펜 서버" emailServer: "전자우펜 서버"
email: "전자우펜" email: "전자우펜"
emailAddress: "전자우펜 주소" emailAddress: "전자우펜 주소"
@ -600,6 +602,7 @@ reporter: "신고한 사람"
reporteeOrigin: "신고덴 사람" reporteeOrigin: "신고덴 사람"
reporterOrigin: "신고한 곳" reporterOrigin: "신고한 곳"
forwardReport: "웬겍 서버에 신고 보내기" forwardReport: "웬겍 서버에 신고 보내기"
forwardReportIsAnonymous: "웬겍 서버서는 나으 정보럴 몬 보고 익멩으 시스템 게정어로 보입니다."
waitingFor: "{x}(얼)럴 지달리고 잇십니다" waitingFor: "{x}(얼)럴 지달리고 잇십니다"
random: "무작이" random: "무작이"
system: "시스템" system: "시스템"
@ -613,12 +616,14 @@ followersCount: "팔로워 수"
noteFavoritesCount: "질겨찾기한 노트 수" noteFavoritesCount: "질겨찾기한 노트 수"
clips: "클립 맨걸기" clips: "클립 맨걸기"
clearCache: "캐시 비우기" clearCache: "캐시 비우기"
nUsers: "{n} 사용자"
typingUsers: "{users} 님이 서고 잇어예" typingUsers: "{users} 님이 서고 잇어예"
unlikeConfirm: "좋네예럴 무룹니꺼?" unlikeConfirm: "좋네예럴 무룹니꺼?"
info: "정보" info: "정보"
selectAccount: "계정 개리기" selectAccount: "계정 개리기"
user: "사용자" user: "사용자"
administration: "간리" administration: "간리"
middle: "엔갆게"
translatedFrom: "{x}서 번옉" translatedFrom: "{x}서 번옉"
on: "킴" on: "킴"
off: "껌" off: "껌"
@ -633,6 +638,7 @@ oneMonth: "한 달"
file: "파일" file: "파일"
typeToConfirm: "게속할라먼 {x}럴 누질라 주이소" typeToConfirm: "게속할라먼 {x}럴 누질라 주이소"
pleaseSelect: "개리 주이소" pleaseSelect: "개리 주이소"
remoteOnly: "웬겍만"
tools: "도구" tools: "도구"
like: "좋네예!" like: "좋네예!"
unlike: "좋네예 무루기" unlike: "좋네예 무루기"
@ -643,7 +649,10 @@ role: "옉할"
noRole: "옉할이 어ᇝ십니다" noRole: "옉할이 어ᇝ십니다"
thisPostMayBeAnnoyingCancel: "아이예" thisPostMayBeAnnoyingCancel: "아이예"
likeOnly: "좋네예마" likeOnly: "좋네예마"
hiddenTags: "수ᇚ훈 해시태그"
myClips: "내 클립" myClips: "내 클립"
preservedUsernames: "예약 사용자 이럼"
specifyUser: "사용자 지정"
icon: "아바타" icon: "아바타"
replies: "답하기" replies: "답하기"
renotes: "리노트" renotes: "리노트"
@ -709,6 +718,16 @@ _achievements:
description: "0분 0초에 노트를 섰어예" description: "0분 0초에 노트를 섰어예"
_tutorialCompleted: _tutorialCompleted:
description: "길라잡이럴 껕냇십니다" description: "길라잡이럴 껕냇십니다"
_role:
displayOrder: "보기 순서"
_priority:
middle: "엔갆게"
_options:
canHideAds: "강고 수ᇚ후기"
_condition:
isRemote: "웬겍 사용자"
isCat: "갱이 사용자"
isBot: "자동 사용자"
_gallery: _gallery:
my: "내 걸" my: "내 걸"
liked: "좋네예한 걸" liked: "좋네예한 걸"
@ -794,6 +813,7 @@ _notification:
mention: "멘션" mention: "멘션"
quote: "따오기" quote: "따오기"
reaction: "반엉" reaction: "반엉"
login: "로그인"
_actions: _actions:
reply: "답하기" reply: "답하기"
_deck: _deck:

View file

@ -8,6 +8,9 @@ search: "검색"
notifications: "알림" notifications: "알림"
username: "유저명" username: "유저명"
password: "비밀번호" password: "비밀번호"
initialPasswordForSetup: "초기 설정용 비밀번호"
initialPasswordIsIncorrect: "초기 설정용 비밀번호가 올바르지 않습니다."
initialPasswordForSetupDescription: "Misskey를 직접 설치하는 경우, 설정 파일에 입력해둔 비밀번호를 사용하세요.\nMisskey 설치를 도와주는 호스팅 서비스 등을 사용하는 경우, 서비스 제공자로부터 받은 비밀번호를 사용하세요.\n비밀번호를 따로 설정하지 않은 경우, 아무것도 입력하지 않아도 됩니다."
forgotPassword: "비밀번호 재설정" forgotPassword: "비밀번호 재설정"
fetchingAsApObject: "연합에서 찾아보는 중" fetchingAsApObject: "연합에서 찾아보는 중"
ok: "확인" ok: "확인"
@ -52,14 +55,15 @@ deleteAndEditConfirm: "이 노트를 삭제한 뒤 다시 편집하시겠습니
addToList: "리스트에 추가" addToList: "리스트에 추가"
addToAntenna: "안테나에 추가" addToAntenna: "안테나에 추가"
sendMessage: "메시지 보내기" sendMessage: "메시지 보내기"
copyRSS: "RSS 주소 복사" copyRSS: "RSS 복사"
copyUsername: "유저명 복사" copyUsername: "유저명 복사"
copyUserId: "유저 ID 복사" copyUserId: "유저 ID 복사"
copyNoteId: "노트 ID 복사" copyNoteId: "노트 ID 복사"
copyFileId: "파일 ID 복사" copyFileId: "파일 ID 복사"
copyFolderId: "폴더 ID 복사" copyFolderId: "폴더 ID 복사"
copyProfileUrl: "프로필 URL 복사" copyProfileUrl: "프로필 URL 복사"
searchUser: "유저 검색" searchUser: "사용자 검색"
searchThisUsersNotes: "사용자의 노트 검색"
reply: "답글" reply: "답글"
loadMore: "더 보기" loadMore: "더 보기"
showMore: "더 보기" showMore: "더 보기"
@ -154,6 +158,7 @@ editList: "리스트 편집"
selectChannel: "채널 선택" selectChannel: "채널 선택"
selectAntenna: "안테나 선택" selectAntenna: "안테나 선택"
editAntenna: "안테나 편집" editAntenna: "안테나 편집"
createAntenna: "안테나 만들기"
selectWidget: "위젯 선택" selectWidget: "위젯 선택"
editWidgets: "위젯 편집" editWidgets: "위젯 편집"
editWidgetsExit: "편집 종료" editWidgetsExit: "편집 종료"
@ -194,6 +199,7 @@ followConfirm: "{name}님을 팔로우 하시겠습니까?"
proxyAccount: "프록시 계정" proxyAccount: "프록시 계정"
proxyAccountDescription: "프록시 계정은 특정 조건 하에서 유저의 리모트 팔로우를 대행하는 계정입니다. 예를 들면, 유저가 리모트 유저를 리스트에 넣었을 때, 리스트에 들어간 유저를 아무도 팔로우한 적이 없다면 액티비티가 서버로 배달되지 않기 때문에, 대신 프록시 계정이 해당 유저를 팔로우하도록 합니다." proxyAccountDescription: "프록시 계정은 특정 조건 하에서 유저의 리모트 팔로우를 대행하는 계정입니다. 예를 들면, 유저가 리모트 유저를 리스트에 넣었을 때, 리스트에 들어간 유저를 아무도 팔로우한 적이 없다면 액티비티가 서버로 배달되지 않기 때문에, 대신 프록시 계정이 해당 유저를 팔로우하도록 합니다."
host: "호스트" host: "호스트"
selectSelf: "본인을 선택"
selectUser: "유저 선택" selectUser: "유저 선택"
recipient: "수신인" recipient: "수신인"
annotation: "내용에 대한 주석" annotation: "내용에 대한 주석"
@ -209,6 +215,7 @@ perDay: "1일마다"
stopActivityDelivery: "액티비티 보내지 않기" stopActivityDelivery: "액티비티 보내지 않기"
blockThisInstance: "이 서버를 차단" blockThisInstance: "이 서버를 차단"
silenceThisInstance: "서버를 사일런스" silenceThisInstance: "서버를 사일런스"
mediaSilenceThisInstance: "서버의 미디어를 사일런스"
operations: "작업" operations: "작업"
software: "소프트웨어" software: "소프트웨어"
version: "버전" version: "버전"
@ -230,6 +237,10 @@ blockedInstances: "차단된 서버"
blockedInstancesDescription: "차단하려는 서버의 호스트 이름을 줄바꿈으로 구분하여 설정합니다. 차단된 인스턴스는 이 인스턴스와 통신할 수 없게 됩니다." blockedInstancesDescription: "차단하려는 서버의 호스트 이름을 줄바꿈으로 구분하여 설정합니다. 차단된 인스턴스는 이 인스턴스와 통신할 수 없게 됩니다."
silencedInstances: "사일런스한 서버" silencedInstances: "사일런스한 서버"
silencedInstancesDescription: "사일런스하려는 서버의 호스트명을 한 줄에 하나씩 입력합니다. 사일런스된 서버에 소속된 유저는 모두 '사일런스'된 상태로 취급되며, 이 서버로부터의 팔로우가 프로필 설정과 무관하게 승인제로 변경되고, 팔로워가 아닌 로컬 유저에게는 멘션할 수 없게 됩니다. 정지된 서버에는 적용되지 않습니다." silencedInstancesDescription: "사일런스하려는 서버의 호스트명을 한 줄에 하나씩 입력합니다. 사일런스된 서버에 소속된 유저는 모두 '사일런스'된 상태로 취급되며, 이 서버로부터의 팔로우가 프로필 설정과 무관하게 승인제로 변경되고, 팔로워가 아닌 로컬 유저에게는 멘션할 수 없게 됩니다. 정지된 서버에는 적용되지 않습니다."
mediaSilencedInstances: "미디어를 사일런스한 서버"
mediaSilencedInstancesDescription: "미디어를 사일런스 하려는 서버의 호스트를 한 줄에 하나씩 입력합니다. 미디어가 사일런스된 서버의 유저가 업로드한 파일은 모두 민감한 미디어로 처리되며, 커스텀 이모지를 사용할 수 없게 됩니다. 또한, 차단한 인스턴스에는 적용되지 않습니다."
federationAllowedHosts: "연합을 허가하는 서버"
federationAllowedHostsDescription: "연합을 허가하는 서버의 호스트를 엔터로 구분해서 설정합니다."
muteAndBlock: "뮤트 및 차단" muteAndBlock: "뮤트 및 차단"
mutedUsers: "뮤트한 유저" mutedUsers: "뮤트한 유저"
blockedUsers: "차단한 유저" blockedUsers: "차단한 유저"
@ -328,6 +339,7 @@ renameFolder: "폴더 이름 바꾸기"
deleteFolder: "폴더 삭제" deleteFolder: "폴더 삭제"
folder: "폴더" folder: "폴더"
addFile: "파일 추가" addFile: "파일 추가"
showFile: "파일 표시하기"
emptyDrive: "드라이브가 비어 있습니다" emptyDrive: "드라이브가 비어 있습니다"
emptyFolder: "폴더가 비어 있습니다" emptyFolder: "폴더가 비어 있습니다"
unableToDelete: "삭제할 수 없습니다" unableToDelete: "삭제할 수 없습니다"
@ -373,7 +385,7 @@ registration: "등록"
enableRegistration: "신규 회원가입을 활성화" enableRegistration: "신규 회원가입을 활성화"
invite: "초대" invite: "초대"
driveCapacityPerLocalAccount: "로컬 유저 한 명당 드라이브 용량" driveCapacityPerLocalAccount: "로컬 유저 한 명당 드라이브 용량"
driveCapacityPerRemoteAccount: "리모트 유저 한 명당 드라이브 용량" driveCapacityPerRemoteAccount: "원격 사용자별 드라이브 용량"
inMb: "메가바이트 단위" inMb: "메가바이트 단위"
bannerUrl: "배너 이미지 URL" bannerUrl: "배너 이미지 URL"
backgroundImageUrl: "배경 이미지 URL" backgroundImageUrl: "배경 이미지 URL"
@ -442,6 +454,7 @@ totpDescription: "인증 앱을 사용하여 일회성 비밀번호 입력"
moderator: "모더레이터" moderator: "모더레이터"
moderation: "조정" moderation: "조정"
moderationNote: "조정 기록" moderationNote: "조정 기록"
moderationNoteDescription: "모더레이터 역할을 가진 유저만 보이는 메모를 적을 수 있습니다."
addModerationNote: "조정 기록 추가하기" addModerationNote: "조정 기록 추가하기"
moderationLogs: "모더레이션 로그" moderationLogs: "모더레이션 로그"
nUsersMentioned: "{n}명이 언급함" nUsersMentioned: "{n}명이 언급함"
@ -503,7 +516,10 @@ uiLanguage: "UI 표시 언어"
aboutX: "{x}에 대하여" aboutX: "{x}에 대하여"
emojiStyle: "이모지 스타일" emojiStyle: "이모지 스타일"
native: "기본" native: "기본"
disableDrawer: "드로어 메뉴를 사용하지 않기" menuStyle: "메뉴 스타일"
style: "스타일"
drawer: "서랍"
popup: "팝업"
showNoteActionsOnlyHover: "마우스가 올라간 때에만 노트 동작 버튼을 표시하기" showNoteActionsOnlyHover: "마우스가 올라간 때에만 노트 동작 버튼을 표시하기"
showReactionsCount: "노트의 반응 수를 표시하기" showReactionsCount: "노트의 반응 수를 표시하기"
noHistory: "기록이 없습니다" noHistory: "기록이 없습니다"
@ -586,6 +602,8 @@ ascendingOrder: "오름차순"
descendingOrder: "내림차순" descendingOrder: "내림차순"
scratchpad: "스크래치 패드" scratchpad: "스크래치 패드"
scratchpadDescription: "스크래치 패드는 AiScript 의 테스트 환경을 제공합니다. Misskey 와 상호 작용하는 코드를 작성, 실행 및 결과를 확인할 수 있습니다." scratchpadDescription: "스크래치 패드는 AiScript 의 테스트 환경을 제공합니다. Misskey 와 상호 작용하는 코드를 작성, 실행 및 결과를 확인할 수 있습니다."
uiInspector: "UI 인스펙터"
uiInspectorDescription: "메모리에 있는 UI 컴포넌트의 인스턴트 목록을 볼 수 있습니다. UI 컴포넌트는 Ui:C: 계열 함수로 만들어집니다."
output: "출력" output: "출력"
script: "스크립트" script: "스크립트"
disablePagesScript: "Pages 에서 AiScript 를 사용하지 않음" disablePagesScript: "Pages 에서 AiScript 를 사용하지 않음"
@ -702,10 +720,7 @@ abuseReported: "신고를 보냈습니다. 신고해 주셔서 감사합니다."
reporter: "신고자" reporter: "신고자"
reporteeOrigin: "피신고자" reporteeOrigin: "피신고자"
reporterOrigin: "신고자" reporterOrigin: "신고자"
forwardReport: "리모트 서버에도 신고 내용 보내기"
forwardReportIsAnonymous: "리모트 서버에서는 나의 정보를 볼 수 없으며, 익명의 시스템 계정으로 표시됩니다."
send: "전송" send: "전송"
abuseMarkAsResolved: "해결됨으로 표시"
openInNewTab: "새 탭에서 열기" openInNewTab: "새 탭에서 열기"
openInSideView: "사이드뷰로 열기" openInSideView: "사이드뷰로 열기"
defaultNavigationBehaviour: "기본 탐색 동작" defaultNavigationBehaviour: "기본 탐색 동작"
@ -907,6 +922,7 @@ followersVisibility: "팔로워의 공개 범위"
continueThread: "글타래 더 보기" continueThread: "글타래 더 보기"
deleteAccountConfirm: "계정이 삭제되고 되돌릴 수 없게 됩니다. 계속하시겠습니까? " deleteAccountConfirm: "계정이 삭제되고 되돌릴 수 없게 됩니다. 계속하시겠습니까? "
incorrectPassword: "비밀번호가 올바르지 않습니다." incorrectPassword: "비밀번호가 올바르지 않습니다."
incorrectTotp: "OTP 번호가 틀렸거나 유효기간이 만료되어 있을 수 있습니다."
voteConfirm: "\"{choice}\"에 투표하시겠습니까?" voteConfirm: "\"{choice}\"에 투표하시겠습니까?"
hide: "숨기기" hide: "숨기기"
useDrawerReactionPickerForMobile: "모바일에서 드로어 메뉴로 표시" useDrawerReactionPickerForMobile: "모바일에서 드로어 메뉴로 표시"
@ -1106,6 +1122,8 @@ preservedUsernames: "예약한 사용자 이름"
preservedUsernamesDescription: "예약할 사용자명을 한 줄에 하나씩 입력합니다. 여기에서 지정한 사용자명으로는 계정을 생성할 수 없게 됩니다. 단, 관리자 권한으로 계정을 생성할 때에는 해당되지 않으며, 이미 존재하는 계정도 영향을 받지 않습니다." preservedUsernamesDescription: "예약할 사용자명을 한 줄에 하나씩 입력합니다. 여기에서 지정한 사용자명으로는 계정을 생성할 수 없게 됩니다. 단, 관리자 권한으로 계정을 생성할 때에는 해당되지 않으며, 이미 존재하는 계정도 영향을 받지 않습니다."
createNoteFromTheFile: "이 파일로 노트를 작성" createNoteFromTheFile: "이 파일로 노트를 작성"
archive: "아카이브" archive: "아카이브"
archived: "보관됨"
unarchive: "보관 취소"
channelArchiveConfirmTitle: "{name} 채널을 보존하시겠습니까?" channelArchiveConfirmTitle: "{name} 채널을 보존하시겠습니까?"
channelArchiveConfirmDescription: "보존한 채널은 채널 목록과 검색 결과에 표시되지 않으며 새로운 노트도 작성할 수 없습니다." channelArchiveConfirmDescription: "보존한 채널은 채널 목록과 검색 결과에 표시되지 않으며 새로운 노트도 작성할 수 없습니다."
thisChannelArchived: "이 채널은 보존되었습니다." thisChannelArchived: "이 채널은 보존되었습니다."
@ -1116,6 +1134,9 @@ preventAiLearning: "기계학습(생성형 AI)으로의 사용을 거부"
preventAiLearningDescription: "외부의 문장 생성 AI나 이미지 생성 AI에 대해 제출한 노트나 이미지 등의 콘텐츠를 학습의 대상으로 사용하지 않도록 요구합니다. 다만, 이 요구사항을 지킬 의무는 없기 때문에 학습을 완전히 방지하는 것은 아닙니다." preventAiLearningDescription: "외부의 문장 생성 AI나 이미지 생성 AI에 대해 제출한 노트나 이미지 등의 콘텐츠를 학습의 대상으로 사용하지 않도록 요구합니다. 다만, 이 요구사항을 지킬 의무는 없기 때문에 학습을 완전히 방지하는 것은 아닙니다."
options: "옵션" options: "옵션"
specifyUser: "사용자 지정" specifyUser: "사용자 지정"
lookupConfirm: "조회 할까요?"
openTagPageConfirm: "해시태그의 페이지를 열까요?"
specifyHost: "호스트 지정"
failedToPreviewUrl: "미리 볼 수 없음" failedToPreviewUrl: "미리 볼 수 없음"
update: "업데이트" update: "업데이트"
rolesThatCanBeUsedThisEmojiAsReaction: "이 이모지를 리액션으로 사용할 수 있는 역할" rolesThatCanBeUsedThisEmojiAsReaction: "이 이모지를 리액션으로 사용할 수 있는 역할"
@ -1249,6 +1270,30 @@ alwaysConfirmFollow: "팔로우일 때 항상 확인하기"
inquiry: "문의하기" inquiry: "문의하기"
tryAgain: "다시 시도해 주세요." tryAgain: "다시 시도해 주세요."
confirmWhenRevealingSensitiveMedia: "민감한 미디어를 열 때 두 번 확인" confirmWhenRevealingSensitiveMedia: "민감한 미디어를 열 때 두 번 확인"
sensitiveMediaRevealConfirm: "민감한 미디어입니다. 표시할까요?"
createdLists: "만든 리스트"
createdAntennas: "만든 안테나"
fromX: "{x}부터"
genEmbedCode: "임베디드 코드 만들기"
noteOfThisUser: "이 유저의 노트 목록"
clipNoteLimitExceeded: "더 이상 이 클립에 노트를 추가 할 수 없습니다."
performance: "퍼포먼스"
modified: "변경 있음"
discard: "파기"
thereAreNChanges: "{n}건 변경이 있습니다."
signinWithPasskey: "패스키로 로그인"
unknownWebAuthnKey: "등록되지 않은 패스키입니다."
passkeyVerificationFailed: "패스키 검증을 실패했습니다."
passkeyVerificationSucceededButPasswordlessLoginDisabled: "패스키를 검증했으나, 비밀번호 없이 로그인하기가 꺼져 있습니다."
messageToFollower: "팔로워에 보낼 메시지"
target: "대상"
_abuseUserReport:
forward: "전달"
forwardDescription: "익명 시스템 계정을 사용하여 리모트 서버에 신고 내용을 전달할 수 있습니다."
resolve: "해결됨"
accept: "인용"
reject: "기각"
resolveTutorial: "적절한 신고 내용에 대응한 경우, \"인용\"을 선택하여 \"해결됨\"으로 기록합니다.\n적절하지 않은 신고를 받은 경우, \"기각\"을 선택하여 \"기각\"으로 기록합니다."
_delivery: _delivery:
status: "전송 상태" status: "전송 상태"
stop: "정지됨" stop: "정지됨"
@ -1320,10 +1365,10 @@ _initialTutorial:
_reaction: _reaction:
title: "'리액션'이 무엇인가요?" title: "'리액션'이 무엇인가요?"
description: "노트에 '리액션'을 보낼 수 있습니다. '좋아요'만으로는 충분히 전해지지 않는 감정을, 이모지에 실어서 가볍게 보낼 수 있습니다." description: "노트에 '리액션'을 보낼 수 있습니다. '좋아요'만으로는 충분히 전해지지 않는 감정을, 이모지에 실어서 가볍게 보낼 수 있습니다."
letsTryReacting: "리액션은 노트의 '{reaction}' 버튼을 클릭하여 붙일 수 있습니다. 지금 표시되는 샘플 노트에 리액션을 달아 보세요!" letsTryReacting: "리액션은 노트의 '+' 버튼을 클릭하여 붙일 수 있습니다. 지금 표시되는 샘플 노트에 리액션을 달아 보세요!"
reactToContinue: "다음으로 진행하려면 리액션을 보내세요." reactToContinue: "다음으로 진행하려면 리액션을 보내세요."
reactNotification: "누군가가 나의 노트에 리액션을 보내면 실시간으로 알림을 받게 됩니다." reactNotification: "누군가가 나의 노트에 리액션을 보내면 실시간으로 알림을 받게 됩니다."
reactDone: "'{undo}' 버튼을 눌러서 리액션을 취소할 수 있습니다." reactDone: "'-' 버튼을 눌러서 리액션을 취소할 수 있습니다."
_timeline: _timeline:
title: "타임라인에 대하여" title: "타임라인에 대하여"
description1: "Misskey에는 종류에 따라 여러 가지의 타임라인으로 구성되어 있습니다.(서버에 따라서는 일부 타임라인을 사용할 수 없는 경우가 있습니다)" description1: "Misskey에는 종류에 따라 여러 가지의 타임라인으로 구성되어 있습니다.(서버에 따라서는 일부 타임라인을 사용할 수 없는 경우가 있습니다)"
@ -1383,6 +1428,7 @@ _serverSettings:
fanoutTimelineDescription: "활성화하면 각종 타임라인을 가져올 때의 성능을 대폭 향상하며, 데이터베이스의 부하를 줄일 수 있습니다. 단, Redis의 메모리 사용량이 증가합니다. 서버의 메모리 용량이 작거나, 서비스가 불안정해지는 경우 비활성화할 수 있습니다." fanoutTimelineDescription: "활성화하면 각종 타임라인을 가져올 때의 성능을 대폭 향상하며, 데이터베이스의 부하를 줄일 수 있습니다. 단, Redis의 메모리 사용량이 증가합니다. 서버의 메모리 용량이 작거나, 서비스가 불안정해지는 경우 비활성화할 수 있습니다."
fanoutTimelineDbFallback: "데이터베이스를 예비로 사용하기" fanoutTimelineDbFallback: "데이터베이스를 예비로 사용하기"
fanoutTimelineDbFallbackDescription: "활성화하면 타임라인의 캐시되어 있지 않은 부분에 대해 DB에 질의하여 정보를 가져옵니다. 비활성화하면 이를 실행하지 않음으로써 서버의 부하를 줄일 수 있지만, 타임라인에서 가져올 수 있는 게시물 범위가 한정됩니다." fanoutTimelineDbFallbackDescription: "활성화하면 타임라인의 캐시되어 있지 않은 부분에 대해 DB에 질의하여 정보를 가져옵니다. 비활성화하면 이를 실행하지 않음으로써 서버의 부하를 줄일 수 있지만, 타임라인에서 가져올 수 있는 게시물 범위가 한정됩니다."
reactionsBufferingDescription: "활성화 한 경우, 리액션 작성 퍼포먼스가 대폭 향상되어 DB의 부하를 줄일 수 있으나, Redis의 메모리 사용량이 많아집니다."
inquiryUrl: "문의처 URL" inquiryUrl: "문의처 URL"
inquiryUrlDescription: "서버 운영자에게 보내는 문의 양식의 URL이나 운영자의 연락처 등이 적힌 웹 페이지의 URL을 설정합니다." inquiryUrlDescription: "서버 운영자에게 보내는 문의 양식의 URL이나 운영자의 연락처 등이 적힌 웹 페이지의 URL을 설정합니다."
_accountMigration: _accountMigration:
@ -1716,10 +1762,15 @@ _role:
canSearchNotes: "노트 검색 이용 가능 여부" canSearchNotes: "노트 검색 이용 가능 여부"
canUseTranslator: "번역 기능의 사용" canUseTranslator: "번역 기능의 사용"
avatarDecorationLimit: "아바타 장식의 최대 붙임 개수" avatarDecorationLimit: "아바타 장식의 최대 붙임 개수"
canImportAntennas: "안테나 가져오기 허용"
canImportBlocking: "차단 목록 가져오기 허용"
canImportFollowing: "팔로우 가져오기 허용"
canImportMuting: "뮤트 목록 가져오기 허용"
canImportUserLists: "리스트 목록 가져오기 허용"
_condition: _condition:
roleAssignedTo: "수동 역할에 이미 할당됨" roleAssignedTo: "수동 역할에 이미 할당됨"
isLocal: "로컬 사용자" isLocal: "로컬 사용자"
isRemote: "리모트 사용자" isRemote: "원격 사용자"
isCat: "고양이 사용자" isCat: "고양이 사용자"
isBot: "봇 사용자" isBot: "봇 사용자"
isSuspended: "정지된 사용자" isSuspended: "정지된 사용자"
@ -1949,10 +2000,11 @@ _sfx:
_soundSettings: _soundSettings:
driveFile: "드라이브에 있는 오디오를 사용" driveFile: "드라이브에 있는 오디오를 사용"
driveFileWarn: "드라이브에 있는 파일을 선택하세요." driveFileWarn: "드라이브에 있는 파일을 선택하세요."
driveFileTypeWarn: "이 파일은 지원되지 않습니다." driveFileTypeWarn: "이 파"
driveFileTypeWarnDescription: "오디오 파일을 선택하세요." driveFileTypeWarnDescription: "오디오 파일을 선택하세요."
driveFileDurationWarn: "오디오가 너무 깁니다" driveFileDurationWarn: "오디오가 너무 깁니다"
driveFileDurationWarnDescription: "긴 오디오로 설정할 경우 미스키 사용에 지장이 갈 수도 있습니다. 그래도 괜찮습니까?" driveFileDurationWarnDescription: "긴 오디오로 설정할 경우 미스키 사용에 지장이 갈 수도 있습니다. 그래도 괜찮습니까?"
driveFileError: "오디오를 불러올 수 없습니다. 설정을 바꿔주세요."
_ago: _ago:
future: "미래" future: "미래"
justNow: "방금 전" justNow: "방금 전"
@ -2209,6 +2261,9 @@ _profile:
changeBanner: "배너 이미지 변경" changeBanner: "배너 이미지 변경"
verifiedLinkDescription: "내용에 자신의 프로필로 향하는 링크가 포함된 페이지의 URL을 삽입하면 소유자 인증 마크가 표시됩니다." verifiedLinkDescription: "내용에 자신의 프로필로 향하는 링크가 포함된 페이지의 URL을 삽입하면 소유자 인증 마크가 표시됩니다."
avatarDecorationMax: "최대 {max}개까지 장식을 할 수 있습니다." avatarDecorationMax: "최대 {max}개까지 장식을 할 수 있습니다."
followedMessage: "팔로우 받았을 때 메시지"
followedMessageDescription: "팔로우 받았을 때 상대방에게 보여줄 단문 메시지를 설정할 수 있습니다."
followedMessageDescriptionForLockedAccount: "팔로우를 승인제로 한 경우, 팔로우 요청을 수락했을 때 보여줍니다."
_exportOrImport: _exportOrImport:
allNotes: "모든 노트" allNotes: "모든 노트"
favoritedNotes: "즐겨찾기한 노트" favoritedNotes: "즐겨찾기한 노트"
@ -2301,6 +2356,7 @@ _pages:
eyeCatchingImageSet: "아이캐치 이미지를 설정" eyeCatchingImageSet: "아이캐치 이미지를 설정"
eyeCatchingImageRemove: "아이캐치 이미지를 삭제" eyeCatchingImageRemove: "아이캐치 이미지를 삭제"
chooseBlock: "블록 추가" chooseBlock: "블록 추가"
enterSectionTitle: "섹션 타이틀을 입력하기"
selectType: "종류 선택" selectType: "종류 선택"
contentBlocks: "콘텐츠" contentBlocks: "콘텐츠"
inputBlocks: "입력" inputBlocks: "입력"
@ -2346,6 +2402,8 @@ _notification:
renotedBySomeUsers: "{n}명이 리노트했습니다" renotedBySomeUsers: "{n}명이 리노트했습니다"
followedBySomeUsers: "{n}명에게 팔로우됨" followedBySomeUsers: "{n}명에게 팔로우됨"
flushNotification: "알림 이력을 초기화" flushNotification: "알림 이력을 초기화"
exportOfXCompleted: "{x} 추출에 성공했습니다."
login: "로그인 알림이 있습니다"
_types: _types:
all: "전부" all: "전부"
note: "사용자의 새 글" note: "사용자의 새 글"
@ -2360,6 +2418,9 @@ _notification:
followRequestAccepted: "팔로우 요청이 승인되었을 때" followRequestAccepted: "팔로우 요청이 승인되었을 때"
roleAssigned: "역할이 부여 됨" roleAssigned: "역할이 부여 됨"
achievementEarned: "도전 과제 획득" achievementEarned: "도전 과제 획득"
exportCompleted: "추출을 성공함"
login: "로그인"
test: "알림 테스트"
app: "연동된 앱을 통한 알림" app: "연동된 앱을 통한 알림"
_actions: _actions:
followBack: "팔로우" followBack: "팔로우"
@ -2411,6 +2472,7 @@ _webhookSettings:
modifyWebhook: "Webhook 수정" modifyWebhook: "Webhook 수정"
name: "이름" name: "이름"
secret: "시크릿" secret: "시크릿"
trigger: "트리거"
active: "활성화" active: "활성화"
_events: _events:
follow: "누군가를 팔로우했을 때" follow: "누군가를 팔로우했을 때"
@ -2421,15 +2483,16 @@ _webhookSettings:
reaction: "누군가 내 노트에 리액션했을 때" reaction: "누군가 내 노트에 리액션했을 때"
mention: "누군가 나를 멘션했을 때" mention: "누군가 나를 멘션했을 때"
_systemEvents: _systemEvents:
abuseReport: "유저로부터 신고를 받았을 때" abuseReport: "유저"
abuseReportResolved: "받은 신고를 처리했을 때" abuseReportResolved: "받은 신고를 처리했을 때"
userCreated: "유저가 생성되었을 때" userCreated: "유저가 생성되었을 때"
deleteConfirm: "Webhook을 삭제할까요?" deleteConfirm: "Webhook을 삭제할까요?"
testRemarks: "스위치 오른쪽에 있는 버튼을 클릭하여 더미 데이터를 사용한 테스트용 웹 훅을 보낼 수 있습니다."
_abuseReport: _abuseReport:
_notificationRecipient: _notificationRecipient:
createRecipient: "신고 수신자 추가" createRecipient: "신고 수신자 추가"
modifyRecipient: "신고 수신자 편집" modifyRecipient: "신고 수신자 편집"
recipientType: "알림 수신 유형" recipientType: "알림 종류"
_recipientType: _recipientType:
mail: "이메일" mail: "이메일"
webhook: "Webhook" webhook: "Webhook"
@ -2437,7 +2500,7 @@ _abuseReport:
mail: "모더레이터 권한을 가진 사용자의 이메일 주소에 알림을 보냅니다 (신고를 받은 때에만)" mail: "모더레이터 권한을 가진 사용자의 이메일 주소에 알림을 보냅니다 (신고를 받은 때에만)"
webhook: "지정한 SystemWebhook에 알림을 보냅니다 (신고를 받은 때와 해결했을 때에 송신)" webhook: "지정한 SystemWebhook에 알림을 보냅니다 (신고를 받은 때와 해결했을 때에 송신)"
keywords: "키워드" keywords: "키워드"
notifiedUser: "신고 알림을 보낼 유저" notifiedUser: "알릴 사용자"
notifiedWebhook: "사용할 Webhook" notifiedWebhook: "사용할 Webhook"
deleteConfirm: "수신자를 삭제하시겠습니까?" deleteConfirm: "수신자를 삭제하시겠습니까?"
_moderationLogTypes: _moderationLogTypes:
@ -2468,6 +2531,8 @@ _moderationLogTypes:
markSensitiveDriveFile: "파일에 열람주의를 설정" markSensitiveDriveFile: "파일에 열람주의를 설정"
unmarkSensitiveDriveFile: "파일에 열람주의를 해제" unmarkSensitiveDriveFile: "파일에 열람주의를 해제"
resolveAbuseReport: "신고 처리" resolveAbuseReport: "신고 처리"
forwardAbuseReport: "신고 전달"
updateAbuseReportNote: "신고 조정 노트 갱신"
createInvitation: "초대 코드 생성" createInvitation: "초대 코드 생성"
createAd: "광고 생성" createAd: "광고 생성"
deleteAd: "광고 삭제" deleteAd: "광고 삭제"
@ -2483,6 +2548,10 @@ _moderationLogTypes:
createAbuseReportNotificationRecipient: "신고 알림 수신자 생성" createAbuseReportNotificationRecipient: "신고 알림 수신자 생성"
updateAbuseReportNotificationRecipient: "신고 알림 수신자 편집" updateAbuseReportNotificationRecipient: "신고 알림 수신자 편집"
deleteAbuseReportNotificationRecipient: "신고 알림 수신자 삭제" deleteAbuseReportNotificationRecipient: "신고 알림 수신자 삭제"
deleteAccount: "계정을 삭제"
deletePage: "페이지를 삭제"
deleteFlash: "Play를 삭제"
deleteGalleryPost: "갤러리 포스트를 삭제"
_fileViewer: _fileViewer:
title: "파일 상세" title: "파일 상세"
type: "파일 유형" type: "파일 유형"
@ -2603,7 +2672,7 @@ _urlPreviewSetting:
timeoutDescription: "미리보기를 로딩하는데 걸리는 시간이 정한 시간보다 오래 걸리는 경우, 미리보기를 생성하지 않습니다." timeoutDescription: "미리보기를 로딩하는데 걸리는 시간이 정한 시간보다 오래 걸리는 경우, 미리보기를 생성하지 않습니다."
maximumContentLength: "Content-Length의 최대치 (byte)" maximumContentLength: "Content-Length의 최대치 (byte)"
maximumContentLengthDescription: "Content-Length가 이 값을 넘어서면 미리보기를 생성하지 않습니다." maximumContentLengthDescription: "Content-Length가 이 값을 넘어서면 미리보기를 생성하지 않습니다."
requireContentLength: "Content-Length를 얻었을 때만 미리보기 만들기" requireContentLength: "Content-Length를 받아온 경우에만 "
requireContentLengthDescription: "상대 서버가 Content-Length를 되돌려주지 않는다면 미리보기를 만들지 않습니다." requireContentLengthDescription: "상대 서버가 Content-Length를 되돌려주지 않는다면 미리보기를 만들지 않습니다."
userAgent: "User-Agent" userAgent: "User-Agent"
userAgentDescription: "미리보기를 얻을 때 사용한 User-Agent를 설정합니다. 비어 있다면 기본값의 User-Agent를 사용합니다." userAgentDescription: "미리보기를 얻을 때 사용한 User-Agent를 설정합니다. 비어 있다면 기본값의 User-Agent를 사용합니다."
@ -2614,3 +2683,22 @@ _mediaControls:
pip: "화면 속 화면" pip: "화면 속 화면"
playbackRate: "재생 속도" playbackRate: "재생 속도"
loop: "반복 재생" loop: "반복 재생"
_contextMenu:
title: "컨텍스트 메뉴"
app: "애플리케이션"
appWithShift: "Shift 키로 애플리케이션"
native: "브라우저의 UI"
_embedCodeGen:
title: "임베디드 코드를 커스터마이즈"
header: "해더를 표시"
autoload: "자동으로 다음 코드를 실행 (비권장)"
maxHeight: "최대 높이"
maxHeightDescription: "최대 값을 무시하려면 0을 입력하세요. 위젯이 상하로 길어지는 것을 방지하려면, 임의의 값을 입력해 주세요."
maxHeightWarn: "높이 최대 값이 설정되어져 있지 않습니다(0). 의도적으로 설정 하지 않았다면 임의의 값을 설정해주세요."
previewIsNotActual: "미리보기로 표시할 수 있는 크기보다 큽니다. 실제로 넣은 코드의 표시가 다른 경우가 있습니다."
rounded: "외곽선을 둥글게 하기"
border: "외곽선에 테두리를 씌우기"
applyToPreview: "미리보기에 반영"
generateCode: "임베디드 코드를 만들기"
codeGenerated: "코드를 만들었습니다."
codeGeneratedDescription: "만들어진 코드를 웹 사이트에 붙여서 사용하세요."

View file

@ -456,6 +456,7 @@ _notification:
renote: "Renote" renote: "Renote"
quote: "ອ້າງອີງ" quote: "ອ້າງອີງ"
reaction: "Reaction" reaction: "Reaction"
login: "ເຂົ້າ​ສູ່​ລະ​ບົບ"
_actions: _actions:
reply: "ຕອບ​ກັບ" reply: "ຕອບ​ກັບ"
renote: "Renote" renote: "Renote"

View file

@ -427,7 +427,7 @@ windowMaximize: "Maximaliseren"
windowRestore: "Herstellen" windowRestore: "Herstellen"
loggedInAsBot: "Momenteel als bot ingelogd" loggedInAsBot: "Momenteel als bot ingelogd"
icon: "Avatar" icon: "Avatar"
replies: "Antwoorden" replies: "Antwoord"
renotes: "Herdelen" renotes: "Herdelen"
_delivery: _delivery:
stop: "Opgeschort" stop: "Opgeschort"
@ -486,6 +486,7 @@ _notification:
renote: "Herdelen" renote: "Herdelen"
quote: "Quote" quote: "Quote"
reaction: "Reacties" reaction: "Reacties"
login: "Inloggen"
_actions: _actions:
reply: "Antwoord" reply: "Antwoord"
renote: "Herdelen" renote: "Herdelen"

View file

@ -701,6 +701,7 @@ _notification:
renote: "Renotes" renote: "Renotes"
quote: "Sitater" quote: "Sitater"
reaction: "Reaksjoner" reaction: "Reaksjoner"
login: "Logg inn"
_actions: _actions:
reply: "Svar" reply: "Svar"
renote: "Renote" renote: "Renote"

View file

@ -81,7 +81,7 @@ exportRequested: "Zażądałeś eksportu. Może to zająć trochę czasu. Po zak
importRequested: "Zażądano importu. Może to zająć chwilę." importRequested: "Zażądano importu. Może to zająć chwilę."
lists: "Listy" lists: "Listy"
noLists: "Nie masz żadnych list" noLists: "Nie masz żadnych list"
note: "Wpis" note: "Utwórz wpis"
notes: "Wpisy" notes: "Wpisy"
following: "Obserwowani" following: "Obserwowani"
followers: "Obserwujący" followers: "Obserwujący"
@ -492,7 +492,6 @@ uiLanguage: "Język wyświetlania UI"
aboutX: "O {x}" aboutX: "O {x}"
emojiStyle: "Styl emoji" emojiStyle: "Styl emoji"
native: "Natywny" native: "Natywny"
disableDrawer: "Nie używaj menu w stylu szuflady"
showNoteActionsOnlyHover: "Pokazuj akcje notatek tylko po najechaniu myszką" showNoteActionsOnlyHover: "Pokazuj akcje notatek tylko po najechaniu myszką"
showReactionsCount: "Wyświetl liczbę reakcji na notatkę" showReactionsCount: "Wyświetl liczbę reakcji na notatkę"
noHistory: "Brak historii" noHistory: "Brak historii"
@ -690,10 +689,7 @@ abuseReported: "Twoje zgłoszenie zostało wysłane. Dziękujemy."
reporter: "Zgłaszający" reporter: "Zgłaszający"
reporteeOrigin: "Pochodzenie zgłoszonego" reporteeOrigin: "Pochodzenie zgłoszonego"
reporterOrigin: "Pochodzenie zgłaszającego" reporterOrigin: "Pochodzenie zgłaszającego"
forwardReport: "Przekaż zgłoszenie do innej instancji"
forwardReportIsAnonymous: "Zamiast twojego konta, anonimowe konto systemowe będzie wyświetlone jako zgłaszający na instancji zdalnej."
send: "Wyślij" send: "Wyślij"
abuseMarkAsResolved: "Oznacz zgłoszenie jako rozwiązane"
openInNewTab: "Otwórz w nowej karcie" openInNewTab: "Otwórz w nowej karcie"
openInSideView: "Otwórz w bocznym widoku" openInSideView: "Otwórz w bocznym widoku"
defaultNavigationBehaviour: "Domyślne zachowanie nawigacji" defaultNavigationBehaviour: "Domyślne zachowanie nawigacji"
@ -1016,8 +1012,8 @@ emailNotSupported: "Wysyłanie wiadomości E-mail nie jest obsługiwane na tym s
postToTheChannel: "Publikuj na kanale" postToTheChannel: "Publikuj na kanale"
youFollowing: "Śledzeni" youFollowing: "Śledzeni"
icon: "Awatar" icon: "Awatar"
replies: "Odpowiedzi" replies: "Odpowiedz"
renotes: "Udostępni" renotes: "Udostępnij"
sourceCode: "Kod źródłowy" sourceCode: "Kod źródłowy"
flip: "Odwróć" flip: "Odwróć"
lastNDays: "W ciągu ostatnich {n} dni" lastNDays: "W ciągu ostatnich {n} dni"
@ -1510,6 +1506,7 @@ _notification:
reaction: "Reakcja" reaction: "Reakcja"
receiveFollowRequest: "Otrzymano prośbę o możliwość obserwacji" receiveFollowRequest: "Otrzymano prośbę o możliwość obserwacji"
followRequestAccepted: "Przyjęto prośbę o możliwość obserwacji" followRequestAccepted: "Przyjęto prośbę o możliwość obserwacji"
login: "Zaloguj się"
app: "Powiadomienia z aplikacji" app: "Powiadomienia z aplikacji"
_actions: _actions:
followBack: "zaobserwował cię z powrotem" followBack: "zaobserwował cię z powrotem"

View file

@ -25,7 +25,7 @@ basicSettings: "Configurações básicas"
otherSettings: "Outras configurações" otherSettings: "Outras configurações"
openInWindow: "Abrir em um janela" openInWindow: "Abrir em um janela"
profile: "Perfil" profile: "Perfil"
timeline: "Linha do tempo" timeline: "Cronologia"
noAccountDescription: "Este usuário não tem uma descrição." noAccountDescription: "Este usuário não tem uma descrição."
login: "Iniciar sessão" login: "Iniciar sessão"
loggingIn: "Iniciando sessão…" loggingIn: "Iniciando sessão…"
@ -82,7 +82,7 @@ exportRequested: "A sua solicitação de exportação foi enviada. Isso pode lev
importRequested: "A sua solicitação de importação foi enviada. Isso pode levar algum tempo." importRequested: "A sua solicitação de importação foi enviada. Isso pode levar algum tempo."
lists: "Listas" lists: "Listas"
noLists: "Não possui nenhuma lista" noLists: "Não possui nenhuma lista"
note: "Post" note: "Publicar"
notes: "Posts" notes: "Posts"
following: "Seguindo" following: "Seguindo"
followers: "Seguidores" followers: "Seguidores"
@ -296,7 +296,7 @@ explore: "Explorar"
messageRead: "Lida" messageRead: "Lida"
noMoreHistory: "Não existe histórico anterior" noMoreHistory: "Não existe histórico anterior"
startMessaging: "Iniciar conversação" startMessaging: "Iniciar conversação"
nUsersRead: "{n} Pessoas leem" nUsersRead: "{n} pessoas leram"
agreeTo: "Eu concordo com {0}" agreeTo: "Eu concordo com {0}"
agree: "Concordar" agree: "Concordar"
agreeBelow: "Eu concordo com o seguinte" agreeBelow: "Eu concordo com o seguinte"
@ -312,7 +312,7 @@ birthday: "Aniversário"
yearsOld: "{age} anos" yearsOld: "{age} anos"
registeredDate: "Data de registro" registeredDate: "Data de registro"
location: "Localização" location: "Localização"
theme: "tema" theme: "Tema"
themeForLightMode: "Temas usados no modo de luz" themeForLightMode: "Temas usados no modo de luz"
themeForDarkMode: "Temas usados no modo escuro" themeForDarkMode: "Temas usados no modo escuro"
light: "Claro" light: "Claro"
@ -509,7 +509,6 @@ uiLanguage: "Idioma de exibição da interface "
aboutX: "Sobre {x}" aboutX: "Sobre {x}"
emojiStyle: "Estilo de emojis" emojiStyle: "Estilo de emojis"
native: "Nativo" native: "Nativo"
disableDrawer: "Não mostrar o menu em formato de gaveta"
showNoteActionsOnlyHover: "Exibir as ações da nota somente ao passar o cursor sobre ela" showNoteActionsOnlyHover: "Exibir as ações da nota somente ao passar o cursor sobre ela"
showReactionsCount: "Ver o número de reações nas notas" showReactionsCount: "Ver o número de reações nas notas"
noHistory: "Ainda não há histórico" noHistory: "Ainda não há histórico"
@ -708,10 +707,7 @@ abuseReported: "Denúncia enviada. Obrigado por sua ajuda."
reporter: "Denunciante" reporter: "Denunciante"
reporteeOrigin: "Origem da denúncia" reporteeOrigin: "Origem da denúncia"
reporterOrigin: "Origem do denunciante" reporterOrigin: "Origem do denunciante"
forwardReport: "Encaminhar a denúncia para o servidor remoto"
forwardReportIsAnonymous: "No servidor remoto, suas informações não serão visíveis, e você será apresentado como uma conta do sistema anônima."
send: "Enviar" send: "Enviar"
abuseMarkAsResolved: "Marcar denúncia como resolvida"
openInNewTab: "Abrir em nova aba" openInNewTab: "Abrir em nova aba"
openInSideView: "Abrir em visão lateral" openInSideView: "Abrir em visão lateral"
defaultNavigationBehaviour: "Navegação padrão" defaultNavigationBehaviour: "Navegação padrão"
@ -2377,6 +2373,7 @@ _notification:
followRequestAccepted: "Aceitou pedidos de seguidor" followRequestAccepted: "Aceitou pedidos de seguidor"
roleAssigned: "Cargo dado" roleAssigned: "Cargo dado"
achievementEarned: "Conquista desbloqueada" achievementEarned: "Conquista desbloqueada"
login: "Iniciar sessão"
app: "Notificações de aplicativos conectados" app: "Notificações de aplicativos conectados"
_actions: _actions:
followBack: "te seguiu de volta" followBack: "te seguiu de volta"

View file

@ -453,7 +453,6 @@ or: "Sau"
language: "Limbă" language: "Limbă"
uiLanguage: "Limba interfeței" uiLanguage: "Limba interfeței"
aboutX: "Despre {x}" aboutX: "Despre {x}"
disableDrawer: "Nu folosi meniuri în stil sertar"
noHistory: "Nu există istoric" noHistory: "Nu există istoric"
signinHistory: "Istoric autentificări" signinHistory: "Istoric autentificări"
doing: "Se procesează..." doing: "Se procesează..."
@ -626,10 +625,7 @@ abuseReported: "Raportul tău a fost trimis. Mulțumim."
reporter: "Raportorul" reporter: "Raportorul"
reporteeOrigin: "Originea raportatului" reporteeOrigin: "Originea raportatului"
reporterOrigin: "Originea raportorului" reporterOrigin: "Originea raportorului"
forwardReport: "Redirecționează raportul către instanța externă"
forwardReportIsAnonymous: "În locul contului tău, va fi afișat un cont anonim, de sistem, ca raportor către instanța externă."
send: "Trimite" send: "Trimite"
abuseMarkAsResolved: "Marchează raportul ca rezolvat"
openInNewTab: "Deschide în tab nou" openInNewTab: "Deschide în tab nou"
openInSideView: "Deschide în vedere laterală" openInSideView: "Deschide în vedere laterală"
defaultNavigationBehaviour: "Comportament de navigare implicit" defaultNavigationBehaviour: "Comportament de navigare implicit"
@ -649,7 +645,7 @@ searchByGoogle: "Caută"
file: "Fișiere" file: "Fișiere"
show: "Arată" show: "Arată"
icon: "Avatar" icon: "Avatar"
replies: "Răspunsuri" replies: "Răspunde"
renotes: "Re-notează" renotes: "Re-notează"
_delivery: _delivery:
stop: "Suspendat" stop: "Suspendat"
@ -715,6 +711,7 @@ _notification:
renote: "Re-notează" renote: "Re-notează"
quote: "Citează" quote: "Citează"
reaction: "Reacție" reaction: "Reacție"
login: "Autentifică-te"
_actions: _actions:
reply: "Răspunde" reply: "Răspunde"
renote: "Re-notează" renote: "Re-notează"

View file

@ -2,7 +2,7 @@
_lang_: "Русский" _lang_: "Русский"
headlineMisskey: "Сеть, сплетённая из заметок" headlineMisskey: "Сеть, сплетённая из заметок"
introMisskey: "Добро пожаловать! Misskey — это децентрализованный сервис микроблогов с открытым исходным кодом.\nПишите «заметки» — делитесь со всеми происходящим вокруг или рассказывайте о себе 📡\nСтавьте «реакции» — выражайте свои чувства и эмоции от заметок других 👍\nОткройте для себя новый мир 🚀" introMisskey: "Добро пожаловать! Misskey — это децентрализованный сервис микроблогов с открытым исходным кодом.\nПишите «заметки» — делитесь со всеми происходящим вокруг или рассказывайте о себе 📡\nСтавьте «реакции» — выражайте свои чувства и эмоции от заметок других 👍\nОткройте для себя новый мир 🚀"
poweredByMisskeyDescription: "{name} сервис на платформе с открытым исходным кодом <b>Misskey</b>, называемый инстансом Misskey." poweredByMisskeyDescription: "{name} сервис на платформе с открытым исходным кодом <b>Misskey</b>, называемый экземпляром Misskey."
monthAndDay: "{day}.{month}" monthAndDay: "{day}.{month}"
search: "Поиск" search: "Поиск"
notifications: "Уведомления" notifications: "Уведомления"
@ -10,15 +10,15 @@ username: "Имя пользователя"
password: "Пароль" password: "Пароль"
forgotPassword: "Забыли пароль?" forgotPassword: "Забыли пароль?"
fetchingAsApObject: "Приём с других сайтов" fetchingAsApObject: "Приём с других сайтов"
ok: "Окей" ok: "Подтвердить"
gotIt: "Ясно!" gotIt: "Ясно!"
cancel: "Отмена" cancel: "Отмена"
noThankYou: "Нет, спасибо" noThankYou: "Нет, спасибо"
enterUsername: "Введите имя пользователя" enterUsername: "Введите имя пользователя"
renotedBy: "{user} делится" renotedBy: "{user} репостнул(а)"
noNotes: "Нет ни одной заметки" noNotes: "Нет ни одной заметки"
noNotifications: "Нет уведомлений" noNotifications: "Нет уведомлений"
instance: "Инстанс" instance: "Экземпляр"
settings: "Настройки" settings: "Настройки"
notificationSettings: "Настройки уведомлений" notificationSettings: "Настройки уведомлений"
basicSettings: "Основные настройки" basicSettings: "Основные настройки"
@ -45,22 +45,24 @@ pin: "Закрепить в профиле"
unpin: "Открепить от профиля" unpin: "Открепить от профиля"
copyContent: "Скопировать содержимое" copyContent: "Скопировать содержимое"
copyLink: "Скопировать ссылку" copyLink: "Скопировать ссылку"
copyLinkRenote: "Скопировать ссылку на репост"
delete: "Удалить" delete: "Удалить"
deleteAndEdit: "Удалить и отредактировать" deleteAndEdit: "Удалить и отредактировать"
deleteAndEditConfirm: "Удалить эту заметку и создать отредактированную? Все реакции, ссылки и ответы на существующую будут будут потеряны." deleteAndEditConfirm: "Удалить этот пост и отредактировать заново? Все реакции, репосты и ответы на него также будут удалены."
addToList: "Добавить в список" addToList: "Добавить в список"
addToAntenna: "Добавить к антенне" addToAntenna: "Добавить к антенне"
sendMessage: "Отправить сообщение" sendMessage: "Отправить сообщение"
copyRSS: "Скопировать RSS" copyRSS: "Скопировать RSS"
copyUsername: "Скопировать имя пользователя" copyUsername: "Скопировать имя пользователя"
copyUserId: "Скопировать идентификатор пользователя" copyUserId: "Скопировать ID пользователя"
copyNoteId: "Скопировать идентификатор заметки" copyNoteId: "Скопировать ID поста"
copyFileId: "Скопировать ID файла" copyFileId: "Скопировать ID файла"
copyFolderId: "Скопировать ID папки" copyFolderId: "Скопировать ID папки"
copyProfileUrl: "Скопировать URL профиля " copyProfileUrl: "Скопировать ссылку на профиль"
searchUser: "Поиск людей" searchUser: "Поиск людей"
searchThisUsersNotes: "Искать по заметкам пользователя"
reply: "Ответ" reply: "Ответ"
loadMore: "Показать еще" loadMore: "Загрузить ещё"
showMore: "Показать ещё" showMore: "Показать ещё"
showLess: "Закрыть" showLess: "Закрыть"
youGotNewFollower: "Новый подписчик" youGotNewFollower: "Новый подписчик"
@ -107,11 +109,14 @@ enterEmoji: "Введите эмодзи"
renote: "Репост" renote: "Репост"
unrenote: "Отмена репоста" unrenote: "Отмена репоста"
renoted: "Репост совершён." renoted: "Репост совершён."
renotedToX: "Репостнуть в {name}."
cantRenote: "Это нельзя репостить." cantRenote: "Это нельзя репостить."
cantReRenote: "Невозможно репостить репост." cantReRenote: "Невозможно репостить репост."
quote: "Цитата" quote: "Цитата"
inChannelRenote: "В канале" inChannelRenote: "В канале"
inChannelQuote: "Заметки в канале" inChannelQuote: "Заметки в канале"
renoteToChannel: "Репостнуть в канал"
renoteToOtherChannel: "Репостнуть в другой канал"
pinnedNote: "Закреплённая заметка" pinnedNote: "Закреплённая заметка"
pinned: "Закрепить в профиле" pinned: "Закрепить в профиле"
you: "Вы" you: "Вы"
@ -150,6 +155,7 @@ editList: "Редактировать список"
selectChannel: "Выберите канал" selectChannel: "Выберите канал"
selectAntenna: "Выберите антенну" selectAntenna: "Выберите антенну"
editAntenna: "Редактировать антенну" editAntenna: "Редактировать антенну"
createAntenna: "Создать антенну"
selectWidget: "Выберите виджет" selectWidget: "Выберите виджет"
editWidgets: "Редактировать виджеты" editWidgets: "Редактировать виджеты"
editWidgetsExit: "Готово" editWidgetsExit: "Готово"
@ -157,11 +163,12 @@ customEmojis: "Собственные эмодзи"
emoji: "Эмодзи" emoji: "Эмодзи"
emojis: "Эмодзи" emojis: "Эмодзи"
emojiName: "Название эмодзи" emojiName: "Название эмодзи"
emojiUrl: "URL эмодзи" emojiUrl: "Ссылка на эмодзи"
addEmoji: "Добавить эмодзи" addEmoji: "Добавить эмодзи"
settingGuide: "Рекомендуемые настройки" settingGuide: "Рекомендуемые настройки"
cacheRemoteFiles: "Кешировать внешние файлы" cacheRemoteFiles: "Кешировать внешние файлы"
cacheRemoteFilesDescription: "Когда эта настройка отключена, файлы с других сайтов будут загружаться прямо оттуда. Это сэкономит место на сервере, но увеличит трафик, так как не будут создаваться эскизы." cacheRemoteFilesDescription: "Когда эта настройка отключена, файлы с других сайтов будут загружаться прямо оттуда. Это сэкономит место на сервере, но увеличит трафик, так как не будут создаваться эскизы."
youCanCleanRemoteFilesCache: "Вы можете очистить кэш, нажав на кнопку 🗑️ в меню управления файлами."
cacheRemoteSensitiveFiles: "Кэшировать внешние файлы «не для всех»" cacheRemoteSensitiveFiles: "Кэшировать внешние файлы «не для всех»"
cacheRemoteSensitiveFilesDescription: "Если отключено, файлы «не для всех» загружаются непосредственно с удалённых серверов, не кэшируясь." cacheRemoteSensitiveFilesDescription: "Если отключено, файлы «не для всех» загружаются непосредственно с удалённых серверов, не кэшируясь."
flagAsBot: "Аккаунт бота" flagAsBot: "Аккаунт бота"
@ -175,6 +182,10 @@ addAccount: "Добавить учётную запись"
reloadAccountsList: "Обновить список учётных записей" reloadAccountsList: "Обновить список учётных записей"
loginFailed: "Неудачная попытка входа" loginFailed: "Неудачная попытка входа"
showOnRemote: "Перейти к оригиналу на сайт" showOnRemote: "Перейти к оригиналу на сайт"
continueOnRemote: "Продолжить на удалённом сервере"
chooseServerOnMisskeyHub: "Выбрать сервер с Misskey Hub"
specifyServerHost: "Укажите сервер напрямую"
inputHostName: "Введите домен"
general: "Общее" general: "Общее"
wallpaper: "Обои" wallpaper: "Обои"
setWallpaper: "Установить обои" setWallpaper: "Установить обои"
@ -185,6 +196,7 @@ followConfirm: "Подписаться на {name}?"
proxyAccount: "Учётная запись прокси" proxyAccount: "Учётная запись прокси"
proxyAccountDescription: "Учетная запись прокси предназначена служить подписчиком на пользователей с других сайтов. Например, если пользователь добавит кого-то с другого сайта а список, деятельность того не отобразится, пока никто с этого же сайта не подписан на него. Чтобы это стало возможным, на него подписывается прокси." proxyAccountDescription: "Учетная запись прокси предназначена служить подписчиком на пользователей с других сайтов. Например, если пользователь добавит кого-то с другого сайта а список, деятельность того не отобразится, пока никто с этого же сайта не подписан на него. Чтобы это стало возможным, на него подписывается прокси."
host: "Хост" host: "Хост"
selectSelf: "Выбрать себя"
selectUser: "Выберите пользователя" selectUser: "Выберите пользователя"
recipient: "Кому" recipient: "Кому"
annotation: "Описание" annotation: "Описание"
@ -199,6 +211,7 @@ perHour: "По часам"
perDay: "По дням" perDay: "По дням"
stopActivityDelivery: "Остановить отправку обновлений активности" stopActivityDelivery: "Остановить отправку обновлений активности"
blockThisInstance: "Блокировать этот инстанс" blockThisInstance: "Блокировать этот инстанс"
silenceThisInstance: "Заглушить этот инстанс"
operations: "Операции" operations: "Операции"
software: "Программы" software: "Программы"
version: "Версия" version: "Версия"
@ -218,6 +231,7 @@ clearCachedFiles: "Очистить кэш"
clearCachedFilesConfirm: "Удалить все закэшированные файлы с других сайтов?" clearCachedFilesConfirm: "Удалить все закэшированные файлы с других сайтов?"
blockedInstances: "Заблокированные инстансы" blockedInstances: "Заблокированные инстансы"
blockedInstancesDescription: "Введите список инстансов, которые хотите заблокировать. Они больше не смогут обмениваться с вашим инстансом." blockedInstancesDescription: "Введите список инстансов, которые хотите заблокировать. Они больше не смогут обмениваться с вашим инстансом."
silencedInstances: "Заглушённые инстансы"
muteAndBlock: "Скрытие и блокировка" muteAndBlock: "Скрытие и блокировка"
mutedUsers: "Скрытые пользователи" mutedUsers: "Скрытые пользователи"
blockedUsers: "Заблокированные пользователи" blockedUsers: "Заблокированные пользователи"
@ -236,7 +250,7 @@ noJobs: "Нет заданий"
federating: "Федерируется" federating: "Федерируется"
blocked: "Заблокировано" blocked: "Заблокировано"
suspended: "Заморожено" suspended: "Заморожено"
all: "Всё" all: "Все"
subscribing: "Подписка" subscribing: "Подписка"
publishing: "Публикация" publishing: "Публикация"
notResponding: "Нет ответа" notResponding: "Нет ответа"
@ -268,7 +282,7 @@ messaging: "Сообщения"
upload: "Загрузить" upload: "Загрузить"
keepOriginalUploading: "Сохранить исходное изображение" keepOriginalUploading: "Сохранить исходное изображение"
keepOriginalUploadingDescription: "Сохраняет исходную версию при загрузке изображений. Если выключить, то при загрузке браузер генерирует изображение для публикации." keepOriginalUploadingDescription: "Сохраняет исходную версию при загрузке изображений. Если выключить, то при загрузке браузер генерирует изображение для публикации."
fromDrive: "С «диска»" fromDrive: "С Диска"
fromUrl: "По ссылке" fromUrl: "По ссылке"
uploadFromUrl: "Загрузить по ссылке" uploadFromUrl: "Загрузить по ссылке"
uploadFromUrlDescription: "Ссылка на файл, который хотите загрузить" uploadFromUrlDescription: "Ссылка на файл, который хотите загрузить"
@ -308,6 +322,7 @@ selectFile: "Выберите файл"
selectFiles: "Выберите файлы" selectFiles: "Выберите файлы"
selectFolder: "Выберите папку" selectFolder: "Выберите папку"
selectFolders: "Выберите папки" selectFolders: "Выберите папки"
fileNotSelected: "Файл не выбран"
renameFile: "Переименовать файл" renameFile: "Переименовать файл"
folderName: "Имя папки" folderName: "Имя папки"
createFolder: "Создать папку" createFolder: "Создать папку"
@ -359,8 +374,8 @@ disablingTimelinesInfo: "У администраторов и модератор
registration: "Регистрация" registration: "Регистрация"
enableRegistration: "Разрешить регистрацию" enableRegistration: "Разрешить регистрацию"
invite: "Пригласить" invite: "Пригласить"
driveCapacityPerLocalAccount: "Объём диска на одного локального пользователя" driveCapacityPerLocalAccount: "Объём Диска на одного локального пользователя"
driveCapacityPerRemoteAccount: "Объём диска на одного пользователя с другого сайта" driveCapacityPerRemoteAccount: "Объём Диска на одного пользователя с другого экземпляра"
inMb: "В мегабайтах" inMb: "В мегабайтах"
bannerUrl: "Ссылка на изображение в шапке" bannerUrl: "Ссылка на изображение в шапке"
backgroundImageUrl: "Ссылка на фоновое изображение" backgroundImageUrl: "Ссылка на фоновое изображение"
@ -379,6 +394,7 @@ mcaptcha: "mCaptcha"
enableMcaptcha: "Включить mCaptcha" enableMcaptcha: "Включить mCaptcha"
mcaptchaSiteKey: "Ключ сайта" mcaptchaSiteKey: "Ключ сайта"
mcaptchaSecretKey: "Секретный ключ" mcaptchaSecretKey: "Секретный ключ"
mcaptchaInstanceUrl: "Ссылка на сервер mCaptcha"
recaptcha: "reCAPTCHA" recaptcha: "reCAPTCHA"
enableRecaptcha: "Включить reCAPTCHA" enableRecaptcha: "Включить reCAPTCHA"
recaptchaSiteKey: "Ключ сайта" recaptchaSiteKey: "Ключ сайта"
@ -393,7 +409,8 @@ manageAntennas: "Настройки антенн"
name: "Название" name: "Название"
antennaSource: "Источник антенны" antennaSource: "Источник антенны"
antennaKeywords: "Ключевые слова" antennaKeywords: "Ключевые слова"
antennaExcludeKeywords: "Исключения" antennaExcludeKeywords: "Чёрный список слов"
antennaExcludeBots: "Исключать ботов"
antennaKeywordsDescription: "Пишите слова через пробел в одной строке, чтобы ловить их появление вместе; на отдельных строках располагайте слова, или группы слов, чтобы ловить любые из них." antennaKeywordsDescription: "Пишите слова через пробел в одной строке, чтобы ловить их появление вместе; на отдельных строках располагайте слова, или группы слов, чтобы ловить любые из них."
notifyAntenna: "Уведомлять о новых заметках" notifyAntenna: "Уведомлять о новых заметках"
withFileAntenna: "Только заметки с вложениями" withFileAntenna: "Только заметки с вложениями"
@ -426,6 +443,7 @@ totp: "Приложение-аутентификатор"
totpDescription: "Описание приложения-аутентификатора" totpDescription: "Описание приложения-аутентификатора"
moderator: "Модератор" moderator: "Модератор"
moderation: "Модерация" moderation: "Модерация"
moderationLogs: "Журнал модерации"
nUsersMentioned: "Упомянуло пользователей: {n}" nUsersMentioned: "Упомянуло пользователей: {n}"
securityKeyAndPasskey: "Ключ безопасности и парольная фраза" securityKeyAndPasskey: "Ключ безопасности и парольная фраза"
securityKey: "Ключ безопасности" securityKey: "Ключ безопасности"
@ -458,10 +476,12 @@ retype: "Введите ещё раз"
noteOf: "Что пишет {user}" noteOf: "Что пишет {user}"
quoteAttached: "Цитата" quoteAttached: "Цитата"
quoteQuestion: "Хотите добавить цитату?" quoteQuestion: "Хотите добавить цитату?"
attachAsFileQuestion: "Текста в буфере обмена слишком много. Прикрепить как текстовый файл?"
noMessagesYet: "Пока ни одного сообщения" noMessagesYet: "Пока ни одного сообщения"
newMessageExists: "Новое сообщение" newMessageExists: "Новое сообщение"
onlyOneFileCanBeAttached: "К сообщению можно прикрепить только один файл" onlyOneFileCanBeAttached: "К сообщению можно прикрепить только один файл"
signinRequired: "Пожалуйста, войдите" signinRequired: "Пожалуйста, войдите"
signinOrContinueOnRemote: "Чтобы продолжить, вам необходимо войти в аккаунт на своём сервере или зарегистрироваться / войти в аккаунт на этом."
invitations: "Приглашения" invitations: "Приглашения"
invitationCode: "Код приглашения" invitationCode: "Код приглашения"
checking: "Проверка" checking: "Проверка"
@ -471,7 +491,7 @@ usernameInvalidFormat: "Можно использовать только лат
tooShort: "Слишком короткий" tooShort: "Слишком короткий"
tooLong: "Слишком длинный" tooLong: "Слишком длинный"
weakPassword: "Слабый пароль" weakPassword: "Слабый пароль"
normalPassword: "Годный пароль" normalPassword: "Хороший пароль"
strongPassword: "Надёжный пароль" strongPassword: "Надёжный пароль"
passwordMatched: "Совпали" passwordMatched: "Совпали"
passwordNotMatched: "Не совпадают" passwordNotMatched: "Не совпадают"
@ -483,8 +503,8 @@ uiLanguage: "Язык интерфейса"
aboutX: "Описание {x}" aboutX: "Описание {x}"
emojiStyle: "Стиль эмодзи" emojiStyle: "Стиль эмодзи"
native: "Системные" native: "Системные"
disableDrawer: "Не использовать выдвижные меню"
showNoteActionsOnlyHover: "Показывать кнопки у заметок только при наведении" showNoteActionsOnlyHover: "Показывать кнопки у заметок только при наведении"
showReactionsCount: "Видеть количество реакций на заметках"
noHistory: "История пока пуста" noHistory: "История пока пуста"
signinHistory: "Журнал посещений" signinHistory: "Журнал посещений"
enableAdvancedMfm: "Включить расширенный MFM" enableAdvancedMfm: "Включить расширенный MFM"
@ -547,7 +567,7 @@ popout: "Развернуть"
volume: "Громкость" volume: "Громкость"
masterVolume: "Основная регулировка громкости" masterVolume: "Основная регулировка громкости"
notUseSound: "Выключить звук" notUseSound: "Выключить звук"
useSoundOnlyWhenActive: "Использовать звук, когда Misskey активен." useSoundOnlyWhenActive: "Воспроизводить звук только когда Misskey активен."
details: "Подробнее" details: "Подробнее"
chooseEmoji: "Выберите эмодзи" chooseEmoji: "Выберите эмодзи"
unableToProcess: "Не удаётся завершить операцию" unableToProcess: "Не удаётся завершить операцию"
@ -601,7 +621,7 @@ poll: "Опрос"
useCw: "Скрывать содержимое под предупреждением" useCw: "Скрывать содержимое под предупреждением"
enablePlayer: "Включить проигрыватель" enablePlayer: "Включить проигрыватель"
disablePlayer: "Выключить проигрыватель" disablePlayer: "Выключить проигрыватель"
expandTweet: "Развернуть твит" expandTweet: "Развернуть заметку"
themeEditor: "Редактор темы оформления" themeEditor: "Редактор темы оформления"
description: "Описание" description: "Описание"
describeFile: "Добавить подпись" describeFile: "Добавить подпись"
@ -613,7 +633,7 @@ plugins: "Расширения"
preferencesBackups: "Резервная копия" preferencesBackups: "Резервная копия"
deck: "Пульт" deck: "Пульт"
undeck: "Покинуть пульт" undeck: "Покинуть пульт"
useBlurEffectForModal: "Размывка под формой поверх всего" useBlurEffectForModal: "Размытие за формой ввода заметки"
useFullReactionPicker: "Полнофункциональный выбор реакций" useFullReactionPicker: "Полнофункциональный выбор реакций"
width: "Ширина" width: "Ширина"
height: "Высота" height: "Высота"
@ -644,7 +664,7 @@ smtpSecure: "Использовать SSL/TLS для SMTP-соединений"
smtpSecureInfo: "Выключите при использовании STARTTLS." smtpSecureInfo: "Выключите при использовании STARTTLS."
testEmail: "Проверка доставки электронной почты" testEmail: "Проверка доставки электронной почты"
wordMute: "Скрытие слов" wordMute: "Скрытие слов"
hardWordMute: "" hardWordMute: "Строгое скрытие слов"
regexpError: "Ошибка в регулярном выражении" regexpError: "Ошибка в регулярном выражении"
regexpErrorDescription: "В списке {tab} скрытых слов, в строке {line} обнаружена синтаксическая ошибка:" regexpErrorDescription: "В списке {tab} скрытых слов, в строке {line} обнаружена синтаксическая ошибка:"
instanceMute: "Глушение инстансов" instanceMute: "Глушение инстансов"
@ -680,10 +700,7 @@ abuseReported: "Жалоба отправлена. Большое спасибо
reporter: "Сообщивший" reporter: "Сообщивший"
reporteeOrigin: "О ком сообщено" reporteeOrigin: "О ком сообщено"
reporterOrigin: "Кто сообщил" reporterOrigin: "Кто сообщил"
forwardReport: "Отправить жалобу на инстанс автора."
forwardReportIsAnonymous: "Жалоба на удалённый инстанс будет отправлена анонимно. Вместо ваших данных у получателя будет отображена системная учётная запись."
send: "Отправить" send: "Отправить"
abuseMarkAsResolved: "Отметить жалобу как решённую"
openInNewTab: "Открыть в новой вкладке" openInNewTab: "Открыть в новой вкладке"
openInSideView: "Открывать в боковой колонке" openInSideView: "Открывать в боковой колонке"
defaultNavigationBehaviour: "Поведение навигации по умолчанию" defaultNavigationBehaviour: "Поведение навигации по умолчанию"
@ -726,6 +743,7 @@ lockedAccountInfo: "Даже если вы вручную подтверждае
alwaysMarkSensitive: "Отмечать файлы как «содержимое не для всех» по умолчанию" alwaysMarkSensitive: "Отмечать файлы как «содержимое не для всех» по умолчанию"
loadRawImages: "Сразу показывать изображения в полном размере" loadRawImages: "Сразу показывать изображения в полном размере"
disableShowingAnimatedImages: "Не проигрывать анимацию" disableShowingAnimatedImages: "Не проигрывать анимацию"
highlightSensitiveMedia: "Выделять содержимое не для всех"
verificationEmailSent: "Вам отправлено письмо для подтверждения. Пройдите, пожалуйста, по ссылке из письма, чтобы завершить проверку." verificationEmailSent: "Вам отправлено письмо для подтверждения. Пройдите, пожалуйста, по ссылке из письма, чтобы завершить проверку."
notSet: "Не настроено" notSet: "Не настроено"
emailVerified: "Адрес электронной почты подтверждён." emailVerified: "Адрес электронной почты подтверждён."
@ -743,7 +761,7 @@ makeExplorable: "Опубликовать профиль в «Обзоре»."
makeExplorableDescription: "Если выключить, ваш профиль не будет показан в разделе «Обзор»." makeExplorableDescription: "Если выключить, ваш профиль не будет показан в разделе «Обзор»."
showGapBetweenNotesInTimeline: "Показывать разделитель между заметками в ленте" showGapBetweenNotesInTimeline: "Показывать разделитель между заметками в ленте"
duplicate: "Дубликат" duplicate: "Дубликат"
left: "Влево" left: "Слева"
center: "По центру" center: "По центру"
wide: "Толстый" wide: "Толстый"
narrow: "Тонкий" narrow: "Тонкий"
@ -822,7 +840,7 @@ noMaintainerInformationWarning: "Не заполнены сведения об
noBotProtectionWarning: "Ботозащита не настроена" noBotProtectionWarning: "Ботозащита не настроена"
configure: "Настроить" configure: "Настроить"
postToGallery: "Опубликовать в галерею" postToGallery: "Опубликовать в галерею"
postToHashtag: "Написать заметку с этим хэштегом" postToHashtag: "Написать заметку с этим хештегом"
gallery: "Галерея" gallery: "Галерея"
recentPosts: "Недавние публикации" recentPosts: "Недавние публикации"
popularPosts: "Популярные публикации" popularPosts: "Популярные публикации"
@ -839,13 +857,13 @@ emailNotConfiguredWarning: "Не указан адрес электронной
ratio: "Соотношение" ratio: "Соотношение"
previewNoteText: "Предварительный просмотр" previewNoteText: "Предварительный просмотр"
customCss: "Индивидуальный CSS" customCss: "Индивидуальный CSS"
customCssWarn: "Используйте эту настройку только если знаете, что делаете. Ошибки здесь чреваты тем, что сайт перестанет нормально работать у вас." customCssWarn: "Используйте эту настройку только если знаете, что делаете. Ошибки здесь чреваты тем, что у вас перестанет нормально работать сайт."
global: "Всеобщая" global: "Всеобщая"
squareAvatars: "Квадратные аватарки" squareAvatars: "Квадратные аватарки"
sent: "Отправить" sent: "Отправить"
received: "Получено" received: "Получено"
searchResult: "Результаты поиска" searchResult: "Результаты поиска"
hashtags: "Хэштег" hashtags: "Хештеги"
troubleshooting: "Разрешение проблем" troubleshooting: "Разрешение проблем"
useBlurEffect: "Размытие в интерфейсе" useBlurEffect: "Размытие в интерфейсе"
learnMore: "Подробнее" learnMore: "Подробнее"
@ -857,7 +875,7 @@ accountDeletionInProgress: "В настоящее время выполняет
usernameInfo: "Имя, которое отличает вашу учетную запись от других на этом сервере. Вы можете использовать алфавит (a~z, A~Z), цифры (0~9) или символы подчеркивания (_). Имена пользователей не могут быть изменены позже." usernameInfo: "Имя, которое отличает вашу учетную запись от других на этом сервере. Вы можете использовать алфавит (a~z, A~Z), цифры (0~9) или символы подчеркивания (_). Имена пользователей не могут быть изменены позже."
aiChanMode: "Режим Ай" aiChanMode: "Режим Ай"
devMode: "Режим разработчика" devMode: "Режим разработчика"
keepCw: "Сохраняйте Предупреждения о содержимом" keepCw: "Сохраняйте предупреждения о содержимом"
pubSub: "Учётные записи Pub/Sub" pubSub: "Учётные записи Pub/Sub"
lastCommunication: "Последнее сообщение" lastCommunication: "Последнее сообщение"
resolved: "Решено" resolved: "Решено"
@ -878,6 +896,8 @@ makeReactionsPublicDescription: "Список сделанных вами реа
classic: "Классика" classic: "Классика"
muteThread: "Скрыть цепочку" muteThread: "Скрыть цепочку"
unmuteThread: "Отменить сокрытие цепочки" unmuteThread: "Отменить сокрытие цепочки"
followingVisibility: "Видимость подписок"
followersVisibility: "Видимость подписчиков"
continueThread: "Показать следующие ответы" continueThread: "Показать следующие ответы"
deleteAccountConfirm: "Учётная запись будет безвозвратно удалена. Подтверждаете?" deleteAccountConfirm: "Учётная запись будет безвозвратно удалена. Подтверждаете?"
incorrectPassword: "Пароль неверен." incorrectPassword: "Пароль неверен."
@ -987,6 +1007,7 @@ assign: "Назначить"
unassign: "Отменить назначение" unassign: "Отменить назначение"
color: "Цвет" color: "Цвет"
manageCustomEmojis: "Управлять пользовательскими эмодзи" manageCustomEmojis: "Управлять пользовательскими эмодзи"
manageAvatarDecorations: "Управление украшениями аватара"
youCannotCreateAnymore: "Вы достигли лимита создания." youCannotCreateAnymore: "Вы достигли лимита создания."
cannotPerformTemporary: "Временно недоступен" cannotPerformTemporary: "Временно недоступен"
cannotPerformTemporaryDescription: "Это действие временно невозможно выполнить из-за превышения лимита выполнения." cannotPerformTemporaryDescription: "Это действие временно невозможно выполнить из-за превышения лимита выполнения."
@ -1003,7 +1024,8 @@ thisPostMayBeAnnoying: "Это сообщение может быть непри
thisPostMayBeAnnoyingHome: "Этот пост может быть отправлен на главную" thisPostMayBeAnnoyingHome: "Этот пост может быть отправлен на главную"
thisPostMayBeAnnoyingCancel: "Этот пост не может быть отменен." thisPostMayBeAnnoyingCancel: "Этот пост не может быть отменен."
thisPostMayBeAnnoyingIgnore: "Этот пост может быть проигнорирован " thisPostMayBeAnnoyingIgnore: "Этот пост может быть проигнорирован "
collapseRenotes: "Свернуть репосты" collapseRenotes: "Сворачивать увиденные репосты"
collapseRenotesDescription: "Сворачивать посты с которыми вы взаимодействовали."
internalServerError: "Внутренняя ошибка сервера" internalServerError: "Внутренняя ошибка сервера"
internalServerErrorDescription: "Внутри сервера произошла непредвиденная ошибка." internalServerErrorDescription: "Внутри сервера произошла непредвиденная ошибка."
copyErrorInfo: "Скопировать код ошибки" copyErrorInfo: "Скопировать код ошибки"
@ -1027,7 +1049,10 @@ resetPasswordConfirm: "Сбросить пароль?"
sensitiveWords: "Чувствительные слова" sensitiveWords: "Чувствительные слова"
sensitiveWordsDescription: "Установите общедоступный диапазон заметки, содержащей заданное слово, на домашний. Можно сделать несколько настроек, разделив их переносами строк." sensitiveWordsDescription: "Установите общедоступный диапазон заметки, содержащей заданное слово, на домашний. Можно сделать несколько настроек, разделив их переносами строк."
sensitiveWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение." sensitiveWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение."
prohibitedWords: "Запрещённые слова"
prohibitedWordsDescription: "Включает вывод ошибки при попытке опубликовать пост, содержащий указанное слово/набор слов.\nМножество слов может быть указано, разделяемые новой строкой."
prohibitedWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение." prohibitedWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение."
hiddenTags: "Скрытые хештеги"
notesSearchNotAvailable: "Поиск заметок недоступен" notesSearchNotAvailable: "Поиск заметок недоступен"
license: "Лицензия" license: "Лицензия"
unfavoriteConfirm: "Удалить избранное?" unfavoriteConfirm: "Удалить избранное?"
@ -1038,9 +1063,14 @@ retryAllQueuesConfirmTitle: "Хотите попробовать ещё раз?"
retryAllQueuesConfirmText: "Нагрузка на сервер может увеличиться" retryAllQueuesConfirmText: "Нагрузка на сервер может увеличиться"
enableChartsForRemoteUser: "Создание диаграмм для удалённых пользователей" enableChartsForRemoteUser: "Создание диаграмм для удалённых пользователей"
enableChartsForFederatedInstances: "Создание диаграмм для удалённых серверов" enableChartsForFederatedInstances: "Создание диаграмм для удалённых серверов"
showClipButtonInNoteFooter: "Показать кнопку добавления в подборку в меню действий с заметкой"
reactionsDisplaySize: "Размер реакций"
limitWidthOfReaction: "Ограничить максимальную ширину реакций и отображать их в уменьшенном размере."
noteIdOrUrl: "ID или ссылка на заметку" noteIdOrUrl: "ID или ссылка на заметку"
video: "Видео" video: "Видео"
videos: "Видео" videos: "Видео"
audio: "Звук"
audioFiles: "Звуковые файлы"
dataSaver: "Экономия трафика" dataSaver: "Экономия трафика"
accountMigration: "Перенос учётной записи" accountMigration: "Перенос учётной записи"
accountMoved: "Учётная запись перенесена" accountMoved: "Учётная запись перенесена"
@ -1052,12 +1082,13 @@ editMemo: "Изменить памятку"
reactionsList: "Список реакций" reactionsList: "Список реакций"
renotesList: "Репосты" renotesList: "Репосты"
notificationDisplay: "Отображение уведомлений" notificationDisplay: "Отображение уведомлений"
leftTop: "Влево вверх" leftTop: "Слева вверху"
rightTop: "Вправо вверх" rightTop: "Справа сверху"
leftBottom: "Влево вниз" leftBottom: "Слева внизу"
rightBottom: "Вправо вниз" rightBottom: "Справа внизу"
vertical: "Вертикальная" stackAxis: "Положение уведомлений"
horizontal: "Сбоку" vertical: "Вертикально"
horizontal: "Горизонтально"
position: "Позиция" position: "Позиция"
serverRules: "Правила сервера" serverRules: "Правила сервера"
pleaseConfirmBelowBeforeSignup: "Для регистрации на данном сервере, необходимо согласится с нижеследующими положениями." pleaseConfirmBelowBeforeSignup: "Для регистрации на данном сервере, необходимо согласится с нижеследующими положениями."
@ -1069,57 +1100,114 @@ createNoteFromTheFile: "Создать заметку из этого файла
archive: "Архив" archive: "Архив"
channelArchiveConfirmTitle: "Переместить {name} в архив?" channelArchiveConfirmTitle: "Переместить {name} в архив?"
channelArchiveConfirmDescription: "Архивированные каналы перестанут отображаться в списке каналов или результатах поиска. В них также нельзя будет добавлять новые записи." channelArchiveConfirmDescription: "Архивированные каналы перестанут отображаться в списке каналов или результатах поиска. В них также нельзя будет добавлять новые записи."
thisChannelArchived: "Этот канал находится в архиве."
displayOfNote: "Отображение заметок" displayOfNote: "Отображение заметок"
initialAccountSetting: "Настройка профиля" initialAccountSetting: "Настройка профиля"
youFollowing: "Подписки" youFollowing: "Подписки"
preventAiLearning: "Отказаться от использования в машинном обучении (Генеративный ИИ)" preventAiLearning: "Отказаться от использования в машинном обучении (Генеративный ИИ)"
preventAiLearningDescription: "Запросить краулеров не использовать опубликованный текст или изображения и т.д. для машинного обучения (Прогнозирующий / Генеративный ИИ) датасетов. Это достигается путём добавления \"noai\" HTTP-заголовка в ответ на соответствующий контент. Полного предотвращения через этот заголовок не избежать, так как он может быть просто проигнорирован."
options: "Настройки ролей" options: "Настройки ролей"
specifyUser: "Указанный пользователь" specifyUser: "Указанный пользователь"
openTagPageConfirm: "Открыть страницу этого хештега?"
specifyHost: "Указать сайт"
failedToPreviewUrl: "Предварительный просмотр недоступен" failedToPreviewUrl: "Предварительный просмотр недоступен"
update: "Обновить" update: "Обновить"
rolesThatCanBeUsedThisEmojiAsReaction: "Роли тех, кому можно использовать эти эмодзи как реакцию" rolesThatCanBeUsedThisEmojiAsReaction: "Роли тех, кому можно использовать эти эмодзи как реакцию"
rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "Если здесь ничего не указать, в качестве реакции эту эмодзи сможет использовать каждый." rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "Если здесь ничего не указать, в качестве реакции эту эмодзи сможет использовать каждый."
rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Эти роли должны быть общедоступными."
cancelReactionConfirm: "Вы действительно хотите удалить свою реакцию?"
later: "Позже" later: "Позже"
goToMisskey: "К Misskey" goToMisskey: "К Misskey"
additionalEmojiDictionary: "Дополнительные словари эмодзи" additionalEmojiDictionary: "Дополнительные словари эмодзи"
installed: "Установлено" installed: "Установлено"
branding: "Бренд" branding: "Бренд"
enableServerMachineStats: "Опубликовать характеристики сервера"
enableIdenticonGeneration: "Включить генерацию иконки пользователя" enableIdenticonGeneration: "Включить генерацию иконки пользователя"
turnOffToImprovePerformance: "Отключение этого параметра может повысить производительность." turnOffToImprovePerformance: "Отключение этого параметра может повысить производительность."
createInviteCode: "Создать код приглашения"
createCount: "Количество приглашений"
expirationDate: "Дата истечения" expirationDate: "Дата истечения"
unused: "Неиспользуемый" noExpirationDate: "Бессрочно"
unused: "Неиспользованное"
used: "Использован"
expired: "Срок действия приглашения истёк" expired: "Срок действия приглашения истёк"
doYouAgree: "Согласны?" doYouAgree: "Согласны?"
icon: "Аватар" icon: "Аватар"
replies: "Ответы" replies: "Ответы"
renotes: "Репост" renotes: "Репост"
loadReplies: "Показать ответы" loadReplies: "Показать ответы"
pinnedList: "Закреплённый список"
keepScreenOn: "Держать экран включённым"
showRenotes: "Показывать репосты"
mutualFollow: "Взаимные подписки"
followingOrFollower: "Подписки или подписчики"
fileAttachedOnly: "Только заметки с файлами"
showRepliesToOthersInTimeline: "Показывать ответы в ленте"
showRepliesToOthersInTimelineAll: "Показывать в ленте ответы пользователей, на которых вы подписаны"
hideRepliesToOthersInTimelineAll: "Скрывать в ленте ответы пользователей, на которых вы подписаны"
sourceCode: "Исходный код" sourceCode: "Исходный код"
sourceCodeIsNotYetProvided: "Исходный код пока не доступен. Свяжитесь с администратором, чтобы исправить эту проблему."
repositoryUrl: "Ссылка на репозиторий"
repositoryUrlDescription: "Если вы используете Misskey как есть (без изменений в исходном коде), введите https://github.com/misskey-dev/misskey"
privacyPolicy: "Политика Конфиденциальности"
privacyPolicyUrl: "Ссылка на Политику Конфиденциальности"
attach: "Прикрепить"
angle: "Угол"
flip: "Переворот" flip: "Переворот"
disableStreamingTimeline: "Отключить обновление ленты в режиме реального времени"
useGroupedNotifications: "Отображать уведомления сгруппировано"
doReaction: "Добавить реакцию"
code: "Код" code: "Код"
remainingN: "Остаётся: {n}"
seasonalScreenEffect: "Эффект времени года на экране"
decorate: "Украсить"
addMfmFunction: "Добавить MFM"
lastNDays: "Последние {n} сут" lastNDays: "Последние {n} сут"
hemisphere: "Место проживания"
enableHorizontalSwipe: "Смахните в сторону, чтобы сменить вкладки"
surrender: "Этот пост не может быть отменен." surrender: "Этот пост не может быть отменен."
useNativeUIForVideoAudioPlayer: "Использовать интерфейс браузера при проигрывании видео и звука"
keepOriginalFilename: "Сохранять исходное имя файла"
keepOriginalFilenameDescription: "Если вы выключите данную настройку, имена файлов будут автоматически заменены случайной строкой при загрузке."
alwaysConfirmFollow: "Всегда подтверждать подписку"
inquiry: "Связаться"
_delivery: _delivery:
stop: "Заморожено" stop: "Заморожено"
_type: _type:
none: "Публикация" none: "Публикация"
_announcement:
tooManyActiveAnnouncementDescription: "Большое количество оповещений может ухудшить пользовательский опыт. Рассмотрите архивирование неактуальных оповещений. "
_initialAccountSetting: _initialAccountSetting:
accountCreated: "Аккаунт успешно создан!" accountCreated: "Аккаунт успешно создан!"
letsStartAccountSetup: "Давайте настроим вашу учётную запись." letsStartAccountSetup: "Давайте настроим вашу учётную запись."
profileSetting: "Настройки профиля" profileSetting: "Настройки профиля"
privacySetting: "Настройки конфиденциальности" privacySetting: "Настройки конфиденциальности"
initialAccountSettingCompleted: "Первоначальная настройка успешно завершена!" initialAccountSettingCompleted: "Первоначальная настройка успешно завершена!"
startTutorial: "Пройти Обучение"
skipAreYouSure: "Пропустить настройку?" skipAreYouSure: "Пропустить настройку?"
_initialTutorial: _initialTutorial:
launchTutorial: "Пройти обучение"
_note: _note:
description: "Посты в Misskey называются 'Заметками.' Заметки отсортированы в хронологическом порядке в ленте и обновляются в режиме реального времени." description: "Посты в Misskey называются 'Заметками.' Заметки отсортированы в хронологическом порядке в ленте и обновляются в режиме реального времени."
_reaction:
reactToContinue: "Добавьте реакцию, чтобы продолжить."
_postNote:
_visibility:
public: "Твоя заметка будет видна всем."
doNotSendConfidencialOnDirect2: "Администратор целевого сервера может видеть что вы отправляете. Будьте осторожны с конфиденциальной информацией, когда отправляете личные заметки пользователям с ненадёжных серверов."
_timelineDescription: _timelineDescription:
home: "В персональной ленте располагаются заметки тех, на которых вы подписаны." home: "В персональной ленте располагаются заметки тех, на которых вы подписаны."
local: "Местная лента показывает заметки всех пользователей этого сайта." local: "Местная лента показывает заметки всех пользователей этого экземпляра."
social: "В социальной ленте собирается всё, что есть в персональной и местной лентах." social: "В социальной ленте собирается всё, что есть в персональной и местной лентах."
global: "В глобальную ленту попадает вообще всё со связанных инстансов." global: "В глобальную ленту попадает вообще всё со связанных экземпляров."
_serverSettings: _serverSettings:
iconUrl: "Адрес на иконку роли" iconUrl: "Адрес на иконку роли"
_accountMigration:
moveFrom: "Перенести другую учётную запись сюда"
moveTo: "Перенести учётную запись на другой сервер"
moveAccountDescription: "Это действие перенесёт ваш аккаунт на другой сервер.\n ・Подписчики с этого аккаунта автоматически подпишутся на новый\n ・Этот аккаунт отпишется от всех пользователей, на которых подписан сейчас\n Вы не сможете создавать новые заметки и т.д. на этом аккаунте\n\nТогда как перенос подписчиков происходит автоматически, вы должны будете подготовиться, сделав некоторые шаги, чтобы перенести список пользователей, на которых вы подписаны. Чтобы сделать это, экспортируйте список подписчиков в файл, который затем импортируете на новом аккаунте в меню настроек. То же самое необходимо будет сделать со списками, также как и со скрытыми и заблокированными пользователями.\n\n(Это объяснение применяется к Misskey v13.12.0 и выше. Другое ActivityPub программное обеспечение, такое, как Mastodon, может работать по-другому."
startMigration: "Перенести"
movedAndCannotBeUndone: "Аккаунт был перемещён. Это действие необратимо."
_achievements: _achievements:
earnedAt: "Разблокировано в" earnedAt: "Разблокировано в"
_types: _types:
@ -1395,6 +1483,7 @@ _role:
canPublicNote: "Может публиковать общедоступные заметки" canPublicNote: "Может публиковать общедоступные заметки"
canInvite: "Может создавать пригласительные коды" canInvite: "Может создавать пригласительные коды"
canManageCustomEmojis: "Управлять пользовательскими эмодзи" canManageCustomEmojis: "Управлять пользовательскими эмодзи"
canManageAvatarDecorations: "Управление украшениями аватара"
driveCapacity: "Доступное пространство на «диске»" driveCapacity: "Доступное пространство на «диске»"
alwaysMarkNsfw: "Всегда отмечать файлы как «не для всех»" alwaysMarkNsfw: "Всегда отмечать файлы как «не для всех»"
pinMax: "Доступное количество закреплённых заметок" pinMax: "Доступное количество закреплённых заметок"
@ -1505,6 +1594,11 @@ _aboutMisskey:
donate: "Пожертвование на Misskey" donate: "Пожертвование на Misskey"
morePatrons: "Большое спасибо и многим другим, кто принял участие в этом проекте! 🥰" morePatrons: "Большое спасибо и многим другим, кто принял участие в этом проекте! 🥰"
patrons: "Материальная поддержка" patrons: "Материальная поддержка"
projectMembers: "Участники проекта"
_displayOfSensitiveMedia:
respect: "Скрывать содержимое не для всех"
ignore: "Показывать содержимое не для всех"
force: "Скрывать всё содержимое"
_instanceTicker: _instanceTicker:
none: "Не показывать" none: "Не показывать"
remote: "Только для других сайтов" remote: "Только для других сайтов"
@ -1533,7 +1627,7 @@ _wordMute:
muteWordsDescription: "Пишите слова через пробел в одной строке, чтобы фильтровать их появление вместе; а если хотите фильтровать любое из них, пишите в отдельных строках." muteWordsDescription: "Пишите слова через пробел в одной строке, чтобы фильтровать их появление вместе; а если хотите фильтровать любое из них, пишите в отдельных строках."
muteWordsDescription2: "Здесь можно использовать регулярные выражения — просто заключите их между двумя дробными чертами (/)." muteWordsDescription2: "Здесь можно использовать регулярные выражения — просто заключите их между двумя дробными чертами (/)."
_instanceMute: _instanceMute:
instanceMuteDescription: "Заметки и репосты с указанных здесь инстансов, а также ответы пользователям оттуда же не будут отображаться." instanceMuteDescription: "Любые активности, затрагивающие инстансы из данного списка, будут скрыты."
instanceMuteDescription2: "Пишите каждый инстанс на отдельной строке" instanceMuteDescription2: "Пишите каждый инстанс на отдельной строке"
title: "Скрывает заметки с заданных инстансов." title: "Скрывает заметки с заданных инстансов."
heading: "Список скрытых инстансов" heading: "Список скрытых инстансов"
@ -1582,7 +1676,7 @@ _theme:
navActive: "Текст на боковой панели (активирован)" navActive: "Текст на боковой панели (активирован)"
navIndicator: "Индикатор на боковой панели" navIndicator: "Индикатор на боковой панели"
link: "Ссылка" link: "Ссылка"
hashtag: "Хэштег" hashtag: "Хештег"
mention: "Упоминание" mention: "Упоминание"
mentionMe: "Упоминания вас" mentionMe: "Упоминания вас"
renote: "Репост" renote: "Репост"
@ -1612,6 +1706,10 @@ _sfx:
note: "Заметки" note: "Заметки"
noteMy: "Собственные заметки" noteMy: "Собственные заметки"
notification: "Уведомления" notification: "Уведомления"
reaction: "При выборе реакции"
_soundSettings:
driveFile: "Использовать аудиофайл с Диска."
driveFileWarn: "Выбрать аудиофайл с Диска."
_ago: _ago:
future: "Из будущего" future: "Из будущего"
justNow: "Только что" justNow: "Только что"
@ -1690,6 +1788,7 @@ _permissions:
"write:gallery": "Редактирование галереи" "write:gallery": "Редактирование галереи"
"read:gallery-likes": "Просмотр списка понравившегося в галерее" "read:gallery-likes": "Просмотр списка понравившегося в галерее"
"write:gallery-likes": "Изменение списка понравившегося в галерее" "write:gallery-likes": "Изменение списка понравившегося в галерее"
"write:admin:reset-password": "Сбросить пароль пользователю"
_auth: _auth:
shareAccessTitle: "Разрешения для приложений" shareAccessTitle: "Разрешения для приложений"
shareAccess: "Дать доступ для «{name}» к вашей учётной записи?" shareAccess: "Дать доступ для «{name}» к вашей учётной записи?"
@ -1743,6 +1842,7 @@ _widgets:
_userList: _userList:
chooseList: "Выберите список" chooseList: "Выберите список"
clicker: "Счётчик щелчков" clicker: "Счётчик щелчков"
birthdayFollowings: "Пользователи, у которых сегодня день рождения"
_cw: _cw:
hide: "Спрятать" hide: "Спрятать"
show: "Показать" show: "Показать"
@ -1796,7 +1896,7 @@ _profile:
name: "Имя" name: "Имя"
username: "Имя пользователя" username: "Имя пользователя"
description: "О себе" description: "О себе"
youCanIncludeHashtags: "Можете использовать здесь хэштеги" youCanIncludeHashtags: "Можете использовать здесь хештеги."
metadata: "Дополнительные сведения" metadata: "Дополнительные сведения"
metadataEdit: "Редактировать дополнительные сведения" metadataEdit: "Редактировать дополнительные сведения"
metadataDescription: "Можно добавить до четырёх дополнительных граф в профиль." metadataDescription: "Можно добавить до четырёх дополнительных граф в профиль."
@ -1804,6 +1904,8 @@ _profile:
metadataContent: "Содержимое" metadataContent: "Содержимое"
changeAvatar: "Поменять аватар" changeAvatar: "Поменять аватар"
changeBanner: "Поменять изображение в шапке" changeBanner: "Поменять изображение в шапке"
verifiedLinkDescription: "Указывая здесь URL, содержащий ссылку на профиль, иконка владения ресурсом может быть отображена рядом с полем"
avatarDecorationMax: "Вы можете добавить до {max} украшений."
_exportOrImport: _exportOrImport:
allNotes: "Все заметки\n" allNotes: "Все заметки\n"
favoritedNotes: "Избранное" favoritedNotes: "Избранное"
@ -1926,6 +2028,9 @@ _notification:
unreadAntennaNote: "Антенна {name}" unreadAntennaNote: "Антенна {name}"
emptyPushNotificationMessage: "Обновлены push-уведомления" emptyPushNotificationMessage: "Обновлены push-уведомления"
achievementEarned: "Получено достижение" achievementEarned: "Получено достижение"
checkNotificationBehavior: "Проверить внешний вид уведомления"
sendTestNotification: "Отправить тестовое уведомление"
flushNotification: "Очистить уведомления"
_types: _types:
all: "Все" all: "Все"
follow: "Подписки" follow: "Подписки"
@ -1938,6 +2043,7 @@ _notification:
receiveFollowRequest: "Получен запрос на подписку" receiveFollowRequest: "Получен запрос на подписку"
followRequestAccepted: "Запрос на подписку одобрен" followRequestAccepted: "Запрос на подписку одобрен"
achievementEarned: "Получение достижений" achievementEarned: "Получение достижений"
login: "Войти"
app: "Уведомления из приложений" app: "Уведомления из приложений"
_actions: _actions:
followBack: "отвечает взаимной подпиской" followBack: "отвечает взаимной подпиской"
@ -1977,19 +2083,57 @@ _dialog:
_disabledTimeline: _disabledTimeline:
title: "Лента отключена" title: "Лента отключена"
description: "Ваша текущая роль не позволяет пользоваться этой лентой." description: "Ваша текущая роль не позволяет пользоваться этой лентой."
_drivecleaner:
orderBySizeDesc: "Размеры файлов по убыванию"
orderByCreatedAtAsc: "По увеличению даты"
_webhookSettings: _webhookSettings:
createWebhook: "Создать вебхук" createWebhook: "Создать вебхук"
modifyWebhook: "Изменить Вебхук"
name: "Название" name: "Название"
secret: "Секрет"
trigger: "Условие срабатывания"
active: "Вкл." active: "Вкл."
_events:
follow: "Когда подписались на пользователя"
followed: "Когда на вас подписались"
note: "Когда создали заметку"
reply: "Когда получили ответ на заметку"
renote: "Когда вас репостнули"
reaction: "Когда получили реакцию"
mention: "Когда вас упоминают"
_systemEvents:
abuseReport: "Когда приходит жалоба"
abuseReportResolved: "Когда разрешается жалоба"
userCreated: "Когда создан пользователь"
deleteConfirm: "Вы уверены, что хотите удалить этот Вебхук?"
_abuseReport: _abuseReport:
_notificationRecipient: _notificationRecipient:
_recipientType: _recipientType:
mail: "Электронная почта" mail: "Электронная почта"
webhook: "Вебхук"
_captions:
webhook: "Отправить уведомление Системному Вебхуку при получении или разрешении жалоб."
notifiedWebhook: "Используемый Вебхук"
_moderationLogTypes: _moderationLogTypes:
suspend: "Заморозить" suspend: "Заморозить"
addCustomEmoji: "Добавлено эмодзи" addCustomEmoji: "Добавлено эмодзи"
updateCustomEmoji: "Изменено эмодзи" updateCustomEmoji: "Изменено эмодзи"
deleteCustomEmoji: "Удалено эмодзи" deleteCustomEmoji: "Удалено эмодзи"
deleteDriveFile: "Файл удалён"
resetPassword: "Сброс пароля:" resetPassword: "Сброс пароля:"
createInvitation: "Создать код приглашения"
createSystemWebhook: "Создать Системный Вебхук"
updateSystemWebhook: "Обновить Системый Вебхук"
deleteSystemWebhook: "Удалить Системный Вебхук"
_fileViewer:
url: "Ссылка"
attachedNotes: "Закреплённые заметки"
_dataSaver:
_code:
title: "Подсветка кода"
_hemisphere:
N: "Северное полушарие"
S: "Южное полушарие"
caption: "Используется для некоторых настроек клиента для определения сезона."
_reversi: _reversi:
total: "Всего" total: "Всего"

View file

@ -17,3 +17,6 @@ _sfx:
note: "නෝට්" note: "නෝට්"
_profile: _profile:
username: "පරිශීලක නාමය" username: "පරිශීලක නාමය"
_notification:
_types:
login: "පිවිසෙන්න"

View file

@ -454,7 +454,6 @@ uiLanguage: "Jazyk používateľského prostredia"
aboutX: "O {x}" aboutX: "O {x}"
emojiStyle: "Štýl emoji" emojiStyle: "Štýl emoji"
native: "Natívne" native: "Natívne"
disableDrawer: "Nepoužívať šuflíkové menu"
showNoteActionsOnlyHover: "Ovládacie prvky poznámky sa zobrazujú len po nabehnutí myši" showNoteActionsOnlyHover: "Ovládacie prvky poznámky sa zobrazujú len po nabehnutí myši"
noHistory: "Žiadna história" noHistory: "Žiadna história"
signinHistory: "História prihlásení" signinHistory: "História prihlásení"
@ -632,10 +631,7 @@ abuseReported: "Vaše nahlásenie je odoslané. Veľmi pekne ďakujeme."
reporter: "Nahlásil" reporter: "Nahlásil"
reporteeOrigin: "Pôvod nahláseného" reporteeOrigin: "Pôvod nahláseného"
reporterOrigin: "Pôvod nahlasovača" reporterOrigin: "Pôvod nahlasovača"
forwardReport: "Preposlať nahlásenie na server"
forwardReportIsAnonymous: "Namiesto vášho účtu bude zobrazený anonymný systémový účet na vzdialenom serveri ako autor nahlásenia."
send: "Poslať" send: "Poslať"
abuseMarkAsResolved: "Označiť nahlásenia ako vyriešené"
openInNewTab: "Otvoriť v novom tabe" openInNewTab: "Otvoriť v novom tabe"
openInSideView: "Otvoriť v bočnom paneli" openInSideView: "Otvoriť v bočnom paneli"
defaultNavigationBehaviour: "Predvolené správanie navigácie" defaultNavigationBehaviour: "Predvolené správanie navigácie"
@ -917,7 +913,7 @@ color: "Farba"
horizontal: "Strana" horizontal: "Strana"
youFollowing: "Sledované" youFollowing: "Sledované"
icon: "Avatar" icon: "Avatar"
replies: "Odpovede" replies: "Odpoved"
renotes: "Preposlať" renotes: "Preposlať"
sourceCode: "Zdrojový kód" sourceCode: "Zdrojový kód"
flip: "Preklopiť" flip: "Preklopiť"
@ -1410,6 +1406,7 @@ _notification:
pollEnded: "Hlasovanie skončilo" pollEnded: "Hlasovanie skončilo"
receiveFollowRequest: "Doručené žiadosti o sledovanie" receiveFollowRequest: "Doručené žiadosti o sledovanie"
followRequestAccepted: "Schválené žiadosti o sledovanie" followRequestAccepted: "Schválené žiadosti o sledovanie"
login: "Prihlásiť sa"
app: "Oznámenia z prepojených aplikácií" app: "Oznámenia z prepojených aplikácií"
_actions: _actions:
followBack: "Sledovať späť\n" followBack: "Sledovať späť\n"

View file

@ -486,7 +486,7 @@ pleaseDonate: "Misskey är en gratis programvara som används på {host}. Donera
resetPasswordConfirm: "Återställ verkligen ditt lösenord?" resetPasswordConfirm: "Återställ verkligen ditt lösenord?"
dataSaver: "Databesparing" dataSaver: "Databesparing"
icon: "Profilbild" icon: "Profilbild"
replies: "Svar" replies: "Svara"
renotes: "Omnotera" renotes: "Omnotera"
_delivery: _delivery:
stop: "Suspenderad" stop: "Suspenderad"
@ -562,6 +562,7 @@ _notification:
renote: "Omnotera" renote: "Omnotera"
quote: "Citat" quote: "Citat"
reaction: "Reaktioner" reaction: "Reaktioner"
login: "Logga in"
_actions: _actions:
reply: "Svara" reply: "Svara"
renote: "Omnotera" renote: "Omnotera"

View file

@ -509,7 +509,6 @@ uiLanguage: "ภาษาอินเทอร์เฟซผู้ใช้ง
aboutX: "เกี่ยวกับ {x}" aboutX: "เกี่ยวกับ {x}"
emojiStyle: "สไตล์ของเอโมจิ" emojiStyle: "สไตล์ของเอโมจิ"
native: "ภาษาแม่" native: "ภาษาแม่"
disableDrawer: "ไม่แสดงเมนูในรูปแบบลิ้นชัก"
showNoteActionsOnlyHover: "แสดงการดำเนินการโน้ตเมื่อโฮเวอร์(วางเมาส์เหนือ)เท่านั้น" showNoteActionsOnlyHover: "แสดงการดำเนินการโน้ตเมื่อโฮเวอร์(วางเมาส์เหนือ)เท่านั้น"
showReactionsCount: "แสดงจำนวนรีแอกชั่นในโน้ต" showReactionsCount: "แสดงจำนวนรีแอกชั่นในโน้ต"
noHistory: "ไม่มีประวัติ" noHistory: "ไม่มีประวัติ"
@ -708,10 +707,7 @@ abuseReported: "เราได้ส่งรายงานของคุณ
reporter: "ผู้รายงาน" reporter: "ผู้รายงาน"
reporteeOrigin: "ปลายทางรายงาน" reporteeOrigin: "ปลายทางรายงาน"
reporterOrigin: "แหล่งผู้รายงาน" reporterOrigin: "แหล่งผู้รายงาน"
forwardReport: "ส่งต่อรายงานไปยังเซิร์ฟเวอร์ระยะไกล"
forwardReportIsAnonymous: "ข้อมูลของคุณจะไม่ปรากฏบนเซิร์ฟเวอร์ระยะไกลและปรากฏเป็นบัญชีระบบที่ไม่ระบุชื่อ"
send: "ส่ง" send: "ส่ง"
abuseMarkAsResolved: "ทำเครื่องหมายรายงานว่าแก้ไขแล้ว"
openInNewTab: "เปิดในแท็บใหม่" openInNewTab: "เปิดในแท็บใหม่"
openInSideView: "เปิดในมุมมองด้านข้าง" openInSideView: "เปิดในมุมมองด้านข้าง"
defaultNavigationBehaviour: "พฤติกรรมการนำทางที่เป็นค่าเริ่มต้น" defaultNavigationBehaviour: "พฤติกรรมการนำทางที่เป็นค่าเริ่มต้น"
@ -1334,10 +1330,10 @@ _initialTutorial:
_reaction: _reaction:
title: "รีแอคชั่นคืออะไร?" title: "รีแอคชั่นคืออะไร?"
description: "โน้ตสามารถ“รีแอคชั่น”ด้วยเอโมจิต่างๆ ซึ่งทำให้สามารถแสดงความแตกต่างเล็กๆ น้อยๆ ที่อาจไม่สามารถสื่อออกมาได้ด้วยการแค่การกด “ถูกใจ”" description: "โน้ตสามารถ“รีแอคชั่น”ด้วยเอโมจิต่างๆ ซึ่งทำให้สามารถแสดงความแตกต่างเล็กๆ น้อยๆ ที่อาจไม่สามารถสื่อออกมาได้ด้วยการแค่การกด “ถูกใจ”"
letsTryReacting: "คุณสามารถเพิ่มรีแอคชั่นได้ด้วยการคลิกปุ่ม “{reaction}” บนโน้ต ลองรีแอคชั่นโน้ตตัวอย่างนี้ดูสิ!" letsTryReacting: "คุณสามารถเพิ่มรีแอคชั่นได้ด้วยการคลิกปุ่ม “+” บนโน้ต ลองรีแอคชั่นโน้ตตัวอย่างนี้ดูสิ!"
reactToContinue: "เพิ่มรีแอคชั่นเพื่อดำเนินการต่อ" reactToContinue: "เพิ่มรีแอคชั่นเพื่อดำเนินการต่อ"
reactNotification: "คุณจะได้รับการแจ้งเตือนแบบเรียลไทม์เมื่อมีคนตอบรีแอคชั่นโน้ตของคุณ" reactNotification: "คุณจะได้รับการแจ้งเตือนแบบเรียลไทม์เมื่อมีคนตอบรีแอคชั่นโน้ตของคุณ"
reactDone: "คุณสามารถยกเลิกรีแอคชั่นได้โดยการกดปุ่ม “{undo}”" reactDone: "คุณสามารถยกเลิกรีแอคชั่นได้โดยการกดปุ่ม “-”"
_timeline: _timeline:
title: "แนวคิดเรื่องของไทม์ไลน์" title: "แนวคิดเรื่องของไทม์ไลน์"
description1: "Misskey มีหลายไทม์ไลน์ขึ้นอยู่กับวิธีการใช้งานของคุณ (บางไทม์ไลน์อาจไม่สามารถใช้ได้ขึ้นอยู่กับนโยบายของเซิร์ฟเวอร์)" description1: "Misskey มีหลายไทม์ไลน์ขึ้นอยู่กับวิธีการใช้งานของคุณ (บางไทม์ไลน์อาจไม่สามารถใช้ได้ขึ้นอยู่กับนโยบายของเซิร์ฟเวอร์)"
@ -1418,7 +1414,7 @@ _achievements:
earnedAt: "ได้รับเมื่อ" earnedAt: "ได้รับเมื่อ"
_types: _types:
_notes1: _notes1:
title: "just setting up my shonk" title: "just setting up my msky"
description: "โพสต์โน้ตเป็นครั้งแรก" description: "โพสต์โน้ตเป็นครั้งแรก"
flavor: "ขอให้มีช่วงเวลาที่ดีกับ Misskey นะคะ!" flavor: "ขอให้มีช่วงเวลาที่ดีกับ Misskey นะคะ!"
_notes10: _notes10:
@ -2375,6 +2371,7 @@ _notification:
followRequestAccepted: "อนุมัติให้ติดตามแล้ว" followRequestAccepted: "อนุมัติให้ติดตามแล้ว"
roleAssigned: "ให้บทบาท" roleAssigned: "ให้บทบาท"
achievementEarned: "ปลดล็อกความสำเร็จแล้ว" achievementEarned: "ปลดล็อกความสำเร็จแล้ว"
login: "เข้าสู่ระบบ"
app: "การแจ้งเตือนจากแอปที่มีลิงก์" app: "การแจ้งเตือนจากแอปที่มีลิงก์"
_actions: _actions:
followBack: "ติดตามกลับด้วย" followBack: "ติดตามกลับด้วย"

View file

@ -446,6 +446,7 @@ _notification:
reaction: "Tepkiler" reaction: "Tepkiler"
receiveFollowRequest: "Takip isteği alındı" receiveFollowRequest: "Takip isteği alındı"
followRequestAccepted: "Takip isteği kabul edildi" followRequestAccepted: "Takip isteği kabul edildi"
login: "Giriş Yap "
_actions: _actions:
reply: "yanıt" reply: "yanıt"
renote: "vazgeçme" renote: "vazgeçme"

View file

@ -17,3 +17,6 @@ _2fa:
renewTOTPCancel: "ئۇنى توختىتىڭ" renewTOTPCancel: "ئۇنى توختىتىڭ"
_widgets: _widgets:
profile: "profile" profile: "profile"
_notification:
_types:
login: "كىرىش"

View file

@ -452,7 +452,6 @@ language: "Мова"
uiLanguage: "Мова інтерфейсу" uiLanguage: "Мова інтерфейсу"
aboutX: "Про {x}" aboutX: "Про {x}"
native: "місцевий" native: "місцевий"
disableDrawer: "Не використовувати висувні меню"
noHistory: "Історія порожня" noHistory: "Історія порожня"
signinHistory: "Історія входів" signinHistory: "Історія входів"
enableAdvancedMfm: "Увімкнути розширений MFM" enableAdvancedMfm: "Увімкнути розширений MFM"
@ -631,10 +630,7 @@ abuseReported: "Дякуємо, вашу скаргу було відправл
reporter: "Репортер" reporter: "Репортер"
reporteeOrigin: "Про кого повідомлено" reporteeOrigin: "Про кого повідомлено"
reporterOrigin: "Хто повідомив" reporterOrigin: "Хто повідомив"
forwardReport: "Переслати звіт на віддалений інстанс"
forwardReportIsAnonymous: "Замість вашого облікового запису анонімний системний обліковий запис буде відображатися як доповідач на віддаленому інстансі"
send: "Відправити" send: "Відправити"
abuseMarkAsResolved: "Позначити скаргу як вирішену"
openInNewTab: "Відкрити в новій вкладці" openInNewTab: "Відкрити в новій вкладці"
openInSideView: "Відкрити збоку" openInSideView: "Відкрити збоку"
defaultNavigationBehaviour: "Поведінка навігації за замовчуванням" defaultNavigationBehaviour: "Поведінка навігації за замовчуванням"
@ -1588,6 +1584,7 @@ _notification:
reaction: "Реакції" reaction: "Реакції"
receiveFollowRequest: "Запити на підписку" receiveFollowRequest: "Запити на підписку"
followRequestAccepted: "Прийняті підписки" followRequestAccepted: "Прийняті підписки"
login: "Увійти"
app: "Сповіщення від додатків" app: "Сповіщення від додатків"
_actions: _actions:
reply: "Відповісти" reply: "Відповісти"

View file

@ -471,7 +471,6 @@ uiLanguage: "Interfeys tili"
aboutX: "{x} haqida" aboutX: "{x} haqida"
emojiStyle: "Emoji ko'rinishi" emojiStyle: "Emoji ko'rinishi"
native: "Mahalliy" native: "Mahalliy"
disableDrawer: "Slayd menyusidan foydalanmang"
showNoteActionsOnlyHover: "Eslatma amallarini faqat sichqonchani olib borganda korsatish" showNoteActionsOnlyHover: "Eslatma amallarini faqat sichqonchani olib borganda korsatish"
noHistory: "Tarix yo'q" noHistory: "Tarix yo'q"
signinHistory: "kirish tarixi" signinHistory: "kirish tarixi"
@ -630,10 +629,7 @@ abuseReported: "Shikoyatingiz yetkazildi. Ma'lumot uchun rahmat."
reporter: "Shikoyat qiluvchi" reporter: "Shikoyat qiluvchi"
reporteeOrigin: "Xabarning kelib chiqishi" reporteeOrigin: "Xabarning kelib chiqishi"
reporterOrigin: "Xabarchining joylashuvi" reporterOrigin: "Xabarchining joylashuvi"
forwardReport: "Xabarni masofadagi serverga yuborish"
forwardReportIsAnonymous: "Sizning yuborayotgan xabaringiz o'z akkountingiz emas balki anonim tarzda qoladi"
send: "Yuborish" send: "Yuborish"
abuseMarkAsResolved: "Yuborilgan xabarni hal qilingan deb belgilash"
openInNewTab: "Yangi tab da ochish" openInNewTab: "Yangi tab da ochish"
openInSideView: "Yon panelda ochish" openInSideView: "Yon panelda ochish"
defaultNavigationBehaviour: "Standart navigatsiya harakati" defaultNavigationBehaviour: "Standart navigatsiya harakati"
@ -843,7 +839,7 @@ rolesAssignedToMe: "Mening rollarim"
resetPasswordConfirm: "Qayta parol o'rnatmoqchimisiz?" resetPasswordConfirm: "Qayta parol o'rnatmoqchimisiz?"
sensitiveWords: "Ta'sirchan so'zlar" sensitiveWords: "Ta'sirchan so'zlar"
icon: "Avatar" icon: "Avatar"
replies: "Javoblar" replies: "Javob berish"
renotes: "Qayta qayd etish" renotes: "Qayta qayd etish"
flip: "Teskari" flip: "Teskari"
_delivery: _delivery:
@ -1058,6 +1054,7 @@ _notification:
quote: "Iqtibos keltirish" quote: "Iqtibos keltirish"
reaction: "Reaktsiyalar" reaction: "Reaktsiyalar"
receiveFollowRequest: "Qabul qilingan kuzatuv so'rovlari" receiveFollowRequest: "Qabul qilingan kuzatuv so'rovlari"
login: "Kirish"
_actions: _actions:
reply: "Javob berish" reply: "Javob berish"
renote: "Qayta qayd qilish" renote: "Qayta qayd qilish"

View file

@ -486,7 +486,6 @@ uiLanguage: "Ngôn ngữ giao diện"
aboutX: "Giới thiệu {x}" aboutX: "Giới thiệu {x}"
emojiStyle: "Kiểu cách Emoji" emojiStyle: "Kiểu cách Emoji"
native: "Bản xứ" native: "Bản xứ"
disableDrawer: "Không dùng menu thanh bên"
showNoteActionsOnlyHover: "Chỉ hiển thị các hành động ghi chú khi di chuột" showNoteActionsOnlyHover: "Chỉ hiển thị các hành động ghi chú khi di chuột"
noHistory: "Không có dữ liệu" noHistory: "Không có dữ liệu"
signinHistory: "Lịch sử đăng nhập" signinHistory: "Lịch sử đăng nhập"
@ -676,10 +675,7 @@ abuseReported: "Báo cáo đã được gửi. Cảm ơn bạn nhiều."
reporter: "Người báo cáo" reporter: "Người báo cáo"
reporteeOrigin: "Bị báo cáo" reporteeOrigin: "Bị báo cáo"
reporterOrigin: "Máy chủ người báo cáo" reporterOrigin: "Máy chủ người báo cáo"
forwardReport: "Chuyển tiếp báo cáo cho máy chủ từ xa"
forwardReportIsAnonymous: "Thay vì tài khoản của bạn, một tài khoản hệ thống ẩn danh sẽ được hiển thị dưới dạng người báo cáo ở máy chủ từ xa."
send: "Gửi" send: "Gửi"
abuseMarkAsResolved: "Đánh dấu đã xử lý"
openInNewTab: "Mở trong tab mới" openInNewTab: "Mở trong tab mới"
openInSideView: "Mở trong thanh bên" openInSideView: "Mở trong thanh bên"
defaultNavigationBehaviour: "Thao tác điều hướng mặc định" defaultNavigationBehaviour: "Thao tác điều hướng mặc định"
@ -1163,7 +1159,7 @@ _achievements:
earnedAt: "Ngày thu nhận" earnedAt: "Ngày thu nhận"
_types: _types:
_notes1: _notes1:
title: "just setting up my shonk" title: "just setting up my msky"
description: "Lần đầu tiên đăng bài" description: "Lần đầu tiên đăng bài"
flavor: "Chúc bạn trên Miskey vui vẻ nha!!" flavor: "Chúc bạn trên Miskey vui vẻ nha!!"
_notes10: _notes10:
@ -1879,6 +1875,7 @@ _notification:
receiveFollowRequest: "Yêu cầu theo dõi" receiveFollowRequest: "Yêu cầu theo dõi"
followRequestAccepted: "Yêu cầu theo dõi được chấp nhận" followRequestAccepted: "Yêu cầu theo dõi được chấp nhận"
achievementEarned: "Hoàn thành Achievement" achievementEarned: "Hoàn thành Achievement"
login: "Đăng nhập"
app: "Từ app liên kết" app: "Từ app liên kết"
_actions: _actions:
followBack: "đã theo dõi lại bạn" followBack: "đã theo dõi lại bạn"

View file

@ -8,6 +8,9 @@ search: "搜索"
notifications: "通知" notifications: "通知"
username: "用户名" username: "用户名"
password: "密码" password: "密码"
initialPasswordForSetup: "初始化密码"
initialPasswordIsIncorrect: "初始化密码不正确"
initialPasswordForSetupDescription: "如果是自己安装的 Misskey请输入配置文件里设好的密码。\n如果使用的是 Misskey 的托管服务等,请输入服务商提供的密码。\n如果没有设置密码请留空并继续。"
forgotPassword: "忘记密码" forgotPassword: "忘记密码"
fetchingAsApObject: "在联邦宇宙查询中..." fetchingAsApObject: "在联邦宇宙查询中..."
ok: "OK" ok: "OK"
@ -90,7 +93,7 @@ followsYou: "正在关注你"
createList: "创建列表" createList: "创建列表"
manageLists: "管理列表" manageLists: "管理列表"
error: "错误" error: "错误"
somethingHappened: "出现了一些问题!" somethingHappened: "出错了"
retry: "重试" retry: "重试"
pageLoadError: "页面加载失败。" pageLoadError: "页面加载失败。"
pageLoadErrorDescription: "这通常是由于网络或浏览器缓存的原因。请清除缓存或等待片刻后重试。" pageLoadErrorDescription: "这通常是由于网络或浏览器缓存的原因。请清除缓存或等待片刻后重试。"
@ -167,7 +170,7 @@ emojiUrl: "emoji 地址"
addEmoji: "添加表情符号" addEmoji: "添加表情符号"
settingGuide: "推荐配置" settingGuide: "推荐配置"
cacheRemoteFiles: "缓存远程文件" cacheRemoteFiles: "缓存远程文件"
cacheRemoteFilesDescription: "启用此设定时,将在此服务器上缓存远程文件。虽然可以加快图片显示的速度,但是相对的会消耗大量的服务器存储空间。用户角色内的网盘容量决定了这个远程用户能在服务器上保留保留多少缓存。当超出了这个限制时,旧的文件将从缓存中被删除,成为链接。当禁用此设定时,则是从一开始就将远程文件保留为链接。此时推荐将 default.yml 的 proxyRemoteFiles 设置为 true 以优化缩略图生成及保护用户隐私。" cacheRemoteFilesDescription: "启用此设定时,将在此服务器上缓存远程文件。虽然可以加快图片显示的速度,但是相对的会消耗大量的服务器存储空间。用户角色内的网盘容量决定了这个远程用户能在服务器上保留多少缓存。当超出了这个限制时,旧的文件将从缓存中被删除,成为链接。当禁用此设定时,则是从一开始就将远程文件保留为链接。此时推荐将 default.yml 的 proxyRemoteFiles 设置为 true 以优化缩略图生成及保护用户隐私。"
youCanCleanRemoteFilesCache: "可以使用文件管理的🗑️按钮来删除所有的缓存。" youCanCleanRemoteFilesCache: "可以使用文件管理的🗑️按钮来删除所有的缓存。"
cacheRemoteSensitiveFiles: "缓存远程敏感媒体文件" cacheRemoteSensitiveFiles: "缓存远程敏感媒体文件"
cacheRemoteSensitiveFilesDescription: "如果禁用这项设定,远程服务器的敏感媒体将不会被缓存,而是直接链接。" cacheRemoteSensitiveFilesDescription: "如果禁用这项设定,远程服务器的敏感媒体将不会被缓存,而是直接链接。"
@ -236,6 +239,8 @@ silencedInstances: "被静音的服务器"
silencedInstancesDescription: "设置要静音的服务器,以换行分隔。被静音的服务器内所有的账户将默认处于「静音」状态,仅能发送关注请求,并且在未关注状态下无法提及本地账户。被阻止的实例不受影响。" silencedInstancesDescription: "设置要静音的服务器,以换行分隔。被静音的服务器内所有的账户将默认处于「静音」状态,仅能发送关注请求,并且在未关注状态下无法提及本地账户。被阻止的实例不受影响。"
mediaSilencedInstances: "已隐藏媒体文件的服务器" mediaSilencedInstances: "已隐藏媒体文件的服务器"
mediaSilencedInstancesDescription: "设置要隐藏媒体文件的服务器,以换行分隔。被设置为隐藏媒体文件服务器内所有账号的文件均按照「敏感内容」处理,且将无法使用自定义表情符号。被阻止的实例不受影响。" mediaSilencedInstancesDescription: "设置要隐藏媒体文件的服务器,以换行分隔。被设置为隐藏媒体文件服务器内所有账号的文件均按照「敏感内容」处理,且将无法使用自定义表情符号。被阻止的实例不受影响。"
federationAllowedHosts: "允许联合的服务器"
federationAllowedHostsDescription: "设定允许联合的服务器,以换行分隔。"
muteAndBlock: "静音/拉黑" muteAndBlock: "静音/拉黑"
mutedUsers: "已静音用户" mutedUsers: "已静音用户"
blockedUsers: "已拉黑的用户" blockedUsers: "已拉黑的用户"
@ -334,6 +339,7 @@ renameFolder: "重命名文件夹"
deleteFolder: "删除文件夹" deleteFolder: "删除文件夹"
folder: "文件夹" folder: "文件夹"
addFile: "添加文件" addFile: "添加文件"
showFile: "显示文件"
emptyDrive: "网盘中无文件" emptyDrive: "网盘中无文件"
emptyFolder: "此文件夹中无文件" emptyFolder: "此文件夹中无文件"
unableToDelete: "无法删除" unableToDelete: "无法删除"
@ -448,6 +454,7 @@ totpDescription: "使用验证器输入一次性密码"
moderator: "监察员" moderator: "监察员"
moderation: "管理" moderation: "管理"
moderationNote: "管理笔记" moderationNote: "管理笔记"
moderationNoteDescription: "可以用来记录仅在管理员之间共享的笔记。"
addModerationNote: "添加管理笔记" addModerationNote: "添加管理笔记"
moderationLogs: "管理日志" moderationLogs: "管理日志"
nUsersMentioned: "{n} 被提到" nUsersMentioned: "{n} 被提到"
@ -509,7 +516,10 @@ uiLanguage: "显示语言"
aboutX: "关于 {x}" aboutX: "关于 {x}"
emojiStyle: "表情符号的样式" emojiStyle: "表情符号的样式"
native: "原生" native: "原生"
disableDrawer: "不显示抽屉菜单" menuStyle: "菜单样式"
style: "样式"
drawer: "抽屉"
popup: "弹窗"
showNoteActionsOnlyHover: "仅在悬停时显示帖子操作" showNoteActionsOnlyHover: "仅在悬停时显示帖子操作"
showReactionsCount: "显示帖子的回应数" showReactionsCount: "显示帖子的回应数"
noHistory: "没有历史记录" noHistory: "没有历史记录"
@ -592,6 +602,8 @@ ascendingOrder: "升序"
descendingOrder: "降序" descendingOrder: "降序"
scratchpad: "AiScript 控制台" scratchpad: "AiScript 控制台"
scratchpadDescription: "AiScript 控制台为 AiScript 提供了实验环境。您可以编写代码与 Misskey 交互,运行并查看结果。" scratchpadDescription: "AiScript 控制台为 AiScript 提供了实验环境。您可以编写代码与 Misskey 交互,运行并查看结果。"
uiInspector: "UI 检查器"
uiInspectorDescription: "查看所有内存中由 UI 组件生成出的实例。UI 组件由 UI:C 系列函数所生成。"
output: "输出" output: "输出"
script: "脚本" script: "脚本"
disablePagesScript: "禁用页面脚本" disablePagesScript: "禁用页面脚本"
@ -708,10 +720,7 @@ abuseReported: "内容已发送。感谢您提交信息。"
reporter: "举报者" reporter: "举报者"
reporteeOrigin: "举报来源" reporteeOrigin: "举报来源"
reporterOrigin: "举报者来源" reporterOrigin: "举报者来源"
forwardReport: "将该举报信息转发给远程服务器"
forwardReportIsAnonymous: "在远程实例上显示的报告者是匿名的系统账号,而不是您的账号。"
send: "发送" send: "发送"
abuseMarkAsResolved: "处理完毕"
openInNewTab: "在新标签页中打开" openInNewTab: "在新标签页中打开"
openInSideView: "在侧边栏中打开" openInSideView: "在侧边栏中打开"
defaultNavigationBehaviour: "默认导航" defaultNavigationBehaviour: "默认导航"
@ -913,6 +922,7 @@ followersVisibility: "关注者的公开范围"
continueThread: "查看更多帖子" continueThread: "查看更多帖子"
deleteAccountConfirm: "将要删除账户。是否确认?" deleteAccountConfirm: "将要删除账户。是否确认?"
incorrectPassword: "密码错误" incorrectPassword: "密码错误"
incorrectTotp: "一次性密码不正确或已过期"
voteConfirm: "确定投给 “{choice}” " voteConfirm: "确定投给 “{choice}” "
hide: "隐藏" hide: "隐藏"
useDrawerReactionPickerForMobile: "在移动设备上使用抽屉显示" useDrawerReactionPickerForMobile: "在移动设备上使用抽屉显示"
@ -1263,6 +1273,23 @@ confirmWhenRevealingSensitiveMedia: "显示敏感内容前需要确认"
sensitiveMediaRevealConfirm: "这是敏感内容。是否显示?" sensitiveMediaRevealConfirm: "这是敏感内容。是否显示?"
createdLists: "已创建的列表" createdLists: "已创建的列表"
createdAntennas: "已创建的天线" createdAntennas: "已创建的天线"
fromX: "从 {x}"
genEmbedCode: "生成嵌入代码"
noteOfThisUser: "此用户的帖子"
clipNoteLimitExceeded: "无法再往此便签内添加更多帖子"
performance: "性能"
modified: "有变更"
discard: "取消"
thereAreNChanges: "有 {n} 处更改"
signinWithPasskey: "使用通行密钥登录"
unknownWebAuthnKey: "此通行密钥未注册。"
passkeyVerificationFailed: "验证通行密钥失败。"
passkeyVerificationSucceededButPasswordlessLoginDisabled: "通行密钥验证成功,但账户未开启无密码登录。"
messageToFollower: "给关注者的消息"
target: "对象"
_abuseUserReport:
forward: "转发"
forwardDescription: "目标是匿名系统账户,将把举报转发给远程服务器。"
_delivery: _delivery:
status: "投递状态" status: "投递状态"
stop: "停止投递" stop: "停止投递"
@ -1334,10 +1361,10 @@ _initialTutorial:
_reaction: _reaction:
title: "什么是回应?" title: "什么是回应?"
description: "您可以在帖子中添加“回应”。 您可以使用反应轻松地表达点“赞”所无法传达的细微差别。" description: "您可以在帖子中添加“回应”。 您可以使用反应轻松地表达点“赞”所无法传达的细微差别。"
letsTryReacting: "回应可以通过点击帖子中的「{reaction}」按钮来添加。试着给这个示例帖子添加一个回应!" letsTryReacting: "回应可以通过点击帖子中的「+」按钮来添加。试着给这个示例帖子添加一个回应!"
reactToContinue: "添加一个回应来继续" reactToContinue: "添加一个回应来继续"
reactNotification: "当您的帖子被某人添加了回应时,将实时收到通知。" reactNotification: "当您的帖子被某人添加了回应时,将实时收到通知。"
reactDone: "通过按下「{undo}」按钮,可以取消已经添加的回应" reactDone: "通过按下「」按钮,可以取消已经添加的回应"
_timeline: _timeline:
title: "时间线的运作方式" title: "时间线的运作方式"
description1: "Misskey 根据使用方式提供了多个时间线(根据服务器的设定,可能有一些被禁用)。" description1: "Misskey 根据使用方式提供了多个时间线(根据服务器的设定,可能有一些被禁用)。"
@ -1397,6 +1424,7 @@ _serverSettings:
fanoutTimelineDescription: "当启用时,可显著提高获取各种时间线时的性能,并减轻数据库的负荷。但是相对的 Redis 的内存使用量将会增加。如果服务器的内存不是很大,又或者运行不稳定的话可以把它关掉。" fanoutTimelineDescription: "当启用时,可显著提高获取各种时间线时的性能,并减轻数据库的负荷。但是相对的 Redis 的内存使用量将会增加。如果服务器的内存不是很大,又或者运行不稳定的话可以把它关掉。"
fanoutTimelineDbFallback: "回退到数据库" fanoutTimelineDbFallback: "回退到数据库"
fanoutTimelineDbFallbackDescription: "当启用时,若时间线未被缓存,则将额外查询数据库。禁用该功能可通过不执行回退处理进一步减少服务器负载,但会限制可检索的时间线范围。" fanoutTimelineDbFallbackDescription: "当启用时,若时间线未被缓存,则将额外查询数据库。禁用该功能可通过不执行回退处理进一步减少服务器负载,但会限制可检索的时间线范围。"
reactionsBufferingDescription: "开启时可显著提高发送回应时的性能,及减轻数据库负荷。但 Redis 的内存用量会相应增加。"
inquiryUrl: "联络地址" inquiryUrl: "联络地址"
inquiryUrlDescription: "用来指定诸如向服务运营商咨询的论坛地址,或记载了运营商联系方式之类的网页地址。" inquiryUrlDescription: "用来指定诸如向服务运营商咨询的论坛地址,或记载了运营商联系方式之类的网页地址。"
_accountMigration: _accountMigration:
@ -1598,7 +1626,7 @@ _achievements:
_postedAt0min0sec: _postedAt0min0sec:
title: "报时" title: "报时"
description: "在 0 点发布一篇帖子" description: "在 0 点发布一篇帖子"
flavor: "嘣 嘣 嘣 Biu——" flavor: "报时信号最后一响,零点整"
_selfQuote: _selfQuote:
title: "自我引用" title: "自我引用"
description: "引用了自己的帖子" description: "引用了自己的帖子"
@ -1647,8 +1675,8 @@ _achievements:
flavor: "今年也请对本服务器多多指教!" flavor: "今年也请对本服务器多多指教!"
_cookieClicked: _cookieClicked:
title: "点击饼干小游戏" title: "点击饼干小游戏"
description: "点击了可疑的饼干" description: "点击了饼干"
flavor: "是不是软件有问题" flavor: "用错软件了"
_brainDiver: _brainDiver:
title: "Brain Diver" title: "Brain Diver"
description: "发布了包含 Brain Diver 链接的帖子" description: "发布了包含 Brain Diver 链接的帖子"
@ -1665,7 +1693,7 @@ _achievements:
_bubbleGameDoubleExplodingHead: _bubbleGameDoubleExplodingHead:
title: "两个🤯" title: "两个🤯"
description: "你合成出了2个游戏里最大的Emoji" description: "你合成出了2个游戏里最大的Emoji"
flavor: "" flavor: "大约能 装满 这些便当盒 🤯 🤯 (比划)"
_role: _role:
new: "创建角色" new: "创建角色"
edit: "编辑角色" edit: "编辑角色"
@ -1730,6 +1758,11 @@ _role:
canSearchNotes: "是否可以搜索帖子" canSearchNotes: "是否可以搜索帖子"
canUseTranslator: "使用翻译功能" canUseTranslator: "使用翻译功能"
avatarDecorationLimit: "可添加头像挂件的最大个数" avatarDecorationLimit: "可添加头像挂件的最大个数"
canImportAntennas: "允许导入天线"
canImportBlocking: "允许导入拉黑列表"
canImportFollowing: "允许导入关注列表"
canImportMuting: "允许导入屏蔽列表"
canImportUserLists: "允许导入用户列表"
_condition: _condition:
roleAssignedTo: "已分配给手动角色" roleAssignedTo: "已分配给手动角色"
isLocal: "是本地用户" isLocal: "是本地用户"
@ -2224,6 +2257,9 @@ _profile:
changeBanner: "修改横幅" changeBanner: "修改横幅"
verifiedLinkDescription: "如果将内容设置为 URL当链接所指向的网页内包含自己的个人资料链接时可以显示一个已验证图标。" verifiedLinkDescription: "如果将内容设置为 URL当链接所指向的网页内包含自己的个人资料链接时可以显示一个已验证图标。"
avatarDecorationMax: "最多可添加 {max} 个挂件" avatarDecorationMax: "最多可添加 {max} 个挂件"
followedMessage: "被关注时显示的消息"
followedMessageDescription: "可以设置被关注时向对方显示的短消息。"
followedMessageDescriptionForLockedAccount: "需要批准才能关注的情况下,消息是在被请求被批准后显示。"
_exportOrImport: _exportOrImport:
allNotes: "所有帖子" allNotes: "所有帖子"
favoritedNotes: "收藏的帖子" favoritedNotes: "收藏的帖子"
@ -2362,6 +2398,8 @@ _notification:
renotedBySomeUsers: "{n} 人转发了" renotedBySomeUsers: "{n} 人转发了"
followedBySomeUsers: "被 {n} 人关注" followedBySomeUsers: "被 {n} 人关注"
flushNotification: "重置通知历史" flushNotification: "重置通知历史"
exportOfXCompleted: "已完成 {x} 个导出"
login: "有新的登录"
_types: _types:
all: "全部" all: "全部"
note: "用户的新帖子" note: "用户的新帖子"
@ -2376,6 +2414,9 @@ _notification:
followRequestAccepted: "关注请求已通过" followRequestAccepted: "关注请求已通过"
roleAssigned: "授予的角色" roleAssigned: "授予的角色"
achievementEarned: "取得的成就" achievementEarned: "取得的成就"
exportCompleted: "已完成导出"
login: "登录"
test: "测试通知"
app: "关联应用的通知" app: "关联应用的通知"
_actions: _actions:
followBack: "回关" followBack: "回关"
@ -2442,6 +2483,7 @@ _webhookSettings:
abuseReportResolved: "当举报被处理时" abuseReportResolved: "当举报被处理时"
userCreated: "当用户被创建时" userCreated: "当用户被创建时"
deleteConfirm: "要删除 webhook 吗?" deleteConfirm: "要删除 webhook 吗?"
testRemarks: "点击开关右侧的按钮,可以发送使用假数据的测试 Webhook。"
_abuseReport: _abuseReport:
_notificationRecipient: _notificationRecipient:
createRecipient: "新建举报通知" createRecipient: "新建举报通知"
@ -2485,6 +2527,8 @@ _moderationLogTypes:
markSensitiveDriveFile: "标记网盘文件为敏感媒体" markSensitiveDriveFile: "标记网盘文件为敏感媒体"
unmarkSensitiveDriveFile: "取消标记网盘文件为敏感媒体" unmarkSensitiveDriveFile: "取消标记网盘文件为敏感媒体"
resolveAbuseReport: "处理举报" resolveAbuseReport: "处理举报"
forwardAbuseReport: "转发举报"
updateAbuseReportNote: "更新举报用管理笔记"
createInvitation: "生成邀请码" createInvitation: "生成邀请码"
createAd: "创建了广告" createAd: "创建了广告"
deleteAd: "删除了广告" deleteAd: "删除了广告"
@ -2640,3 +2684,17 @@ _contextMenu:
app: "应用" app: "应用"
appWithShift: "Shift 键应用" appWithShift: "Shift 键应用"
native: "浏览器的用户界面" native: "浏览器的用户界面"
_embedCodeGen:
title: "自定义嵌入代码"
header: "显示标题"
autoload: "连续加载(不推荐)"
maxHeight: "最大高度"
maxHeightDescription: "若将最大值设为 0 则不限制最大高度。为防止小工具无限增高,建议设置一下。"
maxHeightWarn: "最大高度限制已禁用0。若这不是您想要的效果请将最大高度设一个值。"
previewIsNotActual: "由于超出了预览画面可显示的范围,因此显示内容会与实际嵌入时有所不同。"
rounded: "圆角"
border: "外边框"
applyToPreview: "应用预览"
generateCode: "生成嵌入代码"
codeGenerated: "已生成代码"
codeGeneratedDescription: "将生成的代码贴到网站上来使用。"

View file

@ -8,6 +8,9 @@ search: "搜尋"
notifications: "通知" notifications: "通知"
username: "使用者名稱" username: "使用者名稱"
password: "密碼" password: "密碼"
initialPasswordForSetup: "初始設定用的密碼"
initialPasswordIsIncorrect: "初始設定用的密碼錯誤。"
initialPasswordForSetupDescription: "如果您自己安裝了 Misskey請使用您在設定檔中輸入的密碼。\n如果您使用 Misskey 的託管服務之類的服務,請使用提供的密碼。\n如果您尚未設定密碼請將其留空並繼續。"
forgotPassword: "忘記密碼" forgotPassword: "忘記密碼"
fetchingAsApObject: "從聯邦宇宙取得中..." fetchingAsApObject: "從聯邦宇宙取得中..."
ok: "OK" ok: "OK"
@ -236,6 +239,8 @@ silencedInstances: "被禁言的伺服器"
silencedInstancesDescription: "設定要禁言的伺服器主機名稱,以換行分隔。隸屬於禁言伺服器的所有帳戶都將被視為「禁言帳戶」,只能發出「追隨請求」,而且無法提及未追隨的本地帳戶。這不會影響已封鎖的實例。" silencedInstancesDescription: "設定要禁言的伺服器主機名稱,以換行分隔。隸屬於禁言伺服器的所有帳戶都將被視為「禁言帳戶」,只能發出「追隨請求」,而且無法提及未追隨的本地帳戶。這不會影響已封鎖的實例。"
mediaSilencedInstances: "媒體被禁言的伺服器" mediaSilencedInstances: "媒體被禁言的伺服器"
mediaSilencedInstancesDescription: "設定您想要對媒體設定禁言的伺服器,以換行符號區隔。來自被媒體禁言的伺服器所屬帳戶的所有檔案都會被視為敏感檔案,且自訂表情符號不能使用。被封鎖的伺服器不受影響。" mediaSilencedInstancesDescription: "設定您想要對媒體設定禁言的伺服器,以換行符號區隔。來自被媒體禁言的伺服器所屬帳戶的所有檔案都會被視為敏感檔案,且自訂表情符號不能使用。被封鎖的伺服器不受影響。"
federationAllowedHosts: "允許聯邦通訊的伺服器"
federationAllowedHostsDescription: "設定允許聯邦通訊的伺服器主機,以換行符號分隔。"
muteAndBlock: "靜音和封鎖" muteAndBlock: "靜音和封鎖"
mutedUsers: "被靜音的使用者" mutedUsers: "被靜音的使用者"
blockedUsers: "被封鎖的使用者" blockedUsers: "被封鎖的使用者"
@ -334,6 +339,7 @@ renameFolder: "重新命名資料夾"
deleteFolder: "刪除資料夾" deleteFolder: "刪除資料夾"
folder: "資料夾" folder: "資料夾"
addFile: "加入附件" addFile: "加入附件"
showFile: "瀏覽文件"
emptyDrive: "雲端硬碟為空" emptyDrive: "雲端硬碟為空"
emptyFolder: "資料夾為空" emptyFolder: "資料夾為空"
unableToDelete: "無法刪除" unableToDelete: "無法刪除"
@ -509,7 +515,10 @@ uiLanguage: "介面語言"
aboutX: "關於{x}" aboutX: "關於{x}"
emojiStyle: "表情符號的風格" emojiStyle: "表情符號的風格"
native: "原生" native: "原生"
disableDrawer: "不顯示下拉式選單" menuStyle: "選單風格"
style: "風格"
drawer: "側邊欄"
popup: "彈出式視窗"
showNoteActionsOnlyHover: "僅在游標停留時顯示貼文的操作選項" showNoteActionsOnlyHover: "僅在游標停留時顯示貼文的操作選項"
showReactionsCount: "顯示貼文的反應數目" showReactionsCount: "顯示貼文的反應數目"
noHistory: "沒有歷史紀錄" noHistory: "沒有歷史紀錄"
@ -592,6 +601,8 @@ ascendingOrder: "昇冪"
descendingOrder: "降冪" descendingOrder: "降冪"
scratchpad: "暫存記憶體" scratchpad: "暫存記憶體"
scratchpadDescription: "AiScript 控制臺為 AiScript 的實驗環境。您可以在此編寫、執行和確認程式碼與 Misskey 互動的結果。" scratchpadDescription: "AiScript 控制臺為 AiScript 的實驗環境。您可以在此編寫、執行和確認程式碼與 Misskey 互動的結果。"
uiInspector: "UI 檢查"
uiInspectorDescription: "您可以看到記憶體中存在的 UI 元件實例的清單。 UI 元件由 Ui:C: 系列函數產生。"
output: "輸出" output: "輸出"
script: "腳本" script: "腳本"
disablePagesScript: "停用頁面的 AiScript 腳本" disablePagesScript: "停用頁面的 AiScript 腳本"
@ -708,10 +719,7 @@ abuseReported: "檢舉完成。感謝您的報告。"
reporter: "檢舉者" reporter: "檢舉者"
reporteeOrigin: "檢舉來源" reporteeOrigin: "檢舉來源"
reporterOrigin: "檢舉者來源" reporterOrigin: "檢舉者來源"
forwardReport: "將報告轉送給遠端伺服器"
forwardReportIsAnonymous: "在遠端實例上看不到您的資訊,顯示的報告者是匿名的系统帳戶。"
send: "發送" send: "發送"
abuseMarkAsResolved: "處理完畢"
openInNewTab: "在新分頁中開啟" openInNewTab: "在新分頁中開啟"
openInSideView: "在側欄中開啟" openInSideView: "在側欄中開啟"
defaultNavigationBehaviour: "預設導航" defaultNavigationBehaviour: "預設導航"
@ -913,6 +921,7 @@ followersVisibility: "追隨者的可見性"
continueThread: "查看更多貼文" continueThread: "查看更多貼文"
deleteAccountConfirm: "將要刪除帳戶。是否確定?" deleteAccountConfirm: "將要刪除帳戶。是否確定?"
incorrectPassword: "密碼錯誤。" incorrectPassword: "密碼錯誤。"
incorrectTotp: "一次性密碼錯誤,或者已過期。"
voteConfirm: "確定投給「{choice}」?" voteConfirm: "確定投給「{choice}」?"
hide: "隱藏" hide: "隱藏"
useDrawerReactionPickerForMobile: "在移動設備上使用抽屜顯示" useDrawerReactionPickerForMobile: "在移動設備上使用抽屜顯示"
@ -1186,7 +1195,7 @@ edited: "已編輯"
notificationRecieveConfig: "接受通知的設定" notificationRecieveConfig: "接受通知的設定"
mutualFollow: "互相追隨" mutualFollow: "互相追隨"
followingOrFollower: "追隨中或追隨者" followingOrFollower: "追隨中或追隨者"
fileAttachedOnly: "顯示包含附件的貼文" fileAttachedOnly: "顯示包含附件的貼文"
showRepliesToOthersInTimeline: "顯示給其他人的回覆" showRepliesToOthersInTimeline: "顯示給其他人的回覆"
hideRepliesToOthersInTimeline: "在時間軸上隱藏給其他人的回覆" hideRepliesToOthersInTimeline: "在時間軸上隱藏給其他人的回覆"
showRepliesToOthersInTimelineAll: "在時間軸包含追隨中所有人的回覆" showRepliesToOthersInTimelineAll: "在時間軸包含追隨中所有人的回覆"
@ -1263,6 +1272,19 @@ confirmWhenRevealingSensitiveMedia: "要顯示敏感媒體時需確認"
sensitiveMediaRevealConfirm: "這是敏感媒體。確定要顯示嗎?" sensitiveMediaRevealConfirm: "這是敏感媒體。確定要顯示嗎?"
createdLists: "已建立的清單" createdLists: "已建立的清單"
createdAntennas: "已建立的天線" createdAntennas: "已建立的天線"
fromX: "自 {x}"
genEmbedCode: "產生嵌入程式碼"
noteOfThisUser: "這個使用者的貼文列表"
clipNoteLimitExceeded: "沒辦法在這個摘錄中增加更多貼文了。"
performance: "性能"
modified: "已變更"
discard: "取消"
thereAreNChanges: "有 {n} 處的變更"
signinWithPasskey: "使用密碼金鑰登入"
unknownWebAuthnKey: "未註冊的金鑰。"
passkeyVerificationFailed: "驗證金鑰失敗。"
passkeyVerificationSucceededButPasswordlessLoginDisabled: "雖然驗證金鑰成功,但是無密碼登入的方式是停用的。"
messageToFollower: "給追隨者的訊息"
_delivery: _delivery:
status: "傳送狀態" status: "傳送狀態"
stop: "停止發送" stop: "停止發送"
@ -1334,10 +1356,10 @@ _initialTutorial:
_reaction: _reaction:
title: "什麼是反應?" title: "什麼是反應?"
description: "您可以在貼文中添加「反應」。您可以使用反應輕鬆隨意地表達「最愛/大心」所無法傳達的細微差別。" description: "您可以在貼文中添加「反應」。您可以使用反應輕鬆隨意地表達「最愛/大心」所無法傳達的細微差別。"
letsTryReacting: "可以透過點擊貼文上的「{reaction}」按鈕來添加反應。請嘗試在此範例貼文添加反應!" letsTryReacting: "可以透過點擊貼文上的「+」按鈕來添加反應。請嘗試在此範例貼文添加反應!"
reactToContinue: "添加反應以繼續教學課程。" reactToContinue: "添加反應以繼續教學課程。"
reactNotification: "當有人對您的貼文做出反應時會即時接收到通知。" reactNotification: "當有人對您的貼文做出反應時會即時接收到通知。"
reactDone: "按下「{undo}」按鈕可以取消反應。" reactDone: "按下「-」按鈕可以取消反應。"
_timeline: _timeline:
title: "時間軸如何運作" title: "時間軸如何運作"
description1: "Misskey根據使用方式提供了多個時間軸伺服器可能會將部份時間軸停用。" description1: "Misskey根據使用方式提供了多個時間軸伺服器可能會將部份時間軸停用。"
@ -1397,6 +1419,7 @@ _serverSettings:
fanoutTimelineDescription: "如果啟用的話檢索各個時間軸的性能會顯著提昇資料庫的負荷也會減少。不過Redis 的記憶體使用量會增加。如果伺服器的記憶體容量比較少或者運行不穩定,可以停用。" fanoutTimelineDescription: "如果啟用的話檢索各個時間軸的性能會顯著提昇資料庫的負荷也會減少。不過Redis 的記憶體使用量會增加。如果伺服器的記憶體容量比較少或者運行不穩定,可以停用。"
fanoutTimelineDbFallback: "資料庫的回退" fanoutTimelineDbFallback: "資料庫的回退"
fanoutTimelineDbFallbackDescription: "若啟用,在時間軸沒有快取的情況下將執行回退處理以額外查詢資料庫。若停用,可以透過不執行回退處理來進一步減少伺服器的負荷,但會限制可取得的時間軸範圍。" fanoutTimelineDbFallbackDescription: "若啟用,在時間軸沒有快取的情況下將執行回退處理以額外查詢資料庫。若停用,可以透過不執行回退處理來進一步減少伺服器的負荷,但會限制可取得的時間軸範圍。"
reactionsBufferingDescription: "啟用時,可以顯著提高建立反應時的效能並減少資料庫的負載。 但是Redis 記憶體使用量會增加。"
inquiryUrl: "聯絡表單網址" inquiryUrl: "聯絡表單網址"
inquiryUrlDescription: "指定伺服器運營者的聯絡表單網址,或包含運營者聯絡資訊網頁的網址。" inquiryUrlDescription: "指定伺服器運營者的聯絡表單網址,或包含運營者聯絡資訊網頁的網址。"
_accountMigration: _accountMigration:
@ -1730,6 +1753,11 @@ _role:
canSearchNotes: "可否搜尋貼文" canSearchNotes: "可否搜尋貼文"
canUseTranslator: "使用翻譯功能" canUseTranslator: "使用翻譯功能"
avatarDecorationLimit: "頭像裝飾的最大設置量" avatarDecorationLimit: "頭像裝飾的最大設置量"
canImportAntennas: "允許匯入天線"
canImportBlocking: "允許匯入封鎖名單"
canImportFollowing: "允許匯入跟隨名單"
canImportMuting: "允許匯入靜音名單"
canImportUserLists: "允許匯入清單"
_condition: _condition:
roleAssignedTo: "手動指派角色完成" roleAssignedTo: "手動指派角色完成"
isLocal: "本地使用者" isLocal: "本地使用者"
@ -2224,6 +2252,9 @@ _profile:
changeBanner: "變更橫幅圖像" changeBanner: "變更橫幅圖像"
verifiedLinkDescription: "如果輸入包含您個人資料的網站 URL欄位旁邊將出現驗證圖示。" verifiedLinkDescription: "如果輸入包含您個人資料的網站 URL欄位旁邊將出現驗證圖示。"
avatarDecorationMax: "最多可以設置 {max} 個裝飾。" avatarDecorationMax: "最多可以設置 {max} 個裝飾。"
followedMessage: "被追隨時的訊息"
followedMessageDescription: "可以設定被追隨時顯示給對方的訊息。"
followedMessageDescriptionForLockedAccount: "如果追隨是需要審核的話,在允許追隨請求之後顯示。"
_exportOrImport: _exportOrImport:
allNotes: "所有貼文" allNotes: "所有貼文"
favoritedNotes: "「我的最愛」貼文" favoritedNotes: "「我的最愛」貼文"
@ -2362,6 +2393,8 @@ _notification:
renotedBySomeUsers: "{n}人做了轉發" renotedBySomeUsers: "{n}人做了轉發"
followedBySomeUsers: "被{n}人追隨了" followedBySomeUsers: "被{n}人追隨了"
flushNotification: "重置通知歷史紀錄" flushNotification: "重置通知歷史紀錄"
exportOfXCompleted: "{x} 的匯出已完成。"
login: "已登入"
_types: _types:
all: "全部 " all: "全部 "
note: "使用者的最新貼文" note: "使用者的最新貼文"
@ -2376,6 +2409,9 @@ _notification:
followRequestAccepted: "追隨請求已接受" followRequestAccepted: "追隨請求已接受"
roleAssigned: "已授予角色" roleAssigned: "已授予角色"
achievementEarned: "獲得成就" achievementEarned: "獲得成就"
exportCompleted: "已完成匯出。"
login: "登入"
test: "通知測試"
app: "應用程式通知" app: "應用程式通知"
_actions: _actions:
followBack: "追隨回去" followBack: "追隨回去"
@ -2442,6 +2478,7 @@ _webhookSettings:
abuseReportResolved: "當處理了使用者的檢舉時" abuseReportResolved: "當處理了使用者的檢舉時"
userCreated: "使用者被新增時" userCreated: "使用者被新增時"
deleteConfirm: "請問是否要刪除 Webhook" deleteConfirm: "請問是否要刪除 Webhook"
testRemarks: "按下切換開關右側的按鈕,就會將假資料發送至 Webhook。"
_abuseReport: _abuseReport:
_notificationRecipient: _notificationRecipient:
createRecipient: "新增接收檢舉的通知對象" createRecipient: "新增接收檢舉的通知對象"
@ -2640,3 +2677,17 @@ _contextMenu:
app: "應用程式" app: "應用程式"
appWithShift: "Shift 鍵應用程式" appWithShift: "Shift 鍵應用程式"
native: "瀏覽器的使用者介面" native: "瀏覽器的使用者介面"
_embedCodeGen:
title: "自訂嵌入程式碼"
header: "檢視標頭 "
autoload: "自動繼續載入(不建議)"
maxHeight: "最大高度"
maxHeightDescription: "設定為 0 時代表沒有最大值。請指定某個值以避免小工具持續在縱向延伸。"
maxHeightWarn: "最大高度限制已停用0。如果這個變更不是您想要的請將最大高度設定為某個值。"
previewIsNotActual: "由於超出了預覽畫面可顯示的範圍,因此顯示內容會與實際嵌入時有所不同。"
rounded: "圓角"
border: "給外框加上邊框"
applyToPreview: "反映在預覽中"
generateCode: "建立嵌入程式碼"
codeGenerated: "已產生程式碼"
codeGeneratedDescription: "請將產生的程式碼貼到您的網站上。"

View file

@ -1,6 +1,6 @@
{ {
"name": "sharkey", "name": "sharkey",
"version": "2024.9.0-dev", "version": "2024.9.1-rc",
"codename": "shonk", "codename": "shonk",
"repository": { "repository": {
"type": "git", "type": "git",
@ -8,7 +8,9 @@
}, },
"packageManager": "pnpm@9.6.0", "packageManager": "pnpm@9.6.0",
"workspaces": [ "workspaces": [
"packages/frontend-shared",
"packages/frontend", "packages/frontend",
"packages/frontend-embed",
"packages/backend", "packages/backend",
"packages/sw", "packages/sw",
"packages/misskey-js", "packages/misskey-js",
@ -32,9 +34,13 @@
"watch": "pnpm dev", "watch": "pnpm dev",
"dev": "node scripts/dev.mjs", "dev": "node scripts/dev.mjs",
"lint": "pnpm -r lint", "lint": "pnpm -r lint",
"lint-all": "pnpm -r --no-bail lint",
"eslint": "pnpm -r eslint",
"eslint-all": "pnpm -r --no-bail eslint",
"cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts", "cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts",
"cy:run": "pnpm cypress run", "cy:run": "pnpm cypress run",
"e2e": "pnpm start-server-and-test start:test http://localhost:61812 cy:run", "e2e": "pnpm start-server-and-test start:test http://localhost:61812 cy:run",
"e2e-dev-container": "cp ./.config/cypress-devcontainer.yml ./.config/test.yml && pnpm start-server-and-test start:test http://localhost:61812 cy:run",
"jest": "cd packages/backend && pnpm jest", "jest": "cd packages/backend && pnpm jest",
"jest-and-coverage": "cd packages/backend && pnpm jest-and-coverage", "jest-and-coverage": "cd packages/backend && pnpm jest-and-coverage",
"test": "pnpm -r test", "test": "pnpm -r test",
@ -53,11 +59,11 @@
"fast-glob": "3.3.2", "fast-glob": "3.3.2",
"ignore-walk": "6.0.5", "ignore-walk": "6.0.5",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"postcss": "8.4.40", "postcss": "8.4.47",
"tar": "6.2.1", "tar": "6.2.1",
"terser": "5.31.3", "terser": "5.33.0",
"typescript": "5.5.4", "typescript": "5.6.2",
"esbuild": "0.23.0", "esbuild": "0.23.1",
"glob": "11.0.0" "glob": "11.0.0"
}, },
"devDependencies": { "devDependencies": {
@ -66,10 +72,10 @@
"@typescript-eslint/eslint-plugin": "7.17.0", "@typescript-eslint/eslint-plugin": "7.17.0",
"@typescript-eslint/parser": "7.17.0", "@typescript-eslint/parser": "7.17.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "13.13.1", "cypress": "13.14.2",
"eslint": "9.8.0", "eslint": "9.8.0",
"globals": "15.8.0", "globals": "15.9.0",
"ncp": "2.0.0", "ncp": "2.0.0",
"start-server-and-test": "2.0.4" "start-server-and-test": "2.0.8"
} }
} }

View file

@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: MIT
*/
//@ts-check
(() => {
/** @type {NodeListOf<HTMLIFrameElement>} */
const els = document.querySelectorAll('iframe[data-misskey-embed-id]');
window.addEventListener('message', function (event) {
els.forEach((el) => {
if (event.source !== el.contentWindow) {
return;
}
const id = el.dataset.misskeyEmbedId;
if (event.data.type === 'misskey:embed:ready') {
el.contentWindow?.postMessage({
type: 'misskey:embedParent:registerIframeId',
payload: {
iframeId: id,
}
}, '*');
}
if (event.data.type === 'misskey:embed:changeHeight' && event.data.iframeId === id) {
el.style.height = event.data.payload.height + 'px';
}
});
});
})();

View file

@ -1,5 +1,6 @@
import tsParser from '@typescript-eslint/parser'; import tsParser from '@typescript-eslint/parser';
import sharedConfig from '../shared/eslint.config.js'; import sharedConfig from '../shared/eslint.config.js';
import globals from 'globals';
export default [ export default [
...sharedConfig, ...sharedConfig,
@ -43,4 +44,25 @@ export default [
}], }],
}, },
}, },
{
files: ['src/server/web/**/*.js', 'src/server/web/**/*.ts'],
languageOptions: {
globals: {
...globals.browser,
LANGS: true,
CLIENT_ENTRY: true,
LANGS_VERSION: true,
},
},
},
{
ignores: [
"**/lib/",
"**/temp/",
"**/built/",
"**/coverage/",
"**/node_modules/",
"**/migration/",
]
},
]; ];

View file

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class FollowedMessage1723944246767 {
name = 'FollowedMessage1723944246767';
async up(queryRunner) {
await queryRunner.query('ALTER TABLE "user_profile" ADD "followedMessage" character varying(256)');
}
async down(queryRunner) {
await queryRunner.query('ALTER TABLE "user_profile" DROP COLUMN "followedMessage"');
}
}

View file

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class ReactionsBuffering1726804538569 {
name = 'ReactionsBuffering1726804538569'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "enableReactionsBuffering" boolean NOT NULL DEFAULT false`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableReactionsBuffering"`);
}
}

View file

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class UserScore1727491883993 {
name = 'UserScore1727491883993'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "user" ADD "score" integer NOT NULL DEFAULT '0'`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "score"`);
}
}

View file

@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class MetaFederation1727512908322 {
name = 'MetaFederation1727512908322'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "federation" character varying(128) NOT NULL DEFAULT 'all'`);
await queryRunner.query(`ALTER TABLE "meta" ADD "federationHosts" character varying(1024) array NOT NULL DEFAULT '{}'`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "federationHosts"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "federation"`);
}
}

View file

@ -20,7 +20,7 @@
"restart": "pnpm build && pnpm start", "restart": "pnpm build && pnpm start",
"dev": "node ./scripts/dev.mjs", "dev": "node ./scripts/dev.mjs",
"typecheck": "pnpm --filter megalodon build && tsc --noEmit && tsc -p test --noEmit", "typecheck": "pnpm --filter megalodon build && tsc --noEmit && tsc -p test --noEmit",
"eslint": "eslint --quiet \"src/**/*.ts\" --cache", "eslint": "eslint --quiet \"{src,test,js,@types}/**/*.{js,jsx,ts,tsx,vue}\" --cache",
"lint": "pnpm typecheck && pnpm eslint", "lint": "pnpm typecheck && pnpm eslint",
"jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.unit.cjs", "jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.unit.cjs",
"jest:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.e2e.cjs", "jest:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.e2e.cjs",
@ -65,24 +65,24 @@
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "3.620.0", "@aws-sdk/client-s3": "3.620.0",
"@aws-sdk/lib-storage": "3.620.0", "@aws-sdk/lib-storage": "3.620.0",
"@bull-board/api": "5.21.1", "@bull-board/api": "6.0.0",
"@bull-board/fastify": "5.21.1", "@bull-board/fastify": "6.0.0",
"@bull-board/ui": "5.21.1", "@bull-board/ui": "6.0.0",
"@discordapp/twemoji": "15.0.3", "@discordapp/twemoji": "15.1.0",
"@fastify/accepts": "4.3.0", "@fastify/accepts": "5.0.0",
"@fastify/cookie": "9.3.1", "@fastify/cookie": "10.0.0",
"@fastify/cors": "9.0.1", "@fastify/cors": "10.0.0",
"@fastify/express": "3.0.0", "@fastify/express": "4.0.0",
"@fastify/http-proxy": "9.5.0", "@fastify/http-proxy": "10.0.0",
"@fastify/multipart": "8.3.0", "@fastify/multipart": "9.0.0",
"@fastify/static": "7.0.4", "@fastify/static": "8.0.0",
"@fastify/view": "9.1.0", "@fastify/view": "10.0.0",
"@misskey-dev/sharp-read-bmp": "1.2.0", "@misskey-dev/sharp-read-bmp": "1.2.0",
"@misskey-dev/summaly": "5.1.0", "@misskey-dev/summaly": "5.1.0",
"@napi-rs/canvas": "^0.1.53", "@napi-rs/canvas": "0.1.56",
"@nestjs/common": "10.3.10", "@nestjs/common": "10.4.3",
"@nestjs/core": "10.3.10", "@nestjs/core": "10.4.3",
"@nestjs/testing": "10.3.10", "@nestjs/testing": "10.4.3",
"@peertube/http-signature": "1.7.0", "@peertube/http-signature": "1.7.0",
"@sentry/node": "8.20.0", "@sentry/node": "8.20.0",
"@sentry/profiling-node": "8.20.0", "@sentry/profiling-node": "8.20.0",
@ -100,8 +100,8 @@
"async-mutex": "0.5.0", "async-mutex": "0.5.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "2.0.5", "blurhash": "2.0.5",
"body-parser": "1.20.2", "body-parser": "1.20.3",
"bullmq": "5.10.4", "bullmq": "5.13.2",
"cacheable-lookup": "7.0.0", "cacheable-lookup": "7.0.0",
"cbor": "9.0.2", "cbor": "9.0.2",
"chalk": "5.3.0", "chalk": "5.3.0",
@ -112,12 +112,12 @@
"content-disposition": "0.5.4", "content-disposition": "0.5.4",
"date-fns": "2.30.0", "date-fns": "2.30.0",
"deep-email-validator": "0.1.21", "deep-email-validator": "0.1.21",
"fast-xml-parser": "^4.4.0", "fast-xml-parser": "4.4.1",
"fastify": "4.28.1", "fastify": "5.0.0",
"fastify-multer": "^2.0.3", "fastify-multer": "^2.0.3",
"fastify-raw-body": "4.3.0", "fastify-raw-body": "5.0.0",
"feed": "4.2.2", "feed": "4.2.2",
"file-type": "19.3.0", "file-type": "19.5.0",
"fluent-ffmpeg": "2.1.3", "fluent-ffmpeg": "2.1.3",
"form-data": "4.0.0", "form-data": "4.0.0",
"glob": "11.0.0", "glob": "11.0.0",
@ -127,16 +127,17 @@
"htmlescape": "1.1.1", "htmlescape": "1.1.1",
"http-link-header": "1.1.3", "http-link-header": "1.1.3",
"ioredis": "5.4.1", "ioredis": "5.4.1",
"ip-cidr": "4.0.1", "ip-cidr": "4.0.2",
"ipaddr.js": "2.2.0", "ipaddr.js": "2.2.0",
"is-svg": "5.0.1", "is-svg": "5.1.0",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"jsdom": "24.1.1", "jsdom": "24.1.1",
"json5": "2.2.3", "json5": "2.2.3",
"jsonld": "8.3.2", "jsonld": "8.3.2",
"jsrsasign": "11.1.0", "jsrsasign": "11.1.0",
"megalodon": "workspace:*", "megalodon": "workspace:*",
"meilisearch": "0.41.0", "meilisearch": "0.42.0",
"juice": "11.0.0",
"microformats-parser": "2.0.2", "microformats-parser": "2.0.2",
"mime-types": "2.1.35", "mime-types": "2.1.35",
"misskey-js": "workspace:*", "misskey-js": "workspace:*",
@ -145,24 +146,24 @@
"nanoid": "5.0.7", "nanoid": "5.0.7",
"nested-property": "4.0.0", "nested-property": "4.0.0",
"node-fetch": "3.3.2", "node-fetch": "3.3.2",
"nodemailer": "6.9.14", "nodemailer": "6.9.15",
"oauth": "0.10.0", "oauth": "0.10.0",
"oauth2orize": "1.12.0", "oauth2orize": "1.12.0",
"oauth2orize-pkce": "0.1.2", "oauth2orize-pkce": "0.1.2",
"os-utils": "0.0.14", "os-utils": "0.0.14",
"otpauth": "9.3.1", "otpauth": "9.3.2",
"parse5": "7.1.2", "parse5": "7.1.2",
"pg": "8.12.0", "pg": "8.13.0",
"pkce-challenge": "4.1.0", "pkce-challenge": "4.1.0",
"probe-image-size": "7.2.3", "probe-image-size": "7.2.3",
"promise-limit": "2.7.0", "promise-limit": "2.7.0",
"proxy-addr": "^2.0.7", "proxy-addr": "^2.0.7",
"pug": "3.0.3", "pug": "3.0.3",
"punycode": "2.3.1", "punycode": "2.3.1",
"qrcode": "1.5.3", "qrcode": "1.5.4",
"random-seed": "0.3.0", "random-seed": "0.3.0",
"ratelimiter": "3.4.1", "ratelimiter": "3.4.1",
"re2": "1.21.3", "re2": "1.21.4",
"redis-lock": "0.1.4", "redis-lock": "0.1.4",
"reflect-metadata": "0.2.2", "reflect-metadata": "0.2.2",
"rename": "1.0.4", "rename": "1.0.4",
@ -170,17 +171,17 @@
"rxjs": "7.8.1", "rxjs": "7.8.1",
"sanitize-html": "2.13.0", "sanitize-html": "2.13.0",
"secure-json-parse": "2.7.0", "secure-json-parse": "2.7.0",
"sharp": "0.33.4", "sharp": "0.33.5",
"slacc": "0.0.10", "slacc": "0.0.10",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0", "stringz": "2.1.0",
"systeminformation": "5.22.11", "systeminformation": "5.23.5",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tmp": "0.2.3", "tmp": "0.2.3",
"tsc-alias": "1.8.10", "tsc-alias": "1.8.10",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"typeorm": "0.3.20", "typeorm": "0.3.20",
"typescript": "5.5.4", "typescript": "5.6.2",
"ulid": "2.3.0", "ulid": "2.3.0",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"vary": "1.1.2", "vary": "1.1.2",
@ -190,7 +191,7 @@
}, },
"devDependencies": { "devDependencies": {
"@jest/globals": "29.7.0", "@jest/globals": "29.7.0",
"@nestjs/platform-express": "10.3.10", "@nestjs/platform-express": "10.4.3",
"@simplewebauthn/types": "10.0.0", "@simplewebauthn/types": "10.0.0",
"@swc/jest": "0.2.36", "@swc/jest": "0.2.36",
"@types/accepts": "1.3.7", "@types/accepts": "1.3.7",
@ -199,10 +200,10 @@
"@types/body-parser": "1.19.5", "@types/body-parser": "1.19.5",
"@types/color-convert": "2.0.3", "@types/color-convert": "2.0.3",
"@types/content-disposition": "0.5.8", "@types/content-disposition": "0.5.8",
"@types/fluent-ffmpeg": "2.1.24", "@types/fluent-ffmpeg": "2.1.26",
"@types/htmlescape": "1.1.3", "@types/htmlescape": "1.1.3",
"@types/http-link-header": "1.0.7", "@types/http-link-header": "1.0.7",
"@types/jest": "29.5.12", "@types/jest": "29.5.13",
"@types/js-yaml": "4.0.9", "@types/js-yaml": "4.0.9",
"@types/jsdom": "21.1.7", "@types/jsdom": "21.1.7",
"@types/jsonld": "1.5.15", "@types/jsonld": "1.5.15",
@ -210,11 +211,11 @@
"@types/mime-types": "2.1.4", "@types/mime-types": "2.1.4",
"@types/ms": "0.7.34", "@types/ms": "0.7.34",
"@types/node": "20.14.12", "@types/node": "20.14.12",
"@types/nodemailer": "6.4.15", "@types/nodemailer": "6.4.16",
"@types/oauth": "0.9.5", "@types/oauth": "0.9.5",
"@types/oauth2orize": "1.11.5", "@types/oauth2orize": "1.11.5",
"@types/oauth2orize-pkce": "0.1.2", "@types/oauth2orize-pkce": "0.1.2",
"@types/pg": "8.11.6", "@types/pg": "8.11.10",
"@types/proxy-addr": "^2.0.3", "@types/proxy-addr": "^2.0.3",
"@types/pug": "2.0.10", "@types/pug": "2.0.10",
"@types/punycode": "2.1.4", "@types/punycode": "2.1.4",
@ -222,7 +223,7 @@
"@types/random-seed": "0.3.5", "@types/random-seed": "0.3.5",
"@types/ratelimiter": "3.4.6", "@types/ratelimiter": "3.4.6",
"@types/rename": "1.0.7", "@types/rename": "1.0.7",
"@types/sanitize-html": "2.11.0", "@types/sanitize-html": "2.13.0",
"@types/semver": "7.5.8", "@types/semver": "7.5.8",
"@types/simple-oauth2": "5.0.7", "@types/simple-oauth2": "5.0.7",
"@types/sinonjs__fake-timers": "8.1.5", "@types/sinonjs__fake-timers": "8.1.5",
@ -231,17 +232,17 @@
"@types/uuid": "^9.0.4", "@types/uuid": "^9.0.4",
"@types/vary": "1.1.3", "@types/vary": "1.1.3",
"@types/web-push": "3.6.3", "@types/web-push": "3.6.3",
"@types/ws": "8.5.11", "@types/ws": "8.5.12",
"@typescript-eslint/eslint-plugin": "7.17.0", "@typescript-eslint/eslint-plugin": "7.17.0",
"@typescript-eslint/parser": "7.17.0", "@typescript-eslint/parser": "7.17.0",
"aws-sdk-client-mock": "4.0.1", "aws-sdk-client-mock": "4.0.1",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint-plugin-import": "2.29.1", "eslint-plugin-import": "2.30.0",
"execa": "9.3.0", "execa": "9.4.0",
"fkill": "9.0.0", "fkill": "9.0.0",
"jest": "29.7.0", "jest": "29.7.0",
"jest-mock": "29.7.0", "jest-mock": "29.7.0",
"nodemon": "3.1.4", "nodemon": "3.1.7",
"pid-port": "1.0.0", "pid-port": "1.0.0",
"simple-oauth2": "5.1.0" "simple-oauth2": "5.1.0"
} }

View file

@ -34,4 +34,5 @@ function connectToRedis(redisOptions) {
config.redisForPubsub, config.redisForPubsub,
config.redisForJobQueue, config.redisForJobQueue,
config.redisForTimelines, config.redisForTimelines,
config.redisForReactions,
])).forEach(connectToRedis); ])).forEach(connectToRedis);

View file

@ -13,6 +13,8 @@ import { createPostgresDataSource } from './postgres.js';
import { RepositoryModule } from './models/RepositoryModule.js'; import { RepositoryModule } from './models/RepositoryModule.js';
import { allSettled } from './misc/promise-tracker.js'; import { allSettled } from './misc/promise-tracker.js';
import type { Provider, OnApplicationShutdown } from '@nestjs/common'; import type { Provider, OnApplicationShutdown } from '@nestjs/common';
import { MiMeta } from '@/models/Meta.js';
import { GlobalEvents } from './core/GlobalEventService.js';
const $config: Provider = { const $config: Provider = {
provide: DI.config, provide: DI.config,
@ -78,11 +80,76 @@ const $redisForTimelines: Provider = {
inject: [DI.config], inject: [DI.config],
}; };
const $redisForReactions: Provider = {
provide: DI.redisForReactions,
useFactory: (config: Config) => {
return new Redis.Redis(config.redisForReactions);
},
inject: [DI.config],
};
const $meta: Provider = {
provide: DI.meta,
useFactory: async (db: DataSource, redisForSub: Redis.Redis) => {
const meta = await db.transaction(async transactionalEntityManager => {
// 過去のバグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する
const metas = await transactionalEntityManager.find(MiMeta, {
order: {
id: 'DESC',
},
});
const meta = metas[0];
if (meta) {
return meta;
} else {
// metaが空のときfetchMetaが同時に呼ばれるとここが同時に呼ばれてしまうことがあるのでフェイルセーフなupsertを使う
const saved = await transactionalEntityManager
.upsert(
MiMeta,
{
id: 'x',
},
['id'],
)
.then((x) => transactionalEntityManager.findOneByOrFail(MiMeta, x.identifiers[0]));
return saved;
}
});
async function onMessage(_: string, data: string): Promise<void> {
const obj = JSON.parse(data);
if (obj.channel === 'internal') {
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
switch (type) {
case 'metaUpdated': {
for (const key in body.after) {
(meta as any)[key] = (body.after as any)[key];
}
meta.proxyAccount = null; // joinなカラムは通常取ってこないので
break;
}
default:
break;
}
}
}
redisForSub.on('message', onMessage);
return meta;
},
inject: [DI.db, DI.redisForSub],
};
@Global() @Global()
@Module({ @Module({
imports: [RepositoryModule], imports: [RepositoryModule],
providers: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines], providers: [$config, $db, $meta, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, $redisForReactions],
exports: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, RepositoryModule], exports: [$config, $db, $meta, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, $redisForReactions, RepositoryModule],
}) })
export class GlobalModule implements OnApplicationShutdown { export class GlobalModule implements OnApplicationShutdown {
constructor( constructor(
@ -91,6 +158,7 @@ export class GlobalModule implements OnApplicationShutdown {
@Inject(DI.redisForPub) private redisForPub: Redis.Redis, @Inject(DI.redisForPub) private redisForPub: Redis.Redis,
@Inject(DI.redisForSub) private redisForSub: Redis.Redis, @Inject(DI.redisForSub) private redisForSub: Redis.Redis,
@Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis, @Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis,
@Inject(DI.redisForReactions) private redisForReactions: Redis.Redis,
) { } ) { }
public async dispose(): Promise<void> { public async dispose(): Promise<void> {
@ -103,6 +171,7 @@ export class GlobalModule implements OnApplicationShutdown {
this.redisForPub.disconnect(), this.redisForPub.disconnect(),
this.redisForSub.disconnect(), this.redisForSub.disconnect(),
this.redisForTimelines.disconnect(), this.redisForTimelines.disconnect(),
this.redisForReactions.disconnect(),
]); ]);
} }

View file

@ -51,6 +51,7 @@ type Source = {
redisForPubsub?: RedisOptionsSource; redisForPubsub?: RedisOptionsSource;
redisForJobQueue?: RedisOptionsSource; redisForJobQueue?: RedisOptionsSource;
redisForTimelines?: RedisOptionsSource; redisForTimelines?: RedisOptionsSource;
redisForReactions?: RedisOptionsSource;
meilisearch?: { meilisearch?: {
host: string; host: string;
port: string; port: string;
@ -146,7 +147,7 @@ export type Config = {
proxySmtp: string | undefined; proxySmtp: string | undefined;
proxyBypassHosts: string[] | undefined; proxyBypassHosts: string[] | undefined;
allowedPrivateNetworks: string[] | undefined; allowedPrivateNetworks: string[] | undefined;
maxFileSize: number | undefined; maxFileSize: number;
maxNoteLength: number; maxNoteLength: number;
clusterLimit: number | undefined; clusterLimit: number | undefined;
id: string; id: string;
@ -177,8 +178,10 @@ export type Config = {
authUrl: string; authUrl: string;
driveUrl: string; driveUrl: string;
userAgent: string; userAgent: string;
clientEntry: string; frontendEntry: string;
clientManifestExists: boolean; frontendManifestExists: boolean;
frontendEmbedEntry: string;
frontendEmbedManifestExists: boolean;
mediaProxy: string; mediaProxy: string;
externalMediaProxyEnabled: boolean; externalMediaProxyEnabled: boolean;
videoThumbnailGenerator: string | null; videoThumbnailGenerator: string | null;
@ -186,6 +189,7 @@ export type Config = {
redisForPubsub: RedisOptions & RedisOptionsSource; redisForPubsub: RedisOptions & RedisOptionsSource;
redisForJobQueue: RedisOptions & RedisOptionsSource; redisForJobQueue: RedisOptions & RedisOptionsSource;
redisForTimelines: RedisOptions & RedisOptionsSource; redisForTimelines: RedisOptions & RedisOptionsSource;
redisForReactions: RedisOptions & RedisOptionsSource;
sentryForBackend: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; } | undefined; sentryForBackend: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; } | undefined;
sentryForFrontend: { options: Partial<Sentry.NodeOptions> } | undefined; sentryForFrontend: { options: Partial<Sentry.NodeOptions> } | undefined;
perChannelMaxNoteCacheCount: number; perChannelMaxNoteCacheCount: number;
@ -219,10 +223,15 @@ const path = process.env.MISSKEY_CONFIG_YML
export function loadConfig(): Config { export function loadConfig(): Config {
const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../built/meta.json`, 'utf-8')); const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../built/meta.json`, 'utf-8'));
const clientManifestExists = fs.existsSync(`${_dirname}/../../../built/_vite_/manifest.json`);
const clientManifest = clientManifestExists ? const frontendManifestExists = fs.existsSync(_dirname + '/../../../built/_frontend_vite_/manifest.json');
JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_vite_/manifest.json`, 'utf-8')) 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: 'src/_boot_.ts' } };
const frontendEmbedManifest = frontendEmbedManifestExists ?
JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_frontend_embed_vite_/manifest.json`, 'utf-8'))
: { 'src/boot.ts': { file: 'src/boot.ts' } };
const configFiles = globSync(path).sort(); const configFiles = globSync(path).sort();
@ -282,6 +291,7 @@ export function loadConfig(): Config {
redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis, redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis,
redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis, redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis,
redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis, redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis,
redisForReactions: config.redisForReactions ? convertRedisOptions(config.redisForReactions, host) : redis,
sentryForBackend: config.sentryForBackend, sentryForBackend: config.sentryForBackend,
sentryForFrontend: config.sentryForFrontend, sentryForFrontend: config.sentryForFrontend,
id: config.id, id: config.id,
@ -289,7 +299,7 @@ export function loadConfig(): Config {
proxySmtp: config.proxySmtp, proxySmtp: config.proxySmtp,
proxyBypassHosts: config.proxyBypassHosts, proxyBypassHosts: config.proxyBypassHosts,
allowedPrivateNetworks: config.allowedPrivateNetworks, allowedPrivateNetworks: config.allowedPrivateNetworks,
maxFileSize: config.maxFileSize, maxFileSize: config.maxFileSize ?? 262144000,
maxNoteLength: config.maxNoteLength ?? 3000, maxNoteLength: config.maxNoteLength ?? 3000,
clusterLimit: config.clusterLimit, clusterLimit: config.clusterLimit,
outgoingAddress: config.outgoingAddress, outgoingAddress: config.outgoingAddress,
@ -313,8 +323,10 @@ export function loadConfig(): Config {
config.videoThumbnailGenerator.endsWith('/') ? config.videoThumbnailGenerator.substring(0, config.videoThumbnailGenerator.length - 1) : config.videoThumbnailGenerator config.videoThumbnailGenerator.endsWith('/') ? config.videoThumbnailGenerator.substring(0, config.videoThumbnailGenerator.length - 1) : config.videoThumbnailGenerator
: null, : null,
userAgent: `Misskey/${version} (${config.url})`, userAgent: `Misskey/${version} (${config.url})`,
clientEntry: clientManifest['src/_boot_.ts'], frontendEntry: frontendManifest['src/_boot_.ts'],
clientManifestExists: clientManifestExists, frontendManifestExists: frontendManifestExists,
frontendEmbedEntry: frontendEmbedManifest['src/boot.ts'],
frontendEmbedManifestExists: frontendEmbedManifestExists,
perChannelMaxNoteCacheCount: config.perChannelMaxNoteCacheCount ?? 1000, perChannelMaxNoteCacheCount: config.perChannelMaxNoteCacheCount ?? 1000,
perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 500, perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 500,
deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7), deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7),
@ -455,7 +467,7 @@ function applyEnvOverrides(config: Source) {
_apply_top(['db', ['host', 'port', 'db', 'user', 'pass', 'disableCache']]); _apply_top(['db', ['host', 'port', 'db', 'user', 'pass', 'disableCache']]);
_apply_top(['dbSlaves', Array.from((config.dbSlaves ?? []).keys()), ['host', 'port', 'db', 'user', 'pass']]); _apply_top(['dbSlaves', Array.from((config.dbSlaves ?? []).keys()), ['host', 'port', 'db', 'user', 'pass']]);
_apply_top([ _apply_top([
['redis', 'redisForPubsub', 'redisForJobQueue', 'redisForTimelines'], ['redis', 'redisForPubsub', 'redisForJobQueue', 'redisForTimelines', 'redisForReactions'],
['host', 'port', 'username', 'pass', 'db', 'prefix'], ['host', 'port', 'username', 'pass', 'db', 'prefix'],
]); ]);
_apply_top(['meilisearch', ['host', 'port', 'apikey', 'ssl', 'index', 'scope']]); _apply_top(['meilisearch', ['host', 'port', 'apikey', 'ssl', 'index', 'scope']]);

View file

@ -8,6 +8,8 @@ export const MAX_NOTE_TEXT_LENGTH = 3000;
export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min
export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days
export const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
//#region hard limits //#region hard limits
// If you change DB_* values, you must also change the DB schema. // If you change DB_* values, you must also change the DB schema.

View file

@ -14,10 +14,10 @@ import type {
AbuseReportNotificationRecipientRepository, AbuseReportNotificationRecipientRepository,
MiAbuseReportNotificationRecipient, MiAbuseReportNotificationRecipient,
MiAbuseUserReport, MiAbuseUserReport,
MiMeta,
MiUser, MiUser,
} from '@/models/_.js'; } from '@/models/_.js';
import { EmailService } from '@/core/EmailService.js'; import { EmailService } from '@/core/EmailService.js';
import { MetaService } from '@/core/MetaService.js';
import { RoleService } from '@/core/RoleService.js'; import { RoleService } from '@/core/RoleService.js';
import { RecipientMethod } from '@/models/AbuseReportNotificationRecipient.js'; import { RecipientMethod } from '@/models/AbuseReportNotificationRecipient.js';
import { ModerationLogService } from '@/core/ModerationLogService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js';
@ -27,15 +27,19 @@ import { IdService } from './IdService.js';
@Injectable() @Injectable()
export class AbuseReportNotificationService implements OnApplicationShutdown { export class AbuseReportNotificationService implements OnApplicationShutdown {
constructor( constructor(
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.abuseReportNotificationRecipientRepository) @Inject(DI.abuseReportNotificationRecipientRepository)
private abuseReportNotificationRecipientRepository: AbuseReportNotificationRecipientRepository, private abuseReportNotificationRecipientRepository: AbuseReportNotificationRecipientRepository,
@Inject(DI.redisForSub) @Inject(DI.redisForSub)
private redisForSub: Redis.Redis, private redisForSub: Redis.Redis,
private idService: IdService, private idService: IdService,
private roleService: RoleService, private roleService: RoleService,
private systemWebhookService: SystemWebhookService, private systemWebhookService: SystemWebhookService,
private emailService: EmailService, private emailService: EmailService,
private metaService: MetaService,
private moderationLogService: ModerationLogService, private moderationLogService: ModerationLogService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
) { ) {
@ -93,10 +97,8 @@ export class AbuseReportNotificationService implements OnApplicationShutdown {
.filter(x => x != null), .filter(x => x != null),
); );
// 送信先の鮮度を保つため、毎回取得する
const meta = await this.metaService.fetch(true);
recipientEMailAddresses.push( recipientEMailAddresses.push(
...(meta.email ? [meta.email] : []), ...(this.meta.email ? [this.meta.email] : []),
); );
if (recipientEMailAddresses.length <= 0) { if (recipientEMailAddresses.length <= 0) {

View file

@ -9,7 +9,7 @@ import { IsNull, In, MoreThan, Not } from 'typeorm';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/User.js'; import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/User.js';
import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, UserListMembershipsRepository, UsersRepository } from '@/models/_.js'; import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MiMeta, MutingsRepository, UserListMembershipsRepository, UsersRepository } from '@/models/_.js';
import type { RelationshipJobData, ThinUser } from '@/queue/types.js'; import type { RelationshipJobData, ThinUser } from '@/queue/types.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
@ -22,13 +22,15 @@ import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { ProxyAccountService } from '@/core/ProxyAccountService.js'; import { ProxyAccountService } from '@/core/ProxyAccountService.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { MetaService } from '@/core/MetaService.js';
import InstanceChart from '@/core/chart/charts/instance.js'; import InstanceChart from '@/core/chart/charts/instance.js';
import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js'; import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
@Injectable() @Injectable()
export class AccountMoveService { export class AccountMoveService {
constructor( constructor(
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@ -57,7 +59,6 @@ export class AccountMoveService {
private perUserFollowingChart: PerUserFollowingChart, private perUserFollowingChart: PerUserFollowingChart,
private federatedInstanceService: FederatedInstanceService, private federatedInstanceService: FederatedInstanceService,
private instanceChart: InstanceChart, private instanceChart: InstanceChart,
private metaService: MetaService,
private relayService: RelayService, private relayService: RelayService,
private queueService: QueueService, private queueService: QueueService,
) { ) {
@ -276,7 +277,7 @@ export class AccountMoveService {
if (this.userEntityService.isRemoteUser(oldAccount)) { if (this.userEntityService.isRemoteUser(oldAccount)) {
this.federatedInstanceService.fetch(oldAccount.host).then(async i => { this.federatedInstanceService.fetch(oldAccount.host).then(async i => {
this.instancesRepository.decrement({ id: i.id }, 'followersCount', localFollowerIds.length); this.instancesRepository.decrement({ id: i.id }, 'followersCount', localFollowerIds.length);
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) { if (this.meta.enableChartsForFederatedInstances) {
this.instanceChart.updateFollowers(i.host, false); this.instanceChart.updateFollowers(i.host, false);
} }
}); });

View file

@ -123,11 +123,14 @@ export class AntennaService implements OnApplicationShutdown {
if (antenna.src === 'home') { if (antenna.src === 'home') {
// TODO // TODO
} else if (antenna.src === 'list') { } else if (antenna.src === 'list') {
const listUsers = (await this.userListMembershipsRepository.findBy({ if (antenna.userListId == null) return false;
userListId: antenna.userListId!, const exists = await this.userListMembershipsRepository.exists({
})).map(x => x.userId); where: {
userListId: antenna.userListId,
if (!listUsers.includes(note.userId)) return false; userId: note.userId,
},
});
if (!exists) return false;
} else if (antenna.src === 'users') { } else if (antenna.src === 'users') {
const accts = antenna.users.map(x => { const accts = antenna.users.map(x => {
const { username, host } = Acct.parse(x); const { username, host } = Acct.parse(x);

View file

@ -13,6 +13,7 @@ import {
import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js'; import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
import { SystemWebhookService } from '@/core/SystemWebhookService.js'; import { SystemWebhookService } from '@/core/SystemWebhookService.js';
import { UserSearchService } from '@/core/UserSearchService.js'; import { UserSearchService } from '@/core/UserSearchService.js';
import { WebhookTestService } from '@/core/WebhookTestService.js';
import { AccountMoveService } from './AccountMoveService.js'; import { AccountMoveService } from './AccountMoveService.js';
import { AccountUpdateService } from './AccountUpdateService.js'; import { AccountUpdateService } from './AccountUpdateService.js';
import { AnnouncementService } from './AnnouncementService.js'; import { AnnouncementService } from './AnnouncementService.js';
@ -50,6 +51,7 @@ import { PollService } from './PollService.js';
import { PushNotificationService } from './PushNotificationService.js'; import { PushNotificationService } from './PushNotificationService.js';
import { QueryService } from './QueryService.js'; import { QueryService } from './QueryService.js';
import { ReactionService } from './ReactionService.js'; import { ReactionService } from './ReactionService.js';
import { ReactionsBufferingService } from './ReactionsBufferingService.js';
import { RelayService } from './RelayService.js'; import { RelayService } from './RelayService.js';
import { RoleService } from './RoleService.js'; import { RoleService } from './RoleService.js';
import { S3Service } from './S3Service.js'; import { S3Service } from './S3Service.js';
@ -195,6 +197,7 @@ const $ProxyAccountService: Provider = { provide: 'ProxyAccountService', useExis
const $PushNotificationService: Provider = { provide: 'PushNotificationService', useExisting: PushNotificationService }; const $PushNotificationService: Provider = { provide: 'PushNotificationService', useExisting: PushNotificationService };
const $QueryService: Provider = { provide: 'QueryService', useExisting: QueryService }; const $QueryService: Provider = { provide: 'QueryService', useExisting: QueryService };
const $ReactionService: Provider = { provide: 'ReactionService', useExisting: ReactionService }; const $ReactionService: Provider = { provide: 'ReactionService', useExisting: ReactionService };
const $ReactionsBufferingService: Provider = { provide: 'ReactionsBufferingService', useExisting: ReactionsBufferingService };
const $RelayService: Provider = { provide: 'RelayService', useExisting: RelayService }; const $RelayService: Provider = { provide: 'RelayService', useExisting: RelayService };
const $RoleService: Provider = { provide: 'RoleService', useExisting: RoleService }; const $RoleService: Provider = { provide: 'RoleService', useExisting: RoleService };
const $S3Service: Provider = { provide: 'S3Service', useExisting: S3Service }; const $S3Service: Provider = { provide: 'S3Service', useExisting: S3Service };
@ -214,6 +217,7 @@ const $UserAuthService: Provider = { provide: 'UserAuthService', useExisting: Us
const $VideoProcessingService: Provider = { provide: 'VideoProcessingService', useExisting: VideoProcessingService }; const $VideoProcessingService: Provider = { provide: 'VideoProcessingService', useExisting: VideoProcessingService };
const $UserWebhookService: Provider = { provide: 'UserWebhookService', useExisting: UserWebhookService }; const $UserWebhookService: Provider = { provide: 'UserWebhookService', useExisting: UserWebhookService };
const $SystemWebhookService: Provider = { provide: 'SystemWebhookService', useExisting: SystemWebhookService }; const $SystemWebhookService: Provider = { provide: 'SystemWebhookService', useExisting: SystemWebhookService };
const $WebhookTestService: Provider = { provide: 'WebhookTestService', useExisting: WebhookTestService };
const $UtilityService: Provider = { provide: 'UtilityService', useExisting: UtilityService }; const $UtilityService: Provider = { provide: 'UtilityService', useExisting: UtilityService };
const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: FileInfoService }; const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: FileInfoService };
const $SearchService: Provider = { provide: 'SearchService', useExisting: SearchService }; const $SearchService: Provider = { provide: 'SearchService', useExisting: SearchService };
@ -346,6 +350,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
PushNotificationService, PushNotificationService,
QueryService, QueryService,
ReactionService, ReactionService,
ReactionsBufferingService,
RelayService, RelayService,
RoleService, RoleService,
S3Service, S3Service,
@ -365,6 +370,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
VideoProcessingService, VideoProcessingService,
UserWebhookService, UserWebhookService,
SystemWebhookService, SystemWebhookService,
WebhookTestService,
UtilityService, UtilityService,
FileInfoService, FileInfoService,
SearchService, SearchService,
@ -493,6 +499,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
$PushNotificationService, $PushNotificationService,
$QueryService, $QueryService,
$ReactionService, $ReactionService,
$ReactionsBufferingService,
$RelayService, $RelayService,
$RoleService, $RoleService,
$S3Service, $S3Service,
@ -512,6 +519,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
$VideoProcessingService, $VideoProcessingService,
$UserWebhookService, $UserWebhookService,
$SystemWebhookService, $SystemWebhookService,
$WebhookTestService,
$UtilityService, $UtilityService,
$FileInfoService, $FileInfoService,
$SearchService, $SearchService,
@ -641,6 +649,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
PushNotificationService, PushNotificationService,
QueryService, QueryService,
ReactionService, ReactionService,
ReactionsBufferingService,
RelayService, RelayService,
RoleService, RoleService,
S3Service, S3Service,
@ -660,6 +669,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
VideoProcessingService, VideoProcessingService,
UserWebhookService, UserWebhookService,
SystemWebhookService, SystemWebhookService,
WebhookTestService,
UtilityService, UtilityService,
FileInfoService, FileInfoService,
SearchService, SearchService,
@ -787,6 +797,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
$PushNotificationService, $PushNotificationService,
$QueryService, $QueryService,
$ReactionService, $ReactionService,
$ReactionsBufferingService,
$RelayService, $RelayService,
$RoleService, $RoleService,
$S3Service, $S3Service,
@ -806,6 +817,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
$VideoProcessingService, $VideoProcessingService,
$UserWebhookService, $UserWebhookService,
$SystemWebhookService, $SystemWebhookService,
$WebhookTestService,
$UtilityService, $UtilityService,
$FileInfoService, $FileInfoService,
$SearchService, $SearchService,

View file

@ -42,7 +42,7 @@ export class DownloadService {
const timeout = options.timeout ?? 30 * 1000; const timeout = options.timeout ?? 30 * 1000;
const operationTimeout = options.operationTimeout ?? 60 * 1000; const operationTimeout = options.operationTimeout ?? 60 * 1000;
const maxSize = options.maxSize ?? this.config.maxFileSize ?? 262144000; const maxSize = options.maxSize ?? this.config.maxFileSize;
const urlObj = new URL(url); const urlObj = new URL(url);
let filename = urlObj.pathname.split('/').pop() ?? 'untitled'; let filename = urlObj.pathname.split('/').pop() ?? 'untitled';

View file

@ -11,11 +11,10 @@ import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
import { IsNull } from 'typeorm'; import { IsNull } from 'typeorm';
import { DeleteObjectCommandInput, PutObjectCommandInput, NoSuchKey } from '@aws-sdk/client-s3'; import { DeleteObjectCommandInput, PutObjectCommandInput, NoSuchKey } from '@aws-sdk/client-s3';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository } from '@/models/_.js'; import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository, MiMeta } from '@/models/_.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import Logger from '@/logger.js'; import Logger from '@/logger.js';
import type { MiRemoteUser, MiUser } from '@/models/User.js'; import type { MiRemoteUser, MiUser } from '@/models/User.js';
import { MetaService } from '@/core/MetaService.js';
import { MiDriveFile } from '@/models/DriveFile.js'; import { MiDriveFile } from '@/models/DriveFile.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
@ -99,6 +98,9 @@ export class DriveService {
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@ -115,7 +117,6 @@ export class DriveService {
private userEntityService: UserEntityService, private userEntityService: UserEntityService,
private driveFileEntityService: DriveFileEntityService, private driveFileEntityService: DriveFileEntityService,
private idService: IdService, private idService: IdService,
private metaService: MetaService,
private downloadService: DownloadService, private downloadService: DownloadService,
private internalStorageService: InternalStorageService, private internalStorageService: InternalStorageService,
private s3Service: S3Service, private s3Service: S3Service,
@ -149,9 +150,7 @@ export class DriveService {
// thunbnail, webpublic を必要なら生成 // thunbnail, webpublic を必要なら生成
const alts = await this.generateAlts(path, type, !file.uri); const alts = await this.generateAlts(path, type, !file.uri);
const meta = await this.metaService.fetch(); if (this.meta.useObjectStorage) {
if (meta.useObjectStorage) {
//#region ObjectStorage params //#region ObjectStorage params
let [ext] = (name.match(/\.([a-zA-Z0-9_-]+)$/) ?? ['']); let [ext] = (name.match(/\.([a-zA-Z0-9_-]+)$/) ?? ['']);
@ -170,11 +169,11 @@ export class DriveService {
ext = ''; ext = '';
} }
const baseUrl = meta.objectStorageBaseUrl const baseUrl = this.meta.objectStorageBaseUrl
?? `${ meta.objectStorageUseSSL ? 'https' : 'http' }://${ meta.objectStorageEndpoint }${ meta.objectStoragePort ? `:${meta.objectStoragePort}` : '' }/${ meta.objectStorageBucket }`; ?? `${ this.meta.objectStorageUseSSL ? 'https' : 'http' }://${ this.meta.objectStorageEndpoint }${ this.meta.objectStoragePort ? `:${this.meta.objectStoragePort}` : '' }/${ this.meta.objectStorageBucket }`;
// for original // for original
const key = `${meta.objectStoragePrefix}/${randomUUID()}${ext}`; const key = `${this.meta.objectStoragePrefix}/${randomUUID()}${ext}`;
const url = `${ baseUrl }/${ key }`; const url = `${ baseUrl }/${ key }`;
// for alts // for alts
@ -191,7 +190,7 @@ export class DriveService {
]; ];
if (alts.webpublic) { if (alts.webpublic) {
webpublicKey = `${meta.objectStoragePrefix}/webpublic-${randomUUID()}.${alts.webpublic.ext}`; webpublicKey = `${this.meta.objectStoragePrefix}/webpublic-${randomUUID()}.${alts.webpublic.ext}`;
webpublicUrl = `${ baseUrl }/${ webpublicKey }`; webpublicUrl = `${ baseUrl }/${ webpublicKey }`;
this.registerLogger.info(`uploading webpublic: ${webpublicKey}`); this.registerLogger.info(`uploading webpublic: ${webpublicKey}`);
@ -199,7 +198,7 @@ export class DriveService {
} }
if (alts.thumbnail) { if (alts.thumbnail) {
thumbnailKey = `${meta.objectStoragePrefix}/thumbnail-${randomUUID()}.${alts.thumbnail.ext}`; thumbnailKey = `${this.meta.objectStoragePrefix}/thumbnail-${randomUUID()}.${alts.thumbnail.ext}`;
thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`; thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`;
this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`); this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`);
@ -376,10 +375,8 @@ export class DriveService {
if (type === 'image/apng') type = 'image/png'; if (type === 'image/apng') type = 'image/png';
if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream'; if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream';
const meta = await this.metaService.fetch();
const params = { const params = {
Bucket: meta.objectStorageBucket, Bucket: this.meta.objectStorageBucket,
Key: key, Key: key,
Body: stream, Body: stream,
ContentType: type, ContentType: type,
@ -392,9 +389,9 @@ export class DriveService {
// 許可されているファイル形式でしか拡張子をつけない // 許可されているファイル形式でしか拡張子をつけない
ext ? correctFilename(filename, ext) : filename, ext ? correctFilename(filename, ext) : filename,
); );
if (meta.objectStorageSetPublicRead) params.ACL = 'public-read'; if (this.meta.objectStorageSetPublicRead) params.ACL = 'public-read';
await this.s3Service.upload(meta, params) await this.s3Service.upload(this.meta, params)
.then( .then(
result => { result => {
if ('Bucket' in result) { // CompleteMultipartUploadCommandOutput if ('Bucket' in result) { // CompleteMultipartUploadCommandOutput
@ -463,9 +460,7 @@ export class DriveService {
requestHeaders = null, requestHeaders = null,
ext = null, ext = null,
}: AddFileArgs): Promise<MiDriveFile> { }: AddFileArgs): Promise<MiDriveFile> {
const instance = await this.metaService.fetch();
const userRoleNSFW = user && (await this.roleService.getUserPolicies(user.id)).alwaysMarkNsfw; const userRoleNSFW = user && (await this.roleService.getUserPolicies(user.id)).alwaysMarkNsfw;
const info = await this.fileInfoService.getFileInfo(path); const info = await this.fileInfoService.getFileInfo(path);
this.registerLogger.info(`${JSON.stringify(info)}`); this.registerLogger.info(`${JSON.stringify(info)}`);
@ -569,7 +564,7 @@ export class DriveService {
sensitive ?? false sensitive ?? false
: false; : false;
if (user && this.utilityService.isMediaSilencedHost(instance.mediaSilencedHosts, user.host)) file.isSensitive = true; if (user && this.utilityService.isMediaSilencedHost(this.meta.mediaSilencedHosts, user.host)) file.isSensitive = true;
if (info.sensitive && profile!.autoSensitive) file.isSensitive = true; if (info.sensitive && profile!.autoSensitive) file.isSensitive = true;
if (userRoleNSFW) file.isSensitive = true; if (userRoleNSFW) file.isSensitive = true;
@ -631,7 +626,7 @@ export class DriveService {
// ローカルユーザーのみ // ローカルユーザーのみ
this.perUserDriveChart.update(file, true); this.perUserDriveChart.update(file, true);
} else { } else {
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) { if (this.meta.enableChartsForFederatedInstances) {
this.instanceChart.updateDrive(file, true); this.instanceChart.updateDrive(file, true);
} }
} }
@ -778,7 +773,7 @@ export class DriveService {
// ローカルユーザーのみ // ローカルユーザーのみ
this.perUserDriveChart.update(file, false); this.perUserDriveChart.update(file, false);
} else { } else {
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) { if (this.meta.enableChartsForFederatedInstances) {
this.instanceChart.updateDrive(file, false); this.instanceChart.updateDrive(file, false);
} }
} }
@ -800,14 +795,13 @@ export class DriveService {
@bindThis @bindThis
public async deleteObjectStorageFile(key: string) { public async deleteObjectStorageFile(key: string) {
const meta = await this.metaService.fetch();
try { try {
const param = { const param = {
Bucket: meta.objectStorageBucket, Bucket: this.meta.objectStorageBucket,
Key: key, Key: key,
} as DeleteObjectCommandInput; } as DeleteObjectCommandInput;
await this.s3Service.delete(meta, param); await this.s3Service.delete(this.meta, param);
} catch (err: any) { } catch (err: any) {
if (err.name === 'NoSuchKey') { if (err.name === 'NoSuchKey') {
this.deleteLogger.warn(`The object storage had no such key to delete: ${key}. Skipping this.`, err as Error); this.deleteLogger.warn(`The object storage had no such key to delete: ${key}. Skipping this.`, err as Error);

View file

@ -5,18 +5,17 @@
import { URLSearchParams } from 'node:url'; import { URLSearchParams } from 'node:url';
import * as nodemailer from 'nodemailer'; import * as nodemailer from 'nodemailer';
import juice from 'juice';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { validate as validateEmail } from 'deep-email-validator'; import { validate as validateEmail } from 'deep-email-validator';
import { MetaService } from '@/core/MetaService.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
import type { UserProfilesRepository } from '@/models/_.js'; import type { MiMeta, UserProfilesRepository } from '@/models/_.js';
import { LoggerService } from '@/core/LoggerService.js'; import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { HttpRequestService } from '@/core/HttpRequestService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js';
import { QueueService } from '@/core/QueueService.js';
@Injectable() @Injectable()
export class EmailService { export class EmailService {
@ -26,49 +25,41 @@ export class EmailService {
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.userProfilesRepository) @Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository, private userProfilesRepository: UserProfilesRepository,
private metaService: MetaService,
private loggerService: LoggerService, private loggerService: LoggerService,
private utilityService: UtilityService, private utilityService: UtilityService,
private httpRequestService: HttpRequestService, private httpRequestService: HttpRequestService,
private queueService: QueueService,
) { ) {
this.logger = this.loggerService.getLogger('email'); this.logger = this.loggerService.getLogger('email');
} }
@bindThis @bindThis
public async sendEmail(to: string, subject: string, html: string, text: string) { public async sendEmail(to: string, subject: string, html: string, text: string) {
const meta = await this.metaService.fetch(true); if (!this.meta.enableEmail) return;
if (!meta.enableEmail) return;
const iconUrl = `${this.config.url}/static-assets/mi-white.png`; const iconUrl = `${this.config.url}/static-assets/mi-white.png`;
const emailSettingUrl = `${this.config.url}/settings/email`; const emailSettingUrl = `${this.config.url}/settings/email`;
const enableAuth = meta.smtpUser != null && meta.smtpUser !== ''; const enableAuth = this.meta.smtpUser != null && this.meta.smtpUser !== '';
const transporter = nodemailer.createTransport({ const transporter = nodemailer.createTransport({
host: meta.smtpHost, host: this.meta.smtpHost,
port: meta.smtpPort, port: this.meta.smtpPort,
secure: meta.smtpSecure, secure: this.meta.smtpSecure,
ignoreTLS: !enableAuth, ignoreTLS: !enableAuth,
proxy: this.config.proxySmtp, proxy: this.config.proxySmtp,
auth: enableAuth ? { auth: enableAuth ? {
user: meta.smtpUser, user: this.meta.smtpUser,
pass: meta.smtpPass, pass: this.meta.smtpPass,
} : undefined, } : undefined,
} as any); } as any);
try { const htmlContent = `<!doctype html>
// TODO: htmlサニタイズ
const info = await transporter.sendMail({
from: meta.email!,
to: to,
subject: subject,
text: text,
html: `<!doctype html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
@ -133,7 +124,7 @@ export class EmailService {
<body> <body>
<main> <main>
<header> <header>
<img src="${ meta.logoImageUrl ?? meta.iconUrl ?? iconUrl }"/> <img src="${ this.meta.logoImageUrl ?? this.meta.iconUrl ?? iconUrl }"/>
</header> </header>
<article> <article>
<h1>${ subject }</h1> <h1>${ subject }</h1>
@ -147,7 +138,18 @@ export class EmailService {
<a href="${ this.config.url }">${ this.config.host }</a> <a href="${ this.config.url }">${ this.config.host }</a>
</nav> </nav>
</body> </body>
</html>`, </html>`;
const inlinedHtml = juice(htmlContent);
try {
// TODO: htmlサニタイズ
const info = await transporter.sendMail({
from: this.meta.email!,
to: to,
subject: subject,
text: text,
html: inlinedHtml,
}); });
this.logger.info(`Message sent: ${info.messageId}`); this.logger.info(`Message sent: ${info.messageId}`);
@ -162,8 +164,6 @@ export class EmailService {
available: boolean; available: boolean;
reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned' | 'network' | 'blacklist'; reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned' | 'network' | 'blacklist';
}> { }> {
const meta = await this.metaService.fetch();
const exist = await this.userProfilesRepository.countBy({ const exist = await this.userProfilesRepository.countBy({
emailVerified: true, emailVerified: true,
email: emailAddress, email: emailAddress,
@ -181,11 +181,11 @@ export class EmailService {
reason?: string | null, reason?: string | null,
} = { valid: true, reason: null }; } = { valid: true, reason: null };
if (meta.enableActiveEmailValidation) { if (this.meta.enableActiveEmailValidation) {
if (meta.enableVerifymailApi && meta.verifymailAuthKey != null) { if (this.meta.enableVerifymailApi && this.meta.verifymailAuthKey != null) {
validated = await this.verifyMail(emailAddress, meta.verifymailAuthKey); validated = await this.verifyMail(emailAddress, this.meta.verifymailAuthKey);
} else if (meta.enableTruemailApi && meta.truemailInstance && meta.truemailAuthKey != null) { } else if (this.meta.enableTruemailApi && this.meta.truemailInstance && this.meta.truemailAuthKey != null) {
validated = await this.trueMail(meta.truemailInstance, emailAddress, meta.truemailAuthKey); validated = await this.trueMail(this.meta.truemailInstance, emailAddress, this.meta.truemailAuthKey);
} else { } else {
validated = await validateEmail({ validated = await validateEmail({
email: emailAddress, email: emailAddress,
@ -215,7 +215,7 @@ export class EmailService {
} }
const emailDomain: string = emailAddress.split('@')[1]; const emailDomain: string = emailAddress.split('@')[1];
const isBanned = this.utilityService.isBlockedHost(meta.bannedEmailDomains, emailDomain); const isBanned = this.utilityService.isBlockedHost(this.meta.bannedEmailDomains, emailDomain);
if (isBanned) { if (isBanned) {
return { return {

View file

@ -246,7 +246,7 @@ export interface InternalEventTypes {
avatarDecorationCreated: MiAvatarDecoration; avatarDecorationCreated: MiAvatarDecoration;
avatarDecorationDeleted: MiAvatarDecoration; avatarDecorationDeleted: MiAvatarDecoration;
avatarDecorationUpdated: MiAvatarDecoration; avatarDecorationUpdated: MiAvatarDecoration;
metaUpdated: MiMeta; metaUpdated: { before?: MiMeta; after: MiMeta; };
followChannel: { userId: MiUser['id']; channelId: MiChannel['id']; }; followChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
unfollowChannel: { userId: MiUser['id']; channelId: MiChannel['id']; }; unfollowChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
updateUserProfile: MiUserProfile; updateUserProfile: MiUserProfile;

View file

@ -10,16 +10,18 @@ import type { MiUser } from '@/models/User.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import type { MiHashtag } from '@/models/Hashtag.js'; import type { MiHashtag } from '@/models/Hashtag.js';
import type { HashtagsRepository } from '@/models/_.js'; import type { HashtagsRepository, MiMeta } from '@/models/_.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { FeaturedService } from '@/core/FeaturedService.js'; import { FeaturedService } from '@/core/FeaturedService.js';
import { MetaService } from '@/core/MetaService.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
@Injectable() @Injectable()
export class HashtagService { export class HashtagService {
constructor( constructor(
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.redis) @Inject(DI.redis)
private redisClient: Redis.Redis, // TODO: 専用のRedisサーバーを設定できるようにする private redisClient: Redis.Redis, // TODO: 専用のRedisサーバーを設定できるようにする
@ -29,7 +31,6 @@ export class HashtagService {
private userEntityService: UserEntityService, private userEntityService: UserEntityService,
private featuredService: FeaturedService, private featuredService: FeaturedService,
private idService: IdService, private idService: IdService,
private metaService: MetaService,
private utilityService: UtilityService, private utilityService: UtilityService,
) { ) {
} }
@ -160,10 +161,9 @@ export class HashtagService {
@bindThis @bindThis
public async updateHashtagsRanking(hashtag: string, userId: MiUser['id']): Promise<void> { public async updateHashtagsRanking(hashtag: string, userId: MiUser['id']): Promise<void> {
const instance = await this.metaService.fetch(); const hiddenTags = this.meta.hiddenTags.map(t => normalizeForSearch(t));
const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t));
if (hiddenTags.includes(hashtag)) return; if (hiddenTags.includes(hashtag)) return;
if (this.utilityService.isKeyWordIncluded(hashtag, instance.sensitiveWords)) return; if (this.utilityService.isKeyWordIncluded(hashtag, this.meta.sensitiveWords)) return;
// YYYYMMDDHHmm (10分間隔) // YYYYMMDDHHmm (10分間隔)
const now = new Date(); const now = new Date();

View file

@ -52,7 +52,7 @@ export class MetaService implements OnApplicationShutdown {
switch (type) { switch (type) {
case 'metaUpdated': { case 'metaUpdated': {
this.cache = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい this.cache = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
...body, ...(body.after),
proxyAccount: null, // joinなカラムは通常取ってこないので proxyAccount: null, // joinなカラムは通常取ってこないので
}; };
break; break;
@ -141,7 +141,7 @@ export class MetaService implements OnApplicationShutdown {
}); });
} }
this.globalEventService.publishInternalEvent('metaUpdated', updated); this.globalEventService.publishInternalEvent('metaUpdated', { before, after: updated });
return updated; return updated;
} }

View file

@ -6,7 +6,7 @@
import { URL } from 'node:url'; import { URL } from 'node:url';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import * as parse5 from 'parse5'; import * as parse5 from 'parse5';
import { Window, DocumentFragment, XMLSerializer } from 'happy-dom'; import { Window, XMLSerializer } from 'happy-dom';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { intersperse } from '@/misc/prelude/array.js'; import { intersperse } from '@/misc/prelude/array.js';
@ -465,7 +465,7 @@ export class MfmService {
const serialized = new XMLSerializer().serializeToString(body); const serialized = new XMLSerializer().serializeToString(body);
happyDOM.close().catch(e => {}); happyDOM.close().catch(err => {});
return serialized; return serialized;
} }

View file

@ -8,13 +8,12 @@ import * as mfm from '@transfem-org/sfm-js';
import { In, DataSource, IsNull, LessThan } from 'typeorm'; import { In, DataSource, IsNull, LessThan } from 'typeorm';
import * as Redis from 'ioredis'; import * as Redis from 'ioredis';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import RE2 from 're2';
import { extractMentions } from '@/misc/extract-mentions.js'; import { extractMentions } from '@/misc/extract-mentions.js';
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
import { extractHashtags } from '@/misc/extract-hashtags.js'; import { extractHashtags } from '@/misc/extract-hashtags.js';
import type { IMentionedRemoteUsers } from '@/models/Note.js'; import type { IMentionedRemoteUsers } from '@/models/Note.js';
import { MiNote } from '@/models/Note.js'; import { MiNote } from '@/models/Note.js';
import type { ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import type { ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MiMeta, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
import type { MiDriveFile } from '@/models/DriveFile.js'; import type { MiDriveFile } from '@/models/DriveFile.js';
import type { MiApp } from '@/models/App.js'; import type { MiApp } from '@/models/App.js';
import { concat } from '@/misc/prelude/array.js'; import { concat } from '@/misc/prelude/array.js';
@ -23,11 +22,8 @@ import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js';
import type { IPoll } from '@/models/Poll.js'; import type { IPoll } from '@/models/Poll.js';
import { MiPoll } from '@/models/Poll.js'; import { MiPoll } from '@/models/Poll.js';
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
import { checkWordMute } from '@/misc/check-word-mute.js';
import type { MiChannel } from '@/models/Channel.js'; import type { MiChannel } from '@/models/Channel.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { MemorySingleCache } from '@/misc/cache.js';
import type { MiUserProfile } from '@/models/UserProfile.js';
import { RelayService } from '@/core/RelayService.js'; import { RelayService } from '@/core/RelayService.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
@ -51,7 +47,6 @@ import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
import { RoleService } from '@/core/RoleService.js'; import { RoleService } from '@/core/RoleService.js';
import { MetaService } from '@/core/MetaService.js';
import { SearchService } from '@/core/SearchService.js'; import { SearchService } from '@/core/SearchService.js';
import { FeaturedService } from '@/core/FeaturedService.js'; import { FeaturedService } from '@/core/FeaturedService.js';
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js'; import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
@ -63,6 +58,7 @@ import { trackPromise } from '@/misc/promise-tracker.js';
import { isUserRelated } from '@/misc/is-user-related.js'; import { isUserRelated } from '@/misc/is-user-related.js';
import { IdentifiableError } from '@/misc/identifiable-error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js';
import { LatestNoteService } from '@/core/LatestNoteService.js'; import { LatestNoteService } from '@/core/LatestNoteService.js';
import { CollapsedQueue } from '@/misc/collapsed-queue.js';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@ -154,11 +150,15 @@ type Option = {
@Injectable() @Injectable()
export class NoteCreateService implements OnApplicationShutdown { export class NoteCreateService implements OnApplicationShutdown {
#shutdownController = new AbortController(); #shutdownController = new AbortController();
private updateNotesCountQueue: CollapsedQueue<MiNote['id'], number>;
constructor( constructor(
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.db) @Inject(DI.db)
private db: DataSource, private db: DataSource,
@ -213,7 +213,6 @@ export class NoteCreateService implements OnApplicationShutdown {
private apDeliverManagerService: ApDeliverManagerService, private apDeliverManagerService: ApDeliverManagerService,
private apRendererService: ApRendererService, private apRendererService: ApRendererService,
private roleService: RoleService, private roleService: RoleService,
private metaService: MetaService,
private searchService: SearchService, private searchService: SearchService,
private notesChart: NotesChart, private notesChart: NotesChart,
private perUserNotesChart: PerUserNotesChart, private perUserNotesChart: PerUserNotesChart,
@ -223,7 +222,9 @@ export class NoteCreateService implements OnApplicationShutdown {
private userBlockingService: UserBlockingService, private userBlockingService: UserBlockingService,
private cacheService: CacheService, private cacheService: CacheService,
private latestNoteService: LatestNoteService, private latestNoteService: LatestNoteService,
) { } ) {
this.updateNotesCountQueue = new CollapsedQueue(60 * 1000 * 5, this.collapseNotesCount, this.performUpdateNotesCount);
}
@bindThis @bindThis
public async create(user: { public async create(user: {
@ -256,10 +257,8 @@ export class NoteCreateService implements OnApplicationShutdown {
if (data.channel != null) data.visibleUsers = []; if (data.channel != null) data.visibleUsers = [];
if (data.channel != null) data.localOnly = true; if (data.channel != null) data.localOnly = true;
const meta = await this.metaService.fetch();
if (data.visibility === 'public' && data.channel == null) { if (data.visibility === 'public' && data.channel == null) {
const sensitiveWords = meta.sensitiveWords; const sensitiveWords = this.meta.sensitiveWords;
if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) { if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
data.visibility = 'home'; data.visibility = 'home';
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) { } else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
@ -267,17 +266,17 @@ export class NoteCreateService implements OnApplicationShutdown {
} }
} }
const hasProhibitedWords = await this.checkProhibitedWordsContain({ const hasProhibitedWords = this.checkProhibitedWordsContain({
cw: data.cw, cw: data.cw,
text: data.text, text: data.text,
pollChoices: data.poll?.choices, pollChoices: data.poll?.choices,
}, meta.prohibitedWords); }, this.meta.prohibitedWords);
if (hasProhibitedWords) { if (hasProhibitedWords) {
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words'); throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
} }
const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host); const inSilencedInstance = this.utilityService.isSilencedHost(this.meta.silencedHosts, user.host);
if (data.visibility === 'public' && inSilencedInstance && user.host !== null) { if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
data.visibility = 'home'; data.visibility = 'home';
@ -382,7 +381,7 @@ export class NoteCreateService implements OnApplicationShutdown {
} }
// if the host is media-silenced, custom emojis are not allowed // if the host is media-silenced, custom emojis are not allowed
if (this.utilityService.isMediaSilencedHost(meta.mediaSilencedHosts, user.host)) emojis = []; if (this.utilityService.isMediaSilencedHost(this.meta.mediaSilencedHosts, user.host)) emojis = [];
tags = tags.filter(tag => Array.from(tag).length <= 128).splice(0, 32); tags = tags.filter(tag => Array.from(tag).length <= 128).splice(0, 32);
@ -551,10 +550,8 @@ export class NoteCreateService implements OnApplicationShutdown {
isBot: MiUser['isBot']; isBot: MiUser['isBot'];
noindex: MiUser['noindex']; noindex: MiUser['noindex'];
}, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) { }, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) {
const meta = await this.metaService.fetch();
this.notesChart.update(note, true); this.notesChart.update(note, true);
if (note.visibility !== 'specified' && (meta.enableChartsForRemoteUser || (user.host == null))) { if (note.visibility !== 'specified' && (this.meta.enableChartsForRemoteUser || (user.host == null))) {
this.perUserNotesChart.update(user, note, true); this.perUserNotesChart.update(user, note, true);
} }
@ -562,11 +559,11 @@ export class NoteCreateService implements OnApplicationShutdown {
if (this.userEntityService.isRemoteUser(user)) { if (this.userEntityService.isRemoteUser(user)) {
this.federatedInstanceService.fetch(user.host).then(async i => { this.federatedInstanceService.fetch(user.host).then(async i => {
if (note.renote && note.text) { if (note.renote && note.text) {
this.instancesRepository.increment({ id: i.id }, 'notesCount', 1); this.updateNotesCountQueue.enqueue(i.id, 1);
} else if (!note.renote) { } else if (!note.renote) {
this.instancesRepository.increment({ id: i.id }, 'notesCount', 1); this.updateNotesCountQueue.enqueue(i.id, 1);
} }
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) { if (this.meta.enableChartsForFederatedInstances) {
this.instanceChart.updateNote(i.host, note, true); this.instanceChart.updateNote(i.host, note, true);
} }
}); });
@ -574,7 +571,7 @@ export class NoteCreateService implements OnApplicationShutdown {
// ハッシュタグ更新 // ハッシュタグ更新
if (data.visibility === 'public' || data.visibility === 'home') { if (data.visibility === 'public' || data.visibility === 'home') {
if (user.isBot && meta.enableBotTrending) { if (user.isBot && this.meta.enableBotTrending) {
this.hashtagService.updateHashtags(user, tags); this.hashtagService.updateHashtags(user, tags);
} else if (!user.isBot) { } else if (!user.isBot) {
this.hashtagService.updateHashtags(user, tags); this.hashtagService.updateHashtags(user, tags);
@ -951,15 +948,14 @@ export class NoteCreateService implements OnApplicationShutdown {
@bindThis @bindThis
private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) { private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) {
const meta = await this.metaService.fetch(); if (!this.meta.enableFanoutTimeline) return;
if (!meta.enableFanoutTimeline) return;
const r = this.redisForTimelines.pipeline(); const r = this.redisForTimelines.pipeline();
if (note.channelId) { if (note.channelId) {
this.fanoutTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r); this.fanoutTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r);
this.fanoutTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); this.fanoutTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax : this.meta.perRemoteUserUserTimelineCacheMax, r);
const channelFollowings = await this.channelFollowingsRepository.find({ const channelFollowings = await this.channelFollowingsRepository.find({
where: { where: {
@ -969,9 +965,9 @@ export class NoteCreateService implements OnApplicationShutdown {
}); });
for (const channelFollowing of channelFollowings) { for (const channelFollowing of channelFollowings) {
this.fanoutTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r); this.fanoutTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax, r);
if (note.fileIds.length > 0) { if (note.fileIds.length > 0) {
this.fanoutTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); this.fanoutTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax / 2, r);
} }
} }
} else { } else {
@ -1009,9 +1005,9 @@ export class NoteCreateService implements OnApplicationShutdown {
if (!following.withReplies) continue; if (!following.withReplies) continue;
} }
this.fanoutTimelineService.push(`homeTimeline:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r); this.fanoutTimelineService.push(`homeTimeline:${following.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax, r);
if (note.fileIds.length > 0) { if (note.fileIds.length > 0) {
this.fanoutTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); this.fanoutTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax / 2, r);
} }
} }
@ -1028,25 +1024,25 @@ export class NoteCreateService implements OnApplicationShutdown {
if (!userListMembership.withReplies) continue; if (!userListMembership.withReplies) continue;
} }
this.fanoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r); this.fanoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, this.meta.perUserListTimelineCacheMax, r);
if (note.fileIds.length > 0) { if (note.fileIds.length > 0) {
this.fanoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r); this.fanoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, this.meta.perUserListTimelineCacheMax / 2, r);
} }
} }
// 自分自身のHTL // 自分自身のHTL
if (note.userHost == null) { if (note.userHost == null) {
if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) { if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) {
this.fanoutTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r); this.fanoutTimelineService.push(`homeTimeline:${user.id}`, note.id, this.meta.perUserHomeTimelineCacheMax, r);
if (note.fileIds.length > 0) { if (note.fileIds.length > 0) {
this.fanoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); this.fanoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, this.meta.perUserHomeTimelineCacheMax / 2, r);
} }
} }
} }
// 自分自身以外への返信 // 自分自身以外への返信
if (isReply(note)) { if (isReply(note)) {
this.fanoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); this.fanoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax : this.meta.perRemoteUserUserTimelineCacheMax, r);
if (note.visibility === 'public' && note.userHost == null) { if (note.visibility === 'public' && note.userHost == null) {
this.fanoutTimelineService.push('localTimelineWithReplies', note.id, 300, r); this.fanoutTimelineService.push('localTimelineWithReplies', note.id, 300, r);
@ -1055,9 +1051,9 @@ export class NoteCreateService implements OnApplicationShutdown {
} }
} }
} else { } else {
this.fanoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); this.fanoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax : this.meta.perRemoteUserUserTimelineCacheMax, r);
if (note.fileIds.length > 0) { if (note.fileIds.length > 0) {
this.fanoutTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax / 2 : meta.perRemoteUserUserTimelineCacheMax / 2, r); this.fanoutTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax / 2 : this.meta.perRemoteUserUserTimelineCacheMax / 2, r);
} }
if (note.visibility === 'public' && note.userHost == null) { if (note.visibility === 'public' && note.userHost == null) {
@ -1116,9 +1112,9 @@ export class NoteCreateService implements OnApplicationShutdown {
} }
} }
public async checkProhibitedWordsContain(content: Parameters<UtilityService['concatNoteContentsForKeyWordCheck']>[0], prohibitedWords?: string[]) { public checkProhibitedWordsContain(content: Parameters<UtilityService['concatNoteContentsForKeyWordCheck']>[0], prohibitedWords?: string[]) {
if (prohibitedWords == null) { if (prohibitedWords == null) {
prohibitedWords = (await this.metaService.fetch()).prohibitedWords; prohibitedWords = this.meta.prohibitedWords;
} }
if ( if (
@ -1134,12 +1130,23 @@ export class NoteCreateService implements OnApplicationShutdown {
} }
@bindThis @bindThis
public dispose(): void { private collapseNotesCount(oldValue: number, newValue: number) {
this.#shutdownController.abort(); return oldValue + newValue;
} }
@bindThis @bindThis
public onApplicationShutdown(signal?: string | undefined): void { private async performUpdateNotesCount(id: MiNote['id'], incrBy: number) {
this.dispose(); await this.instancesRepository.increment({ id: id }, 'notesCount', incrBy);
}
@bindThis
public async dispose(): Promise<void> {
this.#shutdownController.abort();
await this.updateNotesCountQueue.performAllNow();
}
@bindThis
public async onApplicationShutdown(signal?: string | undefined): Promise<void> {
await this.dispose();
} }
} }

View file

@ -7,7 +7,7 @@ import { Brackets, In } from 'typeorm';
import { Injectable, Inject } from '@nestjs/common'; import { Injectable, Inject } from '@nestjs/common';
import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js'; import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js';
import { MiNote, IMentionedRemoteUsers } from '@/models/Note.js'; import { MiNote, IMentionedRemoteUsers } from '@/models/Note.js';
import type { InstancesRepository, NotesRepository, UsersRepository } from '@/models/_.js'; import type { InstancesRepository, MiMeta, NotesRepository, UsersRepository } from '@/models/_.js';
import { RelayService } from '@/core/RelayService.js'; import { RelayService } from '@/core/RelayService.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
@ -20,7 +20,6 @@ import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { MetaService } from '@/core/MetaService.js';
import { SearchService } from '@/core/SearchService.js'; import { SearchService } from '@/core/SearchService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js';
import { isQuote, isRenote } from '@/misc/is-renote.js'; import { isQuote, isRenote } from '@/misc/is-renote.js';
@ -32,6 +31,9 @@ export class NoteDeleteService {
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@ -47,7 +49,6 @@ export class NoteDeleteService {
private federatedInstanceService: FederatedInstanceService, private federatedInstanceService: FederatedInstanceService,
private apRendererService: ApRendererService, private apRendererService: ApRendererService,
private apDeliverManagerService: ApDeliverManagerService, private apDeliverManagerService: ApDeliverManagerService,
private metaService: MetaService,
private searchService: SearchService, private searchService: SearchService,
private moderationLogService: ModerationLogService, private moderationLogService: ModerationLogService,
private notesChart: NotesChart, private notesChart: NotesChart,
@ -109,10 +110,8 @@ export class NoteDeleteService {
} }
//#endregion //#endregion
const meta = await this.metaService.fetch();
this.notesChart.update(note, false); this.notesChart.update(note, false);
if (meta.enableChartsForRemoteUser || (user.host == null)) { if (this.meta.enableChartsForRemoteUser || (user.host == null)) {
this.perUserNotesChart.update(user, note, false); this.perUserNotesChart.update(user, note, false);
} }
@ -131,7 +130,7 @@ export class NoteDeleteService {
} else if (!note.renoteId) { } else if (!note.renoteId) {
this.instancesRepository.decrement({ id: i.id }, 'notesCount', 1); this.instancesRepository.decrement({ id: i.id }, 'notesCount', 1);
} }
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) { if (this.meta.enableChartsForFederatedInstances) {
this.instanceChart.updateNote(i.host, note, false); this.instanceChart.updateNote(i.host, note, false);
} }
}); });

View file

@ -8,13 +8,12 @@ import * as mfm from '@transfem-org/sfm-js';
import { DataSource, In, IsNull, LessThan } from 'typeorm'; import { DataSource, In, IsNull, LessThan } from 'typeorm';
import * as Redis from 'ioredis'; import * as Redis from 'ioredis';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import RE2 from 're2';
import { extractMentions } from '@/misc/extract-mentions.js'; import { extractMentions } from '@/misc/extract-mentions.js';
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
import { extractHashtags } from '@/misc/extract-hashtags.js'; import { extractHashtags } from '@/misc/extract-hashtags.js';
import type { IMentionedRemoteUsers } from '@/models/Note.js'; import type { IMentionedRemoteUsers } from '@/models/Note.js';
import { MiNote } from '@/models/Note.js'; import { MiNote } from '@/models/Note.js';
import type { NoteEditRepository, ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository, PollsRepository } from '@/models/_.js'; import type { NoteEditRepository, ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MiMeta, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository, PollsRepository } from '@/models/_.js';
import type { MiDriveFile } from '@/models/DriveFile.js'; import type { MiDriveFile } from '@/models/DriveFile.js';
import type { MiApp } from '@/models/App.js'; import type { MiApp } from '@/models/App.js';
import { concat } from '@/misc/prelude/array.js'; import { concat } from '@/misc/prelude/array.js';
@ -42,7 +41,6 @@ import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
import { RoleService } from '@/core/RoleService.js'; import { RoleService } from '@/core/RoleService.js';
import { MetaService } from '@/core/MetaService.js';
import { SearchService } from '@/core/SearchService.js'; import { SearchService } from '@/core/SearchService.js';
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js'; import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
@ -53,6 +51,8 @@ import { trackPromise } from '@/misc/promise-tracker.js';
import { isUserRelated } from '@/misc/is-user-related.js'; import { isUserRelated } from '@/misc/is-user-related.js';
import { IdentifiableError } from '@/misc/identifiable-error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js';
import { LatestNoteService } from '@/core/LatestNoteService.js'; import { LatestNoteService } from '@/core/LatestNoteService.js';
import { CollapsedQueue } from '@/misc/collapsed-queue.js';
import { NoteCreateService } from '@/core/NoteCreateService.js';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention' | 'edited'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention' | 'edited';
@ -146,11 +146,15 @@ type Option = {
@Injectable() @Injectable()
export class NoteEditService implements OnApplicationShutdown { export class NoteEditService implements OnApplicationShutdown {
#shutdownController = new AbortController(); #shutdownController = new AbortController();
private updateNotesCountQueue: CollapsedQueue<MiNote['id'], number>;
constructor( constructor(
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.db) @Inject(DI.db)
private db: DataSource, private db: DataSource,
@ -208,7 +212,6 @@ export class NoteEditService implements OnApplicationShutdown {
private apDeliverManagerService: ApDeliverManagerService, private apDeliverManagerService: ApDeliverManagerService,
private apRendererService: ApRendererService, private apRendererService: ApRendererService,
private roleService: RoleService, private roleService: RoleService,
private metaService: MetaService,
private searchService: SearchService, private searchService: SearchService,
private activeUsersChart: ActiveUsersChart, private activeUsersChart: ActiveUsersChart,
private instanceChart: InstanceChart, private instanceChart: InstanceChart,
@ -216,7 +219,10 @@ export class NoteEditService implements OnApplicationShutdown {
private userBlockingService: UserBlockingService, private userBlockingService: UserBlockingService,
private cacheService: CacheService, private cacheService: CacheService,
private latestNoteService: LatestNoteService, private latestNoteService: LatestNoteService,
) { } private noteCreateService: NoteCreateService,
) {
this.updateNotesCountQueue = new CollapsedQueue(60 * 1000 * 5, this.collapseNotesCount, this.performUpdateNotesCount);
}
@bindThis @bindThis
public async edit(user: { public async edit(user: {
@ -277,10 +283,8 @@ export class NoteEditService implements OnApplicationShutdown {
if (data.channel != null) data.localOnly = true; if (data.channel != null) data.localOnly = true;
if (data.updatedAt == null) data.updatedAt = new Date(); if (data.updatedAt == null) data.updatedAt = new Date();
const meta = await this.metaService.fetch();
if (data.visibility === 'public' && data.channel == null) { if (data.visibility === 'public' && data.channel == null) {
const sensitiveWords = meta.sensitiveWords; const sensitiveWords = this.meta.sensitiveWords;
if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) { if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
data.visibility = 'home'; data.visibility = 'home';
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) { } else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
@ -288,17 +292,17 @@ export class NoteEditService implements OnApplicationShutdown {
} }
} }
const hasProhibitedWords = await this.checkProhibitedWordsContain({ const hasProhibitedWords = this.noteCreateService.checkProhibitedWordsContain({
cw: data.cw, cw: data.cw,
text: data.text, text: data.text,
pollChoices: data.poll?.choices, pollChoices: data.poll?.choices,
}, meta.prohibitedWords); }, this.meta.prohibitedWords);
if (hasProhibitedWords) { if (hasProhibitedWords) {
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words'); throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
} }
const inSilencedInstance = this.utilityService.isSilencedHost((meta).silencedHosts, user.host); const inSilencedInstance = this.utilityService.isSilencedHost(this.meta.silencedHosts, user.host);
if (data.visibility === 'public' && inSilencedInstance && user.host !== null) { if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
data.visibility = 'home'; data.visibility = 'home';
@ -407,7 +411,7 @@ export class NoteEditService implements OnApplicationShutdown {
} }
// if the host is media-silenced, custom emojis are not allowed // if the host is media-silenced, custom emojis are not allowed
if (this.utilityService.isMediaSilencedHost(meta.mediaSilencedHosts, user.host)) emojis = []; if (this.utilityService.isMediaSilencedHost(this.meta.mediaSilencedHosts, user.host)) emojis = [];
tags = tags.filter(tag => Array.from(tag ?? '').length <= 128).splice(0, 32); tags = tags.filter(tag => Array.from(tag ?? '').length <= 128).splice(0, 32);
@ -512,6 +516,7 @@ export class NoteEditService implements OnApplicationShutdown {
renoteUserId: data.renote ? data.renote.userId : null, renoteUserId: data.renote ? data.renote.userId : null,
renoteUserHost: data.renote ? data.renote.userHost : null, renoteUserHost: data.renote ? data.renote.userHost : null,
userHost: user.host, userHost: user.host,
reactionAndUserPairCache: oldnote.reactionAndUserPairCache,
}); });
if (data.uri != null) note.uri = data.uri; if (data.uri != null) note.uri = data.uri;
@ -581,8 +586,8 @@ export class NoteEditService implements OnApplicationShutdown {
// Register host // Register host
if (this.userEntityService.isRemoteUser(user)) { if (this.userEntityService.isRemoteUser(user)) {
this.federatedInstanceService.fetch(user.host).then(async i => { this.federatedInstanceService.fetch(user.host).then(async i => {
this.instancesRepository.increment({ id: i.id }, 'notesCount', 1); this.updateNotesCountQueue.enqueue(i.id, 1);
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) { if (this.meta.enableChartsForFederatedInstances) {
this.instanceChart.updateNote(i.host, note, true); this.instanceChart.updateNote(i.host, note, true);
} }
}); });
@ -871,15 +876,14 @@ export class NoteEditService implements OnApplicationShutdown {
@bindThis @bindThis
private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) { private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) {
const meta = await this.metaService.fetch(); if (!this.meta.enableFanoutTimeline) return;
if (!meta.enableFanoutTimeline) return;
const r = this.redisForTimelines.pipeline(); const r = this.redisForTimelines.pipeline();
if (note.channelId) { if (note.channelId) {
this.fanoutTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r); this.fanoutTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r);
this.fanoutTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); this.fanoutTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax : this.meta.perRemoteUserUserTimelineCacheMax, r);
const channelFollowings = await this.channelFollowingsRepository.find({ const channelFollowings = await this.channelFollowingsRepository.find({
where: { where: {
@ -889,9 +893,9 @@ export class NoteEditService implements OnApplicationShutdown {
}); });
for (const channelFollowing of channelFollowings) { for (const channelFollowing of channelFollowings) {
this.fanoutTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r); this.fanoutTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax, r);
if (note.fileIds.length > 0) { if (note.fileIds.length > 0) {
this.fanoutTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); this.fanoutTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax / 2, r);
} }
} }
} else { } else {
@ -929,9 +933,9 @@ export class NoteEditService implements OnApplicationShutdown {
if (!following.withReplies) continue; if (!following.withReplies) continue;
} }
this.fanoutTimelineService.push(`homeTimeline:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r); this.fanoutTimelineService.push(`homeTimeline:${following.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax, r);
if (note.fileIds.length > 0) { if (note.fileIds.length > 0) {
this.fanoutTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); this.fanoutTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax / 2, r);
} }
} }
@ -948,25 +952,25 @@ export class NoteEditService implements OnApplicationShutdown {
if (!userListMembership.withReplies) continue; if (!userListMembership.withReplies) continue;
} }
this.fanoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r); this.fanoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, this.meta.perUserListTimelineCacheMax, r);
if (note.fileIds.length > 0) { if (note.fileIds.length > 0) {
this.fanoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r); this.fanoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, this.meta.perUserListTimelineCacheMax / 2, r);
} }
} }
// 自分自身のHTL // 自分自身のHTL
if (note.userHost == null) { if (note.userHost == null) {
if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) { if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) {
this.fanoutTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r); this.fanoutTimelineService.push(`homeTimeline:${user.id}`, note.id, this.meta.perUserHomeTimelineCacheMax, r);
if (note.fileIds.length > 0) { if (note.fileIds.length > 0) {
this.fanoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); this.fanoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, this.meta.perUserHomeTimelineCacheMax / 2, r);
} }
} }
} }
// 自分自身以外への返信 // 自分自身以外への返信
if (isReply(note)) { if (isReply(note)) {
this.fanoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); this.fanoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax : this.meta.perRemoteUserUserTimelineCacheMax, r);
if (note.visibility === 'public' && note.userHost == null) { if (note.visibility === 'public' && note.userHost == null) {
this.fanoutTimelineService.push('localTimelineWithReplies', note.id, 300, r); this.fanoutTimelineService.push('localTimelineWithReplies', note.id, 300, r);
@ -975,9 +979,9 @@ export class NoteEditService implements OnApplicationShutdown {
} }
} }
} else { } else {
this.fanoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); this.fanoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax : this.meta.perRemoteUserUserTimelineCacheMax, r);
if (note.fileIds.length > 0) { if (note.fileIds.length > 0) {
this.fanoutTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax / 2 : meta.perRemoteUserUserTimelineCacheMax / 2, r); this.fanoutTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax / 2 : this.meta.perRemoteUserUserTimelineCacheMax / 2, r);
} }
if (note.visibility === 'public' && note.userHost == null) { if (note.visibility === 'public' && note.userHost == null) {
@ -1036,30 +1040,24 @@ export class NoteEditService implements OnApplicationShutdown {
} }
} }
public async checkProhibitedWordsContain(content: Parameters<UtilityService['concatNoteContentsForKeyWordCheck']>[0], prohibitedWords?: string[]) { @bindThis
if (prohibitedWords == null) { private collapseNotesCount(oldValue: number, newValue: number) {
prohibitedWords = (await this.metaService.fetch()).prohibitedWords; return oldValue + newValue;
}
if (
this.utilityService.isKeyWordIncluded(
this.utilityService.concatNoteContentsForKeyWordCheck(content),
prohibitedWords,
)
) {
return true;
}
return false;
} }
@bindThis @bindThis
public dispose(): void { private async performUpdateNotesCount(id: MiNote['id'], incrBy: number) {
await this.instancesRepository.increment({ id: id }, 'notesCount', incrBy);
}
@bindThis
public async dispose(): Promise<void> {
this.#shutdownController.abort(); this.#shutdownController.abort();
await this.updateNotesCountQueue.performAllNow();
} }
@bindThis @bindThis
public onApplicationShutdown(signal?: string | undefined): void { public async onApplicationShutdown(signal?: string | undefined): Promise<void> {
this.dispose(); await this.dispose();
} }
} }

View file

@ -4,26 +4,25 @@
*/ */
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import type { UsersRepository } from '@/models/_.js'; import type { MiMeta, UsersRepository } from '@/models/_.js';
import type { MiLocalUser } from '@/models/User.js'; import type { MiLocalUser } from '@/models/User.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { MetaService } from '@/core/MetaService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@Injectable() @Injectable()
export class ProxyAccountService { export class ProxyAccountService {
constructor( constructor(
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
private metaService: MetaService,
) { ) {
} }
@bindThis @bindThis
public async fetch(): Promise<MiLocalUser | null> { public async fetch(): Promise<MiLocalUser | null> {
const meta = await this.metaService.fetch(); if (this.meta.proxyAccountId == null) return null;
if (meta.proxyAccountId == null) return null; return await this.usersRepository.findOneByOrFail({ id: this.meta.proxyAccountId }) as MiLocalUser;
return await this.usersRepository.findOneByOrFail({ id: meta.proxyAccountId }) as MiLocalUser;
} }
} }

View file

@ -10,8 +10,7 @@ import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import type { Packed } from '@/misc/json-schema.js'; import type { Packed } from '@/misc/json-schema.js';
import { getNoteSummary } from '@/misc/get-note-summary.js'; import { getNoteSummary } from '@/misc/get-note-summary.js';
import type { MiSwSubscription, SwSubscriptionsRepository } from '@/models/_.js'; import type { MiMeta, MiSwSubscription, SwSubscriptionsRepository } from '@/models/_.js';
import { MetaService } from '@/core/MetaService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { RedisKVCache } from '@/misc/cache.js'; import { RedisKVCache } from '@/misc/cache.js';
@ -54,13 +53,14 @@ export class PushNotificationService implements OnApplicationShutdown {
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.redis) @Inject(DI.redis)
private redisClient: Redis.Redis, private redisClient: Redis.Redis,
@Inject(DI.swSubscriptionsRepository) @Inject(DI.swSubscriptionsRepository)
private swSubscriptionsRepository: SwSubscriptionsRepository, private swSubscriptionsRepository: SwSubscriptionsRepository,
private metaService: MetaService,
) { ) {
this.subscriptionsCache = new RedisKVCache<MiSwSubscription[]>(this.redisClient, 'userSwSubscriptions', { this.subscriptionsCache = new RedisKVCache<MiSwSubscription[]>(this.redisClient, 'userSwSubscriptions', {
lifetime: 1000 * 60 * 60 * 1, // 1h lifetime: 1000 * 60 * 60 * 1, // 1h
@ -73,14 +73,12 @@ export class PushNotificationService implements OnApplicationShutdown {
@bindThis @bindThis
public async pushNotification<T extends keyof PushNotificationsTypes>(userId: string, type: T, body: PushNotificationsTypes[T]) { public async pushNotification<T extends keyof PushNotificationsTypes>(userId: string, type: T, body: PushNotificationsTypes[T]) {
const meta = await this.metaService.fetch(); if (!this.meta.enableServiceWorker || this.meta.swPublicKey == null || this.meta.swPrivateKey == null) return;
if (!meta.enableServiceWorker || meta.swPublicKey == null || meta.swPrivateKey == null) return;
// アプリケーションの連絡先と、サーバーサイドの鍵ペアの情報を登録 // アプリケーションの連絡先と、サーバーサイドの鍵ペアの情報を登録
push.setVapidDetails(this.config.url, push.setVapidDetails(this.config.url,
meta.swPublicKey, this.meta.swPublicKey,
meta.swPrivateKey); this.meta.swPrivateKey);
const subscriptions = await this.subscriptionsCache.fetch(userId); const subscriptions = await this.subscriptionsCache.fetch(userId);

View file

@ -88,6 +88,12 @@ export class QueueService {
repeat: { pattern: '*/5 * * * *' }, repeat: { pattern: '*/5 * * * *' },
removeOnComplete: true, removeOnComplete: true,
}); });
this.systemQueue.add('bakeBufferedReactions', {
}, {
repeat: { pattern: '0 0 * * *' },
removeOnComplete: true,
});
} }
@bindThis @bindThis
@ -511,10 +517,15 @@ export class QueueService {
/** /**
* @see UserWebhookDeliverJobData * @see UserWebhookDeliverJobData
* @see WebhookDeliverProcessorService * @see UserWebhookDeliverProcessorService
*/ */
@bindThis @bindThis
public userWebhookDeliver(webhook: MiWebhook, type: typeof webhookEventTypes[number], content: unknown) { public userWebhookDeliver(
webhook: MiWebhook,
type: typeof webhookEventTypes[number],
content: unknown,
opts?: { attempts?: number },
) {
const data: UserWebhookDeliverJobData = { const data: UserWebhookDeliverJobData = {
type, type,
content, content,
@ -527,7 +538,7 @@ export class QueueService {
}; };
return this.userWebhookDeliverQueue.add(webhook.id, data, { return this.userWebhookDeliverQueue.add(webhook.id, data, {
attempts: 4, attempts: opts?.attempts ?? 4,
backoff: { backoff: {
type: 'custom', type: 'custom',
}, },
@ -538,10 +549,15 @@ export class QueueService {
/** /**
* @see SystemWebhookDeliverJobData * @see SystemWebhookDeliverJobData
* @see WebhookDeliverProcessorService * @see SystemWebhookDeliverProcessorService
*/ */
@bindThis @bindThis
public systemWebhookDeliver(webhook: MiSystemWebhook, type: SystemWebhookEventType, content: unknown) { public systemWebhookDeliver(
webhook: MiSystemWebhook,
type: SystemWebhookEventType,
content: unknown,
opts?: { attempts?: number },
) {
const data: SystemWebhookDeliverJobData = { const data: SystemWebhookDeliverJobData = {
type, type,
content, content,
@ -553,7 +569,7 @@ export class QueueService {
}; };
return this.systemWebhookDeliverQueue.add(webhook.id, data, { return this.systemWebhookDeliverQueue.add(webhook.id, data, {
attempts: 4, attempts: opts?.attempts ?? 4,
backoff: { backoff: {
type: 'custom', type: 'custom',
}, },

View file

@ -4,9 +4,8 @@
*/ */
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository, NoteThreadMutingsRepository } from '@/models/_.js'; import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository, NoteThreadMutingsRepository, MiMeta } from '@/models/_.js';
import { IdentifiableError } from '@/misc/identifiable-error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js';
import type { MiRemoteUser, MiUser } from '@/models/User.js'; import type { MiRemoteUser, MiUser } from '@/models/User.js';
import type { MiNote } from '@/models/Note.js'; import type { MiNote } from '@/models/Note.js';
@ -21,7 +20,6 @@ import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerServ
import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { MetaService } from '@/core/MetaService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
import { UserBlockingService } from '@/core/UserBlockingService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js';
@ -30,9 +28,10 @@ import { RoleService } from '@/core/RoleService.js';
import { FeaturedService } from '@/core/FeaturedService.js'; import { FeaturedService } from '@/core/FeaturedService.js';
import { trackPromise } from '@/misc/promise-tracker.js'; import { trackPromise } from '@/misc/promise-tracker.js';
import { isQuote, isRenote } from '@/misc/is-renote.js'; import { isQuote, isRenote } from '@/misc/is-renote.js';
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
import { PER_NOTE_REACTION_USER_PAIR_CACHE_MAX } from '@/const.js';
const FALLBACK = '\u2764'; const FALLBACK = '\u2764';
const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
const legacies: Record<string, string> = { const legacies: Record<string, string> = {
'like': '👍', 'like': '👍',
@ -71,8 +70,8 @@ const decodeCustomEmojiRegexp = /^:([\p{Letter}\p{Number}\p{Mark}_+-]+)(?:@([\w.
@Injectable() @Injectable()
export class ReactionService { export class ReactionService {
constructor( constructor(
@Inject(DI.redis) @Inject(DI.meta)
private redisClient: Redis.Redis, private meta: MiMeta,
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@ -90,12 +89,12 @@ export class ReactionService {
private emojisRepository: EmojisRepository, private emojisRepository: EmojisRepository,
private utilityService: UtilityService, private utilityService: UtilityService,
private metaService: MetaService,
private customEmojiService: CustomEmojiService, private customEmojiService: CustomEmojiService,
private roleService: RoleService, private roleService: RoleService,
private userEntityService: UserEntityService, private userEntityService: UserEntityService,
private noteEntityService: NoteEntityService, private noteEntityService: NoteEntityService,
private userBlockingService: UserBlockingService, private userBlockingService: UserBlockingService,
private reactionsBufferingService: ReactionsBufferingService,
private idService: IdService, private idService: IdService,
private featuredService: FeaturedService, private featuredService: FeaturedService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
@ -108,8 +107,6 @@ export class ReactionService {
@bindThis @bindThis
public async create(user: { id: MiUser['id']; host: MiUser['host']; isBot: MiUser['isBot'] }, note: MiNote, _reaction?: string | null) { public async create(user: { id: MiUser['id']; host: MiUser['host']; isBot: MiUser['isBot'] }, note: MiNote, _reaction?: string | null) {
const meta = await this.metaService.fetch();
// Check blocking // Check blocking
if (note.userId !== user.id) { if (note.userId !== user.id) {
const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id); const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id);
@ -155,7 +152,7 @@ export class ReactionService {
} }
// for media silenced host, custom emoji reactions are not allowed // for media silenced host, custom emoji reactions are not allowed
if (reacterHost != null && this.utilityService.isMediaSilencedHost(meta.mediaSilencedHosts, reacterHost)) { if (reacterHost != null && this.utilityService.isMediaSilencedHost(this.meta.mediaSilencedHosts, reacterHost)) {
reaction = FALLBACK; reaction = FALLBACK;
} }
} else { } else {
@ -177,7 +174,6 @@ export class ReactionService {
reaction, reaction,
}; };
// Create reaction
try { try {
await this.noteReactionsRepository.insert(record); await this.noteReactionsRepository.insert(record);
} catch (e) { } catch (e) {
@ -201,6 +197,9 @@ export class ReactionService {
} }
// Increment reactions count // Increment reactions count
if (this.meta.enableReactionsBuffering) {
await this.reactionsBufferingService.create(note.id, user.id, reaction, note.reactionAndUserPairCache);
} else {
const sql = `jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + 1)::text::jsonb)`; const sql = `jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + 1)::text::jsonb)`;
await this.notesRepository.createQueryBuilder().update() await this.notesRepository.createQueryBuilder().update()
.set({ .set({
@ -211,6 +210,7 @@ export class ReactionService {
}) })
.where('id = :id', { id: note.id }) .where('id = :id', { id: note.id })
.execute(); .execute();
}
// 30%の確率、セルフではない、3日以内に投稿されたートの場合ハイライト用ランキング更新 // 30%の確率、セルフではない、3日以内に投稿されたートの場合ハイライト用ランキング更新
if ( if (
@ -230,7 +230,7 @@ export class ReactionService {
} }
} }
if (meta.enableChartsForRemoteUser || (user.host == null)) { if (this.meta.enableChartsForRemoteUser || (user.host == null)) {
this.perUserReactionsChart.update(user, note); this.perUserReactionsChart.update(user, note);
} }
@ -317,6 +317,9 @@ export class ReactionService {
} }
// Decrement reactions count // Decrement reactions count
if (this.meta.enableReactionsBuffering) {
await this.reactionsBufferingService.delete(note.id, user.id, exist.reaction);
} else {
const sql = `jsonb_set("reactions", '{${exist.reaction}}', (COALESCE("reactions"->>'${exist.reaction}', '0')::int - 1)::text::jsonb)`; const sql = `jsonb_set("reactions", '{${exist.reaction}}', (COALESCE("reactions"->>'${exist.reaction}', '0')::int - 1)::text::jsonb)`;
await this.notesRepository.createQueryBuilder().update() await this.notesRepository.createQueryBuilder().update()
.set({ .set({
@ -325,6 +328,7 @@ export class ReactionService {
}) })
.where('id = :id', { id: note.id }) .where('id = :id', { id: note.id })
.execute(); .execute();
}
this.globalEventService.publishNoteStream(note.id, 'unreacted', { this.globalEventService.publishNoteStream(note.id, 'unreacted', {
reaction: this.decodeReaction(exist.reaction).reaction, reaction: this.decodeReaction(exist.reaction).reaction,
@ -346,8 +350,21 @@ export class ReactionService {
} }
/** /**
* * -
* 0 * - `@.` `decodeReaction()`
*/
@bindThis
public convertLegacyReaction(reaction: string): string {
reaction = this.decodeReaction(reaction).reaction;
if (Object.keys(legacies).includes(reaction)) return legacies[reaction];
return reaction;
}
// TODO: 廃止
/**
* -
* - `@.` `decodeReaction()`
* - 0
*/ */
@bindThis @bindThis
public convertLegacyReactions(reactions: MiNote['reactions']): MiNote['reactions'] { public convertLegacyReactions(reactions: MiNote['reactions']): MiNote['reactions'] {
@ -360,10 +377,7 @@ export class ReactionService {
return count > 0; return count > 0;
}) })
.map(([reaction, count]) => { .map(([reaction, count]) => {
// unchecked indexed access const key = this.convertLegacyReaction(reaction);
const convertedReaction = legacies[reaction] as string | undefined;
const key = this.decodeReaction(convertedReaction ?? reaction).reaction;
return [key, count] as const; return [key, count] as const;
}) })
@ -418,11 +432,4 @@ export class ReactionService {
host: undefined, host: undefined,
}; };
} }
@bindThis
public convertLegacyReaction(reaction: string): string {
reaction = this.decodeReaction(reaction).reaction;
if (Object.keys(legacies).includes(reaction)) return legacies[reaction];
return reaction;
}
} }

View file

@ -0,0 +1,211 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import { DI } from '@/di-symbols.js';
import type { MiNote } from '@/models/Note.js';
import { bindThis } from '@/decorators.js';
import type { MiUser, NotesRepository } from '@/models/_.js';
import type { Config } from '@/config.js';
import { PER_NOTE_REACTION_USER_PAIR_CACHE_MAX } from '@/const.js';
import type { GlobalEvents } from '@/core/GlobalEventService.js';
import type { OnApplicationShutdown } from '@nestjs/common';
const REDIS_DELTA_PREFIX = 'reactionsBufferDeltas';
const REDIS_PAIR_PREFIX = 'reactionsBufferPairs';
@Injectable()
export class ReactionsBufferingService implements OnApplicationShutdown {
constructor(
@Inject(DI.config)
private config: Config,
@Inject(DI.redisForSub)
private redisForSub: Redis.Redis,
@Inject(DI.redisForReactions)
private redisForReactions: Redis.Redis, // TODO: 専用のRedisインスタンスにする
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
) {
this.redisForSub.on('message', this.onMessage);
}
@bindThis
private async onMessage(_: string, data: string) {
const obj = JSON.parse(data);
if (obj.channel === 'internal') {
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
switch (type) {
case 'metaUpdated': {
// リアクションバッファリングが有効→無効になったら即bake
if (body.before != null && body.before.enableReactionsBuffering && !body.after.enableReactionsBuffering) {
this.bake();
}
break;
}
default:
break;
}
}
}
@bindThis
public async create(noteId: MiNote['id'], userId: MiUser['id'], reaction: string, currentPairs: string[]): Promise<void> {
const pipeline = this.redisForReactions.pipeline();
pipeline.hincrby(`${REDIS_DELTA_PREFIX}:${noteId}`, reaction, 1);
for (let i = 0; i < currentPairs.length; i++) {
pipeline.zadd(`${REDIS_PAIR_PREFIX}:${noteId}`, i, currentPairs[i]);
}
pipeline.zadd(`${REDIS_PAIR_PREFIX}:${noteId}`, Date.now(), `${userId}/${reaction}`);
pipeline.zremrangebyrank(`${REDIS_PAIR_PREFIX}:${noteId}`, 0, -(PER_NOTE_REACTION_USER_PAIR_CACHE_MAX + 1));
await pipeline.exec();
}
@bindThis
public async delete(noteId: MiNote['id'], userId: MiUser['id'], reaction: string): Promise<void> {
const pipeline = this.redisForReactions.pipeline();
pipeline.hincrby(`${REDIS_DELTA_PREFIX}:${noteId}`, reaction, -1);
pipeline.zrem(`${REDIS_PAIR_PREFIX}:${noteId}`, `${userId}/${reaction}`);
// TODO: 「消した要素一覧」も持っておかないとcreateされた時に上書きされて復活する
await pipeline.exec();
}
@bindThis
public async get(noteId: MiNote['id']): Promise<{
deltas: Record<string, number>;
pairs: ([MiUser['id'], string])[];
}> {
const pipeline = this.redisForReactions.pipeline();
pipeline.hgetall(`${REDIS_DELTA_PREFIX}:${noteId}`);
pipeline.zrange(`${REDIS_PAIR_PREFIX}:${noteId}`, 0, -1);
const results = await pipeline.exec();
const resultDeltas = results![0][1] as Record<string, string>;
const resultPairs = results![1][1] as string[];
const deltas = {} as Record<string, number>;
for (const [name, count] of Object.entries(resultDeltas)) {
deltas[name] = parseInt(count);
}
const pairs = resultPairs.map(x => x.split('/') as [MiUser['id'], string]);
return {
deltas,
pairs,
};
}
@bindThis
public async getMany(noteIds: MiNote['id'][]): Promise<Map<MiNote['id'], {
deltas: Record<string, number>;
pairs: ([MiUser['id'], string])[];
}>> {
const map = new Map<MiNote['id'], {
deltas: Record<string, number>;
pairs: ([MiUser['id'], string])[];
}>();
const pipeline = this.redisForReactions.pipeline();
for (const noteId of noteIds) {
pipeline.hgetall(`${REDIS_DELTA_PREFIX}:${noteId}`);
pipeline.zrange(`${REDIS_PAIR_PREFIX}:${noteId}`, 0, -1);
}
const results = await pipeline.exec();
const opsForEachNotes = 2;
for (let i = 0; i < noteIds.length; i++) {
const noteId = noteIds[i];
const resultDeltas = results![i * opsForEachNotes][1] as Record<string, string>;
const resultPairs = results![i * opsForEachNotes + 1][1] as string[];
const deltas = {} as Record<string, number>;
for (const [name, count] of Object.entries(resultDeltas)) {
deltas[name] = parseInt(count);
}
const pairs = resultPairs.map(x => x.split('/') as [MiUser['id'], string]);
map.set(noteId, {
deltas,
pairs,
});
}
return map;
}
// TODO: scanは重い可能性があるので、別途 bufferedNoteIds を直接Redis上に持っておいてもいいかもしれない
@bindThis
public async bake(): Promise<void> {
const bufferedNoteIds = [];
let cursor = '0';
do {
// https://github.com/redis/ioredis#transparent-key-prefixing
const result = await this.redisForReactions.scan(
cursor,
'MATCH',
`${this.config.redis.prefix}:${REDIS_DELTA_PREFIX}:*`,
'COUNT',
'1000');
cursor = result[0];
bufferedNoteIds.push(...result[1].map(x => x.replace(`${this.config.redis.prefix}:${REDIS_DELTA_PREFIX}:`, '')));
} while (cursor !== '0');
const bufferedMap = await this.getMany(bufferedNoteIds);
// clear
const pipeline = this.redisForReactions.pipeline();
for (const noteId of bufferedNoteIds) {
pipeline.del(`${REDIS_DELTA_PREFIX}:${noteId}`);
pipeline.del(`${REDIS_PAIR_PREFIX}:${noteId}`);
}
await pipeline.exec();
// TODO: SQL一個にまとめたい
for (const [noteId, buffered] of bufferedMap) {
const sql = Object.entries(buffered.deltas)
.map(([reaction, count]) =>
`jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + ${count})::text::jsonb)`)
.join(' || ');
this.notesRepository.createQueryBuilder().update()
.set({
reactions: () => sql,
reactionAndUserPairCache: buffered.pairs.map(x => x.join('/')),
})
.where('id = :id', { id: noteId })
.execute();
}
}
@bindThis
public mergeReactions(src: MiNote['reactions'], delta: Record<string, number>): MiNote['reactions'] {
const reactions = { ...src };
for (const [name, count] of Object.entries(delta)) {
if (reactions[name] != null) {
reactions[name] += count;
} else {
reactions[name] = count;
}
}
return reactions;
}
@bindThis
public dispose(): void {
this.redisForSub.off('message', this.onMessage);
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}

View file

@ -8,6 +8,7 @@ import * as Redis from 'ioredis';
import { In } from 'typeorm'; import { In } from 'typeorm';
import { ModuleRef } from '@nestjs/core'; import { ModuleRef } from '@nestjs/core';
import type { import type {
MiMeta,
MiRole, MiRole,
MiRoleAssignment, MiRoleAssignment,
RoleAssignmentsRepository, RoleAssignmentsRepository,
@ -18,7 +19,6 @@ import { MemoryKVCache, MemorySingleCache } from '@/misc/cache.js';
import type { MiUser } from '@/models/User.js'; import type { MiUser } from '@/models/User.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { MetaService } from '@/core/MetaService.js';
import { CacheService } from '@/core/CacheService.js'; import { CacheService } from '@/core/CacheService.js';
import type { RoleCondFormulaValue } from '@/models/Role.js'; import type { RoleCondFormulaValue } from '@/models/Role.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
@ -60,6 +60,11 @@ export type RolePolicies = {
rateLimitFactor: number; rateLimitFactor: number;
canImportNotes: boolean; canImportNotes: boolean;
avatarDecorationLimit: number; avatarDecorationLimit: number;
canImportAntennas: boolean;
canImportBlocking: boolean;
canImportFollowing: boolean;
canImportMuting: boolean;
canImportUserLists: boolean;
}; };
export const DEFAULT_POLICIES: RolePolicies = { export const DEFAULT_POLICIES: RolePolicies = {
@ -91,6 +96,11 @@ export const DEFAULT_POLICIES: RolePolicies = {
rateLimitFactor: 1, rateLimitFactor: 1,
canImportNotes: true, canImportNotes: true,
avatarDecorationLimit: 1, avatarDecorationLimit: 1,
canImportAntennas: true,
canImportBlocking: true,
canImportFollowing: true,
canImportMuting: true,
canImportUserLists: true,
}; };
@Injectable() @Injectable()
@ -105,8 +115,8 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
constructor( constructor(
private moduleRef: ModuleRef, private moduleRef: ModuleRef,
@Inject(DI.redis) @Inject(DI.meta)
private redisClient: Redis.Redis, private meta: MiMeta,
@Inject(DI.redisForTimelines) @Inject(DI.redisForTimelines)
private redisForTimelines: Redis.Redis, private redisForTimelines: Redis.Redis,
@ -123,7 +133,6 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
@Inject(DI.roleAssignmentsRepository) @Inject(DI.roleAssignmentsRepository)
private roleAssignmentsRepository: RoleAssignmentsRepository, private roleAssignmentsRepository: RoleAssignmentsRepository,
private metaService: MetaService,
private cacheService: CacheService, private cacheService: CacheService,
private userEntityService: UserEntityService, private userEntityService: UserEntityService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
@ -343,8 +352,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
@bindThis @bindThis
public async getUserPolicies(userId: MiUser['id'] | null): Promise<RolePolicies> { public async getUserPolicies(userId: MiUser['id'] | null): Promise<RolePolicies> {
const meta = await this.metaService.fetch(); const basePolicies = { ...DEFAULT_POLICIES, ...this.meta.policies };
const basePolicies = { ...DEFAULT_POLICIES, ...meta.policies };
if (userId == null) return basePolicies; if (userId == null) return basePolicies;
@ -393,6 +401,11 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
rateLimitFactor: calc('rateLimitFactor', vs => Math.max(...vs)), rateLimitFactor: calc('rateLimitFactor', vs => Math.max(...vs)),
canImportNotes: calc('canImportNotes', vs => vs.some(v => v === true)), canImportNotes: calc('canImportNotes', vs => vs.some(v => v === true)),
avatarDecorationLimit: calc('avatarDecorationLimit', vs => Math.max(...vs)), avatarDecorationLimit: calc('avatarDecorationLimit', vs => Math.max(...vs)),
canImportAntennas: calc('canImportAntennas', vs => vs.some(v => v === true)),
canImportBlocking: calc('canImportBlocking', vs => vs.some(v => v === true)),
canImportFollowing: calc('canImportFollowing', vs => vs.some(v => v === true)),
canImportMuting: calc('canImportMuting', vs => vs.some(v => v === true)),
canImportUserLists: calc('canImportUserLists', vs => vs.some(v => v === true)),
}; };
} }

View file

@ -9,7 +9,7 @@ import { Inject, Injectable } from '@nestjs/common';
import * as argon2 from 'argon2'; import * as argon2 from 'argon2';
import { DataSource, IsNull } from 'typeorm'; import { DataSource, IsNull } from 'typeorm';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { UsedUsernamesRepository, UsersRepository } from '@/models/_.js'; import type { MiMeta, UsedUsernamesRepository, UsersRepository } from '@/models/_.js';
import { MiUser } from '@/models/User.js'; import { MiUser } from '@/models/User.js';
import { MiUserProfile } from '@/models/UserProfile.js'; import { MiUserProfile } from '@/models/UserProfile.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
@ -21,7 +21,6 @@ import { InstanceActorService } from '@/core/InstanceActorService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import UsersChart from '@/core/chart/charts/users.js'; import UsersChart from '@/core/chart/charts/users.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
import { MetaService } from '@/core/MetaService.js';
import { UserService } from '@/core/UserService.js'; import { UserService } from '@/core/UserService.js';
@Injectable() @Injectable()
@ -30,6 +29,9 @@ export class SignupService {
@Inject(DI.db) @Inject(DI.db)
private db: DataSource, private db: DataSource,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@ -40,7 +42,6 @@ export class SignupService {
private userService: UserService, private userService: UserService,
private userEntityService: UserEntityService, private userEntityService: UserEntityService,
private idService: IdService, private idService: IdService,
private metaService: MetaService,
private instanceActorService: InstanceActorService, private instanceActorService: InstanceActorService,
private usersChart: UsersChart, private usersChart: UsersChart,
) { ) {
@ -57,7 +58,6 @@ export class SignupService {
}) { }) {
const { username, password, passwordHash, host, reason } = opts; const { username, password, passwordHash, host, reason } = opts;
let hash = passwordHash; let hash = passwordHash;
const instance = await this.metaService.fetch(true);
// Validate username // Validate username
if (!this.userEntityService.validateLocalUsername(username)) { if (!this.userEntityService.validateLocalUsername(username)) {
@ -91,7 +91,7 @@ export class SignupService {
const isTheFirstUser = !await this.instanceActorService.realLocalUsersPresent(); const isTheFirstUser = !await this.instanceActorService.realLocalUsersPresent();
if (!opts.ignorePreservedUsernames && !isTheFirstUser) { if (!opts.ignorePreservedUsernames && !isTheFirstUser) {
const isPreserved = instance.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase()); const isPreserved = this.meta.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
if (isPreserved) { if (isPreserved) {
throw new Error('USED_USERNAME'); throw new Error('USED_USERNAME');
} }
@ -117,7 +117,7 @@ export class SignupService {
let account!: MiUser; let account!: MiUser;
let defaultApproval = false; let defaultApproval = false;
if (!instance.approvalRequiredForSignup) defaultApproval = true; if (!this.meta.approvalRequiredForSignup) defaultApproval = true;
// Start transaction // Start transaction
await this.db.transaction(async transactionalEntityManager => { await this.db.transaction(async transactionalEntityManager => {
@ -163,4 +163,3 @@ export class SignupService {
return { account, secret }; return { account, secret };
} }
} }

View file

@ -4,9 +4,9 @@
*/ */
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import type { MiMeta } from '@/models/_.js';
import * as Redis from 'ioredis'; import * as Redis from 'ioredis';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { MetaService } from '@/core/MetaService.js';
import { RedisKVCache } from '@/misc/cache.js'; import { RedisKVCache } from '@/misc/cache.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@ -15,10 +15,11 @@ export class SponsorsService implements OnApplicationShutdown {
private cache: RedisKVCache<void[]>; private cache: RedisKVCache<void[]>;
constructor( constructor(
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.redis) @Inject(DI.redis)
private redisClient: Redis.Redis, private redisClient: Redis.Redis,
private metaService: MetaService,
) { ) {
this.cache = new RedisKVCache<void[]>(this.redisClient, 'sponsors', { this.cache = new RedisKVCache<void[]>(this.redisClient, 'sponsors', {
lifetime: 1000 * 60 * 60, lifetime: 1000 * 60 * 60,
@ -34,14 +35,12 @@ export class SponsorsService implements OnApplicationShutdown {
@bindThis @bindThis
private async fetchInstanceSponsors() { private async fetchInstanceSponsors() {
const meta = await this.metaService.fetch(); if (!(this.meta.donationUrl && this.meta.donationUrl.includes('opencollective.com'))) {
if (!(meta.donationUrl && meta.donationUrl.includes('opencollective.com'))) {
return []; return [];
} }
try { try {
const backers = await fetch(`${meta.donationUrl}/members/users.json`).then((response) => response.json()); const backers = await fetch(`${this.meta.donationUrl}/members/users.json`).then((response) => response.json());
// Merge both together into one array and make sure it only has Active subscriptions // Merge both together into one array and make sure it only has Active subscriptions
const allSponsors = [...backers].filter(sponsor => sponsor.isActive === true && sponsor.role === 'BACKER' && sponsor.tier); const allSponsors = [...backers].filter(sponsor => sponsor.isActive === true && sponsor.role === 'BACKER' && sponsor.tier);

View file

@ -54,7 +54,7 @@ export class SystemWebhookService implements OnApplicationShutdown {
* SystemWebhook . * SystemWebhook .
*/ */
@bindThis @bindThis
public async fetchSystemWebhooks(params?: { public fetchSystemWebhooks(params?: {
ids?: MiSystemWebhook['id'][]; ids?: MiSystemWebhook['id'][];
isActive?: MiSystemWebhook['isActive']; isActive?: MiSystemWebhook['isActive'];
on?: MiSystemWebhook['on']; on?: MiSystemWebhook['on'];
@ -165,19 +165,24 @@ export class SystemWebhookService implements OnApplicationShutdown {
/** /**
* SystemWebhook Webhook配送キューに追加する * SystemWebhook Webhook配送キューに追加する
* @see QueueService.systemWebhookDeliver * @see QueueService.systemWebhookDeliver
* // TODO: contentの型を厳格化する
*/ */
@bindThis @bindThis
public async enqueueSystemWebhook(webhook: MiSystemWebhook | MiSystemWebhook['id'], type: SystemWebhookEventType, content: unknown) { public async enqueueSystemWebhook<T extends SystemWebhookEventType>(
webhook: MiSystemWebhook | MiSystemWebhook['id'],
type: T,
content: unknown,
) {
const webhookEntity = typeof webhook === 'string' const webhookEntity = typeof webhook === 'string'
? (await this.fetchActiveSystemWebhooks()).find(a => a.id === webhook) ? (await this.fetchActiveSystemWebhooks()).find(a => a.id === webhook)
: webhook; : webhook;
if (!webhookEntity || !webhookEntity.isActive) { if (!webhookEntity || !webhookEntity.isActive) {
this.logger.info(`Webhook is not active or not found : ${webhook}`); this.logger.info(`SystemWebhook is not active or not found : ${webhook}`);
return; return;
} }
if (!webhookEntity.on.includes(type)) { if (!webhookEntity.on.includes(type)) {
this.logger.info(`Webhook ${webhookEntity.id} is not listening to ${type}`); this.logger.info(`SystemWebhook ${webhookEntity.id} is not listening to ${type}`);
return; return;
} }

View file

@ -13,23 +13,20 @@ import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
import type { Packed } from '@/misc/json-schema.js';
import InstanceChart from '@/core/chart/charts/instance.js'; import InstanceChart from '@/core/chart/charts/instance.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { UserWebhookService } from '@/core/UserWebhookService.js'; import { UserWebhookService } from '@/core/UserWebhookService.js';
import { NotificationService } from '@/core/NotificationService.js'; import { NotificationService } from '@/core/NotificationService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { FollowingsRepository, FollowRequestsRepository, InstancesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import type { FollowingsRepository, FollowRequestsRepository, InstancesRepository, MiMeta, UserProfilesRepository, UsersRepository } from '@/models/_.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { UserBlockingService } from '@/core/UserBlockingService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js';
import { MetaService } from '@/core/MetaService.js';
import { CacheService } from '@/core/CacheService.js'; import { CacheService } from '@/core/CacheService.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { AccountMoveService } from '@/core/AccountMoveService.js'; import { AccountMoveService } from '@/core/AccountMoveService.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
import type { ThinUser } from '@/queue/types.js'; import type { ThinUser } from '@/queue/types.js';
import Logger from '../logger.js'; import Logger from '../logger.js';
@ -58,6 +55,9 @@ export class UserFollowingService implements OnModuleInit {
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@ -79,13 +79,11 @@ export class UserFollowingService implements OnModuleInit {
private idService: IdService, private idService: IdService,
private queueService: QueueService, private queueService: QueueService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
private metaService: MetaService,
private notificationService: NotificationService, private notificationService: NotificationService,
private federatedInstanceService: FederatedInstanceService, private federatedInstanceService: FederatedInstanceService,
private webhookService: UserWebhookService, private webhookService: UserWebhookService,
private apRendererService: ApRendererService, private apRendererService: ApRendererService,
private accountMoveService: AccountMoveService, private accountMoveService: AccountMoveService,
private fanoutTimelineService: FanoutTimelineService,
private perUserFollowingChart: PerUserFollowingChart, private perUserFollowingChart: PerUserFollowingChart,
private instanceChart: InstanceChart, private instanceChart: InstanceChart,
) { ) {
@ -172,7 +170,7 @@ export class UserFollowingService implements OnModuleInit {
followee.isLocked || followee.isLocked ||
(followeeProfile.carefulBot && follower.isBot) || (followeeProfile.carefulBot && follower.isBot) ||
(this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee) && process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING !== 'true') || (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee) && process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING !== 'true') ||
(this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower) && this.utilityService.isSilencedHost((await this.metaService.fetch()).silencedHosts, follower.host)) (this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower) && this.utilityService.isSilencedHost(this.meta.silencedHosts, follower.host))
) { ) {
let autoAccept = false; let autoAccept = false;
@ -277,16 +275,19 @@ export class UserFollowingService implements OnModuleInit {
followeeId: followee.id, followeeId: followee.id,
followerId: follower.id, followerId: follower.id,
}); });
// 通知を作成
if (follower.host === null) {
this.notificationService.createNotification(follower.id, 'followRequestAccepted', {
}, followee.id);
}
} }
if (alreadyFollowed) return; if (alreadyFollowed) return;
// 通知を作成
if (follower.host === null) {
const profile = await this.cacheService.userProfileCache.fetch(followee.id);
this.notificationService.createNotification(follower.id, 'followRequestAccepted', {
message: profile.followedMessage,
}, followee.id);
}
this.globalEventService.publishInternalEvent('follow', { followerId: follower.id, followeeId: followee.id }); this.globalEventService.publishInternalEvent('follow', { followerId: follower.id, followeeId: followee.id });
const [followeeUser, followerUser] = await Promise.all([ const [followeeUser, followerUser] = await Promise.all([
@ -307,14 +308,14 @@ export class UserFollowingService implements OnModuleInit {
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
this.federatedInstanceService.fetch(follower.host).then(async i => { this.federatedInstanceService.fetch(follower.host).then(async i => {
this.instancesRepository.increment({ id: i.id }, 'followingCount', 1); this.instancesRepository.increment({ id: i.id }, 'followingCount', 1);
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) { if (this.meta.enableChartsForFederatedInstances) {
this.instanceChart.updateFollowing(i.host, true); this.instanceChart.updateFollowing(i.host, true);
} }
}); });
} else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) { } else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
this.federatedInstanceService.fetch(followee.host).then(async i => { this.federatedInstanceService.fetch(followee.host).then(async i => {
this.instancesRepository.increment({ id: i.id }, 'followersCount', 1); this.instancesRepository.increment({ id: i.id }, 'followersCount', 1);
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) { if (this.meta.enableChartsForFederatedInstances) {
this.instanceChart.updateFollowers(i.host, true); this.instanceChart.updateFollowers(i.host, true);
} }
}); });
@ -439,14 +440,14 @@ export class UserFollowingService implements OnModuleInit {
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
this.federatedInstanceService.fetch(follower.host).then(async i => { this.federatedInstanceService.fetch(follower.host).then(async i => {
this.instancesRepository.decrement({ id: i.id }, 'followingCount', 1); this.instancesRepository.decrement({ id: i.id }, 'followingCount', 1);
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) { if (this.meta.enableChartsForFederatedInstances) {
this.instanceChart.updateFollowing(i.host, false); this.instanceChart.updateFollowing(i.host, false);
} }
}); });
} else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) { } else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
this.federatedInstanceService.fetch(followee.host).then(async i => { this.federatedInstanceService.fetch(followee.host).then(async i => {
this.instancesRepository.decrement({ id: i.id }, 'followersCount', 1); this.instancesRepository.decrement({ id: i.id }, 'followersCount', 1);
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) { if (this.meta.enableChartsForFederatedInstances) {
this.instanceChart.updateFollowers(i.host, false); this.instanceChart.updateFollowers(i.host, false);
} }
}); });

View file

@ -5,8 +5,8 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis'; import * as Redis from 'ioredis';
import type { WebhooksRepository } from '@/models/_.js'; import { type WebhooksRepository } from '@/models/_.js';
import type { MiWebhook } from '@/models/Webhook.js'; import { MiWebhook } from '@/models/Webhook.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { GlobalEvents } from '@/core/GlobalEventService.js'; import { GlobalEvents } from '@/core/GlobalEventService.js';
@ -38,6 +38,31 @@ export class UserWebhookService implements OnApplicationShutdown {
return this.activeWebhooks; return this.activeWebhooks;
} }
/**
* UserWebhook .
*/
@bindThis
public fetchWebhooks(params?: {
ids?: MiWebhook['id'][];
isActive?: MiWebhook['active'];
on?: MiWebhook['on'];
}): Promise<MiWebhook[]> {
const query = this.webhooksRepository.createQueryBuilder('webhook');
if (params) {
if (params.ids && params.ids.length > 0) {
query.andWhere('webhook.id IN (:...ids)', { ids: params.ids });
}
if (params.isActive !== undefined) {
query.andWhere('webhook.active = :isActive', { isActive: params.isActive });
}
if (params.on && params.on.length > 0) {
query.andWhere(':on <@ webhook.on', { on: params.on });
}
}
return query.getMany();
}
@bindThis @bindThis
private async onMessage(_: string, data: string): Promise<void> { private async onMessage(_: string, data: string): Promise<void> {
const obj = JSON.parse(data); const obj = JSON.parse(data);

View file

@ -10,12 +10,16 @@ import RE2 from 're2';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { MiMeta } from '@/models/Meta.js';
@Injectable() @Injectable()
export class UtilityService { export class UtilityService {
constructor( constructor(
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.meta)
private meta: MiMeta,
) { ) {
} }
@ -112,4 +116,18 @@ export class UtilityService {
const host = `${this.toPuny(urlObj.hostname)}${urlObj.port.length > 0 ? ':' + urlObj.port : ''}`; const host = `${this.toPuny(urlObj.hostname)}${urlObj.port.length > 0 ? ':' + urlObj.port : ''}`;
return host; return host;
} }
public isFederationAllowedHost(host: string): boolean {
if (this.meta.federation === 'none') return false;
if (this.meta.federation === 'specified' && !this.meta.federationHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`))) return false;
if (this.isBlockedHost(this.meta.blockedHosts, host)) return false;
return true;
}
@bindThis
public isFederationAllowedUri(uri: string): boolean {
const host = this.extractDbHost(uri);
return this.isFederationAllowedHost(host);
}
} }

View file

@ -12,10 +12,9 @@ import {
} from '@simplewebauthn/server'; } from '@simplewebauthn/server';
import { AttestationFormat, isoCBOR, isoUint8Array } from '@simplewebauthn/server/helpers'; import { AttestationFormat, isoCBOR, isoUint8Array } from '@simplewebauthn/server/helpers';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { UserSecurityKeysRepository } from '@/models/_.js'; import type { MiMeta, UserSecurityKeysRepository } from '@/models/_.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { MetaService } from '@/core/MetaService.js';
import { MiUser } from '@/models/_.js'; import { MiUser } from '@/models/_.js';
import { IdentifiableError } from '@/misc/identifiable-error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js';
import type { import type {
@ -23,7 +22,6 @@ import type {
AuthenticatorTransportFuture, AuthenticatorTransportFuture,
CredentialDeviceType, CredentialDeviceType,
PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialCreationOptionsJSON,
PublicKeyCredentialDescriptorFuture,
PublicKeyCredentialRequestOptionsJSON, PublicKeyCredentialRequestOptionsJSON,
RegistrationResponseJSON, RegistrationResponseJSON,
} from '@simplewebauthn/types'; } from '@simplewebauthn/types';
@ -31,33 +29,33 @@ import type {
@Injectable() @Injectable()
export class WebAuthnService { export class WebAuthnService {
constructor( constructor(
@Inject(DI.redis)
private redisClient: Redis.Redis,
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.redis)
private redisClient: Redis.Redis,
@Inject(DI.userSecurityKeysRepository) @Inject(DI.userSecurityKeysRepository)
private userSecurityKeysRepository: UserSecurityKeysRepository, private userSecurityKeysRepository: UserSecurityKeysRepository,
private metaService: MetaService,
) { ) {
} }
@bindThis @bindThis
public async getRelyingParty(): Promise<{ origin: string; rpId: string; rpName: string; rpIcon?: string; }> { public getRelyingParty(): { origin: string; rpId: string; rpName: string; rpIcon?: string; } {
const instance = await this.metaService.fetch();
return { return {
origin: this.config.url, origin: this.config.url,
rpId: this.config.hostname, rpId: this.config.hostname,
rpName: instance.name ?? this.config.host, rpName: this.meta.name ?? this.config.host,
rpIcon: instance.iconUrl ?? undefined, rpIcon: this.meta.iconUrl ?? undefined,
}; };
} }
@bindThis @bindThis
public async initiateRegistration(userId: MiUser['id'], userName: string, userDisplayName?: string): Promise<PublicKeyCredentialCreationOptionsJSON> { public async initiateRegistration(userId: MiUser['id'], userName: string, userDisplayName?: string): Promise<PublicKeyCredentialCreationOptionsJSON> {
const relyingParty = await this.getRelyingParty(); const relyingParty = this.getRelyingParty();
const keys = await this.userSecurityKeysRepository.findBy({ const keys = await this.userSecurityKeysRepository.findBy({
userId: userId, userId: userId,
}); });
@ -104,7 +102,7 @@ export class WebAuthnService {
await this.redisClient.del(`webauthn:challenge:${userId}`); await this.redisClient.del(`webauthn:challenge:${userId}`);
const relyingParty = await this.getRelyingParty(); const relyingParty = this.getRelyingParty();
let verification; let verification;
try { try {
@ -143,7 +141,7 @@ export class WebAuthnService {
@bindThis @bindThis
public async initiateAuthentication(userId: MiUser['id']): Promise<PublicKeyCredentialRequestOptionsJSON> { public async initiateAuthentication(userId: MiUser['id']): Promise<PublicKeyCredentialRequestOptionsJSON> {
const relyingParty = await this.getRelyingParty(); const relyingParty = this.getRelyingParty();
const keys = await this.userSecurityKeysRepository.findBy({ const keys = await this.userSecurityKeysRepository.findBy({
userId: userId, userId: userId,
}); });
@ -166,6 +164,86 @@ export class WebAuthnService {
return authenticationOptions; return authenticationOptions;
} }
/**
* Initiate Passkey Auth (Without specifying user)
* @returns authenticationOptions
*/
@bindThis
public async initiateSignInWithPasskeyAuthentication(context: string): Promise<PublicKeyCredentialRequestOptionsJSON> {
const relyingParty = await this.getRelyingParty();
const authenticationOptions = await generateAuthenticationOptions({
rpID: relyingParty.rpId,
userVerification: 'preferred',
});
await this.redisClient.setex(`webauthn:challenge:${context}`, 90, authenticationOptions.challenge);
return authenticationOptions;
}
/**
* Verify Webauthn AuthenticationCredential
* @throws IdentifiableError
* @returns If the challenge is successful, return the user ID. Otherwise, return null.
*/
@bindThis
public async verifySignInWithPasskeyAuthentication(context: string, response: AuthenticationResponseJSON): Promise<MiUser['id'] | null> {
const challenge = await this.redisClient.get(`webauthn:challenge:${context}`);
if (!challenge) {
throw new IdentifiableError('2d16e51c-007b-4edd-afd2-f7dd02c947f6', `challenge '${context}' not found`);
}
await this.redisClient.del(`webauthn:challenge:${context}`);
const key = await this.userSecurityKeysRepository.findOneBy({
id: response.id,
});
if (!key) {
throw new IdentifiableError('36b96a7d-b547-412d-aeed-2d611cdc8cdc', 'Unknown Webauthn key');
}
const relyingParty = await this.getRelyingParty();
let verification;
try {
verification = await verifyAuthenticationResponse({
response: response,
expectedChallenge: challenge,
expectedOrigin: relyingParty.origin,
expectedRPID: relyingParty.rpId,
authenticator: {
credentialID: key.id,
credentialPublicKey: Buffer.from(key.publicKey, 'base64url'),
counter: key.counter,
transports: key.transports ? key.transports as AuthenticatorTransportFuture[] : undefined,
},
requireUserVerification: true,
});
} catch (error) {
throw new IdentifiableError('b18c89a7-5b5e-4cec-bb5b-0419f332d430', `verification failed: ${error}`);
}
const { verified, authenticationInfo } = verification;
if (!verified) {
return null;
}
await this.userSecurityKeysRepository.update({
id: response.id,
}, {
lastUsed: new Date(),
counter: authenticationInfo.newCounter,
credentialDeviceType: authenticationInfo.credentialDeviceType,
credentialBackedUp: authenticationInfo.credentialBackedUp,
});
return key.userId;
}
@bindThis @bindThis
public async verifyAuthentication(userId: MiUser['id'], response: AuthenticationResponseJSON): Promise<boolean> { public async verifyAuthentication(userId: MiUser['id'], response: AuthenticationResponseJSON): Promise<boolean> {
const challenge = await this.redisClient.get(`webauthn:challenge:${userId}`); const challenge = await this.redisClient.get(`webauthn:challenge:${userId}`);
@ -209,7 +287,7 @@ export class WebAuthnService {
} }
} }
const relyingParty = await this.getRelyingParty(); const relyingParty = this.getRelyingParty();
let verification; let verification;
try { try {

View file

@ -0,0 +1,454 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import { MiAbuseUserReport, MiNote, MiUser, MiWebhook } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
import { MiSystemWebhook, type SystemWebhookEventType } from '@/models/SystemWebhook.js';
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
import { Packed } from '@/misc/json-schema.js';
import { type WebhookEventTypes } from '@/models/Webhook.js';
import { UserWebhookService } from '@/core/UserWebhookService.js';
import { QueueService } from '@/core/QueueService.js';
const oneDayMillis = 24 * 60 * 60 * 1000;
function generateAbuseReport(override?: Partial<MiAbuseUserReport>): MiAbuseUserReport {
return {
id: 'dummy-abuse-report1',
targetUserId: 'dummy-target-user',
targetUser: null,
reporterId: 'dummy-reporter-user',
reporter: null,
assigneeId: null,
assignee: null,
resolved: false,
forwarded: false,
comment: 'This is a dummy report for testing purposes.',
targetUserHost: null,
reporterHost: null,
...override,
};
}
function generateDummyUser(override?: Partial<MiUser>): MiUser {
return {
id: 'dummy-user-1',
updatedAt: new Date(Date.now() - oneDayMillis * 7),
lastFetchedAt: new Date(Date.now() - oneDayMillis * 5),
lastActiveDate: new Date(Date.now() - oneDayMillis * 3),
hideOnlineStatus: false,
username: 'dummy1',
usernameLower: 'dummy1',
name: 'DummyUser1',
followersCount: 10,
followingCount: 5,
movedToUri: null,
movedAt: null,
alsoKnownAs: null,
notesCount: 30,
avatarId: null,
avatar: null,
bannerId: null,
banner: null,
backgroundId: null,
background: null,
avatarUrl: null,
bannerUrl: null,
backgroundUrl: null,
avatarBlurhash: null,
bannerBlurhash: null,
backgroundBlurhash: null,
avatarDecorations: [],
tags: [],
isSuspended: false,
isLocked: false,
isSilenced: false,
isBot: false,
isCat: true,
speakAsCat: true,
isRoot: false,
isExplorable: true,
isHibernated: false,
isDeleted: false,
emojis: [],
score: 0,
host: null,
inbox: null,
sharedInbox: null,
featured: null,
uri: null,
followersUri: null,
token: null,
approved: true,
signupReason: null,
noindex: false,
...override,
};
}
function generateDummyNote(override?: Partial<MiNote>): MiNote {
return {
id: 'dummy-note-1',
replyId: null,
reply: null,
renoteId: null,
renote: null,
threadId: null,
text: 'This is a dummy note for testing purposes.',
name: null,
cw: null,
userId: 'dummy-user-1',
user: null,
localOnly: true,
reactionAcceptance: 'likeOnly',
renoteCount: 10,
repliesCount: 5,
clippedCount: 0,
reactions: {},
visibility: 'public',
uri: null,
url: null,
fileIds: [],
attachedFileTypes: [],
visibleUserIds: [],
mentions: [],
mentionedRemoteUsers: '[]',
reactionAndUserPairCache: [],
emojis: [],
tags: [],
hasPoll: false,
channelId: null,
channel: null,
userHost: null,
replyUserId: null,
replyUserHost: null,
renoteUserId: null,
renoteUserHost: null,
updatedAt: null,
...override,
};
}
function toPackedNote(note: MiNote, detail = true, override?: Packed<'Note'>): Packed<'Note'> {
return {
id: note.id,
createdAt: new Date().toISOString(),
deletedAt: null,
text: note.text,
cw: note.cw,
userId: note.userId,
user: toPackedUserLite(note.user ?? generateDummyUser()),
replyId: note.replyId,
renoteId: note.renoteId,
isHidden: false,
visibility: note.visibility,
mentions: note.mentions,
visibleUserIds: note.visibleUserIds,
fileIds: note.fileIds,
files: [],
tags: note.tags,
poll: null,
emojis: note.emojis,
channelId: note.channelId,
channel: note.channel,
localOnly: note.localOnly,
reactionAcceptance: note.reactionAcceptance,
reactionEmojis: {},
reactions: {},
reactionCount: 0,
renoteCount: note.renoteCount,
repliesCount: note.repliesCount,
uri: note.uri ?? undefined,
url: note.url ?? undefined,
reactionAndUserPairCache: note.reactionAndUserPairCache,
...(detail ? {
clippedCount: note.clippedCount,
reply: note.reply ? toPackedNote(note.reply, false) : null,
renote: note.renote ? toPackedNote(note.renote, true) : null,
myReaction: null,
} : {}),
...override,
};
}
function toPackedUserLite(user: MiUser, override?: Packed<'UserLite'>): Packed<'UserLite'> {
return {
id: user.id,
name: user.name,
username: user.username,
host: user.host,
avatarUrl: user.avatarUrl,
avatarBlurhash: user.avatarBlurhash,
avatarDecorations: user.avatarDecorations.map(it => ({
id: it.id,
angle: it.angle,
flipH: it.flipH,
url: 'https://example.com/dummy-image001.png',
offsetX: it.offsetX,
offsetY: it.offsetY,
})),
isBot: user.isBot,
isCat: user.isCat,
speakAsCat: user.speakAsCat,
emojis: user.emojis,
onlineStatus: 'active',
badgeRoles: [],
noindex: user.noindex,
isModerator: false,
isAdmin: false,
isSystem: false,
isSilenced: user.isSilenced,
...override,
};
}
function toPackedUserDetailedNotMe(user: MiUser, override?: Packed<'UserDetailedNotMe'>): Packed<'UserDetailedNotMe'> {
return {
...toPackedUserLite(user),
url: null,
uri: null,
movedTo: null,
alsoKnownAs: [],
createdAt: new Date().toISOString(),
updatedAt: user.updatedAt?.toISOString() ?? null,
lastFetchedAt: user.lastFetchedAt?.toISOString() ?? null,
bannerUrl: user.bannerUrl,
bannerBlurhash: user.bannerBlurhash,
backgroundUrl: user.backgroundUrl,
backgroundBlurhash: user.backgroundBlurhash,
isLocked: user.isLocked,
isSilenced: false,
isSuspended: user.isSuspended,
description: null,
location: null,
birthday: null,
lang: null,
fields: [],
verifiedLinks: [],
followersCount: user.followersCount,
followingCount: user.followingCount,
notesCount: user.notesCount,
pinnedNoteIds: [],
pinnedNotes: [],
pinnedPageId: null,
pinnedPage: null,
publicReactions: true,
followersVisibility: 'public',
followingVisibility: 'public',
twoFactorEnabled: false,
usePasswordLessLogin: false,
securityKeys: false,
roles: [],
memo: null,
moderationNote: undefined,
isFollowing: false,
isFollowed: false,
hasPendingFollowRequestFromYou: false,
hasPendingFollowRequestToYou: false,
isBlocking: false,
isBlocked: false,
isMuted: false,
isRenoteMuted: false,
notify: 'none',
withReplies: true,
ListenBrainz: null,
...override,
};
}
const dummyUser1 = generateDummyUser();
const dummyUser2 = generateDummyUser({
id: 'dummy-user-2',
updatedAt: new Date(Date.now() - oneDayMillis * 30),
lastFetchedAt: new Date(Date.now() - oneDayMillis),
lastActiveDate: new Date(Date.now() - oneDayMillis),
username: 'dummy2',
usernameLower: 'dummy2',
name: 'DummyUser2',
followersCount: 40,
followingCount: 50,
notesCount: 900,
});
const dummyUser3 = generateDummyUser({
id: 'dummy-user-3',
updatedAt: new Date(Date.now() - oneDayMillis * 15),
lastFetchedAt: new Date(Date.now() - oneDayMillis * 2),
lastActiveDate: new Date(Date.now() - oneDayMillis * 2),
username: 'dummy3',
usernameLower: 'dummy3',
name: 'DummyUser3',
followersCount: 60,
followingCount: 70,
notesCount: 15900,
});
@Injectable()
export class WebhookTestService {
public static NoSuchWebhookError = class extends Error {};
constructor(
private userWebhookService: UserWebhookService,
private systemWebhookService: SystemWebhookService,
private queueService: QueueService,
) {
}
/**
* UserWebhookのテスト送信を行う.
* .
*
* Webhookは以下の設定を無視する.
* - Webhookそのものの有効active
* - on
*/
@bindThis
public async testUserWebhook(
params: {
webhookId: MiWebhook['id'],
type: WebhookEventTypes,
override?: Partial<Omit<MiWebhook, 'id'>>,
},
sender: MiUser | null,
) {
const webhooks = await this.userWebhookService.fetchWebhooks({ ids: [params.webhookId] })
.then(it => it.filter(it => it.userId === sender?.id));
if (webhooks.length === 0) {
throw new WebhookTestService.NoSuchWebhookError();
}
const webhook = webhooks[0];
const send = (contents: unknown) => {
const merged = {
...webhook,
...params.override,
};
// テスト目的なのでUserWebhookServiceの機能を経由せず直接キューに追加するチェック処理などをスキップする意図.
// また、Jobの試行回数も1回だけ.
this.queueService.userWebhookDeliver(merged, params.type, contents, { attempts: 1 });
};
const dummyNote1 = generateDummyNote({
userId: dummyUser1.id,
user: dummyUser1,
});
const dummyReply1 = generateDummyNote({
id: 'dummy-reply-1',
replyId: dummyNote1.id,
reply: dummyNote1,
userId: dummyUser1.id,
user: dummyUser1,
});
const dummyRenote1 = generateDummyNote({
id: 'dummy-renote-1',
renoteId: dummyNote1.id,
renote: dummyNote1,
userId: dummyUser2.id,
user: dummyUser2,
text: null,
});
const dummyMention1 = generateDummyNote({
id: 'dummy-mention-1',
userId: dummyUser1.id,
user: dummyUser1,
text: `@${dummyUser2.username} This is a mention to you.`,
mentions: [dummyUser2.id],
});
switch (params.type) {
case 'note': {
send(toPackedNote(dummyNote1));
break;
}
case 'reply': {
send(toPackedNote(dummyReply1));
break;
}
case 'renote': {
send(toPackedNote(dummyRenote1));
break;
}
case 'mention': {
send(toPackedNote(dummyMention1));
break;
}
case 'follow': {
send(toPackedUserDetailedNotMe(dummyUser1));
break;
}
case 'followed': {
send(toPackedUserLite(dummyUser2));
break;
}
case 'unfollow': {
send(toPackedUserDetailedNotMe(dummyUser3));
break;
}
}
}
/**
* SystemWebhookのテスト送信を行う.
* .
*
* Webhookは以下の設定を無視する.
* - Webhookそのものの有効isActive
* - on
*/
@bindThis
public async testSystemWebhook(
params: {
webhookId: MiSystemWebhook['id'],
type: SystemWebhookEventType,
override?: Partial<Omit<MiSystemWebhook, 'id'>>,
},
) {
const webhooks = await this.systemWebhookService.fetchSystemWebhooks({ ids: [params.webhookId] });
if (webhooks.length === 0) {
throw new WebhookTestService.NoSuchWebhookError();
}
const webhook = webhooks[0];
const send = (contents: unknown) => {
const merged = {
...webhook,
...params.override,
};
// テスト目的なのでSystemWebhookServiceの機能を経由せず直接キューに追加するチェック処理などをスキップする意図.
// また、Jobの試行回数も1回だけ.
this.queueService.systemWebhookDeliver(merged, params.type, contents, { attempts: 1 });
};
switch (params.type) {
case 'abuseReport': {
send(generateAbuseReport({
targetUserId: dummyUser1.id,
targetUser: dummyUser1,
reporterId: dummyUser2.id,
reporter: dummyUser2,
}));
break;
}
case 'abuseReportResolved': {
send(generateAbuseReport({
targetUserId: dummyUser1.id,
targetUser: dummyUser1,
reporterId: dummyUser2.id,
reporter: dummyUser2,
assigneeId: dummyUser3.id,
assignee: dummyUser3,
resolved: true,
}));
break;
}
case 'userCreated': {
send(toPackedUserLite(dummyUser1));
break;
}
}
}
}

View file

@ -18,14 +18,13 @@ import { NoteCreateService } from '@/core/NoteCreateService.js';
import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js'; import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js';
import { AppLockService } from '@/core/AppLockService.js'; import { AppLockService } from '@/core/AppLockService.js';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
import { MetaService } from '@/core/MetaService.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { StatusError } from '@/misc/status-error.js'; import { StatusError } from '@/misc/status-error.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { QueueService } from '@/core/QueueService.js'; import { QueueService } from '@/core/QueueService.js';
import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/_.js'; import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository, MiMeta } from '@/models/_.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import type { MiRemoteUser } from '@/models/User.js'; import type { MiRemoteUser } from '@/models/User.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
@ -51,6 +50,9 @@ export class ApInboxService {
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@ -67,7 +69,6 @@ export class ApInboxService {
private noteEntityService: NoteEntityService, private noteEntityService: NoteEntityService,
private utilityService: UtilityService, private utilityService: UtilityService,
private idService: IdService, private idService: IdService,
private metaService: MetaService,
private abuseReportService: AbuseReportService, private abuseReportService: AbuseReportService,
private userFollowingService: UserFollowingService, private userFollowingService: UserFollowingService,
private apAudienceService: ApAudienceService, private apAudienceService: ApAudienceService,
@ -295,9 +296,8 @@ export class ApInboxService {
return; return;
} }
// アナウンス先をブロックしてたら中断 // アナウンス先が許可されているかチェック
const meta = await this.metaService.fetch(); if (!this.utilityService.isFederationAllowedUri(uri)) return;
if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) return;
const unlock = await this.appLockService.getApLock(uri); const unlock = await this.appLockService.getApLock(uri);

View file

@ -23,11 +23,10 @@ import { MfmService } from '@/core/MfmService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import type { MiUserKeypair } from '@/models/UserKeypair.js'; import type { MiUserKeypair } from '@/models/UserKeypair.js';
import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository, InstancesRepository } from '@/models/_.js'; import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository, InstancesRepository, MiMeta } from '@/models/_.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { MetaService } from '../MetaService.js';
import { JsonLdService } from './JsonLdService.js'; import { JsonLdService } from './JsonLdService.js';
import { ApMfmService } from './ApMfmService.js'; import { ApMfmService } from './ApMfmService.js';
import { CONTEXT } from './misc/contexts.js'; import { CONTEXT } from './misc/contexts.js';
@ -39,6 +38,9 @@ export class ApRendererService {
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@ -65,7 +67,6 @@ export class ApRendererService {
private apMfmService: ApMfmService, private apMfmService: ApMfmService,
private mfmService: MfmService, private mfmService: MfmService,
private idService: IdService, private idService: IdService,
private metaService: MetaService,
) { ) {
} }
@ -273,10 +274,9 @@ export class ApRendererService {
@bindThis @bindThis
public async renderLike(noteReaction: MiNoteReaction, note: { uri: string | null }): Promise<ILike> { public async renderLike(noteReaction: MiNoteReaction, note: { uri: string | null }): Promise<ILike> {
const reaction = noteReaction.reaction; const reaction = noteReaction.reaction;
const meta = await this.metaService.fetch(true);
let isMastodon = false; let isMastodon = false;
if (meta.defaultLike && reaction.replaceAll(':', '') === meta.defaultLike.replaceAll(':', '')) { if (this.meta.defaultLike && reaction.replaceAll(':', '') === this.meta.defaultLike.replaceAll(':', '')) {
const note = await this.notesRepository.findOneBy({ id: noteReaction.noteId }); const note = await this.notesRepository.findOneBy({ id: noteReaction.noteId });
if (note && note.userHost) { if (note && note.userHost) {
@ -518,6 +518,7 @@ export class ApRendererService {
name: user.name, name: user.name,
summary: profile.description ? this.mfmService.toHtml(mfm.parse(profile.description)) : null, summary: profile.description ? this.mfmService.toHtml(mfm.parse(profile.description)) : null,
_misskey_summary: profile.description, _misskey_summary: profile.description,
_misskey_followedMessage: profile.followedMessage,
icon: avatar ? this.renderImage(avatar) : null, icon: avatar ? this.renderImage(avatar) : null,
image: banner ? this.renderImage(banner) : null, image: banner ? this.renderImage(banner) : null,
backgroundUrl: background ? this.renderImage(background) : null, backgroundUrl: background ? this.renderImage(background) : null,

View file

@ -208,12 +208,12 @@ export class ApRequestService {
const contentType = res.headers.get('content-type'); const contentType = res.headers.get('content-type');
if ( if (
res.ok res.ok &&
&& (contentType ?? '').split(';')[0].trimEnd().toLowerCase() === 'text/html' (contentType ?? '').split(';')[0].trimEnd().toLowerCase() === 'text/html' &&
&& _followAlternate === true _followAlternate === true
) { ) {
const html = await res.text(); const html = await res.text();
const window = new Window({ const { window, happyDOM } = new Window({
settings: { settings: {
disableJavaScriptEvaluation: true, disableJavaScriptEvaluation: true,
disableJavaScriptFileLoading: true, disableJavaScriptFileLoading: true,
@ -247,7 +247,7 @@ export class ApRequestService {
} catch (e) { } catch (e) {
// something went wrong parsing the HTML, ignore the whole thing // something went wrong parsing the HTML, ignore the whole thing
} finally { } finally {
await window.happyDOM.close(); happyDOM.close().catch(err => {});
} }
} }
//#endregion //#endregion

View file

@ -7,9 +7,8 @@ import { Inject, Injectable } from '@nestjs/common';
import { IsNull, Not } from 'typeorm'; import { IsNull, Not } from 'typeorm';
import type { MiLocalUser, MiRemoteUser } from '@/models/User.js'; import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
import { InstanceActorService } from '@/core/InstanceActorService.js'; import { InstanceActorService } from '@/core/InstanceActorService.js';
import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository, FollowRequestsRepository } from '@/models/_.js'; import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository, FollowRequestsRepository, MiMeta } from '@/models/_.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { MetaService } from '@/core/MetaService.js';
import { HttpRequestService } from '@/core/HttpRequestService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
@ -30,6 +29,7 @@ export class Resolver {
constructor( constructor(
private config: Config, private config: Config,
private meta: MiMeta,
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
private notesRepository: NotesRepository, private notesRepository: NotesRepository,
private pollsRepository: PollsRepository, private pollsRepository: PollsRepository,
@ -37,7 +37,6 @@ export class Resolver {
private followRequestsRepository: FollowRequestsRepository, private followRequestsRepository: FollowRequestsRepository,
private utilityService: UtilityService, private utilityService: UtilityService,
private instanceActorService: InstanceActorService, private instanceActorService: InstanceActorService,
private metaService: MetaService,
private apRequestService: ApRequestService, private apRequestService: ApRequestService,
private httpRequestService: HttpRequestService, private httpRequestService: HttpRequestService,
private apRendererService: ApRendererService, private apRendererService: ApRendererService,
@ -98,8 +97,7 @@ export class Resolver {
return await this.resolveLocal(value); return await this.resolveLocal(value);
} }
const meta = await this.metaService.fetch(); if (!this.utilityService.isFederationAllowedHost(host)) {
if (this.utilityService.isBlockedHost(meta.blockedHosts, host)) {
throw new Error('Instance is blocked'); throw new Error('Instance is blocked');
} }
@ -190,6 +188,9 @@ export class ApResolverService {
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@ -207,7 +208,6 @@ export class ApResolverService {
private utilityService: UtilityService, private utilityService: UtilityService,
private instanceActorService: InstanceActorService, private instanceActorService: InstanceActorService,
private metaService: MetaService,
private apRequestService: ApRequestService, private apRequestService: ApRequestService,
private httpRequestService: HttpRequestService, private httpRequestService: HttpRequestService,
private apRendererService: ApRendererService, private apRendererService: ApRendererService,
@ -220,6 +220,7 @@ export class ApResolverService {
public createResolver(): Resolver { public createResolver(): Resolver {
return new Resolver( return new Resolver(
this.config, this.config,
this.meta,
this.usersRepository, this.usersRepository,
this.notesRepository, this.notesRepository,
this.pollsRepository, this.pollsRepository,
@ -227,7 +228,6 @@ export class ApResolverService {
this.followRequestsRepository, this.followRequestsRepository,
this.utilityService, this.utilityService,
this.instanceActorService, this.instanceActorService,
this.metaService,
this.apRequestService, this.apRequestService,
this.httpRequestService, this.httpRequestService,
this.apRendererService, this.apRendererService,

View file

@ -557,6 +557,7 @@ const extension_context_definition = {
'_misskey_reaction': 'misskey:_misskey_reaction', '_misskey_reaction': 'misskey:_misskey_reaction',
'_misskey_votes': 'misskey:_misskey_votes', '_misskey_votes': 'misskey:_misskey_votes',
'_misskey_summary': 'misskey:_misskey_summary', '_misskey_summary': 'misskey:_misskey_summary',
'_misskey_followedMessage': 'misskey:_misskey_followedMessage',
'isCat': 'misskey:isCat', 'isCat': 'misskey:isCat',
// Firefish // Firefish
firefish: 'https://joinfirefish.org/ns#', firefish: 'https://joinfirefish.org/ns#',

View file

@ -5,10 +5,9 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { DriveFilesRepository } from '@/models/_.js'; import type { DriveFilesRepository, MiMeta } from '@/models/_.js';
import type { MiRemoteUser } from '@/models/User.js'; import type { MiRemoteUser } from '@/models/User.js';
import type { MiDriveFile } from '@/models/DriveFile.js'; import type { MiDriveFile } from '@/models/DriveFile.js';
import { MetaService } from '@/core/MetaService.js';
import { truncate } from '@/misc/truncate.js'; import { truncate } from '@/misc/truncate.js';
import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js';
import { DriveService } from '@/core/DriveService.js'; import { DriveService } from '@/core/DriveService.js';
@ -25,10 +24,12 @@ export class ApImageService {
private logger: Logger; private logger: Logger;
constructor( constructor(
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.driveFilesRepository) @Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository, private driveFilesRepository: DriveFilesRepository,
private metaService: MetaService,
private apResolverService: ApResolverService, private apResolverService: ApResolverService,
private driveService: DriveService, private driveService: DriveService,
private apLoggerService: ApLoggerService, private apLoggerService: ApLoggerService,
@ -65,12 +66,10 @@ export class ApImageService {
this.logger.info(`Creating the Image: ${image.url}`); this.logger.info(`Creating the Image: ${image.url}`);
const instance = await this.metaService.fetch();
// Cache if remote file cache is on AND either // Cache if remote file cache is on AND either
// 1. remote sensitive file is also on // 1. remote sensitive file is also on
// 2. or the image is not sensitive // 2. or the image is not sensitive
const shouldBeCached = instance.cacheRemoteFiles && (instance.cacheRemoteSensitiveFiles || !image.sensitive); const shouldBeCached = this.meta.cacheRemoteFiles && (this.meta.cacheRemoteSensitiveFiles || !image.sensitive);
await this.federatedInstanceService.fetch(actor.host).then(async i => { await this.federatedInstanceService.fetch(actor.host).then(async i => {
if (i.isNSFW) { if (i.isNSFW) {

View file

@ -6,13 +6,12 @@
import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm'; import { In } from 'typeorm';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { PollsRepository, EmojisRepository, NotesRepository } from '@/models/_.js'; import type { PollsRepository, EmojisRepository, NotesRepository, MiMeta } from '@/models/_.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import type { MiRemoteUser } from '@/models/User.js'; import type { MiRemoteUser } from '@/models/User.js';
import type { MiNote } from '@/models/Note.js'; import type { MiNote } from '@/models/Note.js';
import { toArray, toSingle, unique } from '@/misc/prelude/array.js'; import { toArray, toSingle, unique } from '@/misc/prelude/array.js';
import type { MiEmoji } from '@/models/Emoji.js'; import type { MiEmoji } from '@/models/Emoji.js';
import { MetaService } from '@/core/MetaService.js';
import { AppLockService } from '@/core/AppLockService.js'; import { AppLockService } from '@/core/AppLockService.js';
import type { MiDriveFile } from '@/models/DriveFile.js'; import type { MiDriveFile } from '@/models/DriveFile.js';
import { NoteCreateService } from '@/core/NoteCreateService.js'; import { NoteCreateService } from '@/core/NoteCreateService.js';
@ -47,6 +46,9 @@ export class ApNoteService {
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.pollsRepository) @Inject(DI.pollsRepository)
private pollsRepository: PollsRepository, private pollsRepository: PollsRepository,
@ -69,7 +71,6 @@ export class ApNoteService {
private apMentionService: ApMentionService, private apMentionService: ApMentionService,
private apImageService: ApImageService, private apImageService: ApImageService,
private apQuestionService: ApQuestionService, private apQuestionService: ApQuestionService,
private metaService: MetaService,
private appLockService: AppLockService, private appLockService: AppLockService,
private pollService: PollService, private pollService: PollService,
private noteCreateService: NoteCreateService, private noteCreateService: NoteCreateService,
@ -187,7 +188,7 @@ export class ApNoteService {
/** /**
* *
*/ */
const hasProhibitedWords = await this.noteCreateService.checkProhibitedWordsContain({ cw, text, pollChoices: poll?.choices }); const hasProhibitedWords = this.noteCreateService.checkProhibitedWordsContain({ cw, text, pollChoices: poll?.choices });
if (hasProhibitedWords) { if (hasProhibitedWords) {
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words'); throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
} }
@ -565,9 +566,7 @@ export class ApNoteService {
public async resolveNote(value: string | IObject, options: { sentFrom?: URL, resolver?: Resolver } = {}): Promise<MiNote | null> { public async resolveNote(value: string | IObject, options: { sentFrom?: URL, resolver?: Resolver } = {}): Promise<MiNote | null> {
const uri = getApId(value); const uri = getApId(value);
// ブロックしていたら中断 if (!this.utilityService.isFederationAllowedUri(uri)) {
const meta = await this.metaService.fetch();
if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) {
throw new StatusError('blocked host', 451); throw new StatusError('blocked host', 451);
} }

View file

@ -8,7 +8,7 @@ import promiseLimit from 'promise-limit';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import { ModuleRef } from '@nestjs/core'; import { ModuleRef } from '@nestjs/core';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/_.js'; import type { FollowingsRepository, InstancesRepository, MiMeta, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/_.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import type { MiLocalUser, MiRemoteUser } from '@/models/User.js'; import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
import { MiUser } from '@/models/User.js'; import { MiUser } from '@/models/User.js';
@ -35,7 +35,6 @@ import type { UtilityService } from '@/core/UtilityService.js';
import type { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js'; import { RoleService } from '@/core/RoleService.js';
import { MetaService } from '@/core/MetaService.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import type { AccountMoveService } from '@/core/AccountMoveService.js'; import type { AccountMoveService } from '@/core/AccountMoveService.js';
import { checkHttps } from '@/misc/check-https.js'; import { checkHttps } from '@/misc/check-https.js';
@ -46,7 +45,7 @@ import type { ApNoteService } from './ApNoteService.js';
import type { ApMfmService } from '../ApMfmService.js'; import type { ApMfmService } from '../ApMfmService.js';
import type { ApResolverService, Resolver } from '../ApResolverService.js'; import type { ApResolverService, Resolver } from '../ApResolverService.js';
import type { ApLoggerService } from '../ApLoggerService.js'; import type { ApLoggerService } from '../ApLoggerService.js';
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
import type { ApImageService } from './ApImageService.js'; import type { ApImageService } from './ApImageService.js';
import type { IActor, ICollection, IObject, IOrderedCollection } from '../type.js'; import type { IActor, ICollection, IObject, IOrderedCollection } from '../type.js';
@ -62,7 +61,6 @@ export class ApPersonService implements OnModuleInit {
private driveFileEntityService: DriveFileEntityService; private driveFileEntityService: DriveFileEntityService;
private idService: IdService; private idService: IdService;
private globalEventService: GlobalEventService; private globalEventService: GlobalEventService;
private metaService: MetaService;
private federatedInstanceService: FederatedInstanceService; private federatedInstanceService: FederatedInstanceService;
private fetchInstanceMetadataService: FetchInstanceMetadataService; private fetchInstanceMetadataService: FetchInstanceMetadataService;
private cacheService: CacheService; private cacheService: CacheService;
@ -84,6 +82,9 @@ export class ApPersonService implements OnModuleInit {
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.db) @Inject(DI.db)
private db: DataSource, private db: DataSource,
@ -112,7 +113,6 @@ export class ApPersonService implements OnModuleInit {
this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService'); this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService');
this.idService = this.moduleRef.get('IdService'); this.idService = this.moduleRef.get('IdService');
this.globalEventService = this.moduleRef.get('GlobalEventService'); this.globalEventService = this.moduleRef.get('GlobalEventService');
this.metaService = this.moduleRef.get('MetaService');
this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService'); this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService');
this.fetchInstanceMetadataService = this.moduleRef.get('FetchInstanceMetadataService'); this.fetchInstanceMetadataService = this.moduleRef.get('FetchInstanceMetadataService');
this.cacheService = this.moduleRef.get('CacheService'); this.cacheService = this.moduleRef.get('CacheService');
@ -319,8 +319,8 @@ export class ApPersonService implements OnModuleInit {
this.logger.error('error occurred while fetching following/followers collection', { stack: err }); this.logger.error('error occurred while fetching following/followers collection', { stack: err });
} }
return 'private'; return 'private';
}) }),
) ),
); );
const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/); const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/);
@ -395,6 +395,7 @@ export class ApPersonService implements OnModuleInit {
await transactionalEntityManager.save(new MiUserProfile({ await transactionalEntityManager.save(new MiUserProfile({
userId: user.id, userId: user.id,
description: _description, description: _description,
followedMessage: person._misskey_followedMessage != null ? truncate(person._misskey_followedMessage, 256) : null,
url, url,
fields, fields,
followingVisibility, followingVisibility,
@ -433,10 +434,10 @@ export class ApPersonService implements OnModuleInit {
this.cacheService.uriPersonCache.set(user.uri, user); this.cacheService.uriPersonCache.set(user.uri, user);
// Register host // Register host
this.federatedInstanceService.fetch(host).then(async i => { this.federatedInstanceService.fetch(host).then(i => {
this.instancesRepository.increment({ id: i.id }, 'usersCount', 1); this.instancesRepository.increment({ id: i.id }, 'usersCount', 1);
this.fetchInstanceMetadataService.fetchInstanceMetadata(i); this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) { if (this.meta.enableChartsForFederatedInstances) {
this.instanceChart.newUser(i.host); this.instanceChart.newUser(i.host);
} }
}); });
@ -520,8 +521,8 @@ export class ApPersonService implements OnModuleInit {
return undefined; return undefined;
} }
return 'private'; return 'private';
}) }),
) ),
); );
const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/); const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/);
@ -595,6 +596,7 @@ export class ApPersonService implements OnModuleInit {
url, url,
fields, fields,
description: _description, description: _description,
followedMessage: person._misskey_followedMessage != null ? truncate(person._misskey_followedMessage, 256) : null,
followingVisibility, followingVisibility,
followersVisibility, followersVisibility,
birthday: bday?.[0] ?? null, birthday: bday?.[0] ?? null,

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