diff --git a/.github/workflows/dockle.yml b/.github/workflows/dockle.yml index 3054607913..f006a45ea4 100644 --- a/.github/workflows/dockle.yml +++ b/.github/workflows/dockle.yml @@ -25,7 +25,7 @@ jobs: cp ./compose_example.yml ./compose.yml - run: | docker compose up -d web - docker tag "$(docker compose images web | awk 'OFS=":" {print $4}' | tail -n +2)" misskey-web:latest + docker tag "$(docker compose images --format json web | jq -r '.[] | .ID')" misskey-web:latest - run: | cmd="dockle --exit-code 1 misskey-web:latest ${image_name}" echo "> ${cmd}" diff --git a/CHANGELOG.md b/CHANGELOG.md index a1dc3e2fe9..fab0f01629 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,10 +13,13 @@ - 増加量を抑えるには、最大処理継続時間をデフォルトより短くしてください。 - サーバーの初期設定が完了するまでは連合がオンにならないようになりました - 日本語における公開範囲名称の「ダイレクト」が「指名」に改称されました + - 実際の動作に即した名称になり、馴染みのない人でも理解しやすくなりました + - 他サービスにおける「ダイレクトメッセージ」に相当するMisskeyの機能は「チャット」ですが、「ダイレクト投稿」という名称の機能が存在するとそちらがダイレクトメッセージ機能であるような誤解を生んでいました - mfm.jsをアップデートしました - Enhance: Unicode 15.1 および 16.0 に収録されている絵文字に対応 - Enhance: acctに `.` が入っているユーザーのメンションに対応 - Fix: Unicode絵文字に隣接する異体字セレクタ(`U+FE0F`)が絵文字として認識される問題を修正 +- Enhance: ユーザー検索をロールポリシーで制限できるように ### Client - Feat: AiScriptが1.0に更新されました @@ -30,17 +33,26 @@ - URLに`?safemode=true`を付ける - PWAのショートカットで Safemode を選択して起動する - Feat: ページのタブバーを下部に表示できるように +- Enhance: 「自動でもっと見る」オプションが有効になり、安定性が向上しました - Enhance: コントロールパネルを検索できるように - Enhance: トルコ語 (tr-TR) に対応 -- Enhance: 言語別のスクリプトバンドルを生成するように +- Enhance: 不必要な翻訳データを読み込まなくなり、パフォーマンスが向上しました +- Enhance: 画像エフェクトのパラメータ名の多言語対応 +- Enhance: 依存ソフトウェアの更新 - Fix: 投稿フォームでファイルのアップロードが中止または失敗した際のハンドリングを修正 - Fix: 一部の設定検索結果が存在しないパスになる問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1171) - Fix: テーマエディタが動作しない問題を修正 +- Fix: チャンネルのハイライトページにノートが表示されない問題を修正 +- Fix: カラムの名前が正しくリスト/チャンネルの名前にならない問題を修正 +- Fix: 複数のメンションを1行に記述した場合に、サジェストが正しく表示されない問題を修正 +- Fix: メンションとしての条件を満たしていても、特定の条件(`-`が含まれる場合など)で正しくサジェストされない問題を一部修正 ### Server - Enhance: ノートの削除処理の効率化 - Enhance: 全体的なパフォーマンスの向上 +- Enhance: 依存ソフトウェアの更新 +- Fix: SystemWebhook設定でsecretを空に出来ない問題を修正 ## 2025.7.0 diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index b5b832080f..24a2caea35 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -1599,3 +1599,9 @@ _watermarkEditor: type: "نوع" image: "صور" advanced: "متقدم" +_imageEffector: + _fxProps: + scale: "الحجم" + size: "الحجم" + color: "اللون" + opacity: "الشفافية" diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index 93ee2b1cb5..2b0645b77d 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -1357,3 +1357,10 @@ _watermarkEditor: text: "লেখা" image: "ছবি" advanced: "উন্নত" +_imageEffector: + _fxProps: + scale: "আকার" + size: "আকার" + color: "রং" + opacity: "অস্বচ্ছতা" + lightness: "উজ্জ্বল করুন" diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index 65ac41edeb..2eb8ab33c9 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -1092,6 +1092,7 @@ prohibitedWordsDescription2: "Fent servir espais crearà expressions AND si l'ex hiddenTags: "Etiquetes ocultes" hiddenTagsDescription: "La visibilitat de totes les notes que continguin qualsevol de les paraules configurades seran, automàticament, afegides a \"Inici\". Pots llistar diferents paraules separant les per línies noves." notesSearchNotAvailable: "La cerca de notes no es troba disponible." +usersSearchNotAvailable: "La cerca d'usuaris no està disponible." license: "Llicència" unfavoriteConfirm: "Esborrar dels favorits?" myClips: "Els meus retalls" @@ -1465,6 +1466,7 @@ _settings: contentsUpdateFrequency_description2: "Quan s'activa el mode en temps real, el contingut s'actualitza en temps real, independentment d'aquesta configuració." showUrlPreview: "Mostrar vista prèvia d'URL" showAvailableReactionsFirstInNote: "Mostra les reacciones que pots fer servir al damunt" + showPageTabBarBottom: "Mostrar les pestanyes de les línies de temps a la part inferior" _chat: showSenderName: "Mostrar el nom del remitent" sendOnEnter: "Introdueix per enviar" @@ -1998,6 +2000,7 @@ _role: descriptionOfRateLimitFactor: "Límits baixos són menys restrictius, límits alts són més restrictius." canHideAds: "Pot amagar la publicitat" canSearchNotes: "Pot cercar notes" + canSearchUsers: "Pot cercar usuaris" canUseTranslator: "Pot fer servir el traductor" avatarDecorationLimit: "Nombre màxim de decoracions que es poden aplicar els avatars" canImportAntennas: "Autoritza la importació d'antenes " @@ -3163,10 +3166,10 @@ _watermarkEditor: type: "Tipus" image: "Imatges" advanced: "Avançat" + angle: "Angle" stripe: "Bandes" stripeWidth: "Amplada de la banda" stripeFrequency: "Freqüència de la banda" - angle: "Angle" polkadot: "Lunars" checker: "Escacs" polkadotMainDotOpacity: "Opacitat del lunar principal" @@ -3178,6 +3181,7 @@ _imageEffector: title: "Efecte" addEffect: "Afegeix un efecte" discardChangesConfirm: "Vols descartar els canvis i sortir?" + nothingToConfigure: "No hi ha opcions de configuració disponibles" _fxs: chromaticAberration: "Aberració cromàtica" glitch: "Glitch" @@ -3195,6 +3199,38 @@ _imageEffector: checker: "Escacs" blockNoise: "Bloqueig de soroll" tearing: "Trencament d'imatge " + _fxProps: + angle: "Angle" + scale: "Mida" + size: "Mida" + color: "Color" + opacity: "Opacitat" + normalize: "Normalitzar" + amount: "Quantitat" + lightness: "Brillantor" + contrast: "Contrast" + hue: "Tonalitat" + brightness: "Brillantor" + saturation: "Saturació" + max: "Màxim" + min: "Mínim" + direction: "Direcció " + phase: "Fase" + frequency: "Freqüència " + strength: "Intensitat" + glitchChannelShift: "Canvi de canal " + seed: "Llindar" + redComponent: "Component vermell" + greenComponent: "Component verd" + blueComponent: "Component blau" + threshold: "Llindar" + centerX: "Centre de X" + centerY: "Centre de Y" + zoomLinesSmoothing: "Suavitzat" + zoomLinesSmoothingDescription: "Els paràmetres de suavitzat i amplada de línia en augmentar no es poden fer servir junts." + zoomLinesThreshold: "Amplada de línia a l'augmentar " + zoomLinesMaskSize: "Diàmetre del centre" + zoomLinesBlack: "Obscurir" drafts: "Esborrany " _drafts: select: "Seleccionar esborrany" diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml index 5945ceaf96..21be386e26 100644 --- a/locales/cs-CZ.yml +++ b/locales/cs-CZ.yml @@ -2053,3 +2053,10 @@ _watermarkEditor: type: "Typ" image: "Obrázky" advanced: "Pokročilé" +_imageEffector: + _fxProps: + scale: "Velikost" + size: "Velikost" + color: "Barva" + opacity: "Průhlednost" + lightness: "Zesvětlit" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index e3897613a0..51ae0642aa 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -3147,10 +3147,10 @@ _watermarkEditor: type: "Art" image: "Bilder" advanced: "Fortgeschritten" + angle: "Winkel" stripe: "Streifen" stripeWidth: "Linienbreite" stripeFrequency: "Linienanzahl" - angle: "Winkel" polkadot: "Punktmuster" polkadotMainDotOpacity: "Deckkraft des Hauptpunktes" polkadotMainDotRadius: "Größe des Hauptpunktes" @@ -3173,6 +3173,13 @@ _imageEffector: distort: "Verzerrung" stripe: "Streifen" polkadot: "Punktmuster" + _fxProps: + angle: "Winkel" + scale: "Größe" + size: "Größe" + color: "Farbe" + opacity: "Transparenz" + lightness: "Erhellen" drafts: "Entwurf" _drafts: select: "Entwurf auswählen" diff --git a/locales/en-US.yml b/locales/en-US.yml index f5c0f25ad0..395540ea01 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1092,6 +1092,7 @@ prohibitedWordsDescription2: "Using spaces will create AND expressions and surro hiddenTags: "Hidden hashtags" hiddenTagsDescription: "Select tags which will not shown on trend list.\nMultiple tags could be registered by lines." notesSearchNotAvailable: "Note search is unavailable." +usersSearchNotAvailable: "User search is not available." license: "License" unfavoriteConfirm: "Really remove from favorites?" myClips: "My clips" @@ -1465,6 +1466,7 @@ _settings: contentsUpdateFrequency_description2: "When real-time mode is on, content is updated in real time regardless of this setting." showUrlPreview: "Show URL preview" showAvailableReactionsFirstInNote: "Show available reactions at the top." + showPageTabBarBottom: "Show page tab bar at the bottom" _chat: showSenderName: "Show sender's name" sendOnEnter: "Press Enter to send" @@ -1998,19 +2000,20 @@ _role: descriptionOfRateLimitFactor: "Lower rate limits are less restrictive, higher ones more restrictive. " canHideAds: "Can hide ads" canSearchNotes: "Usage of note search" + canSearchUsers: "User search" canUseTranslator: "Translator usage" - avatarDecorationLimit: "Maximum number of avatar decorations that can be applied" - canImportAntennas: "Allow importing antennas" - canImportBlocking: "Allow importing blocking" - canImportFollowing: "Allow importing following" - canImportMuting: "Allow importing muting" - canImportUserLists: "Allow importing lists" - chatAvailability: "Allow Chat" + avatarDecorationLimit: "Maximum number of avatar decorations" + canImportAntennas: "Can import antennas" + canImportBlocking: "Can import blocking" + canImportFollowing: "Can import following" + canImportMuting: "Can import muting" + canImportUserLists: "Can import lists" + chatAvailability: "Chat" uploadableFileTypes: "Uploadable file types" uploadableFileTypes_caption: "Specifies the allowed MIME/file types. Multiple MIME types can be specified by separating them with a new line, and wildcards can be specified with an asterisk (*). (e.g., image/*)" uploadableFileTypes_caption2: "Some files types might fail to be detected. To allow such files, add {x} to the specification." noteDraftLimit: "Number of possible drafts of server notes" - watermarkAvailable: "Availability of watermark function" + watermarkAvailable: "Watermark function" _condition: roleAssignedTo: "Assigned to manual roles" isLocal: "Local user" @@ -3163,10 +3166,10 @@ _watermarkEditor: type: "Type" image: "Images" advanced: "Advanced" + angle: "Angle" stripe: "Stripes" stripeWidth: "Line width" stripeFrequency: "Lines count" - angle: "Angle" polkadot: "Polkadot" checker: "Checker" polkadotMainDotOpacity: "Opacity of the main dot" @@ -3178,6 +3181,7 @@ _imageEffector: title: "Effects" addEffect: "Add Effects" discardChangesConfirm: "Are you sure you want to leave? You have unsaved changes." + nothingToConfigure: "No configurable options available" _fxs: chromaticAberration: "Chromatic Aberration" glitch: "Glitch" @@ -3195,6 +3199,38 @@ _imageEffector: checker: "Checker" blockNoise: "Block Noise" tearing: "Tearing" + _fxProps: + angle: "Angle" + scale: "Size" + size: "Size" + color: "Color" + opacity: "Opacity" + normalize: "Normalize" + amount: "Amount" + lightness: "Lighten" + contrast: "Contrast" + hue: "Hue" + brightness: "Brightness" + saturation: "Saturation" + max: "Maximum" + min: "Minimum" + direction: "Direction" + phase: "Phase" + frequency: "Frequency" + strength: "Strength" + glitchChannelShift: "Channel shift" + seed: "Seed value" + redComponent: "Red component" + greenComponent: "Green component" + blueComponent: "Blue component" + threshold: "Threshold" + centerX: "Center X" + centerY: "Center Y" + zoomLinesSmoothing: "Smoothing" + zoomLinesSmoothingDescription: "Smoothing and zoom line width cannot be used together." + zoomLinesThreshold: "Zoom line width" + zoomLinesMaskSize: "Center diameter" + zoomLinesBlack: "Make black" drafts: "Drafts" _drafts: select: "Select Draft" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 578c550296..51b377d49f 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -1092,6 +1092,7 @@ prohibitedWordsDescription2: "Si se usan espacios se crearán expresiones AND y hiddenTags: "Hashtags ocultos" hiddenTagsDescription: "Selecciona las etiquetas que no se mostrarán en tendencias. Una etiqueta por línea." notesSearchNotAvailable: "No se puede buscar una nota" +usersSearchNotAvailable: "La búsqueda de usuarios no está disponible." license: "Licencia" unfavoriteConfirm: "¿Desea quitar de favoritos?" myClips: "Mis clips" @@ -1465,6 +1466,7 @@ _settings: contentsUpdateFrequency_description2: "Cuando el modo en tiempo real está activado, el contenido se actualiza en tiempo real independientemente de esta configuración." showUrlPreview: "Mostrar la vista previa de la URL" showAvailableReactionsFirstInNote: "Mostrar las reacciones disponibles en la parte superior." + showPageTabBarBottom: "Mostrar la barra de pestañas de la página en la parte inferior." _chat: showSenderName: "Mostrar el nombre del remitente" sendOnEnter: "Intro para enviar" @@ -1998,6 +2000,7 @@ _role: descriptionOfRateLimitFactor: "Límites más bajos son menos restrictivos, más altos menos restrictivos" canHideAds: "Puede ocultar anuncios" canSearchNotes: "Uso de la búsqueda de notas" + canSearchUsers: "Uso de la búsqueda de usuarios" canUseTranslator: "Uso de traductor" avatarDecorationLimit: "Número máximo de decoraciones de avatar" canImportAntennas: "Permitir la importación de antenas" @@ -3163,10 +3166,10 @@ _watermarkEditor: type: "Tipo" image: "Imágenes" advanced: "Avanzado" + angle: "Ángulo" stripe: "Rayas" stripeWidth: "Anchura de línea" stripeFrequency: "Número de líneas." - angle: "Ángulo" polkadot: "Lunares" checker: "verificador" polkadotMainDotOpacity: "Opacidad del círculo principal" @@ -3178,6 +3181,7 @@ _imageEffector: title: "Efecto" addEffect: "Añadir Efecto" discardChangesConfirm: "¿Ignorar cambios y salir?" + nothingToConfigure: "No hay opciones configurables disponibles." _fxs: chromaticAberration: "Aberración Cromática" glitch: "Glitch" @@ -3195,6 +3199,38 @@ _imageEffector: checker: "Corrector" blockNoise: "Bloquear Ruido" tearing: "Rasgado de Imagen (Tearing)" + _fxProps: + angle: "Ángulo" + scale: "Tamaño" + size: "Tamaño" + color: "Color" + opacity: "Opacidad" + normalize: "Normalización" + amount: "Cantidad" + lightness: "Brillo" + contrast: "Contraste" + hue: "Tonalidad" + brightness: "Brillo" + saturation: "Saturación" + max: "Valor máximo" + min: "Valor mínimo" + direction: "Dirección" + phase: "Fase" + frequency: "Frecuencia" + strength: "Intensidad" + glitchChannelShift: "cambio de canal de imagen" + seed: "Valor de la semilla" + redComponent: "Componente rojo" + greenComponent: "Componente Verde" + blueComponent: "Componente Azul" + threshold: "Umbral" + centerX: "Centrar X" + centerY: "Centrar Y" + zoomLinesSmoothing: "Suavizado" + zoomLinesSmoothingDescription: "El suavizado y el ancho de línea de zoom no se pueden utilizar juntos." + zoomLinesThreshold: "Ancho de línea del zoom" + zoomLinesMaskSize: "Diámetro del centro" + zoomLinesBlack: "Hacer oscuro" drafts: "Borrador" _drafts: select: "Seleccionar borradores" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index d68e7dfde4..f47a350369 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -2372,3 +2372,11 @@ _watermarkEditor: image: "Images" advanced: "Avancé" angle: "Angle" +_imageEffector: + _fxProps: + angle: "Angle" + scale: "Taille" + size: "Taille" + color: "Couleur" + opacity: "Transparence" + lightness: "Clair" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index 009142a4ad..93683508b5 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -2627,3 +2627,11 @@ _watermarkEditor: image: "Gambar" advanced: "Tingkat lanjut" angle: "Sudut" +_imageEffector: + _fxProps: + angle: "Sudut" + scale: "Ukuran" + size: "Ukuran" + color: "Warna" + opacity: "Opasitas" + lightness: "Menerangkan" diff --git a/locales/index.d.ts b/locales/index.d.ts index b0a15e0ad1..028db4043f 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -4386,6 +4386,10 @@ export interface Locale extends ILocale { * ノート検索は利用できません。 */ "notesSearchNotAvailable": string; + /** + * ユーザー検索は利用できません。 + */ + "usersSearchNotAvailable": string; /** * ライセンス */ @@ -7799,6 +7803,10 @@ export interface Locale extends ILocale { * ノート検索の利用 */ "canSearchNotes": string; + /** + * ユーザー検索の利用 + */ + "canSearchUsers": string; /** * 翻訳機能の利用 */ @@ -12203,6 +12211,10 @@ export interface Locale extends ILocale { * 高度 */ "advanced": string; + /** + * 角度 + */ + "angle": string; /** * ストライプ */ @@ -12215,10 +12227,6 @@ export interface Locale extends ILocale { * ラインの数 */ "stripeFrequency": string; - /** - * 角度 - */ - "angle": string; /** * ポルカドット */ @@ -12261,6 +12269,10 @@ export interface Locale extends ILocale { * 変更を破棄して終了しますか? */ "discardChangesConfirm": string; + /** + * 設定項目はありません + */ + "nothingToConfigure": string; "_fxs": { /** * 色収差 @@ -12327,6 +12339,132 @@ export interface Locale extends ILocale { */ "tearing": string; }; + "_fxProps": { + /** + * 角度 + */ + "angle": string; + /** + * サイズ + */ + "scale": string; + /** + * サイズ + */ + "size": string; + /** + * 色 + */ + "color": string; + /** + * 不透明度 + */ + "opacity": string; + /** + * 正規化 + */ + "normalize": string; + /** + * 量 + */ + "amount": string; + /** + * 明るさ + */ + "lightness": string; + /** + * コントラスト + */ + "contrast": string; + /** + * 色相 + */ + "hue": string; + /** + * 輝度 + */ + "brightness": string; + /** + * 彩度 + */ + "saturation": string; + /** + * 最大値 + */ + "max": string; + /** + * 最小値 + */ + "min": string; + /** + * 方向 + */ + "direction": string; + /** + * 位相 + */ + "phase": string; + /** + * 頻度 + */ + "frequency": string; + /** + * 強さ + */ + "strength": string; + /** + * ズレ + */ + "glitchChannelShift": string; + /** + * シード値 + */ + "seed": string; + /** + * 赤色成分 + */ + "redComponent": string; + /** + * 緑色成分 + */ + "greenComponent": string; + /** + * 青色成分 + */ + "blueComponent": string; + /** + * しきい値 + */ + "threshold": string; + /** + * 中心X + */ + "centerX": string; + /** + * 中心Y + */ + "centerY": string; + /** + * スムージング + */ + "zoomLinesSmoothing": string; + /** + * スムージングと集中線の幅の設定は併用できません。 + */ + "zoomLinesSmoothingDescription": string; + /** + * 集中線の幅 + */ + "zoomLinesThreshold": string; + /** + * 中心径 + */ + "zoomLinesMaskSize": string; + /** + * 黒色にする + */ + "zoomLinesBlack": string; + }; }; /** * 下書き diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 53d52dbf7e..81487c4d79 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -1092,6 +1092,7 @@ prohibitedWordsDescription2: "Gli spazi creano la relazione \"E\" tra parole (qu hiddenTags: "Hashtag nascosti" hiddenTagsDescription: "Impedire la visualizzazione del tag impostato nei trend. Puoi impostare più valori, uno per riga." notesSearchNotAvailable: "Non è possibile cercare tra le Note." +usersSearchNotAvailable: "La ricerca profili non è disponibile." license: "Licenza" unfavoriteConfirm: "Vuoi davvero rimuovere la preferenza?" myClips: "Le mie Clip" @@ -1198,7 +1199,7 @@ replies: "Risposte" renotes: "Rinota" loadReplies: "Leggi le risposte" loadConversation: "Leggi la conversazione" -pinnedList: "Elenco in primo piano" +pinnedList: "Lista in primo piano" keepScreenOn: "Mantenere lo schermo acceso" verifiedLink: "Abbiamo confermato la validità di questo collegamento" notifyNotes: "Notifica nuove Note" @@ -1370,6 +1371,10 @@ defaultImageCompressionLevel: "Livello predefinito di compressione immagini" defaultImageCompressionLevel_description: "La compressione diminuisce la qualità dell'immagine, poca compressione mantiene alta qualità delle immagini. Aumentandola, si riducono le dimensioni del file, a discapito della qualità dell'immagine." inMinutes: "min" inDays: "giorni" +safeModeEnabled: "La modalità sicura è attiva" +pluginsAreDisabledBecauseSafeMode: "Tutti i plugin sono disattivati, poiché la modalità sicura è attiva." +customCssIsDisabledBecauseSafeMode: "Il CSS personalizzato non è stato applicato, poiché la modalità sicura è attiva." +themeIsDefaultBecauseSafeMode: "Quando la modalità sicura è attiva, viene utilizzato il tema predefinito. Quando la modalità sicura viene disattivata, il tema torna a essere quello precedente." _order: newest: "Prima i più recenti" oldest: "Meno recenti prima" @@ -1461,6 +1466,7 @@ _settings: contentsUpdateFrequency_description2: "Quando la modalità è in tempo reale, arriveranno a prescindere." showUrlPreview: "Mostra anteprima dell'URL" showAvailableReactionsFirstInNote: "Mostra le reazioni disponibili in alto" + showPageTabBarBottom: "Visualizza le schede della pagina nella parte inferiore" _chat: showSenderName: "Mostra il nome del mittente" sendOnEnter: "Invio spedisce" @@ -1634,6 +1640,10 @@ _serverSettings: fanoutTimelineDbFallback: "Elaborazione dati alternativa" fanoutTimelineDbFallbackDescription: "Attivando l'elaborazione alternativa, verrà interrogato ulteriormente il database se la timeline non è nella cache. \nDisattivando, si può ridurre ulteriormente il carico del server, evitando l'elaborazione alternativa, ma limitando l'intervallo recuperabile delle timeline." reactionsBufferingDescription: "Attivando questa opzione, puoi migliorare significativamente le prestazioni durante la creazione delle reazioni e ridurre il carico sul database. Tuttavia, aumenterà l'impiego di memoria Redis." + remoteNotesCleaning: "Pulizia automatica dei contenuti remoti" + remoteNotesCleaning_description: "Se abilitata, verranno periodicamente rimosse le vecchie Note remote senza relazioni, per ridurre il sovraccarico del sistema." + remoteNotesCleaningMaxProcessingDuration: "Durata massima del processo di pulizia" + remoteNotesCleaningExpiryDaysForEachNotes: "Periodo minimo di conservazione delle note" inquiryUrl: "URL di contatto" inquiryUrlDescription: "Specificare l'URL al modulo di contatto, oppure le informazioni con i dati di contatto dell'amministrazione." openRegistration: "Registrazioni aperte" @@ -1652,6 +1662,8 @@ _serverSettings: userGeneratedContentsVisibilityForVisitor: "Visibilità dei contenuti generati dagli utenti ai non utenti" userGeneratedContentsVisibilityForVisitor_description: "Questa funzionalità è utile per impedire che contenuti remoti inappropriati e difficili da moderare vengano inavvertitamente resi pubblici su Internet tramite il proprio server." userGeneratedContentsVisibilityForVisitor_description2: "Esistono dei rischi nell'esporre incondizionatamente su internet tutto il contenuto del tuo server, incluso il contenuto remoto ricevuto da altri server. In particolare, occorre prestare attenzione, perché le persone non consapevoli della federazione potrebbero erroneamente credere che il contenuto remoto sia stato invece creato all'interno del proprio server." + restartServerSetupWizardConfirm_title: "Vuoi ripetere la procedura guidata di configurazione iniziale del server?" + restartServerSetupWizardConfirm_text: "Verranno ripristinate alcune tue impostazioni personalizzate." _userGeneratedContentsVisibilityForVisitor: all: "Tutto pubblico" localOnly: "Pubblica solo contenuti locali, mantieni privati i contenuti remoti" @@ -1988,6 +2000,7 @@ _role: descriptionOfRateLimitFactor: "I rapporti più bassi sono meno restrittivi, quelli più alti lo sono di più." canHideAds: "Nascondere i banner" canSearchNotes: "Ricercare nelle Note" + canSearchUsers: "Può cercare profili" canUseTranslator: "Tradurre le Note" avatarDecorationLimit: "Numero massimo di decorazioni foto profilo installabili" canImportAntennas: "Può importare Antenne" @@ -3062,6 +3075,7 @@ _bootErrors: otherOption1: "Nelle impostazioni, cancellare le impostazioni del client e svuotare la cache" otherOption2: "Avviare il client predefinito" otherOption3: "Avviare lo strumento di riparazione" + otherOption4: "Avvia Misskey in modalità sicura" _search: searchScopeAll: "Tutte" searchScopeLocal: "Locale" @@ -3098,6 +3112,8 @@ _serverSetupWizard: doYouConnectToFediverse_description1: "Collegandosi a una rete di server distribuiti, denominata Fediverso, potrai scambiare contenuti con altri server, tramite il protocollo di comunicazione ActivityPub." doYouConnectToFediverse_description2: "Connettersi al Fediverso è anche detto \"federazione\"." youCanConfigureMoreFederationSettingsLater: "Puoi svolgere la configurazione avanzata anche dopo. Ad esempio specificando quali server possono federarsi." + remoteContentsCleaning: "Pulizia automatica dei contenuti in arrivo" + remoteContentsCleaning_description: "Con la federazione funzionante, riceverai sempre più contenuti. Abilitando la pulizia automatica, i contenuti non referenziati e obsoleti verranno rimossi automaticamente dai tuoi server, risparmiando spazio di archiviazione." adminInfo: "Informazioni sull'amministratore" adminInfo_description: "Imposta le informazioni dell'amministratore utilizzate per accettare le richieste." adminInfo_mustBeFilled: "Questa operazione è necessaria su un server aperto o se è attiva la federazione." @@ -3150,10 +3166,10 @@ _watermarkEditor: type: "Tipo" image: "Immagini" advanced: "Avanzato" + angle: "Angolo" stripe: "Strisce" stripeWidth: "Larghezza della linea" stripeFrequency: "Il numero di linee" - angle: "Angolo" polkadot: "A pallini" checker: "revisore" polkadotMainDotOpacity: "Opacità del punto principale" @@ -3165,6 +3181,7 @@ _imageEffector: title: "Effetto" addEffect: "Aggiungi effetto" discardChangesConfirm: "Scarta le modifiche ed esci?" + nothingToConfigure: "Nessuna impostazione configurabile." _fxs: chromaticAberration: "Aberrazione cromatica" glitch: "Glitch" @@ -3182,6 +3199,38 @@ _imageEffector: checker: "revisore" blockNoise: "Attenua rumore" tearing: "Strappa immagine" + _fxProps: + angle: "Angolo" + scale: "Dimensioni" + size: "Dimensioni" + color: "Colore" + opacity: "Opacità" + normalize: "Normalizza" + amount: "Quantità" + lightness: "Chiaro" + contrast: "Contrasto" + hue: "Tinta" + brightness: "Luminosità" + saturation: "Saturazione" + max: "Valore massimo" + min: "Valore minimo" + direction: "Orientamento" + phase: "Fasare" + frequency: "Frequenza" + strength: "Forza" + glitchChannelShift: "Glitch cambio canale" + seed: "Seme" + redComponent: "Rosso composito" + greenComponent: "Verde composito" + blueComponent: "Blu composito" + threshold: "Soglia" + centerX: "Centro orizzontale" + centerY: "Centro verticale" + zoomLinesSmoothing: "Levigatura" + zoomLinesSmoothingDescription: "Non si possono usare insieme la levigatura e la larghezza della linea centrale." + zoomLinesThreshold: "Limite delle linee zoom" + zoomLinesMaskSize: "Ampiezza del diametro" + zoomLinesBlack: "Bande nere" drafts: "Bozza" _drafts: select: "Selezionare bozza" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 4562cfe370..7aa88f399d 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1092,6 +1092,7 @@ prohibitedWordsDescription2: "スペースで区切るとAND指定になり、 hiddenTags: "非表示ハッシュタグ" hiddenTagsDescription: "設定したタグをトレンドに表示させないようにします。改行で区切って複数設定できます。" notesSearchNotAvailable: "ノート検索は利用できません。" +usersSearchNotAvailable: "ユーザー検索は利用できません。" license: "ライセンス" unfavoriteConfirm: "お気に入り解除しますか?" myClips: "自分のクリップ" @@ -2020,6 +2021,7 @@ _role: descriptionOfRateLimitFactor: "小さいほど制限が緩和され、大きいほど制限が強化されます。" canHideAds: "広告の非表示" canSearchNotes: "ノート検索の利用" + canSearchUsers: "ユーザー検索の利用" canUseTranslator: "翻訳機能の利用" avatarDecorationLimit: "アイコンデコレーションの最大取付個数" canImportAntennas: "アンテナのインポートを許可" @@ -3267,10 +3269,10 @@ _watermarkEditor: type: "タイプ" image: "画像" advanced: "高度" + angle: "角度" stripe: "ストライプ" stripeWidth: "ラインの幅" stripeFrequency: "ラインの数" - angle: "角度" polkadot: "ポルカドット" checker: "チェッカー" polkadotMainDotOpacity: "メインドットの不透明度" @@ -3283,6 +3285,7 @@ _imageEffector: title: "エフェクト" addEffect: "エフェクトを追加" discardChangesConfirm: "変更を破棄して終了しますか?" + nothingToConfigure: "設定項目はありません" _fxs: chromaticAberration: "色収差" @@ -3302,6 +3305,39 @@ _imageEffector: blockNoise: "ブロックノイズ" tearing: "ティアリング" + _fxProps: + angle: "角度" + scale: "サイズ" + size: "サイズ" + color: "色" + opacity: "不透明度" + normalize: "正規化" + amount: "量" + lightness: "明るさ" + contrast: "コントラスト" + hue: "色相" + brightness: "輝度" + saturation: "彩度" + max: "最大値" + min: "最小値" + direction: "方向" + phase: "位相" + frequency: "頻度" + strength: "強さ" + glitchChannelShift: "ズレ" + seed: "シード値" + redComponent: "赤色成分" + greenComponent: "緑色成分" + blueComponent: "青色成分" + threshold: "しきい値" + centerX: "中心X" + centerY: "中心Y" + zoomLinesSmoothing: "スムージング" + zoomLinesSmoothingDescription: "スムージングと集中線の幅の設定は併用できません。" + zoomLinesThreshold: "集中線の幅" + zoomLinesMaskSize: "中心径" + zoomLinesBlack: "黒色にする" + drafts: "下書き" _drafts: select: "下書きを選択" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index 235f11e197..05113500a3 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -3020,6 +3020,13 @@ _watermarkEditor: angle: "角度" _imageEffector: discardChangesConfirm: "変更をせんで終わるか?" + _fxProps: + angle: "角度" + scale: "大きさ" + size: "大きさ" + color: "色" + opacity: "不透明度" + lightness: "明るさ" _drafts: cannotCreateDraftAnymore: "下書きはこれ以上は作れへんな。" cannotCreateDraft: "この内容で下書きは作れへんな。" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index a06e1231b5..ff9bb32788 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1092,6 +1092,7 @@ prohibitedWordsDescription2: "공백으로 구분하면 AND 지정이 되며, hiddenTags: "숨긴 해시태그" hiddenTagsDescription: "설정한 태그를 트렌드에 표시하지 않도록 합니다. 줄 바꿈으로 하나씩 나눠서 설정할 수 있습니다." notesSearchNotAvailable: "노트 검색을 이용하실 수 없습니다." +usersSearchNotAvailable: "유저 검색을 이용하실 수 없습니다." license: "라이선스" unfavoriteConfirm: "즐겨찾기를 해제하시겠습니까?" myClips: "내 클립" @@ -1465,6 +1466,7 @@ _settings: contentsUpdateFrequency_description2: "실시간 모드가 켜져 있을 때는 이 설정과 상관없이 실시간으로 콘텐츠가 업데이트됩니다." showUrlPreview: "URL 미리보기 표시" showAvailableReactionsFirstInNote: "이용 가능한 리액션을 선두로 표시" + showPageTabBarBottom: "페이지의 탭 바를 아래쪽에 표시" _chat: showSenderName: "발신자 이름 표시" sendOnEnter: "엔터로 보내기" @@ -1998,6 +2000,7 @@ _role: descriptionOfRateLimitFactor: "작을수록 제한이 완화되고, 클수록 제한이 강화됩니다." canHideAds: "광고 숨기기" canSearchNotes: "노트 검색 이용 가능 여부" + canSearchUsers: "유저 검색 이용" canUseTranslator: "번역 기능의 사용" avatarDecorationLimit: "아바타 장식의 최대 붙임 개수" canImportAntennas: "안테나 가져오기 허용" @@ -3163,10 +3166,10 @@ _watermarkEditor: type: "종류" image: "이미지" advanced: "고급" + angle: "각도" stripe: "줄무늬" stripeWidth: "라인의 폭" stripeFrequency: "라인의 수" - angle: "각도" polkadot: "물방울 무늬" checker: "체크 무늬" polkadotMainDotOpacity: "주요 물방울의 불투명도" @@ -3178,6 +3181,7 @@ _imageEffector: title: "이펙트" addEffect: "이펙트를 추가" discardChangesConfirm: "변경을 취소하고 종료하시겠습니까?" + nothingToConfigure: "설정 항목이 없습니다." _fxs: chromaticAberration: "색수차" glitch: "글리치" @@ -3195,6 +3199,38 @@ _imageEffector: checker: "체크 무늬" blockNoise: "노이즈 방지" tearing: "티어링" + _fxProps: + angle: "각도" + scale: "크기" + size: "크기" + color: "색" + opacity: "불투명도" + normalize: "노멀라이즈" + amount: "양" + lightness: "밝음" + contrast: "대비" + hue: "색조" + brightness: "밝기" + saturation: "채도" + max: "최대 값" + min: "최소 값" + direction: "방향" + phase: "위상" + frequency: "빈도" + strength: "강도" + glitchChannelShift: "글리치" + seed: "시드 값" + redComponent: "빨간색 요소" + greenComponent: "녹색 요소" + blueComponent: "파란색 요소" + threshold: "한계 값" + centerX: "X축 중심" + centerY: "Y축 중심" + zoomLinesSmoothing: "다듬기" + zoomLinesSmoothingDescription: "다듬기와 집중선 폭 설정은 같이 쓸 수 없습니다." + zoomLinesThreshold: "집중선 폭" + zoomLinesMaskSize: "중앙 값" + zoomLinesBlack: "검은색으로 하기" drafts: "초안" _drafts: select: "초안 선택" diff --git a/locales/no-NO.yml b/locales/no-NO.yml index a38237208a..1eafd31c4f 100644 --- a/locales/no-NO.yml +++ b/locales/no-NO.yml @@ -742,3 +742,8 @@ _watermarkEditor: text: "Tekst" type: "Type" image: "Bilder" +_imageEffector: + _fxProps: + scale: "Størrelse" + size: "Størrelse" + color: "Farge" diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index 1edd803972..fbd898016e 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -1593,3 +1593,10 @@ _watermarkEditor: type: "Typ" image: "Zdjęcia" advanced: "Zaawansowane" +_imageEffector: + _fxProps: + scale: "Rozmiar" + size: "Rozmiar" + color: "Kolor" + opacity: "Przezroczystość" + lightness: "Rozjaśnij" diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml index 5fdf4f8258..64fec3957e 100644 --- a/locales/pt-PT.yml +++ b/locales/pt-PT.yml @@ -3150,10 +3150,10 @@ _watermarkEditor: type: "Tipo" image: "imagem" advanced: "Avançado" + angle: "Ângulo" stripe: "Listras" stripeWidth: "Largura da linha" stripeFrequency: "Número de linhas" - angle: "Ângulo" polkadot: "Bolinhas" checker: "Xadrez" polkadotMainDotOpacity: "Opacidade da bolinha principal" @@ -3182,6 +3182,13 @@ _imageEffector: checker: "Xadrez" blockNoise: "Bloquear Ruído" tearing: "Descontinuidade" + _fxProps: + angle: "Ângulo" + scale: "Tamanho" + size: "Tamanho" + color: "Cor" + opacity: "Opacidade" + lightness: "Esclarecer" drafts: "Rascunhos" _drafts: select: "Selecionar Rascunho" diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml index f07e4d8d2f..b08341711a 100644 --- a/locales/ro-RO.yml +++ b/locales/ro-RO.yml @@ -1400,3 +1400,7 @@ _watermarkEditor: type: "Tip" image: "Imagini" advanced: "Avansat" +_imageEffector: + _fxProps: + scale: "Dimensiune" + size: "Dimensiune" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index 375b46c3e9..3d3ecca531 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -2257,4 +2257,12 @@ _watermarkEditor: image: "Изображения" advanced: "Для продвинутых" angle: "Угол" +_imageEffector: + _fxProps: + angle: "Угол" + scale: "Размер" + size: "Размер" + color: "Цвет" + opacity: "Непрозрачность" + lightness: "Осветление" drafts: "Черновик" diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index 80a1f2f0a9..c9fd8e6ae9 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -1459,3 +1459,10 @@ _watermarkEditor: type: "Typ" image: "Obrázky" advanced: "Rozšírené" +_imageEffector: + _fxProps: + scale: "Veľkosť" + size: "Veľkosť" + color: "Farba" + opacity: "Priehľadnosť" + lightness: "Zosvetliť" diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml index 95947607cb..96944706e9 100644 --- a/locales/sv-SE.yml +++ b/locales/sv-SE.yml @@ -716,3 +716,8 @@ _search: _watermarkEditor: scale: "Storlek" image: "Bilder" +_imageEffector: + _fxProps: + scale: "Storlek" + size: "Storlek" + color: "Färg" diff --git a/locales/th-TH.yml b/locales/th-TH.yml index 110c089b39..7ed6a3dbdc 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -1092,6 +1092,7 @@ prohibitedWordsDescription2: "ถ้าแยกด้วยเว้นวร hiddenTags: "แฮชแท็กที่ซ่อนอยู่" hiddenTagsDescription: "เลือกแท็กที่จะไม่แสดงในรายการเทรนด์ สามารถลงทะเบียนหลายแท็กได้โดยขึ้นบรรทัดใหม่" notesSearchNotAvailable: "การค้นหาโน้ตไม่พร้อมใช้งาน" +usersSearchNotAvailable: "การค้นหาผู้ใช้ไม่พร้อมใช้งาน" license: "ใบอนุญาต" unfavoriteConfirm: "ลบออกจากรายการโปรดแน่ใจหรอ?" myClips: "คลิปของฉัน" @@ -1370,6 +1371,10 @@ defaultImageCompressionLevel: "ความละเอียดเริ่ม defaultImageCompressionLevel_description: "หากตั้งค่าต่ำ จะรักษาคุณภาพภาพได้ดีขึ้นแต่ขนาดไฟล์จะเพิ่มขึ้นหากตั้งค่าสูง จะลดขนาดไฟล์ได้ แต่คุณภาพภาพจะลดลง" inMinutes: "นาที" inDays: "วัน" +safeModeEnabled: "โหมดปลอดภัยถูกเปิดใช้งาน" +pluginsAreDisabledBecauseSafeMode: "เนื่องจากโหมดปลอดภัยถูกเปิดใช้งาน ปลั๊กอินทั้งหมดจึงถูกปิดใช้งาน" +customCssIsDisabledBecauseSafeMode: "เนื่องจากโหมดปลอดภัยถูกเปิดใช้งาน CSS แบบกำหนดเองจึงไม่ได้ถูกนำมาใช้" +themeIsDefaultBecauseSafeMode: "ในระหว่างที่โหมดปลอดภัยถูกเปิดใช้งาน จะใช้ธีมเริ่มต้น เมื่อปิดโหมดปลอดภัยจะกลับคืนดังเดิม" _order: newest: "เรียงจากใหม่ไปเก่า" oldest: "เรียงจากเก่าไปใหม่" @@ -1461,6 +1466,7 @@ _settings: contentsUpdateFrequency_description2: "เมื่อโหมดเรียลไทม์เปิดอยู่ เนื้อหาจะอัปเดตแบบเรียลไทม์โดยไม่ขึ้นกับการตั้งค่านี้" showUrlPreview: "แสดงตัวอย่าง URL" showAvailableReactionsFirstInNote: "แสดงรีแอคชั่นที่ใช้ได้ไว้หน้าสุด" + showPageTabBarBottom: "แสดงแท็บบาร์ของเพจที่ด้านล่าง" _chat: showSenderName: "แสดงชื่อผู้ส่ง" sendOnEnter: "กด Enter เพื่อส่ง" @@ -1994,6 +2000,7 @@ _role: descriptionOfRateLimitFactor: "ยิ่งตัวเลขน้อยก็ยิ่งจำกัดน้อย ยิ่งมากก็ยิ่งเข้มงวดมากขึ้น" canHideAds: "ซ่อนโฆษณา" canSearchNotes: "การใช้การค้นหาโน้ต" + canSearchUsers: "ค้นหาผู้ใช้" canUseTranslator: "การใช้งานแปล" avatarDecorationLimit: "จำนวนของตกแต่งไอคอนสูงสุดที่สามารถติดตั้งได้" canImportAntennas: "อนุญาตให้นำเข้าเสาอากาศ" @@ -3068,6 +3075,7 @@ _bootErrors: otherOption1: "ลบการตั้งค่าและแคชของไคลเอนต์" otherOption2: "เริ่มใช้งานไคลเอนต์แบบง่าย" otherOption3: "เปิดเครื่องมือซ่อมแซม" + otherOption4: "เริ่มทำงาน Misskey ในโหมดปลอดภัย" _search: searchScopeAll: "ทั้งหมด" searchScopeLocal: "ท้องถิ่น" @@ -3158,10 +3166,10 @@ _watermarkEditor: type: "รูปแบบ" image: "รูปภาพ" advanced: "ขั้นสูง" + angle: "แองเกิล" stripe: "ริ้ว" stripeWidth: "ความกว้างเส้น" stripeFrequency: "จำนวนเส้น" - angle: "แองเกิล" polkadot: "ลายจุด" checker: "ช่องตาราง" polkadotMainDotOpacity: "ความทึบของจุดหลัก" @@ -3173,6 +3181,7 @@ _imageEffector: title: "เอฟเฟกต์" addEffect: "เพิ่มเอฟเฟกต์" discardChangesConfirm: "ต้องการทิ้งการเปลี่ยนแปลงแล้วออกหรือไม่?" + nothingToConfigure: "ไม่มีอะไรให้ตั้งค่า" _fxs: chromaticAberration: "ความคลาดสี" glitch: "กลิตช์" @@ -3190,6 +3199,38 @@ _imageEffector: checker: "ช่องตาราง" blockNoise: "บล็อกที่มีการรบกวน" tearing: "ฉีกขาด" + _fxProps: + angle: "แองเกิล" + scale: "ขนาด" + size: "ขนาด" + color: "สี" + opacity: "ความทึบแสง" + normalize: "นอร์มัลไลซ์" + amount: "จำนวน" + lightness: "สว่าง" + contrast: "คอนทราสต์" + hue: "HUE" + brightness: "ความสว่าง" + saturation: "ความอิ่มตัว" + max: "สูงสุด" + min: "ต่ำสุด" + direction: "ทิศทาง" + phase: "ระยะ" + frequency: "ความถี่" + strength: "ความแรง" + glitchChannelShift: "ความเคลื่อน" + seed: "ซีด" + redComponent: "ส่วนสีแดง" + greenComponent: "ส่วนสีเขียว" + blueComponent: "ส่วนสีน้ำเงิน" + threshold: "เทรชโฮลด์" + centerX: "กลาง X" + centerY: "กลาง Y" + zoomLinesSmoothing: "ทำให้สมูธ" + zoomLinesSmoothingDescription: "ตั้งให้สมูธไม่สามารถใช้ร่วมกับตั้งความกว้างเส้นรวมศูนย์ได้" + zoomLinesThreshold: "ความกว้างเส้นรวมศูนย์" + zoomLinesMaskSize: "ขนาดพื้นที่ตรงกลาง" + zoomLinesBlack: "ทำให้ดำ" drafts: "ร่าง" _drafts: select: "เลือกฉบับร่าง" diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml index fd4aed4a28..cabd80e6e7 100644 --- a/locales/tr-TR.yml +++ b/locales/tr-TR.yml @@ -1,59 +1,59 @@ --- _lang_: "Türkçe" -headlineMisskey: "Notlarla birbirine bağlanan bir ağ" -introMisskey: "Hoş geldiniz! Misskey, açık kaynaklı, merkezi olmayan bir mikroblog hizmetidir.\nDüşüncelerinizi çevrenizdeki herkesle paylaşmak için “notlar” oluşturun. 📡\n“Tepkiler” ile herkesin notları hakkındaki duygularınızı hızlıca ifade edebilirsiniz. 👍\nYeni bir dünyayı keşfedelim! 🚀" +headlineMisskey: "Notlarla birbirine bağlı bir ağ" +introMisskey: "Hoş geldiniz! Misskey, merkezi olmayan bir açık kaynaklı mikroblog platformudur.\n“Notlar” yazarak şu anda neler olduğunu anlatabilir veya olayları başkalarıyla paylaşabilirsiniz. 📡\n“Tepkiler” ile diğer kullanıcıların notları hakkındaki duygularınızı hızlı bir şekilde ifade edebilirsiniz. 👍\nYeni bir dünya sizi bekliyor! 🚀" poweredByMisskeyDescription: "{name}, açık kaynak platformu Misskey (kısaca “Misskey örneği” olarak anılır) tarafından desteklenen hizmetlerden biridir." -monthAndDay: "{month}/{day}" -search: "Arama" +monthAndDay: "{day}.{month}." +search: "Ara" reset: "Sıfırla" notifications: "Bildirimler" username: "Kullanıcı Adı" password: "Şifre" initialPasswordForSetup: "Kurulum için ilk şifre" -initialPasswordIsIncorrect: "Kurulum için ilk parola yanlış" -initialPasswordForSetupDescription: "Misskey'i kendiniz kurduysanız, yapılandırma dosyasına girdiğiniz parolayı kullanın.\nMisskey barındırma hizmeti kullanıyorsanız, verilen parolayı kullanın.\nParola belirlemediyseniz, devam etmek için boş bırakın." +initialPasswordIsIncorrect: "Kurulum için ilk şifre yanlış" +initialPasswordForSetupDescription: "Misskey'i kendiniz kurduysan, yapılandırma dosyasında belirtilen şifreyi kullan.\nMisskey barındırma hizmeti veya benzeri bir hizmet kullanıyorsan, orada belirtilen şifreyi kullan.\nŞifre belirlemediysen, devam etmek için boş bırak." forgotPassword: "Şifremi unuttum" -fetchingAsApObject: "Fediverse'den getiriliyor..." +fetchingAsApObject: "Fediverse'den talep ediliyor..." ok: "Tamam" gotIt: "Anladım!" -cancel: "İptal" -noThankYou: "Şimdi değil" -enterUsername: "Kullanıcı adını girin" -renotedBy: "{user} tarafından renot edildi" +cancel: "Vazgeç" +noThankYou: "Hayır, teşekkürler." +enterUsername: "Kullanıcı adı gir" +renotedBy: "{user} renote etti" noNotes: "Not yok" noNotifications: "Bildirim yok" instance: "Sunucu" -settings: "Ayarlarlar" +settings: "Ayarlar" notificationSettings: "Bildirim Ayarları" basicSettings: "Temel Ayarlar" otherSettings: "Diğer Ayarlar" openInWindow: "Pencerede aç" profile: "Profil" -timeline: "Timeline" +timeline: "Pano" noAccountDescription: "Bu kullanıcı henüz biyografisini yazmamış." login: "Giriş Yap" -loggingIn: "Giriş yapılıyor" +loggingIn: "Giriş Yapılıyor..." logout: "Çıkış Yap" signup: "Kaydol" uploading: "Yükleniyor..." save: "Kaydet" users: "Kullanıcılar" addUser: "Kullanıcı ekle" -favorite: "Favorilere ekle" +favorite: "Favori" favorites: "Favoriler" -unfavorite: "Favorilerden kaldır" -favorited: "Favorilere eklendi." -alreadyFavorited: "Zaten favorilere eklendi" -cantFavorite: "Favorilere ekleyemedim." +unfavorite: "Favoriden kaldır" +favorited: "Favoriye eklendi." +alreadyFavorited: "Zaten favoride" +cantFavorite: "Favoriye eklenemedi" pin: "Profiline sabitle" unpin: "Profilden sabitlemeyi kaldır" copyContent: "İçeriği kopyala" -copyLink: "Linki kopyala" +copyLink: "Link kopyala" copyRemoteLink: "Uzak linki kopyala" copyLinkRenote: "Renote linkini kopyala" delete: "Sil" deleteAndEdit: "Sil ve yeniden düzenle" -deleteAndEditConfirm: "Bu notu silip yeniden düzenlemek ister misiniz? Bu notla ilgili tüm Tepkiler, Yeniden Notlar ve Yanıtlar da silinecektir." +deleteAndEditConfirm: "Bu notu silip yeniden düzenlemek ister misin? Bu notla ilgili tüm Tepki, Renote ve Yanıtlar da silinecek." addToList: "Listeye ekle" addToAntenna: "Anten'e ekle" sendMessage: "Mesaj gönder" @@ -65,7 +65,7 @@ copyFileId: "Dosya ID'yi kopyala" copyFolderId: "Klasör ID'yi kopyala" copyProfileUrl: "Profil URL kopyala" searchUser: "Kullanıcı ara" -searchThisUsersNotes: "Bu kullanıcının notlarını ara" +searchThisUsersNotes: "Kullanıcının notlarını ara" reply: "Yanıtla" loadMore: "Daha fazla yükle" showMore: "Daha fazlasını göster" @@ -81,16 +81,16 @@ import: "İçeri aktar" export: "Dışa aktar" files: "Dosyalar" download: "İndir" -driveFileDeleteConfirm: "“{name}” dosyasını silmek istediğinizden emin misiniz? Bu dosyaya ekli tüm notlar da silinecektir." -unfollowConfirm: "{name}'yi takipten çıkarmak istediğinizden emin misiniz?" -exportRequested: "Dışa aktarma işlemi talep ettiniz. Bu işlem biraz zaman alabilir. İşlem tamamlandığında Drive'ınıza eklenecektir." -importRequested: "İçe aktarma talebinde bulundunuz. Bu işlem biraz zaman alabilir." +driveFileDeleteConfirm: "“{name}” dosyasını silmek istediğinden emin misin? Bu dosyaya ekli tüm notlar da silinecek." +unfollowConfirm: "{name} kullanıcısını cidden takipden çıkmak istiyor musun?" +exportRequested: "Dışa aktarma işlemi talep ettin. Bu işlem biraz zaman alabilir. İşlem tamamlandığında Drive'ına eklenecek." +importRequested: "İçe aktarma talebinde bulundun. Bu işlem biraz zaman alabilir." lists: "Listeler" -noLists: "Hiçbir listeniz yok." +noLists: "Hiç liste yok" note: "Not" notes: "Notlar" -following: "Takip eden" -followers: "Takipçiler" +following: "Takip" +followers: "Takipçi" followsYou: "Sizi takip ediyor" createList: "Liste oluştur" manageLists: "Listeleri yönet" @@ -98,9 +98,9 @@ error: "Hata" somethingHappened: "Bir hata oluştu" retry: "Tekrar dene" pageLoadError: "Sayfa yüklenirken bir hata oluştu." -pageLoadErrorDescription: "Bu durum genellikle ağ hataları veya tarayıcının önbelleği nedeniyle oluşur. Önbelleği temizleyin ve bir süre bekledikten sonra tekrar deneyin." -serverIsDead: "Bu sunucu yanıt vermiyor. Lütfen bir süre bekleyin ve tekrar deneyin." -youShouldUpgradeClient: "Bu sayfayı görüntülemek için lütfen yenileyerek istemcinizi güncelleyin." +pageLoadErrorDescription: "Bu durum genellikle ağ hataları veya tarayıcının önbelleği nedeniyle oluşur. Önbelleği temizleyin ve bir süre bekledikten sonra tekrar dene." +serverIsDead: "Bu sunucu yanıt vermiyor. Lütfen bir süre bekleyin ve tekrar dene." +youShouldUpgradeClient: "Bu sayfayı görüntülemek için lütfen yenileyerek istemcini güncelle." enterListName: "Listeye bir ad girin" privacy: "Gizlilik" makeFollowManuallyApprove: "Takip istekleri onay gerektirir" @@ -110,18 +110,18 @@ followRequest: "Takip isteği gönder" followRequests: "Takip istekleri" unfollow: "Takibi bırak" followRequestPending: "Takip isteği beklemede" -enterEmoji: "Bir emoji girin" +enterEmoji: "Bir emoji gir" renote: "Renote" -unrenote: "Renote'u kaldır" -renoted: "Renote edildi" -renotedToX: "{name} adına kayıtlıdır." -cantRenote: "Bu gönderi renote edilemez." -cantReRenote: "Bir renote yeniden renote edilemez." +unrenote: "Renote geri al" +renoted: "Renote yapıldı." +renotedToX: "{name} adresine Renote" +cantRenote: "Bu not renote edilemez." +cantReRenote: "Renote yeniden Renote edilemez." quote: "Alıntı" inChannelRenote: "Kanal içi renote" inChannelQuote: "Kanal içi alıntı" -renoteToChannel: "Kanala not et" -renoteToOtherChannel: "Diğer kanala not edin\n" +renoteToChannel: "Kanala Renote" +renoteToOtherChannel: "Diğer kanala Renote\n" pinnedNote: "Sabit not" pinned: "Profiline sabitle" you: "Sen" @@ -129,14 +129,14 @@ clickToShow: "Göstermek için tıklayın" sensitive: "Hassas" add: "Ekle" reaction: "Tepki" -reactions: "Tepki" +reactions: "Tepkiler" emojiPicker: "Emoji seçici" pinnedEmojisForReactionSettingDescription: "Tepki verirken sabitlenecek ve görüntülenecek emojileri ayarlayın." pinnedEmojisSettingDescription: "Emoji seçiciyi görüntülerken sabitlenecek ve görüntülenecek emojileri ayarlayın" emojiPickerDisplay: "Emoji seçici ekranı" overwriteFromPinnedEmojisForReaction: "Tepki ayarlarından geçersiz kılma" overwriteFromPinnedEmojis: "Genel ayarlardan geçersiz kılma" -reactionSettingDescription2: "Sıralamayı değiştirmek için sürükleyin, silmek için tıklayın, eklemek için “+” tuşuna basın." +reactionSettingDescription2: "Sıralamayı değiştirmek için sürükle, silmek için tıkla, eklemek için “+” tuşuna bas." rememberNoteVisibility: "Not görünürlük ayarlarını hatırla" attachCancel: "Eki kaldır" deleteFile: "Dosyayı sil" @@ -146,15 +146,15 @@ enterFileName: "Dosya ismini gir" mute: "Gizle" unmute: "sesi aç" renoteMute: "sesi kapat" -renoteUnmute: "sesi açmayı iptal et" +renoteUnmute: "Renote sessiz modunu kaldır" block: "engelle" unblock: "engellemeyi kaldır" suspend: "askıya al" -unsuspend: "askıya alma" -blockConfirm: "Onayı engelle" -unblockConfirm: "engellemeyi kaldır onayla" +unsuspend: "askıya almayı kaldır" +blockConfirm: "Engeli onayla" +unblockConfirm: "Engel kaldırmayı onayla" suspendConfirm: "Hesap askıya alınsın mı?" -unsuspendConfirm: "Hesap askıdan kaldırılsın mı" +unsuspendConfirm: "Hesap askıdan kaldırılsın mı?" selectList: "Bir liste seç" editList: "Listeyi düzenle" selectChannel: "Kanal seç" @@ -172,16 +172,16 @@ emojiUrl: "Emoji URL'si" addEmoji: "Emoji ekle" settingGuide: "Önerilen ayarlar" cacheRemoteFiles: "Uzak dosyalar ön belleğe alınsın" -cacheRemoteFilesDescription: "Bu ayar açık olduğunda diğer sitelerin dosyaları doğrudan uzak sunucudan yüklenecektir. Bu ayarı kapatmak depolama kullanımını azaltacak ama küçük resimler oluşturulmadığından trafiği arttıracaktır." -youCanCleanRemoteFilesCache: "Dosya yönetimi görünümünde 🗑️ düğmesine tıklayarak önbelleği temizleyebilirsiniz." +cacheRemoteFilesDescription: "Bu ayar açık olduğunda diğer sitelerin dosyaları doğrudan uzak sunucudan yüklenece. Bu ayarı kapatmak depolama kullanımını azaltacak ama küçük resimler oluşturulmadığından trafiği arttıracak." +youCanCleanRemoteFilesCache: "Dosya yönetimi görünümünde 🗑️ düğmesine tıklayarak önbelleği temizleyebilirsin." cacheRemoteSensitiveFiles: "Hassas uzak dosyalar ön belleğe alınsın" -cacheRemoteSensitiveFilesDescription: "Bu ayar kapalı olduğunda hassas uzak dosyalar ön belleğe alınmadan doğrudan uzak sunucudan yüklenecektir." +cacheRemoteSensitiveFilesDescription: "Bu ayar kapalı olduğunda hassas uzak dosyalar ön belleğe alınmadan doğrudan uzak sunucudan yüklenecek." flagAsBot: "Bot olarak işaretle" -flagAsBotDescription: "Bu hesap bir program tarafından kontrol ediliyorsa bu seçeneği etkinleştirin. Etkinleştirildiğinde, diğer geliştiriciler için bir işaret görevi görerek diğer botlarla sonsuz etkileşim zincirlerini önleyecek ve Misskey'in iç sistemlerini bu hesabı bir bot olarak ele alacak şekilde ayarlayacaktır." +flagAsBotDescription: "Bu hesap bir program tarafından kontrol ediliyorsa bu seçeneği etkinleştir. Etkinleştirildiğinde, diğer geliştiriciler için bir işaret görevi görerek diğer botlarla sonsuz etkileşim zincirlerini önleyecek ve Misskey'in iç sistemlerini bu hesabı bir bot olarak ele alacak şekilde ayarlayacak." flagAsCat: "Kedi hesabı" flagAsCatDescription: "Kedi hesabı" -flagShowTimelineReplies: "Timeline'da notlara gelen cevapları göster" -flagShowTimelineRepliesDescription: "Açık olduğu durumda, Timeline'da kullanıcıların başkalarına verdiği cevaplar gözükür." +flagShowTimelineReplies: "Pano'da notlara gelen cevapları göster" +flagShowTimelineRepliesDescription: "Açık olduğu durumda, Pano'da kullanıcıların başkalarına verdiği cevaplar gözükür." autoAcceptFollowed: "Takip edilen hesapların takip isteklerini kabul et" addAccount: "Hesap ekle" reloadAccountsList: "Hesap listesini güncelle" @@ -195,14 +195,14 @@ general: "Genel" wallpaper: "Duvar kağıdı" setWallpaper: "Duvar kağıdını ayarla" removeWallpaper: "Duvar kağıdını kaldır" -searchWith: "Arama: {q}" -youHaveNoLists: "Hiçbir listeniz yok." -followConfirm: "{name}'i takip etmek istediğinizden emin misiniz?" +searchWith: "Ara: {q}" +youHaveNoLists: "Hiç listeniz yok." +followConfirm: "{name} kullanıcısını takip etmek istediğinden emin misin?" proxyAccount: "Proxy hesabı" -proxyAccountDescription: "Proxy hesabı, belirli koşullar altında kullanıcılar için uzaktan takipçi görevi gören bir hesaptır. Örneğin, bir kullanıcı listeye uzaktan bir kullanıcı eklediğinde, o kullanıcıyı takip eden yerel kullanıcı yoksa uzaktan kullanıcının etkinliği örneğe iletilmez, bunun yerine proxy hesabı takip eder." +proxyAccountDescription: "Proxy hesabı, belirli koşullar altında kullanıcılar için uzaktan takipçi görevi gören bir hesap. Örneğin, bir kullanıcı listeye uzaktan bir kullanıcı eklediğinde, o kullanıcıyı takip eden yerel kullanıcı yoksa uzaktan kullanıcının etkinliği örneğe iletilmez, bunun yerine proxy hesabı takip eder." host: "Host" selectSelf: "Kendimi seç" -selectUser: "Bir kullanıcı seçin" +selectUser: "Kullanıcı seç" recipient: "Alıcı" annotation: "Yorumlar" federation: "Federasyon" @@ -223,7 +223,7 @@ software: "Yazılım" softwareName: "Yazılım" version: "Sürüm" metadata: "Meta veri" -withNFiles: "{n} dosya(lar)" +withNFiles: "{n} dosya" monitor: "Monitör" jobQueue: "İşlem sırası" cpuAndMemory: "CPU ve Bellek" @@ -232,16 +232,16 @@ disk: "Disk" instanceInfo: "Sunucu Bilgisi" statistics: "İstatistikler" clearQueue: "Kuyruğu temizle" -clearQueueConfirmTitle: "Kuyruğu silmek istediğinizden emin misiniz?" -clearQueueConfirmText: "Kuyrukta kalan teslim edilmemiş notlar birleştirilmeyecektir. Genellikle bu işlem gerekli değildir." -clearCachedFiles: "Clear cache" -clearCachedFilesConfirm: "Tüm önbelleğe alınmış uzak dosyaları silmek istediğinizden emin misiniz?" +clearQueueConfirmTitle: "Kuyruğu silmek istediğinden emin misin?" +clearQueueConfirmText: "Kuyrukta kalan teslim edilmemiş notlar birleştirilmeyecek. Genellikle bu işlem gerekli değildir." +clearCachedFiles: "Önbelleği temizle" +clearCachedFilesConfirm: "Tüm önbelleğe alınmış uzak dosyaları silmek istediğinden emin misin?" blockedInstances: "Engellenen Sunucu" -blockedInstancesDescription: "Engellemek istediğiniz örneklerin ana bilgisayar adlarını satır sonlarıyla ayırarak listeleyin. Listelenen örnekler artık bu örnekle iletişim kuramayacaktır." +blockedInstancesDescription: "Engellemek istediğin sunucuların ana bilgisayar adlarını satır sonlarıyla ayırarak liste. Listelenen örnekler artık bu örnekle iletişim kuramayacaktır." silencedInstances: "Susturulmuş sunucular" -silencedInstancesDescription: "Sessize almak istediğiniz sunucuların ana bilgisayar adlarını yeni bir satırla ayırarak listeleyin. Listelenen sunuculara ait tüm hesaplar sessize alınmış olarak kabul edilecek ve yalnızca takip isteklerinde bulunabilecek, takip edilmedikleri takdirde yerel hesapları etiketleyemeyeceklerdir. Bu, engellenen sunucuları etkilemeyecektir." +silencedInstancesDescription: "Sessize almak istediğin sunucuların ana bilgisayar adlarını yeni bir satırla ayırarak listele. Listelenen sunuculara ait tüm hesaplar sessize alınmış olarak kabul edilecek ve yalnızca takip isteklerinde bulunabilecek, takip edilmedikleri takdirde yerel hesapları etiketleyemeyeceklerdir. Bu, engellenen sunucuları etkilemeyecek." mediaSilencedInstances: "Medya susturulmuş sunucular" -mediaSilencedInstancesDescription: "Medya sessize almak istediğiniz sunucuların ana bilgisayar adlarını yeni bir satırla ayırarak listeleyin. Listelenen sunuculara ait tüm hesaplar hassas hesap olarak değerlendirilecek ve özel emojiler kullanılamayacaktır. Bu durum, engellenen sunucuları etkilemeyecektir." +mediaSilencedInstancesDescription: "Medya sessize almak istediğin sunucuların ana bilgisayar adlarını yeni bir satırla ayırarak liste. Listelenen sunuculara ait tüm hesaplar hassas hesap olarak değerlendirilecek ve özel emojiler kullanılamayacaktır. Bu durum, engellenen sunucuları etkilemeyecek." federationAllowedHosts: "Federasyona izin verilen sunucular" federationAllowedHostsDescription: "Federasyona izin vermek istediğiniz sunucuların ana bilgisayar adlarını satır sonlarıyla ayırın." muteAndBlock: "Sessize Alma ve Engelleme" @@ -249,15 +249,15 @@ mutedUsers: "Sessize alınan kullanıcılar" blockedUsers: "Engellenen kullanıcılar" noUsers: "Kullanıcı yok" editProfile: "Profili düzenle" -noteDeleteConfirm: "Bu notu silmek istediğinizden emin misiniz?" -pinLimitExceeded: "Artık daha fazla not sabitleyemezsiniz" +noteDeleteConfirm: "Bu notu silmek istediğinden emin misin?" +pinLimitExceeded: "Artık daha fazla not sabitleyemezsin" done: "Tamam" -processing: "İşleme..." +processing: "İşleniyor..." preview: "Önizleme" default: "Varsayılan" defaultValueIs: "Varsayılan: {value}" noCustomEmojis: "Emoji yok" -noJobs: "Hiç iş yok" +noJobs: "Hiç ş yok" federating: "Birleştirme" blocked: "Engellenmiş" suspended: "Askıya alınmış" @@ -277,31 +277,31 @@ newPasswordRetype: "Yeni şifreyi tekrar girin" attachFile: "Dosyaları ekle" more: "Daha fazlası!" featured: "Öne çıkan" -usernameOrUserId: "Kullanıcı adı veya ID'si" +usernameOrUserId: "Kullanıcı adı veya ID" noSuchUser: "Kullanıcı bulunamadı" lookup: "Sorgu" announcements: "Duyurular" imageUrl: "Görsel URL" remove: "Sil" removed: "Silindi" -removeAreYouSure: "“{x}” öğesini kaldırmak istediğinizden emin misiniz?" -deleteAreYouSure: "“{x}” öğesini silmek istediğinizden emin misiniz?" -resetAreYouSure: "Gerçekten sıfırlansın mı?" -areYouSure: "Emin misiniz?" +removeAreYouSure: "“{x}” öğesini kaldırmak istediğinizden emin misin?" +deleteAreYouSure: "“{x}” öğesini silmek istediğinizden emin misin?" +resetAreYouSure: "Cidden sıfırlansın mı?" +areYouSure: "Emin misin?" saved: "Kaydedildi" upload: "Yükle" keepOriginalUploading: "Orijinal görüntüyü koru" keepOriginalUploadingDescription: "Orijinal olarak yüklenen görüntüyü olduğu gibi kaydeder. Kapalıysa, yükleme sırasında web'de görüntülenecek bir sürüm oluşturulur." -fromDrive: "Sürücüden" +fromDrive: "Drive'den" fromUrl: "URL'den" -uploadFromUrl: "Bir URL'den yükle" +uploadFromUrl: "URL'den yükle" uploadFromUrlDescription: "Yüklemek istediğiniz dosyanın URL'si" uploadFromUrlRequested: "Yükleme istendi" uploadFromUrlMayTakeTime: "Yükleme işleminin tamamlanması biraz zaman alabilir." uploadNFiles: "{n} dosya yükle" explore: "Keşfet" messageRead: "Oku" -noMoreHistory: "Daha fazla geçmiş bilgisi yoktur." +noMoreHistory: "Daha fazla geçmiş bilgisi yok." startChat: "Sohbete başla" nUsersRead: "{n} tarafından okundu" agreeTo: "{0}'ı kabul ediyorum." @@ -311,47 +311,47 @@ basicNotesBeforeCreateAccount: "Önemli notlar" termsOfService: "Hizmet Şartları" start: "Başla" home: "Ana sayfa" -remoteUserCaution: "Bu kullanıcı uzak bir örnekten geldiği için, gösterilen bilgiler eksik olabilir." +remoteUserCaution: "Bu kullanıcı uzak bir sunucudan geldiği için, gösterilen bilgiler eksik olabilir." activity: "Etkinlik" images: "Görseller" image: "Görsel" birthday: "Doğum günü" yearsOld: "{age} yaşında" -registeredDate: "Katılım tarihi" +registeredDate: "Katılma tarihi" location: "Konum" -theme: "Temalar" +theme: "Tema" themeForLightMode: "Aydınlık Mod'da kullanılacak tema" themeForDarkMode: "Karanlık Mod'da kullanılacak tema" light: "Aydınlık" dark: "Karanlık" lightThemes: "Aydınlık temalar" darkThemes: "Karanlık temalar" -syncDeviceDarkMode: "Karanlık Modu cihaz ayarlarınızla senkronize edin" -switchDarkModeManuallyWhenSyncEnabledConfirm: "\"{x}\" açık. Senkronizasyonu kapatıp modları manuel olarak değiştirmek ister misiniz?" -drive: "Sürücü" +syncDeviceDarkMode: "Karanlık Modu cihaz ayarlarınızla senkronize et" +switchDarkModeManuallyWhenSyncEnabledConfirm: "\"{x}\" açık. Senkronizasyonu kapatıp modları manuel olarak değiştirmek ister misin?" +drive: "Drive" fileName: "Dosya adı" -selectFile: "Bir dosya seçin" +selectFile: "Dosya seçin" selectFiles: "Dosyaları seçin" -selectFolder: "Bir klasör seçin" +selectFolder: "Klasör seçin" selectFolders: "Klasörleri seçin" -fileNotSelected: "Hiçbir dosya seçilmedi" +fileNotSelected: "Hiç dosya seçilmedi" renameFile: "Dosyayı yeniden adlandır" folderName: "Klasör adı" createFolder: "Bir klasör oluşturun" -renameFolder: "Bu klasörü yeniden adlandırın" +renameFolder: "Bu klasörü yeniden adlandır" deleteFolder: "Bu klasörü sil" folder: "Dosya" addFile: "Bir dosya ekle" showFile: "Dosyaları göster" -emptyDrive: "Sürücünüz boş" +emptyDrive: "Drive boş" emptyFolder: "Bu klasör boş" unableToDelete: "Silinemiyor" inputNewFileName: "Yeni bir dosya adı girin" inputNewDescription: "Yeni alternatif metin girin" inputNewFolderName: "Yeni bir klasör adı girin" -circularReferenceFolder: "Hedef klasör, taşımak istediğiniz klasörün bir alt klasörüdür." +circularReferenceFolder: "Hedef klasör, taşımak istediğiniz klasörün bir alt klasörü." hasChildFilesOrFolders: "Bu klasör boş olmadığı için silinemez." -copyUrl: "URL'yi kopyala" +copyUrl: "URL kopyala" rename: "Yeniden adlandır" avatar: "Avatar" banner: "Banner" @@ -360,7 +360,7 @@ whenServerDisconnected: "Sunucu ile bağlantı kesildiğinde" disconnectedFromServer: "Sunucu bağlantısı kesildi" reload: "Yenile" doNothing: "Yoksay" -reloadConfirm: "Zaman çizelgesini yenilemek ister misiniz?" +reloadConfirm: "Zaman çizelgesini yenilemek ister misin?" watch: "İzle" unwatch: "İzlemeyi bırak" accept: "Kabul et" @@ -381,41 +381,41 @@ pages: "Sayfalar" integration: "Entegrasyon" connectService: "Bağlan" disconnectService: "Bağlantıyı kes" -enableLocalTimeline: "Yerel Timeline'ı etkinleştir" -enableGlobalTimeline: "Küresel Timeline'ı etkinleştir" -disablingTimelinesInfo: "Yöneticiler ve Moderatörler, etkinleştirilmemiş olsalar bile her zaman tüm Timeline'a erişebilecekler." +enableLocalTimeline: "Yerel Pano'yu etkinleştir" +enableGlobalTimeline: "Global Pano'yu etkinleştir" +disablingTimelinesInfo: "Yöneticiler ve Moderatörler, etkinleştirilmemiş olsalar bile her zaman tüm Pano'ya erişebilecekler." registration: "Kaydol" invite: "Davet et" -driveCapacityPerLocalAccount: "Yerel kullanıcı başına sürücü kapasitesi" -driveCapacityPerRemoteAccount: "Uzak kullanıcı başına sürücü kapasitesi" +driveCapacityPerLocalAccount: "Yerel kullanıcı başına Drive kapasitesi" +driveCapacityPerRemoteAccount: "Uzak kullanıcı başına Drive kapasitesi" inMb: "Megabayt cinsinden" -bannerUrl: "Banner görseli URL'si" -backgroundImageUrl: "Arka plan görseli URL'si" +bannerUrl: "Banner görseli URL" +backgroundImageUrl: "Arka plan görseli URL" basicInfo: "Temel bilgiler" pinnedUsers: "Sabitlenmiş kullanıcılar" -pinnedUsersDescription: "“Keşfet” sekmesinde sabitlenecek kullanıcı adlarını satır sonlarıyla ayırarak listeleyin." +pinnedUsersDescription: "“Keşfet” sekmesinde sabitlenecek kullanıcı adlarını satır sonlarıyla ayırarak liste." pinnedPages: "Sabitlenmiş Sayfalar" -pinnedPagesDescription: "Bu örneğin üst sayfasına sabitlemek istediğiniz Sayfaların yollarını satır sonlarıyla ayırarak girin." +pinnedPagesDescription: "Bu örneğin üst sayfasına sabitlemek istediğin Sayfaların yollarını satır sonlarıyla ayırarak gir." pinnedClipId: "Sabitlenecek klibin ID" pinnedNotes: "Sabitlenmiş notlar" hcaptcha: "hCaptcha" -enableHcaptcha: "hCaptcha'yı etkinleştir" +enableHcaptcha: "hCaptcha etkinleştir" hcaptchaSiteKey: "Site anahtar" hcaptchaSecretKey: "Gizli anahtar" mcaptcha: "mCaptcha" -enableMcaptcha: "mCaptcha'yı etkinleştir" +enableMcaptcha: "mCaptcha etkinleştir" mcaptchaSiteKey: "Site anahtarı" mcaptchaSecretKey: "Gizli anahtar" mcaptchaInstanceUrl: "mCaptcha sunucu URL'si" recaptcha: "reCAPTCHA" -enableRecaptcha: "reCAPTCHA'yı etkinleştir" +enableRecaptcha: "reCAPTCHA etkinleştir" recaptchaSiteKey: "Site anahtar" recaptchaSecretKey: "Gizli anahtar" turnstile: "Turnstile" -enableTurnstile: "Turnstile'yi etkinleştir" +enableTurnstile: "Turnstile etkinleştir" turnstileSiteKey: "Site anahtar" turnstileSecretKey: "Gizli anahtar" -avoidMultiCaptchaConfirm: "Birden fazla Captcha sistemi kullanmak, aralarında çakışmaya neden olabilir. Şu anda etkin olan diğer Captcha sistemlerini devre dışı bırakmak ister misiniz? Etkin kalmalarını istiyorsanız, iptal düğmesine basın." +avoidMultiCaptchaConfirm: "Birden fazla Captcha sistemi kullanmak, aralarında çakışmaya neden olabilir. Şu anda etkin olan diğer Captcha sistemlerini devre dışı bırakmak ister misiniz? Etkin kalmalarını istiyorsan, iptal düğmesine bas." antennas: "Antenler" manageAntennas: "Antenleri Yönet" name: "İsim" @@ -427,17 +427,17 @@ antennaKeywordsDescription: "VE koşulu için boşluklarla, VEYA koşulu için s notifyAntenna: "Yeni notlar hakkında bildirimde bulunun" withFileAntenna: "Sadece dosyalı notlar" excludeNotesInSensitiveChannel: "Hassas kanallardan gelen notları hariç tutun" -enableServiceworker: "Tarayıcınız için Push Bildirimlerini Etkinleştirin" -antennaUsersDescription: "Satır başına bir kullanıcı adı listeleyin" +enableServiceworker: "Tarayıcınız için Push Bildirimlerini Etkinleştir" +antennaUsersDescription: "Satır başına bir kullanıcı adı listele" caseSensitive: "Harfe duyarlı" withReplies: "Yanıtları ekle" connectedTo: "Aşağıdaki hesap(lar) bağlı" notesAndReplies: "Notlar ve yanıtlar" withFiles: "Dosyalar dahil" silence: "Sessize al" -silenceConfirm: "Bu kullanıcıyı susturmak istediğinizden emin misiniz?" +silenceConfirm: "Bu kullanıcıyı susturmak istediğinden emin misin?" unsilence: "Sessize almayı geri al" -unsilenceConfirm: "Bu kullanıcının sessize alınmasını geri almak istediğinizden emin misiniz?" +unsilenceConfirm: "Bu kullanıcının sessize alınmasını geri almak istediğinden emin misin?" popularUsers: "Popüler kullanıcılar" recentlyUpdatedUsers: "Son zamanlarda aktif olan kullanıcılar" recentlyRegisteredUsers: "Yeni katılan kullanıcılar" @@ -457,10 +457,10 @@ totpDescription: "Tek seferlik şifreleri girmek için bir kimlik doğrulama uyg moderator: "Moderatör" moderation: "Moderasyon" moderationNote: "Moderasyon notu" -moderationNoteDescription: "Moderatörler arasında paylaşılacak notları girebilirsiniz." +moderationNoteDescription: "Moderatörler arasında paylaşılacak notları girebilirsin." addModerationNote: "Moderasyon notu ekle" moderationLogs: "Moderasyon günlükleri" -nUsersMentioned: "{n} kullanıcı tarafından bahsedildi" +nUsersMentioned: "{n} kullanıcı bahsetti" securityKeyAndPasskey: "Güvenlik ve geçiş anahtarları" securityKey: "Güvenlik anahtarı" lastUsed: "Son kullanılan" @@ -489,19 +489,19 @@ text: "Metin" enable: "Etkin" next: "Sonraki" retype: "Tekrar girin" -noteOf: "{user} tarafından not" +noteOf: "{user} not'u" quoteAttached: "Alıntı" quoteQuestion: "Alıntı olarak ekle?" -attachAsFileQuestion: "Panodaki metin uzun. Metin dosyası olarak eklemek ister misiniz?" -onlyOneFileCanBeAttached: "Bir mesaja yalnızca bir dosya ekleyebilirsiniz." +attachAsFileQuestion: "Panodaki metin uzun. Metin dosyası olarak eklemek ister misin?" +onlyOneFileCanBeAttached: "Bir mesaja yalnızca bir dosya ekleyebilirsin." signinRequired: "Devam etmeden önce lütfen kayıt olun veya giriş yapın." signinOrContinueOnRemote: "Devam etmek için sunucunuzu taşıyın veya bu sunucuya kaydolun / giriş yapın." invitations: "Davetler" invitationCode: "Davet kodu" checking: "Kontrol ediliyor..." -available: "Mevcut" -unavailable: "Mevcut değil" -usernameInvalidFormat: "Büyük ve küçük harfler, rakamlar ve alt çizgi kullanabilirsiniz. (a~z、A~Z、0~9)" +available: "Kullanılabilir" +unavailable: "Kullanılamaz" +usernameInvalidFormat: "Büyük ve küçük harfler, rakamlar ve alt çizgi kullanabilirsin. (a~z、A~Z、0~9)" tooShort: "Çok kısa" tooLong: "Çok uzun" weakPassword: "Zayıf şifre" @@ -537,13 +537,13 @@ regenerate: "Yeniden oluştur" fontSize: "Yazı tipi boyutu" mediaListWithOneImageAppearance: "Tek bir resim içeren medya listelerinin yüksekliği" limitTo: "{x} ile sınırlandır" -noFollowRequests: "Bekleyen takip istekleriniz yok." +noFollowRequests: "Bekleyen takip istekleri yok." openImageInNewTab: "Görüntüleri yeni sekmede aç" dashboard: "Gösterge paneli" local: "Yerel" -remote: "Uzaktan" +remote: "Uzak" total: "Toplam" -weekOverWeekChanges: "Geçen haftadan bu yana yapılan değişiklikler" +weekOverWeekChanges: "Geçen haftadan beri yapılan değişiklikler" dayOverDayChanges: "Dünkü değişiklikler" appearance: "Görünüm" clientSettings: "İstemci Ayarları" @@ -552,31 +552,31 @@ promotion: "Tanıtım" promote: "Tanıtıldı" numberOfDays: "Gün sayısı" hideThisNote: "Bu notu gizle" -showFeaturedNotesInTimeline: "Timeline'da öne çıkan notları göster" +showFeaturedNotesInTimeline: "Pano'da öne çıkan notları göster" objectStorage: "Nesne Depolama" useObjectStorage: "Nesne depolamayı kullanın" objectStorageBaseUrl: "Temel URL" objectStorageBaseUrlDesc: "Referans olarak kullanılan URL. CDN veya Proxy kullanıyorsanız, bunların URL'sini belirtin.\nS3 için ‘https://.s3.amazonaws.com’ ve GCS veya eşdeğer hizmetler için ‘https://storage.googleapis.com/’ vb. kullanın." objectStorageBucket: "Kova" objectStorageBucketDesc: "Lütfen sağlayıcınızda kullanılan kova adını belirtin." -objectStoragePrefix: "Önek" +objectStoragePrefix: "Ön ek" objectStoragePrefixDesc: "Dosyalar bu öneke sahip dizinler altında saklanacaktır." objectStorageEndpoint: "Uç nokta" objectStorageEndpointDesc: "AWS S3 kullanıyorsanız bu alanı boş bırakın, aksi takdirde kullandığınız hizmete bağlı olarak uç noktayı ‘’ veya ‘:’ olarak belirtin." objectStorageRegion: "Bölge" -objectStorageRegionDesc: "'xx-east-1' gibi bir bölge belirtin. Hizmetiniz bölgeler arasında ayrım yapmıyorsa, ‘us-east-1’ girin. AWS yapılandırma dosyalarını veya ortam değişkenlerini kullanıyorsanız boş bırakın." +objectStorageRegionDesc: "'xx-east-1' gibi bir bölge belirt. Hizmetin bölgeler arasında ayrım yapmıyorsa, ‘us-east-1’ girin. AWS yapılandırma dosyalarını veya ortam değişkenlerini kullanıyorsan boş bırak." objectStorageUseSSL: "SSL kullanın" objectStorageUseSSLDesc: "API bağlantıları için HTTPS kullanmayacaksanız bunu kapatın." objectStorageUseProxy: "Proxy üzerinden bağlan" objectStorageUseProxyDesc: "API bağlantıları için Proxy kullanmayacaksanız bunu kapatın." objectStorageSetPublicRead: "Yükleme sırasında \"genel-okuma\" ayarını yapın" -s3ForcePathStyleDesc: "s3ForcePathStyle etkinleştirilirse, kova adı URL'nin ana bilgisayar adı yerine URL yoluna eklenmelidir. Kendi kendine barındırılan bir Minio örneği gibi hizmetleri kullanırken bu ayarı etkinleştirmeniz gerekebilir." +s3ForcePathStyleDesc: "s3ForcePathStyle etkinleştirilirse, kova adı URL'nin ana bilgisayar adı yerine URL yoluna eklenmelidir. Kendi kendine barındırılan bir Minio örneği gibi hizmetleri kullanırken bu ayarı etkinleştirmen gerekebilir." serverLogs: "Sunucu log kayıtları" deleteAll: "Tümünü sil" showFixedPostForm: "Gönderi formunu zaman çizelgesinin en üstünde görüntüle" showFixedPostFormInChannel: "Gönderi formunu zaman çizelgesinin en üstünde görüntüle (Kanallar)" withRepliesByDefaultForNewlyFollowed: "Yeni takip edilen kullanıcıların yanıtlarını varsayılan olarak zaman çizelgesine dahil et" -newNoteRecived: "Yeni notlar var" +newNoteRecived: "Yeni Not'lar var" newNote: "Yeni Not" sounds: "Sesler" sound: "Ses" @@ -602,41 +602,41 @@ installedDate: "Yetkili" lastUsedDate: "En son kullanıldığı tarih" state: "Durum" sort: "Sıralama düzeni" -ascendingOrder: "Yükselme" -descendingOrder: "Alçalma" +ascendingOrder: "Artan" +descendingOrder: "Azalan" scratchpad: "Not defteri" -scratchpadDescription: "Scratchpad, AiScript deneyleri için bir ortam sağlar. Misskey ile etkileşim halindeyken yazabilir, çalıştırabilir ve sonuçlarını kontrol edebilirsiniz." +scratchpadDescription: "Scratchpad, AiScript deneyleri için bir ortam sağlar. Misskey ile etkileşim halindeyken yazabilir, çalıştırabilir ve sonuçlarını kontrol edebilirsin." uiInspector: "UI denetçisi" -uiInspectorDescription: "Bellekteki UI bileşeni sunucu listesini görebilirsiniz. UI bileşeni, Ui:C: işlevi tarafından oluşturulacaktır." +uiInspectorDescription: "Bellekteki UI bileşeni sunucu listesini görebilirsin. UI bileşeni, Ui:C: işlevi tarafından oluşturulacak." output: "Çıktı" script: "Script" disablePagesScript: "Sayfalarda AiScript'i devre dışı bırak" updateRemoteUser: "Uzak kullanıcı bilgilerini güncelle" unsetUserAvatar: "Avatar'ı kaldır" -unsetUserAvatarConfirm: "Avatarı silmek istediğinizden emin misiniz?" +unsetUserAvatarConfirm: "Avatarı silmek istediğinden emin misin?" unsetUserBanner: "Banner'ı kaldır" -unsetUserBannerConfirm: "Banner'ı kaldırmak istediğinizden emin misiniz?" +unsetUserBannerConfirm: "Banner'ı kaldırmak istediğinden emin misin?" deleteAllFiles: "Tüm dosyaları sil" -deleteAllFilesConfirm: "Tüm dosyaları silmek istediğinizden emin misiniz?" -removeAllFollowing: "Takip ettiğiniz tüm kullanıcıları takipten çıkarın" -removeAllFollowingDescription: "Bu komutu çalıştırmak, {host} adresindeki tüm hesapları takipten çıkarır. Örneğin, örnek artık mevcut değilse bu komutu çalıştırın." +deleteAllFilesConfirm: "Tüm dosyaları silmek istediğinden emin misin?" +removeAllFollowing: "Takip ettiğin tüm kullanıcıları takipten çıkar" +removeAllFollowingDescription: "Bu komutu çalıştırmak, {host} adresindeki tüm hesapları takipten çıkarır. Örneğin, sunucu artık mevcut değilse bu komutu çalıştırın." userSuspended: "Bu kullanıcı askıya alınmıştır." userSilenced: "Bu kullanıcı susturuluyor." yourAccountSuspendedTitle: "Bu hesap askıya alınmıştır." -yourAccountSuspendedDescription: "Bu hesap, sunucunun hizmet şartlarını veya benzerlerini ihlal ettiği için askıya alınmıştır. Daha ayrıntılı bir neden öğrenmek isterseniz yöneticiyle iletişime geçin. Lütfen yeni bir hesap oluşturmayın." +yourAccountSuspendedDescription: "Bu hesap, sunucunun hizmet şartlarını veya benzerlerini ihlal ettiği için askıya alınmıştır. Daha ayrıntılı bir neden öğrenmek istersen yöneticiyle iletişime geç. Lütfen yeni bir hesap oluşturma." tokenRevoked: "Geçersiz jeton" tokenRevokedDescription: "Bu jetonun süresi doldu. Lütfen tekrar giriş yapın." accountDeleted: "Hesap silindi" -accountDeletedDescription: "Bu hesap silinmiştir." +accountDeletedDescription: "Bu hesap silinmiş." menu: "Menü" divider: "Bölücü" addItem: "Öğe Ekle" rearrange: "Yeniden düzenle" relays: "Röleler" addRelay: "Röle ekle" -inboxUrl: "Gelen Kutusu URL'si" +inboxUrl: "Gelen Kutusu URL" addedRelays: "Eklenen Röleler" -serviceworkerInfo: "Push bildirimleri için etkinleştirilmelidir." +serviceworkerInfo: "Push bildirimleri için etkinleştirilmeli." deletedNote: "Silinen not" invisibleNote: "Görünmez not" enableInfiniteScroll: "Otomatik olarak daha fazlasını yükle" @@ -670,12 +670,12 @@ adminPermission: "Yönetici İzinleri" enableAll: "Tümünü etkinleştir" disableAll: "Tümünü devre dışı bırak" tokenRequested: "Hesaba erişim izni ver" -pluginTokenRequestedDescription: "Bu eklenti, burada ayarlanan izinleri kullanabilecektir." +pluginTokenRequestedDescription: "Bu eklenti, burada ayarlanan izinleri kullanabilecek." notificationType: "Bildirim türü" edit: "Düzenle" emailServer: "E-posta sunucusu" enableEmail: "E-posta dağıtımını etkinleştir" -emailConfigInfo: "Kayıt sırasında veya şifrenizi unuttuğunuzda E-postanızı doğrulamak için kullanılır." +emailConfigInfo: "Kayıt sırasında veya şifreni unuttuğunda E-postanı doğrulamak için kullanılır." email: "E-Posta" emailAddress: "E-Posta adresi" smtpConfig: "SMTP Sunucu yapılandırması" @@ -691,7 +691,7 @@ wordMute: "Kelime sustur" wordMuteDescription: "Belirtilen kelime veya kelime öbeğini içeren notları küçültün. Küçültülmüş notlar, üzerlerine tıklanarak görüntülenebilir." hardWordMute: "Zorla kelime sustur" showMutedWord: "Sessize alınan kelimeleri göster" -hardWordMuteDescription: "Belirtilen kelime veya kelime öbeğini içeren notları gizleyin. Kelime sessize alma özelliğinden farklı olarak, not tamamen görünmez hale gelir." +hardWordMuteDescription: "Belirtilen kelime veya kelime öbeğini içeren notları gizle. Kelime sessize alma özelliğinden farklı olarak, not tamamen görünmez hale gelir." regexpError: "Düzenli ifade hatası" regexpErrorDescription: "{tab} kelimesinin {line} satırındaki düzenli ifadede bir hata oluştu:" instanceMute: "Sunucu Sessizleştirme" @@ -724,7 +724,7 @@ abuseReports: "Raporlar" reportAbuse: "Rapor" reportAbuseRenote: "Raporu yeniden gönder" reportAbuseOf: "{name} raporu" -fillAbuseReportDescription: "Bu raporla ilgili ayrıntıları lütfen doldurun. Belirli bir notla ilgiliyse, lütfen URL'sini de ekleyin." +fillAbuseReportDescription: "Bu raporla ilgili ayrıntıları lütfen doldur. Belirli bir notla ilgiliyse, lütfen URL'sini de ekle." abuseReported: "Raporunuz gönderildi. Çok teşekkür ederiz." reporter: "Raporlayan" reporteeOrigin: "Bildirim Kaynağı" @@ -745,27 +745,27 @@ createNew: "Yeni oluştur" optional: "Opsiyonel" createNewClip: "Klip oluştur" unclip: "Klip kaldır" -confirmToUnclipAlreadyClippedNote: "Bu not zaten “{name}” klibinin bir parçasıdır. Bu klipten silmek ister misiniz?" -public: "Halka açık" +confirmToUnclipAlreadyClippedNote: "Bu not zaten “{name}” klibinin bir parçası. Bu klipten silmek ister misin?" +public: "Herkese açık" private: "Özel" -i18nInfo: "Misskey, gönüllüler tarafından çeşitli dillere çevrilmektedir. {link} adresinden yardımcı olabilirsiniz." -manageAccessTokens: "Manage access tokens" -accountInfo: "Erişim belirteçlerini yönetme" +i18nInfo: "Misskey, gönüllüler tarafından çeşitli dillere çevrilmektedir. {link} adresinden yardımcı olabilirsin." +manageAccessTokens: "Acces Tokens yönet" +accountInfo: "Hesap bilgileri" notesCount: "Not sayısı" -repliesCount: "Gönderilen yanıt sayısı" -renotesCount: "Gönderilen renote sayısı" +repliesCount: "Yanıt sayısı" +renotesCount: "Renote sayısı" repliedCount: "Alınan yanıt sayısı" -renotedCount: "Alınan renot sayısı" -followingCount: "Takip edilen hesap sayısı" +renotedCount: "Alınan Renote sayısı" +followingCount: "Takip sayısı" followersCount: "Takipçi sayısı" -sentReactionsCount: "Gönderilen tepki sayısı" +sentReactionsCount: "Tepki sayısı" receivedReactionsCount: "Alınan tepki sayısı" -pollVotesCount: "Gönderilen anket oylarının sayısı" -pollVotedCount: "Alınan anket oylarının sayısı" +pollVotesCount: "Anket oy sayısı" +pollVotedCount: "Alınan anket oy sayısı" yes: "Evet" no: "Hayır" -driveFilesCount: "Sürücü dosya sayısı" -driveUsage: "Sürücü alanı kullanımı" +driveFilesCount: "Drive dosya sayısı" +driveUsage: "Drive alanı kullanımı" noCrawle: "Tarayıcı indekslemesini reddet" noCrawleDescription: "Arama motorlarından profilinde, notlarında, sayfalarında vb. dolaşılmamasını ve dizine eklememesini talep et." lockedAccountInfo: "Notunuzun görünürlüğünü “Yalnızca takipçiler” olarak ayarlamadığınız sürece, takipçilerin manuel olarak onaylanmasını gerektirse bile notlarınız herkes tarafından görülebilir." @@ -775,84 +775,84 @@ disableShowingAnimatedImages: "Animasyonlu görüntüleri oynatmayın" highlightSensitiveMedia: "Hassas medyayı vurgulayın" verificationEmailSent: "Doğrulama e-postası gönderildi. Doğrulamayı tamamlamak için e-postadaki bağlantıyı takip edin." notSet: "Ayarlı değil" -emailVerified: "E-posta adresi doğrulanmıştır." -noteFavoritesCount: "Favori notların sayısı" -pageLikesCount: "Beğenilen Sayfa Sayısı" -pageLikedCount: "Alınan sayfa beğenileri sayısı" -contact: "Alınan Sayfa beğenileri sayısı" +emailVerified: "E-posta adresi doğrulandı." +noteFavoritesCount: "Favori not sayısı" +pageLikesCount: "Beğenilen sayfa sayısı" +pageLikedCount: "Alınan sayfa beğen sayısı" +contact: "İletişim" useSystemFont: "Sistemin varsayılan yazı tipini kullanın" clips: "Klipler" experimentalFeatures: "Deneysel özellikler" experimental: "Deneysel" -thisIsExperimentalFeature: "Bu deneysel bir özelliktir. İşlevselliği değişebilir ve amaçlandığı gibi çalışmayabilir." +thisIsExperimentalFeature: "Bu deneysel bir özellik. İşlevselliği değişebilir ve amaçlandığı gibi çalışmayabilir." developer: "Geliştirici" -makeExplorable: "Hesabı “Keşfet” bölümünde görünür hale getirin" +makeExplorable: "Hesabı “Keşfet” bölümünde görünür hale getir" makeExplorableDescription: "Bunu kapatırsanız, hesabınız “Keşfet” bölümünde görünmez." duplicate: "Çoğalt" left: "Sol" center: "Merkez" wide: "Geniş" narrow: "Dar" -reloadToApplySetting: "Bu ayar, sayfa yeniden yüklendikten sonra geçerli olacaktır. Şimdi yeniden yüklemek ister misiniz?" +reloadToApplySetting: "Bu ayar, sayfa yeniden yüklendikten sonra geçerli olacaktır. Şimdi yeniden yüklemek ister misin?" needReloadToApply: "Bunun yansıtılması için yeniden yükleme yapılması gerekir." needToRestartServerToApply: "Değişikliğin yansıtılması için Misskey'in yeniden başlatılması gerekir." showTitlebar: "Başlık çubuğunu göster" -clearCache: "Clear cache" +clearCache: "Önbellek temizle" onlineUsersCount: "{n} kullanıcı çevrim içi" nUsers: "{n} Kullanıcı" nNotes: "{n} Not" sendErrorReports: "Hata raporları gönder" -sendErrorReportsDescription: "Etkinleştirildiğinde, bir sorun oluştuğunda ayrıntılı hata bilgileri Misskey ile paylaşılacak ve bu da Misskey'in kalitesinin iyileştirilmesine yardımcı olacaktır.\nBu bilgiler arasında işletim sisteminizin sürümü, kullandığınız tarayıcı, Misskey'deki faaliyetleriniz vb. yer alacaktır." +sendErrorReportsDescription: "Etkinleştirildiğinde, bir sorun oluştuğunda ayrıntılı hata bilgileri Misskey ile paylaşılacak ve bu da Misskey'in kalitesinin iyileştirilmesine yardımcı olacak.\nBu bilgiler arasında işletim sisteminizin sürümü, kullandığınız tarayıcı, Misskey'deki faaliyetlerin vb. yer alacaktır." myTheme: "Benim temam" backgroundColor: "Arka plan rengi" accentColor: "Vurgu rengi" textColor: "Metin rengi" -saveAs: "Farklı kaydet..." +saveAs: "Farklı kaydet" advanced: "Gelişmiş" advancedSettings: "Gelişmiş ayarlar" value: "Değer" createdAt: "Oluşturuldu" updatedAt: "Güncellendi" -saveConfirm: "Değişiklikleri kaydetmek ister misiniz?" -deleteConfirm: "Gerçekten silmek istiyor musunuz?" +saveConfirm: "Değişiklikleri kaydetmek ister misin?" +deleteConfirm: "Cidden silmek istiyor musunuz?" invalidValue: "Geçersiz değer." registry: "Kayıt Defteri" closeAccount: "Hesabı kapat" currentVersion: "Şu anki sürüm" latestVersion: "En yeni sürüm" -youAreRunningUpToDateClient: "Müşteri yazılımınızın en yeni sürümünü kullanıyorsunuz." +youAreRunningUpToDateClient: "İstemci yazılımınızın en yeni sürümünü kullanıyorsunuz." newVersionOfClientAvailable: "İstemcinin daha yeni bir sürümü var." usageAmount: "Kullanım" capacity: "Kapasite" -inUse: "Kullanılmış" +inUse: "Kullanılıyor" editCode: "Kodu düzenle" apply: "Uygula" receiveAnnouncementFromInstance: "Bu sunucudan bildirimler alın" -emailNotification: "E-posta bildirimleri" +emailNotification: "E-posta bildirimi" publish: "Yayınla" inChannelSearch: "Kanalda ara" useReactionPickerForContextMenu: "Sağ tıklama ile tepki seçiciyi aç" typingUsers: "{users} yazıyor..." jumpToSpecifiedDate: "Belirli bir tarihe atla" -showingPastTimeline: "Şu anda eski bir Timeline görüntüleniyor." -clear: "Geri dön" +showingPastTimeline: "Şu anda eski bir Pano görüntüleniyor." +clear: "Temizle" markAllAsRead: "Tümünü okundu olarak işaretle" goBack: "Geri" -unlikeConfirm: "Gerçekten beğenini kaldırmak mı istiyorsun?" +unlikeConfirm: "Cidden beğenini kaldırmak mı istiyorsun?" fullView: "Tam görünüm" quitFullView: "Tam ekranı kapat" addDescription: "Açıklama ekle" -userPagePinTip: "Bireysel notların menüsünden “Profiline sabitle” seçeneğini seçerek notları burada görüntüleyebilirsiniz." +userPagePinTip: "Bireysel notların menüsünden “Profiline sabitle” seçeneğini seçerek notları burada görüntüleyebilirsin." notSpecifiedMentionWarning: "Bu notta, alıcılar arasında yer almayan kullanıcılar hakkında bilgiler bulunmaktadır." info: "Hakkında" -userInfo: "Kullanıcı bilgileri" +userInfo: "Kullanıcı hakkında" unknown: "Bilinmiyor" onlineStatus: "Çevrimiçi durumu" hideOnlineStatus: "Çevrimiçi durumunu gizle" hideOnlineStatusDescription: "Çevrimiçi durumunuzu gizlemek, arama gibi bazı özelliklerin kullanışlılığını azaltır." -online: "Çevrimiçi" +online: "Online" active: "Aktif" -offline: "Çevrimdışı" +offline: "Offline" notRecommended: "Tavsiye edilmez" botProtection: "Bot Koruması" instanceBlocking: "Blocked/Silenced Instances" @@ -888,7 +888,7 @@ ratio: "Oran" previewNoteText: "Önizlemeyi göster" customCss: "Özel CSS" customCssWarn: "Bu ayar, yalnızca ne işe yaradığını biliyorsanız kullanılmalıdır. Yanlış değerler girilmesi, istemcinin normal şekilde çalışmamasına neden olabilir." -global: "Küresel" +global: "Global" squareAvatars: "Kare avatarlar" sent: "Gönderilen" received: "Alınan" @@ -902,7 +902,7 @@ whatIsNew: "Değişiklikleri göster" translate: "Çevir" translatedFrom: "{x}'ten çevrilmiştir." accountDeletionInProgress: "Hesap silme işlemi şu anda devam ediyor." -usernameInfo: "Bu sunucudaki diğer hesaplardan hesabınızı ayıran bir isim. Alfabe (a~z, A~Z), rakamlar (0~9) veya alt çizgi (_) kullanabilirsiniz. Kullanıcı adları daha sonra değiştirilemez." +usernameInfo: "Bu sunucudaki diğer hesaplardan hesabını ayıran bir isim. Alfabe (a~z, A~Z), rakamlar (0~9) veya alt çizgi (_) kullanabilirsin. Kullanıcı adları daha sonra değiştirilemez." aiChanMode: "Ai Modu" devMode: "Geliştirici modu" keepCw: "İçerik uyarılarını sakla" @@ -911,7 +911,7 @@ lastCommunication: "Son iletişim" resolved: "Çözülmüş" unresolved: "Çözülmemiş" breakFollow: "Takipçiyi kaldır" -breakFollowConfirm: "Bu takipçiyi gerçekten silmek istiyor musun?" +breakFollowConfirm: "Bu takipçiyi ciddden silmek istiyor musun?" itsOn: "Etkin" itsOff: "Devre Dışı" on: "Açık" @@ -922,14 +922,14 @@ filter: "Filtre" controlPanel: "Kontrol Paneli" manageAccounts: "Hesapları Yönet" makeReactionsPublic: "Tepki geçmişini herkese açık olarak ayarla" -makeReactionsPublicDescription: "Bu, geçmişteki tüm tepkilerinizin listesini herkese açık hale getirecektir." +makeReactionsPublicDescription: "Bu, geçmişteki tüm tepkilerinin listesini herkese açık hale getirecek." classic: "Klasik" muteThread: "Konuyu sessize al" unmuteThread: "Konuyu sessizden çıkar" followingVisibility: "Takip edilenlerin görünürlüğü" followersVisibility: "Takipçilerin görünürlüğü" continueThread: "Konunun devamını görüntüle" -deleteAccountConfirm: "Bu, hesabınızı geri dönüşü olmayan bir şekilde silecektir. Devam etmek istiyor musunuz?" +deleteAccountConfirm: "Bu, hesabını geri dönüşü olmayan bir şekilde silecek. Devam etmek istiyor musun?" incorrectPassword: "Yanlış şifre." incorrectTotp: "Tek kullanımlık şifre yanlış veya süresi dolmuş." voteConfirm: "\"{choice}\" için oyunuzu onaylıyor musunuz?" @@ -963,7 +963,7 @@ reflectMayTakeTime: "Bunun yansıtılması biraz zaman alabilir." failedToFetchAccountInformation: "Hesap bilgileri alınamadı" rateLimitExceeded: "Hız sınırı aşıldı" cropImage: "Görüntüyü kırp" -cropImageAsk: "Bu görüntüyü kırpmak ister misiniz?" +cropImageAsk: "Bu görüntüyü kırpmak ister misin?" cropYes: "Kırp" cropNo: "Olduğu gibi kullanın" file: "Dosyalar" @@ -973,7 +973,7 @@ noEmailServerWarning: "E-posta sunucusu yapılandırılmamış." thereIsUnresolvedAbuseReportWarning: "Çözülmemiş raporlar var." recommended: "Önerilen" check: "Kontrol" -driveCapOverrideLabel: "Bu kullanıcının sürücü kapasitesini değiştirin" +driveCapOverrideLabel: "Bu kullanıcının Drive kapasitesini değiştir" driveCapOverrideCaption: "Kapasiteyi varsayılan değere sıfırlamak için 0 veya daha düşük bir değer girin." requireAdminForView: "Bunu görüntülemek için yönetici hesabıyla oturum açmanız gerekir." isSystemAccount: "Sistem tarafından oluşturulan ve otomatik olarak işletilen bir hesap." @@ -982,8 +982,8 @@ deleteAccount: "Hesabı sil" document: "Dokümantasyon" numberOfPageCache: "Önbelleğe alınmış sayfa sayısı" numberOfPageCacheDescription: "Bu sayıyı artırmak, kullanıcının cihazında daha fazla bellek kullanımı nedeniyle daha fazla yük oluşturmakla birlikte, kullanıcının rahatlığını artıracaktır." -logoutConfirm: "Çıkmak istediğinizden emin misiniz?" -logoutWillClearClientData: "Oturumu kapatmak, tarayıcıdan istemcinin ayarlarını siler. Tekrar oturum açtığınızda ayarları geri yükleyebilmek için, ayarlarınızın otomatik yedeklenmesini etkinleştirmeniz gerekir." +logoutConfirm: "Çıkmak istediğinden emin misin?" +logoutWillClearClientData: "Oturumu kapatmak, tarayıcıdan istemcinin ayarlarını siler. Tekrar oturum açtığında ayarları geri yükleyebilmek için, ayarlarının otomatik yedeklenmesini etkinleştirmen gerekir." lastActiveDate: "Son kullanımı" statusbar: "Durum çubuğu" pleaseSelect: "Bir seçenek seçin" @@ -1000,7 +1000,7 @@ localOnly: "Yalnızca yerel" remoteOnly: "Sadece uzaktan" failedToUpload: "Yükleme başarısız" cannotUploadBecauseInappropriate: "Bu dosya, dosyanın bazı kısımlarının uygunsuz olabileceği tespit edildiği için yüklenemiyor." -cannotUploadBecauseNoFreeSpace: "Sürücü kapasitesi yetersiz olduğu için yükleme başarısız oldu." +cannotUploadBecauseNoFreeSpace: "Drive kapasitesi yetersiz olduğu için yükleme başarısız oldu." cannotUploadBecauseExceedsFileSizeLimit: "Bu dosya, dosya boyutu sınırını aştığı için yüklenemiyor." cannotUploadBecauseUnallowedFileType: "Yetkisiz dosya türü nedeniyle yükleme yapılamıyor." beta: "Beta" @@ -1032,7 +1032,7 @@ numberOfLikes: "Beğeniler" show: "Göster" neverShow: "Bir daha gösterme" remindMeLater: "Belki daha sonra" -didYouLikeMisskey: "Misskey'i sevdiniz mi?" +didYouLikeMisskey: "Misskey'i sevdin mi?" pleaseDonate: "{host} ücretsiz yazılım Misskey kullanmaktadır. Misskey'in geliştirilmesinin devam edebilmesi için bağışlarınızı çok takdir ederiz!" correspondingSourceIsAvailable: "İlgili kaynak kodu {anchor} adresinde mevcuttur." roles: "Roller" @@ -1044,10 +1044,10 @@ assign: "Atama" unassign: "Atamayı kaldır" color: "Renk" manageCustomEmojis: "Özel Emojileri Yönet" -manageAvatarDecorations: "Avatar süslemelerini yönet" +manageAvatarDecorations: "Avatar süslerini yönet" youCannotCreateAnymore: "Oluşturma sınırına ulaştınız." cannotPerformTemporary: "Geçici olarak kullanılamıyor" -cannotPerformTemporaryDescription: "Bu işlem, yürütme sınırını aştığı için geçici olarak gerçekleştirilememektedir. Lütfen bir süre bekleyin ve tekrar deneyin." +cannotPerformTemporaryDescription: "Bu işlem, yürütme sınırını aştığı için geçici olarak gerçekleştirilememekte. Lütfen bir süre bekle ve tekrar dene." invalidParamError: "Geçersiz parametreler" invalidParamErrorDescription: "İstek parametreleri geçersiz. Bu durum genellikle bir hata nedeniyle oluşur, ancak boyut sınırlarını aşan girdiler veya benzer nedenlerden de kaynaklanabilir." permissionDeniedError: "İşlem reddedildi" @@ -1056,35 +1056,35 @@ preset: "Ön ayar" selectFromPresets: "Ön ayarlardan seçim yapın" achievements: "Başarılar" gotInvalidResponseError: "Geçersiz sunucu yanıtı" -gotInvalidResponseErrorDescription: "Sunucu erişilemez durumda olabilir veya bakım çalışması yapılmaktadır. Lütfen daha sonra tekrar deneyin." +gotInvalidResponseErrorDescription: "Sunucu erişilemez durumda olabilir veya bakım çalışması yapılmaktadır. Lütfen daha sonra tekrar dene." thisPostMayBeAnnoying: "Bu not başkalarını rahatsız edebilir." thisPostMayBeAnnoyingHome: "Ana zaman çizelgesine gönder" thisPostMayBeAnnoyingCancel: "İptal" thisPostMayBeAnnoyingIgnore: "Yine de gönder" -collapseRenotes: "Zaten gördüğünüz notları daraltın" -collapseRenotesDescription: "Daha önce tepki verdiğiniz veya yeniden not aldığınız notları daraltın." +collapseRenotes: "Daha önce görüntülenen Renote'lari kısaltılmış olarak göster" +collapseRenotesDescription: "Zaten yanıtladığın veya renote aldığın notları kapat." internalServerError: "İç Sunucu Hatası" internalServerErrorDescription: "Sunucu beklenmedik bir hatayla karşılaştı." copyErrorInfo: "Hata ayrıntılarını kopyala" -joinThisServer: "Bu örnekte kaydolun" -exploreOtherServers: "Başka bir örnek arayın" -letsLookAtTimeline: "Timeline'a bir göz atın" -disableFederationConfirm: "Federasyonu gerçekten devre dışı bırakmak mı?" -disableFederationConfirmWarn: "Federasyondan ayrılsa bile, aksi belirtilmedikçe gönderiler herkese açık olmaya devam edecektir. Genellikle bunu yapmanız gerekmez." +joinThisServer: "Bu sunucuda kaydolun" +exploreOtherServers: "Başka bir sunucu arayın" +letsLookAtTimeline: "Pano'ya bir göz atın" +disableFederationConfirm: "Federasyonu cidden devre dışı bırakmak istiyor musun?" +disableFederationConfirmWarn: "Federasyondan ayrılsa bile, aksi belirtilmedikçe gönderiler herkese açık olmaya devam edecek. Genellikle bunu yapmanız gerekmez." disableFederationOk: "Devre Dışı" invitationRequiredToRegister: "Bu etkinlik davetle katılımlıdır. Geçerli bir davet kodu girerek kaydolmanız gerekir." -emailNotSupported: "Bu örnek, E-Posta göndermeyi desteklemiyor." +emailNotSupported: "Bu sunucu, E-Posta göndermeyi desteklemiyor." postToTheChannel: "Kanalına gönder" cannotBeChangedLater: "Bu daha sonra değiştirilemez." reactionAcceptance: "Tepki Kabulü" likeOnly: "Sadece beğeniler" -likeOnlyForRemote: "Tüm (Yalnızca uzak örnekler için beğeniler)" +likeOnlyForRemote: "Tüm (Yalnızca uzak sunucu için beğeniler)" nonSensitiveOnly: "Hassas olmayanlar için" nonSensitiveOnlyForLocalLikeOnlyForRemote: "Yalnızca hassas olmayanlar (Yalnızca uzaktan beğeniler)" rolesAssignedToMe: "Bana atanan roller" -resetPasswordConfirm: "Şifrenizi gerçekten sıfırlamak istiyor musunuz?" +resetPasswordConfirm: "Şifreni gerçekten sıfırlamak istiyor musun?" sensitiveWords: "Hassas kelimeler" -sensitiveWordsDescription: "Yapılandırılan kelimelerden herhangi birini içeren tüm notların görünürlüğü otomatik olarak “Ana Sayfa” olarak ayarlanacaktır. Satır sonları ile ayırarak birden fazla not listeleyebilirsiniz." +sensitiveWordsDescription: "Yapılandırılan kelimelerden herhangi birini içeren tüm notların görünürlüğü otomatik olarak “Ana Sayfa” olarak ayarlanacaktır. Satır sonları ile ayırarak birden fazla not listeleyebilirsin." sensitiveWordsDescription2: "Boşluk kullanmak AND ifadeleri oluşturur ve anahtar kelimeleri eğik çizgi ile çevrelemek bunları düzenli ifadeye dönüştürür." prohibitedWords: "Yasaklanmış kelimeler" prohibitedWordsDescription: "Belirlenen kelime(ler)i içeren bir not göndermeye çalışıldığında hata verir. Birden fazla kelime, yeni satırla ayrılmış olarak ayarlanabilir." @@ -1092,19 +1092,20 @@ prohibitedWordsDescription2: "Boşluk kullanmak AND ifadeleri oluşturur ve anah hiddenTags: "Gizli hashtag'ler" hiddenTagsDescription: "Trend listesinde gösterilmeyecek etiketleri seçin.\nSatırlarla birden fazla etiket kaydedilebilir." notesSearchNotAvailable: "Not arama özelliği kullanılamıyor." +usersSearchNotAvailable: "Kullanıcı araması mevcut değildir." license: "Lisans" -unfavoriteConfirm: "Gerçekten favorilerden kaldırmak istiyor musunuz?" +unfavoriteConfirm: "Cidden favorilerden kaldırmak istiyor musunuz?" myClips: "Kliplerim" -drivecleaner: "Sürücü Temizleyici" +drivecleaner: "Drive Temizleyici" retryAllQueuesNow: "Tüm kuyrukları yeniden çalıştırmayı deneyin" -retryAllQueuesConfirmTitle: "Gerçekten hepsini tekrar denemek istiyor musunuz?" +retryAllQueuesConfirmTitle: "Cidden hepsini tekrar denemek istiyor musunuz?" retryAllQueuesConfirmText: "Bu, sunucu yükünü geçici olarak artıracaktır." enableChartsForRemoteUser: "Uzak kullanıcı veri grafikleri oluşturun" -enableChartsForFederatedInstances: "Uzak örnek veri grafikleri oluşturun" +enableChartsForFederatedInstances: "Uzak sunucu veri grafikleri oluşturun" enableStatsForFederatedInstances: "Uzak sunucu istatistiklerini alın" showClipButtonInNoteFooter: "Not eylem menüsüne “Klip” ekle" reactionsDisplaySize: "Tepki ekran boyutu" -limitWidthOfReaction: "Tepkilerin maksimum genişliğini sınırlayın ve bunları küçültülmüş boyutta görüntüleyin." +limitWidthOfReaction: "Tepkilerin maksimum genişliğini sınırla ve bunları küçültülmüş boyutta görüntüle." noteIdOrUrl: "Not ID veya URL" video: "Video" videos: "Videolar" @@ -1130,18 +1131,18 @@ vertical: "Dikey" horizontal: "Yatay" position: "Pozisyon" serverRules: "Sunucu kuralları" -pleaseConfirmBelowBeforeSignup: "Bu sunucuya kaydolmak için aşağıdakileri gözden geçirip kabul etmelisiniz:" -pleaseAgreeAllToContinue: "Devam etmek için yukarıdaki tüm alanları kabul etmelisiniz." +pleaseConfirmBelowBeforeSignup: "Bu sunucuya kaydolmak için aşağıdakileri gözden geçirip kabul etmelisin:" +pleaseAgreeAllToContinue: "Devam etmek için yukarıdaki tüm alanları kabul etmelisin." continue: "Devam et" preservedUsernames: "Rezerve edilmiş kullanıcı adları" -preservedUsernamesDescription: "Rezervasyon yapmak için kullanıcı adlarını satır sonlarıyla ayırarak listeleyin. Bu kullanıcı adları normal hesap oluşturma sırasında kullanılamaz hale gelir, ancak yöneticiler tarafından manuel olarak hesap oluşturmak için kullanılabilir. Bu kullanıcı adlarını kullanan mevcut hesaplar etkilenmez." +preservedUsernamesDescription: "Rezervasyon yapmak için kullanıcı adlarını satır sonlarıyla ayırarak listele. Bu kullanıcı adları normal hesap oluşturma sırasında kullanılamaz hale gelir, ancak yöneticiler tarafından manuel olarak hesap oluşturmak için kullanılabilir. Bu kullanıcı adlarını kullanan mevcut hesaplar etkilenmez." createNoteFromTheFile: "Bu dosyadan not oluşturun" archive: "Arşiv" archived: "Arşivle" unarchive: "Arşivden çıkar" -channelArchiveConfirmTitle: "Gerçekten {name} arşivlemek mi istiyorsunuz?" -channelArchiveConfirmDescription: "Arşivlenmiş bir kanal artık kanal listesinde veya arama sonuçlarında görünmeyecektir. Ayrıca, bu kanala yeni gönderiler eklenemeyecektir." -thisChannelArchived: "Bu kanal arşivlenmiştir." +channelArchiveConfirmTitle: "Cidden {name} arşivlemek mi istiyorsun?" +channelArchiveConfirmDescription: "Arşivlenmiş bir kanal artık kanal listesinde veya arama sonuçlarında görünmeyecektir. Ayrıca, bu kanala yeni gönderiler eklenemeyecek." +thisChannelArchived: "Bu kanal arşivlenmiş." displayOfNote: "Not ekranı" initialAccountSetting: "Profil ayarları" youFollowing: "Takip edildi" @@ -1149,16 +1150,16 @@ preventAiLearning: "Makine Öğreniminde (Üretken Ai) kullanımını reddet" preventAiLearningDescription: "Tarayıcılardan, makine öğrenimi (Tahminsel / Üretken Ai) veri kümelerinde yayınlanan metin veya görsel materyalleri vb. kullanmamalarını talep eder. Bu, ilgili içeriğe “noai” HTML-Response bayrağı eklenerek gerçekleştirilir. Ancak, bu bayrakla tam bir önleme sağlanamaz, çünkü bu bayrak basitçe göz ardı edilebilir." options: "Seçenekler" specifyUser: "Belirli kullanıcı" -lookupConfirm: "Yukarı bakmak ister misiniz?" -openTagPageConfirm: "Bir hashtag sayfası açmak ister misiniz?" +lookupConfirm: "Yukarı bakmak ister misin?" +openTagPageConfirm: "Bir hashtag sayfası açmak ister misin?" specifyHost: "Belirli ana bilgisayar" failedToPreviewUrl: "Önizleme yapılamadı" update: "Güncelle" -rolesThatCanBeUsedThisEmojiAsReaction: "Bu emojiyi tepki olarak kullanabileceğiniz roller" +rolesThatCanBeUsedThisEmojiAsReaction: "Bu emojiyi tepki olarak kullanabileceğin roller" rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "Herhangi bir rol belirtilmezse, herkes bu emojiyi tepki olarak kullanabilir." rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Bu roller herkese açık olmalıdır." -cancelReactionConfirm: "Tepkinizi gerçekten silmek istiyor musunuz?" -changeReactionConfirm: "Tepkinizi gerçekten değiştirmek istiyor musunuz?" +cancelReactionConfirm: "Tepkini cidden silmek istiyor musun?" +changeReactionConfirm: "Tepkini cidden değiştirmek istiyor musun?" later: "Daha sonra" goToMisskey: "Misskey'e" additionalEmojiDictionary: "Ek emoji sözlükleri" @@ -1171,7 +1172,7 @@ createInviteCode: "Davet Kodu oluştur" createWithOptions: "Seçeneklerle oluştur" createCount: "Davet sayısı" inviteCodeCreated: "Davet oluşturuldu" -inviteLimitExceeded: "Oluşturulabilecek davetiyelerin maksimum sayısına ulaştınız." +inviteLimitExceeded: "Oluşturulabilecek davetiyelerin maksimum sayısına ulaştın." createLimitRemaining: "{limit} Davet limiti kaldı" inviteLimitResetCycle: "Bu limit {time} tarihinde {limit} değerine sıfırlanacaktır." expirationDate: "Son kullanma tarihi" @@ -1193,7 +1194,7 @@ forYou: "Senin için" currentAnnouncements: "Güncel duyurular" pastAnnouncements: "Geçmiş duyurular" youHaveUnreadAnnouncements: "Okunmamış duyurular var." -useSecurityKey: "Güvenlik anahtarınızı veya şifrenizi kullanmak için lütfen tarayıcınızın veya cihazınızın talimatlarını izleyin." +useSecurityKey: "Güvenlik anahtarını veya şifreni kullanmak için lütfen tarayıcının veya cihazının talimatlarını izle." replies: "Yanıtla" renotes: "Renote'lar" loadReplies: "Yanıtları göster" @@ -1206,18 +1207,18 @@ unnotifyNotes: "Yeni notlar hakkında bildirim almayı durdur" authentication: "Kimlik doğrulama" authenticationRequiredToContinue: "Devam etmek için lütfen kimlik doğrulaması yapın." dateAndTime: "Zaman damgası" -showRenotes: "Renot'ları göster" +showRenotes: "Renote'ları göster" edited: "Düzenlendi" notificationRecieveConfig: "Bildirim Ayarları" mutualFollow: "Karşılıklı takip" followingOrFollower: "Takip eden veya takipçi" fileAttachedOnly: "Yalnızca dosya içeren notlar" -showRepliesToOthersInTimeline: "Timeline'da diğer kişilere verilen yanıtları göster" -hideRepliesToOthersInTimeline: "Timeline'dan diğer kişilerin yanıtlarını gizle" -showRepliesToOthersInTimelineAll: "Timeline'da takip ettiğiniz herkesin diğerlerine verdiği yanıtları göster" -hideRepliesToOthersInTimelineAll: "Timeline'de takip ettiğiniz herkesten diğer kişilere verilen yanıtları gizleyin" -confirmShowRepliesAll: "Bu işlem geri alınamaz. Takip ettiğiniz herkesin yanıtlarını zaman çizelgenizde diğer kullanıcılara göstermek istiyor musunuz?" -confirmHideRepliesAll: "Bu işlem geri alınamaz. Şu anda takip ettiğiniz tüm kullanıcıların yanıtlarını zaman tünelinde gerçekten göstermeyecek misiniz?" +showRepliesToOthersInTimeline: "Pano'da diğer kişilere verilen yanıtları göster" +hideRepliesToOthersInTimeline: "Pano'dan diğer kişilerin yanıtlarını gizle" +showRepliesToOthersInTimelineAll: "Pano'da takip ettiğin herkesin diğerlerine verdiği yanıtları göster" +hideRepliesToOthersInTimelineAll: "Pano'da takip ettiğin herkesten diğer kişilere verilen yanıtları gizle" +confirmShowRepliesAll: "Bu işlem geri alınamaz. Takip ettiğin herkesin yanıtlarını zaman çizelgende diğer kullanıcılara göstermek istiyor musun?" +confirmHideRepliesAll: "Bu işlem geri alınamaz. Şu anda takip ettiğin tüm kullanıcıların yanıtlarını zaman tünelinde cidden göstermeyecek misin?" externalServices: "Dış Hizmetler" sourceCode: "Kaynak kodu" sourceCodeIsNotYetProvided: "Kaynak kodu henüz mevcut değildir. Bu sorunu gidermek için yöneticiyle iletişime geçin." @@ -1232,24 +1233,24 @@ impressumDescription: "Almanya gibi bazı ülkelerde, ticari web sitelerinde iş privacyPolicy: "Gizlilik Politikası" privacyPolicyUrl: "Gizlilik Politikası URL'si" tosAndPrivacyPolicy: "Hizmet Şartları ve Gizlilik Politikası" -avatarDecorations: "Avatar süslemeleri" +avatarDecorations: "Avatar süsleri" attach: "Ek" detach: "Kaldır" detachAll: "Tümünü Kaldır" angle: "Açı" flip: "Çevir" -showAvatarDecorations: "Avatar süslemelerini göster" +showAvatarDecorations: "Avatar süslerini göster" releaseToRefresh: "Yenilemek için serbest bırak" refreshing: "Yenileniyor..." pullDownToRefresh: "Yenilemek için aşağı çekin" useGroupedNotifications: "Gruplandırılmış bildirimleri göster" signupPendingError: "E-posta adresini doğrulamada bir sorun oluştu. Bağlantının süresi dolmuş olabilir." -cwNotationRequired: "“İçeriği gizle” seçeneği etkinleştirilirse, bir açıklama sağlanmalıdır." +cwNotationRequired: "“İçeriği gizle” seçeneği etkinleştirilirse, bir açıklama sağlanmalı." doReaction: "Tepki ekle" code: "Kod" reloadRequiredToApplySettings: "Ayarları uygulamak için yeniden yükleme gereklidir." remainingN: "Kalan: {n}" -overwriteContentConfirm: "Mevcut içeriği üzerine yazmak istediğinizden emin misiniz?" +overwriteContentConfirm: "Mevcut içeriği üzerine yazmak istediğinden emin misin?" seasonalScreenEffect: "Mevsimsel Ekran Efekti" decorate: "Süsle" addMfmFunction: "MFM ekle" @@ -1266,7 +1267,7 @@ ranking: "Sıralama" lastNDays: "Son {n} gün" backToTitle: "Başlığa geri dön" hemisphere: "Yaşadığınız yer" -withSensitive: "Hassas dosyalara notlar ekleyin" +withSensitive: "Hassas dosyalara notlar ekle" userSaysSomethingSensitive: "{name} tarafından gönderilen mesaj hassas içerik barındırmaktadır." enableHorizontalSwipe: "Kaydırarak sekmeler arasında geçiş yapın" loading: "Yükleniyor" @@ -1278,13 +1279,13 @@ useBackupCode: "Yedek kodları kullanın" launchApp: "Uygulamayı başlatın" useNativeUIForVideoAudioPlayer: "Video ve ses oynatımı için tarayıcı kullanıcı arayüzünü kullan" keepOriginalFilename: "Orijinal dosya adını koru" -keepOriginalFilenameDescription: "Bu ayarı kapatırsanız, dosya yüklediğinizde dosya adları otomatik olarak rastgele bir dizeyle değiştirilecektir." +keepOriginalFilenameDescription: "Bu ayarı kapatırsan, dosya yüklediğinde dosya adları otomatik olarak rastgele bir dizeyle değiştirilecek." noDescription: "Açıklama yok" alwaysConfirmFollow: "Takip ederken her zaman onaylayın" inquiry: "İletişim" -tryAgain: "Lütfen daha sonra tekrar deneyin." +tryAgain: "Lütfen daha sonra tekrar dene." confirmWhenRevealingSensitiveMedia: "Confirm when revealing sensitive media" -sensitiveMediaRevealConfirm: "Bu hassas bir medya olabilir. Açıklamakta emin misiniz?" +sensitiveMediaRevealConfirm: "Bu hassas bir medya olabilir. Açıklamakta emin misin?" createdLists: "Oluşturulan listeler" createdAntennas: "Oluşturulan antenler" fromX: "{x}'den" @@ -1305,7 +1306,7 @@ testCaptchaWarning: "Bu işlev CAPTCHA testi amacıyla tasarlanmıştır.\n\nBu dosyaları notlara eklerken yeniden kullanabilir veya daha sonra paylaşmak üzere önceden yükleyebilirsiniz. \nBir dosyayı silerken dikkatli olun, çünkü kullanıldığı her yerde (notlar, sayfalar, avatarlar, afişler vb.) mevcut olmayacaktır. \nAyrıca dosyalarınızı düzenlemek için klasörler oluşturabilirsiniz." +driveAboutTip: "Drive'da, geçmişte yüklediğin dosyaların bir listesi görüntülenir. \nBu dosyaları notlara eklerken yeniden kullanabilir veya daha sonra paylaşmak üzere önceden yükleyebilirsin. \nBir dosyayı silerken dikkatli ol, çünkü kullanıldığı her yerde (notlar, sayfalar, avatarlar, afişler vb.) mevcut olmayacakt. \nAyrıca dosyalarını düzenlemek için klasörler oluşturabilirsin." scrollToClose: "Kaydırarak kapatın" advice: "Tavsiye" realtimeMode: "Gerçek zamanlı mod" @@ -1363,9 +1364,9 @@ emojiUnmute: "Emoji ses aç" muteX: "Sessiz {x}" unmuteX: "Sesi aç {x}" abort: "İptal" -tip: "İpuçları & Püf Noktaları" -redisplayAllTips: "Tüm “İpuçları & Püf Noktaları” tekrar göster" -hideAllTips: "Tüm “İpuçları & Püf Noktaları” gizle" +tip: "İpucu & Püf Nokta" +redisplayAllTips: "Tüm “İpucu & Püf Nokta” tekrar göster" +hideAllTips: "Tüm “İpucu & Püf Nokta” gizle" defaultImageCompressionLevel: "Varsayılan görüntü sıkıştırma düzeyi" defaultImageCompressionLevel_description: "Düşük seviye görüntü kalitesini korur ancak dosya boyutunu artırır.Yüksek seviye dosya boyutunu azaltır ancak görüntü kalitesini düşürür." inMinutes: "Dakika(lar)" @@ -1410,7 +1411,7 @@ _chat: chatNotAvailableInOtherAccount: "Sohbet işlevi diğer kullanıcı için devre dışı bırakılmıştır." cannotChatWithTheUser: "Bu kullanıcıyla sohbet başlatılamıyor." cannotChatWithTheUser_description: "Sohbet kullanılamıyor veya karşı taraf sohbeti etkinleştirmedi." - youAreNotAMemberOfThisRoomButInvited: "Bu odanın katılımcısı değilsiniz, ancak bir davet aldınız. Lütfen daveti kabul ederek katılın." + youAreNotAMemberOfThisRoomButInvited: "Bu odanın katılımcısı değilsin, ancak bir davet aldın. Lütfen daveti kabul ederek katıl." doYouAcceptInvitation: "Daveti kabul ediyor musunuz?" chatWithThisUser: "Kullanıcıyla sohbet et" thisUserAllowsChatOnlyFromFollowers: "Bu kullanıcı yalnızca takipçilerinden gelen sohbetleri kabul eder." @@ -1418,12 +1419,12 @@ _chat: thisUserAllowsChatOnlyFromMutualFollowing: "Bu kullanıcı, yalnızca karşılıklı takip eden kullanıcıların sohbetlerini kabul eder." thisUserNotAllowedChatAnyone: "Bu kullanıcı kimseyle sohbet etmiyor." chatAllowedUsers: "Sohbet etmesine izin verilecek kişiler" - chatAllowedUsers_note: "Bu ayardan bağımsız olarak, sohbet mesajı gönderdiğiniz herkesle sohbet edebilirsiniz." + chatAllowedUsers_note: "Bu ayardan bağımsız olarak, sohbet mesajı gönderdiğin herkesle sohbet edebilirsin." _chatAllowedUsers: everyone: "Herkes" - followers: "Sadece takipçileriniz" + followers: "Sadece takipçilerin" following: "Only users you are following" - mutual: "Sadece takip ettiğiniz kullanıcılar" + mutual: "Sadece takiplerin" none: "Kimse" _emojiPalette: palettes: "Palet" @@ -1431,24 +1432,24 @@ _emojiPalette: paletteForMain: "Ana palet" paletteForReaction: "Reaksiyon paleti" _settings: - driveBanner: "Sürücüyü yönetebilir ve yapılandırabilir, kullanımı kontrol edebilir ve dosya yükleme ayarlarını yapılandırabilirsiniz." - pluginBanner: "Eklentilerle istemci özelliklerini genişletebilirsiniz. Eklentileri yükleyebilir, ayrı ayrı yapılandırabilir ve yönetebilirsiniz." - notificationsBanner: "Sunucudan gelen bildirimlerin türlerini ve kapsamını ve push bildirimlerini yapılandırabilirsiniz." + driveBanner: "Drive'ı yönetebilir ve yapılandırabilir, kullanımı kontrol edebilir ve dosya yükleme ayarlarını yapılandırabilirsin." + pluginBanner: "Eklentilerle istemci özelliklerini genişletebilirsin. Eklentileri yükleyebilir, ayrı ayrı yapılandırabilir ve yönetebilirsin." + notificationsBanner: "Sunucudan gelen bildirimlerin türlerini ve kapsamını ve push bildirimlerini yapılandırabilirsin." api: "API" webhook: "Webhook" serviceConnection: "Hizmet entegrasyonu" serviceConnectionBanner: "Dış uygulamalar veya hizmetlerle entegrasyon sağlamak için erişim belirteçlerini ve Webhook'ları yönetin ve yapılandırın." accountData: "Hesap verileri" accountDataBanner: "Hesap verilerini yönetmek için dışa ve içe aktarma." - muteAndBlockBanner: "İçeriği gizlemek ve belirli kullanıcıların eylemlerini kısıtlamak için ayarları yapılandırabilir ve yönetebilirsiniz." - accessibilityBanner: "Müşterinin görsellerini ve davranışını kişiselleştirebilir ve kullanımı optimize etmek için ayarları yapılandırabilirsiniz." - privacyBanner: "Hesap gizliliği ile ilgili ayarları, örneğin içerik görünürlüğü, bulunabilirlik ve takip onayı gibi ayarları yapılandırabilirsiniz." - securityBanner: "Şifre, oturum açma yöntemleri, kimlik doğrulama uygulamaları ve Passkeys gibi hesap güvenliği ile ilgili ayarları yapılandırabilirsiniz." - preferencesBanner: "İstediğiniz şekilde istemcinin genel davranışını yapılandırabilirsiniz." - appearanceBanner: "İstemcinin görünüm ve ekran ayarlarını tercihlerinize göre yapılandırabilirsiniz." - soundsBanner: "İstemcide oynatma için ses ayarlarını yapılandırabilirsiniz." - timelineAndNote: "Timeline ve not" - makeEveryTextElementsSelectable: "Tüm metin öğelerini seçilebilir hale getirin" + muteAndBlockBanner: "İçeriği gizlemek ve belirli kullanıcıların eylemlerini kısıtlamak için ayarları yapılandırabilir ve yönetebilirsin." + accessibilityBanner: "İstemci, görünüm ve davranışları açısından en iyi şekilde kullanılmak üzere kişiselleştirilebilir ve ayarlanabilir." + privacyBanner: "Hesap gizliliği ile ilgili ayarları, örneğin içerik görünürlüğü, bulunabilirlik ve takip onayı gibi ayarları yapılandırabilirsin." + securityBanner: "Şifre, oturum açma yöntemleri, kimlik doğrulama uygulamaları ve Passkeys gibi hesap güvenliği ile ilgili ayarları yapılandırabilirsin." + preferencesBanner: "İstediğin şekilde istemcinin genel davranışını yapılandırabilirsin." + appearanceBanner: "İstemcinin görünüm ve ekran ayarlarını tercihlerini göre yapılandırabilirsin." + soundsBanner: "İstemcide oynatma için ses ayarlarını yapılandırabilirsin." + timelineAndNote: "Pano ve not" + makeEveryTextElementsSelectable: "Tüm metin öğelerini seçilebilir hale getir" makeEveryTextElementsSelectable_description: "Bunu etkinleştirmek bazı durumlarda kullanılabilirliği azaltabilir." useStickyIcons: "Kaydırma sırasında simgeleri takip et" enableHighQualityImagePlaceholders: "Yüksek kaliteli görüntüler için yer tutucuları göster" @@ -1458,39 +1459,40 @@ _settings: ifOff: "Kapalıyken" enableSyncThemesBetweenDevices: "Yüklü temaları cihazlar arasında senkronize edin" enablePullToRefresh: "Yenilemek için çekin" - enablePullToRefresh_description: "Fareyi kullanırken, kaydırma tekerleğini basılı tutarken sürükleyin." + enablePullToRefresh_description: "Fareyi kullanırken, kaydırma tekerleğini basılı tutarken sürükle." realtimeMode_description: "Sunucu ile bağlantı kurar ve içeriği gerçek zamanlı olarak günceller. Bu, trafik ve bellek tüketimini artırabilir." contentsUpdateFrequency: "İçerik erişim sıklığı" contentsUpdateFrequency_description: "Değer ne kadar yüksek olursa içerik o kadar sık güncellenir, ancak bu durum performansı düşürür ve trafik ile bellek tüketimini artırır." contentsUpdateFrequency_description2: "Gerçek zamanlı mod açık olduğunda, bu ayardan bağımsız olarak içerik gerçek zamanlı olarak güncellenir." - showUrlPreview: "URL önizlemesini göster" + showUrlPreview: "URL önizlemesi" showAvailableReactionsFirstInNote: "Mevcut tepkileri en üstte göster." + showPageTabBarBottom: "Sayfa sekme çubuğunu aşağıda göster" _chat: showSenderName: "Gönderenin adını göster" sendOnEnter: "Enter tuşuna basarak gönderin" _preferencesProfile: profileName: "Profil adı" - profileNameDescription: "Bu cihazı tanımlayan bir ad belirleyin." + profileNameDescription: "Bu cihazı tanımlayan bir ad belirle." profileNameDescription2: "Örnek: “Ana bilgisayar”, “Akıllı telefon”" manageProfiles: "Profilleri Yönet" _preferencesBackup: autoBackup: "Otomatik yedekleme" restoreFromBackup: "Yedeklemeden geri yükle" noBackupsFoundTitle: "Yedekleme bulunamadı" - noBackupsFoundDescription: "Otomatik olarak oluşturulan yedekleme bulunamadı, ancak manuel olarak bir yedekleme dosyası kaydettiyseniz, bunu içe aktarabilir ve geri yükleyebilirsiniz." + noBackupsFoundDescription: "Otomatik olarak oluşturulan yedekleme bulunamadı, ancak manuel olarak bir yedekleme dosyası kaydettiysen, bunu içe aktarabilir ve geri yükleyebilirsin." selectBackupToRestore: "Geri yüklemek için bir yedekleme seçin" youNeedToNameYourProfileToEnableAutoBackup: "Otomatik yedeklemeyi etkinleştirmek için bir profil adı ayarlanmalıdır." - autoPreferencesBackupIsNotEnabledForThisDevice: "Bu cihazda ayarların otomatik yedeklemesi etkinleştirilmemiştir." + autoPreferencesBackupIsNotEnabledForThisDevice: "Bu cihazda ayarların otomatik yedeklemesi etkinleştirilmemiş." backupFound: "Ayarların yedeği bulundu" _accountSettings: requireSigninToViewContents: "İçeriği görüntülemek için oturum açmanız gerekir." - requireSigninToViewContentsDescription1: "Oluşturduğunuz tüm notları ve diğer içeriği görüntülemek için oturum açmanız gerekir. Bu, tarayıcıların bilgilerinizi toplamasına engel olacaktır." - requireSigninToViewContentsDescription2: "İçerik, URL önizlemelerinde (OGP), web sayfalarına gömülü olarak veya not alıntıları desteklemeyen sunucularda görüntülenmeyecektir." + requireSigninToViewContentsDescription1: "Oluşturduğun tüm notları ve diğer içeriği görüntülemek için oturum açman gerekir. Bu, tarayıcıların bilgilerini toplamasına engel olacaktır." + requireSigninToViewContentsDescription2: "İçerik, URL önizlemelerinde (OGP), web sayfalarına gömülü olarak veya not alıntıları desteklemeyen sunucularda görüntülenmeyecek." requireSigninToViewContentsDescription3: "Bu kısıtlamalar, diğer uzak sunuculardan gelen birleştirilmiş içerik için geçerli olmayabilir." makeNotesFollowersOnlyBefore: "Geçmiş notların yalnızca takipçilere gösterilmesini sağlayın" makeNotesFollowersOnlyBeforeDescription: "Bu özellik etkinleştirildiğinde, yalnızca takipçiler belirlenen tarih ve saatten sonra veya belirlenen süre boyunca görünür olan notları görebilir. Bu özellik devre dışı bırakıldığında, notun yayın durumu da geri yüklenir." makeNotesHiddenBefore: "Geçmiş notları gizli yap" - makeNotesHiddenBeforeDescription: "Bu özellik etkinleştirildiğinde, belirlenen tarih ve saatten geçmiş olan veya yalnızca sizin görebildiğiniz notlar. Bu özellik devre dışı bırakıldığında, notun yayın durumu da geri yüklenecektir." + makeNotesHiddenBeforeDescription: "Bu özellik etkinleştirildiğinde, belirlenen tarih ve saatten geçmiş olan veya yalnızca sizin görebildiğiniz notlar. Bu özellik devre dışı bırakıldığında, notun yayın durumu da geri yüklenecek." mayNotEffectForFederatedNotes: "Uzak sunucuya bağlı notlar etkilenmeyebilir." mayNotEffectSomeSituations: "Bu kısıtlamalar basitleştirilmiştir. Uzaktaki bir sunucuda görüntüleme veya moderasyon sırasında gibi bazı durumlarda geçerli olmayabilir." notesHavePassedSpecifiedPeriod: "Belirtilen sürenin geçtiğini unutmayın." @@ -1501,7 +1503,7 @@ _abuseUserReport: resolve: "Çözüm" accept: "Kabul et" reject: "Reddet" - resolveTutorial: "Raporun içeriği meşruysa, “Kabul Et” seçeneğini seçerek sorunu çözülmüş olarak işaretleyin.\nRaporun içeriği meşru değilse, “Reddet” seçeneğini seçerek raporu yok sayın." + resolveTutorial: "Raporun içeriği meşruysa, “Kabul Et” seçeneğini seçerek sorunu çözülmüş olarak işaretle.\nRaporun içeriği meşru değilse, “Reddet” seçeneğini seçerek raporu yok say." _delivery: status: "Teslimat durumu" stop: "Askıya al" @@ -1526,74 +1528,74 @@ _bubbleGame: _howToPlay: section1: "Konumu ayarlayın ve nesneyi kutuya bırakın." section2: "Aynı türden iki nesne birbirine dokunduğunda, farklı bir nesneye dönüşür ve puan kazanırsınız." - section3: "Kutu dolduğunda oyun biter. Kutuyu doldurmadan nesneleri birleştirerek yüksek puan almaya çalışın!" + section3: "Kutu dolduğunda oyun biter. Kutuyu doldurmadan nesneleri birleştirerek yüksek puan almaya çalış!" _announcement: forExistingUsers: "Sadece mevcut kullanıcılar" - forExistingUsersDescription: "Bu duyuru, etkinleştirildiğinde yalnızca yayınlandığı anda mevcut olan kullanıcılara gösterilecektir. Devre dışı bırakıldığında, yayınlandıktan sonra yeni kaydolan kullanıcılar da bu duyuruyu görecektir." + forExistingUsersDescription: "Bu duyuru, etkinleştirildiğinde yalnızca yayınlandığı anda mevcut olan kullanıcılara gösterilecek. Devre dışı bırakıldığında, yayınlandıktan sonra yeni kaydolan kullanıcılar da bu duyuruyu görecek." needConfirmationToRead: "Ayrı okuma onayı gerektirir" needConfirmationToReadDescription: "Etkinleştirildiğinde, bu duyuruyu okundu olarak işaretlemek için ayrı bir onay mesajı görüntülenir. Bu duyuru, “Tümünü okundu olarak işaretle” işlevinden de hariç tutulur." end: "Arşiv duyurusu" - tooManyActiveAnnouncementDescription: "Çok fazla aktif duyuru olması kullanıcı deneyimini kötüleştirebilir. Artık geçerliliğini yitirmiş duyuruları arşivlemeyi düşünün." + tooManyActiveAnnouncementDescription: "Çok fazla aktif duyuru olması kullanıcı deneyimini kötüleştirebilir. Artık geçerliliğini yitirmiş duyuruları arşivlemeyi düşün." readConfirmTitle: "Okundu olarak işaretle?" - readConfirmText: "Bu, “{title}” içeriğini okundu olarak işaretleyecektir." + readConfirmText: "Bu, “{title}” içeriğini okundu olarak işaretleyecek." shouldNotBeUsedToPresentPermanentInfo: "Duyuruları, uzun vadede geçerli olacak bilgiler için değil, güncel ve zaman sınırlı bilgileri yayınlamak için kullanmak en iyisidir." dialogAnnouncementUxWarn: "Aynı anda iki veya daha fazla diyalog tarzı bildirim olması, kullanıcı deneyimini önemli ölçüde etkileyebilir, bu nedenle lütfen bunları dikkatli kullanın." silence: "Bildirim yok" - silenceDescription: "Bu seçeneği etkinleştirdiğinizde, bu duyurunun bildirimi atlanacak ve kullanıcı bunu okumak zorunda kalmayacaktır." + silenceDescription: "Bu seçeneği etkinleştirdiğinde, bu duyurunun bildirimi atlanacak ve kullanıcı bunu okumak zorunda kalmayacak." _initialAccountSetting: accountCreated: "Hesabınız başarıyla oluşturuldu!" - letsStartAccountSetup: "Öncelikle, profilinizi oluşturalım." - letsFillYourProfile: "Öncelikle profilinizi oluşturalım." + letsStartAccountSetup: "Şimdi hesabını oluşturalım." + letsFillYourProfile: "Önce profilini oluşturalım." profileSetting: "Profil ayarları" privacySetting: "Gizlilik ayarları" - theseSettingsCanEditLater: "Bu ayarları daha sonra istediğiniz zaman değiştirebilirsiniz." - youCanEditMoreSettingsInSettingsPageLater: "“Ayarlar” sayfasından yapılandırabileceğiniz daha birçok ayar bulunmaktadır. Daha sonra mutlaka ziyaret edin." - followUsers: "İlgilendiğiniz bazı kullanıcıları takip ederek zaman akışınızı oluşturmaya çalışın." - pushNotificationDescription: "Push bildirimlerini etkinleştirdiğinizde, {name} adresinden gelen bildirimleri doğrudan cihazınıza alabilirsiniz." + theseSettingsCanEditLater: "Bu ayarları daha sonra istediğin zaman değiştirebilirsin." + youCanEditMoreSettingsInSettingsPageLater: "“Ayarlar” sayfasından yapılandırabileceğin daha birçok ayar bulunmaktadır. Daha sonra mutlaka ziyaret et." + followUsers: "İlgilendiğiniz bazı kullanıcıları takip ederek zaman akışını oluşturmaya çalış." + pushNotificationDescription: "Push bildirimlerini etkinleştirdiğinde, {name} adresinden gelen bildirimleri doğrudan cihazınıza alabilirsin." initialAccountSettingCompleted: "Profil kurulumu tamamlandı!" - haveFun: "{name}'in keyfini çıkarın!" - youCanContinueTutorial: "{name} (Misskey) kullanımına ilişkin bir eğiticiye geçebilir veya buradan kurulumu sonlandırıp hemen kullanmaya başlayabilirsiniz." + haveFun: "{name} ile iyi eğlenceler!" + youCanContinueTutorial: "{name} (Misskey) öğreticisine geçebilir veya buradan kurulumu sonlandırıp hemen kullanabilirsin." startTutorial: "Öğreticiye başla" - skipAreYouSure: "Profil kurulumunu gerçekten atlamak mı istiyorsunuz?" - laterAreYouSure: "Profil ayarlarını gerçekten daha sonra mı yapacaksınız?" + skipAreYouSure: "Profil kurulumunu cidden atlamak mı istiyorsun?" + laterAreYouSure: "Profil ayarlarını cidden daha sonra mı yapacaksın?" _initialTutorial: launchTutorial: "Öğreticiyi izle" title: "Öğretici" wellDone: "Tebrikler!" skipAreYouSure: "Öğreticiyi kapatmak mı istiyorsunuz?" _landing: - title: "Öğreticiye hoş geldiniz" - description: "Burada, Misskey'i kullanmanın temellerini ve özelliklerini öğrenebilirsiniz." + title: "Öğreticiye hoş geldin" + description: "Burada, Misskey'i kullanmanın temellerini ve özelliklerini öğrenebilirsin." _note: title: "Not nedir?" description: "Misskey'deki gönderiler “Notlar” olarak adlandırılır. Notlar zaman çizelgesinde kronolojik olarak düzenlenir ve gerçek zamanlı olarak güncellenir." reply: "Bir mesaja yanıt vermek için bu düğmeye tıklayın. Yanıtlara yanıt vermek de mümkündür, böylece konuşma bir konu başlığı gibi devam eder." - renote: "Bu notu kendi zaman çizelgenizde paylaşabilirsiniz. Ayrıca yorumlarınızla birlikte alıntı da yapabilirsiniz." - reaction: "Not'a tepkiler ekleyebilirsiniz. Daha fazla ayrıntı bir sonraki sayfada açıklanacaktır." - menu: "Not ayrıntılarını görüntüleyebilir, bağlantıları kopyalayabilir ve çeşitli diğer işlemleri gerçekleştirebilirsiniz." + renote: "Bu notu kendi zaman çizelgende paylaşabilirsiniz. Ayrıca yorumlarınızla birlikte alıntı da yapabilirsin." + reaction: "Not'a tepkiler ekleyebilirsin. Daha fazla ayrıntı bir sonraki sayfada açıklanacak." + menu: "Not ayrıntılarını görüntüleyebilir, bağlantıları kopyalayabilir ve çeşitli diğer işlemleri gerçekleştirebilirsin." _reaction: title: "Reaksiyonlar nedir?" - description: "Notlara çeşitli emojilerle tepki verilebilir. Tepkiler, sadece bir ‘beğeni’ ile ifade edilemeyen nüansları ifade etmenizi sağlar." + description: "Notlara çeşitli emojilerle tepki verilebilir. Tepkiler, sadece bir ‘beğeni’ ile ifade edilemeyen nüansları ifade etmeni sağlar." letsTryReacting: "Notun üzerindeki ‘+’ düğmesine tıklayarak tepkiler eklenebilir. Bu örnek nota tepki verin!" - reactToContinue: "Devam etmek için bir tepki ekleyin." - reactNotification: "Birisi notunuza tepki verdiğinde gerçek zamanlı bildirimler alacaksınız." - reactDone: "“-” düğmesine basarak bir tepkiyi geri alabilirsiniz." + reactToContinue: "Devam etmek için bir tepki ekle." + reactNotification: "Biri notunuza tepki verdiğinde gerçek zamanlı bildirimler alacaksınız." + reactDone: "“-” düğmesine basarak bir tepkiyi geri alabilirsin." _timeline: - title: "Timeline Kavramı" - description1: "Misskey, kullanıma göre birden fazla Timeline sunar (bazı Timeline'lar sunucunun politikalarına bağlı olarak kullanılamayabilir)." - home: "Takip ettiğiniz hesapların notlarını görüntüleyebilirsiniz." - local: "Bu sunucudaki tüm kullanıcıların notlarını görüntüleyebilirsiniz." - social: "Ev ve Yerel Timeline'dan notlar görüntülenecektir." - global: "Bağlı tüm sunuculardan gelen notları görüntüleyebilirsiniz." - description2: "Ekranın üst kısmındaki Timeline'lar arasında istediğiniz zaman geçiş yapabilirsiniz." - description3: "Ayrıca, Liste Timeline'ı ve Kanal Timeline'ı da bulunmaktadır. Daha fazla ayrıntı için lütfen {link} adresine bakın." + title: "Pano Kavramı" + description1: "Misskey, kullanıma göre birden fazla Pano sunar (Bazı Pano'lar sunucunun politikalarına bağlı olarak kullanılamayabilir)." + home: "Takip ettiğin hesapların notlarını görüntüleyebilirsin." + local: "Bu sunucudaki tüm kullanıcıların notlarını görüntüleyebilirsin." + social: "Ev ve Yerel Pano'dan notlar görüntülenecek." + global: "Bağlı tüm sunuculardan gelen notları görüntüleyebilirsin." + description2: "Ekranın üst kısmındaki Pano'lar arasında istediğin zaman geçiş yapabilirsin." + description3: "Ayrıca, Liste Pano ve Kanal Pano da bulunmaktadır. Daha fazla ayrıntı için lütfen {link} adresine bakın." _postNote: title: "Not Yayınlama Ayarları" description1: "Misskey'de not yayınlarken çeşitli seçenekler mevcuttur. Yayınlama formu şu şekildedir." _visibility: - description: "Notunuzu kimlerin görüntüleyebileceğini sınırlayabilirsiniz." + description: "Notunu kimlerin görüntüleyebileceğini sınırlayabilirsin." public: "Notunuz tüm kullanıcılar tarafından görülebilir olacaktır." - home: "Yalnızca Ana zaman akışında herkese açık. Profilinizi ziyaret edenler, takipçileriniz ve yeniden notlar aracılığıyla bunu görebilirler." + home: "Yalnızca Ana zaman akışında herkese açık. Profilinizi ziyaret edenler, takipçilerin ve yeniden notlar aracılığıyla bunu görebilirler." followers: "Sadece takipçiler tarafından görülebilir. Sadece takipçiler görebilir, başkaları göremez ve başkaları tarafından yeniden not edilemez." direct: "Yalnızca belirli kullanıcılar tarafından görülebilir ve alıcıya bildirim gönderilir. Doğrudan mesajlaşma yerine alternatif olarak kullanılabilir." doNotSendConfidencialOnDirect1: "Hassas bilgileri gönderirken dikkatli olun!" @@ -1601,30 +1603,30 @@ _initialTutorial: localOnly: "Bu bayrakla yayınlamak, notu diğer sunuculara aktarmaz. Diğer sunuculardaki kullanıcılar, yukarıdaki görüntüleme ayarlarından bağımsız olarak bu notları doğrudan görüntüleyemezler." _cw: title: "İçerik Uyarısı" - description: "Gövde yerine, “yorumlar” alanına yazılan içerik görüntülenecektir. “Devamını oku” düğmesine basıldığında gövde görüntülenecektir." + description: "Gövde yerine, “Yorumlar” alanına yazılan içerik görüntülenecek. “Devamını oku” düğmesine basıldığında gövde görüntülenecek." _exampleNote: cw: "Bu kesinlikle sizi acıktıracak!" note: "Az önce çikolata kaplı bir donut yedim 🍩😋" useCases: "Bu, sunucu kurallarına uyulurken, gerekli notlar için veya spoiler veya hassas metinlerin kendi kendine kısıtlanması için kullanılır." _howToMakeAttachmentsSensitive: title: "Ekleri Hassas Olarak İşaretleme" - description: "Sunucu kuralları gereği gerekli olan veya bozulmaması gereken ekler için “hassas” bayrağı ekleyin." - tryThisFile: "Bu forma ekli resmi hassas olarak işaretlemeyi deneyin!" + description: "Sunucu kuralları gereği gerekli olan veya bozulmaması gereken ekler için “hassas” bayrağı ekle." + tryThisFile: "Bu forma ekli resmi hassas olarak işaretlemeyi dene!" _exampleNote: note: "Oops, natto kapağını açarken berbat ettim..." method: "Bir eki hassas olarak işaretlemek için, dosya küçük resmini tıklayın, menüyü açın ve “Hassas Olarak İşaretle” seçeneğini tıklayın." sensitiveSucceeded: "Dosya eklerken, lütfen sunucu kurallarına uygun olarak hassasiyet ayarlarını yapın." - doItToContinue: "Devam etmek için ek dosyayı hassas olarak işaretleyin." + doItToContinue: "Devam etmek için ek dosyayı hassas olarak işaretle." _done: title: "Eğitimi tamamladınız! 🎉" description: "Burada tanıtılan işlevler sadece küçük bir kısmıdır. Misskey'i kullanma konusunda daha ayrıntılı bilgi için lütfen şu kaynağa bakın: {link}." _timelineDescription: - home: "Ana Timeline'da, takip ettiğiniz hesapların notlarını görebilirsiniz." - local: "Yerel Timeline'de, bu sunucudaki tüm kullanıcıların notlarını görebilirsiniz." - social: "Sosyal Timeline, Ana Sayfa ve Yerel Timeline'dan gelen notları görüntüler." - global: "Global Timeline'da, bağlı tüm sunuculardan gelen notları görebilirsiniz." + home: "Ana Pano'da, takip ettiğin hesapların notlarını görebilirsin." + local: "Yerel Pano'da, bu sunucudaki tüm kullanıcıların notlarını görebilirsin." + social: "Sosyal Pano, Ana Sayfa ve Yerel Pano'dan gelen notları görüntüler." + global: "Global Pano'da, bağlı tüm sunuculardan gelen notları görebilirsin." _serverRules: - description: "Kayıt öncesinde gösterilecek bir dizi kural. Hizmet Şartlarının özetini belirlemeniz önerilir." + description: "Kayıt öncesinde gösterilecek bir dizi kural. Hizmet Şartlarının özetini belirlemen önerilir." _serverSettings: iconUrl: "Simge URL'si" appIconDescription: " {host} bir uygulama olarak görüntülendiğinde kullanılacak simgeyi belirtir." @@ -1634,23 +1636,23 @@ _serverSettings: manifestJsonOverride: "manifest.json Geçersiz Kılma" shortName: "Kısa ad" shortNameDescription: "Resmi adın uzun olması durumunda görüntülenebilen, örneğin adının kısaltması." - fanoutTimelineDescription: "Etkinleştirildiğinde Timeline alma performansını büyük ölçüde artırır ve veritabanı yükünü azaltır. Bunun karşılığında Redis'in bellek kullanımı artacaktır. Sunucu belleği düşükse veya sunucu kararsızsa bunu devre dışı bırakmayı düşünün." + fanoutTimelineDescription: "Etkinleştirildiğinde Pano alma performansını büyük ölçüde artırır ve veritabanı yükünü azaltır. Bunun karşılığında Redis'in bellek kullanımı artacaktır. Sunucu belleği düşükse veya sunucu kararsızsa bunu devre dışı bırakmayı düşün." fanoutTimelineDbFallback: "Veritabanına geri dön" - fanoutTimelineDbFallbackDescription: "Etkinleştirildiğinde, Timeline önbelleğe alınmamışsa ek sorgular için veritabanına geri döner. Bu özelliği devre dışı bırakmak, geri dönüş sürecini ortadan kaldırarak sunucu yükünü daha da azaltır, ancak alınabilecek zaman çizelgelerinin aralığını sınırlar." - reactionsBufferingDescription: "Etkinleştirildiğinde, reaksiyon oluşturma sırasında performans büyük ölçüde artacak ve veritabanı üzerindeki yük azalacaktır. Ancak, Redis bellek kullanımı artacaktır." + fanoutTimelineDbFallbackDescription: "Etkinleştirildiğinde, Pano önbelleğe alınmamışsa ek sorgular için veritabanına geri döner. Bu özelliği devre dışı bırakmak, geri dönüş sürecini ortadan kaldırarak sunucu yükünü daha da azaltır, ancak alınabilecek zaman çizelgelerinin aralığını sınırlar." + reactionsBufferingDescription: "Etkinleştirildiğinde, reaksiyon oluşturma sırasında performans büyük ölçüde artacak ve veritabanı üzerindeki yük azalacaktır. Ancak, Redis bellek kullanımı artacakt." remoteNotesCleaning: "Uzak notların otomatik olarak temizlenmesi" - remoteNotesCleaning_description: "Etkinleştirildiğinde, kullanılmayan ve güncelliğini yitirmiş uzak notlar, veritabanının şişmesini önlemek için periyodik olarak temizlenecektir." + remoteNotesCleaning_description: "Etkinleştirildiğinde, kullanılmayan ve güncelliğini yitirmiş uzak notlar, veritabanının şişmesini önlemek için periyodik olarak temizlenecek." remoteNotesCleaningMaxProcessingDuration: "Maksimum temizleme işlem süresi" remoteNotesCleaningExpiryDaysForEachNotes: "Notları saklamak için minimum gün sayısı" inquiryUrl: "Sorgu URL'si" inquiryUrlDescription: "Sorgu formu için sunucu yöneticisine bir URL veya iletişim bilgileri için bir web sayfası belirtin." - openRegistration: "Hesap oluşturmayı açık hale getirin" - openRegistrationWarning: "Kayıt açma işlemi riskler içerir. Sunucuyu sürekli olarak izleyen ve herhangi bir sorun durumunda hemen müdahale edebilen bir sisteminiz varsa, bu işlemi etkinleştirmeniz önerilir." + openRegistration: "Hesap oluşturmayı açık hale getir" + openRegistrationWarning: "Kayıt açma işlemi riskler içerir. Sunucuyu sürekli olarak izleyen ve herhangi bir sorun durumunda hemen müdahale edebilen bir sistemin varsa, bu işlemi etkinleştirmen önerilir." thisSettingWillAutomaticallyOffWhenModeratorsInactive: "Bir süre boyunca moderatör etkinliği algılanmazsa, spam'ı önlemek için bu ayar otomatik olarak kapatılır." deliverSuspendedSoftware: "Askıya Alınan Yazılım" deliverSuspendedSoftwareDescription: "Güvenlik açığı veya diğer nedenlerle sunucunun yazılımının belirli bir isim ve sürüm aralığı için teslimatı durdurabilirsiniz. Bu sürüm bilgileri sunucu tarafından sağlanır ve güvenilirliği garanti edilmez. Sürümü belirtmek için semver aralığı belirtilebilir, ancak >= 2024.3.1 belirtildiğinde 2024.3.1-custom.0 gibi özel sürümler dahil edilmez, bu nedenle >= 2024.3.1-0 gibi ön sürüm belirtimi kullanılması önerilir." singleUserMode: "Tek kullanıcı modu" - singleUserMode_description: "Bu sunucunun tek kullanıcısıysanız, bu modu etkinleştirerek performansını optimize edebilirsiniz." + singleUserMode_description: "Bu sunucunun tek kullanıcısıysanız, bu modu etkinleştirerek performansını optimize edebilirsin." signToActivityPubGet: "ActivityPub GET isteklerini imzalayın" signToActivityPubGet_description: "Normalde bu özellik etkinleştirilmiş olmalıdır. Bu özelliği devre dışı bırakmak federasyonla ilgili sorunları iyileştirebilir, ancak diğer yandan bazı diğer sunuculara yönelik federasyonu devre dışı bırakabilir." proxyRemoteFiles: "Proxy uzak dosyalar" @@ -1660,7 +1662,7 @@ _serverSettings: userGeneratedContentsVisibilityForVisitor: "Kullanıcılar tarafından oluşturulan içeriğin misafirlere görünürlüğü" userGeneratedContentsVisibilityForVisitor_description: "Bu, uygunsuz ve iyi denetlenmemiş uzaktaki içeriğin kendi sunucunuz aracılığıyla istemeden internette yayınlanmasını önlemek için yararlıdır." userGeneratedContentsVisibilityForVisitor_description2: "Sunucu tarafından alınan uzak içerik dahil olmak üzere sunucudaki tüm içeriği koşulsuz olarak İnternet'e yayınlamak risklidir. Bu, içeriğin dağıtılmış yapısından haberdar olmayan misafirler için özellikle önemlidir, çünkü onlar yanlışlıkla uzak içeriğin bile sunucudaki kullanıcılar tarafından oluşturulan içerik olduğunu düşünebilirler." - restartServerSetupWizardConfirm_title: "Sunucu kurulum sihirbazını yeniden başlatmak ister misiniz?" + restartServerSetupWizardConfirm_title: "Sunucu kurulum sihirbazını yeniden başlatmak ister misin?" restartServerSetupWizardConfirm_text: "Bazı mevcut ayarlar sıfırlanacaktır." _userGeneratedContentsVisibilityForVisitor: all: "Her şey halka açıktır." @@ -1674,12 +1676,12 @@ _accountMigration: moveTo: "Bu hesabı başka bir hesaba taşıyın" moveToLabel: "Taşınacak hesap:" moveCannotBeUndone: "Hesap taşıma işlemi geri alınamaz." - moveAccountDescription: "Bu işlem, hesabınızı farklı bir hesaba taşıyacaktır.\n・Bu hesabın takipçileri otomatik olarak yeni hesaba taşınacaktır.\n・Bu hesap, şu anda takip ettiği tüm kullanıcıları takipten çıkaracaktır.\n・Bu hesapta yeni notlar vb. oluşturamayacaksınız.\n\nTakipçilerin taşınması otomatik olarak gerçekleşirken, takip ettiğiniz kullanıcıların listesini taşımak için bazı adımları manuel olarak hazırlamanız gerekir. Bunu yapmak için, ayarlar menüsünden takipçilerinizi dışa aktarın ve daha sonra yeni hesaba içe aktarın. Aynı prosedür, listelerinizin yanı sıra sessize aldığınız ve engellediğiniz kullanıcılar için de geçerlidir.\n\n(Bu açıklama Misskey v13.12.0 ve sonraki sürümler için geçerlidir. Mastodon gibi diğer ActivityPub yazılımları farklı şekilde çalışabilir.)" + moveAccountDescription: "Bu işlem, hesabını farklı bir hesaba taşıyacaktır.\n・Bu hesabın takipçileri otomatik olarak yeni hesaba taşınacak.\n・Bu hesap, şu anda takip ettiği tüm kullanıcıları takipten çıkaracak.\n・Bu hesapta yeni notlar vb. oluşturamayacaksın.\n\nTakipçilerin taşınması otomatik olarak gerçekleşirken, takip ettiğin kullanıcıların listesini taşımak için bazı adımları manuel olarak hazırlaman gerekir. Bunu yapmak için, ayarlar menüsünden takipçilerini dışa aktar ve daha sonra yeni hesaba içe aktar. Aynı prosedür, listelerinin yanı sıra sessize aldığın ve engellediğin kullanıcılar için de geçerli.\n\n(Bu açıklama Misskey v13.12.0 ve sonraki sürümler için geçerlidir. Mastodon gibi diğer ActivityPub yazılımları farklı şekilde çalışabilir.)" moveAccountHowTo: "Geçiş yapmak için, önce taşınacak hesapta bu hesap için bir takma ad oluşturun.\nTakma adı oluşturduktan sonra, taşınacak hesabı aşağıdaki biçimde girin: @username@server.example.com" startMigration: "Taşın" - migrationConfirm: "Bu hesabı {account} hesabına gerçekten taşımak istiyor musunuz? Bu işlem başlatıldıktan sonra durdurulamaz veya geri alınamaz ve bu hesabı artık orijinal haliyle kullanamazsınız." + migrationConfirm: "Bu hesabı {account} hesabına gerçekten taşımak istiyor musun? Bu işlem başlatıldıktan sonra durdurulamaz veya geri alınamaz ve bu hesabı artık orijinal haliyle kullanamazsın." movedAndCannotBeUndone: "\nBu hesap taşınmıştır.\nTaşıma işlemi geri alınamaz." - postMigrationNote: "Bu hesap, geçiş işlemi tamamlandıktan 24 saat sonra şu anda takip ettiği tüm hesapları takipten çıkaracaktır.\nHem takipçi sayısı hem de takip edilenler sayısı sıfır olacaktır. Takipçilerinizin bu hesabın yalnızca takipçilere açık gönderilerini görememesi durumunu önlemek için, takipçileriniz bu hesabı takip etmeye devam edecektir." + postMigrationNote: "Bu hesap, geçiş işlemi tamamlandıktan 24 saat sonra şu anda takip ettiği tüm hesapları takipten çıkaracak.\nHem takipçi sayısı hem de takip edilenler sayısı sıfır olacak. Takipçilerinin bu hesabın yalnızca takipçilere açık gönderilerini görememesi durumunu önlemek için, takipçilerin bu hesabı takip etmeye devam edecek." movedTo: "Yeni hesap:" _achievements: earnedAt: "Şurada açıldı" @@ -1786,7 +1788,7 @@ _achievements: flavor: "Misskey'i kullandığınız için teşekkür ederiz!" _noteClipped1: title: "Kesinlikle... kesmeliyim..." - description: "İlk notunuzu ekleyin" + description: "İlk notunu ekle" _noteFavorited1: title: "Yıldız gözlemcisi" description: "İlk notunu favorilerine ekle" @@ -1795,10 +1797,10 @@ _achievements: description: "Başka birinin notlarınızdan birini favorilerine eklemesini sağlayın" _profileFilled: title: "İyi hazırlanmış" - description: "Profilinizi oluşturun" + description: "Profilini oluştur" _markedAsCat: title: "Ben bir kediyim." - description: "Hesabınızı kedi olarak işaretleyin" + description: "Hesabını kedi olarak işaretle" flavor: "Sana daha sonra bir isim vereceğim." _following1: title: "İlk kullanıcınızı takip edin" @@ -1845,7 +1847,7 @@ _achievements: _iLoveMisskey: title: "Misskey'i seviyorum" description: "“I ❤ #Misskey” yazısını paylaş" - flavor: "Misskey geliştirme ekibi desteğiniz için çok teşekkür eder!" + flavor: "Misskey geliştirme ekibi desteğin için çok teşekkür eder!" _foundTreasure: title: "Hazine Avı" description: "Gizli hazineyi buldunuz." @@ -1874,7 +1876,7 @@ _achievements: description: "Ev zaman çizelgenizin hızı 20 npm'yi (dakika başına not sayısı) aşıyor mu?" _viewInstanceChart: title: "Analist" - description: "Sunucunuzun grafiklerini görüntüleyin" + description: "Sunucunun grafiklerini görüntüle" _outputHelloWorldOnScratchpad: title: "Merhaba, dünya!" description: "Scratchpad'de “hello world” yazdırın." @@ -1883,9 +1885,9 @@ _achievements: description: "Aynı anda en az 3 pencere açık olsun." _driveFolderCircularReference: title: "Döngüsel Referans" - description: "Drive'da yinelemeli olarak iç içe geçmiş bir klasör oluşturmaya çalışın." + description: "Drive'da yinelemeli olarak iç içe geçmiş bir klasör oluşturmaya çalış." _reactWithoutRead: - title: "Gerçekten okudun mu?" + title: "Cidden okudun mu?" description: "100 karakterden uzun bir notun yayınlanmasından itibaren 3 saniye içinde yanıt verin." _clickedClickHere: title: "Buraya tıklayın" @@ -1911,7 +1913,7 @@ _achievements: _loggedInOnNewYearsDay: title: "Yeni yılınız kutlu olsun!" description: "Yılın ilk gününde oturum açıldı" - flavor: "Bu örnekte bir başka harika yıla" + flavor: "Bu sunucuda bir başka harika yıla" _cookieClicked: title: "Çerezleri tıklayarak oynanan bir oyun" description: "Çerezi tıkladı" @@ -1922,7 +1924,7 @@ _achievements: flavor: "Misskey-Misskey La-Tu-Ma" _smashTestNotificationButton: title: "Test taşması" - description: "Bildirim testini çok kısa bir süre içinde tekrar tekrar tetikleyin." + description: "Bildirim testini çok kısa bir süre içinde tekrar tekrar tetikle." _tutorialCompleted: title: "Misskey Temel Kurs Diploması" description: "Eğitim tamamlandı" @@ -1932,7 +1934,7 @@ _achievements: _bubbleGameDoubleExplodingHead: title: "Çift🤯" description: "Aynı anda balon oyunundaki en büyük iki nesne" - flavor: "Öğle yemeği kutunuzu şöyle doldurabilirsiniz 🤯 🤯 biraz." + flavor: "Öğle yemeği kutunu şöyle doldurabilirsin 🤯 🤯 biraz." _role: new: "Yeni rol" edit: "Rolü düzenle" @@ -1949,21 +1951,21 @@ _role: condition: "Durum" isConditionalRole: "Bu, koşullu bir roldür." isPublic: "Kamu rolü" - descriptionOfIsPublic: "Bu rol, atanan kullanıcıların profillerinde görüntülenecektir." + descriptionOfIsPublic: "Bu rol, atanan kullanıcıların profillerinde görüntülenecek." options: "Seçenekler" policies: "Politikalar" baseRole: "Rol şablonu" useBaseValue: "Rol şablonu değerini kullan" - chooseRoleToAssign: "Atamak istediğiniz rolü seçin" + chooseRoleToAssign: "Atamak istediğin rolü seç" iconUrl: "Simge URL'si" asBadge: "Rozet olarak göster" descriptionOfAsBadge: "This role's icon will be displayed next to the username of users with this role if turned on." - isExplorable: "Rolü keşfedilebilir hale getirin" - descriptionOfIsExplorable: "Bu rolün zaman çizelgesi ve bu role sahip kullanıcıların listesi, etkinleştirilirse kamuya açık hale getirilecektir." + isExplorable: "Rolü keşfedilebilir hale getir" + descriptionOfIsExplorable: "Bu rolün zaman çizelgesi ve bu role sahip kullanıcıların listesi, etkinleştirilirse kamuya açık hale getirilecek." displayOrder: "Pozisyon" descriptionOfDisplayOrder: "Sayı ne kadar yüksekse, UI pozisyonu da o kadar yüksek olur." preserveAssignmentOnMoveAccount: "Geçiş sırasında rol atamalarını koruyun" - preserveAssignmentOnMoveAccount_description: "Etkinleştirildiğinde, bu rol, bu role sahip bir hesap taşındığında hedef hesaba aktarılacaktır." + preserveAssignmentOnMoveAccount_description: "Etkinleştirildiğinde, bu rol, bu role sahip bir hesap taşındığında hedef hesaba aktarılacak." canEditMembersByModerator: "Moderatörlerin bu rol için üye listesini düzenlemesine izin ver" descriptionOfCanEditMembersByModerator: "Etkinleştirildiğinde, moderatörler ve yöneticiler bu role kullanıcıları atayabilir ve atamalarını kaldırabilir. Devre dışı bırakıldığında, yalnızca yöneticiler kullanıcıları atayabilir." priority: "Öncelik" @@ -1972,34 +1974,35 @@ _role: middle: "Orta" high: "Yüksek" _options: - gtlAvailable: "Küresel zaman çizelgesini görüntüleyebilir" + gtlAvailable: "Global Pano'yu görüntüleyebilir" ltlAvailable: "Yerel zaman çizelgesini görüntüleyebilir" canPublicNote: "Halka açık notlar gönderebilir" mentionMax: "Bir notta maksimum bahsetme sayısı" - canInvite: "Örnek davet kodları oluşturabilir" + canInvite: "Sunucu davet kodları oluşturabilir" inviteLimit: "Davet sınırı" inviteLimitCycle: "Davet sınırı bekleme süresi" inviteExpirationTime: "Davet süresi dolma aralığı" canManageCustomEmojis: "Özel emojileri yönetebilir" - canManageAvatarDecorations: "Avatar süslemelerini yönet" - driveCapacity: "Sürücü kapasitesi" - maxFileSize: "Yükleyebileceğiniz maksimum dosya boyutu" - alwaysMarkNsfw: "Dosyaları her zaman NSFW olarak işaretleyin" + canManageAvatarDecorations: "Avatar süslerini yönet" + driveCapacity: "Drive kapasitesi" + maxFileSize: "Yükleyebileceğin maksimum dosya boyutu" + alwaysMarkNsfw: "Dosyaları her zaman NSFW olarak işaretle" canUpdateBioMedia: "Bir simge veya banner görüntüsünü düzenleyebilir" pinMax: "Sabitlenmiş notların maksimum sayısı" antennaMax: "Maksimum anten sayısı" wordMuteMax: "Kelime sessizlerinde izin verilen maksimum karakter sayısı" webhookMax: "Maksimum Webhook sayısı" clipMax: "Maksimum klip sayısı" - noteEachClipsMax: "Bir klip içindeki maksimum nota sayısı" + noteEachClipsMax: "Bir klip içindeki maksimum not sayısı" userListMax: "Maksimum kullanıcı listesi sayısı" userEachUserListsMax: "Kullanıcı listesindeki maksimum kullanıcı sayısı" rateLimitFactor: "Hız Sınırı" descriptionOfRateLimitFactor: "Daha düşük oran sınırları daha az kısıtlayıcıdır, daha yüksek olanlar ise daha kısıtlayıcıdır." canHideAds: "Reklamları gizleyebilir" canSearchNotes: "Not arama kullanımı" + canSearchUsers: "Kullanıcı arama" canUseTranslator: "Çevirmen kullanımı" - avatarDecorationLimit: "Uygulanabilecek maksimum avatar süsleme sayısı" + avatarDecorationLimit: "Maksimum avatar süsü sayısı" canImportAntennas: "Antenlerin içe aktarılmasına izin ver" canImportBlocking: "Engellemeyi içe aktarmaya izin ver" canImportFollowing: "Aşağıdakilerin içe aktarılmasına izin ver" @@ -2008,7 +2011,7 @@ _role: chatAvailability: "Sohbeti İzin Ver" uploadableFileTypes: "Yüklenebilir dosya türleri" uploadableFileTypes_caption: "İzin verilen MIME/dosya türlerini belirtir. Birden fazla MIME türü, yeni bir satırla ayırarak belirtilebilir ve joker karakterler yıldız işareti (*) ile belirtilebilir. (örneğin, image/*)" - uploadableFileTypes_caption2: "Bazı dosya türleri algılanamayabilir. Bu tür dosyalara izin vermek için, spesifikasyona {x} ekleyin." + uploadableFileTypes_caption2: "Bazı dosya türleri algılanamayabilir. Bu tür dosyalara izin vermek için, spesifikasyona {x} ekle." noteDraftLimit: "Sunucu notlarının olası taslak sayısı" watermarkAvailable: "Filigran işlevinin kullanılabilirliği" _condition: @@ -2052,12 +2055,12 @@ _ffVisibility: private: "Özel" _signup: almostThere: "Neredeyse vardık" - emailAddressInfo: "Lütfen E-Posta adresinizi girin. Bu adres kamuya açık hale getirilmeyecektir." - emailSent: "Onay e-postası E-Posta adresinize ({email}) gönderilmiştir. Hesap oluşturma işlemini tamamlamak için e-postadaki bağlantıya tıklayın." + emailAddressInfo: "Lütfen E-Posta adresini gir. Bu adres kamuya açık hale getirilmeyecek." + emailSent: "Onay e-postası E-Posta adresine ({email}) gönderilmiştir. Hesap oluşturma işlemini tamamlamak için e-postadaki bağlantıya tıkla." _accountDelete: accountDelete: "Hesabı sil" - mayTakeTime: "Hesap silme işlemi kaynak yoğun bir işlem olduğundan, oluşturduğunuz içerik miktarına ve yüklediğiniz dosya sayısına bağlı olarak tamamlanması biraz zaman alabilir." - sendEmail: "Hesap silme işlemi tamamlandıktan sonra, bu hesaba kayıtlı E-Posta adresine bir e-posta gönderilecektir." + mayTakeTime: "Hesap silme işlemi kaynak yoğun bir işlem olduğundan, oluşturduğun içerik miktarına ve yüklediğin dosya sayısına bağlı olarak tamamlanması biraz zaman alabilir." + sendEmail: "Hesap silme işlemi tamamlandıktan sonra, bu hesaba kayıtlı E-Posta adresine bir e-posta gönderilecek." requestAccountDelete: "Hesap silme talebi" started: "Silme işlemi başlatıldı." inProgress: "Silme işlemi şu anda devam ediyor." @@ -2071,8 +2074,8 @@ _ad: setZeroToDisable: "Bu değeri 0 olarak ayarlayarak gerçek zamanlı güncelleme reklamlarını devre dışı bırakın." adsTooClose: "Mevcut reklam aralığı çok düşük olduğu için kullanıcı deneyimini önemli ölçüde kötüleştirebilir." _forgotPassword: - enterEmail: "Kayıt olurken kullandığınız E-Posta adresini girin. Şifrenizi sıfırlayabileceğiniz bir bağlantı bu adrese gönderilecektir." - ifNoEmail: "Kayıt sırasında E-Posta kullanmadıysanız, lütfen bunun yerine örnek yöneticisiyle iletişime geçin." + enterEmail: "Kayıt olurken kullandığın E-Posta adresini gir. Şifreni sıfırlayabileceğin bir bağlantı bu adrese gönderilecek." + ifNoEmail: "Kayıt sırasında E-Posta kullanmadıysanız, lütfen bunun yerine sunucu yöneticisiyle iletişime geçin." contactAdmin: "This instance does not support using email addresses, please contact the instance administrator to reset your password instead." _gallery: my: "Benim Galerim" @@ -2086,7 +2089,7 @@ _email: title: "Bir takip isteği aldınız." _plugin: install: "Eklentileri yükle takip isteği aldınız" - installWarn: "Güvenilir olmayan eklentileri yüklemeyiniz." + installWarn: "Güvenilir olmayan eklentileri yükleme." manage: "Eklentileri yönet" viewSource: "Kaynak görüntüle" viewLog: "Günlüğü göster" @@ -2099,11 +2102,11 @@ _preferencesBackups: inputName: "Lütfen bu yedekleme için bir ad girin." cannotSave: "Kaydetme başarısız oldu" nameAlreadyExists: "“{name}” adlı bir yedekleme zaten mevcut. Lütfen farklı bir ad girin." - applyConfirm: "Bu cihaza “{name}” yedeklemesini gerçekten uygulamak istiyor musunuz? Bu cihazın mevcut ayarları üzerine yazılacaktır." + applyConfirm: "Bu cihaza “{name}” yedeklemesini cidden uygulamak istiyor musun? Bu cihazın mevcut ayarları üzerine yazılacaktır." saveConfirm: "Yedeklemeyi {name} olarak kaydedin?" - deleteConfirm: "{name} yedeklemesini silmek ister misiniz?" - renameConfirm: "Bu yedeğin adını “{old}” den “{new}” ye değiştirmek ister misiniz?" - noBackups: "Yedekleme mevcut değildir. “Yeni yedekleme oluştur” seçeneğini kullanarak bu sunucudaki istemci ayarlarınızı yedekleyebilirsiniz." + deleteConfirm: "{name} yedeklemesini silmek ister misin?" + renameConfirm: "Bu yedeğin adını “{old}” den “{new}” ye değiştirmek ister misin?" + noBackups: "Yedekleme mevcut değil. “Yeni yedekleme oluştur” seçeneğini kullanarak bu sunucudaki istemci ayarlarınızı yedekleyebilirsin." createdAt: "Oluşturulma tarihi: {date} {time}" updatedAt: "Güncelleme tarihi: {date} {time}" cannotLoad: "Yükleme başarısız" @@ -2167,7 +2170,7 @@ _instanceMute: heading: "Sessize alınacak sunucuların listesi" _theme: explore: "Temaları Keşfedin" - install: "Bir tema yükleyin" + install: "Bir tema yükle" manage: "Temaları yönet" code: "Tema kodu" copyThemeCode: "Tema kodunu kopyala" @@ -2176,7 +2179,7 @@ _theme: installedThemes: "Yüklü temalar" builtinThemes: "Yerleşik temalar" instanceTheme: "Sunucu teması" - alreadyInstalled: "Bu tema zaten yüklenmiştir." + alreadyInstalled: "Bu tema zaten yüklenmiş." invalid: "Bu temanın biçimi geçersizdir." make: "Bir tema oluşturun" base: "Base" @@ -2195,8 +2198,8 @@ _theme: darken: "Koyulaştır" lighten: "Hafiflet" inputConstantName: "Bu sabit için bir ad girin" - importInfo: "Buraya tema kodunu girerseniz, onu tema düzenleyicisine aktarabilirsiniz." - deleteConstantConfirm: "{const} sabitini gerçekten silmek istiyor musunuz?" + importInfo: "Buraya tema kodunu girersen, onu tema düzenleyicisine aktarabilirsin." + deleteConstantConfirm: "{const} sabitini cidden silmek istiyor musun?" keys: accent: "Aksan" bg: "Arka plan" @@ -2245,18 +2248,18 @@ _soundSettings: driveFileTypeWarnDescription: "Bir ses dosyası seçin" driveFileDurationWarn: "Ses kaydı çok uzun." driveFileDurationWarnDescription: "Uzun sesli mesajlar Misskey'in kullanımını engelleyebilir. Devam etmek istiyor musunuz?" - driveFileError: "Ses yüklenemedi. Lütfen ayarları değiştirin." + driveFileError: "Ses yüklenemedi. Lütfen ayarları değiştir." _ago: - future: "Gelecekte" - justNow: "Şu anda" - secondsAgo: "{n} saniye önce" - minutesAgo: "{n} dakika önce" - hoursAgo: "{n} saat önce" - daysAgo: "{n} gün önce" - weeksAgo: "{n} hafta önce" - monthsAgo: "{n} ay önce" - yearsAgo: "{n} yıl önce" - invalid: "Yok" + future: "Gelecek" + justNow: "Şimdi" + secondsAgo: "{n} sn" + minutesAgo: "{n} dk" + hoursAgo: "{n} sa" + daysAgo: "{n} gün" + weeksAgo: "{n} hafta" + monthsAgo: "{n} ay" + yearsAgo: "{n} yıl" + invalid: "Geçersiz" _timeIn: seconds: "{n} saniye içinde" minutes: "{n} dakika içinde" @@ -2271,7 +2274,7 @@ _time: hour: "Saat(ler)" day: "Gün(ler)" _2fa: - alreadyRegistered: "2 faktörlü kimlik doğrulama cihazını zaten kaydettiniz." + alreadyRegistered: "2fa kimlik doğrulama cihazını zaten kaydettin." registerTOTP: "Kimlik doğrulama uygulamasını kaydet" step1: "Öncelikle, cihazınıza bir kimlik doğrulama uygulaması (örneğin {a} veya {b}) yükleyin." step2: "Ardından, bu ekranda görüntülenen QR kodunu tarayın." @@ -2279,15 +2282,15 @@ _2fa: step3Title: "Doğrulama kodunu girin" step3: "Uygulamanız tarafından sağlanan kimlik doğrulama kodunu (token) girerek kurulumu tamamlayın." setupCompleted: "Kurulum tamamlandı" - step4: "Bundan sonra, gelecekteki tüm oturum açma girişimlerinde bu tür bir oturum açma jetonu istenecektir." + step4: "Bundan sonra, gelecekteki tüm oturum açma girişimlerinde bu tür bir oturum açma jetonu istenecek." securityKeyNotSupported: "Tarayıcınız güvenlik anahtarlarını desteklemiyor." registerTOTPBeforeKey: "Güvenlik veya geçiş anahtarını kaydetmek için bir kimlik doğrulama uygulaması kurun." - securityKeyInfo: "Parmak izi veya PIN kimlik doğrulamasının yanı sıra, hesabınızı daha da güvenli hale getirmek için FIDO2'yi destekleyen donanım güvenlik anahtarları aracılığıyla kimlik doğrulama da ayarlayabilirsiniz." + securityKeyInfo: "Parmak izi veya PIN kimlik doğrulamasının yanı sıra, hesabını daha da güvenli hale getirmek için FIDO2'yi destekleyen donanım güvenlik anahtarları aracılığıyla kimlik doğrulama da ayarlayabilirsin." registerSecurityKey: "Güvenlik veya geçiş anahtarını kaydedin" securityKeyName: "Bir anahtar adı girin" tapSecurityKey: "Güvenlik veya geçiş anahtarını kaydetmek için lütfen tarayıcınızı takip edin." removeKey: "Güvenlik anahtarını kaldır" - removeKeyConfirm: "{name} anahtarını gerçekten silmek istiyor musunuz?" + removeKeyConfirm: "{name} anahtarını cidden silmek istiyor musun?" whyTOTPOnlyRenew: "Güvenlik anahtarı kayıtlı olduğu sürece kimlik doğrulama uygulaması kaldırılamaz." renewTOTP: "Kimlik doğrulama uygulamasını yeniden yapılandırın" renewTOTPConfirm: "Bu, önceki uygulamanızdaki doğrulama kodlarının çalışmamasına neden olacaktır." @@ -2295,43 +2298,43 @@ _2fa: renewTOTPCancel: "İptal" checkBackupCodesBeforeCloseThisWizard: "Bu pencereyi kapatmadan önce, lütfen aşağıdaki yedek kodları not edin." backupCodes: "Yedek kodlar" - backupCodesDescription: "İki faktörlü kimlik doğrulama uygulamasını kullanamaz hale gelmeniz durumunda, bu kodları kullanarak hesabınıza erişebilirsiniz. Her kod yalnızca bir kez kullanılabilir. Lütfen bu kodları güvenli bir yerde saklayın." + backupCodesDescription: "İki faktörlü kimlik doğrulama uygulamasını kullanamaz hale gelmen durumunda, bu kodları kullanarak hesabınıza erişebilirsin. Her kod yalnızca bir kez kullanılabilir. Lütfen bu kodları güvenli bir yerde sakla." backupCodeUsedWarning: "Yedek kod kullanıldı. Artık kullanamıyorsanız, lütfen iki faktörlü kimlik doğrulamayı mümkün olan en kısa sürede yeniden yapılandırın." - backupCodesExhaustedWarning: "Tüm yedek kodlar kullanıldı. İki faktörlü kimlik doğrulama uygulamanıza erişiminizi kaybederseniz, bu hesaba erişemezsiniz. Lütfen iki faktörlü kimlik doğrulamayı yeniden yapılandırın." + backupCodesExhaustedWarning: "Tüm yedek kodlar kullanıldı. İki faktörlü kimlik doğrulama uygulamana erişimini kaybedersen, bu hesaba erişemezsin. Lütfen iki faktörlü kimlik doğrulamayı yeniden yapılandır." moreDetailedGuideHere: "İşte ayrıntılı kılavuz" _permissions: - "read:account": "Hesap bilgilerinizi görüntüleyin" - "write:account": "Hesap bilgilerinizi düzenleyin" - "read:blocks": "Engellenen kullanıcıların listesini görüntüleyin" - "write:blocks": "Engellenen kullanıcılar listenizi düzenleyin" - "read:drive": "Drive dosyalarınıza ve klasörlerinize erişin" - "write:drive": "Drive dosyalarınızı ve klasörlerinizi düzenleyin veya silin" - "read:favorites": "Favoriler listenizi görüntüleyin" - "write:favorites": "Favoriler listenizi düzenleyin" - "read:following": "Takip ettiğiniz kişilerle ilgili bilgileri görüntüleyin" + "read:account": "Hesap bilgilerini gör" + "write:account": "Hesap bilgilerini düzenle" + "read:blocks": "Engellenen kullanıcıların listesini görüntüle" + "write:blocks": "Engellenen kullanıcılar listeni düzenle" + "read:drive": "Drive dosyalarına ve klasörlerine eriş" + "write:drive": "Drive dosyalarını ve klasörlerini düzenle veya sil" + "read:favorites": "Favoriler listeni görüntüle" + "write:favorites": "Favoriler listeni düzenle" + "read:following": "Takip ettiğin kişilerle ilgili bilgileri görüntüle" "write:following": "Diğer hesapları takip et veya takipten çıkar" - "read:messaging": "Sohbetlerinizi görüntüleyin" + "read:messaging": "Sohbetlerini görüntüle" "write:messaging": "Sohbet mesajlarını oluşturun veya silin" - "read:mutes": "Sessize alınan kullanıcıların listesini görüntüleyin" - "write:mutes": "Sessize alınan kullanıcıların listesini düzenleyin" + "read:mutes": "Sessize alınan kullanıcıların listesini görüntüle" + "write:mutes": "Sessize alınan kullanıcıların listesini düzenle" "write:notes": "Notlar oluşturun veya silin" - "read:notifications": "Bildirimlerinizi görüntüleyin" - "write:notifications": "Bildirimlerinizi yönetin" - "read:reactions": "Tepkilerinizi görüntüleyin" - "write:reactions": "Tepkilerinizi düzenleyin" + "read:notifications": "Bildirimlerini görüntüle" + "write:notifications": "Bildirimlerini yönet" + "read:reactions": "Tepkilerini görüntüle" + "write:reactions": "Tepkilerini düzenle" "write:votes": "Ankete oy verin" - "read:pages": "Sayfalarınızı görüntüleyin" - "write:pages": "Sayfalarınızı düzenleyin veya silin" + "read:pages": "Sayfalarını görüntüle" + "write:pages": "Sayfalarını düzenle veya sil" "read:page-likes": "Beğenilen sayfaların listesini görüntüle" "write:page-likes": "Beğenilen sayfaların listesini düzenle" - "read:user-groups": "Kullanıcı gruplarınızı görüntüleyin" - "write:user-groups": "Kullanıcı gruplarınızı düzenleyin veya silin" - "read:channels": "Kanallarınızı görüntüleyin" - "write:channels": "Kanallarınızı düzenleyin" + "read:user-groups": "Kullanıcı gruplarını görüntüle" + "write:user-groups": "Kullanıcı gruplarını düzenle veya sil" + "read:channels": "Kanallarını görüntüle" + "write:channels": "Kanallarını düzenle" "read:gallery": "Galeriyi görüntüle" "write:gallery": "Galeri düzenle" - "read:gallery-likes": "Beğendiğiniz galeri gönderilerinin listesini görüntüleyin" - "write:gallery-likes": "Beğendiğiniz galeri gönderilerinin listesini düzenleyin" + "read:gallery-likes": "Beğendiğin galeri gönderilerinin listesini görüntüle" + "write:gallery-likes": "Beğendiğin galeri gönderilerinin listesini düzenle" "read:flash": "Oynat" "write:flash": "Oyunları Düzenle" "read:flash-likes": "Beğenilen Oyunların listesini görüntüle" @@ -2341,7 +2344,7 @@ _permissions: "write:admin:delete-all-files-of-a-user": "Bir kullanıcının tüm dosyalarını sil" "read:admin:index-stats": "Veritabanı dizin istatistiklerini görüntüle" "read:admin:table-stats": "Veritabanı tablosu istatistiklerini görüntüle" - "read:admin:user-ips": "Kullanıcı IP adreslerini görüntüleyin" + "read:admin:user-ips": "Kullanıcı IP adreslerini görüntüle" "read:admin:meta": "Sunucu meta verilerini görüntüle" "write:admin:reset-password": "Kullanıcı şifresini sıfırla" "write:admin:resolve-abuse-user-report": "Kullanıcı raporunu çözme" @@ -2363,8 +2366,8 @@ _permissions: "read:admin:invite-codes": "Davet kodlarını görüntüle" "write:admin:announcements": "Duyuruları yönet" "read:admin:announcements": "Duyuruları görüntüle" - "write:admin:avatar-decorations": "Avatar süslemelerini yönetebilir" - "read:admin:avatar-decorations": "Avatar süslemelerini görüntüle" + "write:admin:avatar-decorations": "Avatar süslerini yönetebilir" + "read:admin:avatar-decorations": "Avatar süslerini görüntüle" "write:admin:federation": "Federasyon verilerini yönetme" "write:admin:account": "Kullanıcı hesabını yönet" "read:admin:account": "Kullanıcı hesabını görüntüle" @@ -2373,8 +2376,8 @@ _permissions: "write:admin:queue": "İş kuyruğunu yönet" "read:admin:queue": "İş kuyruğu bilgilerini görüntüle" "write:admin:promo": "Promosyon notlarını yönet" - "write:admin:drive": "Kullanıcı sürücüsünü yönet" - "read:admin:drive": "Kullanıcı sürücü bilgilerini görüntüle" + "write:admin:drive": "Kullanıcı Drive'ını yönet" + "read:admin:drive": "Kullanıcı Drive bilgilerini görüntüle" "read:admin:stream": "Yönetici için WebSocket API'sını kullanın" "write:admin:ad": "Reklamları yönet" "read:admin:ad": "Reklamları görüntüle" @@ -2389,7 +2392,7 @@ _permissions: _auth: shareAccessTitle: "Uygulama izinlerinin verilmesi" shareAccess: "“{name}”nin bu hesaba erişmesine izin vermek ister misiniz?" - shareAccessAsk: "Bu uygulamanın hesabınıza erişmesine izin vermek istediğinizden emin misiniz?" + shareAccessAsk: "Bu uygulamanın hesabınıza erişmesine izin vermek istediğinden emin misin?" permission: "{name} aşağıdaki izinleri talep etmektedir." permissionAsk: "Bu uygulama aşağıdaki izinleri talep etmektedir" pleaseGoBack: "Lütfen uygulamaya geri dönün." @@ -2398,7 +2401,7 @@ _auth: denied: "Erişim reddedildi" scopeUser: "Aşağıdaki kullanıcı olarak çalıştırın" pleaseLogin: "Uygulamaları yetkilendirmek için lütfen giriş yapın." - byClickingYouWillBeRedirectedToThisUrl: "Erişim izni verildiğinde, otomatik olarak aşağıdaki URL'ye yönlendirileceksiniz." + byClickingYouWillBeRedirectedToThisUrl: "Erişim izni verildiğinde, otomatik olarak aşağıdaki URL'ye yönlendirileceksin." _antennaSources: all: "Tüm notlar" homeTimeline: "Takip edilen kullanıcıların notları" @@ -2418,7 +2421,7 @@ _widgets: instanceInfo: "Sunucu Bilgisi" memo: "Yapışkan notlar" notifications: "Bildirimler" - timeline: "Timeline" + timeline: "Pano" calendar: "Takvim" trends: "Trend olan" clock: "Saat" @@ -2453,7 +2456,7 @@ _cw: _poll: noOnlyOneChoice: "En az iki seçenek gereklidir." choiceN: "Seçim {n}" - noMore: "Daha fazla seçenek ekleyemezsiniz." + noMore: "Daha fazla seçenek ekleyemezsin." canMultipleVote: "Birden fazla seçenek seçilmesine izin ver" expiration: "Anketi sonlandır" infinite: "Asla" @@ -2478,20 +2481,20 @@ _visibility: home: "Ana sayfa" homeDescription: "Yalnızca ana zaman çizelgesine gönder" followers: "Takipçiler" - followersDescription: "Sadece takipçilerinize görünür hale getirin" + followersDescription: "Sadece takipçilerine görünür hale getir" specified: "Doğrudan" specifiedDescription: "Yalnızca belirli kullanıcılar için görünür hale getir" disableFederation: "Federasyon olmadan" disableFederationDescription: "Diğer sunuculara aktarma" _postForm: - quitInspiteOfThereAreUnuploadedFilesConfirm: "Yüklenmemiş dosyalar var, bunları silip formu kapatmak ister misiniz?" - uploaderTip: "Dosya henüz yüklenmemiştir. Dosya menüsünden dosyayı yeniden adlandırabilir, görüntüleri kırpabilir, filigran ekleyebilir ve dosyayı sıkıştırabilir veya sıkıştırmayı kaldırabilirsiniz. Notu yayınladığınızda dosyalar otomatik olarak yüklenir." + quitInspiteOfThereAreUnuploadedFilesConfirm: "Yüklenmemiş dosyalar var, bunları silip formu kapatmak ister misin?" + uploaderTip: "Dosya henüz yüklenmemiş. Dosya menüsünden dosyayı yeniden adlandırabilir, görüntüleri kırpabilir, filigran ekleyebilir ve dosyayı sıkıştırabilir veya sıkıştırmayı kaldırabilirsin. Notu yayınladığında dosyalar otomatik olarak yüklenir." replyPlaceholder: "Bu notu yanıtla..." quotePlaceholder: "Bu notu alıntı yap..." channelPlaceholder: "Bir kanala gönder..." _placeholders: a: "Ne yapıyorsun?" - b: "Çevrenizde neler oluyor?" + b: "Çevrende neler oluyor?" c: "Aklında ne var?" d: "Ne söylemek istiyorsun?" e: "Yazmaya başlayın..." @@ -2503,16 +2506,16 @@ _profile: youCanIncludeHashtags: "Biyografinize hashtag'ler de ekleyebilirsiniz." metadata: "Ek Bilgiler" metadataEdit: "Ek bilgileri düzenle" - metadataDescription: "Bunları kullanarak profilinizde ek bilgi alanları görüntüleyebilirsiniz." + metadataDescription: "Bunları kullanarak profilinde ek bilgi alanları görüntüleyebilirsin." metadataLabel: "Etiket" metadataContent: "İçerik" - changeAvatar: "Avatarı değiştir" - changeBanner: "Change banner" - verifiedLinkDescription: "Buraya profilinize bağlantı içeren bir URL girerek, alanın yanında bir sahiplik doğrulama simgesi görüntülenebilir." - avatarDecorationMax: "En fazla {max} dekorasyon ekleyebilirsiniz." + changeAvatar: "Avatar değiştir" + changeBanner: "Banner değiştir" + verifiedLinkDescription: "Buraya profiline bağlantı içeren bir URL girerek, alanın yanında bir sahiplik doğrulama simgesi görüntülenebilir." + avatarDecorationMax: "En fazla {max} süs ekleyebilirsin." followedMessage: "Takip edildiğinizde gönderilen mesaj" - followedMessageDescription: "Aboneleriniz sizi takip ettiklerinde görüntülenmesini istediğiniz kısa bir mesaj ayarlayabilirsiniz." - followedMessageDescriptionForLockedAccount: "Takip isteklerinin onay gerektirdiğini ayarladıysanız, bir takip isteğini kabul ettiğinizde bu mesaj görüntülenir." + followedMessageDescription: "Abonelerin seni takip ettiklerinde görüntülenmesini istediğin kısa bir mesaj ayarlayabilirsin." + followedMessageDescriptionForLockedAccount: "Takip isteklerinin onay gerektirmesini ayarladıysan, bir takip isteğini kabul ettiğinde bu mesaj görüntülenir." _exportOrImport: allNotes: "Tüm notlar" favoritedNotes: "Favori notlar" @@ -2541,20 +2544,20 @@ _charts: _instanceCharts: requests: "Talepler" users: "Kullanıcı sayısındaki fark" - usersTotal: "Kümülatif kullanıcı sayısı" + usersTotal: "Toplam kullanıcı sayısı" notes: "Not sayısındaki fark" - notesTotal: "Kümülatif not sayısı" - ff: "Takip edilen kullanıcı sayısı / takipçi sayısı farkı" - ffTotal: "Takip edilen kullanıcıların / takipçilerin toplam sayısı" + notesTotal: "Toplam not sayısı" + ff: "Takip / Takipçi sayısı farkı" + ffTotal: "Takip / Takipçi toplam sayısı" cacheSize: "Önbellek boyutundaki fark" - cacheSizeTotal: "Kümülatif önbellek boyutu" + cacheSizeTotal: "Önbelleğin toplam boyutu" files: "Dosya sayısındaki fark" filesTotal: "Toplam dosya sayısı" _timelines: home: "Ana Sayfa" local: "Yerel" social: "Sosyal" - global: "Küresel" + global: "Global" _play: new: "Oyun Oluştur" edit: "Düzenle Oynat" @@ -2570,18 +2573,18 @@ _play: title: "Başlık" script: "Senaryo" summary: "Açıklama" - visibilityDescription: "Özel olarak ayarlamak, profilinizde görünmeyeceği anlamına gelir, ancak URL'ye sahip olan herkes yine de erişebilir." + visibilityDescription: "Özel olarak ayarlamak, profilinde görünmeyeceği anlamına gelir, ancak URL'ye sahip olan herkes yine de erişebilir." _pages: newPage: "Yeni bir Sayfa oluşturun" editPage: "Bu sayfayı düzenle" readPage: "Bu Sayfanın Kaynağını Görüntüleme" pageSetting: "Sayfa ayarları" nameAlreadyExists: "Belirtilen Sayfa URL'si zaten mevcut." - invalidNameTitle: "Belirtilen Sayfa URL'si geçersiz" + invalidNameTitle: "Belirtilen Sayfa URL geçersiz" invalidNameText: "Sayfa başlığının boş olmadığından emin olun." editThisPage: "Bu sayfayı düzenle" viewSource: "Kaynak görüntüle" - viewPage: "Sayfalarınızı görüntüleyin" + viewPage: "Sayfalarını görüntüle" like: "Beğen" unlike: "Benzerlerini kaldır" my: "Benzerlerini kaldır" @@ -2618,7 +2621,7 @@ _pages: note: "Gömülü not" _note: id: "Not Kimliği" - idDescription: "Alternatif olarak notun URL'sini buraya yapıştırabilirsiniz." + idDescription: "Alternatif olarak notun URL buraya yapıştırabilirsin." detailed: "Ayrıntılı görünüm" _relayStatus: requesting: "Beklemede" @@ -2632,12 +2635,12 @@ _notification: youRenoted: "{name}'den Renote" youWereFollowed: "seni takip etti" youReceivedFollowRequest: "Bir takip isteği aldınız." - yourFollowRequestAccepted: "Takip isteğiniz kabul edildi." + yourFollowRequestAccepted: "Takip isteğin kabul edildi." pollEnded: "Anket sonuçları açıklandı." newNote: "Yeni not" unreadAntennaNote: "{name} anteni" roleAssigned: "Verilen rol" - chatRoomInvitationReceived: "Sohbet odasına davet edildiniz." + chatRoomInvitationReceived: "Sohbet odasına davet edildin." emptyPushNotificationMessage: "Push bildirimleri güncellendi" achievementEarned: "Achievement unlocked" testNotification: "Test bildirimi" @@ -2650,7 +2653,7 @@ _notification: followedBySomeUsers: "{n} kullanıcı tarafından takip ediliyor" flushNotification: "Bildirimleri temizle" exportOfXCompleted: "{x} ihracatı tamamlandı." - login: "Birisi oturum açtı" + login: "Biri oturum açtı" createToken: "Bir erişim jetonu oluşturuldu." createTokenDescription: "Eğer bilmiyorsanız, “{text}” aracılığıyla erişim jetonunu silin." _types: @@ -2688,20 +2691,20 @@ _deck: configureColumn: "Sütun ayarları" swapLeft: "Sol sütunla değiştir" swapRight: "Sağ sütunla değiştir" - swapUp: "Yukarıdaki sütunla değiştirin" - swapDown: "Aşağıdaki sütunla değiştirin" + swapUp: "Yukarıdaki sütunla değiştir" + swapDown: "Aşağıdaki sütunla değiştir" stackLeft: "Sol sütunda yığın" popRight: "Sağdaki pop sütunu" profile: "Profil" newProfile: "Yeni profil" deleteProfile: "Profili sil" introduction: "Sütunları serbestçe düzenleyerek size en uygun arayüzü oluşturun!" - introduction2: "Ekranın sağındaki + işaretine tıklayarak istediğiniz zaman yeni sütunlar ekleyebilirsiniz." - widgetsIntroduction: "Lütfen sütun menüsünden “Widget'ları düzenle” seçeneğini seçin ve bir widget ekleyin." + introduction2: "Ekranın sağındaki + işaretine tıklayarak istediğin zaman yeni sütunlar ekleyebilirsin." + widgetsIntroduction: "Lütfen sütun menüsünden “Widget'ları düzenle” seçeneğini seç ve bir widget ekle." useSimpleUiForNonRootPages: "Gezinilen sayfalar için basit kullanıcı arayüzü kullanın" - usedAsMinWidthWhenFlexible: "“Otomatik genişlik ayarı” seçeneği etkinleştirildiğinde, bunun için minimum genişlik kullanılacaktır." + usedAsMinWidthWhenFlexible: "“Otomatik genişlik ayarı” seçeneği etkinleştirildiğinde, bunun için minimum genişlik kullanılacak." flexible: "Otomatik genişlik ayarı" - enableSyncBetweenDevicesForProfiles: "Cihazlar arasında profil bilgilerinin senkronizasyonunu etkinleştirin" + enableSyncBetweenDevicesForProfiles: "Cihazlar arasında profil bilgilerinin senkronizasyonunu etkinleştir" _columns: main: "Ana" widgets: "Widget'lar" @@ -2712,14 +2715,14 @@ _deck: channel: "Kanal" mentions: "Bahsetmeler" direct: "Doğrudan notlar" - roleTimeline: "Rol Timeline" + roleTimeline: "Rol Pano" chat: "Sohbet" _dialog: charactersExceeded: "Maksimum karakter sınırını aştınız! Şu anda {current} karakterde {max} karakterlik sınırın {current} karakterinde bulunuyorsunuz." charactersBelow: "You're below the minimum character limit! Currently at {current} of {min}." _disabledTimeline: - title: "Timeline devre dışı bırakıldı" - description: "Mevcut rolleriniz altında bu Timeline'ı kullanamazsınız." + title: "Pano devre dışı bırakıldı" + description: "Mevcut rollerinle bu Pano kullanılamaz." _drivecleaner: orderBySizeDesc: "Azalan Dosya Boyutları" orderByCreatedAtAsc: "Yükselen Tarihler" @@ -2744,7 +2747,7 @@ _webhookSettings: userCreated: "Kullanıcı oluşturulduğunda" inactiveModeratorsWarning: "Moderatörler bir süredir aktif olmadıklarında" inactiveModeratorsInvitationOnlyChanged: "Bir moderatör bir süre aktif olmadığında ve sunucu davetle erişilebilir hale getirildiğinde" - deleteConfirm: "Webhook'u silmek istediğinizden emin misiniz?" + deleteConfirm: "Webhook'u silmek istediğinden emin misin?" testRemarks: "Anahtarın sağındaki düğmeyi tıklayarak sahte verilerle bir test Webhook gönderin." _abuseReport: _notificationRecipient: @@ -2760,7 +2763,7 @@ _abuseReport: keywords: "Anahtar kelimeler" notifiedUser: "Bildirilecek kullanıcılar" notifiedWebhook: "Kullanılacak webhook" - deleteConfirm: "Bildirim alıcısını silmek istediğinizden emin misiniz?" + deleteConfirm: "Bildirim alıcısını silmek istediğinden emin misin?" _moderationLogTypes: createRole: "Rol oluşturuldu" deleteRole: "Rol silindi" @@ -2776,11 +2779,11 @@ _moderationLogTypes: updateUserNote: "Moderasyon notu güncellendi" deleteDriveFile: "Dosya silindi" deleteNote: "Not silindi" - createGlobalAnnouncement: "Küresel duyuru oluşturuldu" + createGlobalAnnouncement: "Global duyuru oluşturuldu" createUserAnnouncement: "Kullanıcı duyurusu oluşturuldu" - updateGlobalAnnouncement: "Küresel duyuru güncellendi" + updateGlobalAnnouncement: "Global duyuru güncellendi" updateUserAnnouncement: "Kullanıcı duyurusu güncellendi" - deleteGlobalAnnouncement: "Küresel duyuru silindi" + deleteGlobalAnnouncement: "Global duyuru silindi" deleteUserAnnouncement: "Kullanıcı duyurusu silindi" resetPassword: "Şifreyi sıfırla" suspendRemoteInstance: "Uzak sunucu askıya alındı" @@ -2797,7 +2800,7 @@ _moderationLogTypes: updateAd: "Reklam güncellendi" createAvatarDecoration: "Avatar dekorasyonu oluşturuldu" updateAvatarDecoration: "Avatar dekorasyonu güncellendi" - deleteAvatarDecoration: "Avatar süslemesi silindi" + deleteAvatarDecoration: "Avatar süsü silindi" unsetUserAvatar: "Kullanıcı avatarı ayarlanmamış" unsetUserBanner: "Kullanıcı başlığı ayarlanmamış" createSystemWebhook: "Sistem Webhook oluşturuldu" @@ -2811,7 +2814,7 @@ _moderationLogTypes: deleteFlash: "Oyun silindi" deleteGalleryPost: "Galeri gönderisi silindi" deleteChatRoom: "Deleted Chat Room" - updateProxyAccountDescription: "Proxy hesabının açıklamasını güncelleyin" + updateProxyAccountDescription: "Proxy hesabının açıklamasını güncelle" _fileViewer: title: "Dosya ayrıntıları" type: "Dosya türü" @@ -2825,9 +2828,9 @@ _externalResourceInstaller: title: "Harici siteden yükle" checkVendorBeforeInstall: "Yüklemeden önce bu kaynağın dağıtımcısının güvenilir olduğundan emin olun." _plugin: - title: "Bu eklentiyi yüklemek ister misiniz?" + title: "Bu eklentiyi yüklemek ister misin?" _theme: - title: "Bu temayı yüklemek ister misiniz?" + title: "Bu temayı yüklemek ister misin?" _meta: base: "Temel renk şeması" _vendorInfo: @@ -2837,13 +2840,13 @@ _externalResourceInstaller: _errors: _invalidParams: title: "Geçersiz parametreler" - description: "Harici bir siteden veri yüklemek için yeterli bilgi yok. Lütfen girdiğiniz URL'yi kontrol edin." + description: "Harici bir siteden veri yüklemek için yeterli bilgi yok. Lütfen girdiğin URL'yi kontrol et." _resourceTypeNotSupported: title: "Bu harici kaynak desteklenmemektedir." - description: "Bu harici kaynağın türü desteklenmemektedir. Lütfen site yöneticisiyle iletişime geçin." + description: "Bu harici kaynağın türü desteklenmemektedir. Lütfen site yöneticisiyle iletişime geç." _failedToFetch: title: "Veriler alınamadı" - fetchErrorDescription: "Harici siteyle iletişim sırasında bir hata oluştu. Tekrar denemeniz sorunu çözmezse, lütfen site yöneticisine başvurun." + fetchErrorDescription: "Harici siteyle iletişim sırasında bir hata oluştu. Tekrar denemen sorunu çözmezse, lütfen site yöneticisine başvur." parseErrorDescription: "Harici siteden yüklenen veriler işlenirken bir hata oluştu. Lütfen site yöneticisiyle iletişime geçin." _hashUnmatched: title: "Veri doğrulama başarısız oldu" @@ -2853,13 +2856,13 @@ _externalResourceInstaller: description: "İstenen veriler başarıyla alındı, ancak AiScript ayrıştırma sırasında bir hata oluştu. Lütfen eklenti yazarına başvurun. Hata ayrıntıları Javascript konsolunda görüntülenebilir." _pluginInstallFailed: title: "Eklenti kurulumu başarısız oldu" - description: "Eklenti yükleme sırasında bir sorun oluştu. Lütfen tekrar deneyin. Hata ayrıntıları Javascript konsolunda görüntülenebilir." + description: "Eklenti yükleme sırasında bir sorun oluştu. Lütfen tekrar dene. Hata ayrıntıları Javascript konsolunda görüntülenebilir." _themeParseFailed: title: "Tema ayrıştırma başarısız oldu" description: "İstenen veriler başarıyla alındı, ancak tema ayrıştırma sırasında bir hata oluştu. Lütfen tema yazarıyla iletişime geçin. Hata ayrıntıları Javascript konsolunda görüntülenebilir." _themeInstallFailed: title: "Tema yüklenemedi" - description: "Tema yükleme sırasında bir sorun oluştu. Lütfen tekrar deneyin. Hata ayrıntıları Javascript konsolunda görüntülenebilir." + description: "Tema yükleme sırasında bir sorun oluştu. Lütfen tekrar dene. Hata ayrıntıları Javascript konsolunda görüntülenebilir." _dataSaver: _media: title: "Medya yükleniyor" @@ -2869,7 +2872,7 @@ _dataSaver: description: "Avatar görüntüsünün animasyonunu durdurun. Animasyonlu görüntüler normal görüntülere göre dosya boyutu açısından daha büyük olabilir ve bu da veri trafiğinde daha fazla azalmaya yol açabilir." _urlPreviewThumbnail: title: "URL önizleme küçük resimlerini gizle" - description: "URL önizleme küçük resimleri artık yüklenmeyecektir." + description: "URL önizleme küçük resimleri artık yüklenmeyecek." _disableUrlPreview: title: "URL önizlemesini devre dışı bırak" description: "URL önizleme işlevini devre dışı bırakır. Küçük resimler aksine, bu işlev bağlantılı bilgilerin kendisinin yüklenmesini azaltır." @@ -2888,8 +2891,8 @@ _reversi: blackIs: "{name} siyah oynuyor." rules: "Kurallar" thisGameIsStartedSoon: "Oyun kısa süre içinde başlayacak." - waitingForOther: "Rakibin sırasını beklemek" - waitingForMe: "Sıranızı bekliyorsunuz" + waitingForOther: "Rakibin sırasını bekle" + waitingForMe: "Sıranı bekliyorsun" waitingBoth: "Hazır olun" ready: "Hazır" cancelReady: "Hazır değil" @@ -2919,7 +2922,7 @@ _reversi: gameCanceled: "Oyun iptal edildi." shareToTlTheGameWhenStart: "Oyun başlatıldığında zaman çizelgesinde paylaş" iStartedAGame: "Oyun başladı! #MisskeyReversi" - opponentHasSettingsChanged: "Rakip ayarlarını değiştirmiştir." + opponentHasSettingsChanged: "Rakip ayarlarını değiştirmiş." allowIrregularRules: "Düzensiz kurallar (tamamen ücretsiz)" disallowIrregularRules: "Düzensiz kurallar yok" showBoardLabels: "Tahtada satır ve sütun numaralarını göster" @@ -2931,7 +2934,7 @@ _urlPreviewSetting: title: "URL önizleme ayarları" enable: "URL önizlemesini etkinleştir" allowRedirect: "URL önizleme yönlendirmesine izin ver" - allowRedirectDescription: "Bir URL'de yönlendirme ayarlanmışsa, bu özelliği etkinleştirerek yönlendirmeyi takip edebilir ve yönlendirilen içeriğin önizlemesini görüntüleyebilirsiniz. Bu özelliği devre dışı bırakmak sunucu kaynaklarından tasarruf sağlar, ancak yönlendirilen içerik görüntülenmez." + allowRedirectDescription: "Bir URL'de yönlendirme ayarlanmışsa, bu özelliği etkinleştirerek yönlendirmeyi takip edebilir ve yönlendirilen içeriğin önizlemesini görüntüleyebilirsin. Bu özelliği devre dışı bırakmak sunucu kaynaklarından tasarruf sağlar, ancak yönlendirilen içerik görüntülenmez." timeout: "Önizleme alırken zaman aşımı (ms)" timeoutDescription: "Önizlemeyi almak bu değerden daha uzun sürerse, önizleme oluşturulmaz." maximumContentLength: "Maksimum İçerik Uzunluğu (bayt)" @@ -2941,7 +2944,7 @@ _urlPreviewSetting: userAgent: "Kullanıcı Aracısı" userAgentDescription: "Önizlemeleri alırken kullanılacak Kullanıcı Aracısını ayarlar. Boş bırakılırsa, varsayılan Kullanıcı Aracısı kullanılır." summaryProxy: "Önizlemeler oluşturan proxy uç noktaları" - summaryProxyDescription: "Misskey'in kendisi değil, Summaly Proxy kullanarak önizlemeler oluşturun." + summaryProxyDescription: "Misskey'in kendisi değil, Summaly Proxy kullanarak önizlemeler oluştur." summaryProxyDescription2: "Aşağıdaki parametreler, sorgu dizesi olarak proxy'ye bağlanır. Proxy bunları desteklemiyorsa, değerler yok sayılır." _mediaControls: pip: "Resim içinde resim" @@ -2967,11 +2970,11 @@ _customEmojisManager: deleteSelectionRows: "Seçili satırları sil" deleteSelectionRanges: "Seçimdeki satırları sil" searchSettings: "Arama ayarları" - searchSettingCaption: "Ayrıntılı arama kriterleri belirleyin." + searchSettingCaption: "Ayrıntılı arama kriterleri belirle." searchLimit: "Sonuç sayısı" sortOrder: "Sıralama düzeni" registrationLogs: "Kayıt günlüğü" - registrationLogsCaption: "Emojileri güncellerken veya silerken günlükler görüntülenecektir. Güncelleme veya silme işleminden sonra, yeni bir sayfaya geçildiğinde veya yeniden yüklendiğinde günlükler kaybolacaktır." + registrationLogsCaption: "Emojileri güncellerken veya silerken günlükler görüntülenecek. Güncelleme veya silme işleminden sonra, yeni bir sayfaya geçildiğinde veya yeniden yüklendiğinde günlükler kaybolacak." alertEmojisRegisterFailedDescription: "Emojileri güncelleyemedi veya silemedi. Ayrıntılar için kayıt günlüğünü kontrol edin." _logs: showSuccessLogSwitch: "Başarı günlüğünü göster" @@ -2989,43 +2992,43 @@ _customEmojisManager: tabTitleRegister: "Emoji kaydı" _list: emojisNothing: "Kayıtlı Emoji yok." - markAsDeleteTargetRows: "Silinecek hedef olarak seçilen satırları işaretleyin" - markAsDeleteTargetRanges: "Seçimdeki satırları silinecek hedef olarak işaretleyin" + markAsDeleteTargetRows: "Silinecek hedef olarak seçilen satırları işaretle" + markAsDeleteTargetRanges: "Seçimdeki satırları silinecek hedef olarak işaretle" alertUpdateEmojisNothingDescription: "Güncellenmiş Emoji yok." alertDeleteEmojisNothingDescription: "Silinecek Emoji yok." - confirmMovePage: "Sayfaları taşımak ister misiniz?" - confirmChangeView: "Görüntüleme şeklini değiştirmek ister misiniz?" + confirmMovePage: "Sayfaları taşımak ister misin?" + confirmChangeView: "Görüntüleme şeklini değiştirmek ister misn?" confirmUpdateEmojisDescription: "{count} Emoji'yi güncelle. Devam etmek istediğinden emin misin?" - confirmDeleteEmojisDescription: "İşaretli {count} Emoji(leri) silin. Devam etmek istediğinizden emin misiniz?" + confirmDeleteEmojisDescription: "İşaretli {count} Emoji(leri) silin. Devam etmek istediğinden emin misin?" confirmResetDescription: "Şimdiye kadar yapılan tüm değişiklikler geri alınacaktır." - confirmMovePageDesciption: "Bu sayfadaki Emojilerde değişiklikler yapılmıştır.\nSayfayı kaydetmeden terk ederseniz, bu sayfada yapılan tüm değişiklikler silinecektir." + confirmMovePageDesciption: "Bu sayfadaki Emojilerde değişiklikler yapılmış.\nSayfayı kaydetmeden terk ederseniz, bu sayfada yapılan tüm değişiklikler silinecek." dialogSelectRoleTitle: "Emojilerde rol setine göre arama yapın" _register: uploadSettingTitle: "Yükleme ayarları" - uploadSettingDescription: "Bu ekranda, Emoji yüklerken davranışı yapılandırabilirsiniz." + uploadSettingDescription: "Bu ekranda, Emoji yüklerken davranışı yapılandırabilirsin." directoryToCategoryLabel: "“Kategori” alanına dizin adını girin." directoryToCategoryCaption: "Bir dizini sürükleyip bıraktığınızda, “kategori” alanına dizin adını girin." - confirmRegisterEmojisDescription: "Listeden Emojileri yeni özel Emojiler olarak kaydedin. Devam etmek istediğinizden emin misiniz? (Aşırı yüklemeyi önlemek için, tek bir işlemde yalnızca {count} Emoji kaydedilebilir)" - confirmClearEmojisDescription: "Düzenlemeleri silin ve listeden Emojileri temizleyin. Devam etmek istediğinizden emin misiniz?" - confirmUploadEmojisDescription: "Sürücüye sürüklenip bırakılan {count} dosyayı/dosyaları yükleyin. Devam etmek istediğinizden emin misiniz?" + confirmRegisterEmojisDescription: "Listeden Emojileri yeni özel Emojiler olarak kaydet. Devam etmek istediğinden emin misin? (Aşırı yüklemeyi önlemek için, tek bir işlemde yalnızca {count} Emoji kaydedilebilir)" + confirmClearEmojisDescription: "Düzenlemeleri sil ve listeden Emojileri temizle. Devam etmek istediğinden emin misiniz?" + confirmUploadEmojisDescription: "Drive'a sürüklenip bırakılan {count} dosyayı yükle. Devam etmek istediğinden emin misin?" _embedCodeGen: title: "Gömme kodunu özelleştir" header: "Başlığı göster" autoload: "Otomatik olarak daha fazlasını yükle (kullanımdan kaldırıldı)" maxHeight: "Maksimum yükseklik" - maxHeightDescription: "0 olarak ayarlandığında maksimum yükseklik ayarı devre dışı bırakılır. Widget'ın dikey olarak genişlemeye devam etmesini önlemek için bir değer belirtin." - maxHeightWarn: "Maksimum yükseklik sınırı devre dışıdır (0). Bu istenmeyen bir durumsa, maksimum yüksekliği bir değer olarak ayarlayın." + maxHeightDescription: "0 olarak ayarlandığında maksimum yükseklik ayarı devre dışı bırakılır. Widget'ın dikey olarak genişlemeye devam etmesini önlemek için bir değer belirt." + maxHeightWarn: "Maksimum yükseklik sınırı devre dışıdır (0). Bu istenmeyen bir durumsa, maksimum yüksekliği bir değer olarak ayarla." previewIsNotActual: "Ekran, önizleme ekranında görüntülenen aralığı aştığı için gerçek gömme işleminden farklıdır." - rounded: "Yuvarlak hale getirin" + rounded: "Yuvarlak hale getir" border: "Dış çerçeveye kenarlık ekle" applyToPreview: "Önizlemeye başvur" generateCode: "Gömme kodu oluştur" codeGenerated: "Kod oluşturuldu" - codeGeneratedDescription: "Oluşturulan kodu web sitenize yapıştırarak içeriği gömün." + codeGeneratedDescription: "Oluşturulan kodu web sitene yapıştırarak içeriği göm." _selfXssPrevention: warning: "UYARI" - title: "“Bu ekrana bir şey yapıştırın” tamamen bir aldatmacadır." - description1: "Buraya bir şey yapıştırırsanız, kötü niyetli bir kullanıcı hesabınızı ele geçirebilir veya kişisel bilgilerinizi çalabilir." + title: "“Bu ekrana bir şey yapıştırın” tamamen bir aldatmaca." + description1: "Buraya bir şey yapıştırırsan, kötü niyetli bir kullanıcı hesabını ele geçirebilir veya kişisel bilgilerini çalabilir." description2: "Yapıştırmaya çalıştığınız şeyi tam olarak anlamıyorsanız, %c hemen çalışmayı bırakın ve bu pencereyi kapatın." description3: "Daha fazla bilgi için lütfen buraya bakın. {link}" _followRequest: @@ -3037,10 +3040,10 @@ _remoteLookupErrors: description: "Bu sunucu ile iletişim devre dışı bırakılmış olabilir veya bu sunucu engellenmiş olabilir.\nLütfen sunucu yöneticisi ile iletişime geçin." _uriInvalid: title: "URI geçersiz" - description: "Girdiğiniz URI ile ilgili bir sorun var. Lütfen URI'da kullanılamayan karakterler girip girmediğinizi kontrol edin." + description: "Girdiğin URI ile ilgili bir sorun var. Lütfen URI'da kullanılamayan karakterler girip girmediğini kontrol et." _requestFailed: title: "İstek başarısız oldu" - description: "Bu sunucuyla iletişim kurulamadı. Sunucu kapalı olabilir. Ayrıca, geçersiz veya mevcut olmayan bir URI girmediğinizden emin olun." + description: "Bu sunucuyla iletişim kurulamadı. Sunucu kapalı olabilir. Ayrıca, geçersiz veya mevcut olmayan bir URI girmediğinizden emin ol." _responseInvalid: title: "Yanıt geçersiz" description: "Bu sunucuyla iletişim kurabildi, ancak elde edilen veriler yanlıştı." @@ -3049,7 +3052,7 @@ _remoteLookupErrors: description: "İstenen kaynak bulunamadı, lütfen URI'yi tekrar kontrol edin." _captcha: verify: "Lütfen CAPTCHA'yı doğrulayın" - testSiteKeyMessage: "Site ve gizli anahtarlar için test değerlerini girerek önizlemeyi kontrol edebilirsiniz.\nAyrıntılar için lütfen aşağıdaki sayfaya bakın." + testSiteKeyMessage: "Site ve gizli anahtarlar için test değerlerini girerek önizlemeyi kontrol edebilirsin.\nAyrıntılar için lütfen aşağıdaki sayfaya bak." _error: _requestFailed: title: "CAPTCHA isteği başarısız oldu" @@ -3064,7 +3067,7 @@ _bootErrors: title: "Yükleme başarısız" serverError: "Bir süre bekledikten ve yeniden yükledikten sonra sorun devam ederse, lütfen aşağıdaki Hata ID ile sunucu yöneticisine başvurun." solution: "Aşağıdakiler sorunu çözebilir." - solution1: "Tarayıcınızı ve işletim sisteminizi en son sürüme güncelleyin." + solution1: "Tarayıcını ve işletim sistemini en son sürüme güncelle." solution2: "Reklam engelleyiciyi devre dışı bırak" solution3: "Tarayıcı önbelleğini temizle" solution4: "Tor Tarayıcı için dom.webaudio.enabled değerini true olarak ayarlayın." @@ -3098,19 +3101,19 @@ _serverSetupWizard: open: "Genel sunucu" open_description: "Herkesin kayıt olmasına izin verin." openServerAdvice: "Çok sayıda bilinmeyen kullanıcıyı kabul etmek risklidir. Herhangi bir sorunu çözmek için güvenilir bir moderasyon sistemi kullanmanızı öneririz." - openServerAntiSpamAdvice: "Sunucunuzun spam için bir basamak haline gelmesini önlemek için, reCAPTCHA gibi anti-bot işlevlerini etkinleştirerek güvenliğe de özen göstermelisiniz." + openServerAntiSpamAdvice: "Sunucunuzun spam için bir basamak haline gelmesini önlemek için, reCAPTCHA gibi anti-bot işlevlerini etkinleştirerek güvenliğe de özen göstermelisin." howManyUsersDoYouExpect: "Kaç kullanıcı bekliyorsunuz?" _scale: small: "100'den az (küçük ölçekli)" medium: "100'den fazla ve 1000'den az kullanıcı (orta büyüklükte)" large: "1000'den fazla (Büyük ölçekli)" largeScaleServerAdvice: "Büyük sunucular, yük dengeleme ve veritabanı çoğaltma gibi gelişmiş altyapı bilgisi gerektirebilir." - doYouConnectToFediverse: "Fediverse'e bağlanmak ister misiniz?" + doYouConnectToFediverse: "Fediverse'e bağlanmak ister misin?" doYouConnectToFediverse_description1: "Dağıtılmış sunucular ağına (Fediverse) bağlandığında, içerik diğer sunucularla paylaşılabilir." doYouConnectToFediverse_description2: "Fediverse ile bağlantı kurmak “federasyon” olarak da adlandırılır." youCanConfigureMoreFederationSettingsLater: "Birleştirilmiş sunucuları belirtme gibi gelişmiş ayarlar daha sonra yapılandırılabilir." remoteContentsCleaning: "Alınan içeriklerin otomatik olarak temizlenmesi" - remoteContentsCleaning_description: "Federasyon, sürekli içerik akışına neden olabilir. Otomatik temizleme özelliğini etkinleştirmek, depolama alanından tasarruf etmek için sunucudan eski ve referanslanmamış içeriği kaldıracaktır." + remoteContentsCleaning_description: "Federasyon, sürekli içerik akışına neden olabilir. Otomatik temizleme özelliğini etkinleştirmek, depolama alanından tasarruf etmek için sunucudan eski ve referanslanmamış içeriği kaldıracak." adminInfo: "Yönetici bilgileri" adminInfo_description: "Sorguları almak için kullanılan yönetici bilgilerini ayarlar." adminInfo_mustBeFilled: "Genel sunucu veya federasyon açıksa girilmelidir." @@ -3118,39 +3121,39 @@ _serverSetupWizard: applyTheseSettings: "Bu ayarları uygulayın" skipSettings: "Ayarları atla" settingsCompleted: "Kurulum tamamlandı!" - settingsCompleted_description: "Zaman ayırdığınız için teşekkür ederiz. Artık her şey hazır olduğuna göre, sunucuyu hemen kullanmaya başlayabilirsiniz." + settingsCompleted_description: "Zaman ayırdığınız için teşekkür ederiz. Artık her şey hazır olduğuna göre, sunucuyu hemen kullanmaya başlayabilirsin." settingsCompleted_description2: "Sunucu ayarları “Kontrol Paneli”nden değiştirilebilir." donationRequest: "Bağış Talebi" _donationRequest: text1: "Misskey, gönüllüler tarafından geliştirilen ücretsiz bir yazılımdır." - text2: "Bu yazılımı gelecekte de geliştirmeye devam edebilmemiz için desteğinizi rica ederiz." + text2: "Bu yazılımı gelecekte de geliştirmeye devam edebilmemiz için desteğini rica ederiz." text3: "Destekçilere özel avantajlar da var!" _uploader: editImage: "Resmi Düzenle" compressedToX: "{x} boyutuna sıkıştırıldı" savedXPercent: "{x}% tasarruf" - abortConfirm: "Bazı dosyalar yüklenmedi, iptal etmek ister misiniz?" - doneConfirm: "Bazı dosyalar yüklenmedi, yine de devam etmek istiyor musunuz?" - maxFileSizeIsX: "Yükleyebileceğiniz maksimum dosya boyutu {x}" + abortConfirm: "Bazı dosyalar yüklenmedi, iptal etmek ister misin?" + doneConfirm: "Bazı dosyalar yüklenmedi, yine de devam etmek istiyor musun?" + maxFileSizeIsX: "Yükleyebileceğin maksimum dosya boyutu {x}" allowedTypes: "Yüklenebilir dosya türleri" - tip: "Dosya henüz yüklenmediğinden, bu iletişim kutusu yüklemeden önce dosyayı onaylamanıza, yeniden adlandırmanıza, sıkıştırmanıza ve kırpmanıza olanak tanır. Hazır olduğunuzda, “Yükle” düğmesine basarak yüklemeyi başlatabilirsiniz." + tip: "Dosya henüz yüklenmediğinden, bu iletişim kutusu yüklemeden önce dosyayı onaylamanıza, yeniden adlandırmana, sıkıştırmana ve kırpmana olanak tanır. Hazır olduğunda, “Yükle” düğmesine basarak yüklemeyi başlatabilirsin." _clientPerformanceIssueTip: title: "Performans ipuçları" - makeSureDisabledAdBlocker: "Reklam engelleyicinizi devre dışı bırakın" - makeSureDisabledAdBlocker_description: "Reklam engelleyiciler performansı etkileyebilir, lütfen sisteminizde veya tarayıcınızın özelliklerinde/uzantılarında reklam engelleyicilerin etkinleştirilmediğinden emin olun." + makeSureDisabledAdBlocker: "Reklam engelleyicini devre dışı bırak" + makeSureDisabledAdBlocker_description: "Reklam engelleyiciler performansı etkileyebilir, lütfen sisteminde veya tarayıcının özelliklerinde/uzantılarında reklam engelleyicilerin etkinleştirilmediğinden emin ol." makeSureDisabledCustomCss: "Özel CSS'yi devre dışı bırak" - makeSureDisabledCustomCss_description: "Stil geçersiz kılma, performansı etkileyebilir. Stil geçersiz kılan özel CSS veya uzantıların etkinleştirilmediğinden emin olun." + makeSureDisabledCustomCss_description: "Stil geçersiz kılma, performansı etkileyebilir. Stil geçersiz kılan özel CSS veya uzantıların etkinleştirilmediğinden emin ol." makeSureDisabledAddons: "Uzantıları devre dışı bırak" makeSureDisabledAddons_description: "Bazı uzantılar istemci davranışını engelleyebilir ve performansı etkileyebilir. Lütfen tarayıcı uzantılarınızı devre dışı bırakın ve durumun düzelip düzelmediğini kontrol edin." _clip: - tip: "Clip, notlarınızı düzenlemenizi sağlayan bir özelliktir." + tip: "Klip, notları gruplandırmanıza olanak tanıyan bir özelliktir." _userLists: - tip: "Listeler, oluşturulurken belirttiğiniz herhangi bir kullanıcıyı içerebilir. Oluşturulan liste, yalnızca belirtilen kullanıcıları gösteren bir zaman çizelgesi olarak görüntülenebilir." + tip: "Listeler, oluşturulurken belirttiğin herhangi bir kullanıcıyı içerebilir. Oluşturulan liste, yalnızca belirtilen kullanıcıları gösteren bir zaman çizelgesi olarak görüntülenebilir." watermark: "Filigran" defaultPreset: "Varsayılan Ön Ayar" _watermarkEditor: tip: "Kredi bilgileri gibi bir filigran görüntüye eklenebilir." - quitWithoutSaveConfirm: "Kaydedilmemiş değişiklikleri silmek ister misiniz?" + quitWithoutSaveConfirm: "Kaydedilmemiş değişiklikleri silmek ister misin?" driveFileTypeWarn: "Bu dosya desteklenmiyor" driveFileTypeWarnDescription: "Bir görüntü dosyası seçin" title: "Filigranı Düzenle" @@ -3163,10 +3166,10 @@ _watermarkEditor: type: "Tür" image: "Görseller" advanced: "Gelişmiş" + angle: "Açı" stripe: "Çizgiler" stripeWidth: "Çizgi genişliği" stripeFrequency: "Satır sayısı" - angle: "Açı" polkadot: "Nokta deseni" checker: "Kontrolcü" polkadotMainDotOpacity: "Ana noktanın opaklığı" @@ -3177,7 +3180,8 @@ _watermarkEditor: _imageEffector: title: "Effektler" addEffect: "Efektler Ekle" - discardChangesConfirm: "Gerçekten çıkmak istiyor musunuz? Kaydedilmemiş değişiklikleriniz var." + discardChangesConfirm: "Cidden çıkmak istiyor musun? Kaydedilmemiş değişikliklerin var." + nothingToConfigure: "Yapılandırılabilir seçenekler mevcut değildir." _fxs: chromaticAberration: "Renk Sapması" glitch: "Bozulma" @@ -3188,20 +3192,52 @@ _imageEffector: colorClamp: "Renk Sıkıştırma" colorClampAdvanced: "Renk Sıkıştırma (Gelişmiş)" distort: "Bozulma" - threshold: "İkilileştir" + threshold: "Binarize" zoomLines: "Doymuş hatlar" stripe: "Çizgiler" polkadot: "Nokta deseni" checker: "Denetleyici" blockNoise: "Gürültüyü Engelle" tearing: "Yırtılma" + _fxProps: + angle: "Açı" + scale: "Boyut" + size: "Boyut" + color: "Renk" + opacity: "Opaklık" + normalize: "Normalize" + amount: "Miktar" + lightness: "Hafiflet" + contrast: "Kontrast" + hue: "Hue" + brightness: "Parlaklık" + saturation: "Doygunluk" + max: "Maksimum" + min: "Minimum" + direction: "Yön" + phase: "Aşama" + frequency: "Sıklık" + strength: "Güç" + glitchChannelShift: "Kanal değişimi" + seed: "Tohum değeri" + redComponent: "Kırmızı bileşen" + greenComponent: "Yeşil bileşen" + blueComponent: "Mavi bileşen" + threshold: "Eşik" + centerX: "Merkez X" + centerY: "Merkez Y" + zoomLinesSmoothing: "Düzeltme" + zoomLinesSmoothingDescription: "Düzeltme ve yakınlaştırma çizgi genişliği birlikte kullanılamaz." + zoomLinesThreshold: "Zoom çizgi genişliği" + zoomLinesMaskSize: "Merkez çapı" + zoomLinesBlack: "Siyah yap" drafts: "Taslaklar" _drafts: select: "Taslak Seç" cannotCreateDraftAnymore: "Oluşturulabilecek taslak sayısı aşılmıştır." cannotCreateDraft: "Bu içerikle taslak oluşturamazsınız." delete: "Taslak Sil" - deleteAreYouSure: "Taslağı silmek ister misiniz?" + deleteAreYouSure: "Taslağı silmek ister misin?" noDrafts: "Taslak yok" replyTo: "{user} notunu yanıtla" quoteOf: "{user} notuna alıntı" diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index 26843c6917..46d9ab95ff 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -1648,3 +1648,10 @@ _watermarkEditor: type: "Тип" image: "Зображення" advanced: "Розширені" +_imageEffector: + _fxProps: + scale: "Розмір" + size: "Розмір" + color: "Колір" + opacity: "Непрозорість" + lightness: "Яскравість" diff --git a/locales/uz-UZ.yml b/locales/uz-UZ.yml index bfa91cb0db..cfa2e26fd5 100644 --- a/locales/uz-UZ.yml +++ b/locales/uz-UZ.yml @@ -1102,3 +1102,7 @@ _watermarkEditor: type: "turi" image: "Rasmlar" advanced: "Murakkab" +_imageEffector: + _fxProps: + color: "Rang" + lightness: "Yoritish" diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index 049e96b044..8b4f1bfd5e 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -2091,3 +2091,11 @@ _watermarkEditor: image: "Hình ảnh" advanced: "Nâng cao" angle: "Góc" +_imageEffector: + _fxProps: + angle: "Góc" + scale: "Kích thước" + size: "Kích thước" + color: "Màu sắc" + opacity: "Độ trong suốt" + lightness: "Độ sáng" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 8843fc682e..b095374e4d 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -1092,6 +1092,7 @@ prohibitedWordsDescription2: "AND 条件用空格分隔,正则表达式用斜 hiddenTags: "隐藏标签" hiddenTagsDescription: "设定的标签将不会在时间线上显示。可使用换行来设置多个标签。" notesSearchNotAvailable: "帖子检索不可用" +usersSearchNotAvailable: "用户检索不可用" license: "许可信息" unfavoriteConfirm: "确定要取消收藏吗?" myClips: "我的便签" @@ -1465,6 +1466,7 @@ _settings: contentsUpdateFrequency_description2: "当实时模式开启时,无论此设置如何,内容都会实时更新。" showUrlPreview: "显示 URL 预览" showAvailableReactionsFirstInNote: "在顶部显示可用的回应" + showPageTabBarBottom: "在下方显示页面标签栏" _chat: showSenderName: "显示发送者的名字" sendOnEnter: "回车键发送" @@ -1998,6 +2000,7 @@ _role: descriptionOfRateLimitFactor: "值越小限制越少,值越大限制越多。" canHideAds: "可以隐藏广告" canSearchNotes: "是否可以搜索帖子" + canSearchUsers: "使用用户检索" canUseTranslator: "使用翻译功能" avatarDecorationLimit: "可添加头像挂件的最大个数" canImportAntennas: "允许导入天线" @@ -3163,10 +3166,10 @@ _watermarkEditor: type: "类型" image: "图片" advanced: "高级" + angle: "角度" stripe: "条纹" stripeWidth: "线条宽度" stripeFrequency: "线条数量" - angle: "角度" polkadot: "波点" checker: "检查" polkadotMainDotOpacity: "主波点的不透明度" @@ -3178,6 +3181,7 @@ _imageEffector: title: "效果" addEffect: "添加效果" discardChangesConfirm: "丢弃当前设置并退出?" + nothingToConfigure: "还没有设置" _fxs: chromaticAberration: "色差" glitch: "故障" @@ -3195,6 +3199,38 @@ _imageEffector: checker: "检查" blockNoise: "块状噪点" tearing: "撕裂" + _fxProps: + angle: "角度" + scale: "大小" + size: "大小" + color: "颜色" + opacity: "不透明度" + normalize: "标准化" + amount: "数量" + lightness: "浅色" + contrast: "对比度" + hue: "色调" + brightness: "亮度" + saturation: "饱和度" + max: "最大值" + min: "最小值" + direction: "方向" + phase: "相位" + frequency: "频率" + strength: "强度" + glitchChannelShift: "错位" + seed: "种子" + redComponent: "红色成分" + greenComponent: "绿色成分" + blueComponent: "蓝色成分" + threshold: "阈值" + centerX: "中心 X " + centerY: "中心 Y" + zoomLinesSmoothing: "平滑" + zoomLinesSmoothingDescription: "平滑和集中线宽度设置不能同时使用。" + zoomLinesThreshold: "集中线宽度" + zoomLinesMaskSize: "中心直径" + zoomLinesBlack: "变成黑色" drafts: "草稿" _drafts: select: "选择草稿" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 00d4a3a9ac..b9f5f9a6c9 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -1092,6 +1092,7 @@ prohibitedWordsDescription2: "空格代表「以及」(AND),斜線包圍 hiddenTags: "隱藏標籤" hiddenTagsDescription: "設定的標籤不會在趨勢中顯示,換行可以設定多個標籤。" notesSearchNotAvailable: "無法使用搜尋貼文功能。" +usersSearchNotAvailable: "無法使用使用者搜尋功能。" license: "授權" unfavoriteConfirm: "要取消收錄我的最愛嗎?" myClips: "我的摘錄" @@ -1373,7 +1374,7 @@ inDays: "日" safeModeEnabled: "啟用安全模式" pluginsAreDisabledBecauseSafeMode: "由於啟用安全模式,所有的外掛都被停用。" customCssIsDisabledBecauseSafeMode: "由於啟用安全模式,所有的客製 CSS 都被停用。" -themeIsDefaultBecauseSafeMode: "啟用安全模式時將使用預設主題,關閉安全模式時將恢復預設主題。" +themeIsDefaultBecauseSafeMode: "在安全模式啟用期間將使用預設主題。關閉安全模式後會恢復原本的設定。" _order: newest: "最新的在前" oldest: "最舊的在前" @@ -1465,6 +1466,7 @@ _settings: contentsUpdateFrequency_description2: "當即時模式開啟時,不論此設定為何,內容都會即時更新。" showUrlPreview: "顯示網址預覽" showAvailableReactionsFirstInNote: "將可用的反應顯示在頂部" + showPageTabBarBottom: "在底部顯示頁面的標籤列" _chat: showSenderName: "顯示發送者的名稱" sendOnEnter: "按下 Enter 發送訊息" @@ -1549,7 +1551,7 @@ _initialAccountSetting: theseSettingsCanEditLater: "這裡的設定可以在之後變更。" youCanEditMoreSettingsInSettingsPageLater: "除此之外,還可以在「設定」頁面進行各種設定。之後請確認看看。" followUsers: "為了構築時間軸,試著追隨您感興趣的使用者吧。" - pushNotificationDescription: "啟用推送通知後,就可以在裝置上接收來自{name}的通知了。" + pushNotificationDescription: "啟用推送通知後,就可以在裝置上接收來自 {name} 的通知了。" initialAccountSettingCompleted: "初始設定完成了!" haveFun: "盡情享受{name}吧!" youCanContinueTutorial: "您可以繼續學習如何使用{name}(Misskey),也可以就此打住,立即開始使用。" @@ -1998,6 +2000,7 @@ _role: descriptionOfRateLimitFactor: "值越小限制越少,值越大限制越多。" canHideAds: "不顯示廣告" canSearchNotes: "可否搜尋貼文" + canSearchUsers: "可使用使用者搜尋功能" canUseTranslator: "使用翻譯功能" avatarDecorationLimit: "頭像可掛上的最大裝飾數量" canImportAntennas: "允許匯入天線" @@ -3163,10 +3166,10 @@ _watermarkEditor: type: "類型" image: "圖片" advanced: "進階" + angle: "角度" stripe: "條紋" stripeWidth: "線條寬度" stripeFrequency: "線條數量" - angle: "角度" polkadot: "波卡圓點" checker: "棋盤格" polkadotMainDotOpacity: "主圓點的不透明度" @@ -3178,6 +3181,7 @@ _imageEffector: title: "特效" addEffect: "新增特效" discardChangesConfirm: "捨棄更改並退出嗎?" + nothingToConfigure: "無可設定的項目" _fxs: chromaticAberration: "色差" glitch: "異常雜訊效果" @@ -3195,6 +3199,38 @@ _imageEffector: checker: "棋盤格" blockNoise: "阻擋雜訊" tearing: "撕裂" + _fxProps: + angle: "角度" + scale: "大小" + size: "大小" + color: "顏色" + opacity: "透明度" + normalize: "正規化" + amount: "數量" + lightness: "亮度" + contrast: "對比度" + hue: "色相" + brightness: "亮度" + saturation: "彩度" + max: "最大值" + min: "最小值" + direction: "方向" + phase: "相位" + frequency: "頻率" + strength: "強度" + glitchChannelShift: "偏移" + seed: "種子值" + redComponent: "紅色成分" + greenComponent: "綠色成分" + blueComponent: "青色成分" + threshold: "閾值" + centerX: "X中心座標" + centerY: "Y中心座標" + zoomLinesSmoothing: "平滑化" + zoomLinesSmoothingDescription: "平滑化與集中線寬度設定不能同時使用。" + zoomLinesThreshold: "集中線的寬度" + zoomLinesMaskSize: "中心直徑" + zoomLinesBlack: "變成黑色" drafts: "草稿\n" _drafts: select: "選擇草槁" diff --git a/package.json b/package.json index 4ed9498433..b2dbf74c3b 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "misskey", - "version": "2025.8.0-alpha.5", + "version": "2025.8.0-alpha.12", "codename": "nasubi", "repository": { "type": "git", "url": "https://github.com/misskey-dev/misskey.git" }, - "packageManager": "pnpm@10.13.1", + "packageManager": "pnpm@10.14.0", "workspaces": [ "packages/frontend-shared", "packages/frontend", @@ -53,7 +53,7 @@ }, "dependencies": { "cssnano": "7.1.0", - "esbuild": "0.25.6", + "esbuild": "0.25.8", "execa": "9.6.0", "fast-glob": "3.3.3", "glob": "11.0.3", @@ -62,20 +62,20 @@ "postcss": "8.5.6", "tar": "7.4.3", "terser": "5.43.1", - "typescript": "5.8.3" + "typescript": "5.9.2" }, "devDependencies": { "@misskey-dev/eslint-plugin": "2.1.0", - "@types/node": "22.16.4", - "@typescript-eslint/eslint-plugin": "8.37.0", - "@typescript-eslint/parser": "8.37.0", + "@types/node": "22.17.1", + "@typescript-eslint/eslint-plugin": "8.39.0", + "@typescript-eslint/parser": "8.39.0", "cross-env": "7.0.3", - "cypress": "14.5.2", - "eslint": "9.31.0", + "cypress": "14.5.4", + "eslint": "9.33.0", "globals": "16.3.0", "ncp": "2.0.0", - "pnpm": "10.13.1", - "start-server-and-test": "2.0.12" + "pnpm": "10.14.0", + "start-server-and-test": "2.0.13" }, "optionalDependencies": { "@tensorflow/tfjs-core": "4.22.0" diff --git a/packages/backend/migration/1755168347001-PageCountInNote.js b/packages/backend/migration/1755168347001-PageCountInNote.js new file mode 100644 index 0000000000..9f1894ab2f --- /dev/null +++ b/packages/backend/migration/1755168347001-PageCountInNote.js @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class PageCountInNote1755168347001 { + name = 'PageCountInNote1755168347001' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "note" ADD "pageCount" smallint NOT NULL DEFAULT '0'`); + + // Update existing notes + // block_list CTE collects all page blocks on the pages including child blocks in the section blocks. + // The clipped_notes CTE counts how many distinct pages each note block is referenced in. + // Finally, we update the note table with the count of pages for each referenced note. + await queryRunner.query(` + WITH RECURSIVE block_list AS ( + ( + SELECT + page.id as page_id, + block as block + FROM page + CROSS JOIN LATERAL jsonb_array_elements(page.content) block + WHERE block->>'type' = 'note' OR block->>'type' = 'section' + ) + UNION ALL + ( + SELECT + block_list.page_id, + child_block AS block + FROM LATERAL ( + SELECT page_id, block + FROM block_list + WHERE block_list.block->>'type' = 'section' + ) block_list + CROSS JOIN LATERAL jsonb_array_elements(block_list.block->'children') child_block + WHERE child_block->>'type' = 'note' OR child_block->>'type' = 'section' + ) + ), + clipped_notes AS ( + SELECT + (block->>'note') AS note_id, + COUNT(distinct block_list.page_id) AS count + FROM block_list + WHERE block_list.block->>'type' = 'note' + GROUP BY block->>'note' + ) + UPDATE note + SET "pageCount" = clipped_notes.count + FROM clipped_notes + WHERE note.id = clipped_notes.note_id; + `); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "pageCount"`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index b62dd46790..13ec9a862d 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -38,17 +38,17 @@ }, "optionalDependencies": { "@swc/core-android-arm64": "1.3.11", - "@swc/core-darwin-arm64": "1.12.0", - "@swc/core-darwin-x64": "1.12.0", + "@swc/core-darwin-arm64": "1.13.3", + "@swc/core-darwin-x64": "1.13.3", "@swc/core-freebsd-x64": "1.3.11", - "@swc/core-linux-arm-gnueabihf": "1.12.0", - "@swc/core-linux-arm64-gnu": "1.12.0", - "@swc/core-linux-arm64-musl": "1.12.0", - "@swc/core-linux-x64-gnu": "1.12.0", - "@swc/core-linux-x64-musl": "1.12.0", - "@swc/core-win32-arm64-msvc": "1.12.0", - "@swc/core-win32-ia32-msvc": "1.12.0", - "@swc/core-win32-x64-msvc": "1.12.0", + "@swc/core-linux-arm-gnueabihf": "1.13.3", + "@swc/core-linux-arm64-gnu": "1.13.3", + "@swc/core-linux-arm64-musl": "1.13.3", + "@swc/core-linux-x64-gnu": "1.13.3", + "@swc/core-linux-x64-musl": "1.13.3", + "@swc/core-win32-arm64-msvc": "1.13.3", + "@swc/core-win32-ia32-msvc": "1.13.3", + "@swc/core-win32-x64-msvc": "1.13.3", "@tensorflow/tfjs": "4.22.0", "@tensorflow/tfjs-node": "4.22.0", "bufferutil": "4.0.9", @@ -68,8 +68,8 @@ "utf-8-validate": "6.0.5" }, "dependencies": { - "@aws-sdk/client-s3": "3.826.0", - "@aws-sdk/lib-storage": "3.826.0", + "@aws-sdk/client-s3": "3.864.0", + "@aws-sdk/lib-storage": "3.864.0", "@discordapp/twemoji": "16.0.1", "@fastify/accepts": "5.0.2", "@fastify/cookie": "11.0.2", @@ -80,19 +80,19 @@ "@fastify/static": "8.2.0", "@fastify/view": "10.0.2", "@misskey-dev/sharp-read-bmp": "1.2.0", - "@misskey-dev/summaly": "5.2.1", - "@napi-rs/canvas": "0.1.71", - "@nestjs/common": "11.1.3", - "@nestjs/core": "11.1.3", - "@nestjs/testing": "11.1.3", + "@misskey-dev/summaly": "5.2.3", + "@napi-rs/canvas": "0.1.77", + "@nestjs/common": "11.1.6", + "@nestjs/core": "11.1.6", + "@nestjs/testing": "11.1.6", "@peertube/http-signature": "1.7.0", "@sentry/node": "8.55.0", "@sentry/profiling-node": "8.55.0", "@simplewebauthn/server": "12.0.0", "@sinonjs/fake-timers": "11.3.1", "@smithy/node-http-handler": "2.5.0", - "@swc/cli": "0.7.7", - "@swc/core": "1.12.0", + "@swc/cli": "0.7.8", + "@swc/core": "1.13.3", "@twemoji/parser": "16.0.0", "@types/redis-info": "3.0.3", "accepts": "1.3.8", @@ -102,10 +102,10 @@ "bcryptjs": "2.4.3", "blurhash": "2.0.5", "body-parser": "1.20.3", - "bullmq": "5.53.2", + "bullmq": "5.56.9", "cacheable-lookup": "7.0.0", "cbor": "9.0.2", - "chalk": "5.4.1", + "chalk": "5.5.0", "chalk-template": "1.1.0", "chokidar": "4.0.3", "cli-highlight": "2.1.11", @@ -113,18 +113,18 @@ "content-disposition": "0.5.4", "date-fns": "2.30.0", "deep-email-validator": "0.1.21", - "fastify": "5.3.3", + "fastify": "5.4.0", "fastify-raw-body": "5.0.0", "feed": "4.2.2", "file-type": "19.6.0", "fluent-ffmpeg": "2.1.3", - "form-data": "4.0.3", + "form-data": "4.0.4", "got": "14.4.7", "happy-dom": "16.8.1", "hpagent": "1.2.0", "htmlescape": "1.1.1", "http-link-header": "1.1.3", - "ioredis": "5.6.1", + "ioredis": "5.7.0", "ip-cidr": "4.0.2", "ipaddr.js": "2.2.0", "is-svg": "5.1.0", @@ -136,7 +136,7 @@ "juice": "11.0.1", "meilisearch": "0.51.0", "mfm-js": "0.25.0", - "microformats-parser": "2.0.3", + "microformats-parser": "2.0.4", "mime-types": "2.1.35", "misskey-js": "workspace:*", "misskey-reversi": "workspace:*", @@ -152,7 +152,7 @@ "os-utils": "0.0.14", "otpauth": "9.4.0", "parse5": "7.3.0", - "pg": "8.16.0", + "pg": "8.16.3", "pkce-challenge": "4.1.0", "probe-image-size": "7.2.3", "promise-limit": "2.7.0", @@ -174,25 +174,25 @@ "slacc": "0.0.10", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", - "systeminformation": "5.27.1", + "systeminformation": "5.27.7", "tinycolor2": "1.6.0", "tmp": "0.2.3", "tsc-alias": "1.8.16", "tsconfig-paths": "4.2.0", - "typeorm": "0.3.24", - "typescript": "5.8.3", + "typeorm": "0.3.25", + "typescript": "5.9.2", "ulid": "2.4.0", "vary": "1.1.2", "web-push": "3.6.7", - "ws": "8.18.2", + "ws": "8.18.3", "xev": "3.0.2" }, "devDependencies": { "@jest/globals": "29.7.0", - "@nestjs/platform-express": "10.4.19", - "@sentry/vue": "9.28.0", + "@nestjs/platform-express": "10.4.20", + "@sentry/vue": "9.45.0", "@simplewebauthn/types": "12.0.0", - "@swc/jest": "0.2.38", + "@swc/jest": "0.2.39", "@types/accepts": "1.3.7", "@types/archiver": "6.0.3", "@types/bcryptjs": "2.4.6", @@ -209,12 +209,12 @@ "@types/jsrsasign": "10.5.15", "@types/mime-types": "2.1.4", "@types/ms": "0.7.34", - "@types/node": "22.15.31", + "@types/node": "22.17.1", "@types/nodemailer": "6.4.17", "@types/oauth": "0.9.6", "@types/oauth2orize": "1.11.5", "@types/oauth2orize-pkce": "0.1.2", - "@types/pg": "8.15.4", + "@types/pg": "8.15.5", "@types/pug": "2.0.10", "@types/qrcode": "1.5.5", "@types/random-seed": "0.3.5", @@ -230,11 +230,11 @@ "@types/vary": "1.1.3", "@types/web-push": "3.6.4", "@types/ws": "8.18.1", - "@typescript-eslint/eslint-plugin": "8.34.0", - "@typescript-eslint/parser": "8.34.0", + "@typescript-eslint/eslint-plugin": "8.39.0", + "@typescript-eslint/parser": "8.39.0", "aws-sdk-client-mock": "4.1.0", "cross-env": "7.0.3", - "eslint-plugin-import": "2.31.0", + "eslint-plugin-import": "2.32.0", "execa": "8.0.1", "fkill": "9.0.0", "jest": "29.7.0", @@ -242,6 +242,6 @@ "nodemon": "3.1.10", "pid-port": "1.0.2", "simple-oauth2": "5.1.0", - "supertest": "7.1.1" + "supertest": "7.1.4" } } diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index 0c0c5d3a39..a30bff0fe4 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -78,6 +78,7 @@ import { ChannelFollowingService } from './ChannelFollowingService.js'; import { ChatService } from './ChatService.js'; import { RegistryApiService } from './RegistryApiService.js'; import { ReversiService } from './ReversiService.js'; +import { PageService } from './PageService.js'; import { ChartLoggerService } from './chart/ChartLoggerService.js'; import FederationChart from './chart/charts/federation.js'; @@ -227,6 +228,7 @@ const $ChannelFollowingService: Provider = { provide: 'ChannelFollowingService', const $ChatService: Provider = { provide: 'ChatService', useExisting: ChatService }; const $RegistryApiService: Provider = { provide: 'RegistryApiService', useExisting: RegistryApiService }; const $ReversiService: Provider = { provide: 'ReversiService', useExisting: ReversiService }; +const $PageService: Provider = { provide: 'PageService', useExisting: PageService }; const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService }; const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart }; @@ -379,6 +381,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting ChatService, RegistryApiService, ReversiService, + PageService, ChartLoggerService, FederationChart, @@ -527,6 +530,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $ChatService, $RegistryApiService, $ReversiService, + $PageService, $ChartLoggerService, $FederationChart, @@ -676,6 +680,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting ChatService, RegistryApiService, ReversiService, + PageService, FederationChart, NotesChart, @@ -822,6 +827,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $ChatService, $RegistryApiService, $ReversiService, + $PageService, $FederationChart, $NotesChart, diff --git a/packages/backend/src/core/HttpRequestService.ts b/packages/backend/src/core/HttpRequestService.ts index 3ddfe52045..f7973cbb66 100644 --- a/packages/backend/src/core/HttpRequestService.ts +++ b/packages/backend/src/core/HttpRequestService.ts @@ -6,6 +6,7 @@ import * as http from 'node:http'; import * as https from 'node:https'; import * as net from 'node:net'; +import * as stream from 'node:stream'; import ipaddr from 'ipaddr.js'; import CacheableLookup from 'cacheable-lookup'; import fetch from 'node-fetch'; @@ -26,12 +27,6 @@ export type HttpRequestSendOptions = { validators?: ((res: Response) => void)[]; }; -declare module 'node:http' { - interface Agent { - createConnection(options: net.NetConnectOpts, callback?: (err: unknown, stream: net.Socket) => void): net.Socket; - } -} - class HttpRequestServiceAgent extends http.Agent { constructor( private config: Config, @@ -41,11 +36,11 @@ class HttpRequestServiceAgent extends http.Agent { } @bindThis - public createConnection(options: net.NetConnectOpts, callback?: (err: unknown, stream: net.Socket) => void): net.Socket { + public createConnection(options: http.ClientRequestArgs, callback?: (err: Error | null, stream: stream.Duplex) => void): stream.Duplex { const socket = super.createConnection(options, callback) .on('connect', () => { - const address = socket.remoteAddress; - if (process.env.NODE_ENV === 'production') { + if (socket instanceof net.Socket && process.env.NODE_ENV === 'production') { + const address = socket.remoteAddress; if (address && ipaddr.isValid(address)) { if (this.isPrivateIp(address)) { socket.destroy(new Error(`Blocked address: ${address}`)); @@ -80,11 +75,11 @@ class HttpsRequestServiceAgent extends https.Agent { } @bindThis - public createConnection(options: net.NetConnectOpts, callback?: (err: unknown, stream: net.Socket) => void): net.Socket { + public createConnection(options: http.ClientRequestArgs, callback?: (err: Error | null, stream: stream.Duplex) => void): stream.Duplex { const socket = super.createConnection(options, callback) .on('connect', () => { - const address = socket.remoteAddress; - if (process.env.NODE_ENV === 'production') { + if (socket instanceof net.Socket && process.env.NODE_ENV === 'production') { + const address = socket.remoteAddress; if (address && ipaddr.isValid(address)) { if (this.isPrivateIp(address)) { socket.destroy(new Error(`Blocked address: ${address}`)); diff --git a/packages/backend/src/core/PageService.ts b/packages/backend/src/core/PageService.ts new file mode 100644 index 0000000000..7f0e5c7ccc --- /dev/null +++ b/packages/backend/src/core/PageService.ts @@ -0,0 +1,223 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { DataSource, In, Not } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import { + type NotesRepository, + MiPage, + type PagesRepository, + MiDriveFile, + type UsersRepository, + MiNote, +} from '@/models/_.js'; +import { bindThis } from '@/decorators.js'; +import { RoleService } from '@/core/RoleService.js'; +import { IdService } from '@/core/IdService.js'; +import type { MiUser } from '@/models/User.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; + +export interface PageBody { + title: string; + name: string; + summary: string | null; + content: Array>; + variables: Array>; + script: string; + eyeCatchingImage?: MiDriveFile | null; + font: string; + alignCenter: boolean; + hideTitleWhenPinned: boolean; +} + +@Injectable() +export class PageService { + constructor( + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private roleService: RoleService, + private moderationLogService: ModerationLogService, + private idService: IdService, + ) { + } + + @bindThis + public async create( + me: MiUser, + body: PageBody, + ): Promise { + await this.pagesRepository.findBy({ + userId: me.id, + name: body.name, + }).then(result => { + if (result.length > 0) { + throw new IdentifiableError('1a79e38e-3d83-4423-845b-a9d83ff93b61'); + } + }); + + const page = await this.pagesRepository.insertOne(new MiPage({ + id: this.idService.gen(), + updatedAt: new Date(), + title: body.title, + name: body.name, + summary: body.summary, + content: body.content, + variables: body.variables, + script: body.script, + eyeCatchingImageId: body.eyeCatchingImage ? body.eyeCatchingImage.id : null, + userId: me.id, + visibility: 'public', + alignCenter: body.alignCenter, + hideTitleWhenPinned: body.hideTitleWhenPinned, + font: body.font, + })); + + const referencedNotes = this.collectReferencedNotes(page.content); + if (referencedNotes.length > 0) { + await this.notesRepository.increment({ id: In(referencedNotes) }, 'pageCount', 1); + } + + return page; + } + + @bindThis + public async update( + me: MiUser, + pageId: MiPage['id'], + body: Partial, + ): Promise { + await this.db.transaction(async (transaction) => { + const page = await transaction.findOne(MiPage, { + where: { + id: pageId, + }, + lock: { mode: 'for_no_key_update' }, + }); + + if (page == null) { + throw new IdentifiableError('66aefd3c-fdb2-4a71-85ae-cc18bea85d3f'); + } + if (page.userId !== me.id) { + throw new IdentifiableError('d0017699-8256-46f1-aed4-bc03bed73616'); + } + + if (body.name != null) { + await transaction.findBy(MiPage, { + id: Not(pageId), + userId: me.id, + name: body.name, + }).then(result => { + if (result.length > 0) { + throw new IdentifiableError('d05bfe24-24b6-4ea2-a3ec-87cc9bf4daa4'); + } + }); + } + + await transaction.update(MiPage, page.id, { + updatedAt: new Date(), + title: body.title, + name: body.name, + summary: body.summary === undefined ? page.summary : body.summary, + content: body.content, + variables: body.variables, + script: body.script, + alignCenter: body.alignCenter, + hideTitleWhenPinned: body.hideTitleWhenPinned, + font: body.font, + eyeCatchingImageId: body.eyeCatchingImage === undefined ? undefined : (body.eyeCatchingImage?.id ?? null), + }); + + console.log("page.content", page.content); + + if (body.content != null) { + const beforeReferencedNotes = this.collectReferencedNotes(page.content); + const afterReferencedNotes = this.collectReferencedNotes(body.content); + + const removedNotes = beforeReferencedNotes.filter(noteId => !afterReferencedNotes.includes(noteId)); + const addedNotes = afterReferencedNotes.filter(noteId => !beforeReferencedNotes.includes(noteId)); + + if (removedNotes.length > 0) { + await transaction.decrement(MiNote, { id: In(removedNotes) }, 'pageCount', 1); + } + if (addedNotes.length > 0) { + await transaction.increment(MiNote, { id: In(addedNotes) }, 'pageCount', 1); + } + } + }); + } + + @bindThis + public async delete(me: MiUser, pageId: MiPage['id']): Promise { + await this.db.transaction(async (transaction) => { + const page = await transaction.findOne(MiPage, { + where: { + id: pageId, + }, + lock: { mode: 'pessimistic_write' }, // same lock level as DELETE + }); + + if (page == null) { + throw new IdentifiableError('66aefd3c-fdb2-4a71-85ae-cc18bea85d3f'); + } + + if (!await this.roleService.isModerator(me) && page.userId !== me.id) { + throw new IdentifiableError('d0017699-8256-46f1-aed4-bc03bed73616'); + } + + await transaction.delete(MiPage, page.id); + + if (page.userId !== me.id) { + const user = await this.usersRepository.findOneByOrFail({ id: page.userId }); + this.moderationLogService.log(me, 'deletePage', { + pageId: page.id, + pageUserId: page.userId, + pageUserUsername: user.username, + page, + }); + } + + const referencedNotes = this.collectReferencedNotes(page.content); + if (referencedNotes.length > 0) { + await transaction.decrement(MiNote, { id: In(referencedNotes) }, 'pageCount', 1); + } + }); + } + + collectReferencedNotes(content: MiPage['content']): string[] { + const referencingNotes = new Set(); + const recursiveCollect = (content: unknown[]) => { + for (const contentElement of content) { + if (typeof contentElement === 'object' + && contentElement !== null + && 'type' in contentElement) { + if (contentElement.type === 'note' + && 'note' in contentElement + && typeof contentElement.note === 'string') { + referencingNotes.add(contentElement.note); + } + if (contentElement.type === 'section' + && 'children' in contentElement + && Array.isArray(contentElement.children)) { + recursiveCollect(contentElement.children); + } + } + } + }; + recursiveCollect(content); + return [...referencingNotes]; + } +} diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 4be568b334..0f225a8242 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -103,6 +103,7 @@ export class QueueService { for (const def of REPEATABLE_SYSTEM_JOB_DEF) { this.systemQueue.upsertJobScheduler(def.name, { pattern: def.pattern, + immediately: false, }, { name: def.name, opts: { diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index cddfc0094e..3df7ee69ee 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -43,6 +43,7 @@ export type RolePolicies = { canManageCustomEmojis: boolean; canManageAvatarDecorations: boolean; canSearchNotes: boolean; + canSearchUsers: boolean; canUseTranslator: boolean; canHideAds: boolean; driveCapacityMb: number; @@ -82,6 +83,7 @@ export const DEFAULT_POLICIES: RolePolicies = { canManageCustomEmojis: false, canManageAvatarDecorations: false, canSearchNotes: false, + canSearchUsers: true, canUseTranslator: true, canHideAds: false, driveCapacityMb: 100, @@ -402,6 +404,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { canManageCustomEmojis: calc('canManageCustomEmojis', vs => vs.some(v => v === true)), canManageAvatarDecorations: calc('canManageAvatarDecorations', vs => vs.some(v => v === true)), canSearchNotes: calc('canSearchNotes', vs => vs.some(v => v === true)), + canSearchUsers: calc('canSearchUsers', vs => vs.some(v => v === true)), canUseTranslator: calc('canUseTranslator', vs => vs.some(v => v === true)), canHideAds: calc('canHideAds', vs => vs.some(v => v === true)), driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)), diff --git a/packages/backend/src/core/WebhookTestService.ts b/packages/backend/src/core/WebhookTestService.ts index 9cf985b688..907b5ea6be 100644 --- a/packages/backend/src/core/WebhookTestService.ts +++ b/packages/backend/src/core/WebhookTestService.ts @@ -85,6 +85,7 @@ function generateDummyNote(override?: Partial): MiNote { renoteCount: 10, repliesCount: 5, clippedCount: 0, + pageCount: 0, reactions: {}, visibility: 'public', uri: null, diff --git a/packages/backend/src/models/Note.ts b/packages/backend/src/models/Note.ts index ff46615729..26d5c1d535 100644 --- a/packages/backend/src/models/Note.ts +++ b/packages/backend/src/models/Note.ts @@ -114,6 +114,13 @@ export class MiNote { }) public clippedCount: number; + // The number of note page blocks referencing this note. + // This column is used by Remote Note Cleaning and manually updated rather than automatically with triggers. + @Column('smallint', { + default: 0, + }) + public pageCount: number; + @Column('jsonb', { default: {}, }) diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts index c9cdbd5d89..0b9234cb81 100644 --- a/packages/backend/src/models/json-schema/role.ts +++ b/packages/backend/src/models/json-schema/role.ts @@ -212,6 +212,10 @@ export const packedRolePoliciesSchema = { type: 'boolean', optional: false, nullable: false, }, + canSearchUsers: { + type: 'boolean', + optional: false, nullable: false, + }, canUseTranslator: { type: 'boolean', optional: false, nullable: false, diff --git a/packages/backend/src/queue/processors/CleanRemoteNotesProcessorService.ts b/packages/backend/src/queue/processors/CleanRemoteNotesProcessorService.ts index 6c64d6aa39..f53d403280 100644 --- a/packages/backend/src/queue/processors/CleanRemoteNotesProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanRemoteNotesProcessorService.ts @@ -5,9 +5,9 @@ import { setTimeout } from 'node:timers/promises'; import { Inject, Injectable } from '@nestjs/common'; -import { And, Brackets, In, IsNull, LessThan, MoreThan, Not } from 'typeorm'; +import { DataSource, IsNull, LessThan, QueryFailedError, Not } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { MiMeta, MiNote, NoteFavoritesRepository, NotesRepository, UserNotePiningsRepository } from '@/models/_.js'; +import type { MiMeta, MiNote, NotesRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; @@ -25,11 +25,8 @@ export class CleanRemoteNotesProcessorService { @Inject(DI.notesRepository) private notesRepository: NotesRepository, - @Inject(DI.noteFavoritesRepository) - private noteFavoritesRepository: NoteFavoritesRepository, - - @Inject(DI.userNotePiningsRepository) - private userNotePiningsRepository: UserNotePiningsRepository, + @Inject(DI.db) + private db: DataSource, private idService: IdService, private queueLoggerService: QueueLoggerService, @@ -37,12 +34,22 @@ export class CleanRemoteNotesProcessorService { this.logger = this.queueLoggerService.logger.createSubLogger('clean-remote-notes'); } + @bindThis + private computeProgress(minId: string, maxId: string, cursorLeft: string) { + const minTs = this.idService.parse(minId).date.getTime(); + const maxTs = this.idService.parse(maxId).date.getTime(); + const cursorTs = this.idService.parse(cursorLeft).date.getTime(); + + return ((cursorTs - minTs) / (maxTs - minTs)) * 100; + } + @bindThis public async process(job: Bull.Job>): Promise<{ deletedCount: number; oldest: number | null; newest: number | null; - skipped?: boolean; + skipped: boolean; + transientErrors: number; }> { if (!this.meta.enableRemoteNotesCleaning) { this.logger.info('Remote notes cleaning is disabled, skipping...'); @@ -51,6 +58,7 @@ export class CleanRemoteNotesProcessorService { oldest: null, newest: null, skipped: true, + transientErrors: 0, }; } @@ -59,7 +67,106 @@ export class CleanRemoteNotesProcessorService { const maxDuration = this.meta.remoteNotesCleaningMaxProcessingDurationInMinutes * 60 * 1000; // Convert minutes to milliseconds const startAt = Date.now(); - const MAX_NOTE_COUNT_PER_QUERY = 50; + //#region queries + // The date limit for the newest note to be considered for deletion. + // All notes newer than this limit will always be retained. + const newestLimit = this.idService.gen(Date.now() - (1000 * 60 * 60 * 24 * this.meta.remoteNotesCleaningExpiryDaysForEachNotes)); + + // The condition for removing the notes. + // The note must be: + // - old enough (older than the newestLimit) + // - a remote note (userHost is not null). + // - not have clipped + // - not have pinned on the user profile + // - not has been favorite by any user + const removalCriteria = [ + 'note."id" < :newestLimit', + 'note."clippedCount" = 0', + 'note."pageCount" = 0', + 'note."userHost" IS NOT NULL', + 'NOT EXISTS (SELECT 1 FROM user_note_pining WHERE "noteId" = note."id")', + 'NOT EXISTS (SELECT 1 FROM note_favorite WHERE "noteId" = note."id")', + ].join(' AND '); + + const minId = (await this.notesRepository.createQueryBuilder('note') + .select('MIN(note.id)', 'minId') + .where({ + id: LessThan(newestLimit), + userHost: Not(IsNull()), + replyId: IsNull(), + renoteId: IsNull(), + }) + .getRawOne<{ minId?: MiNote['id'] }>())?.minId; + + if (!minId) { + this.logger.info('No notes can possibly be deleted, skipping...'); + return { + deletedCount: 0, + oldest: null, + newest: null, + skipped: false, + transientErrors: 0, + }; + } + + // start with a conservative limit and adjust it based on the query duration + const minimumLimit = 10; + let currentLimit = 100; + let cursorLeft = '0'; + + const candidateNotesCteName = 'candidate_notes'; + + // tree walk down all root notes, short-circuit when the first unremovable note is found + const candidateNotesQueryBase = this.notesRepository.createQueryBuilder('note') + .select('note."id"', 'id') + .addSelect('note."replyId"', 'replyId') + .addSelect('note."renoteId"', 'renoteId') + .addSelect('note."id"', 'rootId') + .addSelect('TRUE', 'isRemovable') + .addSelect('TRUE', 'isBase') + .where('note."id" > :cursorLeft') + .andWhere(removalCriteria) + .andWhere({ replyId: IsNull(), renoteId: IsNull() }); + + const candidateNotesQueryInductive = this.notesRepository.createQueryBuilder('note') + .select('note.id', 'id') + .addSelect('note."replyId"', 'replyId') + .addSelect('note."renoteId"', 'renoteId') + .addSelect('parent."rootId"', 'rootId') + .addSelect(removalCriteria, 'isRemovable') + .addSelect('FALSE', 'isBase') + .innerJoin(candidateNotesCteName, 'parent', 'parent."id" = note."replyId" OR parent."id" = note."renoteId"') + .where('parent."isRemovable" = TRUE'); + + // A note tree can be deleted if there are no unremovable rows with the same rootId. + // + // `candidate_notes` will have the following structure after recursive query (some columns omitted): + // After performing a LEFT JOIN with `candidate_notes` as `unremovable`, + // the note tree containing unremovable notes will be anti-joined. + // For removable rows, the `unremovable` columns will have `NULL` values. + // | id | rootId | isRemovable | + // |-----|--------|-------------| + // | aaa | aaa | TRUE | + // | bbb | aaa | FALSE | + // | ccc | aaa | FALSE | + // | ddd | ddd | TRUE | + // | eee | ddd | TRUE | + // | fff | fff | TRUE | + // | ggg | ggg | FALSE | + // + const candidateNotesQuery = this.db.createQueryBuilder() + .select(`"${candidateNotesCteName}"."id"`, 'id') + .addSelect('unremovable."id" IS NULL', 'isRemovable') + .addSelect(`BOOL_OR("${candidateNotesCteName}"."isBase")`, 'isBase') + .addCommonTableExpression( + `((SELECT "base".* FROM (${candidateNotesQueryBase.orderBy('note.id', 'ASC').limit(currentLimit).getQuery()}) AS "base") UNION ${candidateNotesQueryInductive.getQuery()})`, + candidateNotesCteName, + { recursive: true }, + ) + .from(candidateNotesCteName, candidateNotesCteName) + .leftJoin(candidateNotesCteName, 'unremovable', `unremovable."rootId" = "${candidateNotesCteName}"."rootId" AND unremovable."isRemovable" = FALSE`) + .groupBy(`"${candidateNotesCteName}"."id"`) + .addGroupBy('unremovable."id" IS NULL'); const stats = { deletedCount: 0, @@ -67,111 +174,107 @@ export class CleanRemoteNotesProcessorService { newest: null as number | null, }; - // The date limit for the newest note to be considered for deletion. - // All notes newer than this limit will always be retained. - const newestLimit = this.idService.gen(Date.now() - (1000 * 60 * 60 * 24 * this.meta.remoteNotesCleaningExpiryDaysForEachNotes)); - - let cursor = '0'; // oldest note ID to start from - - while (true) { + let lowThroughputWarned = false; + let transientErrors = 0; + for (;;) { + //#region check time const batchBeginAt = Date.now(); - // We use string literals instead of query builder for several reasons: - // - for removeCondition, we need to use it in having clause, which is not supported by Brackets. - // - for recursive part, we need to preserve the order of columns, but typeorm query builder does not guarantee the order of columns in the result query + const elapsed = batchBeginAt - startAt; - // The condition for removing the notes. - // The note must be: - // - old enough (older than the newestLimit) - // - a remote note (userHost is not null). - // - not have clipped - // - not have pinned on the user profile - // - not has been favorite by any user - const removeCondition = 'note.id < :newestLimit' - + ' AND note."clippedCount" = 0' - + ' AND note."userHost" IS NOT NULL' - // using both userId and noteId instead of just noteId to use index on user_note_pining table. - // This is safe because notes are only pinned by the user who created them. - + ' AND NOT EXISTS(SELECT 1 FROM "user_note_pining" WHERE "noteId" = note."id" AND "userId" = note."userId")' - // We cannot use userId trick because users can favorite notes from other users. - + ' AND NOT EXISTS(SELECT 1 FROM "note_favorite" WHERE "noteId" = note."id")' - ; - - // The initiator query contains the oldest ${MAX_NOTE_COUNT_PER_QUERY} remote non-clipped notes - const initiatorQuery = ` - SELECT "note"."id" AS "id", "note"."replyId" AS "replyId", "note"."renoteId" AS "renoteId", "note"."id" AS "initiatorId" - FROM "note" "note" WHERE ${removeCondition} AND "note"."id" > :cursor ORDER BY "note"."id" ASC LIMIT ${MAX_NOTE_COUNT_PER_QUERY}`; - - // The union query queries the related notes and replies related to the initiator query - const unionQuery = ` - SELECT "note"."id", "note"."replyId", "note"."renoteId", rn."initiatorId" - FROM "note" "note" - INNER JOIN "related_notes" "rn" - ON "note"."replyId" = rn.id - OR "note"."renoteId" = rn.id - OR "note"."id" = rn."replyId" - OR "note"."id" = rn."renoteId" - `; - const recursiveQuery = `(${initiatorQuery}) UNION (${unionQuery})`; - - const removableInitiatorNotesQuery = this.notesRepository.createQueryBuilder('note') - .select('rn."initiatorId"') - .innerJoin('related_notes', 'rn', 'note.id = rn.id') - .groupBy('rn."initiatorId"') - .having(`bool_and(${removeCondition})`); - - const notesQuery = this.notesRepository.createQueryBuilder('note') - .addCommonTableExpression(recursiveQuery, 'related_notes', { recursive: true }) - .select('note.id', 'id') - .addSelect('rn."initiatorId"') - .innerJoin('related_notes', 'rn', 'note.id = rn.id') - .where(`rn."initiatorId" IN (${ removableInitiatorNotesQuery.getQuery() })`) - .setParameters({ cursor, newestLimit }); - - const notes: { id: MiNote['id'], initiatorId: MiNote['id'] }[] = await notesQuery.getRawMany(); - - const fetchedCount = notes.length; - - // update the cursor to the newest initiatorId found in the fetched notes. - // We don't use 'id' since the note can be newer than the initiator note. - for (const note of notes) { - if (cursor < note.initiatorId) { - cursor = note.initiatorId; - } - } - - if (notes.length > 0) { - await this.notesRepository.delete(notes.map(note => note.id)); - - for (const note of notes) { - const t = this.idService.parse(note.id).date.getTime(); - if (stats.oldest === null || t < stats.oldest) { - stats.oldest = t; - } - if (stats.newest === null || t > stats.newest) { - stats.newest = t; - } - } - - stats.deletedCount += notes.length; - } - - job.log(`Deleted ${notes.length} of ${fetchedCount}; ${Date.now() - batchBeginAt}ms`); - - const elapsed = Date.now() - startAt; + const progress = this.computeProgress(minId, newestLimit, cursorLeft > minId ? cursorLeft : minId); if (elapsed >= maxDuration) { - this.logger.info(`Reached maximum duration of ${maxDuration}ms, stopping...`); - job.log('Reached maximum duration, stopping cleaning.'); + job.log(`Reached maximum duration of ${maxDuration}ms, stopping... (last cursor: ${cursorLeft}, final progress ${progress}%)`); job.updateProgress(100); break; } - job.updateProgress((elapsed / maxDuration) * 100); + const wallClockUsage = elapsed / maxDuration; + if (wallClockUsage > 0.5 && progress < 50 && !lowThroughputWarned) { + const msg = `Not projected to finish in time! (wall clock usage ${wallClockUsage * 100}% at ${progress}%, current limit ${currentLimit})`; + this.logger.warn(msg); + job.log(msg); + lowThroughputWarned = true; + } + job.updateProgress(progress); + //#endregion - await setTimeout(1000 * 5); // Wait a moment to avoid overwhelming the db + const queryBegin = performance.now(); + let noteIds = null; + + try { + noteIds = await candidateNotesQuery.setParameters( + { newestLimit, cursorLeft }, + ).getRawMany<{ id: MiNote['id'], isRemovable: boolean, isBase: boolean }>(); + } catch (e) { + if (currentLimit > minimumLimit && e instanceof QueryFailedError && e.driverError?.code === '57014') { + // Statement timeout (maybe suddenly hit a large note tree), reduce the limit and try again + // continuous failures will eventually converge to currentLimit == minimumLimit and then throw + currentLimit = Math.max(minimumLimit, Math.floor(currentLimit * 0.25)); + continue; + } + throw e; + } + + if (noteIds.length === 0) { + job.log('No more notes to clean.'); + break; + } + + const queryDuration = performance.now() - queryBegin; + // try to adjust such that each query takes about 1~5 seconds and reasonable NodeJS heap so the task stays responsive + // this should not oscillate.. + if (queryDuration > 5000 || noteIds.length > 5000) { + currentLimit = Math.floor(currentLimit * 0.5); + } else if (queryDuration < 1000 && noteIds.length < 1000) { + currentLimit = Math.floor(currentLimit * 1.5); + } + // clamp to a sane range + currentLimit = Math.min(Math.max(currentLimit, minimumLimit), 5000); + + const deletableNoteIds = noteIds.filter(result => result.isRemovable).map(result => result.id); + if (deletableNoteIds.length > 0) { + try { + await this.notesRepository.delete(deletableNoteIds); + + for (const id of deletableNoteIds) { + const t = this.idService.parse(id).date.getTime(); + if (stats.oldest === null || t < stats.oldest) { + stats.oldest = t; + } + if (stats.newest === null || t > stats.newest) { + stats.newest = t; + } + } + + stats.deletedCount += deletableNoteIds.length; + } catch (e) { + // check for integrity violation errors (class 23) that might have occurred between the check and the delete + // we can safely continue to the next batch + if (e instanceof QueryFailedError && e.driverError?.code?.startsWith('23')) { + transientErrors++; + job.log(`Error deleting notes: ${e} (transient race condition?)`); + } else { + throw e; + } + } + } + + cursorLeft = noteIds.filter(result => result.isBase).reduce((max, { id }) => id > max ? id : max, cursorLeft); + + job.log(`Deleted ${noteIds.length} notes; ${Date.now() - batchBeginAt}ms`); + + if (process.env.NODE_ENV !== 'test') { + await setTimeout(Math.min(1000 * 5, queryDuration)); // Wait a moment to avoid overwhelming the db + } + }; + + if (transientErrors > 0) { + const msg = `${transientErrors} transient errors occurred while cleaning remote notes. You may need a second pass to complete the cleaning.`; + this.logger.warn(msg); + job.log(msg); } - this.logger.succ('cleaning of remote notes completed.'); return { @@ -179,6 +282,7 @@ export class CleanRemoteNotesProcessorService { oldest: stats.oldest, newest: stats.newest, skipped: false, + transientErrors, }; } } diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts index 14a53e0c42..b643c2a6d0 100644 --- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import type { DriveFilesRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; @@ -14,6 +14,7 @@ import type { MiNote } from '@/models/Note.js'; import { EmailService } from '@/core/EmailService.js'; import { bindThis } from '@/decorators.js'; import { SearchService } from '@/core/SearchService.js'; +import { PageService } from '@/core/PageService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbUserDeleteJobData } from '../types.js'; @@ -35,7 +36,11 @@ export class DeleteAccountProcessorService { @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, + @Inject(DI.pagesRepository) + private pagesRepository: PagesRepository, + private driveService: DriveService, + private pageService: PageService, private emailService: EmailService, private queueLoggerService: QueueLoggerService, private searchService: SearchService, @@ -112,6 +117,28 @@ export class DeleteAccountProcessorService { this.logger.succ('All of files deleted'); } + { + // delete pages. Necessary for decrementing pageCount of notes. + while (true) { + const pages = await this.pagesRepository.find({ + where: { + userId: user.id, + }, + take: 100, + order: { + id: 1, + }, + }); + + if (pages.length === 0) { + break; + } + for (const page of pages) { + await this.pageService.delete(user, page.id); + } + } + } + { // Send email notification const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); if (profile.email && profile.emailVerified) { diff --git a/packages/backend/src/server/api/endpoints/admin/system-webhook/create.ts b/packages/backend/src/server/api/endpoints/admin/system-webhook/create.ts index 28071e7a33..93d293ed41 100644 --- a/packages/backend/src/server/api/endpoints/admin/system-webhook/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/system-webhook/create.ts @@ -48,8 +48,8 @@ export const paramDef = { }, secret: { type: 'string', - minLength: 1, maxLength: 1024, + default: '', }, }, required: [ @@ -57,7 +57,6 @@ export const paramDef = { 'name', 'on', 'url', - 'secret', ], } as const; diff --git a/packages/backend/src/server/api/endpoints/admin/system-webhook/update.ts b/packages/backend/src/server/api/endpoints/admin/system-webhook/update.ts index 8d68bb8f87..e021806398 100644 --- a/packages/backend/src/server/api/endpoints/admin/system-webhook/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/system-webhook/update.ts @@ -52,8 +52,8 @@ export const paramDef = { }, secret: { type: 'string', - minLength: 1, maxLength: 1024, + default: '', }, }, required: [ @@ -62,7 +62,6 @@ export const paramDef = { 'name', 'on', 'url', - 'secret', ], } as const; diff --git a/packages/backend/src/server/api/endpoints/chat/read-all.ts b/packages/backend/src/server/api/endpoints/chat/read-all.ts index 2ed9497eef..e2d9601aa6 100644 --- a/packages/backend/src/server/api/endpoints/chat/read-all.ts +++ b/packages/backend/src/server/api/endpoints/chat/read-all.ts @@ -32,6 +32,8 @@ export default class extends Endpoint { // eslint- private chatService: ChatService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'read'); + await this.chatService.readAllChatMessages(me.id); }); } diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-chat-messages.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-chat-messages.ts index 5be477f468..b34ac4abd1 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/attached-chat-messages.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/attached-chat-messages.ts @@ -10,6 +10,7 @@ import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; import { ChatEntityService } from '@/core/entities/ChatEntityService.js'; +import { ChatService } from '@/core/ChatService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -60,14 +61,21 @@ export default class extends Endpoint { // eslint- @Inject(DI.chatMessagesRepository) private chatMessagesRepository: ChatMessagesRepository, + private chatService: ChatService, private chatEntityService: ChatEntityService, private queryService: QueryService, private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { + const isModerator = await this.roleService.isModerator(me); + + if (!isModerator) { + await this.chatService.checkChatAvailability(me.id, 'read'); + } + const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId, - userId: await this.roleService.isModerator(me) ? undefined : me.id, + userId: isModerator ? undefined : me.id, }); if (file == null) { diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index 6de5fe3d44..96bc2a953a 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -5,12 +5,13 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; -import type { DriveFilesRepository, PagesRepository } from '@/models/_.js'; -import { IdService } from '@/core/IdService.js'; -import { MiPage, pageNameSchema } from '@/models/Page.js'; +import type { DriveFilesRepository, MiDriveFile, PagesRepository } from '@/models/_.js'; +import { pageNameSchema } from '@/models/Page.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; import { DI } from '@/di-symbols.js'; +import { PageService } from '@/core/PageService.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -77,11 +78,11 @@ export default class extends Endpoint { // eslint- @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, + private pageService: PageService, private pageEntityService: PageEntityService, - private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { - let eyeCatchingImage = null; + let eyeCatchingImage: MiDriveFile | null = null; if (ps.eyeCatchingImageId != null) { eyeCatchingImage = await this.driveFilesRepository.findOneBy({ id: ps.eyeCatchingImageId, @@ -102,24 +103,20 @@ export default class extends Endpoint { // eslint- } }); - const page = await this.pagesRepository.insertOne(new MiPage({ - id: this.idService.gen(), - updatedAt: new Date(), - title: ps.title, - name: ps.name, - summary: ps.summary, - content: ps.content, - variables: ps.variables, - script: ps.script, - eyeCatchingImageId: eyeCatchingImage ? eyeCatchingImage.id : null, - userId: me.id, - visibility: 'public', - alignCenter: ps.alignCenter, - hideTitleWhenPinned: ps.hideTitleWhenPinned, - font: ps.font, - })); + try { + const page = await this.pageService.create(me, { + ...ps, + eyeCatchingImage, + summary: ps.summary ?? null, + }); - return await this.pageEntityService.pack(page); + return await this.pageEntityService.pack(page); + } catch (err) { + if (err instanceof IdentifiableError && err.id === '1a79e38e-3d83-4423-845b-a9d83ff93b61') { + throw new ApiError(meta.errors.nameAlreadyExists); + } + throw err; + } }); } } diff --git a/packages/backend/src/server/api/endpoints/pages/delete.ts b/packages/backend/src/server/api/endpoints/pages/delete.ts index f2bc946788..a33868552d 100644 --- a/packages/backend/src/server/api/endpoints/pages/delete.ts +++ b/packages/backend/src/server/api/endpoints/pages/delete.ts @@ -4,12 +4,14 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { PagesRepository, UsersRepository } from '@/models/_.js'; +import type { MiDriveFile, PagesRepository, UsersRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../error.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { PageService } from '@/core/PageService.js'; export const meta = { tags: ['pages'], @@ -44,36 +46,17 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.pagesRepository) - private pagesRepository: PagesRepository, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - private moderationLogService: ModerationLogService, - private roleService: RoleService, + private pageService: PageService, ) { super(meta, paramDef, async (ps, me) => { - const page = await this.pagesRepository.findOneBy({ id: ps.pageId }); - - if (page == null) { - throw new ApiError(meta.errors.noSuchPage); - } - - if (!await this.roleService.isModerator(me) && page.userId !== me.id) { - throw new ApiError(meta.errors.accessDenied); - } - - await this.pagesRepository.delete(page.id); - - if (page.userId !== me.id) { - const user = await this.usersRepository.findOneByOrFail({ id: page.userId }); - this.moderationLogService.log(me, 'deletePage', { - pageId: page.id, - pageUserId: page.userId, - pageUserUsername: user.username, - page, - }); + try { + await this.pageService.delete(me, ps.pageId); + } catch (err) { + if (err instanceof IdentifiableError) { + if (err.id === '66aefd3c-fdb2-4a71-85ae-cc18bea85d3f') throw new ApiError(meta.errors.noSuchPage); + if (err.id === 'd0017699-8256-46f1-aed4-bc03bed73616') throw new ApiError(meta.errors.accessDenied); + } + throw err; } }); } diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts index a6aeb6002e..6fa5c1d75c 100644 --- a/packages/backend/src/server/api/endpoints/pages/update.ts +++ b/packages/backend/src/server/api/endpoints/pages/update.ts @@ -4,13 +4,14 @@ */ import ms from 'ms'; -import { Not } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { PagesRepository, DriveFilesRepository } from '@/models/_.js'; +import type { DriveFilesRepository, MiDriveFile } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; import { pageNameSchema } from '@/models/Page.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { PageService } from '@/core/PageService.js'; export const meta = { tags: ['pages'], @@ -75,57 +76,37 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.pagesRepository) - private pagesRepository: PagesRepository, - @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, + + private pageService: PageService, ) { super(meta, paramDef, async (ps, me) => { - const page = await this.pagesRepository.findOneBy({ id: ps.pageId }); - if (page == null) { - throw new ApiError(meta.errors.noSuchPage); - } - if (page.userId !== me.id) { - throw new ApiError(meta.errors.accessDenied); - } + try { + let eyeCatchingImage: MiDriveFile | null | undefined | string = ps.eyeCatchingImageId; + if (eyeCatchingImage != null) { + eyeCatchingImage = await this.driveFilesRepository.findOneBy({ + id: eyeCatchingImage, + userId: me.id, + }); - if (ps.eyeCatchingImageId != null) { - const eyeCatchingImage = await this.driveFilesRepository.findOneBy({ - id: ps.eyeCatchingImageId, - userId: me.id, - }); - - if (eyeCatchingImage == null) { - throw new ApiError(meta.errors.noSuchFile); - } - } - - if (ps.name != null) { - await this.pagesRepository.findBy({ - id: Not(ps.pageId), - userId: me.id, - name: ps.name, - }).then(result => { - if (result.length > 0) { - throw new ApiError(meta.errors.nameAlreadyExists); + if (eyeCatchingImage == null) { + throw new ApiError(meta.errors.noSuchFile); } - }); - } + } - await this.pagesRepository.update(page.id, { - updatedAt: new Date(), - title: ps.title, - name: ps.name, - summary: ps.summary === undefined ? page.summary : ps.summary, - content: ps.content, - variables: ps.variables, - script: ps.script, - alignCenter: ps.alignCenter, - hideTitleWhenPinned: ps.hideTitleWhenPinned, - font: ps.font, - eyeCatchingImageId: ps.eyeCatchingImageId, - }); + await this.pageService.update(me, ps.pageId, { + ...ps, + eyeCatchingImage, + }); + } catch (err) { + if (err instanceof IdentifiableError) { + if (err.id === '66aefd3c-fdb2-4a71-85ae-cc18bea85d3f') throw new ApiError(meta.errors.noSuchPage); + if (err.id === 'd0017699-8256-46f1-aed4-bc03bed73616') throw new ApiError(meta.errors.accessDenied); + if (err.id === 'd05bfe24-24b6-4ea2-a3ec-87cc9bf4daa4') throw new ApiError(meta.errors.nameAlreadyExists); + } + throw err; + } }); } } diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts index 5d36847e03..c422286152 100644 --- a/packages/backend/src/server/api/endpoints/users/search.ts +++ b/packages/backend/src/server/api/endpoints/users/search.ts @@ -13,6 +13,7 @@ export const meta = { tags: ['users'], requireCredential: false, + requiredRolePolicy: 'canSearchUsers', description: 'Search for users.', diff --git a/packages/backend/src/server/api/stream/Connection.ts b/packages/backend/src/server/api/stream/Connection.ts index c9801d8314..8e28ab263b 100644 --- a/packages/backend/src/server/api/stream/Connection.ts +++ b/packages/backend/src/server/api/stream/Connection.ts @@ -32,7 +32,6 @@ export default class Connection { public subscriber: StreamEventEmitter; private channels: Channel[] = []; private subscribingNotes: Partial> = {}; - private cachedNotes: Packed<'Note'>[] = []; public userProfile: MiUserProfile | null = null; public following: Record | undefined> = {}; public followingChannels: Set = new Set(); @@ -132,26 +131,6 @@ export default class Connection { this.sendMessageToWs(data.type, data.body); } - @bindThis - public cacheNote(note: Packed<'Note'>) { - const add = (note: Packed<'Note'>) => { - const existIndex = this.cachedNotes.findIndex(n => n.id === note.id); - if (existIndex > -1) { - this.cachedNotes[existIndex] = note; - return; - } - - this.cachedNotes.unshift(note); - if (this.cachedNotes.length > 32) { - this.cachedNotes.splice(32); - } - }; - - add(note); - if (note.reply) add(note.reply); - if (note.renote) add(note.renote); - } - @bindThis private onReadNotification(payload: JsonValue | undefined) { this.notificationService.readAllNotification(this.user!.id); diff --git a/packages/backend/src/server/api/stream/channels/antenna.ts b/packages/backend/src/server/api/stream/channels/antenna.ts index 53dc7f18b6..e08562fdf9 100644 --- a/packages/backend/src/server/api/stream/channels/antenna.ts +++ b/packages/backend/src/server/api/stream/channels/antenna.ts @@ -43,8 +43,6 @@ class AntennaChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; - this.connection.cacheNote(note); - this.send('note', note); } else { this.send(data.type, data.body); diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index 7108e0cd6e..ac79c31854 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -49,8 +49,6 @@ class ChannelChannel extends Channel { } } - this.connection.cacheNote(note); - this.send('note', note); } diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts index 795980821b..d7c781ad12 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -65,8 +65,6 @@ class GlobalTimelineChannel extends Channel { } } - this.connection.cacheNote(note); - this.send('note', note); } diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts index 8105f15cb1..c911d63642 100644 --- a/packages/backend/src/server/api/stream/channels/hashtag.ts +++ b/packages/backend/src/server/api/stream/channels/hashtag.ts @@ -53,8 +53,6 @@ class HashtagChannel extends Channel { } } - this.connection.cacheNote(note); - this.send('note', note); } diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index 66644ed58c..157d9fc279 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -86,8 +86,6 @@ class HomeTimelineChannel extends Channel { } } - this.connection.cacheNote(note); - this.send('note', note); } diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index 5681311493..db5b4576be 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -100,8 +100,6 @@ class HybridTimelineChannel extends Channel { } } - this.connection.cacheNote(note); - this.send('note', note); } diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts index 2984e18774..3d7ed6acdb 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -75,8 +75,6 @@ class LocalTimelineChannel extends Channel { } } - this.connection.cacheNote(note); - this.send('note', note); } diff --git a/packages/backend/src/server/api/stream/channels/main.ts b/packages/backend/src/server/api/stream/channels/main.ts index 863d7f4c4e..525f24c105 100644 --- a/packages/backend/src/server/api/stream/channels/main.ts +++ b/packages/backend/src/server/api/stream/channels/main.ts @@ -39,7 +39,6 @@ class MainChannel extends Channel { const note = await this.noteEntityService.pack(data.body.note.id, this.user, { detail: true, }); - this.connection.cacheNote(note); data.body.note = note; } break; @@ -52,7 +51,6 @@ class MainChannel extends Channel { const note = await this.noteEntityService.pack(data.body.id, this.user, { detail: true, }); - this.connection.cacheNote(note); data.body = note; } break; diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index 4f38351e94..5bfd8fa68c 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -118,8 +118,6 @@ class UserListChannel extends Channel { } } - this.connection.cacheNote(note); - this.send('note', note); } diff --git a/packages/backend/test-federation/test/utils.ts b/packages/backend/test-federation/test/utils.ts index b5103e00be..7e24bb7904 100644 --- a/packages/backend/test-federation/test/utils.ts +++ b/packages/backend/test-federation/test/utils.ts @@ -190,7 +190,8 @@ export async function uploadFile( path = '../../test/resources/192.jpg', ): Promise { const filename = path.split('/').pop() ?? 'untitled'; - const blob = new Blob([await readFile(join(__dirname, path))]); + const buffer = await readFile(join(__dirname, path)); + const blob = new Blob([new Uint8Array(buffer)]); const body = new FormData(); body.append('i', user.i); diff --git a/packages/backend/test/unit/NoteCreateService.ts b/packages/backend/test/unit/NoteCreateService.ts index f2d4c8ffbb..23f409420e 100644 --- a/packages/backend/test/unit/NoteCreateService.ts +++ b/packages/backend/test/unit/NoteCreateService.ts @@ -40,6 +40,7 @@ describe('NoteCreateService', () => { renoteCount: 0, repliesCount: 0, clippedCount: 0, + pageCount: 0, reactions: {}, visibility: 'public', uri: null, diff --git a/packages/backend/test/unit/misc/is-renote.ts b/packages/backend/test/unit/misc/is-renote.ts index 0b713e8bf6..74d17abcb6 100644 --- a/packages/backend/test/unit/misc/is-renote.ts +++ b/packages/backend/test/unit/misc/is-renote.ts @@ -23,6 +23,7 @@ const base: MiNote = { renoteCount: 0, repliesCount: 0, clippedCount: 0, + pageCount: 0, reactions: {}, visibility: 'public', uri: null, diff --git a/packages/backend/test/unit/queue/processors/CleanRemoteNotesProcessorService.ts b/packages/backend/test/unit/queue/processors/CleanRemoteNotesProcessorService.ts new file mode 100644 index 0000000000..631e160afc --- /dev/null +++ b/packages/backend/test/unit/queue/processors/CleanRemoteNotesProcessorService.ts @@ -0,0 +1,652 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { jest } from '@jest/globals'; +import { Test, TestingModule } from '@nestjs/testing'; +import ms from 'ms'; +import { + type MiNote, + type MiUser, + type NotesRepository, + type NoteFavoritesRepository, + type UserNotePiningsRepository, + type UsersRepository, + type UserProfilesRepository, + MiMeta, +} from '@/models/_.js'; +import { CleanRemoteNotesProcessorService } from '@/queue/processors/CleanRemoteNotesProcessorService.js'; +import { DI } from '@/di-symbols.js'; +import { IdService } from '@/core/IdService.js'; +import { QueueLoggerService } from '@/queue/QueueLoggerService.js'; +import { GlobalModule } from '@/GlobalModule.js'; +import { secureRndstr } from '@/misc/secure-rndstr.js'; + +describe('CleanRemoteNotesProcessorService', () => { + let app: TestingModule; + let service: CleanRemoteNotesProcessorService; + let idService: IdService; + let notesRepository: NotesRepository; + let noteFavoritesRepository: NoteFavoritesRepository; + let userNotePiningsRepository: UserNotePiningsRepository; + let usersRepository: UsersRepository; + let userProfilesRepository: UserProfilesRepository; + + // Local user + let alice: MiUser; + // Remote user 1 + let bob: MiUser; + // Remote user 2 + let carol: MiUser; + + const meta = new MiMeta(); + + // Mock job object + const createMockJob = () => ({ + log: jest.fn(), + updateProgress: jest.fn(), + }); + + async function createUser(data: Partial = {}) { + const id = idService.gen(); + const un = data.username || secureRndstr(16); + const user = await usersRepository + .insert({ + id, + username: un, + usernameLower: un.toLowerCase(), + ...data, + }) + .then(x => usersRepository.findOneByOrFail(x.identifiers[0])); + + await userProfilesRepository.save({ + userId: id, + }); + + return user; + } + + async function createNote(data: Partial, user: MiUser, time?: number): Promise { + const id = idService.gen(time); + const note = await notesRepository + .insert({ + id: id, + text: `note_${id}`, + userId: user.id, + userHost: user.host, + visibility: 'public', + ...data, + }) + .then(x => notesRepository.findOneByOrFail(x.identifiers[0])); + return note; + } + + beforeAll(async () => { + app = await Test + .createTestingModule({ + imports: [ + GlobalModule, + ], + providers: [ + CleanRemoteNotesProcessorService, + IdService, + { + provide: QueueLoggerService, + useFactory: () => ({ + logger: { + createSubLogger: () => ({ + info: jest.fn(), + warn: jest.fn(), + succ: jest.fn(), + }), + }, + }), + }, + ], + }) + .overrideProvider(DI.meta).useFactory({ factory: () => meta }) + .compile(); + + service = app.get(CleanRemoteNotesProcessorService); + idService = app.get(IdService); + notesRepository = app.get(DI.notesRepository); + noteFavoritesRepository = app.get(DI.noteFavoritesRepository); + userNotePiningsRepository = app.get(DI.userNotePiningsRepository); + usersRepository = app.get(DI.usersRepository); + userProfilesRepository = app.get(DI.userProfilesRepository); + + alice = await createUser({ username: 'alice', host: null }); + bob = await createUser({ username: 'bob', host: 'remote1.example.com' }); + carol = await createUser({ username: 'carol', host: 'remote2.example.com' }); + + app.enableShutdownHooks(); + }); + + beforeEach(() => { + // Reset mocks + jest.clearAllMocks(); + + // Set default meta values + meta.enableRemoteNotesCleaning = true; + meta.remoteNotesCleaningMaxProcessingDurationInMinutes = 0.3; + meta.remoteNotesCleaningExpiryDaysForEachNotes = 30; + }, 60 * 1000); + + afterEach(async () => { + // Clean up test data + await Promise.all([ + notesRepository.createQueryBuilder().delete().execute(), + userNotePiningsRepository.createQueryBuilder().delete().execute(), + noteFavoritesRepository.createQueryBuilder().delete().execute(), + ]); + }, 60 * 1000); + + afterAll(async () => { + await app.close(); + }); + + describe('basic', () => { + test('should skip cleaning when enableRemoteNotesCleaning is false', async () => { + meta.enableRemoteNotesCleaning = false; + const job = createMockJob(); + + const result = await service.process(job as any); + + expect(result).toEqual({ + deletedCount: 0, + oldest: null, + newest: null, + skipped: true, + transientErrors: 0, + }); + }); + + test('should return success result when enableRemoteNotesCleaning is true and no notes to clean', async () => { + const job = createMockJob(); + + await createNote({}, alice); + const result = await service.process(job as any); + + expect(result).toEqual({ + deletedCount: 0, + oldest: null, + newest: null, + skipped: false, + transientErrors: 0, + }); + }, 3000); + + test('should clean remote notes and return stats', async () => { + // Remote notes + const remoteNotes = await Promise.all([ + createNote({}, bob), + createNote({}, carol), + createNote({}, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000), + createNote({}, carol, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 2000), // Note older than expiry + ]); + + // Local notes + const localNotes = await Promise.all([ + createNote({}, alice), + createNote({}, alice, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000), + ]); + + const job = createMockJob(); + + const result = await service.process(job as any); + + expect(result).toEqual({ + deletedCount: 2, + oldest: expect.any(Number), + newest: expect.any(Number), + skipped: false, + transientErrors: 0, + }); + + // Check side-by-side from all notes + const remainingNotes = await notesRepository.find(); + expect(remainingNotes.length).toBe(4); + expect(remainingNotes.some(n => n.id === remoteNotes[0].id)).toBe(true); + expect(remainingNotes.some(n => n.id === remoteNotes[1].id)).toBe(true); + expect(remainingNotes.some(n => n.id === remoteNotes[2].id)).toBe(false); + expect(remainingNotes.some(n => n.id === remoteNotes[3].id)).toBe(false); + expect(remainingNotes.some(n => n.id === localNotes[0].id)).toBe(true); + expect(remainingNotes.some(n => n.id === localNotes[1].id)).toBe(true); + }); + }); + + describe('advanced', () => { + // お気に入り + test('should not delete note that is favorited by any user', async () => { + const job = createMockJob(); + + // Create old remote note that should be deleted + const olderRemoteNote = await createNote({}, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000); + + // Favorite the note + await noteFavoritesRepository.save({ + id: idService.gen(), + userId: alice.id, + noteId: olderRemoteNote.id, + }); + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(0); + expect(result.skipped).toBe(false); + + const remainingNote = await notesRepository.findOneBy({ id: olderRemoteNote.id }); + expect(remainingNote).not.toBeNull(); + }); + + // ピン留め + test('should not delete note that is pinned by the user', async () => { + const job = createMockJob(); + + // Create old remote note that should be deleted + const olderRemoteNote = await createNote({}, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000); + + // Pin the note by the user who created it + await userNotePiningsRepository.save({ + id: idService.gen(), + userId: bob.id, // Same user as the note creator + noteId: olderRemoteNote.id, + }); + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(0); + expect(result.skipped).toBe(false); + + const remainingNote = await notesRepository.findOneBy({ id: olderRemoteNote.id }); + expect(remainingNote).not.toBeNull(); + }); + + // クリップ + test('should not delete note that is clipped', async () => { + const job = createMockJob(); + + // Create old remote note that is clipped + const clippedNote = await createNote({ + clippedCount: 1, // Clipped + }, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000); + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(0); + expect(result.skipped).toBe(false); + + const remainingNote = await notesRepository.findOneBy({ id: clippedNote.id }); + expect(remainingNote).not.toBeNull(); + }); + + // ページ + test('should not delete note that is embedded in a page', async () => { + const job = createMockJob(); + + // Create old remote note that is embedded in a page + const clippedNote = await createNote({ + pageCount: 1, // Embedded in a page + }, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000); + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(0); + expect(result.skipped).toBe(false); + + const remainingNote = await notesRepository.findOneBy({ id: clippedNote.id }); + expect(remainingNote).not.toBeNull(); + }); + + // 古いreply, renoteが含まれている時の挙動 + test('should handle reply/renote relationships correctly', async () => { + const job = createMockJob(); + + // Create old remote notes with reply/renote relationships + const originalNote = await createNote({}, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000); + const replyNote = await createNote({ + replyId: originalNote.id, + }, carol, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 2000); + const renoteNote = await createNote({ + renoteId: originalNote.id, + }, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 3000); + + const result = await service.process(job as any); + + // Should delete all three notes as they are all old and remote + expect(result.deletedCount).toBe(3); + expect(result.skipped).toBe(false); + + const remainingNotes = await notesRepository.find(); + expect(remainingNotes.some(n => n.id === originalNote.id)).toBe(false); + expect(remainingNotes.some(n => n.id === replyNote.id)).toBe(false); + expect(remainingNotes.some(n => n.id === renoteNote.id)).toBe(false); + }); + + // 古いリモートノートに新しいリプライがある時、どちらも削除されない + test('should not delete both old remote note with new reply', async () => { + const job = createMockJob(); + + // Create old remote note that should be deleted + const oldNote = await createNote({}, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000); + + // Create a reply note that is newer than the expiry period + const recentReplyNote = await createNote({ + replyId: oldNote.id, + }, carol, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) + 1000); + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(0); // Only the old note should be deleted + expect(result.skipped).toBe(false); + + const remainingNotes = await notesRepository.find(); + expect(remainingNotes.some(n => n.id === oldNote.id)).toBe(true); + expect(remainingNotes.some(n => n.id === recentReplyNote.id)).toBe(true); // Recent reply note should remain + }); + + // 古いリモートノートに新しいリプライと古いリプライがある時、全て残る + test('should not delete old remote note with new reply and old reply', async () => { + const job = createMockJob(); + + // Create old remote note that should be deleted + const oldNote = await createNote({}, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000); + + // Create a reply note that is newer than the expiry period + const recentReplyNote = await createNote({ + replyId: oldNote.id, + }, carol, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) + 1000); + + // Create an old reply note that should be deleted + const oldReplyNote = await createNote({ + replyId: oldNote.id, + }, carol, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 2000); + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(0); + expect(result.skipped).toBe(false); + + const remainingNotes = await notesRepository.find(); + expect(remainingNotes.some(n => n.id === oldNote.id)).toBe(true); + expect(remainingNotes.some(n => n.id === recentReplyNote.id)).toBe(true); // Recent reply note should remain + expect(remainingNotes.some(n => n.id === oldReplyNote.id)).toBe(true); // Old reply note should be deleted + }); + + // リプライがお気に入りされているとき、どちらも削除されない + test('should not delete reply note that is favorited', async () => { + const job = createMockJob(); + + // Create old remote note that should be deleted + const olderRemoteNote = await createNote({}, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000); + + // Create a reply note that is newer than the expiry period + const replyNote = await createNote({ + replyId: olderRemoteNote.id, + }, carol, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 2000); + + // Favorite the reply note + await noteFavoritesRepository.save({ + id: idService.gen(), + userId: alice.id, + noteId: replyNote.id, + }); + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(0); // Only the old note should be deleted + expect(result.skipped).toBe(false); + + const remainingNotes = await notesRepository.find(); + expect(remainingNotes.some(n => n.id === olderRemoteNote.id)).toBe(true); + expect(remainingNotes.some(n => n.id === replyNote.id)).toBe(true); // Recent reply note should remain + }); + + // リプライがピン留めされているとき、どちらも削除されない + test('should not delete reply note that is pinned', async () => { + const job = createMockJob(); + + // Create old remote note that should be deleted + const olderRemoteNote = await createNote({}, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000); + + // Create a reply note that is newer than the expiry period + const replyNote = await createNote({ + replyId: olderRemoteNote.id, + }, carol, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 2000); + + // Pin the reply note + await userNotePiningsRepository.save({ + id: idService.gen(), + userId: carol.id, + noteId: replyNote.id, + }); + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(0); // Only the old note should be deleted + expect(result.skipped).toBe(false); + + const remainingNotes = await notesRepository.find(); + expect(remainingNotes.some(n => n.id === olderRemoteNote.id)).toBe(true); + expect(remainingNotes.some(n => n.id === replyNote.id)).toBe(true); // Reply note should remain + }); + + // リプライがクリップされているとき、どちらも削除されない + test('should not delete reply note that is clipped', async () => { + const job = createMockJob(); + + // Create old remote note that should be deleted + const olderRemoteNote = await createNote({}, bob, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000); + + // Create a reply note that is old but clipped + const replyNote = await createNote({ + replyId: olderRemoteNote.id, + clippedCount: 1, // Clipped + }, carol, Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 2000); + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(0); // Both notes should be kept because reply is clipped + expect(result.skipped).toBe(false); + + const remainingNotes = await notesRepository.find(); + expect(remainingNotes.some(n => n.id === olderRemoteNote.id)).toBe(true); + expect(remainingNotes.some(n => n.id === replyNote.id)).toBe(true); + }); + + test('should handle mixed scenarios with multiple conditions', async () => { + const job = createMockJob(); + + // Create various types of notes + const oldTime = Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000; + + // Should be deleted: old remote note with no special conditions + const deletableNote = await createNote({}, bob, oldTime); + + // Should NOT be deleted: old remote note but favorited + const favoritedNote = await createNote({}, carol, oldTime); + await noteFavoritesRepository.save({ + id: idService.gen(), + userId: alice.id, + noteId: favoritedNote.id, + }); + + // Should NOT be deleted: old remote note but pinned + const pinnedNote = await createNote({}, bob, oldTime); + await userNotePiningsRepository.save({ + id: idService.gen(), + userId: bob.id, + noteId: pinnedNote.id, + }); + + // Should NOT be deleted: old remote note but clipped + const clippedNote = await createNote({ + clippedCount: 2, + }, carol, oldTime); + + // Should NOT be deleted: old local note + const localNote = await createNote({}, alice, oldTime); + + // Should NOT be deleted: new remote note + const newerRemoteNote = await createNote({}, bob); + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(1); // Only deletableNote should be deleted + expect(result.skipped).toBe(false); + + const remainingNotes = await notesRepository.find(); + expect(remainingNotes.length).toBe(5); + expect(remainingNotes.some(n => n.id === deletableNote.id)).toBe(false); // Deleted + expect(remainingNotes.some(n => n.id === favoritedNote.id)).toBe(true); // Kept + expect(remainingNotes.some(n => n.id === pinnedNote.id)).toBe(true); // Kept + expect(remainingNotes.some(n => n.id === clippedNote.id)).toBe(true); // Kept + expect(remainingNotes.some(n => n.id === localNote.id)).toBe(true); // Kept + expect(remainingNotes.some(n => n.id === newerRemoteNote.id)).toBe(true); // Kept + }); + + // 大量のノート + test('should handle large number of notes correctly', async () => { + const AMOUNT = 130; + const job = createMockJob(); + + const oldTime = Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000; + const noteIds = []; + for (let i = 0; i < AMOUNT; i++) { + const note = await createNote({}, bob, oldTime - i); + noteIds.push(note.id); + } + + const result = await service.process(job as any); + + // Should delete all notes, but may require multiple batches + expect(result.deletedCount).toBe(AMOUNT); + expect(result.skipped).toBe(false); + + const remainingNotes = await notesRepository.find(); + expect(remainingNotes.length).toBe(0); + }); + + // 大量のノート + リプライ or リノート + test('should handle large number of notes with replies correctly', async () => { + const AMOUNT = 130; + const job = createMockJob(); + + const oldTime = Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000; + const noteIds = []; + for (let i = 0; i < AMOUNT; i++) { + const note = await createNote({}, bob, oldTime - i - AMOUNT); + noteIds.push(note.id); + if (i % 2 === 0) { + // Create a reply for every second note + await createNote({ replyId: note.id }, carol, oldTime - i); + } else { + // Create a renote for every second note + await createNote({ renoteId: note.id }, bob, oldTime - i); + } + } + + const result = await service.process(job as any); + // Should delete all notes, but may require multiple batches + expect(result.deletedCount).toBe(AMOUNT * 2); + expect(result.skipped).toBe(false); + }); + + // 大量の古いノート + 新しいリプライ or リノート + test('should handle large number of old notes with new replies correctly', async () => { + const AMOUNT = 130; + const job = createMockJob(); + + const oldTime = Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000; + const newTime = Date.now(); + const noteIds = []; + for (let i = 0; i < AMOUNT; i++) { + const note = await createNote({}, bob, oldTime - i); + noteIds.push(note.id); + if (i % 2 === 0) { + // Create a reply for every second note + await createNote({ replyId: note.id }, carol, newTime + i); + } else { + // Create a renote for every second note + await createNote({ renoteId: note.id }, bob, newTime + i); + } + } + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(0); + expect(result.skipped).toBe(false); + }); + + // 大量の残す対象(clippedCount: 1)と大量の削除対象 + test('should handle large number of notes, mixed conditions with clippedCount', async () => { + const AMOUNT_BASE = 70; + const job = createMockJob(); + + const oldTime = Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000; + const noteIds = []; + for (let i = 0; i < AMOUNT_BASE; i++) { + const note = await createNote({ clippedCount: 1 }, bob, oldTime - i - AMOUNT_BASE); + noteIds.push(note.id); + } + for (let i = 0; i < AMOUNT_BASE; i++) { + const note = await createNote({}, carol, oldTime - i); + noteIds.push(note.id); + } + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(AMOUNT_BASE); // Assuming half are deletable + expect(result.skipped).toBe(false); + }); + + // 大量の残す対象(リプライ)と大量の削除対象 + test('should handle large number of notes, mixed conditions with replies', async () => { + const AMOUNT_BASE = 70; + const job = createMockJob(); + const oldTime = Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 1000; + const newTime = Date.now(); + for (let i = 0; i < AMOUNT_BASE; i++) { + // should remain + const note = await createNote({}, carol, oldTime - AMOUNT_BASE - i); + // should remain + await createNote({ replyId: note.id }, bob, newTime + i); + } + + const noteIdsExpectedToBeDeleted = []; + for (let i = 0; i < AMOUNT_BASE; i++) { + // should be deleted + const note = await createNote({}, bob, oldTime - i); + noteIdsExpectedToBeDeleted.push(note.id); + } + + const result = await service.process(job as any); + expect(result.deletedCount).toBe(AMOUNT_BASE); // Assuming all replies are deletable + expect(result.skipped).toBe(false); + + const remainingNotes = await notesRepository.find(); + expect(remainingNotes.length).toBe(AMOUNT_BASE * 2); // Only replies should remain + noteIdsExpectedToBeDeleted.forEach(id => { + expect(remainingNotes.some(n => n.id === id)).toBe(false); // All original notes should be deleted + }); + }); + + test('should update cursor correctly during batch processing', async () => { + const job = createMockJob(); + + // Create notes with specific timing to test cursor behavior + const baseTime = Date.now() - ms(`${meta.remoteNotesCleaningExpiryDaysForEachNotes} days`) - 10000; + + const note1 = await createNote({}, bob, baseTime); + const note2 = await createNote({}, carol, baseTime - 1000); + const note3 = await createNote({}, bob, baseTime - 2000); + + const result = await service.process(job as any); + + expect(result.deletedCount).toBe(3); + expect(result.newest).toBe(idService.parse(note1.id).date.getTime()); + expect(result.oldest).toBe(idService.parse(note3.id).date.getTime()); + expect(result.skipped).toBe(false); + }); + }); +}); diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index 7eecf8bb0d..ace614115c 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -317,7 +317,7 @@ export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadO const formData = new FormData(); formData.append('file', blob ?? - new File([await readFile(absPath)], basename(absPath.toString()))); + new File([new Uint8Array(await readFile(absPath))], basename(absPath.toString()))); formData.append('force', 'true'); if (name) { formData.append('name', name); @@ -608,8 +608,8 @@ export async function initTestDb(justBorrow = false, initEntities?: any[]) { username: config.db.user, password: config.db.pass, database: config.db.db, - synchronize: true && !justBorrow, - dropSchema: true && !justBorrow, + synchronize: !justBorrow, + dropSchema: !justBorrow, entities: initEntities ?? entities, }); @@ -661,7 +661,9 @@ export async function captureWebhook(postAction: () => let timeoutHandle: NodeJS.Timeout | null = null; const result = await new Promise(async (resolve, reject) => { fastify.all('/', async (req, res) => { - timeoutHandle && clearTimeout(timeoutHandle); + if (timeoutHandle) { + clearTimeout(timeoutHandle); + } const body = JSON.stringify(req.body); res.status(200).send('ok'); diff --git a/packages/frontend-builder/locale-inliner.ts b/packages/frontend-builder/locale-inliner.ts index 75bcdc5b3f..9bef465eeb 100644 --- a/packages/frontend-builder/locale-inliner.ts +++ b/packages/frontend-builder/locale-inliner.ts @@ -69,8 +69,10 @@ export class LocaleInliner { async saveAllLocales(locales: Record) { const localeNames = Object.keys(locales); for (const localeName of localeNames) { + this.logger.info(`Creating bundle for ${localeName}`); await this.saveLocale(localeName, locales[localeName]); } + this.logger.info('Done'); } async saveLocale(localeName: string, localeJson: Locale) { diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index 654aceb8f5..0248d75f75 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -585,6 +585,14 @@ defineExpose({ grid-template-columns: var(--columns); font-size: 30px; + > .config { + aspect-ratio: 1 / 1; + width: auto; + height: auto; + min-width: 0; + font-size: 14px; + } + > .item { aspect-ratio: 1 / 1; width: auto; diff --git a/packages/frontend/src/components/MkFormFooter.vue b/packages/frontend/src/components/MkFormFooter.vue index 96214a9542..eb559e611c 100644 --- a/packages/frontend/src/components/MkFormFooter.vue +++ b/packages/frontend/src/components/MkFormFooter.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> - + {{ i18n.tsx.thereAreNChanges({ n: form.modifiedCount.value }) }} {{ i18n.ts.discard }} @@ -16,16 +16,11 @@ SPDX-License-Identifier: AGPL-3.0-only - - diff --git a/packages/frontend/src/components/MkImageEffectorFxForm.vue b/packages/frontend/src/components/MkImageEffectorFxForm.vue new file mode 100644 index 0000000000..d7ab620132 --- /dev/null +++ b/packages/frontend/src/components/MkImageEffectorFxForm.vue @@ -0,0 +1,95 @@ + + + + + + + {{ v.label ?? k }} + {{ v.caption }} + + { + params[k] = v.default; + }" + > + {{ v.label ?? k }} + {{ v.caption }} + + + {{ v.label ?? k }} + {{ v.caption }} + + + {{ item.label }} + + + + + {{ v.label ?? k }} + {{ v.caption }} + + + { const c = getRgb(v); if (c != null) params[k] = c; }"> + {{ v.label ?? k }} + {{ v.caption }} + + + + {{ i18n.ts._imageEffector.nothingToConfigure }} + + + + + + + diff --git a/packages/frontend/src/components/MkPositionSelector.vue b/packages/frontend/src/components/MkPositionSelector.vue index 002950cdf1..739f55125b 100644 --- a/packages/frontend/src/components/MkPositionSelector.vue +++ b/packages/frontend/src/components/MkPositionSelector.vue @@ -44,6 +44,11 @@ const y = defineModel('y', { default: 'center' }); height: 32px; background: var(--MI_THEME-panel); border-radius: 4px; + transition: background 0.1s ease; + + &:not(.active):hover { + background: var(--MI_THEME-buttonHoverBg); + } &.active { background: var(--MI_THEME-accentedBg); diff --git a/packages/frontend/src/components/MkStreamingNotesTimeline.vue b/packages/frontend/src/components/MkStreamingNotesTimeline.vue index c2b4d6cd04..9ace0b32d5 100644 --- a/packages/frontend/src/components/MkStreamingNotesTimeline.vue +++ b/packages/frontend/src/components/MkStreamingNotesTimeline.vue @@ -47,7 +47,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + {{ i18n.ts.loadMore }} diff --git a/packages/frontend/src/components/MkStreamingNotificationsTimeline.vue b/packages/frontend/src/components/MkStreamingNotificationsTimeline.vue index ac1f06619a..93ffee8d98 100644 --- a/packages/frontend/src/components/MkStreamingNotificationsTimeline.vue +++ b/packages/frontend/src/components/MkStreamingNotificationsTimeline.vue @@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + {{ i18n.ts.loadMore }} diff --git a/packages/frontend/src/deck.ts b/packages/frontend/src/deck.ts index 73a3cecd3b..208adae8fe 100644 --- a/packages/frontend/src/deck.ts +++ b/packages/frontend/src/deck.ts @@ -62,6 +62,8 @@ export type Column = { withSensitive?: boolean; onlyFiles?: boolean; soundSetting?: SoundStore; + // The cache for the name of the antenna, channel, list, or role + timelineNameCache?: string; }; const _currentProfile = prefer.s['deck.profiles'].find(p => p.name === prefer.s['deck.profile']); diff --git a/packages/frontend/src/directives/appear.ts b/packages/frontend/src/directives/appear.ts index 802477e00b..f5fec108dc 100644 --- a/packages/frontend/src/directives/appear.ts +++ b/packages/frontend/src/directives/appear.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import { throttle } from 'throttle-debounce'; import type { Directive } from 'vue'; export default { @@ -10,12 +11,14 @@ export default { const fn = binding.value; if (fn == null) return; - const observer = new IntersectionObserver(entries => { + const check = throttle(1000, (entries) => { if (entries.some(entry => entry.isIntersecting)) { fn(); } }); + const observer = new IntersectionObserver(check); + observer.observe(src); src._observer_ = observer; diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue index 057deec4cf..17258cc11b 100644 --- a/packages/frontend/src/pages/about-misskey.vue +++ b/packages/frontend/src/pages/about-misskey.vue @@ -111,6 +111,9 @@ SPDX-License-Identifier: AGPL-3.0-only + + + @@ -286,6 +289,9 @@ const patronsWithIcon = [{ }, { name: '井上千二十四', icon: 'https://assets.misskey-hub.net/patrons/193afa1f039b4c339866039c3dcd74bf.jpg', +}, { + name: 'NigN', + icon: 'https://assets.misskey-hub.net/patrons/1ccaef8e73ec4a50b59ff7cd688ceb84.jpg', }]; const patrons = [ @@ -399,6 +405,8 @@ const patrons = [ 'みりめい', '東雲 琥珀', 'ほとラズ', + 'スズカケン', + '蒼井よみこ', ]; const thereIsTreasure = ref($i && !claimedAchievements.includes('foundTreasure')); diff --git a/packages/frontend/src/pages/admin/bot-protection.vue b/packages/frontend/src/pages/admin/bot-protection.vue index bd919146f8..7ed280358a 100644 --- a/packages/frontend/src/pages/admin/bot-protection.vue +++ b/packages/frontend/src/pages/admin/bot-protection.vue @@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only Turnstile testCaptcha {{ i18n.ts.none }} ({{ i18n.ts.notRecommended }}) - + diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index c172e22688..bb96a1cde1 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -346,6 +346,26 @@ SPDX-License-Identifier: AGPL-3.0-only + + {{ i18n.ts._role._options.canSearchUsers }} + + {{ i18n.ts._role.useBaseValue }} + {{ role.policies.canSearchUsers.value ? i18n.ts.yes : i18n.ts.no }} + + + + + {{ i18n.ts._role.useBaseValue }} + + + {{ i18n.ts.enable }} + + + {{ i18n.ts._role.priority }} + + + + {{ i18n.ts._role._options.canUseTranslator }} diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue index e78a4bbc11..efdf8620ef 100644 --- a/packages/frontend/src/pages/admin/roles.vue +++ b/packages/frontend/src/pages/admin/roles.vue @@ -122,6 +122,14 @@ SPDX-License-Identifier: AGPL-3.0-only + + {{ i18n.ts._role._options.canSearchUsers }} + {{ policies.canSearchUsers ? i18n.ts.yes : i18n.ts.no }} + + {{ i18n.ts.enable }} + + + {{ i18n.ts._role._options.canUseTranslator }} {{ policies.canUseTranslator ? i18n.ts.yes : i18n.ts.no }} diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index 7ce42ea0cb..0437191695 100644 --- a/packages/frontend/src/pages/channel.vue +++ b/packages/frontend/src/pages/channel.vue @@ -112,7 +112,7 @@ const favorited = ref(false); const searchQuery = ref(''); const searchPaginator = shallowRef(); const searchKey = ref(''); -const featuredPaginator = markRaw(new Paginator('channels/featured', { +const featuredPaginator = markRaw(new Paginator('notes/featured', { limit: 10, computedParams: computed(() => ({ channelId: props.channelId, diff --git a/packages/frontend/src/pages/search.vue b/packages/frontend/src/pages/search.vue index b6d21a4616..8d2bf9eb42 100644 --- a/packages/frontend/src/pages/search.vue +++ b/packages/frontend/src/pages/search.vue @@ -15,16 +15,22 @@ SPDX-License-Identifier: AGPL-3.0-only - + + + + + {{ i18n.ts.usersSearchNotAvailable }} +