diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bfac44a89..838727be06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,19 +1,23 @@ ## 2024.9.0 ### General +- Feat: ノート単体・ユーザーのノート・クリップのノートの埋め込み機能 + - 埋め込みコードやウェブサイトへの実装方法の詳細は https://misskey-hub.net/docs/for-users/features/embed/ をご覧ください +- Feat: パスキーでログインボタンを実装 (#14574) - Feat: UserWebhookとSystemWebhookのテスト送信機能を追加 (#14445) - Feat: モデレーターはユーザーにかかわらずファイルが添付されているノートを検索できるように (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/680) - Enhance: ユーザーによるコンテンツインポートの可否をロールポリシーで制御できるように +- Enhance: 依存関係の更新 +- Enhance: l10nの更新 ### Client -- Feat: ノート単体・ユーザーのノート・クリップのノートの埋め込み機能 - - 埋め込みコードやウェブサイトへの実装方法の詳細は https://misskey-hub.net/docs/for-users/features/embed/ をご覧ください - Enhance: サイズ制限を超過するファイルをアップロードしようとした際にエラーを出すように - Enhance: アイコンデコレーション管理画面にプレビューを追加 - Enhance: コントロールパネル内のファイル一覧でセンシティブなファイルを区別しやすく - Enhance: ScratchpadにUIインスペクターを追加 - Enhance: Play編集画面の項目の並びを少しリデザイン +- Enhance: 各種メニューをドロワー表示するかどうか設定可能に - Fix: サーバーメトリクスが2つ以上あるとリロード直後の表示がおかしくなる問題を修正 - Fix: コントロールパネル内のAp requests内のチャートの表示がおかしかった問題を修正 - Fix: 月の違う同じ日はセパレータが表示されないのを修正 @@ -27,7 +31,7 @@ - Fix: 一部画面のページネーションが動作しにくくなっていたのを修正 ( #12766 , #11449 ) ### Server -- Feat: Misskey® Reactions Buffering Technology™ (RBT)により、リアクションの作成負荷を低減することが可能に +- Feat: Misskey® Reactions Boost Technology™ (RBT)により、リアクションの作成負荷を低減することが可能に - Enhance: 連合する必要のないプロフィール項目しか更新されなかった場合には連合先にUpdateアクティビティを発行しないように - Fix: アンテナの書き込み時にキーワードが与えられなかった場合のエラーをApiErrorとして投げるように - この変更により、公式フロントエンドでは入力の不備が内部エラーとして報告される代わりに一般的なエラーダイアログで報告されます @@ -35,10 +39,13 @@ - Fix: 外部ページを解析する際に、ページに紐づけられた関連リソースも読み込まれてしまう問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/26e0412fbb91447c37e8fb06ffb0487346063bb8) - Fix: Continue importing from file if single emoji import fails -- Fix: `Retry-After`ヘッダーが送信されなかった問題を修正 +- Fix: `Retry-After`ヘッダーが送信されなかった問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/8a982c61c01909e7540ff1be9f019df07c3f0624) -- Fix: サーバーサイドのDOM解析完了時にリソースを開放するように - (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/634) +- Fix: サーバーサイドのDOM解析完了時にリソースを開放するように + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/634) +- Fix: ``を追って照会するのはOKレスポンスが返却された場合のみに + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/633) +- Fix: メールにスタイルが適用されていなかった問題を修正 ## 2024.8.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 72c84c2f18..f61311f1e5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -572,3 +572,24 @@ marginはそのコンポーネントを使う側が設定する ### indexというファイル名を使うな ESMではディレクトリインポートは廃止されているのと、ディレクトリインポートせずともファイル名が index だと何故か一部のライブラリ?でディレクトリインポートだと見做されてエラーになる + +## CSS Recipe + +### Lighten CSS vars + +``` css +color: hsl(from var(--accent) h s calc(l + 10)); +``` + +### Darken CSS vars + +``` css +color: hsl(from var(--accent) h s calc(l - 10)); +``` + +### Add alpha to CSS vars + +``` css +color: color(from var(--accent) srgb r g b / 0.5); +``` + diff --git a/idea/MkDisableSection.vue b/idea/MkDisableSection.vue new file mode 100644 index 0000000000..d177886569 --- /dev/null +++ b/idea/MkDisableSection.vue @@ -0,0 +1,41 @@ + + + + + + + diff --git a/idea/README.md b/idea/README.md new file mode 100644 index 0000000000..f64d16800a --- /dev/null +++ b/idea/README.md @@ -0,0 +1 @@ +使われなくなったけど消すのは勿体ない(将来使えるかもしれない)コードを入れておくとこ diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index 7bd9a1bb32..49ea2bb64f 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -60,6 +60,7 @@ copyFileId: "Copiar ID d'arxiu" copyFolderId: "Copiar ID de carpeta" copyProfileUrl: "Copiar URL del perfil" searchUser: "Cercar un usuari" +searchThisUsersNotes: "Cerca les publicacions de l'usuari" reply: "Respondre" loadMore: "Carregar més" showMore: "Veure més" @@ -108,11 +109,14 @@ enterEmoji: "Introduir un emoji" renote: "Impulsa" unrenote: "Anul·la l'impuls" renoted: "S'ha impulsat" +renotedToX: "Impulsat per {name}." cantRenote: "No es pot impulsar aquesta publicació" cantReRenote: "No es pot impulsar l'impuls." quote: "Cita" inChannelRenote: "Renotar només al Canal" inChannelQuote: "Citar només al Canal" +renoteToChannel: "Impulsa a un canal" +renoteToOtherChannel: "Impulsa a un altre canal" pinnedNote: "Nota fixada" pinned: "Fixar al perfil" you: "Tu" @@ -151,6 +155,7 @@ editList: "Editar llista" selectChannel: "Selecciona un canal" selectAntenna: "Tria una antena" editAntenna: "Modificar antena" +createAntenna: "Crea una antena" selectWidget: "Triar un giny" editWidgets: "Editar ginys" editWidgetsExit: "Fet" @@ -177,6 +182,10 @@ addAccount: "Afegeix un compte" reloadAccountsList: "Recarregar la llista de contactes" loginFailed: "S'ha produït un error al accedir." showOnRemote: "Navega més en el perfil original" +continueOnRemote: "Veure perfil original" +chooseServerOnMisskeyHub: "Escull un servidor des del Hub de Misskey" +specifyServerHost: "Especifica un servidor directament" +inputHostName: "Introdueix el domini" general: "General" wallpaper: "Fons de Pantalla" setWallpaper: "Defineix el fons de pantalla" @@ -187,6 +196,7 @@ followConfirm: "Estàs segur que vols deixar de seguir {name}?" proxyAccount: "Compte de proxy" proxyAccountDescription: "Un compte proxy és un compte que actua com a seguidor remot per als usuaris en determinades condicions. Per exemple, quan un usuari afegeix un usuari remot a la llista, l'activitat de l'usuari remot no es lliurarà al servidor si cap usuari local segueix aquest usuari, de manera que el compte proxy el seguirà." host: "Amfitrió" +selectSelf: "Escollir manualment" selectUser: "Selecciona usuari/a" recipient: "Destinatari" annotation: "Comentaris" @@ -202,6 +212,7 @@ perDay: "Per dia" stopActivityDelivery: "Deixa d'enviar activitats" blockThisInstance: "Deixa d'enviar activitats" silenceThisInstance: "Silencia aquesta instància " +mediaSilenceThisInstance: "Silenciar els arxius d'aquesta instància " operations: "Accions" software: "Programari" version: "Versió" @@ -223,6 +234,8 @@ blockedInstances: "Instàncies bloquejades" blockedInstancesDescription: "Llista els enllaços d'amfitrió de les instàncies que vols bloquejar separades per un salt de pàgina. Les instàncies llistades no podran comunicar-se amb aquesta instància." silencedInstances: "Instàncies silenciades" silencedInstancesDescription: "Llista els enllaços d'amfitrió de les instàncies que vols silenciar. Tots els comptes de les instàncies llistades s'establiran com silenciades i només podran fer sol·licitacions de seguiment, i no podran mencionar als comptes locals si no els segueixen. Això no afectarà les instàncies bloquejades." +mediaSilencedInstances: "Instàncies amb els arxius silenciats" +mediaSilencedInstancesDescription: "Llista els noms dels servidors que vulguis silenciar els arxius, un servidor per línia. Tots els comptes que pertanyin als servidors llistats seran tractats com sensibles i no podran fer servir emojis personalitzats. Això no tindrà efecte sobre els servidors blocats." muteAndBlock: "Silencia i bloca" mutedUsers: "Usuaris silenciats" blockedUsers: "Usuaris bloquejats" @@ -313,6 +326,7 @@ selectFile: "Selecciona fitxers" selectFiles: "Selecciona fitxers" selectFolder: "Selecció de carpeta" selectFolders: "Selecció de carpeta" +fileNotSelected: "Cap fitxer seleccionat" renameFile: "Canvia el nom del fitxer" folderName: "Nom de la carpeta" createFolder: "Crea una carpeta" @@ -468,10 +482,12 @@ retype: "Torneu a introduir-la" noteOf: "Publicació de: {user}" quoteAttached: "Frase adjunta" quoteQuestion: "Vols annexar-la com a cita?" +attachAsFileQuestion: "El text copiat és massa llarg. Vols adjuntar-lo com un fitxer de text?" noMessagesYet: "Encara no hi ha missatges" newMessageExists: "Has rebut un nou missatge" onlyOneFileCanBeAttached: "Només pots adjuntar un fitxer a un missatge" signinRequired: "Si us plau, Registra't o inicia la sessió abans de continuar" +signinOrContinueOnRemote: "Per continuar necessites moure el teu servidor o registrar-te / iniciar sessió en aquest servidor." invitations: "Convida" invitationCode: "Codi d'invitació" checking: "Comprovació en curs..." @@ -543,7 +559,7 @@ objectStorageUseSSLDesc: "Desactiva'l si no tens pensat fer servir HTTPS per les objectStorageUseProxy: "Connectar-se mitjançant un Proxy" objectStorageUseProxyDesc: "Desactiva'l si no faràs servir un Proxy per les connexions de l'API" objectStorageSetPublicRead: "Configurar les pujades com públiques " -s3ForcePathStyleDesc: "Si s3ForcePathStyle es troba activat el nom del dipòsit s'ha d'incloure a l'adreça URL en comtes del nom del host. Potser que necessitis activar-ho quan facis servir, per exemple, Minio a un servidor propi." +s3ForcePathStyleDesc: "Si s3ForcePathStyle es troba activat el nom del cubell s'haurà d'especificar com a part de l'adreça URL en comptes del nom del servidor. Podria ser que necessitis activar aquesta opció quan facis servir serveis com ara l'allotjament a un servidor propi." serverLogs: "Registres del servidor" deleteAll: "Elimina-ho tot" showFixedPostForm: "Mostrar el formulari per escriure a l'inici de la línia de temps" @@ -576,6 +592,8 @@ ascendingOrder: "Ascendent" descendingOrder: "Descendent" scratchpad: "Bloc de proves" scratchpadDescription: "El bloc de proves proporciona un entorn experimental per AiScript. Pot escriure i verificar els resultats que interactuen amb Misskey." +uiInspector: "Inspector de la interfície" +uiInspectorDescription: "Podeu visualitzar una llista d'elements UI presents en la memòria. Els components de la interfície d'usuari són generats per les funcions Ui:C:." output: "Sortida" script: "Script" disablePagesScript: "Desactivar AiScript a les pàgines " @@ -832,6 +850,7 @@ administration: "Administració" accounts: "Comptes" switch: "Canvia" noMaintainerInformationWarning: "La informació de l'administrador no s'ha configurat" +noInquiryUrlWarning: "No s'ha desat l'URL de consulta." noBotProtectionWarning: "La protecció contra bots no s'ha configurat." configure: "Configurar" postToGallery: "Crear una nova publicació a la galeria" @@ -1021,6 +1040,7 @@ thisPostMayBeAnnoyingHome: "Publicar a la línia de temps d'Inici" thisPostMayBeAnnoyingCancel: "Cancel·lar " thisPostMayBeAnnoyingIgnore: "Publicar de totes maneres" collapseRenotes: "Col·lapsar les renotes que ja has vist" +collapseRenotesDescription: "Col·lapse les notes a les quals ja has reaccionat o que ja has renotat" internalServerError: "Error intern del servidor" internalServerErrorDescription: "El servidor ha fallat de manera inexplicable." copyErrorInfo: "Copiar la informació de l'error " @@ -1094,6 +1114,8 @@ preservedUsernames: "Noms d'usuaris reservats" preservedUsernamesDescription: "Llistat de noms d'usuaris que no es poden fer servir separats per salts de linia. Aquests noms d'usuaris no estaran disponibles quan es creï un compte d'usuari normal, però els administradors els poden fer servir per crear comptes manualment. Per altre banda els comptes ja creats amb aquests noms d'usuari no es veure'n afectats." createNoteFromTheFile: "Compon una nota des d'aquest fitxer" archive: "Arxiu" +archived: "Arxivat" +unarchive: "Desarxivar" channelArchiveConfirmTitle: "Vols arxivar {name}?" channelArchiveConfirmDescription: "Un Canal arxivat no apareixerà a la llista de canals o als resultats de cerca. Tampoc es poden afegir noves entrades." thisChannelArchived: "Aquest Canal ha sigut arxivat." @@ -1104,6 +1126,9 @@ preventAiLearning: "Descartar l'ús d'aprenentatge automàtic (IA Generativa)" preventAiLearningDescription: "Demanar els indexadors no fer servir els texts, imatges, etc. en cap conjunt de dades per alimentar l'aprenentatge automàtic (IA Predictiva/ Generativa). Això s'aconsegueix afegint la etiqueta \"noai\" com a resposta HTML al contingut corresponent. Prevenir aquest ús totalment pot ser que no sigui aconseguit, ja que molts indexadors poden obviar aquesta etiqueta." options: "Opcions" specifyUser: "Especificar usuari" +lookupConfirm: "Vols fer una cerca?" +openTagPageConfirm: "Vols obrir una pàgina d'etiquetes?" +specifyHost: "Especifica un servidor" failedToPreviewUrl: "Vista prèvia no disponible" update: "Actualitzar" rolesThatCanBeUsedThisEmojiAsReaction: "Rols que poden fer servir aquest emoji com a reacció " @@ -1172,7 +1197,10 @@ confirmShowRepliesAll: "Aquesta opció no té marxa enrere. Vols mostrar les tev confirmHideRepliesAll: "Aquesta opció no té marxa enrere. Vols ocultar les teves respostes a tots els usuaris que segueixes a la línia de temps?" externalServices: "Serveis externs" sourceCode: "Codi font" +sourceCodeIsNotYetProvided: "El codi font encara no es troba disponible. Contacta amb l'administrador per solucionar aquest problema." repositoryUrl: "URL del repositori" +repositoryUrlDescription: "Si estàs fent servir Misskey tal com és (sense cap canvi al codi font), introdueix https://github.com/misskey-dev/misskey" +repositoryUrlOrTarballRequired: "Si no ofereixes cap repositori, publica un fitxer tarball. Dona una ullada a .config/example.yml per a més informació." feedback: "Opinió" feedbackUrl: "URL per a opinar" impressum: "Impressum" @@ -1211,6 +1239,7 @@ showReplay: "Veure reproducció" replay: "Reproduir" replaying: "Reproduint" endReplay: "Tanca la redifusió" +copyReplayData: "Copia les dades de la resposta" ranking: "Classificació" lastNDays: "Últims {n} dies" backToTitle: "Torna al títol" @@ -1224,12 +1253,42 @@ gameRetry: "Torna a provar" notUsePleaseLeaveBlank: "Si no voleu usar-ho, deixeu-ho en blanc" useTotp: "Usa una contrasenya d'un sol ús" useBackupCode: "Usa un codi de recuperació" +launchApp: "Inicia l'aplicació " +useNativeUIForVideoAudioPlayer: "Fes servir la UI del navegador quan reprodueixis vídeo i àudio " +keepOriginalFilename: "Desa el nom del fitxer original" +keepOriginalFilenameDescription: "Si desactives aquesta opció els noms dels fitxers se substituiran per una cadena aleatòria quan carreguis nous fitxers de forma automàtica." +noDescription: "No hi ha una descripció " +alwaysConfirmFollow: "Confirma sempre els seguiments" +inquiry: "Contacte" +tryAgain: "Intenta-ho més tard." +confirmWhenRevealingSensitiveMedia: "Confirmació quan revelis contingut sensible " +sensitiveMediaRevealConfirm: "Aquest contingut potser sensible. Segur que ho vols revelar?" +createdLists: "Llistes creades " +createdAntennas: "Antenes creades" +fromX: "De {x}" +genEmbedCode: "Obtenir el codi per incrustar" +noteOfThisUser: "Notes d'aquest usuari" +clipNoteLimitExceeded: "No es poden afegir més notes a aquest clip." _delivery: + status: "Estat d'entrega " stop: "Suspés" + resume: "Torna a enviar" _type: none: "S'està publicant" + manuallySuspended: "Suspendre manualment" + goneSuspended: "Servidor suspès perquè el servidor s'ha esborrat" + autoSuspendedForNotResponding: "Servidor suspès perquè el servidor no respon" _bubbleGame: howToPlay: "Com es juga" + hold: "Mantenir" + _score: + score: "Puntuació " + scoreYen: "Diners guanyats" + highScore: "Millor puntuació " + maxChain: "Nombre màxim de combos" + yen: "{yen}Ien" + estimatedQty: "{qty}peces" + scoreSweets: "{onigiriQtyWithUnit}ongiris" _howToPlay: section1: "Ajusta la posició i deixa caure l'objecte dintre la caixa." section2: "Quan dos objectes del mateix tipus es toquen, canviaran en un objecte diferent i guanyares punts." @@ -1344,6 +1403,9 @@ _serverSettings: fanoutTimelineDescription: "Quan es troba activat millora bastant el rendiment quan es recuperen les línies de temps i redueix la carrega de la base de dades. Com a contrapunt, l'ús de memòria de Redis es veurà incrementada. Considera d'estabilitat aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes de inestabilitat." fanoutTimelineDbFallback: "Carregar de la base de dades" fanoutTimelineDbFallbackDescription: "Quan s'activa, la línia de temps fa servir la base de dades per consultes adicionals si la línia de temps no es troba a la memòria cau. Si és desactiva la càrrega del servidor és veure reduïda, però també és reduirà el nombre de línies de temps que és poden obtenir." + reactionsBufferingDescription: "Quan s'activa aquesta opció millora bastant el rendiment en recuperar les línies de temps reduint la càrrega de la base. Com a contrapunt, augmentarà l'ús de memòria de Redís. Desactiva aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes d'inestabilitat." + inquiryUrl: "URL de consulta " + inquiryUrlDescription: "Escriu adreça URL per al formulari de consulta per al mantenidor del servidor o una pàgina web amb el contacte d'informació." _accountMigration: moveFrom: "Migrar un altre compte a aquest" moveFromSub: "Crear un àlies per un altre compte" @@ -1651,6 +1713,7 @@ _role: gtlAvailable: "Pot veure la línia de temps global" ltlAvailable: "Pot veure la línia de temps local" canPublicNote: "Pot enviar notes públiques" + mentionMax: "Nombre màxim de mencions a una nota" canInvite: "Pot crear invitacions a la instància " inviteLimit: "Límit d'invitacions " inviteLimitCycle: "Temps de refresc de les invitacions" @@ -1659,6 +1722,7 @@ _role: canManageAvatarDecorations: "Gestiona les decoracions dels avatars " driveCapacity: "Capacitat del disc" alwaysMarkNsfw: "Marca sempre els fitxers com a sensibles" + canUpdateBioMedia: "Permet l'edició d'una icona o un bàner" pinMax: "Nombre màxim de notes fixades" antennaMax: "Nombre màxim d'antenes" wordMuteMax: "Nombre màxim de caràcters permesos a les paraules silenciades" @@ -1673,9 +1737,20 @@ _role: canSearchNotes: "Pot cercar notes" canUseTranslator: "Pot fer servir el traductor" avatarDecorationLimit: "Nombre màxim de decoracions que es poden aplicar els avatars" + canImportAntennas: "Autoritza la importació d'antenes " + canImportBlocking: "Autoritza la importació de bloquejats" + canImportFollowing: "Autoritza la importació de seguidors" + canImportMuting: "Autoritza la importació de silenciats" + canImportUserLists: "Autoritza la importació de llistes d'usuaris " _condition: + roleAssignedTo: "Assignat a rols manuals" isLocal: "Usuari local" isRemote: "Usuari remot" + isCat: "Usuaris gats" + isBot: "Usuaris bots" + isSuspended: "Usuari suspès" + isLocked: "Comptes privats" + isExplorable: "Fes que el compte aparegui a les cerques" createdLessThan: "Han passat menys de X a passat des de la creació del compte" createdMoreThan: "Han passat més de X des de la creació del compte" followersLessThanOrEq: "Té menys de X seguidors" @@ -1745,6 +1820,7 @@ _plugin: installWarn: "Si us plau, no instal·lis afegits que no siguin de confiança." manage: "Gestionar els afegits" viewSource: "Veure l'origen " + viewLog: "Mostra el registre" _preferencesBackups: list: "Llista de còpies de seguretat" saveNew: "Fer una còpia de seguretat nova" @@ -1774,6 +1850,8 @@ _aboutMisskey: contributors: "Col·laboradors principals" allContributors: "Tots els col·laboradors " source: "Codi font" + original: "Original" + thisIsModifiedVersion: "En {name} fa servir una versió modificada de Misskey." translation: "Tradueix Misskey" donate: "Fes un donatiu a Misskey" morePatrons: "També agraïm el suport d'altres col·laboradors que no surten en aquesta llista. Gràcies! 🥰" @@ -1901,6 +1979,7 @@ _soundSettings: driveFileTypeWarnDescription: "Seleccionar un fitxer d'àudio " driveFileDurationWarn: "L'àudio és massa llarg" driveFileDurationWarnDescription: "Els àudios molt llargs pot interrompre l'ús de Misskey. Vols continuar?" + driveFileError: "El so no es pot carregar. Canvia la configuració" _ago: future: "Futur " justNow: "Ara mateix" @@ -1953,6 +2032,7 @@ _2fa: backupCodesDescription: "Si l'aplicació d'autenticació no es pot utilitzar, es pot accedir al compte utilitzant els següents codis de còpia de seguretat. Assegura't de mantenir aquests codis en un lloc segur. Cada codi es pot utilitzar només una vegada." backupCodeUsedWarning: "Es va utilitzar un codi de còpia de seguretat. Si l'aplicació de certificació està disponible, reconfigura l'aplicació d'autenticació tan aviat com sigui possible." backupCodesExhaustedWarning: "Es van utilitzar tots els codis de còpia de seguretat. Si no es pot utilitzar l'aplicació d'autenticació, ja no es pot accedir al compte. Torna a registrar l'aplicació d'autenticació." + moreDetailedGuideHere: "Aquí tens una guia al detall" _permissions: "read:account": "Veure la informació del compte." "write:account": "Editar la informació del compte." @@ -2026,22 +2106,73 @@ _permissions: "read:admin:emoji": "Veure emojis" "write:admin:queue": "Gestionar la cua de feines" "read:admin:queue": "Veure la cua de feines" + "write:admin:promo": "Gestiona les notes promocionals" + "write:admin:drive": "Gestiona el disc de l'usuari" + "read:admin:drive": "Veure la informació del disc de l'usuari" + "read:admin:stream": "Fes servir l'API sobre Websocket per l'administració" + "write:admin:ad": "Gestiona la publicitat" + "read:admin:ad": "Veure anuncis" + "write:invite-codes": "Crear codis d'invitació" + "read:invite-codes": "Obtenir codis d'invitació" + "write:clip-favorite": "Gestionar els clips favorits" + "read:clip-favorite": "Veure clips favorits" + "read:federation": "Veure dades de federació" + "write:report-abuse": "Informar d'un abús" +_auth: + shareAccessTitle: "Concedeix permisos a l'aplicació" + shareAccess: "Vols que {name} pugui accedir al vostre compte?" + shareAccessAsk: "Segur que vols que aquesta aplicació pugui accedir al vostre compte?" + permission: "{name} demana els següents permisos" + permissionAsk: "Aquesta aplicació demana els següents permisos" + pleaseGoBack: "Si us plau, torna a l'aplicació" + callback: "Tornant a l'aplicació" + denied: "Accés denegat" + pleaseLogin: "Si us plau, identificat per autoritzar l'aplicació." _antennaSources: all: "Totes les publicacions" homeTimeline: "Publicacions dels usuaris seguits" users: "Publicacions d'usuaris específics" userList: "Publicacions d'una llista d'usuaris" + userBlacklist: "Totes les notes excepte les d'un o alguns usuaris especificats" +_weekday: + sunday: "Diumenge" + monday: "Dilluns" + tuesday: "Dimarts" + wednesday: "Dimecres" + thursday: "Dijous" + friday: "Divendres" + saturday: "Dissabte" _widgets: profile: "Perfil" instanceInfo: "Informació del fitxer d'instal·lació" + memo: "Notes adhesives" notifications: "Notificacions" timeline: "Línia de temps" + calendar: "Calendari" + trends: "Tendència" + clock: "Rellotge" + rss: "Lector RSS" + rssTicker: "RSS ticker" activity: "Activitat" + photos: "Fotografies" + digitalClock: "Rellotge digital" + unixClock: "Rellotge UNIX" federation: "Federació" + instanceCloud: "Núvol d'instàncies" + postForm: "Formulari de publicació" + slideshow: "Presentació" button: "Botó " + onlineUsers: "Usuaris actius" jobQueue: "Cua de tasques" + serverMetric: "Mètriques del servidor" + aiscript: "Consola AiScript" + aiscriptApp: "Aplicació AiScript" + aichan: "Ai" + userList: "Llistat d'usuaris" _userList: chooseList: "Tria una llista" + clicker: "Clicker" + birthdayFollowings: "Usuaris que fan l'aniversari avui" _cw: hide: "Amagar" show: "Carregar més" @@ -2107,25 +2238,74 @@ _profile: avatarDecorationMax: "Pot afegir un màxim de {max} decoracions." _exportOrImport: allNotes: "Totes les publicacions" + favoritedNotes: "Notes preferides" clips: "Retalls" followingList: "Seguint" muteList: "Silencia" blockingList: "Bloqueja" userLists: "Llistes" + excludeMutingUsers: "Exclou usuaris silenciats" + excludeInactiveUsers: "Exclou usuaris inactius" + withReplies: "Inclou a la línia de temps les respostes d'usuaris importats" _charts: federation: "Federació" + apRequest: "Peticions" + usersIncDec: "Diferència entre el nombre d'usuaris" + usersTotal: "Nombre total d'usuaris" + activeUsers: "Usuaris actius" + notesIncDec: "Diferència entre el nombre de notes" + localNotesIncDec: "Diferencia en el nombre de notes locals" + remoteNotesIncDec: "Diferencia en el nombre de notes remotes" + notesTotal: "Nombre total de notes" + filesIncDec: "Diferencia en el nombre de fitxers" + filesTotal: "Nombre total de fitxers" + storageUsageIncDec: "Diferencia en l'emmagatzematge usat" + storageUsageTotal: "Emmagatzematge usat" +_instanceCharts: + requests: "Peticions" + users: "Diferència entre el nombre d'usuaris" + usersTotal: "Usuaris totals acumulats" + notes: "Diferència entre el nombre de notes" + notesTotal: "Notes totals acumulades" + ff: "Diferència en nombre d'usuaris seguits / seguidors" + ffTotal: "Nombre total acumulat d'usuaris seguits / seguidors" + cacheSize: "Diferència a la mida de la memòria cau" + cacheSizeTotal: "Total acumulat de la mida de la memòria cau" + files: "Diferència al nombre d'arxius" + filesTotal: "Nombre acumulatiu de fitxers" _timelines: home: "Inici" local: "Local" social: "Social" global: "Global" _play: + new: "Crear un guió" + edit: "Editar guió" + created: "Guió creat" + updated: "Guió editat" + deleted: "Guió esborrat" + pageSetting: "Configuració del guió" + editThisPage: "Edita aquest guió" viewSource: "Veure l'origen " + my: "Els meus guions" + liked: "Guions que m'han agradat" featured: "Popular" title: "Títol " script: "Script" summary: "Descripció" + visibilityDescription: "" _pages: + newPage: "pa" + editPage: "Editar la pàgina" + readPage: "Veure el codi font d'aquesta pàgina" + created: "La pàgina ha sigut creada correctament" + updated: "La pàgina s'ha editat correctament" + deleted: "La pàgina s'ha esborrat sense problemes" + pageSetting: "Configuració de la pàgina" + nameAlreadyExists: "L'adreça URL de la pàgina ja existeix" + invalidNameTitle: "L'adreça URL de la pàgina no és vàlida" + invalidNameText: "Assegurat que el títol de la pàgina no és buit" + editThisPage: "Editar la pàgina" viewSource: "Veure l'origen " viewPage: "Veure les teves pàgines " like: "M'agrada " @@ -2148,6 +2328,7 @@ _pages: eyeCatchingImageSet: "Escull una miniatura" eyeCatchingImageRemove: "Esborrar la miniatura" chooseBlock: "Afegeix un bloc" + enterSectionTitle: "Escriu el títol de la secció" selectType: "Seleccionar tipus" contentBlocks: "Contingut" inputBlocks: "Entrada " @@ -2158,6 +2339,8 @@ _pages: section: "Secció " image: "Imatges" button: "Botó " + dynamic: "Blocs dinàmics" + dynamicDescription: "Aquest bloc és antic. Ara en endavant fes servir {play}" note: "Incorporar una Nota" _note: id: "ID de la publicació" @@ -2187,29 +2370,50 @@ _notification: sendTestNotification: "Enviar notificació de prova" notificationWillBeDisplayedLikeThis: "Les notificacions és veure'n així " reactedBySomeUsers: "Han reaccionat {n} usuaris" + likedBySomeUsers: "A {n} usuaris els hi agrada la teva nota" renotedBySomeUsers: "L'han impulsat {n} usuaris" + followedBySomeUsers: "Et segueixen {n} usuaris" + flushNotification: "Netejar notificacions" _types: all: "Tots" + note: "Notes noves" follow: "Seguint" mention: "Menció" + reply: "Respostes" renote: "Renotar" quote: "Citar" reaction: "Reaccions" + pollEnded: "Enquesta terminada" + receiveFollowRequest: "Rebuda una petició de seguiment" + followRequestAccepted: "Petició de seguiment acceptada" + roleAssigned: "Rol donat" + achievementEarned: "Assoliment desbloquejat" + app: "Notificacions d'aplicacions" _actions: followBack: "t'ha seguit també" reply: "Respondre" renote: "Renotar" _deck: + alwaysShowMainColumn: "Mostrar sempre la columna principal" columnAlign: "Alinea les columnes" addColumn: "Afig una columna" + newNoteNotificationSettings: "Configuració de notificacions per a notes noves" + configureColumn: "Configuració de columnes" swapLeft: "Mou a l’esquerra" swapRight: "Mou a la dreta" swapUp: "Mou cap amunt" swapDown: "Mou cap avall" + stackLeft: "Pila a la columna esquerra" popRight: "Col·loca a la dreta" profile: "Perfil" newProfile: "Perfil nou" deleteProfile: "Elimina el perfil" + introduction: "Crea la interfície perfecta posant les columnes allà on vulguis!" + introduction2: "Fes clic al botó + de la dreta per afegir noves columnes sempre que vulguis." + widgetsIntroduction: "Selecciona \"Editar ginys\" a la columna del menú i afegeix un." + useSimpleUiForNonRootPages: "Usa una interfície senzilla per a les pàgines navegades" + usedAsMinWidthWhenFlexible: "L'amplada mínima es farà servir quan \"Ajust automàtic de l'amplada\" estigui activat" + flexible: "Ajust automàtic de l'amplada" _columns: main: "Principal" widgets: "Ginys" @@ -2220,18 +2424,77 @@ _deck: channel: "Canals" mentions: "Mencions" direct: "Publicacions directes" + roleTimeline: "Línia de temps dels rols" +_dialog: + charactersExceeded: "Has arribat al màxim de caràcters! Actualment és {current} de {max}" + charactersBelow: "Ets per sota del mínim de caràcters! Actualment és {current} de {min}" +_disabledTimeline: + title: "Línia de tems desactivada" + description: "No pots fer servir aquesta línia de temps amb els teus rols actuals." +_drivecleaner: + orderBySizeDesc: "Mida del fitxer descendent" + orderByCreatedAtAsc: "Data ascendent" _webhookSettings: + createWebhook: "Crear un Webhook" + modifyWebhook: "Modificar un Webhook" name: "Nom" + secret: "Secret" + trigger: "Activador" active: "Activat" + _events: + follow: "Quan se segueix a un usuari" + followed: "Quan et segueixen" + note: "Quan es publica una nota" + reply: "Quan es rep una resposta" + renote: "Quan es renoti" + reaction: "Quan es rep una reacció " + mention: "Quan et mencionen" + _systemEvents: + abuseReport: "Quan reps un nou informe de moderació " + abuseReportResolved: "Quan resols un informe de moderació " + userCreated: "Quan es crea un usuari" + deleteConfirm: "Segur que vols esborrar el webhook?" + testRemarks: "Si feu clic al botó a la dreta de l'interruptor, podeu enviar un webhook de prova amb dades dummy." _abuseReport: _notificationRecipient: + createRecipient: "Afegeix un destinatari a l'informe de moderació " + modifyRecipient: "Editar un destinatari en l'informe de moderació " + recipientType: "Tipus de notificació " _recipientType: mail: "Correu electrònic" + webhook: "Webhook" + _captions: + mail: "Enviar un correu electrònic a tots els moderadors quan es rep un informe de moderació " + webhook: "Enviar una notificació al SystemWebhook quan es rebi o es resolgui un informe de moderació " + keywords: "Paraules clau" + notifiedUser: "Usuaris que s'han de notificar " + notifiedWebhook: "Webhook que s'ha de fer servir" + deleteConfirm: "Segur que vols esborrar el destinatari de l'informe de moderació?" _moderationLogTypes: + createRole: "Rol creat" + deleteRole: "Rol esborrat" + updateRole: "Rol actualitzat" + assignRole: "Assignat al rol" + unassignRole: "Esborrat del rol" suspend: "Suspèn" + unsuspend: "Suspensió treta" + addCustomEmoji: "Afegit emoji personalitzat" + updateCustomEmoji: "Actualitzat emoji personalitzat" + deleteCustomEmoji: "Esborrat emoji personalitzat" + updateServerSettings: "Configuració del servidor actualitzada" + updateUserNote: "Nota de moderació actualitzada" + deleteDriveFile: "Fitxer esborrat" + deleteNote: "Nota esborrada" + createGlobalAnnouncement: "Anunci global creat" + createUserAnnouncement: "Anunci individual creat" + updateGlobalAnnouncement: "Anunci global actualitzat" + updateUserAnnouncement: "Anunci individual actualitzat " + deleteGlobalAnnouncement: "Anunci global esborrat" + deleteUserAnnouncement: "Anunci individual esborrat " resetPassword: "Restableix la contrasenya" suspendRemoteInstance: "Servidor remot suspès " unsuspendRemoteInstance: "S'ha tret la suspensió del servidor remot" + updateRemoteInstanceNote: "Nota de moderació de la instància remota actualitzada" markSensitiveDriveFile: "Fitxer marcat com a sensible" unmarkSensitiveDriveFile: "S'ha tret la marca de sensible del fitxer" resolveAbuseReport: "Informe resolt" @@ -2244,6 +2507,16 @@ _moderationLogTypes: deleteAvatarDecoration: "S'ha esborrat la decoració de l'avatar " unsetUserAvatar: "Esborrar l'avatar d'aquest usuari" unsetUserBanner: "Esborrar el bàner d'aquest usuari" + createSystemWebhook: "Crear un SystemWebhook" + updateSystemWebhook: "Actualitzar SystemWebhook" + deleteSystemWebhook: "Esborrar SystemWebhook" + createAbuseReportNotificationRecipient: "Crear un destinatari per l'informe de moderació " + updateAbuseReportNotificationRecipient: "Actualitzar destinatari per l'informe de moderació " + deleteAbuseReportNotificationRecipient: "Esborrar destinatari de l'informe de moderació " + deleteAccount: "Esborrar el compte " + deletePage: "Esborrar la pàgina" + deleteFlash: "Esborrar el guió" + deleteGalleryPost: "Esborrar la publicació de la galeria" _fileViewer: title: "Detall del fitxer" type: "Tipus de fitxer" @@ -2270,5 +2543,54 @@ _externalResourceInstaller: _errors: _invalidParams: title: "Paràmetres no vàlids " + description: "No hi ha suficient informació per carregar les dades del lloc extern. Confirma l'URL que hi ha escrita." + _resourceTypeNotSupported: + title: "El recurs extern no està suportat." + description: "Aquesta mena de recurs no està suportat. Contacta amb l'administrador." + _failedToFetch: + title: "Ha fallat l'obtenció de dades" + fetchErrorDescription: "Ha aparegut un error comunicant-se amb el lloc extern. Si després d'intentar-ho un altre cop no es resol, contacta amb l'administrador." + parseErrorDescription: "Ha aparegut un error processant les dades carregades del lloc extern. Contacta amb l'administrador." + _hashUnmatched: + title: "Ha fallat la verificació de les dades" + description: "Ha aparegut un error verificant les dades obtingudes. Com a mesura de seguretat la instal·lació no pot continuar. Contacta amb l'administrador." + _pluginParseFailed: + title: "Error d'AiScript" + description: "Les dades sol·licitades s'han obtingut correctament, però hem trobat un error durant el processament d'AiScript. Contacta amb l'autor de l'afegit. Detalls de l'error es pot veure a la consola JavaScript." + _pluginInstallFailed: + title: "La instal·lació de l'afegit a fallat" + description: "Ha aparegut un error durant la instal·lació de l'afegit. Intenta-ho una altra vegada. El detall de l'error es pot veure a la consola JavaScript." + _themeParseFailed: + title: "Ha fallat el processament del tema" + description: "Les dades sol·licitades s'han obtingut correctament, però hem trobat un error durant el processament del tema. Contacta amb l'autor de l'afegit. Detalls de l'error es pot veure a la consola JavaScript." + _themeInstallFailed: + title: "La instal·lació del tema a fallat" + description: "Ha aparegut un error durant la instal·lació del tema. Intenta-ho una altra vegada. El detall de l'error es pot veure a la consola JavaScript." +_dataSaver: + _media: + title: "Carregant multimèdia " + description: "Desactiva la càrrega automàtica d'imatges i vídeos. Les imatges i els vídeos amagats es carregaran quan es faci clic a sobre." + _avatar: + title: "Avatars animats" + description: "Detenir l'animació dels avatars animats. Les imatges animades solen tenir un pes més gran que les imatges normals, reduint el tràfic disponible." + _urlPreview: + title: "Miniatures vista prèvia de l'URL" + description: "Les imatges en miniatura que serveixen com a vista prèvia de les URLs no es tornaran a carregar." + _code: + title: "Ressaltat del codi " _reversi: total: "Total" +_embedCodeGen: + title: "Personalitza el codi per incrustar" + header: "Mostrar la capçalera" + autoload: "Carregar automàticament (no recomanat)" + maxHeight: "Alçada màxima" + maxHeightDescription: "0 anul·la la configuració màxima. Per evitar que continuï creixent verticalment, especifiqui qualsevol valor." + maxHeightWarn: "El límit màxim d'alçada és nul (0). Si això no és un canvi previst, estableix el màxim d'alçada a un cert valor." + previewIsNotActual: "La visualització és diferent de la que es mostra quan s'implanta." + rounded: "Angle recte" + border: "Afegeix un marc al contenidor" + applyToPreview: "Aplica a la vista prèvia" + generateCode: "Crea el codi per incrustar" + codeGenerated: "Codi generat" + codeGeneratedDescription: "Si us plau, enganxeu el codi generat al lloc web." diff --git a/locales/en-US.yml b/locales/en-US.yml index c82ea3c9a2..61e45628ae 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1079,7 +1079,7 @@ enableChartsForRemoteUser: "Generate remote user data charts" enableChartsForFederatedInstances: "Generate remote instance data charts" showClipButtonInNoteFooter: "Add \"Clip\" to note action menu" reactionsDisplaySize: "Reaction display size" -limitWidthOfReaction: "Limits the maximum width of reactions and display them in reduced size." +limitWidthOfReaction: "Limit the maximum width of reactions and display them in reduced size." noteIdOrUrl: "Note ID or URL" video: "Video" videos: "Videos" @@ -1263,6 +1263,10 @@ confirmWhenRevealingSensitiveMedia: "Confirm when revealing sensitive media" sensitiveMediaRevealConfirm: "This might be a sensitive media. Are you sure to reveal?" createdLists: "Created lists" createdAntennas: "Created antennas" +fromX: "From {x}" +genEmbedCode: "Generate embed code" +noteOfThisUser: "Notes by this user" +clipNoteLimitExceeded: "No more notes can be added to this clip." _delivery: status: "Delivery status" stop: "Suspended" @@ -2439,7 +2443,7 @@ _webhookSettings: mention: "When being mentioned" _systemEvents: abuseReport: "When received a new abuse report" - abuseReportResolved: "When resolved abuse reports" + abuseReportResolved: "When resolved abuse report" userCreated: "When user is created" deleteConfirm: "Are you sure you want to delete the Webhook?" _abuseReport: @@ -2640,3 +2644,17 @@ _contextMenu: app: "Application" appWithShift: "Application with shift key" native: "Native" +_embedCodeGen: + title: "Customize embed code" + header: "Show header" + autoload: "Automatically load more (deprecated)" + maxHeight: "Max height" + maxHeightDescription: "Setting it to 0 disables the max height setting. Specify some value to prevent the widget from continuing to expand vertically." + maxHeightWarn: "The max height limit is disabled (0). If this was not intended, set the max height to some value." + previewIsNotActual: "The display differs from the actual embedding because it exceeds the range displayed on the preview screen." + rounded: "Make it rounded" + border: "Add a border to the outer frame" + applyToPreview: "Apply to the preview" + generateCode: "Generate embed code" + codeGenerated: "The code has been generated" + codeGeneratedDescription: "Paste the generated code into your website to embed the content." diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 2621965d1b..45ad792717 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -109,11 +109,14 @@ enterEmoji: "Ingresar emojis" renote: "Renotar" unrenote: "Quitar renota" renoted: "Renotado" +renotedToX: "{name} usuarios han renotado。" cantRenote: "No se puede renotar este post" cantReRenote: "No se puede renotar una renota" quote: "Citar" inChannelRenote: "Renota sólo del canal" inChannelQuote: "Cita sólo del canal" +renoteToChannel: "Renotar a otro canal" +renoteToOtherChannel: "Renotar a otro canal" pinnedNote: "Nota fijada" pinned: "Fijar al perfil" you: "Tú" @@ -152,6 +155,7 @@ editList: "Editar lista" selectChannel: "Seleccionar canal" selectAntenna: "Seleccionar antena" editAntenna: "Editar antena" +createAntenna: "Crear una antena" selectWidget: "Seleccionar widget" editWidgets: "Editar widgets" editWidgetsExit: "Terminar edición" @@ -178,6 +182,10 @@ addAccount: "Agregar Cuenta" reloadAccountsList: "Recargar lista de cuentas" loginFailed: "Error al iniciar sesión." showOnRemote: "Ver en una instancia remota" +continueOnRemote: "Ver en una instancia remota" +chooseServerOnMisskeyHub: "Elegir un servidor en Misskey Hub" +specifyServerHost: "Especifica una instancia directamente" +inputHostName: "Introduzca el dominio" general: "General" wallpaper: "Fondo de pantalla" setWallpaper: "Establecer fondo de pantalla" @@ -1095,6 +1103,8 @@ preservedUsernames: "Nombre de usuario reservado" preservedUsernamesDescription: "La lista de nombres de usuario para reservar tienen que separarse con saltos de línea.\nEstos estarán indisponibles durante la creación de cuentas, pero pueden ser usados para que los administradores puedan crear esas cuentas manualmente. Las cuentas existentes con esos nombres de usuario no se verán afectadas." createNoteFromTheFile: "Componer una nota desde éste archivo" archive: "Archivo" +archived: "Archivado" +unarchive: "Desarchivar" channelArchiveConfirmTitle: "¿Seguro de archivar {name}?" channelArchiveConfirmDescription: "Un canal archivado no aparecerá en la lista de canales ni en los resultados. Las nuevas publicaciones tampoco serán añadidas." thisChannelArchived: "El canal ha sido archivado." @@ -2396,6 +2406,8 @@ _abuseReport: _notificationRecipient: _recipientType: mail: "Correo" + webhook: "Webhook" + keywords: "Palabras Clave" _moderationLogTypes: createRole: "Rol creado" deleteRole: "Rol eliminado" @@ -2499,6 +2511,7 @@ _hemisphere: S: "Hemisferio sur" _reversi: reversi: "Reversi" + rules: "Reglas" won: "{name} ha ganado" total: "Total" _urlPreviewSetting: diff --git a/locales/id-ID.yml b/locales/id-ID.yml index 24f7482fca..b579d3261b 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -60,6 +60,7 @@ copyFileId: "Salin Berkas" copyFolderId: "Salin Folder" copyProfileUrl: "Salin Alamat Web Profil" searchUser: "Cari pengguna" +searchThisUsersNotes: "Mencari catatan pengguna" reply: "Balas" loadMore: "Selebihnya" showMore: "Selebihnya" @@ -154,6 +155,7 @@ editList: "Sunting daftar" selectChannel: "Pilih kanal" selectAntenna: "Pilih Antena" editAntenna: "Sunting antena" +createAntenna: "Membuat antena." selectWidget: "Pilih gawit" editWidgets: "Sunting gawit" editWidgetsExit: "Selesai" diff --git a/locales/index.d.ts b/locales/index.d.ts index 2a27eb3e15..1250aa4f4d 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -2053,9 +2053,21 @@ export interface Locale extends ILocale { */ "native": string; /** - * メニューをドロワーで表示しない + * メニューのスタイル */ - "disableDrawer": string; + "menuStyle": string; + /** + * スタイル + */ + "style": string; + /** + * ドロワー + */ + "drawer": string; + /** + * ポップアップ + */ + "popup": string; /** * ノートのアクションをホバー時のみ表示する */ @@ -5108,6 +5120,22 @@ export interface Locale extends ILocale { * {n}件の変更があります */ "thereAreNChanges": ParameterizedString<"n">; + /** + * パスキーでログイン + */ + "signinWithPasskey": string; + /** + * 登録されていないパスキーです。 + */ + "unknownWebAuthnKey": string; + /** + * パスキーの検証に失敗しました。 + */ + "passkeyVerificationFailed": string; + /** + * パスキーの検証に成功しましたが、パスワードレスログインが無効になっています。 + */ + "passkeyVerificationSucceededButPasswordlessLoginDisabled": string; "_delivery": { /** * 配信状態 diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 2b4b1e425e..f56cd4e5b2 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -1,6 +1,6 @@ --- _lang_: "Italiano" -headlineMisskey: "Rete collegata tramite note" +headlineMisskey: "Rete collegata tramite Note" introMisskey: "Eccoci! Misskey è un servizio di microblogging decentralizzato, libero e aperto. \n\n📡 Puoi pubblicare «Note» per condividere ciò che sta succedendo o per dire a tutti qualcosa su di te. \n\n👍 Puoi reagire inviando emoji rapidi alle «Note» provenienti da altri profili nel Fediverso.\n\n🚀 Esplora un nuovo mondo insieme a noi!" poweredByMisskeyDescription: "{name} è uno dei servizi (chiamati istanze) che utilizzano la piattaforma open source Misskey." monthAndDay: "{day}/{month}" @@ -60,6 +60,7 @@ copyFileId: "Copia ID del file" copyFolderId: "Copia ID della cartella" copyProfileUrl: "Copia URL del profilo" searchUser: "Cerca profilo" +searchThisUsersNotes: "Cerca le sue Note" reply: "Rispondi" loadMore: "Mostra di più" showMore: "Espandi" @@ -154,6 +155,7 @@ editList: "Modifica Lista" selectChannel: "Seleziona canale" selectAntenna: "Scegli un'antenna" editAntenna: "Modifica Antenna" +createAntenna: "Crea Antenna" selectWidget: "Seleziona il riquadro" editWidgets: "Modifica i riquadri" editWidgetsExit: "Conferma le modifiche" @@ -194,6 +196,7 @@ followConfirm: "Vuoi seguire {name}?" proxyAccount: "Profilo proxy" proxyAccountDescription: "Un profilo proxy funziona come follower per i profili remoti, sotto certe condizioni. Ad esempio, quando un profilo locale ne inserisce uno remoto in una lista (senza seguirlo), se nessun altro segue quel profilo remoto, le attività non possono essere distribuite. Dunque, il profilo proxy le seguirà per tutti." host: "Host" +selectSelf: "Segli me" selectUser: "Seleziona profilo" recipient: "Destinatario" annotation: "Annotazione preventiva" @@ -209,6 +212,7 @@ perDay: "giornaliero" stopActivityDelivery: "Interrompi la distribuzione di attività" blockThisInstance: "Bloccare l'istanza" silenceThisInstance: "Silenziare l'istanza" +mediaSilenceThisInstance: "Silenzia i media dell'istanza" operations: "Operazioni" software: "Software" version: "Versione" @@ -230,6 +234,8 @@ blockedInstances: "Istanze bloccate" blockedInstancesDescription: "Elenca le istanze che vuoi bloccare, una per riga. Esse non potranno più interagire con la tua istanza." silencedInstances: "Istanze silenziate" silencedInstancesDescription: "Elenca i nomi host delle istanze che vuoi silenziare. Tutti i profili nelle istanze silenziate vengono trattati come tali. Possono solo inviare richieste di follow e menzionare soltanto i profili locali che seguono. Le istanze bloccate non sono interessate." +mediaSilencedInstances: "Istanze coi media silenziati" +mediaSilencedInstancesDescription: "Elenca i nomi host delle istanze di cui vuoi silenziare i media, uno per riga. Tutti gli allegati dei profili nelle istanze silenziate per via degli allegati espliciti, verranno impostati come tali, le emoji personalizzate non saranno disponibili. Le istanze bloccate sono escluse." muteAndBlock: "Silenziare e bloccare" mutedUsers: "Profili silenziati" blockedUsers: "Profili bloccati" @@ -449,7 +455,7 @@ securityKeyAndPasskey: "Chiave di sicurezza e accesso" securityKey: "Chiave di sicurezza" lastUsed: "Ultima attività" lastUsedAt: "Uso più recente: {t}" -unregister: "Annulla l'iscrizione" +unregister: "Rimuovi autenticazione a due fattori (2FA/MFA)" passwordLessLogin: "Accedi senza password" passwordLessLoginDescription: "Accedi senza password, usando la chiave di sicurezza" resetPassword: "Ripristina la password" @@ -559,7 +565,7 @@ deleteAll: "Cancella cronologia" showFixedPostForm: "Visualizzare la finestra di pubblicazione in cima alla timeline" showFixedPostFormInChannel: "Per i canali, mostra il modulo di pubblicazione in cima alla timeline" withRepliesByDefaultForNewlyFollowed: "Quando segui nuovi profili, includi le risposte in TL come impostazione predefinita" -newNoteRecived: "Nuove note da leggere" +newNoteRecived: "Nuove Note da leggere" sounds: "Impostazioni suoni" sound: "Suono" listen: "Ascolta" @@ -586,6 +592,8 @@ ascendingOrder: "Aumenta" descendingOrder: "Diminuisce" scratchpad: "ScratchPad" scratchpadDescription: "Lo Scratchpad offre un ambiente per esperimenti di AiScript. È possibile scrivere, eseguire e confermare i risultati dell'interazione del codice con Misskey." +uiInspector: "UI Inspector" +uiInspectorDescription: "Puoi visualizzare un elenco di elementi UI presenti in memoria. I componenti dell'interfaccia utente vengono generati dalle funzioni Ui:C:." output: "Uscita" script: "Script" disablePagesScript: "Disabilita AiScript nelle pagine" @@ -1106,6 +1114,8 @@ preservedUsernames: "Nomi utente riservati" preservedUsernamesDescription: "Elenca, uno per linea, i nomi utente che non possono essere registrati durante la creazione del profilo. La restrizione non si applica agli amministratori. Inoltre, i profili già registrati sono esenti." createNoteFromTheFile: "Crea Nota da questo file" archive: "Archivio" +archived: "Archiviato" +unarchive: "Annulla archiviazione" channelArchiveConfirmTitle: "Vuoi davvero archiviare {name}?" channelArchiveConfirmDescription: "Un canale archiviato non compare nell'elenco canali, nemmeno nei risultati di ricerca. Non può ricevere nemmeno nuove Note." thisChannelArchived: "Questo canale è stato archiviato." @@ -1116,6 +1126,9 @@ preventAiLearning: "Impedisci l'apprendimento della IA" preventAiLearningDescription: "Aggiungendo il campo \"noai\" alla risposta HTML, si indica ai Robot esterni di non usare testi e allegati per addestrare sistemi di Machine Learning (IA predittiva/generativa). Anche se è impossibile sapere se la richiesta venga onorata o semplicemente ignorata." options: "Opzioni del ruolo" specifyUser: "Profilo specifico" +lookupConfirm: "Vuoi davvero richiedere informazioni?" +openTagPageConfirm: "Vuoi davvero aprire la pagina dell'hashtag?" +specifyHost: "Specifica l'host" failedToPreviewUrl: "Anteprima non disponibile" update: "Aggiorna" rolesThatCanBeUsedThisEmojiAsReaction: "Ruoli che possono usare questa emoji come reazione" @@ -1250,6 +1263,13 @@ inquiry: "Contattaci" tryAgain: "Per favore riprova" confirmWhenRevealingSensitiveMedia: "Richiedi conferma prima di mostrare gli allegati espliciti" sensitiveMediaRevealConfirm: "Questo allegato è esplicito, vuoi vederlo?" +createdLists: "Liste create" +createdAntennas: "Antenne create" +fromX: "Da {x}" +genEmbedCode: "Ottieni il codice di incorporamento" +noteOfThisUser: "Elenco di Note di questo profilo" +clipNoteLimitExceeded: "Non è possibile aggiungere ulteriori Note a questa Clip." +performance: "Prestazioni" _delivery: status: "Stato della consegna" stop: "Sospensione" @@ -1304,7 +1324,7 @@ _initialAccountSetting: skipAreYouSure: "Vuoi davvero saltare la configurazione iniziale?" laterAreYouSure: "Vuoi davvero rimandare la configurazione iniziale?" _initialTutorial: - launchTutorial: "Guarda il tutorial" + launchTutorial: "Inizia il tutorial" title: "Tutorial" wellDone: "Ottimo lavoro!" skipAreYouSure: "Vuoi davvero interrompere il tutorial?" @@ -1314,13 +1334,13 @@ _initialTutorial: _note: title: "Cosa sono le Note?" description: "Gli status su Misskey sono chiamati \"Note\". Le Note sono elencate in ordine cronologico nelle timeline e vengono aggiornate in tempo reale." - reply: "Puoi rispondere alle Note. Puoi anche rispondere alle risposte e continuare i dialoghi come un conversazioni." - renote: "Puoi ri-condividere le Note, facendole rifluire sulla Timeline. Puoi anche aggiungere testo e citare altri profili." - reaction: "Puoi aggiungere una reazione. Nella pagina successiva spiegheremo i dettagli." - menu: "Puoi svolgere varie attività, come visualizzare i dettagli delle Note o copiare i collegamenti." + reply: "Puoi rispondere alle Note, alle altre risposte e dialogare in conversazioni." + renote: "Puoi ri-condividere le Note, ritorneranno sulla Timeline. Aggiungendo del testo, scriverai una Citazione." + reaction: "Puoi aggiungere una reazione. Nella pagina successiva ti spiego come." + menu: "Per altre attività, ad esempio, vedere i dettagli delle Note o copiare i collegamenti." _reaction: title: "Cosa sono le Reazioni?" - description: "Puoi reagire alle Note. Le sensazioni che non si riescono a trasmettere con i \"Mi piace\" si possono esprimere facilmente inviando una reazione." + description: "Reazioni alle Note. Le sensazioni che non si possono descrivere con \"Mi piace\" si esprimono facilmente con le reazioni." letsTryReacting: "Puoi aggiungere una Reazione cliccando il bottone \"+\" (più) della relativa Nota. Prova ad aggiungerne una a questa Nota di esempio!" reactToContinue: "Aggiungere la Reazione ti consentirà di procedere col tutorial." reactNotification: "Quando qualcuno reagisce alle tue Note, ricevi una notifica in tempo reale." @@ -1333,7 +1353,7 @@ _initialTutorial: social: "sia le Note della Timeline Home che quelle della Timeline Locale, insieme!" global: "le Note da pubblicate da tutte le altre istanze federate con la nostra." description2: "Nella parte superiore dello schermo, puoi scegliere una Timeline o l'altra in qualsiasi momento." - description3: "Ci sono anche sequenze temporali di elenchi, sequenze temporali di canali, ecc. Per ulteriori dettagli, consultare il {link}.\nPuoi vedere anche Timeline delle liste di profili (se ne hai create), canali, ecc... Per i dettagli, visita {link}." + description3: "Ci sono anche sequenze temporali di elenchi, sequenze temporali di canali, ecc. Per ulteriori dettagli, consultare la {link}.\nPuoi vedere anche Timeline delle liste di profili (se ne hai create), canali, ecc... Per i dettagli, c'è la {link}." _postNote: title: "La Nota e le sue impostazioni" description1: "Quando scrivi una Nota su Misskey, hai a disposizione varie opzioni. Il modulo di invio è simile a questo." @@ -1384,6 +1404,7 @@ _serverSettings: fanoutTimelineDescription: "Attivando questa funzionalità migliori notevolmente la capacità delle Timeline di collezionare Note, riducendo il carico sul database. Tuttavia, aumenterà l'impiego di memoria RAM per Redis. Disattiva se il tuo server ha poca RAM o la funzionalità è irregolare." 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." inquiryUrl: "URL di contatto" inquiryUrlDescription: "Specificare l'URL al modulo di contatto, oppure le informazioni con i dati di contatto dell'amministrazione." _accountMigration: @@ -1717,6 +1738,11 @@ _role: canSearchNotes: "Ricercare nelle Note" canUseTranslator: "Tradurre le Note" avatarDecorationLimit: "Numero massimo di decorazioni foto profilo installabili" + canImportAntennas: "Può importare Antenne" + canImportBlocking: "Può importare Blocchi" + canImportFollowing: "Può importare Following" + canImportMuting: "Può importare Silenziati" + canImportUserLists: "Può importare liste di Profili" _condition: roleAssignedTo: "Assegnato a ruoli manualmente" isLocal: "Profilo locale" @@ -1954,6 +1980,7 @@ _soundSettings: driveFileTypeWarnDescription: "Per favore, scegli un file di tipo audio" driveFileDurationWarn: "La durata dell'audio è troppo lunga" driveFileDurationWarnDescription: "Scegliere un audio lungo potrebbe interferire con l'uso di Misskey. Vuoi continuare lo stesso?" + driveFileError: "Impossibile caricare l'audio. Si prega di modificare le impostazioni" _ago: future: "Futuro" justNow: "Adesso" @@ -2302,6 +2329,7 @@ _pages: eyeCatchingImageSet: "Imposta un'immagine attraente" eyeCatchingImageRemove: "Elimina immagine attraente" chooseBlock: "Aggiungi blocco" + enterSectionTitle: "Inserisci il titolo della sezione" selectType: "Seleziona tipo" contentBlocks: "Contenuto" inputBlocks: "Blocchi di input" @@ -2412,6 +2440,7 @@ _webhookSettings: modifyWebhook: "Modifica Webhook" name: "Nome" secret: "Segreto" + trigger: "Trigger" active: "Attivo" _events: follow: "Quando segui un profilo" @@ -2424,7 +2453,9 @@ _webhookSettings: _systemEvents: abuseReport: "Quando arriva una segnalazione" abuseReportResolved: "Quando una segnalazione è risolta" + userCreated: "Quando viene creato un profilo" deleteConfirm: "Vuoi davvero eliminare il Webhook?" + testRemarks: "Clicca il bottone a destra dell'interruttore, per provare l'invio di un webhook con dati fittizi." _abuseReport: _notificationRecipient: createRecipient: "Aggiungi destinatario della segnalazione" @@ -2483,6 +2514,10 @@ _moderationLogTypes: createAbuseReportNotificationRecipient: "Crea destinatario per le notifiche di segnalazioni" updateAbuseReportNotificationRecipient: "Aggiorna destinatario notifiche di segnalazioni" deleteAbuseReportNotificationRecipient: "Elimina destinatario notifiche di segnalazioni" + deleteAccount: "Quando viene eliminato un profilo" + deletePage: "Pagina eliminata" + deleteFlash: "Play eliminato" + deleteGalleryPost: "Eliminazione pubblicazione nella Galleria" _fileViewer: title: "Dettagli del file" type: "Tipo di file" @@ -2614,3 +2649,22 @@ _mediaControls: pip: "Sovraimpressione" playbackRate: "Velocità di riproduzione" loop: "Ripetizione infinita" +_contextMenu: + title: "Menu contestuale" + app: "Applicazione" + appWithShift: "Applicazione Shift+Tasto" + native: "Interfaccia utente del browser" +_embedCodeGen: + title: "Personalizza il codice di incorporamento" + header: "Mostra la testata" + autoload: "Carica automaticamente di più (sconsigliato)" + maxHeight: "Altezza massima" + maxHeightDescription: "Specifica un valore per evitare che continui a crescere verticalmente. Il valore 0 disabilita il limite d'altezza." + maxHeightWarn: "L'altezza massima è disabilitata (0). Se l'effetto è indesiderato, prova a impostare l'altezza massima a un valore specifico." + previewIsNotActual: "Poiché supera l'intervallo che può essere visualizzato in anteprima, la visualizzazione vera e propria sarà diversa quando effettivamente incorporata." + rounded: "Bordo arrotondato" + border: "Aggiungi un bordo al contenitore" + applyToPreview: "Applica all'anteprima" + generateCode: "Crea il codice di incorporamento" + codeGenerated: "Codice generato" + codeGeneratedDescription: "Incolla il codice appena generato sul tuo sito web." diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 80cd8dc7cc..6c5df3e658 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -509,7 +509,10 @@ uiLanguage: "UIの表示言語" aboutX: "{x}について" emojiStyle: "絵文字のスタイル" native: "ネイティブ" -disableDrawer: "メニューをドロワーで表示しない" +menuStyle: "メニューのスタイル" +style: "スタイル" +drawer: "ドロワー" +popup: "ポップアップ" showNoteActionsOnlyHover: "ノートのアクションをホバー時のみ表示する" showReactionsCount: "ノートのリアクション数を表示する" noHistory: "履歴はありません" @@ -1273,6 +1276,10 @@ performance: "パフォーマンス" modified: "変更あり" discard: "破棄" thereAreNChanges: "{n}件の変更があります" +signinWithPasskey: "パスキーでログイン" +unknownWebAuthnKey: "登録されていないパスキーです。" +passkeyVerificationFailed: "パスキーの検証に失敗しました。" +passkeyVerificationSucceededButPasswordlessLoginDisabled: "パスキーの検証に成功しましたが、パスワードレスログインが無効になっています。" _delivery: status: "配信状態" diff --git a/locales/ko-GS.yml b/locales/ko-GS.yml index 9323ed2a26..77fcc9f489 100644 --- a/locales/ko-GS.yml +++ b/locales/ko-GS.yml @@ -583,6 +583,9 @@ describeFile: "캡션 옇기" enterFileDescription: "캡션 서기" author: "맨던 사람" manage: "간리" +large: "커게" +medium: "엔갆게" +small: "쪼맪게" emailServer: "전자우펜 서버" email: "전자우펜" emailAddress: "전자우펜 주소" @@ -600,6 +603,7 @@ reporter: "신고한 사람" reporteeOrigin: "신고덴 사람" reporterOrigin: "신고한 곳" forwardReport: "웬겍 서버에 신고 보내기" +forwardReportIsAnonymous: "웬겍 서버서는 나으 정보럴 몬 보고 익멩으 시스템 게정어로 보입니다." waitingFor: "{x}(얼)럴 지달리고 잇십니다" random: "무작이" system: "시스템" @@ -613,12 +617,14 @@ followersCount: "팔로워 수" noteFavoritesCount: "질겨찾기한 노트 수" clips: "클립 맨걸기" clearCache: "캐시 비우기" +nUsers: "{n} 사용자" typingUsers: "{users} 님이 서고 잇어예" unlikeConfirm: "좋네예럴 무룹니꺼?" info: "정보" selectAccount: "계정 개리기" user: "사용자" administration: "간리" +middle: "엔갆게" translatedFrom: "{x}서 번옉" on: "킴" off: "껌" @@ -633,6 +639,7 @@ oneMonth: "한 달" file: "파일" typeToConfirm: "게속할라먼 {x}럴 누질라 주이소" pleaseSelect: "개리 주이소" +remoteOnly: "웬겍만" tools: "도구" like: "좋네예!" unlike: "좋네예 무루기" @@ -643,7 +650,10 @@ role: "옉할" noRole: "옉할이 어ᇝ십니다" thisPostMayBeAnnoyingCancel: "아이예" likeOnly: "좋네예마" +hiddenTags: "수ᇚ훈 해시태그" myClips: "내 클립" +preservedUsernames: "예약 사용자 이럼" +specifyUser: "사용자 지정" icon: "아바타" replies: "답하기" renotes: "리노트" @@ -709,6 +719,16 @@ _achievements: description: "0분 0초에 노트를 섰어예" _tutorialCompleted: description: "길라잡이럴 껕냇십니다" +_role: + displayOrder: "보기 순서" + _priority: + middle: "엔갆게" + _options: + canHideAds: "강고 수ᇚ후기" + _condition: + isRemote: "웬겍 사용자" + isCat: "갱이 사용자" + isBot: "자동 사용자" _gallery: my: "내 걸" liked: "좋네예한 걸" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 34c1cc3ebf..e9b09b0c76 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -52,14 +52,15 @@ deleteAndEditConfirm: "이 노트를 삭제한 뒤 다시 편집하시겠습니 addToList: "리스트에 추가" addToAntenna: "안테나에 추가" sendMessage: "메시지 보내기" -copyRSS: "RSS 주소 복사" +copyRSS: "RSS 복사" copyUsername: "유저명 복사" copyUserId: "유저 ID 복사" copyNoteId: "노트 ID 복사" copyFileId: "파일 ID 복사" copyFolderId: "폴더 ID 복사" copyProfileUrl: "프로필 URL 복사" -searchUser: "유저 검색" +searchUser: "사용자 검색" +searchThisUsersNotes: "사용자의 노트 검색" reply: "답글" loadMore: "더 보기" showMore: "더 보기" @@ -154,6 +155,7 @@ editList: "리스트 편집" selectChannel: "채널 선택" selectAntenna: "안테나 선택" editAntenna: "안테나 편집" +createAntenna: "안테나 만들기" selectWidget: "위젯 선택" editWidgets: "위젯 편집" editWidgetsExit: "편집 종료" @@ -194,6 +196,7 @@ followConfirm: "{name}님을 팔로우 하시겠습니까?" proxyAccount: "프록시 계정" proxyAccountDescription: "프록시 계정은 특정 조건 하에서 유저의 리모트 팔로우를 대행하는 계정입니다. 예를 들면, 유저가 리모트 유저를 리스트에 넣었을 때, 리스트에 들어간 유저를 아무도 팔로우한 적이 없다면 액티비티가 서버로 배달되지 않기 때문에, 대신 프록시 계정이 해당 유저를 팔로우하도록 합니다." host: "호스트" +selectSelf: "본인을 선택" selectUser: "유저 선택" recipient: "수신인" annotation: "내용에 대한 주석" @@ -209,6 +212,7 @@ perDay: "1일마다" stopActivityDelivery: "액티비티 보내지 않기" blockThisInstance: "이 서버를 차단" silenceThisInstance: "서버를 사일런스" +mediaSilenceThisInstance: "서버의 미디어를 사일런스" operations: "작업" software: "소프트웨어" version: "버전" @@ -230,6 +234,8 @@ blockedInstances: "차단된 서버" blockedInstancesDescription: "차단하려는 서버의 호스트 이름을 줄바꿈으로 구분하여 설정합니다. 차단된 인스턴스는 이 인스턴스와 통신할 수 없게 됩니다." silencedInstances: "사일런스한 서버" silencedInstancesDescription: "사일런스하려는 서버의 호스트명을 한 줄에 하나씩 입력합니다. 사일런스된 서버에 소속된 유저는 모두 '사일런스'된 상태로 취급되며, 이 서버로부터의 팔로우가 프로필 설정과 무관하게 승인제로 변경되고, 팔로워가 아닌 로컬 유저에게는 멘션할 수 없게 됩니다. 정지된 서버에는 적용되지 않습니다." +mediaSilencedInstances: "미디어를 사일런스한 서버" +mediaSilencedInstancesDescription: "미디어를 사일런스 하려는 서버의 호스트를 한 줄에 하나씩 입력합니다. 미디어가 사일런스된 서버의 유저가 업로드한 파일은 모두 민감한 미디어로 처리되며, 커스텀 이모지를 사용할 수 없게 됩니다. 또한, 차단한 인스턴스에는 적용되지 않습니다." muteAndBlock: "뮤트 및 차단" mutedUsers: "뮤트한 유저" blockedUsers: "차단한 유저" @@ -373,7 +379,7 @@ registration: "등록" enableRegistration: "신규 회원가입을 활성화" invite: "초대" driveCapacityPerLocalAccount: "로컬 유저 한 명당 드라이브 용량" -driveCapacityPerRemoteAccount: "리모트 유저 한 명당 드라이브 용량" +driveCapacityPerRemoteAccount: "원격 사용자별 드라이브 용량" inMb: "메가바이트 단위" bannerUrl: "배너 이미지 URL" backgroundImageUrl: "배경 이미지 URL" @@ -586,6 +592,8 @@ ascendingOrder: "오름차순" descendingOrder: "내림차순" scratchpad: "스크래치 패드" scratchpadDescription: "스크래치 패드는 AiScript 의 테스트 환경을 제공합니다. Misskey 와 상호 작용하는 코드를 작성, 실행 및 결과를 확인할 수 있습니다." +uiInspector: "UI 인스펙터" +uiInspectorDescription: "메모리에 있는 UI 컴포넌트의 인스턴트 목록을 볼 수 있습니다. UI 컴포넌트는 Ui:C: 계열 함수로 만들어집니다." output: "출력" script: "스크립트" disablePagesScript: "Pages 에서 AiScript 를 사용하지 않음" @@ -1106,6 +1114,8 @@ preservedUsernames: "예약한 사용자 이름" preservedUsernamesDescription: "예약할 사용자명을 한 줄에 하나씩 입력합니다. 여기에서 지정한 사용자명으로는 계정을 생성할 수 없게 됩니다. 단, 관리자 권한으로 계정을 생성할 때에는 해당되지 않으며, 이미 존재하는 계정도 영향을 받지 않습니다." createNoteFromTheFile: "이 파일로 노트를 작성" archive: "아카이브" +archived: "보관됨" +unarchive: "보관 취소" channelArchiveConfirmTitle: "{name} 채널을 보존하시겠습니까?" channelArchiveConfirmDescription: "보존한 채널은 채널 목록과 검색 결과에 표시되지 않으며 새로운 노트도 작성할 수 없습니다." thisChannelArchived: "이 채널은 보존되었습니다." @@ -1116,6 +1126,9 @@ preventAiLearning: "기계학습(생성형 AI)으로의 사용을 거부" preventAiLearningDescription: "외부의 문장 생성 AI나 이미지 생성 AI에 대해 제출한 노트나 이미지 등의 콘텐츠를 학습의 대상으로 사용하지 않도록 요구합니다. 다만, 이 요구사항을 지킬 의무는 없기 때문에 학습을 완전히 방지하는 것은 아닙니다." options: "옵션" specifyUser: "사용자 지정" +lookupConfirm: "조회 할까요?" +openTagPageConfirm: "해시태그의 페이지를 열까요?" +specifyHost: "호스트 지정" failedToPreviewUrl: "미리 볼 수 없음" update: "업데이트" rolesThatCanBeUsedThisEmojiAsReaction: "이 이모지를 리액션으로 사용할 수 있는 역할" @@ -1249,6 +1262,17 @@ alwaysConfirmFollow: "팔로우일 때 항상 확인하기" inquiry: "문의하기" tryAgain: "다시 시도해 주세요." confirmWhenRevealingSensitiveMedia: "민감한 미디어를 열 때 두 번 확인" +sensitiveMediaRevealConfirm: "민감한 미디어입니다. 표시할까요?" +createdLists: "만든 리스트" +createdAntennas: "만든 안테나" +fromX: "{x}부터" +genEmbedCode: "임베디드 코드 만들기" +noteOfThisUser: "이 유저의 노트 목록" +clipNoteLimitExceeded: "더 이상 이 클립에 노트를 추가 할 수 없습니다." +performance: "퍼포먼스" +modified: "변경 있음" +discard: "파기" +thereAreNChanges: "{n}건 변경이 있습니다." _delivery: status: "전송 상태" stop: "정지됨" @@ -1383,6 +1407,7 @@ _serverSettings: fanoutTimelineDescription: "활성화하면 각종 타임라인을 가져올 때의 성능을 대폭 향상하며, 데이터베이스의 부하를 줄일 수 있습니다. 단, Redis의 메모리 사용량이 증가합니다. 서버의 메모리 용량이 작거나, 서비스가 불안정해지는 경우 비활성화할 수 있습니다." fanoutTimelineDbFallback: "데이터베이스를 예비로 사용하기" fanoutTimelineDbFallbackDescription: "활성화하면 타임라인의 캐시되어 있지 않은 부분에 대해 DB에 질의하여 정보를 가져옵니다. 비활성화하면 이를 실행하지 않음으로써 서버의 부하를 줄일 수 있지만, 타임라인에서 가져올 수 있는 게시물 범위가 한정됩니다." + reactionsBufferingDescription: "활성화 한 경우, 리액션 작성 퍼포먼스가 대폭 향상되어 DB의 부하를 줄일 수 있으나, Redis의 메모리 사용량이 많아집니다." inquiryUrl: "문의처 URL" inquiryUrlDescription: "서버 운영자에게 보내는 문의 양식의 URL이나 운영자의 연락처 등이 적힌 웹 페이지의 URL을 설정합니다." _accountMigration: @@ -1716,10 +1741,15 @@ _role: canSearchNotes: "노트 검색 이용 가능 여부" canUseTranslator: "번역 기능의 사용" avatarDecorationLimit: "아바타 장식의 최대 붙임 개수" + canImportAntennas: "안테나 가져오기 허용" + canImportBlocking: "차단 목록 가져오기 허용" + canImportFollowing: "팔로우 가져오기 허용" + canImportMuting: "뮤트 목록 가져오기 허용" + canImportUserLists: "리스트 목록 가져오기 허용" _condition: roleAssignedTo: "수동 역할에 이미 할당됨" isLocal: "로컬 사용자" - isRemote: "리모트 사용자" + isRemote: "원격 사용자" isCat: "고양이 사용자" isBot: "봇 사용자" isSuspended: "정지된 사용자" @@ -1953,6 +1983,7 @@ _soundSettings: driveFileTypeWarnDescription: "오디오 파일을 선택하세요." driveFileDurationWarn: "오디오가 너무 깁니다" driveFileDurationWarnDescription: "긴 오디오로 설정할 경우 미스키 사용에 지장이 갈 수도 있습니다. 그래도 괜찮습니까?" + driveFileError: "오디오를 불러올 수 없습니다. 설정을 바꿔주세요." _ago: future: "미래" justNow: "방금 전" @@ -2301,6 +2332,7 @@ _pages: eyeCatchingImageSet: "아이캐치 이미지를 설정" eyeCatchingImageRemove: "아이캐치 이미지를 삭제" chooseBlock: "블록 추가" + enterSectionTitle: "섹션 타이틀을 입력하기" selectType: "종류 선택" contentBlocks: "콘텐츠" inputBlocks: "입력" @@ -2411,6 +2443,7 @@ _webhookSettings: modifyWebhook: "Webhook 수정" name: "이름" secret: "시크릿" + trigger: "트리거" active: "활성화" _events: follow: "누군가를 팔로우했을 때" @@ -2425,11 +2458,12 @@ _webhookSettings: abuseReportResolved: "받은 신고를 처리했을 때" userCreated: "유저가 생성되었을 때" deleteConfirm: "Webhook을 삭제할까요?" + testRemarks: "스위치 오른쪽에 있는 버튼을 클릭하여 더미 데이터를 사용한 테스트용 웹 훅을 보낼 수 있습니다." _abuseReport: _notificationRecipient: createRecipient: "신고 수신자 추가" modifyRecipient: "신고 수신자 편집" - recipientType: "알림 수신 유형" + recipientType: "알림 종류" _recipientType: mail: "이메일" webhook: "Webhook" @@ -2437,7 +2471,7 @@ _abuseReport: mail: "모더레이터 권한을 가진 사용자의 이메일 주소에 알림을 보냅니다 (신고를 받은 때에만)" webhook: "지정한 SystemWebhook에 알림을 보냅니다 (신고를 받은 때와 해결했을 때에 송신)" keywords: "키워드" - notifiedUser: "신고 알림을 보낼 유저" + notifiedUser: "알릴 사용자" notifiedWebhook: "사용할 Webhook" deleteConfirm: "수신자를 삭제하시겠습니까?" _moderationLogTypes: @@ -2483,6 +2517,10 @@ _moderationLogTypes: createAbuseReportNotificationRecipient: "신고 알림 수신자 생성" updateAbuseReportNotificationRecipient: "신고 알림 수신자 편집" deleteAbuseReportNotificationRecipient: "신고 알림 수신자 삭제" + deleteAccount: "계정을 삭제" + deletePage: "페이지를 삭제" + deleteFlash: "Play를 삭제" + deleteGalleryPost: "갤러리 포스트를 삭제" _fileViewer: title: "파일 상세" type: "파일 유형" @@ -2614,3 +2652,22 @@ _mediaControls: pip: "화면 속 화면" playbackRate: "재생 속도" loop: "반복 재생" +_contextMenu: + title: "컨텍스트 메뉴" + app: "애플리케이션" + appWithShift: "Shift 키로 애플리케이션" + native: "브라우저의 UI" +_embedCodeGen: + title: "임베디드 코드를 커스터마이즈" + header: "해더를 표시" + autoload: "자동으로 다음 코드를 실행 (비권장)" + maxHeight: "최대 높이" + maxHeightDescription: "최대 값을 무시하려면 0을 입력하세요. 위젯이 상하로 길어지는 것을 방지하려면, 임의의 값을 입력해 주세요." + maxHeightWarn: "높이 최대 값이 설정되어져 있지 않습니다(0). 의도적으로 설정 하지 않았다면 임의의 값을 설정해주세요." + previewIsNotActual: "미리보기로 표시할 수 있는 크기보다 큽니다. 실제로 넣은 코드의 표시가 다른 경우가 있습니다." + rounded: "외곽선을 둥글게 하기" + border: "외곽선에 테두리를 씌우기" + applyToPreview: "미리보기에 반영" + generateCode: "임베디드 코드를 만들기" + codeGenerated: "코드를 만들었습니다." + codeGeneratedDescription: "만들어진 코드를 웹 사이트에 붙여서 사용하세요." diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml index 87f934201c..41246c18ae 100644 --- a/locales/pt-PT.yml +++ b/locales/pt-PT.yml @@ -1,5 +1,5 @@ --- -_lang_: "Português" +_lang_: "日本語" headlineMisskey: "Uma rede ligada por notas" introMisskey: "Bem-vindo! O Misskey é um serviço de microblog descentralizado de código aberto.\nCrie \"notas\" para compartilhar o que está acontecendo agora ou para se expressar com todos à sua volta 📡\nVocê também pode adicionar rapidamente reações às notas de outras pessoas usando a função \"Reações\" 👍\nVamos explorar um novo mundo 🚀" poweredByMisskeyDescription: "{name} é uma instância da plataforma de código aberto Misskey." @@ -9,7 +9,7 @@ notifications: "Notificações" username: "Nome de usuário" password: "Senha" forgotPassword: "Esqueci-me da senha" -fetchingAsApObject: "Buscando no Fediverso" +fetchingAsApObject: "Buscando no Fediverso..." ok: "OK" gotIt: "Entendi" cancel: "Cancelar" @@ -25,7 +25,7 @@ basicSettings: "Configurações básicas" otherSettings: "Outras configurações" openInWindow: "Abrir em um janela" profile: "Perfil" -timeline: "Linha do tempo" +timeline: "Cronologia" noAccountDescription: "Este usuário não tem uma descrição." login: "Iniciar sessão" loggingIn: "Iniciando sessão…" @@ -82,7 +82,7 @@ exportRequested: "A sua solicitação de exportação foi enviada. Isso pode lev importRequested: "A sua solicitação de importação foi enviada. Isso pode levar algum tempo." lists: "Listas" noLists: "Não possui nenhuma lista" -note: "Post" +note: "Publicar" notes: "Posts" following: "Seguindo" followers: "Seguidores" @@ -272,7 +272,7 @@ more: "Mais!" featured: "Destaques" usernameOrUserId: "Nome de usuário ou ID do usuário" noSuchUser: "Usuário não encontrado" -lookup: "Buscando" +lookup: "Consultar" announcements: "Avisos" imageUrl: "URL da imagem" remove: "Remover" @@ -296,7 +296,7 @@ explore: "Explorar" messageRead: "Lida" noMoreHistory: "Não existe histórico anterior" startMessaging: "Iniciar conversação" -nUsersRead: "{n} Pessoas leem" +nUsersRead: "{n} pessoas leram" agreeTo: "Eu concordo com {0}" agree: "Concordar" agreeBelow: "Eu concordo com o seguinte" @@ -312,7 +312,7 @@ birthday: "Aniversário" yearsOld: "{age} anos" registeredDate: "Data de registro" location: "Localização" -theme: "tema" +theme: "Tema" themeForLightMode: "Temas usados ​​no modo de luz" themeForDarkMode: "Temas usados ​​no modo escuro" light: "Claro" @@ -700,7 +700,7 @@ fileIdOrUrl: "ID do arquivo ou URL" behavior: "Comportamento" sample: "Exemplo" abuseReports: "Denúncias" -reportAbuse: "Denúncias" +reportAbuse: "Denunciar" reportAbuseRenote: "Reportar repostagem" reportAbuseOf: "Denunciar {name}" fillAbuseReportDescription: "Por favor, forneça detalhes sobre o motivo da denúncia. Se houver uma nota específica envolvida, inclua também a URL dela." @@ -843,7 +843,7 @@ switchAccount: "Trocar conta" enabled: "Ativado" disabled: "Desativado" quickAction: "Ações rápidas" -user: "Usuários" +user: "Usuário" administration: "Administrar" accounts: "Contas" switch: "Trocar" @@ -1263,6 +1263,7 @@ confirmWhenRevealingSensitiveMedia: "Confirmar ao revelar mídia sensível" sensitiveMediaRevealConfirm: "Essa mídia pode ser sensível. Deseja revelá-la?" createdLists: "Listas criadas" createdAntennas: "Antenas criadas" +clipNoteLimitExceeded: "Não é possível adicionar mais notas ao clipe." _delivery: status: "Estado de entrega" stop: "Suspenso" @@ -2316,6 +2317,7 @@ _pages: eyeCatchingImageSet: "Escolher miniatura" eyeCatchingImageRemove: "Excluir miniatura" chooseBlock: "Adicionar bloco" + enterSectionTitle: "Insira um título à seção" selectType: "Selecionar um tipo" contentBlocks: "Conteúdo" inputBlocks: "Inserir" @@ -2368,7 +2370,7 @@ _notification: mention: "Menção" reply: "Respostas" renote: "Repostar" - quote: "Citar" + quote: "Citações" reaction: "Reações" pollEnded: "Enquetes terminando" receiveFollowRequest: "Recebeu pedidos de seguidor" @@ -2499,6 +2501,10 @@ _moderationLogTypes: createAbuseReportNotificationRecipient: "Criar um destinatário para relatórios de abuso" updateAbuseReportNotificationRecipient: "Atualizar destinatários para relatórios de abuso" deleteAbuseReportNotificationRecipient: "Remover um destinatário para relatórios de abuso" + deleteAccount: "Remover conta" + deletePage: "Remover página" + deleteFlash: "Remover Play" + deleteGalleryPost: "Remover a publicação da galeria" _fileViewer: title: "Detalhes do arquivo" type: "Tipo de arquivo" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index 88f59155d6..9610600ed0 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -2,7 +2,7 @@ _lang_: "Русский" headlineMisskey: "Сеть, сплетённая из заметок" introMisskey: "Добро пожаловать! Misskey — это децентрализованный сервис микроблогов с открытым исходным кодом.\nПишите «заметки» — делитесь со всеми происходящим вокруг или рассказывайте о себе 📡\nСтавьте «реакции» — выражайте свои чувства и эмоции от заметок других 👍\nОткройте для себя новый мир 🚀" -poweredByMisskeyDescription: "{name} – сервис на платформе с открытым исходным кодом Misskey, называемый инстансом Misskey." +poweredByMisskeyDescription: "{name} – сервис на платформе с открытым исходным кодом Misskey, называемый экземпляром Misskey." monthAndDay: "{day}.{month}" search: "Поиск" notifications: "Уведомления" @@ -10,15 +10,15 @@ username: "Имя пользователя" password: "Пароль" forgotPassword: "Забыли пароль?" fetchingAsApObject: "Приём с других сайтов" -ok: "Окей" +ok: "Подтвердить" gotIt: "Ясно!" cancel: "Отмена" noThankYou: "Нет, спасибо" enterUsername: "Введите имя пользователя" -renotedBy: "{user} делится" +renotedBy: "{user} репостнул(а)" noNotes: "Нет ни одной заметки" noNotifications: "Нет уведомлений" -instance: "Инстанс" +instance: "Экземпляр" settings: "Настройки" notificationSettings: "Настройки уведомлений" basicSettings: "Основные настройки" @@ -45,22 +45,24 @@ pin: "Закрепить в профиле" unpin: "Открепить от профиля" copyContent: "Скопировать содержимое" copyLink: "Скопировать ссылку" +copyLinkRenote: "Скопировать ссылку на репост" delete: "Удалить" deleteAndEdit: "Удалить и отредактировать" -deleteAndEditConfirm: "Удалить эту заметку и создать отредактированную? Все реакции, ссылки и ответы на существующую будут будут потеряны." +deleteAndEditConfirm: "Удалить этот пост и отредактировать заново? Все реакции, репосты и ответы на него также будут удалены." addToList: "Добавить в список" addToAntenna: "Добавить к антенне" sendMessage: "Отправить сообщение" copyRSS: "Скопировать RSS" copyUsername: "Скопировать имя пользователя" -copyUserId: "Скопировать идентификатор пользователя" -copyNoteId: "Скопировать идентификатор заметки" +copyUserId: "Скопировать ID пользователя" +copyNoteId: "Скопировать ID поста" copyFileId: "Скопировать ID файла" copyFolderId: "Скопировать ID папки" -copyProfileUrl: "Скопировать URL профиля " +copyProfileUrl: "Скопировать ссылку на профиль" searchUser: "Поиск людей" +searchThisUsersNotes: "Искать по заметкам пользователя" reply: "Ответ" -loadMore: "Показать еще" +loadMore: "Загрузить ещё" showMore: "Показать ещё" showLess: "Закрыть" youGotNewFollower: "Новый подписчик" @@ -107,11 +109,14 @@ enterEmoji: "Введите эмодзи" renote: "Репост" unrenote: "Отмена репоста" renoted: "Репост совершён." +renotedToX: "Репостнуть в {name}." cantRenote: "Это нельзя репостить." cantReRenote: "Невозможно репостить репост." quote: "Цитата" inChannelRenote: "В канале" inChannelQuote: "Заметки в канале" +renoteToChannel: "Репостнуть в канал" +renoteToOtherChannel: "Репостнуть в другой канал" pinnedNote: "Закреплённая заметка" pinned: "Закрепить в профиле" you: "Вы" @@ -150,6 +155,7 @@ editList: "Редактировать список" selectChannel: "Выберите канал" selectAntenna: "Выберите антенну" editAntenna: "Редактировать антенну" +createAntenna: "Создать антенну" selectWidget: "Выберите виджет" editWidgets: "Редактировать виджеты" editWidgetsExit: "Готово" @@ -157,11 +163,12 @@ customEmojis: "Собственные эмодзи" emoji: "Эмодзи" emojis: "Эмодзи" emojiName: "Название эмодзи" -emojiUrl: "URL эмодзи" +emojiUrl: "Ссылка на эмодзи" addEmoji: "Добавить эмодзи" settingGuide: "Рекомендуемые настройки" cacheRemoteFiles: "Кешировать внешние файлы" cacheRemoteFilesDescription: "Когда эта настройка отключена, файлы с других сайтов будут загружаться прямо оттуда. Это сэкономит место на сервере, но увеличит трафик, так как не будут создаваться эскизы." +youCanCleanRemoteFilesCache: "Вы можете очистить кэш, нажав на кнопку 🗑️ в меню управления файлами." cacheRemoteSensitiveFiles: "Кэшировать внешние файлы «не для всех»" cacheRemoteSensitiveFilesDescription: "Если отключено, файлы «не для всех» загружаются непосредственно с удалённых серверов, не кэшируясь." flagAsBot: "Аккаунт бота" @@ -175,6 +182,10 @@ addAccount: "Добавить учётную запись" reloadAccountsList: "Обновить список учётных записей" loginFailed: "Неудачная попытка входа" showOnRemote: "Перейти к оригиналу на сайт" +continueOnRemote: "Продолжить на удалённом сервере" +chooseServerOnMisskeyHub: "Выбрать сервер с Misskey Hub" +specifyServerHost: "Укажите сервер напрямую" +inputHostName: "Введите домен" general: "Общее" wallpaper: "Обои" setWallpaper: "Установить обои" @@ -185,6 +196,7 @@ followConfirm: "Подписаться на {name}?" proxyAccount: "Учётная запись прокси" proxyAccountDescription: "Учетная запись прокси предназначена служить подписчиком на пользователей с других сайтов. Например, если пользователь добавит кого-то с другого сайта а список, деятельность того не отобразится, пока никто с этого же сайта не подписан на него. Чтобы это стало возможным, на него подписывается прокси." host: "Хост" +selectSelf: "Выбрать себя" selectUser: "Выберите пользователя" recipient: "Кому" annotation: "Описание" @@ -199,6 +211,7 @@ perHour: "По часам" perDay: "По дням" stopActivityDelivery: "Остановить отправку обновлений активности" blockThisInstance: "Блокировать этот инстанс" +silenceThisInstance: "Заглушить этот инстанс" operations: "Операции" software: "Программы" version: "Версия" @@ -218,6 +231,7 @@ clearCachedFiles: "Очистить кэш" clearCachedFilesConfirm: "Удалить все закэшированные файлы с других сайтов?" blockedInstances: "Заблокированные инстансы" blockedInstancesDescription: "Введите список инстансов, которые хотите заблокировать. Они больше не смогут обмениваться с вашим инстансом." +silencedInstances: "Заглушённые инстансы" muteAndBlock: "Скрытие и блокировка" mutedUsers: "Скрытые пользователи" blockedUsers: "Заблокированные пользователи" @@ -236,7 +250,7 @@ noJobs: "Нет заданий" federating: "Федерируется" blocked: "Заблокировано" suspended: "Заморожено" -all: "Всё" +all: "Все" subscribing: "Подписка" publishing: "Публикация" notResponding: "Нет ответа" @@ -268,7 +282,7 @@ messaging: "Сообщения" upload: "Загрузить" keepOriginalUploading: "Сохранить исходное изображение" keepOriginalUploadingDescription: "Сохраняет исходную версию при загрузке изображений. Если выключить, то при загрузке браузер генерирует изображение для публикации." -fromDrive: "С «диска»" +fromDrive: "С Диска" fromUrl: "По ссылке" uploadFromUrl: "Загрузить по ссылке" uploadFromUrlDescription: "Ссылка на файл, который хотите загрузить" @@ -308,6 +322,7 @@ selectFile: "Выберите файл" selectFiles: "Выберите файлы" selectFolder: "Выберите папку" selectFolders: "Выберите папки" +fileNotSelected: "Файл не выбран" renameFile: "Переименовать файл" folderName: "Имя папки" createFolder: "Создать папку" @@ -359,8 +374,8 @@ disablingTimelinesInfo: "У администраторов и модератор registration: "Регистрация" enableRegistration: "Разрешить регистрацию" invite: "Пригласить" -driveCapacityPerLocalAccount: "Объём диска на одного локального пользователя" -driveCapacityPerRemoteAccount: "Объём диска на одного пользователя с другого сайта" +driveCapacityPerLocalAccount: "Объём Диска на одного локального пользователя" +driveCapacityPerRemoteAccount: "Объём Диска на одного пользователя с другого экземпляра" inMb: "В мегабайтах" bannerUrl: "Ссылка на изображение в шапке" backgroundImageUrl: "Ссылка на фоновое изображение" @@ -379,6 +394,7 @@ mcaptcha: "mCaptcha" enableMcaptcha: "Включить mCaptcha" mcaptchaSiteKey: "Ключ сайта" mcaptchaSecretKey: "Секретный ключ" +mcaptchaInstanceUrl: "Ссылка на сервер mCaptcha" recaptcha: "reCAPTCHA" enableRecaptcha: "Включить reCAPTCHA" recaptchaSiteKey: "Ключ сайта" @@ -393,7 +409,8 @@ manageAntennas: "Настройки антенн" name: "Название" antennaSource: "Источник антенны" antennaKeywords: "Ключевые слова" -antennaExcludeKeywords: "Исключения" +antennaExcludeKeywords: "Чёрный список слов" +antennaExcludeBots: "Исключать ботов" antennaKeywordsDescription: "Пишите слова через пробел в одной строке, чтобы ловить их появление вместе; на отдельных строках располагайте слова, или группы слов, чтобы ловить любые из них." notifyAntenna: "Уведомлять о новых заметках" withFileAntenna: "Только заметки с вложениями" @@ -426,6 +443,7 @@ totp: "Приложение-аутентификатор" totpDescription: "Описание приложения-аутентификатора" moderator: "Модератор" moderation: "Модерация" +moderationLogs: "Журнал модерации" nUsersMentioned: "Упомянуло пользователей: {n}" securityKeyAndPasskey: "Ключ безопасности и парольная фраза" securityKey: "Ключ безопасности" @@ -458,10 +476,12 @@ retype: "Введите ещё раз" noteOf: "Что пишет {user}" quoteAttached: "Цитата" quoteQuestion: "Хотите добавить цитату?" +attachAsFileQuestion: "Текста в буфере обмена слишком много. Прикрепить как текстовый файл?" noMessagesYet: "Пока ни одного сообщения" newMessageExists: "Новое сообщение" onlyOneFileCanBeAttached: "К сообщению можно прикрепить только один файл" signinRequired: "Пожалуйста, войдите" +signinOrContinueOnRemote: "Чтобы продолжить, вам необходимо войти в аккаунт на своём сервере или зарегистрироваться / войти в аккаунт на этом." invitations: "Приглашения" invitationCode: "Код приглашения" checking: "Проверка" @@ -471,7 +491,7 @@ usernameInvalidFormat: "Можно использовать только лат tooShort: "Слишком короткий" tooLong: "Слишком длинный" weakPassword: "Слабый пароль" -normalPassword: "Годный пароль" +normalPassword: "Хороший пароль" strongPassword: "Надёжный пароль" passwordMatched: "Совпали" passwordNotMatched: "Не совпадают" @@ -485,6 +505,7 @@ emojiStyle: "Стиль эмодзи" native: "Системные" disableDrawer: "Не использовать выдвижные меню" showNoteActionsOnlyHover: "Показывать кнопки у заметок только при наведении" +showReactionsCount: "Видеть количество реакций на заметках" noHistory: "История пока пуста" signinHistory: "Журнал посещений" enableAdvancedMfm: "Включить расширенный MFM" @@ -547,7 +568,7 @@ popout: "Развернуть" volume: "Громкость" masterVolume: "Основная регулировка громкости" notUseSound: "Выключить звук" -useSoundOnlyWhenActive: "Использовать звук, когда Misskey активен." +useSoundOnlyWhenActive: "Воспроизводить звук только когда Misskey активен." details: "Подробнее" chooseEmoji: "Выберите эмодзи" unableToProcess: "Не удаётся завершить операцию" @@ -601,7 +622,7 @@ poll: "Опрос" useCw: "Скрывать содержимое под предупреждением" enablePlayer: "Включить проигрыватель" disablePlayer: "Выключить проигрыватель" -expandTweet: "Развернуть твит" +expandTweet: "Развернуть заметку" themeEditor: "Редактор темы оформления" description: "Описание" describeFile: "Добавить подпись" @@ -613,7 +634,7 @@ plugins: "Расширения" preferencesBackups: "Резервная копия" deck: "Пульт" undeck: "Покинуть пульт" -useBlurEffectForModal: "Размывка под формой поверх всего" +useBlurEffectForModal: "Размытие за формой ввода заметки" useFullReactionPicker: "Полнофункциональный выбор реакций" width: "Ширина" height: "Высота" @@ -644,7 +665,7 @@ smtpSecure: "Использовать SSL/TLS для SMTP-соединений" smtpSecureInfo: "Выключите при использовании STARTTLS." testEmail: "Проверка доставки электронной почты" wordMute: "Скрытие слов" -hardWordMute: "" +hardWordMute: "Строгое скрытие слов" regexpError: "Ошибка в регулярном выражении" regexpErrorDescription: "В списке {tab} скрытых слов, в строке {line} обнаружена синтаксическая ошибка:" instanceMute: "Глушение инстансов" @@ -726,6 +747,7 @@ lockedAccountInfo: "Даже если вы вручную подтверждае alwaysMarkSensitive: "Отмечать файлы как «содержимое не для всех» по умолчанию" loadRawImages: "Сразу показывать изображения в полном размере" disableShowingAnimatedImages: "Не проигрывать анимацию" +highlightSensitiveMedia: "Выделять содержимое не для всех" verificationEmailSent: "Вам отправлено письмо для подтверждения. Пройдите, пожалуйста, по ссылке из письма, чтобы завершить проверку." notSet: "Не настроено" emailVerified: "Адрес электронной почты подтверждён." @@ -743,7 +765,7 @@ makeExplorable: "Опубликовать профиль в «Обзоре»." makeExplorableDescription: "Если выключить, ваш профиль не будет показан в разделе «Обзор»." showGapBetweenNotesInTimeline: "Показывать разделитель между заметками в ленте" duplicate: "Дубликат" -left: "Влево" +left: "Слева" center: "По центру" wide: "Толстый" narrow: "Тонкий" @@ -822,7 +844,7 @@ noMaintainerInformationWarning: "Не заполнены сведения об noBotProtectionWarning: "Ботозащита не настроена" configure: "Настроить" postToGallery: "Опубликовать в галерею" -postToHashtag: "Написать заметку с этим хэштегом" +postToHashtag: "Написать заметку с этим хештегом" gallery: "Галерея" recentPosts: "Недавние публикации" popularPosts: "Популярные публикации" @@ -839,13 +861,13 @@ emailNotConfiguredWarning: "Не указан адрес электронной ratio: "Соотношение" previewNoteText: "Предварительный просмотр" customCss: "Индивидуальный CSS" -customCssWarn: "Используйте эту настройку только если знаете, что делаете. Ошибки здесь чреваты тем, что сайт перестанет нормально работать у вас." +customCssWarn: "Используйте эту настройку только если знаете, что делаете. Ошибки здесь чреваты тем, что у вас перестанет нормально работать сайт." global: "Всеобщая" squareAvatars: "Квадратные аватарки" sent: "Отправить" received: "Получено" searchResult: "Результаты поиска" -hashtags: "Хэштег" +hashtags: "Хештеги" troubleshooting: "Разрешение проблем" useBlurEffect: "Размытие в интерфейсе" learnMore: "Подробнее" @@ -857,7 +879,7 @@ accountDeletionInProgress: "В настоящее время выполняет usernameInfo: "Имя, которое отличает вашу учетную запись от других на этом сервере. Вы можете использовать алфавит (a~z, A~Z), цифры (0~9) или символы подчеркивания (_). Имена пользователей не могут быть изменены позже." aiChanMode: "Режим Ай" devMode: "Режим разработчика" -keepCw: "Сохраняйте Предупреждения о содержимом" +keepCw: "Сохраняйте предупреждения о содержимом" pubSub: "Учётные записи Pub/Sub" lastCommunication: "Последнее сообщение" resolved: "Решено" @@ -878,6 +900,8 @@ makeReactionsPublicDescription: "Список сделанных вами реа classic: "Классика" muteThread: "Скрыть цепочку" unmuteThread: "Отменить сокрытие цепочки" +followingVisibility: "Видимость подписок" +followersVisibility: "Видимость подписчиков" continueThread: "Показать следующие ответы" deleteAccountConfirm: "Учётная запись будет безвозвратно удалена. Подтверждаете?" incorrectPassword: "Пароль неверен." @@ -987,6 +1011,7 @@ assign: "Назначить" unassign: "Отменить назначение" color: "Цвет" manageCustomEmojis: "Управлять пользовательскими эмодзи" +manageAvatarDecorations: "Управление украшениями аватара" youCannotCreateAnymore: "Вы достигли лимита создания." cannotPerformTemporary: "Временно недоступен" cannotPerformTemporaryDescription: "Это действие временно невозможно выполнить из-за превышения лимита выполнения." @@ -1003,7 +1028,8 @@ thisPostMayBeAnnoying: "Это сообщение может быть непри thisPostMayBeAnnoyingHome: "Этот пост может быть отправлен на главную" thisPostMayBeAnnoyingCancel: "Этот пост не может быть отменен." thisPostMayBeAnnoyingIgnore: "Этот пост может быть проигнорирован " -collapseRenotes: "Свернуть репосты" +collapseRenotes: "Сворачивать увиденные репосты" +collapseRenotesDescription: "Сворачивать посты с которыми вы взаимодействовали." internalServerError: "Внутренняя ошибка сервера" internalServerErrorDescription: "Внутри сервера произошла непредвиденная ошибка." copyErrorInfo: "Скопировать код ошибки" @@ -1027,7 +1053,10 @@ resetPasswordConfirm: "Сбросить пароль?" sensitiveWords: "Чувствительные слова" sensitiveWordsDescription: "Установите общедоступный диапазон заметки, содержащей заданное слово, на домашний. Можно сделать несколько настроек, разделив их переносами строк." sensitiveWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение." +prohibitedWords: "Запрещённые слова" +prohibitedWordsDescription: "Включает вывод ошибки при попытке опубликовать пост, содержащий указанное слово/набор слов.\nМножество слов может быть указано, разделяемые новой строкой." prohibitedWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение." +hiddenTags: "Скрытые хештеги" notesSearchNotAvailable: "Поиск заметок недоступен" license: "Лицензия" unfavoriteConfirm: "Удалить избранное?" @@ -1038,9 +1067,14 @@ retryAllQueuesConfirmTitle: "Хотите попробовать ещё раз?" retryAllQueuesConfirmText: "Нагрузка на сервер может увеличиться" enableChartsForRemoteUser: "Создание диаграмм для удалённых пользователей" enableChartsForFederatedInstances: "Создание диаграмм для удалённых серверов" +showClipButtonInNoteFooter: "Показать кнопку добавления в подборку в меню действий с заметкой" +reactionsDisplaySize: "Размер реакций" +limitWidthOfReaction: "Ограничить максимальную ширину реакций и отображать их в уменьшенном размере." noteIdOrUrl: "ID или ссылка на заметку" video: "Видео" videos: "Видео" +audio: "Звук" +audioFiles: "Звуковые файлы" dataSaver: "Экономия трафика" accountMigration: "Перенос учётной записи" accountMoved: "Учётная запись перенесена" @@ -1052,12 +1086,13 @@ editMemo: "Изменить памятку" reactionsList: "Список реакций" renotesList: "Репосты" notificationDisplay: "Отображение уведомлений" -leftTop: "Влево вверх" -rightTop: "Вправо вверх" -leftBottom: "Влево вниз" -rightBottom: "Вправо вниз" -vertical: "Вертикальная" -horizontal: "Сбоку" +leftTop: "Слева вверху" +rightTop: "Справа сверху" +leftBottom: "Слева внизу" +rightBottom: "Справа внизу" +stackAxis: "Положение уведомлений" +vertical: "Вертикально" +horizontal: "Горизонтально" position: "Позиция" serverRules: "Правила сервера" pleaseConfirmBelowBeforeSignup: "Для регистрации на данном сервере, необходимо согласится с нижеследующими положениями." @@ -1069,57 +1104,114 @@ createNoteFromTheFile: "Создать заметку из этого файла archive: "Архив" channelArchiveConfirmTitle: "Переместить {name} в архив?" channelArchiveConfirmDescription: "Архивированные каналы перестанут отображаться в списке каналов или результатах поиска. В них также нельзя будет добавлять новые записи." +thisChannelArchived: "Этот канал находится в архиве." displayOfNote: "Отображение заметок" initialAccountSetting: "Настройка профиля" youFollowing: "Подписки" preventAiLearning: "Отказаться от использования в машинном обучении (Генеративный ИИ)" +preventAiLearningDescription: "Запросить краулеров не использовать опубликованный текст или изображения и т.д. для машинного обучения (Прогнозирующий / Генеративный ИИ) датасетов. Это достигается путём добавления \"noai\" HTTP-заголовка в ответ на соответствующий контент. Полного предотвращения через этот заголовок не избежать, так как он может быть просто проигнорирован." options: "Настройки ролей" specifyUser: "Указанный пользователь" +openTagPageConfirm: "Открыть страницу этого хештега?" +specifyHost: "Указать сайт" failedToPreviewUrl: "Предварительный просмотр недоступен" update: "Обновить" rolesThatCanBeUsedThisEmojiAsReaction: "Роли тех, кому можно использовать эти эмодзи как реакцию" rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "Если здесь ничего не указать, в качестве реакции эту эмодзи сможет использовать каждый." +rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Эти роли должны быть общедоступными." +cancelReactionConfirm: "Вы действительно хотите удалить свою реакцию?" later: "Позже" goToMisskey: "К Misskey" additionalEmojiDictionary: "Дополнительные словари эмодзи" installed: "Установлено" branding: "Бренд" +enableServerMachineStats: "Опубликовать характеристики сервера" enableIdenticonGeneration: "Включить генерацию иконки пользователя" turnOffToImprovePerformance: "Отключение этого параметра может повысить производительность." +createInviteCode: "Создать код приглашения" +createCount: "Количество приглашений" expirationDate: "Дата истечения" -unused: "Неиспользуемый" +noExpirationDate: "Бессрочно" +unused: "Неиспользованное" +used: "Использован" expired: "Срок действия приглашения истёк" doYouAgree: "Согласны?" icon: "Аватар" replies: "Ответы" renotes: "Репост" loadReplies: "Показать ответы" +pinnedList: "Закреплённый список" +keepScreenOn: "Держать экран включённым" +showRenotes: "Показывать репосты" +mutualFollow: "Взаимные подписки" +followingOrFollower: "Подписки или подписчики" +fileAttachedOnly: "Только заметки с файлами" +showRepliesToOthersInTimeline: "Показывать ответы в ленте" +showRepliesToOthersInTimelineAll: "Показывать в ленте ответы пользователей, на которых вы подписаны" +hideRepliesToOthersInTimelineAll: "Скрывать в ленте ответы пользователей, на которых вы подписаны" sourceCode: "Исходный код" +sourceCodeIsNotYetProvided: "Исходный код пока не доступен. Свяжитесь с администратором, чтобы исправить эту проблему." +repositoryUrl: "Ссылка на репозиторий" +repositoryUrlDescription: "Если вы используете Misskey как есть (без изменений в исходном коде), введите https://github.com/misskey-dev/misskey" +privacyPolicy: "Политика Конфиденциальности" +privacyPolicyUrl: "Ссылка на Политику Конфиденциальности" +attach: "Прикрепить" +angle: "Угол" flip: "Переворот" +disableStreamingTimeline: "Отключить обновление ленты в режиме реального времени" +useGroupedNotifications: "Отображать уведомления сгруппировано" +doReaction: "Добавить реакцию" code: "Код" +remainingN: "Остаётся: {n}" +seasonalScreenEffect: "Эффект времени года на экране" +decorate: "Украсить" +addMfmFunction: "Добавить MFM" lastNDays: "Последние {n} сут" +hemisphere: "Место проживания" +enableHorizontalSwipe: "Смахните в сторону, чтобы сменить вкладки" surrender: "Этот пост не может быть отменен." +useNativeUIForVideoAudioPlayer: "Использовать интерфейс браузера при проигрывании видео и звука" +keepOriginalFilename: "Сохранять исходное имя файла" +keepOriginalFilenameDescription: "Если вы выключите данную настройку, имена файлов будут автоматически заменены случайной строкой при загрузке." +alwaysConfirmFollow: "Всегда подтверждать подписку" +inquiry: "Связаться" _delivery: stop: "Заморожено" _type: none: "Публикация" +_announcement: + tooManyActiveAnnouncementDescription: "Большое количество оповещений может ухудшить пользовательский опыт. Рассмотрите архивирование неактуальных оповещений. " _initialAccountSetting: accountCreated: "Аккаунт успешно создан!" letsStartAccountSetup: "Давайте настроим вашу учётную запись." profileSetting: "Настройки профиля" privacySetting: "Настройки конфиденциальности" initialAccountSettingCompleted: "Первоначальная настройка успешно завершена!" + startTutorial: "Пройти Обучение" skipAreYouSure: "Пропустить настройку?" _initialTutorial: + launchTutorial: "Пройти обучение" _note: description: "Посты в Misskey называются 'Заметками.' Заметки отсортированы в хронологическом порядке в ленте и обновляются в режиме реального времени." + _reaction: + reactToContinue: "Добавьте реакцию, чтобы продолжить." + _postNote: + _visibility: + public: "Твоя заметка будет видна всем." + doNotSendConfidencialOnDirect2: "Администратор целевого сервера может видеть что вы отправляете. Будьте осторожны с конфиденциальной информацией, когда отправляете личные заметки пользователям с ненадёжных серверов." _timelineDescription: home: "В персональной ленте располагаются заметки тех, на которых вы подписаны." - local: "Местная лента показывает заметки всех пользователей этого сайта." + local: "Местная лента показывает заметки всех пользователей этого экземпляра." social: "В социальной ленте собирается всё, что есть в персональной и местной лентах." - global: "В глобальную ленту попадает вообще всё со связанных инстансов." + global: "В глобальную ленту попадает вообще всё со связанных экземпляров." _serverSettings: iconUrl: "Адрес на иконку роли" +_accountMigration: + moveFrom: "Перенести другую учётную запись сюда" + moveTo: "Перенести учётную запись на другой сервер" + moveAccountDescription: "Это действие перенесёт ваш аккаунт на другой сервер.\n ・Подписчики с этого аккаунта автоматически подпишутся на новый\n ・Этот аккаунт отпишется от всех пользователей, на которых подписан сейчас\n ・Вы не сможете создавать новые заметки и т.д. на этом аккаунте\n\nТогда как перенос подписчиков происходит автоматически, вы должны будете подготовиться, сделав некоторые шаги, чтобы перенести список пользователей, на которых вы подписаны. Чтобы сделать это, экспортируйте список подписчиков в файл, который затем импортируете на новом аккаунте в меню настроек. То же самое необходимо будет сделать со списками, также как и со скрытыми и заблокированными пользователями.\n\n(Это объяснение применяется к Misskey v13.12.0 и выше. Другое ActivityPub программное обеспечение, такое, как Mastodon, может работать по-другому." + startMigration: "Перенести" + movedAndCannotBeUndone: "Аккаунт был перемещён. Это действие необратимо." _achievements: earnedAt: "Разблокировано в" _types: @@ -1395,6 +1487,7 @@ _role: canPublicNote: "Может публиковать общедоступные заметки" canInvite: "Может создавать пригласительные коды" canManageCustomEmojis: "Управлять пользовательскими эмодзи" + canManageAvatarDecorations: "Управление украшениями аватара" driveCapacity: "Доступное пространство на «диске»" alwaysMarkNsfw: "Всегда отмечать файлы как «не для всех»" pinMax: "Доступное количество закреплённых заметок" @@ -1505,6 +1598,11 @@ _aboutMisskey: donate: "Пожертвование на Misskey" morePatrons: "Большое спасибо и многим другим, кто принял участие в этом проекте! 🥰" patrons: "Материальная поддержка" + projectMembers: "Участники проекта" +_displayOfSensitiveMedia: + respect: "Скрывать содержимое не для всех" + ignore: "Показывать содержимое не для всех" + force: "Скрывать всё содержимое" _instanceTicker: none: "Не показывать" remote: "Только для других сайтов" @@ -1533,7 +1631,7 @@ _wordMute: muteWordsDescription: "Пишите слова через пробел в одной строке, чтобы фильтровать их появление вместе; а если хотите фильтровать любое из них, пишите в отдельных строках." muteWordsDescription2: "Здесь можно использовать регулярные выражения — просто заключите их между двумя дробными чертами (/)." _instanceMute: - instanceMuteDescription: "Заметки и репосты с указанных здесь инстансов, а также ответы пользователям оттуда же не будут отображаться." + instanceMuteDescription: "Любые активности, затрагивающие инстансы из данного списка, будут скрыты." instanceMuteDescription2: "Пишите каждый инстанс на отдельной строке" title: "Скрывает заметки с заданных инстансов." heading: "Список скрытых инстансов" @@ -1582,7 +1680,7 @@ _theme: navActive: "Текст на боковой панели (активирован)" navIndicator: "Индикатор на боковой панели" link: "Ссылка" - hashtag: "Хэштег" + hashtag: "Хештег" mention: "Упоминание" mentionMe: "Упоминания вас" renote: "Репост" @@ -1612,6 +1710,10 @@ _sfx: note: "Заметки" noteMy: "Собственные заметки" notification: "Уведомления" + reaction: "При выборе реакции" +_soundSettings: + driveFile: "Использовать аудиофайл с Диска." + driveFileWarn: "Выбрать аудиофайл с Диска." _ago: future: "Из будущего" justNow: "Только что" @@ -1690,6 +1792,7 @@ _permissions: "write:gallery": "Редактирование галереи" "read:gallery-likes": "Просмотр списка понравившегося в галерее" "write:gallery-likes": "Изменение списка понравившегося в галерее" + "write:admin:reset-password": "Сбросить пароль пользователю" _auth: shareAccessTitle: "Разрешения для приложений" shareAccess: "Дать доступ для «{name}» к вашей учётной записи?" @@ -1743,6 +1846,7 @@ _widgets: _userList: chooseList: "Выберите список" clicker: "Счётчик щелчков" + birthdayFollowings: "Пользователи, у которых сегодня день рождения" _cw: hide: "Спрятать" show: "Показать" @@ -1796,7 +1900,7 @@ _profile: name: "Имя" username: "Имя пользователя" description: "О себе" - youCanIncludeHashtags: "Можете использовать здесь хэштеги" + youCanIncludeHashtags: "Можете использовать здесь хештеги." metadata: "Дополнительные сведения" metadataEdit: "Редактировать дополнительные сведения" metadataDescription: "Можно добавить до четырёх дополнительных граф в профиль." @@ -1804,6 +1908,8 @@ _profile: metadataContent: "Содержимое" changeAvatar: "Поменять аватар" changeBanner: "Поменять изображение в шапке" + verifiedLinkDescription: "Указывая здесь URL, содержащий ссылку на профиль, иконка владения ресурсом может быть отображена рядом с полем" + avatarDecorationMax: "Вы можете добавить до {max} украшений." _exportOrImport: allNotes: "Все заметки\n" favoritedNotes: "Избранное" @@ -1926,6 +2032,9 @@ _notification: unreadAntennaNote: "Антенна {name}" emptyPushNotificationMessage: "Обновлены push-уведомления" achievementEarned: "Получено достижение" + checkNotificationBehavior: "Проверить внешний вид уведомления" + sendTestNotification: "Отправить тестовое уведомление" + flushNotification: "Очистить уведомления" _types: all: "Все" follow: "Подписки" @@ -1977,19 +2086,57 @@ _dialog: _disabledTimeline: title: "Лента отключена" description: "Ваша текущая роль не позволяет пользоваться этой лентой." +_drivecleaner: + orderBySizeDesc: "Размеры файлов по убыванию" + orderByCreatedAtAsc: "По увеличению даты" _webhookSettings: createWebhook: "Создать вебхук" + modifyWebhook: "Изменить Вебхук" name: "Название" + secret: "Секрет" + trigger: "Условие срабатывания" active: "Вкл." + _events: + follow: "Когда подписались на пользователя" + followed: "Когда на вас подписались" + note: "Когда создали заметку" + reply: "Когда получили ответ на заметку" + renote: "Когда вас репостнули" + reaction: "Когда получили реакцию" + mention: "Когда вас упоминают" + _systemEvents: + abuseReport: "Когда приходит жалоба" + abuseReportResolved: "Когда разрешается жалоба" + userCreated: "Когда создан пользователь" + deleteConfirm: "Вы уверены, что хотите удалить этот Вебхук?" _abuseReport: _notificationRecipient: _recipientType: mail: "Электронная почта" + webhook: "Вебхук" + _captions: + webhook: "Отправить уведомление Системному Вебхуку при получении или разрешении жалоб." + notifiedWebhook: "Используемый Вебхук" _moderationLogTypes: suspend: "Заморозить" addCustomEmoji: "Добавлено эмодзи" updateCustomEmoji: "Изменено эмодзи" deleteCustomEmoji: "Удалено эмодзи" + deleteDriveFile: "Файл удалён" resetPassword: "Сброс пароля:" + createInvitation: "Создать код приглашения" + createSystemWebhook: "Создать Системный Вебхук" + updateSystemWebhook: "Обновить Системый Вебхук" + deleteSystemWebhook: "Удалить Системный Вебхук" +_fileViewer: + url: "Ссылка" + attachedNotes: "Закреплённые заметки" +_dataSaver: + _code: + title: "Подсветка кода" +_hemisphere: + N: "Северное полушарие" + S: "Южное полушарие" + caption: "Используется для некоторых настроек клиента для определения сезона." _reversi: total: "Всего" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index d422c3afc5..ff00a1bb8f 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -592,6 +592,8 @@ ascendingOrder: "升序" descendingOrder: "降序" scratchpad: "AiScript 控制台" scratchpadDescription: "AiScript 控制台为 AiScript 提供了实验环境。您可以编写代码与 Misskey 交互,运行并查看结果。" +uiInspector: "UI 检查器" +uiInspectorDescription: "查看所有内存中由 UI 组件生成出的实例。UI 组件由 UI:C 系列函数所生成。" output: "输出" script: "脚本" disablePagesScript: "禁用页面脚本" @@ -1263,6 +1265,11 @@ confirmWhenRevealingSensitiveMedia: "显示敏感内容前需要确认" sensitiveMediaRevealConfirm: "这是敏感内容。是否显示?" createdLists: "已创建的列表" createdAntennas: "已创建的天线" +fromX: "从 {x}" +genEmbedCode: "生成嵌入代码" +noteOfThisUser: "此用户的帖子" +clipNoteLimitExceeded: "无法再往此便签内添加更多帖子" +performance: "性能" _delivery: status: "投递状态" stop: "停止投递" @@ -1397,6 +1404,7 @@ _serverSettings: fanoutTimelineDescription: "当启用时,可显著提高获取各种时间线时的性能,并减轻数据库的负荷。但是相对的 Redis 的内存使用量将会增加。如果服务器的内存不是很大,又或者运行不稳定的话可以把它关掉。" fanoutTimelineDbFallback: "回退到数据库" fanoutTimelineDbFallbackDescription: "当启用时,若时间线未被缓存,则将额外查询数据库。禁用该功能可通过不执行回退处理进一步减少服务器负载,但会限制可检索的时间线范围。" + reactionsBufferingDescription: "开启时可显著提高发送回应时的性能,及减轻数据库负荷。但 Redis 的内存用量会相应增加。" inquiryUrl: "联络地址" inquiryUrlDescription: "用来指定诸如向服务运营商咨询的论坛地址,或记载了运营商联系方式之类的网页地址。" _accountMigration: @@ -1598,7 +1606,7 @@ _achievements: _postedAt0min0sec: title: "报时" description: "在 0 点发布一篇帖子" - flavor: "嘣 嘣 嘣 Biu——!" + flavor: "报时信号最后一响,零点整" _selfQuote: title: "自我引用" description: "引用了自己的帖子" @@ -1647,8 +1655,8 @@ _achievements: flavor: "今年也请对本服务器多多指教!" _cookieClicked: title: "点击饼干小游戏" - description: "点击了可疑的饼干" - flavor: "是不是软件有问题?" + description: "点击了饼干" + flavor: "用错软件了?" _brainDiver: title: "Brain Diver" description: "发布了包含 Brain Diver 链接的帖子" @@ -1665,7 +1673,7 @@ _achievements: _bubbleGameDoubleExplodingHead: title: "两个🤯" description: "你合成出了2个游戏里最大的Emoji" - flavor: "" + flavor: "大约能 装满 这些便当盒 🤯 🤯 (比划)" _role: new: "创建角色" edit: "编辑角色" @@ -1730,6 +1738,11 @@ _role: canSearchNotes: "是否可以搜索帖子" canUseTranslator: "使用翻译功能" avatarDecorationLimit: "可添加头像挂件的最大个数" + canImportAntennas: "允许导入天线" + canImportBlocking: "允许导入拉黑列表" + canImportFollowing: "允许导入关注列表" + canImportMuting: "允许导入屏蔽列表" + canImportUserLists: "允许导入用户列表" _condition: roleAssignedTo: "已分配给手动角色" isLocal: "是本地用户" @@ -2442,6 +2455,7 @@ _webhookSettings: abuseReportResolved: "当举报被处理时" userCreated: "当用户被创建时" deleteConfirm: "要删除 webhook 吗?" + testRemarks: "点击开关右侧的按钮,可以发送使用假数据的测试 Webhook。" _abuseReport: _notificationRecipient: createRecipient: "新建举报通知" @@ -2640,3 +2654,17 @@ _contextMenu: app: "应用" appWithShift: "Shift 键应用" native: "浏览器的用户界面" +_embedCodeGen: + title: "自定义嵌入代码" + header: "显示标题" + autoload: "连续加载(不推荐)" + maxHeight: "最大高度" + maxHeightDescription: "若将最大值设为 0 则不限制最大高度。为防止小工具无限增高,建议设置一下。" + maxHeightWarn: "最大高度限制已禁用(0)。若这不是您想要的效果,请将最大高度设一个值。" + previewIsNotActual: "由于超出了预览画面可显示的范围,因此显示内容会与实际嵌入时有所不同。" + rounded: "圆角" + border: "外边框" + applyToPreview: "应用预览" + generateCode: "生成嵌入代码" + codeGenerated: "已生成代码" + codeGeneratedDescription: "将生成的代码贴到网站上来使用。" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 57da480d28..c684fbe628 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -592,6 +592,8 @@ ascendingOrder: "昇冪" descendingOrder: "降冪" scratchpad: "暫存記憶體" scratchpadDescription: "AiScript 控制臺為 AiScript 的實驗環境。您可以在此編寫、執行和確認程式碼與 Misskey 互動的結果。" +uiInspector: "UI 檢查" +uiInspectorDescription: "您可以看到記憶體中存在的 UI 元件實例的清單。 UI 元件由 Ui:C: 系列函數產生。" output: "輸出" script: "腳本" disablePagesScript: "停用頁面的 AiScript 腳本" @@ -1186,7 +1188,7 @@ edited: "已編輯" notificationRecieveConfig: "接受通知的設定" mutualFollow: "互相追隨" followingOrFollower: "追隨中或追隨者" -fileAttachedOnly: "顯示包含附件的貼文" +fileAttachedOnly: "只顯示包含附件的貼文" showRepliesToOthersInTimeline: "顯示給其他人的回覆" hideRepliesToOthersInTimeline: "在時間軸上隱藏給其他人的回覆" showRepliesToOthersInTimelineAll: "在時間軸包含追隨中所有人的回覆" @@ -1263,6 +1265,14 @@ confirmWhenRevealingSensitiveMedia: "要顯示敏感媒體時需確認" sensitiveMediaRevealConfirm: "這是敏感媒體。確定要顯示嗎?" createdLists: "已建立的清單" createdAntennas: "已建立的天線" +fromX: "自 {x}" +genEmbedCode: "產生嵌入程式碼" +noteOfThisUser: "這個使用者的貼文列表" +clipNoteLimitExceeded: "沒辦法在這個摘錄中增加更多貼文了。" +performance: "性能" +modified: "已變更" +discard: "取消" +thereAreNChanges: "有 {n} 處的變更" _delivery: status: "傳送狀態" stop: "停止發送" @@ -1397,6 +1407,7 @@ _serverSettings: fanoutTimelineDescription: "如果啟用的話,檢索各個時間軸的性能會顯著提昇,資料庫的負荷也會減少。不過,Redis 的記憶體使用量會增加。如果伺服器的記憶體容量比較少或者運行不穩定,可以停用。" fanoutTimelineDbFallback: "資料庫的回退" fanoutTimelineDbFallbackDescription: "若啟用,在時間軸沒有快取的情況下將執行回退處理以額外查詢資料庫。若停用,可以透過不執行回退處理來進一步減少伺服器的負荷,但會限制可取得的時間軸範圍。" + reactionsBufferingDescription: "啟用時,可以顯著提高建立反應時的效能並減少資料庫的負載。 但是,Redis 記憶體使用量會增加。" inquiryUrl: "聯絡表單網址" inquiryUrlDescription: "指定伺服器運營者的聯絡表單網址,或包含運營者聯絡資訊網頁的網址。" _accountMigration: @@ -1730,6 +1741,11 @@ _role: canSearchNotes: "可否搜尋貼文" canUseTranslator: "使用翻譯功能" avatarDecorationLimit: "頭像裝飾的最大設置量" + canImportAntennas: "允許匯入天線" + canImportBlocking: "允許匯入封鎖名單" + canImportFollowing: "允許匯入跟隨名單" + canImportMuting: "允許匯入靜音名單" + canImportUserLists: "允許匯入清單" _condition: roleAssignedTo: "手動指派角色完成" isLocal: "本地使用者" @@ -2442,6 +2458,7 @@ _webhookSettings: abuseReportResolved: "當處理了使用者的檢舉時" userCreated: "使用者被新增時" deleteConfirm: "請問是否要刪除 Webhook?" + testRemarks: "按下切換開關右側的按鈕,就會將假資料發送至 Webhook。" _abuseReport: _notificationRecipient: createRecipient: "新增接收檢舉的通知對象" @@ -2640,3 +2657,17 @@ _contextMenu: app: "應用程式" appWithShift: "Shift 鍵應用程式" native: "瀏覽器的使用者介面" +_embedCodeGen: + title: "自訂嵌入程式碼" + header: "檢視標頭 " + autoload: "自動繼續載入(不建議)" + maxHeight: "最大高度" + maxHeightDescription: "設定為 0 時代表沒有最大值。請指定某個值以避免小工具持續在縱向延伸。" + maxHeightWarn: "最大高度限制已停用(0)。如果這個變更不是您想要的,請將最大高度設定為某個值。" + previewIsNotActual: "由於超出了預覽畫面可顯示的範圍,因此顯示內容會與實際嵌入時有所不同。" + rounded: "圓角" + border: "給外框加上邊框" + applyToPreview: "反映在預覽中" + generateCode: "建立嵌入程式碼" + codeGenerated: "已產生程式碼" + codeGeneratedDescription: "請將產生的程式碼貼到您的網站上。" diff --git a/package.json b/package.json index 63c22cdc5b..91ff93301d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2024.9.0-alpha.8", + "version": "2024.9.0-alpha.10", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts index 0f69cf93a9..6ae8ccfbb3 100644 --- a/packages/backend/src/GlobalModule.ts +++ b/packages/backend/src/GlobalModule.ts @@ -126,8 +126,8 @@ const $meta: Provider = { const { type, body } = obj.message as GlobalEvents['internal']['payload']; switch (type) { case 'metaUpdated': { - for (const key in body) { - (meta as any)[key] = (body as any)[key]; + for (const key in body.after) { + (meta as any)[key] = (body.after as any)[key]; } meta.proxyAccount = null; // joinなカラムは通常取ってこないので break; diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index 87aa70713e..03646ff566 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -241,7 +241,7 @@ export interface InternalEventTypes { avatarDecorationCreated: MiAvatarDecoration; avatarDecorationDeleted: MiAvatarDecoration; avatarDecorationUpdated: MiAvatarDecoration; - metaUpdated: MiMeta; + metaUpdated: { before?: MiMeta; after: MiMeta; }; followChannel: { userId: MiUser['id']; channelId: MiChannel['id']; }; unfollowChannel: { userId: MiUser['id']; channelId: MiChannel['id']; }; updateUserProfile: MiUserProfile; diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts index ec630f804e..3d88d0aefe 100644 --- a/packages/backend/src/core/MetaService.ts +++ b/packages/backend/src/core/MetaService.ts @@ -52,7 +52,7 @@ export class MetaService implements OnApplicationShutdown { switch (type) { case 'metaUpdated': { this.cache = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい - ...body, + ...(body.after), proxyAccount: null, // joinなカラムは通常取ってこないので }; break; @@ -141,7 +141,7 @@ export class MetaService implements OnApplicationShutdown { }); } - this.globalEventService.publishInternalEvent('metaUpdated', updated); + this.globalEventService.publishInternalEvent('metaUpdated', { before, after: updated }); return updated; } diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 18efc9d562..89e3eafa0e 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -55,6 +55,7 @@ import { UserBlockingService } from '@/core/UserBlockingService.js'; import { isReply } from '@/misc/is-reply.js'; import { trackPromise } from '@/misc/promise-tracker.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { CollapsedQueue } from '@/misc/collapsed-queue.js'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; @@ -146,6 +147,7 @@ type Option = { @Injectable() export class NoteCreateService implements OnApplicationShutdown { #shutdownController = new AbortController(); + private updateNotesCountQueue: CollapsedQueue; constructor( @Inject(DI.config) @@ -215,7 +217,9 @@ export class NoteCreateService implements OnApplicationShutdown { private instanceChart: InstanceChart, private utilityService: UtilityService, private userBlockingService: UserBlockingService, - ) { } + ) { + this.updateNotesCountQueue = new CollapsedQueue(60 * 1000 * 5, this.collapseNotesCount, this.performUpdateNotesCount); + } @bindThis public async create(user: { @@ -509,7 +513,7 @@ export class NoteCreateService implements OnApplicationShutdown { // Register host if (this.userEntityService.isRemoteUser(user)) { this.federatedInstanceService.fetch(user.host).then(async i => { - this.instancesRepository.increment({ id: i.id }, 'notesCount', 1); + this.updateNotesCountQueue.enqueue(i.id, 1); if (this.meta.enableChartsForFederatedInstances) { this.instanceChart.updateNote(i.host, note, true); } @@ -1028,12 +1032,23 @@ export class NoteCreateService implements OnApplicationShutdown { } @bindThis - public dispose(): void { - this.#shutdownController.abort(); + private collapseNotesCount(oldValue: number, newValue: number) { + return oldValue + newValue; } @bindThis - public onApplicationShutdown(signal?: string | undefined): void { - this.dispose(); + private async performUpdateNotesCount(id: MiNote['id'], incrBy: number) { + await this.instancesRepository.increment({ id: id }, 'notesCount', incrBy); + } + + @bindThis + public async dispose(): Promise { + this.#shutdownController.abort(); + await this.updateNotesCountQueue.performAllNow(); + } + + @bindThis + public async onApplicationShutdown(signal?: string | undefined): Promise { + await this.dispose(); } } diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index 062d64f46b..6f9fe53937 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -337,10 +337,22 @@ export class ReactionService { //#endregion } + /** + * - 文字列タイプのレガシーな形式のリアクションを現在の形式に変換する + * - ローカルのリアクションのホストを `@.` にする(`decodeReaction()`の効果) + */ + @bindThis + public convertLegacyReaction(reaction: string): string { + reaction = this.decodeReaction(reaction).reaction; + if (Object.keys(legacies).includes(reaction)) return legacies[reaction]; + return reaction; + } + // TODO: 廃止 /** - * 文字列タイプのレガシーな形式のリアクションを現在の形式に変換しつつ、 - * データベース上には存在する「0個のリアクションがついている」という情報を削除する。 + * - 文字列タイプのレガシーな形式のリアクションを現在の形式に変換する + * - ローカルのリアクションのホストを `@.` にする(`decodeReaction()`の効果) + * - データベース上には存在する「0個のリアクションがついている」という情報を削除する */ @bindThis public convertLegacyReactions(reactions: MiNote['reactions']): MiNote['reactions'] { @@ -353,10 +365,7 @@ export class ReactionService { return count > 0; }) .map(([reaction, count]) => { - // unchecked indexed access - const convertedReaction = legacies[reaction] as string | undefined; - - const key = this.decodeReaction(convertedReaction ?? reaction).reaction; + const key = this.convertLegacyReaction(reaction); return [key, count] as const; }) @@ -411,11 +420,4 @@ export class ReactionService { host: undefined, }; } - - @bindThis - public convertLegacyReaction(reaction: string): string { - reaction = this.decodeReaction(reaction).reaction; - if (Object.keys(legacies).includes(reaction)) return legacies[reaction]; - return reaction; - } } diff --git a/packages/backend/src/core/ReactionsBufferingService.ts b/packages/backend/src/core/ReactionsBufferingService.ts index b1a197feeb..b4207c5106 100644 --- a/packages/backend/src/core/ReactionsBufferingService.ts +++ b/packages/backend/src/core/ReactionsBufferingService.ts @@ -11,22 +11,48 @@ import { bindThis } from '@/decorators.js'; import type { MiUser, NotesRepository } from '@/models/_.js'; import type { Config } from '@/config.js'; import { PER_NOTE_REACTION_USER_PAIR_CACHE_MAX } from '@/const.js'; +import type { GlobalEvents } from '@/core/GlobalEventService.js'; +import type { OnApplicationShutdown } from '@nestjs/common'; const REDIS_DELTA_PREFIX = 'reactionsBufferDeltas'; const REDIS_PAIR_PREFIX = 'reactionsBufferPairs'; @Injectable() -export class ReactionsBufferingService { +export class ReactionsBufferingService implements OnApplicationShutdown { constructor( @Inject(DI.config) private config: Config, + @Inject(DI.redisForSub) + private redisForSub: Redis.Redis, + @Inject(DI.redisForReactions) private redisForReactions: Redis.Redis, // TODO: 専用のRedisインスタンスにする @Inject(DI.notesRepository) private notesRepository: NotesRepository, ) { + this.redisForSub.on('message', this.onMessage); + } + + @bindThis + private async onMessage(_: string, data: string) { + const obj = JSON.parse(data); + + if (obj.channel === 'internal') { + const { type, body } = obj.message as GlobalEvents['internal']['payload']; + switch (type) { + case 'metaUpdated': { + // リアクションバッファリングが有効→無効になったら即bake + if (body.before != null && body.before.enableReactionsBuffering && !body.after.enableReactionsBuffering) { + this.bake(); + } + break; + } + default: + break; + } + } } @bindThis @@ -159,4 +185,27 @@ export class ReactionsBufferingService { .execute(); } } + + @bindThis + public mergeReactions(src: MiNote['reactions'], delta: Record): MiNote['reactions'] { + const reactions = { ...src }; + for (const [name, count] of Object.entries(delta)) { + if (reactions[name] != null) { + reactions[name] += count; + } else { + reactions[name] = count; + } + } + return reactions; + } + + @bindThis + public dispose(): void { + this.redisForSub.off('message', this.onMessage); + } + + @bindThis + public onApplicationShutdown(signal?: string | undefined): void { + this.dispose(); + } } diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts index a40c6ff1c9..75ab0a207c 100644 --- a/packages/backend/src/core/WebAuthnService.ts +++ b/packages/backend/src/core/WebAuthnService.ts @@ -164,6 +164,86 @@ export class WebAuthnService { return authenticationOptions; } + /** + * Initiate Passkey Auth (Without specifying user) + * @returns authenticationOptions + */ + @bindThis + public async initiateSignInWithPasskeyAuthentication(context: string): Promise { + const relyingParty = await this.getRelyingParty(); + + const authenticationOptions = await generateAuthenticationOptions({ + rpID: relyingParty.rpId, + userVerification: 'preferred', + }); + + await this.redisClient.setex(`webauthn:challenge:${context}`, 90, authenticationOptions.challenge); + + return authenticationOptions; + } + + /** + * Verify Webauthn AuthenticationCredential + * @throws IdentifiableError + * @returns If the challenge is successful, return the user ID. Otherwise, return null. + */ + @bindThis + public async verifySignInWithPasskeyAuthentication(context: string, response: AuthenticationResponseJSON): Promise { + const challenge = await this.redisClient.get(`webauthn:challenge:${context}`); + + if (!challenge) { + throw new IdentifiableError('2d16e51c-007b-4edd-afd2-f7dd02c947f6', `challenge '${context}' not found`); + } + + await this.redisClient.del(`webauthn:challenge:${context}`); + + const key = await this.userSecurityKeysRepository.findOneBy({ + id: response.id, + }); + + if (!key) { + throw new IdentifiableError('36b96a7d-b547-412d-aeed-2d611cdc8cdc', 'Unknown Webauthn key'); + } + + const relyingParty = await this.getRelyingParty(); + + let verification; + try { + verification = await verifyAuthenticationResponse({ + response: response, + expectedChallenge: challenge, + expectedOrigin: relyingParty.origin, + expectedRPID: relyingParty.rpId, + authenticator: { + credentialID: key.id, + credentialPublicKey: Buffer.from(key.publicKey, 'base64url'), + counter: key.counter, + transports: key.transports ? key.transports as AuthenticatorTransportFuture[] : undefined, + }, + requireUserVerification: true, + }); + } catch (error) { + throw new IdentifiableError('b18c89a7-5b5e-4cec-bb5b-0419f332d430', `verification failed: ${error}`); + } + + const { verified, authenticationInfo } = verification; + + if (!verified) { + return null; + } + + await this.userSecurityKeysRepository.update({ + id: response.id, + }, { + lastUsed: new Date(), + counter: authenticationInfo.newCounter, + credentialDeviceType: authenticationInfo.credentialDeviceType, + credentialBackedUp: authenticationInfo.credentialBackedUp, + }); + + return key.userId; + } + @bindThis public async verifyAuthentication(userId: MiUser['id'], response: AuthenticationResponseJSON): Promise { const challenge = await this.redisClient.get(`webauthn:challenge:${userId}`); diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts index 7c78f3319b..c7d19adfd5 100644 --- a/packages/backend/src/core/activitypub/ApRequestService.ts +++ b/packages/backend/src/core/activitypub/ApRequestService.ts @@ -205,7 +205,11 @@ export class ApRequestService { //#region リクエスト先がhtmlかつactivity+jsonへのalternate linkタグがあるとき const contentType = res.headers.get('content-type'); - if ((contentType ?? '').split(';')[0].trimEnd().toLowerCase() === 'text/html' && _followAlternate === true) { + if ( + res.ok && + (contentType ?? '').split(';')[0].trimEnd().toLowerCase() === 'text/html' && + _followAlternate === true + ) { const html = await res.text(); const { window, happyDOM } = new Window({ settings: { diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 7e64b9fc8d..c64e9151a7 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -16,25 +16,12 @@ import { bindThis } from '@/decorators.js'; import { DebounceLoader } from '@/misc/loader.js'; import { IdService } from '@/core/IdService.js'; import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js'; -import { MetaService } from '@/core/MetaService.js'; import type { OnModuleInit } from '@nestjs/common'; import type { CustomEmojiService } from '../CustomEmojiService.js'; import type { ReactionService } from '../ReactionService.js'; import type { UserEntityService } from './UserEntityService.js'; import type { DriveFileEntityService } from './DriveFileEntityService.js'; -function mergeReactions(src: Record, delta: Record) { - const reactions = { ...src }; - for (const [name, count] of Object.entries(delta)) { - if (reactions[name] != null) { - reactions[name] += count; - } else { - reactions[name] = count; - } - } - return reactions; -} - @Injectable() export class NoteEntityService implements OnModuleInit { private userEntityService: UserEntityService; @@ -329,12 +316,7 @@ export class NoteEntityService implements OnModuleInit { : this.meta.enableReactionsBuffering ? await this.reactionsBufferingService.get(note.id) : { deltas: {}, pairs: [] }; - const reactions = mergeReactions(this.reactionService.convertLegacyReactions(note.reactions), bufferedReactions.deltas ?? {}); - for (const [name, count] of Object.entries(reactions)) { - if (count <= 0) { - delete reactions[name]; - } - } + const reactions = this.reactionService.convertLegacyReactions(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions.deltas ?? {})); const reactionAndUserPairCache = note.reactionAndUserPairCache.concat(bufferedReactions.pairs.map(x => x.join('/'))); @@ -451,7 +433,7 @@ export class NoteEntityService implements OnModuleInit { for (const note of notes) { if (note.renote && (note.text == null && note.fileIds.length === 0)) { // pure renote - const reactionsCount = Object.values(mergeReactions(note.renote.reactions, bufferedReactions?.get(note.renote.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); + const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.renote.reactions, bufferedReactions?.get(note.renote.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); if (reactionsCount === 0) { myReactionsMap.set(note.renote.id, null); } else if (reactionsCount <= note.renote.reactionAndUserPairCache.length + (bufferedReactions?.get(note.renote.id)?.pairs.length ?? 0)) { @@ -467,7 +449,7 @@ export class NoteEntityService implements OnModuleInit { } } else { if (note.id < oldId) { - const reactionsCount = Object.values(mergeReactions(note.reactions, bufferedReactions?.get(note.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); + const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions?.get(note.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); if (reactionsCount === 0) { myReactionsMap.set(note.id, null); } else if (reactionsCount <= note.reactionAndUserPairCache.length + (bufferedReactions?.get(note.id)?.pairs.length ?? 0)) { diff --git a/packages/backend/src/misc/collapsed-queue.ts b/packages/backend/src/misc/collapsed-queue.ts new file mode 100644 index 0000000000..5bc20a78ae --- /dev/null +++ b/packages/backend/src/misc/collapsed-queue.ts @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +type Job = { + value: V; + timer: NodeJS.Timeout; +}; + +// TODO: redis使えるようにする +export class CollapsedQueue { + private jobs: Map> = new Map(); + + constructor( + private timeout: number, + private collapse: (oldValue: V, newValue: V) => V, + private perform: (key: K, value: V) => Promise, + ) {} + + enqueue(key: K, value: V) { + if (this.jobs.has(key)) { + const old = this.jobs.get(key)!; + const merged = this.collapse(old.value, value); + this.jobs.set(key, { ...old, value: merged }); + } else { + const timer = setTimeout(() => { + const job = this.jobs.get(key)!; + this.jobs.delete(key); + this.perform(key, job.value); + }, this.timeout); + this.jobs.set(key, { value, timer }); + } + } + + async performAllNow() { + const entries = [...this.jobs.entries()]; + this.jobs.clear(); + for (const [_key, job] of entries) { + clearTimeout(job.timer); + } + await Promise.allSettled(entries.map(([key, job]) => this.perform(key, job.value))); + } +} diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 2df37bedf4..68999b5d17 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -4,7 +4,7 @@ */ import { URL } from 'node:url'; -import { Inject, Injectable } from '@nestjs/common'; +import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import httpSignature from '@peertube/http-signature'; import * as Bull from 'bullmq'; import type Logger from '@/logger.js'; @@ -25,14 +25,22 @@ import { JsonLdService } from '@/core/activitypub/JsonLdService.js'; import { ApInboxService } from '@/core/activitypub/ApInboxService.js'; import { bindThis } from '@/decorators.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { QueueLoggerService } from '../QueueLoggerService.js'; -import type { InboxJobData } from '../types.js'; +import { CollapsedQueue } from '@/misc/collapsed-queue.js'; +import { MiNote } from '@/models/Note.js'; import { MiMeta } from '@/models/Meta.js'; import { DI } from '@/di-symbols.js'; +import { QueueLoggerService } from '../QueueLoggerService.js'; +import type { InboxJobData } from '../types.js'; + +type UpdateInstanceJob = { + latestRequestReceivedAt: Date, + shouldUnsuspend: boolean, +}; @Injectable() -export class InboxProcessorService { +export class InboxProcessorService implements OnApplicationShutdown { private logger: Logger; + private updateInstanceQueue: CollapsedQueue; constructor( @Inject(DI.meta) @@ -51,6 +59,7 @@ export class InboxProcessorService { private queueLoggerService: QueueLoggerService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('inbox'); + this.updateInstanceQueue = new CollapsedQueue(60 * 1000 * 5, this.collapseUpdateInstanceJobs, this.performUpdateInstance); } @bindThis @@ -187,11 +196,9 @@ export class InboxProcessorService { // Update stats this.federatedInstanceService.fetch(authUser.user.host).then(i => { - this.federatedInstanceService.update(i.id, { + this.updateInstanceQueue.enqueue(i.id, { latestRequestReceivedAt: new Date(), - isNotResponding: false, - // もしサーバーが死んでるために配信が止まっていた場合には自動的に復活させてあげる - suspensionState: i.suspensionState === 'autoSuspendedForNotResponding' ? 'none' : undefined, + shouldUnsuspend: i.suspensionState === 'autoSuspendedForNotResponding', }); this.fetchInstanceMetadataService.fetchInstanceMetadata(i); @@ -227,4 +234,36 @@ export class InboxProcessorService { } return 'ok'; } + + @bindThis + public collapseUpdateInstanceJobs(oldJob: UpdateInstanceJob, newJob: UpdateInstanceJob) { + const latestRequestReceivedAt = oldJob.latestRequestReceivedAt < newJob.latestRequestReceivedAt + ? newJob.latestRequestReceivedAt + : oldJob.latestRequestReceivedAt; + const shouldUnsuspend = oldJob.shouldUnsuspend || newJob.shouldUnsuspend; + return { + latestRequestReceivedAt, + shouldUnsuspend, + }; + } + + @bindThis + public async performUpdateInstance(id: string, job: UpdateInstanceJob) { + await this.federatedInstanceService.update(id, { + latestRequestReceivedAt: new Date(), + isNotResponding: false, + // もしサーバーが死んでるために配信が止まっていた場合には自動的に復活させてあげる + suspensionState: job.shouldUnsuspend ? 'none' : undefined, + }); + } + + @bindThis + public async dispose(): Promise { + await this.updateInstanceQueue.performAllNow(); + } + + @bindThis + async onApplicationShutdown(signal?: string) { + await this.dispose(); + } } diff --git a/packages/backend/src/server/ServerModule.ts b/packages/backend/src/server/ServerModule.ts index 12d5061985..3ab0b815f2 100644 --- a/packages/backend/src/server/ServerModule.ts +++ b/packages/backend/src/server/ServerModule.ts @@ -46,6 +46,7 @@ import { UserListChannelService } from './api/stream/channels/user-list.js'; import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.js'; import { ReversiChannelService } from './api/stream/channels/reversi.js'; import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js'; +import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.js'; @Module({ imports: [ @@ -71,6 +72,7 @@ import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js AuthenticateService, RateLimiterService, SigninApiService, + SigninWithPasskeyApiService, SigninService, SignupApiService, StreamingApiServerService, diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts index 13cbdfc3be..709a044601 100644 --- a/packages/backend/src/server/api/ApiServerService.ts +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -8,6 +8,7 @@ import cors from '@fastify/cors'; import multipart from '@fastify/multipart'; import fastifyCookie from '@fastify/cookie'; import { ModuleRef } from '@nestjs/core'; +import { AuthenticationResponseJSON } from '@simplewebauthn/types'; import type { Config } from '@/config.js'; import type { InstancesRepository, AccessTokensRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; @@ -17,6 +18,7 @@ import endpoints from './endpoints.js'; import { ApiCallService } from './ApiCallService.js'; import { SignupApiService } from './SignupApiService.js'; import { SigninApiService } from './SigninApiService.js'; +import { SigninWithPasskeyApiService } from './SigninWithPasskeyApiService.js'; import type { FastifyInstance, FastifyPluginOptions } from 'fastify'; @Injectable() @@ -37,6 +39,7 @@ export class ApiServerService { private apiCallService: ApiCallService, private signupApiService: SignupApiService, private signinApiService: SigninApiService, + private signinWithPasskeyApiService: SigninWithPasskeyApiService, ) { //this.createServer = this.createServer.bind(this); } @@ -131,6 +134,12 @@ export class ApiServerService { }; }>('/signin', (request, reply) => this.signinApiService.signin(request, reply)); + fastify.post<{ + Body: { + credential?: AuthenticationResponseJSON; + }; + }>('/signin-with-passkey', (request, reply) => this.signinWithPasskeyApiService.signin(request, reply)); + fastify.post<{ Body: { code: string; } }>('/signup-pending', (request, reply) => this.signupApiService.signupPending(request, reply)); fastify.get('/v1/instance/peers', async (request, reply) => { diff --git a/packages/backend/src/server/api/SigninWithPasskeyApiService.ts b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts new file mode 100644 index 0000000000..9ba23c54e2 --- /dev/null +++ b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts @@ -0,0 +1,173 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { randomUUID } from 'crypto'; +import { Inject, Injectable } from '@nestjs/common'; +import { IsNull } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { + SigninsRepository, + UserProfilesRepository, + UsersRepository, +} from '@/models/_.js'; +import type { Config } from '@/config.js'; +import { getIpHash } from '@/misc/get-ip-hash.js'; +import type { MiLocalUser, MiUser } from '@/models/User.js'; +import { IdService } from '@/core/IdService.js'; +import { bindThis } from '@/decorators.js'; +import { WebAuthnService } from '@/core/WebAuthnService.js'; +import Logger from '@/logger.js'; +import { LoggerService } from '@/core/LoggerService.js'; +import type { IdentifiableError } from '@/misc/identifiable-error.js'; +import { RateLimiterService } from './RateLimiterService.js'; +import { SigninService } from './SigninService.js'; +import type { AuthenticationResponseJSON } from '@simplewebauthn/types'; +import type { FastifyReply, FastifyRequest } from 'fastify'; + +@Injectable() +export class SigninWithPasskeyApiService { + private logger: Logger; + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + @Inject(DI.signinsRepository) + private signinsRepository: SigninsRepository, + + private idService: IdService, + private rateLimiterService: RateLimiterService, + private signinService: SigninService, + private webAuthnService: WebAuthnService, + private loggerService: LoggerService, + ) { + this.logger = this.loggerService.getLogger('PasskeyAuth'); + } + + @bindThis + public async signin( + request: FastifyRequest<{ + Body: { + credential?: AuthenticationResponseJSON; + context?: string; + }; + }>, + reply: FastifyReply, + ) { + reply.header('Access-Control-Allow-Origin', this.config.url); + reply.header('Access-Control-Allow-Credentials', 'true'); + + const body = request.body; + const credential = body['credential']; + + function error(status: number, error: { id: string }) { + reply.code(status); + return { error }; + } + + const fail = async (userId: MiUser['id'], status?: number, failure?: { id: string }) => { + // Append signin history + await this.signinsRepository.insert({ + id: this.idService.gen(), + userId: userId, + ip: request.ip, + headers: request.headers as any, + success: false, + }); + return error(status ?? 500, failure ?? { id: '4e30e80c-e338-45a0-8c8f-44455efa3b76' }); + }; + + try { + // Not more than 1 API call per 250ms and not more than 100 attempts per 30min + // NOTE: 1 Sign-in require 2 API calls + await this.rateLimiterService.limit({ key: 'signin-with-passkey', duration: 60 * 30 * 1000, max: 200, minInterval: 250 }, getIpHash(request.ip)); + } catch (err) { + reply.code(429); + return { + error: { + message: 'Too many failed attempts to sign in. Try again later.', + code: 'TOO_MANY_AUTHENTICATION_FAILURES', + id: '22d05606-fbcf-421a-a2db-b32610dcfd1b', + }, + }; + } + + // Initiate Passkey Auth challenge with context + if (!credential) { + const context = randomUUID(); + this.logger.info(`Initiate Passkey challenge: context: ${context}`); + const authChallengeOptions = { + option: await this.webAuthnService.initiateSignInWithPasskeyAuthentication(context), + context: context, + }; + reply.code(200); + return authChallengeOptions; + } + + const context = body.context; + if (!context || typeof context !== 'string') { + // If try Authentication without context + return error(400, { + id: '1658cc2e-4495-461f-aee4-d403cdf073c1', + }); + } + + this.logger.debug(`Try Sign-in with Passkey: context: ${context}`); + + let authorizedUserId: MiUser['id'] | null; + try { + authorizedUserId = await this.webAuthnService.verifySignInWithPasskeyAuthentication(context, credential); + } catch (err) { + this.logger.warn(`Passkey challenge Verify error! : ${err}`); + const errorId = (err as IdentifiableError).id; + return error(403, { + id: errorId, + }); + } + + if (!authorizedUserId) { + return error(403, { + id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', + }); + } + + // Fetch user + const user = await this.usersRepository.findOneBy({ + id: authorizedUserId, + host: IsNull(), + }) as MiLocalUser | null; + + if (user == null) { + return error(403, { + id: '652f899f-66d4-490e-993e-6606c8ec04c3', + }); + } + + if (user.isSuspended) { + return error(403, { + id: 'e03a5f46-d309-4865-9b69-56282d94e1eb', + }); + } + + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); + + // Authentication was successful, but passwordless login is not enabled + if (!profile.usePasswordLessLogin) { + return await fail(user.id, 403, { + id: '2d84773e-f7b7-4d0b-8f72-bb69b584c912', + }); + } + + const signinResponse = this.signinService.signin(request, reply, user); + return { + signinResponse: signinResponse, + }; + } +} diff --git a/packages/backend/src/server/web/views/base-embed.pug b/packages/backend/src/server/web/views/base-embed.pug index 2bab20a36c..baa0909676 100644 --- a/packages/backend/src/server/web/views/base-embed.pug +++ b/packages/backend/src/server/web/views/base-embed.pug @@ -13,6 +13,8 @@ html(class='embed') meta(name='referrer' content='origin') meta(name='theme-color' content= themeColor || '#86b300') meta(name='theme-color-orig' content= themeColor || '#86b300') + meta(property='og:site_name' content= instanceName || 'Misskey') + meta(property='instance_url' content= instanceUrl) meta(name='viewport' content='width=device-width, initial-scale=1') meta(name='format-detection' content='telephone=no,date=no,address=no,email=no,url=no') link(rel='icon' href= icon || '/favicon.ico') diff --git a/packages/frontend-embed/src/components/EmInstanceTicker.vue b/packages/frontend-embed/src/components/EmInstanceTicker.vue index eeeaee528e..4a116e317a 100644 --- a/packages/frontend-embed/src/components/EmInstanceTicker.vue +++ b/packages/frontend-embed/src/components/EmInstanceTicker.vue @@ -29,12 +29,12 @@ const props = defineProps<{ // if no instance data is given, this is for the local instance const instance = props.instance ?? { name: serverMetadata.name, - themeColor: (document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement).content, + themeColor: (document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement)?.content, }; const faviconUrl = computed(() => props.instance ? mediaProxy.getProxiedImageUrlNullable(props.instance.faviconUrl, 'preview') : mediaProxy.getProxiedImageUrlNullable(serverMetadata.iconUrl, 'preview') ?? '/favicon.ico'); -const themeColor = serverMetadata.themeColor ?? '#777777'; +const themeColor = props.instance?.themeColor ?? serverMetadata.themeColor ?? '#777777'; const bg = { background: `linear-gradient(90deg, ${themeColor}, ${themeColor}00)`, diff --git a/packages/frontend-embed/src/components/EmMediaImage.vue b/packages/frontend-embed/src/components/EmMediaImage.vue index fe1aa5a877..470352469b 100644 --- a/packages/frontend-embed/src/components/EmMediaImage.vue +++ b/packages/frontend-embed/src/components/EmMediaImage.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> -
+
- - - - +
@@ -110,7 +112,6 @@ import MkSwitch from '@/components/MkSwitch.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkInput from '@/components/MkInput.vue'; import MkLink from '@/components/MkLink.vue'; -import MkButton from '@/components/MkButton.vue'; import { useForm } from '@/scripts/use-form.js'; import MkFormFooter from '@/components/MkFormFooter.vue'; diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue index 537c86cb14..5207f0e38e 100644 --- a/packages/frontend/src/pages/admin/settings.vue +++ b/packages/frontend/src/pages/admin/settings.vue @@ -169,42 +169,44 @@ SPDX-License-Identifier: AGPL-3.0-only - - - - +
@@ -230,7 +232,6 @@ SPDX-License-Identifier: AGPL-3.0-only