Merge branch 'develop' into beta

This commit is contained in:
ThatOneCalculator 2023-09-20 07:47:30 -07:00
commit 09dc0bb550
No known key found for this signature in database
GPG Key ID: 8703CACD01000000
112 changed files with 3906 additions and 2901 deletions

View File

@ -3,7 +3,13 @@
**What does this PR do?** _(Please give us a brief description of what this PR does.)_ **What does this PR do?** _(Please give us a brief description of what this PR does.)_
**Contribution Guidelines** **Contribution Guidelines**
By submitting this issue, you agree to follow our [Contribution Guidelines](https://git.joinfirefish.org/firefish/firefish/-/blob/develop/CONTRIBUTING.md) By submitting this merge request, you agree to follow our [Contribution Guidelines](https://git.joinfirefish.org/firefish/firefish/-/blob/develop/CONTRIBUTING.md)
- [ ] I agree to follow this project's Contribution Guidelines - [ ] I agree to follow this project's Contribution Guidelines
- [ ] I have made sure to test this pull request - [ ] I have made sure to test this pull request
- [ ] I have made sure to run `pnpm run format` before submitting this pull request - [ ] I have made sure to run `pnpm run format` before submitting this pull request
If this merge request makes changes to the Firefish API, please update `docs/api-change.md`
- [ ] I updated the documentation
<!-- Uncomment if your merge request has multiple authors -->
<!-- Co-authored-by: Name <email@email.com> -->

View File

@ -23,31 +23,32 @@ Before creating an issue, please check the following:
> Do not close issues that are about to be resolved. It should remain open until a commit that actually resolves it is merged. > Do not close issues that are about to be resolved. It should remain open until a commit that actually resolves it is merged.
## Before implementation ## Before implementation
When you want to add a feature or fix a bug, **first have the design and policy reviewed in an Issue** (if it is not there, please make one). Without this step, there is a high possibility that the PR will not be merged even if it is implemented. When you want to add a feature or fix a bug, **first have the design and policy reviewed in an Issue** (if it is not there, please make one). Without this step, there is a high possibility that the MR will not be merged even if it is implemented.
At this point, you also need to clarify the goals of the PR you will create, and make sure that the other members of the team are aware of them. At this point, you also need to clarify the goals of the MR you will create, and make sure that the other members of the team are aware of them.
PRs that do not have a clear set of do's and don'ts tend to be bloated and difficult to review. MRs that do not have a clear set of do's and don'ts tend to be bloated and difficult to review.
Also, when you start implementation, assign yourself to the Issue (if you cannot do it yourself, ask another member to assign you). By expressing your intention to work the Issue, you can prevent conflicts in the work. Also, when you start implementation, assign yourself to the Issue (if you cannot do it yourself, ask another member to assign you). By expressing your intention to work the Issue, you can prevent conflicts in the work.
## Well-known branches ## Well-known branches
- The **`main`** branch is tracking the latest release and used for production purposes. - The **`main`** branch is tracking the latest release and used for production purposes.
- The **`develop`** branch is where we work for the next release. - The **`develop`** branch is where we work for the next release.
- When you create a PR, basically target it to this branch. **But create a different branch** - When you create a MR, basically target it to this branch. **But create a different branch**
- The **`l10n_develop`** branch is reserved for localization management. - The **`l10n_develop`** branch is reserved for localization management.
- **`feature/*`** branches are reserved for the development of a specific feature - **`feature/*`** branches are reserved for the development of a specific feature
## Creating a PR ## Creating a merge request (MR)
Thank you for your PR! Before creating a PR, please check the following: Thank you for your MR! Before creating a MR, please check the following:
- If possible, prefix the title with a keyword that identifies the type of this PR, as shown below. - If possible, prefix the title with a keyword that identifies the type of this MR, as shown below.
- `fix` / `refactor` / `feat` / `enhance` / `perf` / `chore` etc. You are also welcome to use gitmoji. This is important as we use these to A) easier read the git history and B) generate our changelog. Without propper prefixing it is possible that your PR is rejected. - `fix` / `refactor` / `feat` / `enhance` / `perf` / `chore` etc. You are also welcome to use gitmoji. This is important as we use these to A) easier read the git history and B) generate our changelog. Without propper prefixing it is possible that your MR is rejected.
- Also, make sure that the granularity of this PR is appropriate. Please do not include more than one type of change or interest in a single PR. - Also, make sure that the granularity of this MR is appropriate. Please do not include more than one type of change or interest in a single MR.
- If there is an Issue which will be resolved by this PR, please include a reference to the Issue in the text. Good examples include `Closing: #21` or `Resolves: #21` - If there is an Issue which will be resolved by this MR, please include a reference to the Issue in the text. Good examples include `Closing: #21` or `Resolves: #21`
- Check if there are any documents that need to be created or updated due to this change. - Check if there are any documents that need to be created or updated due to this change.
- For example, you need to update `docs/api-change.md` if the MR includes API changes.
- If you have added a feature or fixed a bug, please add a test case if possible. - If you have added a feature or fixed a bug, please add a test case if possible.
- Please make sure that formatting, tests and Lint are passed in advance. - Please make sure that formatting, tests and Lint are passed in advance.
- You can run it with `pnpm run format`, `pnpm run test` and `pnpm run lint`. [See more info](#testing) - You can run it with `pnpm run format`, `pnpm run test` and `pnpm run lint`. [See more info](#testing)
- If this PR includes UI changes, please attach a screenshot in the text. - If this MR includes UI changes, please attach a screenshot in the text.
Thanks for your cooperation 🤗 Thanks for your cooperation 🤗
@ -56,12 +57,12 @@ Be willing to comment on the good points and not just the things you want fixed
### Review perspective ### Review perspective
- Scope - Scope
- Are the goals of the PR clear? - Are the goals of the MR clear?
- Is the granularity of the PR appropriate? - Is the granularity of the MR appropriate?
- Security - Security
- Does merging this PR create a vulnerability? - Does merging this MR create a vulnerability?
- Performance - Performance
- Will merging this PR cause unexpected performance degradation? - Will merging this MR cause unexpected performance degradation?
- Is there a more efficient way? - Is there a more efficient way?
- Testing - Testing
- Does the test ensure the expected behavior? - Does the test ensure the expected behavior?
@ -69,12 +70,14 @@ Be willing to comment on the good points and not just the things you want fixed
- Does it check for anomalies? - Does it check for anomalies?
## Deploy (SOON) ## Deploy (SOON)
The `/deploy` command by issue comment can be used to deploy the contents of a PR to the preview environment. The `/deploy` command by issue comment can be used to deploy the contents of a MR to the preview environment.
``` ```
/deploy sha=<commit hash> /deploy sha=<commit hash>
``` ```
An actual domain will be assigned so you can test the federation. An actual domain will be assigned so you can test the federation.
# THE FOLLOWING IS OUTDATED:
## Merge ## Merge
## Release ## Release
@ -95,9 +98,6 @@ During development, it is useful to use the `yarn dev` command.
This command monitors the server-side and client-side source files and automatically builds them if they are modified. This command monitors the server-side and client-side source files and automatically builds them if they are modified.
In addition, it will also automatically start the Misskey server process. In addition, it will also automatically start the Misskey server process.
# THE FOLLOWING IS OUTDATED:
## Testing ## Testing
- Test codes are located in [`/test`](/test). - Test codes are located in [`/test`](/test).

View File

@ -209,6 +209,7 @@ Please don't use ElasticSearch unless you already have an ElasticSearch setup an
- To add custom error images, place them in the `./custom/assets/badges` directory, replacing the files already there. - To add custom error images, place them in the `./custom/assets/badges` directory, replacing the files already there.
- To add custom sounds, place only mp3 files in the `./custom/assets/sounds` directory. - To add custom sounds, place only mp3 files in the `./custom/assets/sounds` directory.
- To update custom assets without rebuilding, just run `pnpm run gulp`. - To update custom assets without rebuilding, just run `pnpm run gulp`.
- To block ChatGPT, CommonCrawl, or other crawlers from indexing your instance, uncomment the respective rules in `./custom/robots.txt`.
## 🧑‍🔬 Configuring a new server ## 🧑‍🔬 Configuring a new server

14
custom/assets/robots.txt Normal file
View File

@ -0,0 +1,14 @@
User-agent: *
Allow: /
# Uncomment the following to block CommonCrawl
#
# User-agent: CCBot
# User-agent: CCBot/2.0
# User-agent: CCBot/3.1
# Disallow: /
# Uncomment the following to block ChatGPT
#
# User-agent: GPTBot
# Disallow: /

9
docs/api-change.md Normal file
View File

@ -0,0 +1,9 @@
# Changes to the Firefish API
Breaking changes are indecated by the :warning: icon.
## v1.0.5 (unreleased)
### dev11
- :warning: `notes/translate` now requires credentials.

View File

@ -1,6 +0,0 @@
# **DO NOT edit locale files** except `ja-JP.yml`.
When you add text to the ja-JP file (of misskey-dev/misskey), it will automatically be applied to other language files.
Translations added in ja-JP file should contain the original Japanese strings.
Please see [Contribution guide](../CONTRIBUTING.md) for more information.

View File

@ -474,3 +474,4 @@ _preferencesBackups:
cannotLoad: Неуспешно зареждане cannotLoad: Неуспешно зареждане
editWidgetsExit: Готово editWidgetsExit: Готово
done: Готово done: Готово
emailRequiredForSignup: Изискване за адрес на е-поща за регистриране

View File

@ -1311,8 +1311,7 @@ yes: Sí
no: No no: No
noCrawle: Rebutjar la indexació dels restrejadors noCrawle: Rebutjar la indexació dels restrejadors
driveUsage: Espai fet servir al Disk driveUsage: Espai fet servir al Disk
noCrawleDescription: No permetre que els buscadors guardin la informació de les pàgines noCrawleDescription: Demanar als motors de cerca externs no indexar el teu contingut.
de perfil, publicacions, Pàgines, etc.
alwaysMarkSensitive: Marcar per defecte com a NSFW alwaysMarkSensitive: Marcar per defecte com a NSFW
lockedAccountInfo: Si has configurat la visibilitat del compte per "Només seguidors" lockedAccountInfo: Si has configurat la visibilitat del compte per "Només seguidors"
les teves publicacions no serien visibles per a ningú més, inclús si has d'aprovar les teves publicacions no serien visibles per a ningú més, inclús si has d'aprovar
@ -2186,3 +2185,10 @@ confirm: Confirmar
importZip: Importar ZIP importZip: Importar ZIP
exportZip: Exportar ZIP exportZip: Exportar ZIP
emojiPackCreator: Creador de paquets Emoji emojiPackCreator: Creador de paquets Emoji
detectPostLanguage: Detecta l'idioma automàticament i mostra un botó per els articles
en altres idiomes
indexableDescription: Permet al cercador intern mostrar els missatges públics
indexable: Indexable
languageForTranslation: Idioma de traducció d'articles
openServerInfo: Mostra la informació del servidor fent clic al símbol del servidor
en un missatge

View File

@ -737,8 +737,7 @@ no: "Nein"
driveFilesCount: "Anzahl der Dateien in Cloud-Drive" driveFilesCount: "Anzahl der Dateien in Cloud-Drive"
driveUsage: "Cloud-Drive-Auslastung" driveUsage: "Cloud-Drive-Auslastung"
noCrawle: "Crawler-Indexierung ablehnen" noCrawle: "Crawler-Indexierung ablehnen"
noCrawleDescription: "Suchmaschinen bitten, die eigene Profilseite, Beiträge, Nutzer-Seiten noCrawleDescription: "Externe Suchmaschinen auffordern, Ihre Inhalte nicht zu indizieren."
usw. nicht zu indexieren."
lockedAccountInfo: "Auch wenn du Follow-Anfragen auf manuelle Bestätigung setzt, wird lockedAccountInfo: "Auch wenn du Follow-Anfragen auf manuelle Bestätigung setzt, wird
jeder deiner Posts öffentlich sichtbar sein, sofern du ihre Sichtbarkeit nicht auf jeder deiner Posts öffentlich sichtbar sein, sofern du ihre Sichtbarkeit nicht auf
\"Nur Follower\" setzt." \"Nur Follower\" setzt."
@ -2104,7 +2103,7 @@ pwa: PWA installieren
cw: Inhaltswarnung cw: Inhaltswarnung
older: älter older: älter
newer: neuer newer: neuer
accessibility: Erreichbarkeit accessibility: Barrierefreiheit
jumpToPrevious: Zum Vorherigen springen jumpToPrevious: Zum Vorherigen springen
silencedWarning: Diese Meldung wird angezeigt, weil diese Nutzer von Servern stammen, silencedWarning: Diese Meldung wird angezeigt, weil diese Nutzer von Servern stammen,
die Ihr Administrator abgeschaltet hat, so dass es sich möglicherweise um Spam handelt. die Ihr Administrator abgeschaltet hat, so dass es sich möglicherweise um Spam handelt.
@ -2207,4 +2206,12 @@ addRe: Ein "re:" am Anfang des Kommentars hinzufügen, um einem Beitrag mit eine
confirm: Bestätigen confirm: Bestätigen
importZip: ZIP Importieren importZip: ZIP Importieren
emojiPackCreator: Emoji-Pack Ersteller emojiPackCreator: Emoji-Pack Ersteller
exportZip: ZIP Exportieren exportZip: ZIP exportieren
detectPostLanguage: Sprache automatisch erkennen und eine Schaltfläche zum Übersetzen
von Beiträgen in Fremdsprachen anzeigen
indexableDescription: Der integrierten Suche erlauben, Ihre öffentlichen Beiträge
anzuzeigen
indexable: Indexierbar
languageForTranslation: Übersetzungssprache veröffentlichen
openServerInfo: Anzeigen von Serverinformationen durch Anklicken des Server-Tickers
in einem Beitrag

View File

@ -675,7 +675,7 @@ emptyToDisableSmtpAuth: "Leave username and password empty to disable SMTP verif
smtpSecure: "Use implicit SSL/TLS for SMTP connections" smtpSecure: "Use implicit SSL/TLS for SMTP connections"
smtpSecureInfo: "Turn this off when using STARTTLS" smtpSecureInfo: "Turn this off when using STARTTLS"
testEmail: "Test email delivery" testEmail: "Test email delivery"
wordMute: "Word mute" wordMute: "Word and language mutes"
regexpError: "Regular Expression error" regexpError: "Regular Expression error"
regexpErrorDescription: "An error occurred in the regular expression on line {line} regexpErrorDescription: "An error occurred in the regular expression on line {line}
of your {tab} word mutes:" of your {tab} word mutes:"
@ -762,8 +762,7 @@ no: "No"
driveFilesCount: "Number of Drive files" driveFilesCount: "Number of Drive files"
driveUsage: "Drive space usage" driveUsage: "Drive space usage"
noCrawle: "Reject crawler indexing" noCrawle: "Reject crawler indexing"
noCrawleDescription: "Ask search engines to not index your profile page, posts, Pages, noCrawleDescription: "Ask external search engines to not index your content."
etc."
lockedAccountInfo: "Unless you set your post visiblity to \"Followers only\", your lockedAccountInfo: "Unless you set your post visiblity to \"Followers only\", your
posts will be visible to anyone, even if you require followers to be manually approved." posts will be visible to anyone, even if you require followers to be manually approved."
alwaysMarkSensitive: "Mark as NSFW by default" alwaysMarkSensitive: "Mark as NSFW by default"
@ -1139,6 +1138,12 @@ confirm: "Confirm"
importZip: "Import ZIP" importZip: "Import ZIP"
exportZip: "Export ZIP" exportZip: "Export ZIP"
emojiPackCreator: "Emoji pack creator" emojiPackCreator: "Emoji pack creator"
indexable: "Indexable"
indexableDescription: "Allow built-in search to show your public posts"
languageForTranslation: "Post translation language"
detectPostLanguage: "Automatically detect the language and show a translate button for posts in foreign languages"
vibrate: "Play vibrations"
openServerInfo: "Show server information by clicking the server ticker on a post"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "Reduces the effort of server moderation through automatically recognizing description: "Reduces the effort of server moderation through automatically recognizing
@ -1370,14 +1375,19 @@ _menuDisplay:
hide: "Hide" hide: "Hide"
_wordMute: _wordMute:
muteWords: "Muted words" muteWords: "Muted words"
muteLangs: "Muted Languages"
muteWordsDescription: "Separate with spaces for an AND condition or with line breaks muteWordsDescription: "Separate with spaces for an AND condition or with line breaks
for an OR condition." for an OR condition."
muteWordsDescription2: "Surround keywords with slashes to use regular expressions." muteWordsDescription2: "Surround keywords with slashes to use regular expressions."
muteLangsDescription: "Separate with spaces or line breaks for an OR condition."
muteLangsDescription2: "Use language code e.g. en, fr, ja, zh."
softDescription: "Hide posts that fulfil the set conditions from the timeline." softDescription: "Hide posts that fulfil the set conditions from the timeline."
langDescription: "Hide posts that match set language from the timeline."
hardDescription: "Prevents posts fulfilling the set conditions from being added hardDescription: "Prevents posts fulfilling the set conditions from being added
to the timeline. In addition, these posts will not be added to the timeline even to the timeline. In addition, these posts will not be added to the timeline even
if the conditions are changed." if the conditions are changed."
soft: "Soft" soft: "Soft"
lang: "Language"
hard: "Hard" hard: "Hard"
mutedNotes: "Muted posts" mutedNotes: "Muted posts"
_instanceMute: _instanceMute:

View File

@ -874,7 +874,7 @@ pubSub: "Cuentas Pub/Sub"
lastCommunication: "Última comunicación" lastCommunication: "Última comunicación"
resolved: "Resuelto" resolved: "Resuelto"
unresolved: "Sin resolver" unresolved: "Sin resolver"
breakFollow: "Dejar de seguir" breakFollow: "Quitar seguidor"
itsOn: "¡Está encendido!" itsOn: "¡Está encendido!"
itsOff: "¡Está apagado!" itsOff: "¡Está apagado!"
emailRequiredForSignup: "Se requere una dirección de correo electrónico para el registro emailRequiredForSignup: "Se requere una dirección de correo electrónico para el registro

View File

@ -309,11 +309,11 @@ emptyDrive: "Le Drive est vide"
emptyFolder: "Le dossier est vide" emptyFolder: "Le dossier est vide"
unableToDelete: "Suppression impossible" unableToDelete: "Suppression impossible"
inputNewFileName: "Entrez un nouveau nom de fichier" inputNewFileName: "Entrez un nouveau nom de fichier"
inputNewDescription: "Veuillez entrer une nouvelle description" inputNewDescription: "Veuillez entrer une nouvelle description au fichier"
inputNewFolderName: "Entrez un nouveau nom de dossier" inputNewFolderName: "Entrez un nouveau nom de dossier"
circularReferenceFolder: "Le dossier de destination est un sous-dossier du dossier circularReferenceFolder: "Le dossier de destination est un sous-dossier du dossier
que vous souhaitez déplacer." que vous souhaitez déplacer."
hasChildFilesOrFolders: "Impossible de supprimer ce dossier car il n'est pas vide." hasChildFilesOrFolders: "Impossible de supprimer ce dossier, car il n'est pas vide."
copyUrl: "Copier lURL" copyUrl: "Copier lURL"
rename: "Renommer" rename: "Renommer"
avatar: "Avatar" avatar: "Avatar"
@ -605,7 +605,7 @@ disablePlayer: "Fermer le lecteur vidéo"
expandTweet: "Étendre le tweet" expandTweet: "Étendre le tweet"
themeEditor: "Éditeur de thèmes" themeEditor: "Éditeur de thèmes"
description: "Description" description: "Description"
describeFile: "Ajouter une description d'image" describeFile: "Ajouter une description"
enterFileDescription: "Saisissez une description" enterFileDescription: "Saisissez une description"
author: "Auteur·rice" author: "Auteur·rice"
leaveConfirm: "Vous avez des modifications non-sauvegardées. Voulez-vous les ignorer leaveConfirm: "Vous avez des modifications non-sauvegardées. Voulez-vous les ignorer
@ -683,7 +683,7 @@ fillAbuseReportDescription: "Veuillez expliquer les raisons du signalement. S'il
d'une publication en particulier, veuillez inclure le lien." d'une publication en particulier, veuillez inclure le lien."
abuseReported: "Le rapport est envoyé. Merci." abuseReported: "Le rapport est envoyé. Merci."
reporter: "Signalé par" reporter: "Signalé par"
reporteeOrigin: "Origine du signalement" reporteeOrigin: "Origine du signalé"
reporterOrigin: "Origine du signalement" reporterOrigin: "Origine du signalement"
forwardReport: "Transférer le signalement à linstance distante" forwardReport: "Transférer le signalement à linstance distante"
send: "Envoyer" send: "Envoyer"
@ -724,8 +724,8 @@ no: "Non"
driveFilesCount: "Nombre de fichiers dans le Drive" driveFilesCount: "Nombre de fichiers dans le Drive"
driveUsage: "Utilisation du Drive" driveUsage: "Utilisation du Drive"
noCrawle: "Refuser l'indexation par les robots" noCrawle: "Refuser l'indexation par les robots"
noCrawleDescription: "Demandez aux moteurs de recherche de ne pas indexer votre page noCrawleDescription: "Demandez aux moteurs de recherche externes de ne pas indexer
de profil, vos publications, vos pages, etc." votre contenu."
lockedAccountInfo: "À moins que vous ne définissiez la visibilité de votre publication lockedAccountInfo: "À moins que vous ne définissiez la visibilité de votre publication
sur \"Abonné-e-s\", vos publications sont visibles par tous, même si vous exigez sur \"Abonné-e-s\", vos publications sont visibles par tous, même si vous exigez
que les demandes d'abonnement soient approuvées manuellement." que les demandes d'abonnement soient approuvées manuellement."
@ -2085,7 +2085,7 @@ silenceThisInstance: Masquer ce serveur
silencedInstances: Serveurs masqués silencedInstances: Serveurs masqués
silenced: Masqué silenced: Masqué
deleted: Effacé deleted: Effacé
editNote: Modifier publication editNote: Modifier la publication
edited: 'Modifié à {date} {time}' edited: 'Modifié à {date} {time}'
flagShowTimelineRepliesDescription: Si activé, affiche dans le fil les réponses des flagShowTimelineRepliesDescription: Si activé, affiche dans le fil les réponses des
utilisatieur·rice·s aux publications des autres. utilisatieur·rice·s aux publications des autres.
@ -2209,4 +2209,12 @@ addRe: Ajouter "re:" au début dun avertissement de contenu (CW) en réponse
confirm: Confirmer confirm: Confirmer
importZip: Importer ZIP importZip: Importer ZIP
exportZip: Exporter ZIP exportZip: Exporter ZIP
emojiPackCreator: Créateur de pack demoji emojiPackCreator: Créateur de pack démoji
detectPostLanguage: Détecte automatiquement le langage et affiche un bouton de traduction
pour les langues étrangères
indexableDescription: Permettre à la recherche interne dafficher vos publications
publiques
openServerInfo: Afficher les informations du serveur en cliquant sur le bandeau de
serveur dune publication
indexable: Indexable
languageForTranslation: Langage post-traduction

View File

@ -81,7 +81,7 @@ deleteAndEdit: Eliminar e editar
blockConfirm: Tes a certeza de querer bloquear esta conta? blockConfirm: Tes a certeza de querer bloquear esta conta?
deleteAndEditConfirm: Tes a certeza de querer eliminar esta publicación e editala? deleteAndEditConfirm: Tes a certeza de querer eliminar esta publicación e editala?
Perderás todas as súas reaccións, promocións e respostas. Perderás todas as súas reaccións, promocións e respostas.
editNote: Editar nota editNote: Editar publicación
edited: Editado o {date} {time} edited: Editado o {date} {time}
sendMessage: Enviar unha mensaxe sendMessage: Enviar unha mensaxe
copyUsername: Copiar identificador copyUsername: Copiar identificador
@ -282,3 +282,76 @@ storageUsage: Uso da almacenaxe
charts: Gráficas charts: Gráficas
perHour: Por hora perHour: Por hora
followRequest: Solicitar seguimento followRequest: Solicitar seguimento
messageRead: Ler
noMoreHistory: Non hai máis historial
images: Imaxes
manageGroups: Xestionar grupos
unableToDelete: Non se puido eliminar
syncDeviceDarkMode: Syncr Modo Escuro cos axustes do teu dispositivo
uploadFromUrl: Subir desde un URL
emptyDrive: O teu Disco está baleiro
copyUrl: Copiar URL
nUsersRead: lido por {n}
uploadFromUrlRequested: Solicitaches unha subida
circularReferenceFolder: O cartafol de destino é un subcartafol do cartafol que queres
mover.
selectFile: Elexir un ficheiro
inputNewFileName: Escribe o novo nome
agreeTo: Acepto os {0}
whenServerDisconnected: Cando se perda a conexión co servidor
selectFolder: Elexir un cartafol
saved: Gardado
selectFiles: Elexir ficheiros
fileName: Nome do ficheiro
explore: Descubrir
keepOriginalUploadingDescription: Garda a imaxe subida orixinalmente tal como é. Se
o desactivas, a versión que se mostrará na web será creada ao subila.
folderName: Nome do cartafol
lightThemes: Decorados claros
rename: Cambiar nome
activity: Actividade
fromUrl: Desde URL
darkThemes: Decorados escuros
birthday: Aniversario
registeredDate: Conta creada en
fromDrive: Desde Disco
uploadFromUrlMayTakeTime: Podería tardar una anaco en completarse a subida.
theme: Decorados
renameFolder: Cambiar nome a este cartafol
resetAreYouSure: Queres restablecer?
startMessaging: Comezar un novo chat
light: Claro
themeForLightMode: Decorado a usar no Modo Claro
inputNewDescription: Escribe a descrición
start: Comezar
selectFolders: Elexir cartafoles
remoteUserCaution: A información das usuarias remotas podería estar incompleta.
exportRequested: Solicitaches unha exportación. Vainos levar un pouco. Vai ser engadida
ao teu Disco cando estea completa.
deleteFolder: Eliminar este cartafol
drive: Disco
importRequested: Solicitaches unha importación. Vainos levar un anaco.
uploadFromUrlDescription: URL do ficheiro que queres subir
location: Localización
unfollowConfirm: Tes a certeza de que queres deixar de seguir a {name}?
banner: Cabeceira
dark: Escuro
home: Inicio
keepOriginalUploading: Manter imaxe orixinal
upload: Subir
yearsOld: '{age} anos de idade'
emptyFolder: Este cartafol está baleiro
messaging: Chat
nsfw: NSFW
addFile: Engadir un ficheiro
tos: Termos do Servizo
themeForDarkMode: Decorado a usar no Modo Escuro
deleteAreYouSure: Tes a certeza de querer desbotar "{x}"?
createFolder: Crear un cartafol
renameFile: Cambiar nome ao ficheiro
lookup: Buscar
avatar: Avatar
driveFileDeleteConfirm: Tes a certeza de querer eliminar o ficheiro "{name}"? Vai
ser retirado de todas as publicacións nas que estea como anexo.
inputNewFolderName: Escribe o novo nome do cartafol
hasChildFilesOrFolders: Como o cartafol non está baleiro, non pode ser eliminado.

View File

@ -721,8 +721,7 @@ no: "Tidak"
driveFilesCount: "Jumlah berkas drive" driveFilesCount: "Jumlah berkas drive"
driveUsage: "Penggunaan ruang penyimpanan drive" driveUsage: "Penggunaan ruang penyimpanan drive"
noCrawle: "Tolak pengindeksan crawler" noCrawle: "Tolak pengindeksan crawler"
noCrawleDescription: "Minta mesin pencari untuk tidak mengindeks halaman profil, postingan, noCrawleDescription: "Minta mesin pencari eksternal untuk tidak mengindeks kontenmu."
Halaman kamu, dll."
lockedAccountInfo: "Kecuali kamu mengatur visibilitas postingan milikmu ke \"Hanya lockedAccountInfo: "Kecuali kamu mengatur visibilitas postingan milikmu ke \"Hanya
pengikut\", postingan milikmu akan dapat dilihat oleh siapa saja, bahkan jika kamu pengikut\", postingan milikmu akan dapat dilihat oleh siapa saja, bahkan jika kamu
memerlukan pengikut untuk disetujui secara manual." memerlukan pengikut untuk disetujui secara manual."
@ -1462,7 +1461,7 @@ _profile:
metadata: "Informasi tambahan" metadata: "Informasi tambahan"
metadataEdit: "Sunting informasi tambahan" metadataEdit: "Sunting informasi tambahan"
metadataDescription: "Kamu dapat menampilkan hingga 4 bagian informasi tambahan metadataDescription: "Kamu dapat menampilkan hingga 4 bagian informasi tambahan
ke dalam profilmu. Anda dapat menambahkan tag {a} atau tag {l} dengan {rel} untuk ke dalam profilmu. Kamu bisa menambahkan tag {a} atau tag {l} dengan {rel} untuk
memverifikasi tautan di profil kamu!" memverifikasi tautan di profil kamu!"
metadataLabel: "Label" metadataLabel: "Label"
metadataContent: "Isi" metadataContent: "Isi"
@ -2169,3 +2168,10 @@ confirm: Konfirmasi
importZip: Impor ZIP importZip: Impor ZIP
exportZip: Ekspor ZIP exportZip: Ekspor ZIP
emojiPackCreator: Pembuat paket emoji emojiPackCreator: Pembuat paket emoji
detectPostLanguage: Deteksi bahasa secara otomatis dan tampilkan tombol terjemahkan
untuk kiriman dalam bahasa asing
indexableDescription: Perbolehkan pencarian di sini untuk menampilkan kiriman publikmu
indexable: Dapat diindeks
languageForTranslation: Bahasa terjemahan kiriman
openServerInfo: Tampilkan informasi server dengan mengeklik ticker server di sebuah
kiriman

View File

@ -70,20 +70,12 @@ module.exports = Object.entries(locales).reduce(
(a, [k, v]) => ( (a, [k, v]) => (
(a[k] = (() => { (a[k] = (() => {
const [lang] = k.split("-"); const [lang] = k.split("-");
switch (k) { return k === "en-US" ? v :
case "en-US": merge(
return v;
case "ja-JP":
case "ja-KS":
return merge(locales["en-US"], v);
default:
return merge(
locales["en-US"], locales["en-US"],
locales["ja-JP"],
locales[`${lang}-${primaries[lang]}`] || {}, locales[`${lang}-${primaries[lang]}`] || {},
v, v,
); );
}
})()), })()),
a a
), ),

File diff suppressed because it is too large Load Diff

View File

@ -66,7 +66,7 @@ import: "インポート"
export: "エクスポート" export: "エクスポート"
files: "ファイル" files: "ファイル"
download: "ダウンロード" download: "ダウンロード"
driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?これにより、このファイルが添付されている投稿も削除されます。" driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?これにより、添付ファイルとして含まれているすべての投稿から削除されます。"
unfollowConfirm: "{name}さんのフォローを解除しますか?" unfollowConfirm: "{name}さんのフォローを解除しますか?"
exportRequested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。" exportRequested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。"
importRequested: "インポートをリクエストしました。これには時間がかかる場合があります。" importRequested: "インポートをリクエストしました。これには時間がかかる場合があります。"
@ -988,6 +988,8 @@ youHaveUnreadAnnouncements: "未読のお知らせがあります"
neverShow: "今後表示しない" neverShow: "今後表示しない"
remindMeLater: "また後で" remindMeLater: "また後で"
addRe: "閲覧注意の投稿への返信で、注釈の先頭に\"re:\"を追加する" addRe: "閲覧注意の投稿への返信で、注釈の先頭に\"re:\"を追加する"
languageForTranslation: "投稿翻訳に使用する言語"
detectPostLanguage: "投稿の言語を自動検出し、外国語の投稿に翻訳ボタンを表示する"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てられます。サーバーの負荷が少し増えます。" description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てられます。サーバーの負荷が少し増えます。"
@ -1198,11 +1200,16 @@ _menuDisplay:
hide: "隠す" hide: "隠す"
_wordMute: _wordMute:
muteWords: "ミュートするワード" muteWords: "ミュートするワード"
muteLangs: "ミュートされた言語"
muteWordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。" muteWordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。"
muteWordsDescription2: "キーワードをスラッシュで囲むと正規表現になります。" muteWordsDescription2: "キーワードをスラッシュで囲むと正規表現になります。"
muteLangsDescription: "OR 条件の場合はスペースまたは改行で区切ります。"
muteLangsDescription2: "言語コードを使用します。例: en, fr, ja, zh."
softDescription: "指定した条件の投稿をタイムラインから隠します。" softDescription: "指定した条件の投稿をタイムラインから隠します。"
langDescription: "設定した言語に一致する投稿をタイムラインから非表示にします。"
hardDescription: "指定した条件の投稿をタイムラインに追加しないようにします。追加されなかった投稿は、条件を変更しても除外されたままになります。" hardDescription: "指定した条件の投稿をタイムラインに追加しないようにします。追加されなかった投稿は、条件を変更しても除外されたままになります。"
soft: "ソフト" soft: "ソフト"
lang: "言語"
hard: "ハード" hard: "ハード"
mutedNotes: "ミュートされた投稿" mutedNotes: "ミュートされた投稿"
_instanceMute: _instanceMute:
@ -1970,7 +1977,7 @@ _feeds:
rss: RSS rss: RSS
jsonFeed: JSONフィード jsonFeed: JSONフィード
copyFeed: フィードのURLをコピー copyFeed: フィードのURLをコピー
origin: 起源 origin: 元のサーバー
delete2fa: 2要素認証を無効化 delete2fa: 2要素認証を無効化
deletePasskeys: パスキーを削除 deletePasskeys: パスキーを削除
delete2faConfirm: これで、このアカウントの2要素認証は完全に削除されます。続行しますか? delete2faConfirm: これで、このアカウントの2要素認証は完全に削除されます。続行しますか?
@ -1980,3 +1987,4 @@ importZip: ZIPをインポート
emojiPackCreator: 絵文字パックの作者 emojiPackCreator: 絵文字パックの作者
confirm: 確認 confirm: 確認
exportZip: ZIPをエクスポート exportZip: ZIPをエクスポート
openServerInfo: "投稿内のサーバー名をクリックでサーバー情報を開く"

View File

@ -82,10 +82,8 @@ somethingHappened: "なんやアカンことが起きたで"
retry: "もっぺんやる?" retry: "もっぺんやる?"
pageLoadError: "ページの読み込みに失敗してもた… えろうすんまへん" pageLoadError: "ページの読み込みに失敗してもた… えろうすんまへん"
pageLoadErrorDescription: "これは普通、ネットワークかブラウザキャッシュが原因やからね。キャッシュをクリアするか、もうちっとだけ待ってくれへんか?" pageLoadErrorDescription: "これは普通、ネットワークかブラウザキャッシュが原因やからね。キャッシュをクリアするか、もうちっとだけ待ってくれへんか?"
serverIsDead: "The server is not responding. Please wait for a while before trying serverIsDead: "サーバーの応答がおまへん。ちーとの間待ってからもっかい試してみぃな。"
again." youShouldUpgradeClient: "このページを表示するには、リロードして新しいバージョンのクライアントを使うてや。"
youShouldUpgradeClient: "To display this page, please reload and use a new version
client. "
enterListName: "リスト名を入れてや" enterListName: "リスト名を入れてや"
privacy: "プライバシー" privacy: "プライバシー"
makeFollowManuallyApprove: "自分が認めた人だけがこのアカウントをフォローできるようにする" makeFollowManuallyApprove: "自分が認めた人だけがこのアカウントをフォローできるようにする"
@ -109,7 +107,7 @@ clickToShow: "押したら見えるで"
sensitive: "ちょっとアカンやつやで" sensitive: "ちょっとアカンやつやで"
add: "増やす" add: "増やす"
reaction: "リアクション" reaction: "リアクション"
reactionSetting: "Reaction that will be displayed in Picker. " reactionSetting: "ピッカーに表示しはるリアクション"
reactionSettingDescription2: "ドラッグで並び替え、クリックで削除、+を押して追加やで。" reactionSettingDescription2: "ドラッグで並び替え、クリックで削除、+を押して追加やで。"
rememberNoteVisibility: "公開範囲覚えといて" rememberNoteVisibility: "公開範囲覚えといて"
attachCancel: "のっけるのやめる" attachCancel: "のっけるのやめる"
@ -144,9 +142,8 @@ flagAsBot: "ワイはBotや 🤖"
flagAsBotDescription: "もしこのアカウントがプログラムによって運用されるんやったら、このフラグをオンにしてたのむで。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Firefishのシステム上での扱いがBotに合ったもんになったりするんやで。" flagAsBotDescription: "もしこのアカウントがプログラムによって運用されるんやったら、このフラグをオンにしてたのむで。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Firefishのシステム上での扱いがBotに合ったもんになったりするんやで。"
flagAsCat: "ワイはCatや 🐯" flagAsCat: "ワイはCatや 🐯"
flagAsCatDescription: "自分、猫ちゃんならこのフラグつけてみ?" flagAsCatDescription: "自分、猫ちゃんならこのフラグつけてみ?"
flagShowTimelineReplies: "It will display the reply to the note in the timeline. " flagShowTimelineReplies: "タイムラインに返信を表示させたる"
flagShowTimelineRepliesDescription: "It will display the reply to notes other than flagShowTimelineRepliesDescription: "有効にすると、タイムラインに他のユーザー宛ての投稿も表示したるで。"
the user notes in the timeline when you turn it on. "
autoAcceptFollowed: "フォローしとるユーザーからのフォローリクエストを勝手に許可しとく" autoAcceptFollowed: "フォローしとるユーザーからのフォローリクエストを勝手に許可しとく"
addAccount: "アカウントを追加" addAccount: "アカウントを追加"
loginFailed: "ログインに失敗してしもうた…" loginFailed: "ログインに失敗してしもうた…"
@ -241,9 +238,8 @@ resetAreYouSure: "リセットしてええん?"
saved: "保存したで!" saved: "保存したで!"
messaging: "チャット" messaging: "チャット"
upload: "アップロード" upload: "アップロード"
keepOriginalUploading: "Retain the original image. " keepOriginalUploading: "画質をそのまんまにする"
keepOriginalUploadingDescription: "When uploading the clip, the original version will keepOriginalUploadingDescription: "オリジナルの画像をそのまんまアップロードするで。オフにすると、ファイルサイズを削減したWeb公開用画像を生成したるで。"
be retained. Turning it of then uploading will produce images for public use. "
fromDrive: "ドライブから" fromDrive: "ドライブから"
fromUrl: "URLから" fromUrl: "URLから"
uploadFromUrl: "URLアップロード" uploadFromUrl: "URLアップロード"
@ -582,7 +578,7 @@ disableAll: "全部使えへんようにする"
tokenRequested: "アカウントへのアクセス許可" tokenRequested: "アカウントへのアクセス許可"
pluginTokenRequestedDescription: "このプラグインはここで設定した権限を使えるようになるで。" pluginTokenRequestedDescription: "このプラグインはここで設定した権限を使えるようになるで。"
notificationType: "通知の種類" notificationType: "通知の種類"
edit: "編集" edit: "投稿をいじる"
emailServer: "メールサーバー" emailServer: "メールサーバー"
enableEmail: "メール配信を受け取る" enableEmail: "メール配信を受け取る"
emailConfigInfo: "メールアドレスの確認とかパスワードリセットの時に使うで" emailConfigInfo: "メールアドレスの確認とかパスワードリセットの時に使うで"
@ -739,7 +735,7 @@ showingPastTimeline: "過去のタイムラインを表示してるで"
clear: "クリア" clear: "クリア"
markAllAsRead: "もうみな読んでもうたわ" markAllAsRead: "もうみな読んでもうたわ"
goBack: "戻る" goBack: "戻る"
unlikeConfirm: "いいね解除するんか" unlikeConfirm: "ええやんを解除するんけ"
fullView: "フルビュー" fullView: "フルビュー"
quitFullView: "フルビュー解除" quitFullView: "フルビュー解除"
addDescription: "説明を追加するで" addDescription: "説明を追加するで"
@ -814,7 +810,7 @@ searchByGoogle: "探す"
indefinitely: "無期限" indefinitely: "無期限"
file: "ファイル" file: "ファイル"
requireAdminForView: "これを見るには管理者アカウントでログインしとらなあかんで。" requireAdminForView: "これを見るには管理者アカウントでログインしとらなあかんで。"
isSystemAccount: "システムが自動で作成・管理しとるアカウントやで。" isSystemAccount: "システムが自動で作成・管理しとるアカウントやで。モデレーション・編集・削除するとサーバーの動作が不正になる可能性があるので、操作せんといてください。"
typeToConfirm: "この操作をやるんなら {x} と入力してなー" typeToConfirm: "この操作をやるんなら {x} と入力してなー"
deleteAccount: "アカウント削除するで" deleteAccount: "アカウント削除するで"
document: "ドキュメント" document: "ドキュメント"
@ -857,7 +853,9 @@ _ffVisibility:
_ad: _ad:
back: "戻る" back: "戻る"
_gallery: _gallery:
unlike: "良くないわ" unlike: "やっぱよくないわ"
like: ええやん!
liked: ええやんと思った投稿
_email: _email:
_follow: _follow:
title: "フォローされたで" title: "フォローされたで"
@ -1073,9 +1071,16 @@ _poll:
votesCount: "{n}票" votesCount: "{n}票"
vote: "投票する" vote: "投票する"
_visibility: _visibility:
publicDescription: "みんなに公開" publicDescription: "うちの投稿、みんな見てや"
home: "ホーム" home: "ホームタイムラインのみ"
followers: "フォロワー" followers: "フォロワーのみ"
localOnly: ローカルのみ
followersDescription: フォロワーと返信相手だけに見せたる
specified: ダイレクト
localOnlyDescription: 他のサーバーには見せとうない
specifiedDescription: 指定した相手だけに見せたる
public: 公開
homeDescription: ローカルTLやグローバルTLには流さへん
_profile: _profile:
name: "名前" name: "名前"
username: "ユーザー名" username: "ユーザー名"
@ -1125,7 +1130,7 @@ _pages:
pageSetting: "ページ設定" pageSetting: "ページ設定"
viewPage: "ページを見る" viewPage: "ページを見る"
like: "ええやん" like: "ええやん"
unlike: "良くないわ" unlike: "やっぱ気に入らん"
liked: "ええと思ったページ" liked: "ええと思ったページ"
contents: "コンテンツ" contents: "コンテンツ"
summary: "ページの要約" summary: "ページの要約"
@ -1443,6 +1448,8 @@ _postForm:
c: なに考えとりまっか? c: なに考えとりまっか?
d: なんや言いたいんちゃいますか? d: なんや言いたいんちゃいますか?
f: あんさん書くんを待っとるんどす... f: あんさん書くんを待っとるんどす...
a: いまなにしとん?
flagSpeakAsCat: 猫弁で話す flagSpeakAsCat: 猫弁で話す
flagSpeakAsCatDescription: 猫モードが有効の場合にオンにすると、ワレの投稿の「な」を「にゃ」に変換るで。 flagSpeakAsCatDescription: オンにすると、ワレの投稿の「な」を「にゃ」に変換したるで。
welcomeBackWithName: おおきに、{name}はん welcomeBackWithName: おおきに、{name}はん
migration: アカウントの引っ越し

View File

@ -9,7 +9,7 @@ password: "비밀번호"
forgotPassword: "비밀번호 재설정" forgotPassword: "비밀번호 재설정"
fetchingAsApObject: "연합에서 조회 중" fetchingAsApObject: "연합에서 조회 중"
ok: "OK" ok: "OK"
gotIt: "알겠어요" gotIt: "알겠어요!"
cancel: "취소" cancel: "취소"
enterUsername: "유저명 입력" enterUsername: "유저명 입력"
renotedBy: "{user}님이 부스트" renotedBy: "{user}님이 부스트"
@ -22,21 +22,21 @@ otherSettings: "기타 설정"
openInWindow: "창으로 열기" openInWindow: "창으로 열기"
profile: "프로필" profile: "프로필"
timeline: "타임라인" timeline: "타임라인"
noAccountDescription: "자기소개가 없습니다" noAccountDescription: "자기소개가 없습니다."
login: "로그인" login: "로그인"
loggingIn: "로그인 중" loggingIn: "로그인 중"
logout: "로그아웃" logout: "로그아웃"
signup: "회원 가입" signup: "회원 가입"
uploading: "업로드 중" uploading: "업로드 중..."
save: "저장" save: "저장"
users: "유저" users: "유저"
addUser: "유저 추가" addUser: "유저 추가"
favorite: "즐겨찾기" favorite: "즐겨찾기"
favorites: "즐겨찾기" favorites: "즐겨찾기"
unfavorite: "즐겨찾기에서 제거" unfavorite: "즐겨찾기에서 제거"
favorited: "즐겨찾기에 등록했습니다" favorited: "즐겨찾기에 등록했습니다."
alreadyFavorited: "이미 즐겨찾기에 등록되어 있습니다" alreadyFavorited: "이미 즐겨찾기에 등록되어 있습니다."
cantFavorite: "즐겨찾기에 등록하지 못했습니다" cantFavorite: "즐겨찾기에 등록하지 못했습니다."
pin: "프로필에 고정" pin: "프로필에 고정"
unpin: "프로필에서 고정 해제" unpin: "프로필에서 고정 해제"
copyContent: "내용 복사" copyContent: "내용 복사"
@ -96,7 +96,7 @@ followRequestPending: "팔로우 허가 대기중"
enterEmoji: "이모지 입력" enterEmoji: "이모지 입력"
renote: "부스트" renote: "부스트"
unrenote: "부스트 취소" unrenote: "부스트 취소"
renoted: "부스트 하였습니다" renoted: "부스트 하였습니다."
cantRenote: "이 게시물은 부스트할 수 없습니다." cantRenote: "이 게시물은 부스트할 수 없습니다."
cantReRenote: "부스트를 부스트할 수 없습니다." cantReRenote: "부스트를 부스트할 수 없습니다."
quote: "인용" quote: "인용"
@ -143,7 +143,7 @@ flagAsBot: "나는 봇입니다"
flagAsBotDescription: "이 계정을 자동화된 수단으로 운용할 경우에 활성화해 주세요. 이 플래그를 활성화하면, 다른 봇이 이를 참고하여 flagAsBotDescription: "이 계정을 자동화된 수단으로 운용할 경우에 활성화해 주세요. 이 플래그를 활성화하면, 다른 봇이 이를 참고하여
봇 끼리의 무한 연쇄 반응을 회피하거나, 이 계정의 시스템 상에서의 취급이 Bot 운영에 최적화되는 등의 변화가 생깁니다." 봇 끼리의 무한 연쇄 반응을 회피하거나, 이 계정의 시스템 상에서의 취급이 Bot 운영에 최적화되는 등의 변화가 생깁니다."
flagAsCat: "나는 고양이다냥" flagAsCat: "나는 고양이다냥"
flagAsCatDescription: "이 계정이 고양이라면 활성화 해주세요." flagAsCatDescription: "고양이귀를 쓰고 냥냥거려요!"
flagShowTimelineReplies: "타임라인에 게시물의 답글을 표시하기" flagShowTimelineReplies: "타임라인에 게시물의 답글을 표시하기"
flagShowTimelineRepliesDescription: "이 설정을 활성화하면 타임라인에 다른 유저 간의 답글을 표시합니다." flagShowTimelineRepliesDescription: "이 설정을 활성화하면 타임라인에 다른 유저 간의 답글을 표시합니다."
autoAcceptFollowed: "팔로우 중인 유저로부터의 팔로우 요청을 자동 수락" autoAcceptFollowed: "팔로우 중인 유저로부터의 팔로우 요청을 자동 수락"
@ -202,7 +202,7 @@ blockedUsers: "차단한 유저"
noUsers: "아무도 없습니다" noUsers: "아무도 없습니다"
editProfile: "프로필 수정" editProfile: "프로필 수정"
noteDeleteConfirm: "이 게시물을 삭제하시겠습니까?" noteDeleteConfirm: "이 게시물을 삭제하시겠습니까?"
pinLimitExceeded: "더 이상 고정할 수 없습니다." pinLimitExceeded: "더 이상 고정할 수 없습니다"
intro: "Firefish의 설치가 완료되었습니다! 관리자 계정을 생성해주세요." intro: "Firefish의 설치가 완료되었습니다! 관리자 계정을 생성해주세요."
done: "완료" done: "완료"
processing: "처리중" processing: "처리중"
@ -361,7 +361,7 @@ name: "이름"
antennaSource: "받을 소스" antennaSource: "받을 소스"
antennaKeywords: "받을 키워드" antennaKeywords: "받을 키워드"
antennaExcludeKeywords: "제외할 키워드" antennaExcludeKeywords: "제외할 키워드"
antennaKeywordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다" antennaKeywordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다."
notifyAntenna: "새로운 글을 알림" notifyAntenna: "새로운 글을 알림"
withFileAntenna: "파일이 첨부된 게시물만" withFileAntenna: "파일이 첨부된 게시물만"
enableServiceworker: "ServiceWorker 사용" enableServiceworker: "ServiceWorker 사용"
@ -437,10 +437,10 @@ onlyOneFileCanBeAttached: "메시지에 첨부할 수 있는 파일은 하나까
signinRequired: "로그인 해주세요" signinRequired: "로그인 해주세요"
invitations: "초대" invitations: "초대"
invitationCode: "초대 코드" invitationCode: "초대 코드"
checking: "확인하는 중입니다" checking: "확인하는 중입니다..."
available: "사용 가능합니다" available: "사용 가능합니다"
unavailable: "사용할 수 없습니다" unavailable: "사용할 수 없습니다"
usernameInvalidFormat: "a~z, A~Z, 0-9, _를 사용할 수 있습니다" usernameInvalidFormat: "a~z, A~Z, 0-9, _를 사용할 수 있습니다."
tooShort: "너무 짧습니다" tooShort: "너무 짧습니다"
tooLong: "너무 깁니다" tooLong: "너무 깁니다"
weakPassword: "약한 비밀번호" weakPassword: "약한 비밀번호"
@ -463,7 +463,7 @@ joinOrCreateGroup: "다른 그룹의 초대를 받거나, 직접 새 그룹을
noHistory: "기록이 없습니다" noHistory: "기록이 없습니다"
signinHistory: "로그인 기록" signinHistory: "로그인 기록"
disableAnimatedMfm: "움직임이 있는 MFM을 비활성화" disableAnimatedMfm: "움직임이 있는 MFM을 비활성화"
doing: "잠시만요" doing: "기다려 주세요..."
category: "카테고리" category: "카테고리"
tags: "태그" tags: "태그"
docSource: "이 문서의 소스" docSource: "이 문서의 소스"
@ -491,7 +491,7 @@ objectStorage: "오브젝트 스토리지"
useObjectStorage: "오브젝트 스토리지를 사용" useObjectStorage: "오브젝트 스토리지를 사용"
objectStorageBaseUrl: "Base URL" objectStorageBaseUrl: "Base URL"
objectStorageBaseUrlDesc: "오브젝트 (미디어) 참조 URL 을 만들 때 사용되는 URL입니다. CDN 또는 프록시를 사용하는 objectStorageBaseUrlDesc: "오브젝트 (미디어) 참조 URL 을 만들 때 사용되는 URL입니다. CDN 또는 프록시를 사용하는
경우 그 URL을 지정하고, 그 외의 경우 사용할 서비스의 가이드에 따라 공개적으로 액세스 할 수 있는 주소를 지정해 주세요. 예를 들어, AWS 경우 그 URL을 지정하고, 그 외의 경우 사용할 서비스의 가이드에 따라 공개적으로 액세스 할 수 있는 주소를 지정해 주세요.\n예를 들어, AWS
S3의 경우 'https://<bucket>.s3.amazonaws.com', GCS등의 경우 'https://storage.googleapis.com/<bucket>' S3의 경우 'https://<bucket>.s3.amazonaws.com', GCS등의 경우 'https://storage.googleapis.com/<bucket>'
와 같이 지정합니다." 와 같이 지정합니다."
objectStorageBucket: "Bucket" objectStorageBucket: "Bucket"
@ -596,7 +596,7 @@ notificationType: "알림 유형"
edit: "편집" edit: "편집"
emailServer: "메일 서버" emailServer: "메일 서버"
enableEmail: "이메일 송신 기능 활성화" enableEmail: "이메일 송신 기능 활성화"
emailConfigInfo: "가입 시 메일 주소 확인이나 비밀번호 초기화 시에 사용합니다." emailConfigInfo: "가입 시 메일 주소 확인이나 비밀번호 초기화 시에 사용합니다"
email: "이메일" email: "이메일"
emailAddress: "메일 주소" emailAddress: "메일 주소"
smtpConfig: "SMTP 서버 설정" smtpConfig: "SMTP 서버 설정"
@ -604,9 +604,9 @@ smtpHost: "호스트"
smtpPort: "포트" smtpPort: "포트"
smtpUser: "유저명" smtpUser: "유저명"
smtpPass: "비밀번호" smtpPass: "비밀번호"
emptyToDisableSmtpAuth: "SMTP 인증을 사용하지 않으려면 공란으로 비워둡니다." emptyToDisableSmtpAuth: "SMTP 인증을 사용하지 않으려면 공란으로 비워둡니다"
smtpSecure: "SMTP 연결에 Implicit SSL/TTS 사용" smtpSecure: "SMTP 연결에 Implicit SSL/TTS 사용"
smtpSecureInfo: "STARTTLS 사용 시에는 해제합니다." smtpSecureInfo: "STARTTLS 사용 시에는 해제합니다"
testEmail: "이메일 전송 테스트" testEmail: "이메일 전송 테스트"
wordMute: "단어 뮤트" wordMute: "단어 뮤트"
regexpError: "정규 표현식 오류" regexpError: "정규 표현식 오류"
@ -683,14 +683,14 @@ no: "아니오"
driveFilesCount: "드라이브 파일 개수" driveFilesCount: "드라이브 파일 개수"
driveUsage: "드라이브 사용량" driveUsage: "드라이브 사용량"
noCrawle: "검색엔진의 인덱싱 거부" noCrawle: "검색엔진의 인덱싱 거부"
noCrawleDescription: "검색엔진에 사용자 페이지, 게시물, 페이지 등의 콘텐츠를 인덱싱되지 않게 합니다." noCrawleDescription: "검색엔진이 나의 컨텐츠를 인덱싱하지 않게 합니다."
lockedAccountInfo: "팔로우를 승인으로 승인받더라도 게시물의 공개 범위를 '팔로워'로 하지 않는 한 누구나 당신의 글을 볼 수 있습니다." lockedAccountInfo: "팔로우를 승인으로 승인받더라도 게시물의 공개 범위를 '팔로워'로 하지 않는 한 누구나 당신의 글을 볼 수 있습니다."
alwaysMarkSensitive: "미디어를 항상 열람 주의로 설정" alwaysMarkSensitive: "미디어를 항상 열람 주의로 설정"
loadRawImages: "첨부한 이미지의 썸네일을 원본화질로 표시" loadRawImages: "첨부한 이미지의 썸네일을 원본화질로 표시"
disableShowingAnimatedImages: "움직이는 이미지를 자동으로 재생하지 않음" disableShowingAnimatedImages: "움직이는 이미지를 자동으로 재생하지 않음"
verificationEmailSent: "확인 메일을 발송하였습니다. 설정을 완료하려면 메일에 첨부된 링크를 확인해 주세요." verificationEmailSent: "확인 메일을 발송하였습니다. 설정을 완료하려면 메일에 첨부된 링크를 확인해 주세요."
notSet: "설정되지 않음" notSet: "설정되지 않음"
emailVerified: "메일 주소가 확인되었습니다." emailVerified: "메일 주소가 확인되었습니다"
noteFavoritesCount: "즐겨찾기한 게시물 수" noteFavoritesCount: "즐겨찾기한 게시물 수"
pageLikesCount: "좋아요 한 Page 수" pageLikesCount: "좋아요 한 Page 수"
pageLikedCount: "Page에 받은 좋아요 수" pageLikedCount: "Page에 받은 좋아요 수"
@ -954,7 +954,7 @@ _forgotPassword:
_gallery: _gallery:
my: "내 갤러리" my: "내 갤러리"
liked: "좋아요 한 갤러리" liked: "좋아요 한 갤러리"
like: "좋아요!" like: "좋아요"
unlike: "좋아요 취소" unlike: "좋아요 취소"
_email: _email:
_follow: _follow:
@ -982,7 +982,7 @@ _preferencesBackups:
createdAt: "생성 날짜: {date} {time}" createdAt: "생성 날짜: {date} {time}"
updatedAt: "갱신 날짜: {date} {time}" updatedAt: "갱신 날짜: {date} {time}"
cannotLoad: "가져오기에 실패했습니다" cannotLoad: "가져오기에 실패했습니다"
invalidFile: "파일 형식이 올바르지 않습니다." invalidFile: "파일 형식이 올바르지 않습니다"
_registry: _registry:
scope: "범위" scope: "범위"
key: "키" key: "키"
@ -1033,9 +1033,9 @@ _mfm:
blockCode: "코드(블록)" blockCode: "코드(블록)"
blockCodeDescription: "여러 행의 코드를 문법 강조를 적용하여 블록으로 표시합니다." blockCodeDescription: "여러 행의 코드를 문법 강조를 적용하여 블록으로 표시합니다."
inlineMath: "수식(인라인)" inlineMath: "수식(인라인)"
inlineMathDescription: "수식(KaTeX)를 인라인으로 보이게 합니다." inlineMathDescription: "수식(KaTeX)를 인라인으로 보이게 합니다"
blockMath: "수식(블록)" blockMath: "수식(블록)"
blockMathDescription: "수식(KaTeX)을 블록으로 보이게 합니다." blockMathDescription: "수식(KaTeX)을 블록으로 보이게 합니다"
quote: "인용" quote: "인용"
quoteDescription: "내용을 인용문으로 표시합니다." quoteDescription: "내용을 인용문으로 표시합니다."
emoji: "커스텀 이모지" emoji: "커스텀 이모지"
@ -1163,7 +1163,7 @@ _theme:
darken: "어두움" darken: "어두움"
lighten: "밝음" lighten: "밝음"
inputConstantName: "상수 이름을 입력하세요" inputConstantName: "상수 이름을 입력하세요"
importInfo: "여기에 테마 코드를 붙여 넣어 에디터로 불러올 수 있습니다." importInfo: "여기에 테마 코드를 붙여 넣어 에디터로 불러올 수 있습니다"
deleteConstantConfirm: "상수 {const}를 삭제하시겠습니까?" deleteConstantConfirm: "상수 {const}를 삭제하시겠습니까?"
keys: keys:
accent: "강조 색상" accent: "강조 색상"
@ -1294,7 +1294,7 @@ _permissions:
_auth: _auth:
shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?" shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?"
shareAccessAsk: "이 애플리케이션이 계정에 접근하는 것을 허용하시겠습니까?" shareAccessAsk: "이 애플리케이션이 계정에 접근하는 것을 허용하시겠습니까?"
permissionAsk: "이 앱은 다음의 권한을 요청합니다" permissionAsk: "이 앱은 다음의 권한을 요청합니다:"
pleaseGoBack: "앱으로 돌아가서 시도해 주세요" pleaseGoBack: "앱으로 돌아가서 시도해 주세요"
callback: "앱으로 돌아갑니다" callback: "앱으로 돌아갑니다"
denied: "접근이 거부되었습니다" denied: "접근이 거부되었습니다"
@ -1793,7 +1793,7 @@ _deck:
deleteProfile: "작업 공간 삭제" deleteProfile: "작업 공간 삭제"
introduction: "칼럼을 조합해서 나만의 인터페이스를 구성해 보아요!" introduction: "칼럼을 조합해서 나만의 인터페이스를 구성해 보아요!"
introduction2: "나중에라도 화면 우측의 + 버튼을 눌러 새 칼럼을 추가할 수 있습니다." introduction2: "나중에라도 화면 우측의 + 버튼을 눌러 새 칼럼을 추가할 수 있습니다."
widgetsIntroduction: "칼럼 메뉴의 \"위젯 편집\"에서 위젯을 추가해 주세요" widgetsIntroduction: "칼럼 메뉴의 \"위젯 편집\"에서 위젯을 추가해 주세요."
_columns: _columns:
main: "메인" main: "메인"
widgets: "위젯" widgets: "위젯"
@ -2016,3 +2016,7 @@ deletePasskeys: 보안 키 삭제
deletePasskeysConfirm: 이 계정에서 모든 보안 키를 영구히 삭제합니다. 계속하시겠습니까? deletePasskeysConfirm: 이 계정에서 모든 보안 키를 영구히 삭제합니다. 계속하시겠습니까?
inputNotMatch: 입력이 일치하지 않습니다 inputNotMatch: 입력이 일치하지 않습니다
addRe: 열람주의로 표시된 게시물의 답장에 're:' 붙이기 addRe: 열람주의로 표시된 게시물의 답장에 're:' 붙이기
detectPostLanguage: 외국어로 인식된 게시물에 번역 버튼을 표시
indexableDescription: Firefish 검색에서 나의 공개 게시물에 대한 검색을 허용합니다
indexable: 인덱스 허용
languageForTranslation: 게시물 번역에 사용할 언어

View File

@ -80,17 +80,17 @@ showMore: Vis mer
followRequestAccepted: Følgeforespørsel godtatt followRequestAccepted: Følgeforespørsel godtatt
import: Importer import: Importer
export: Eksporter export: Eksporter
logout: Logger ut logout: Logg ut
removeReaction: Fjern dine reaksjoner removeReaction: Fjern dine reaksjoner
renotedBy: Fremhevet av {user} renotedBy: Delt av {user}
deleteAndEditConfirm: Er du sikker på at du vil slette og redigere denne posten? Du deleteAndEditConfirm: Er du sikker på at du vil slette og redigere denne posten? Du
vil miste alle reaksjoner, fremhevinger og svar til den. vil miste alle reaksjoner, delinger og svar til den.
pageLoadError: En feil oppsto ved lasting av siden. pageLoadError: En feil oppsto ved lasting av siden.
privacy: Personvern privacy: Personvern
enterEmoji: Legg inn emoji enterEmoji: Legg inn emoji
renoted: Fremhevet. renoted: Delt.
cantRenote: Denne posten kan ikke fremheves. cantRenote: Denne posten kan ikke deles.
cantReRenote: En fremheving kan ikke fremheves. cantReRenote: En deling kan ikke deles.
quote: Sitér quote: Sitér
pinned: Fest til profil pinned: Fest til profil
clickToShow: Klikk for å vise clickToShow: Klikk for å vise
@ -107,7 +107,7 @@ reaction: Reaksjoner
driveFileDeleteConfirm: Er du sikker på at du vil slette filen «{name}»? Den vil fjernes driveFileDeleteConfirm: Er du sikker på at du vil slette filen «{name}»? Den vil fjernes
fra alle poster den er vedlagt i. fra alle poster den er vedlagt i.
defaultNoteVisibility: Standard synlighet defaultNoteVisibility: Standard synlighet
unrenote: Fjern fremmheving unrenote: Trekk tilbake deling
unpin: Løsne fra profilen unpin: Løsne fra profilen
youGotNewFollower: følger deg youGotNewFollower: følger deg
mention: Omtale mention: Omtale
@ -149,7 +149,7 @@ images: Bilder
birthday: Fødselsdag birthday: Fødselsdag
yearsOld: '{age} år gammel' yearsOld: '{age} år gammel'
renameFolder: Gi katalogen nytt navn renameFolder: Gi katalogen nytt navn
remoteUserCaution: Informasjon fra eksterne brukere kan være ufullstendig. remoteUserCaution: Informasjon om eksterne brukere kan være ufullstendig.
activity: Aktivitet activity: Aktivitet
drive: Disk drive: Disk
renameFile: Omdøp fil renameFile: Omdøp fil
@ -167,7 +167,7 @@ popularUsers: Populære brukere
moderator: Moderator moderator: Moderator
groupName: Gruppenavn groupName: Gruppenavn
transfer: Overfør transfer: Overfør
preferencesBackups: Foretrukne sikkerhetskopier preferencesBackups: Sikkerhetskopi av innstillinger
edit: Rediger edit: Rediger
emailServer: Eposttjener emailServer: Eposttjener
testEmail: Test epost-utsending testEmail: Test epost-utsending
@ -177,8 +177,8 @@ useGlobalSettingDesc: Hvis dette er slått på vil varslingsinnstillingene til k
bli brukt. Om den er slått av kan du bruke individuell konfigurasjon. bli brukt. Om den er slått av kan du bruke individuell konfigurasjon.
attachCancel: Slett vedegg attachCancel: Slett vedegg
markAsSensitive: Merk som sensitivt innhold markAsSensitive: Merk som sensitivt innhold
renoteMute: Stum fremhevinger renoteMute: Stum delinger
renoteUnmute: Vis boosts fra bruker renoteUnmute: Vis delinger
addEmoji: Legg til addEmoji: Legg til
settingGuide: Foreslåtte innstillinger settingGuide: Foreslåtte innstillinger
cacheRemoteFilesDescription: Når denne innstillingen er avslått vil eksterne filer cacheRemoteFilesDescription: Når denne innstillingen er avslått vil eksterne filer
@ -242,7 +242,7 @@ antennaKeywordsDescription: Skill ordene med mellomrom for logisk "OG" og med li
for logisk "ELLER". for logisk "ELLER".
withReplies: Ta med svar withReplies: Ta med svar
connectedTo: Følgende konto(er) er sammenkoblet connectedTo: Følgende konto(er) er sammenkoblet
withFiles: Inkluder filer withFiles: Med filer
lastUsed: Sist brukt lastUsed: Sist brukt
unregister: Avregistrer unregister: Avregistrer
passwordLessLogin: Passordløs innlogging passwordLessLogin: Passordløs innlogging
@ -512,7 +512,7 @@ useFullReactionPicker: Bruk reaksjonsvelger i full størrelse
width: Bredde width: Bredde
regexpErrorDescription: 'En feil oppsto under det regulære uttrykket på linje {line} regexpErrorDescription: 'En feil oppsto under det regulære uttrykket på linje {line}
av stumming av {tab}:' av stumming av {tab}:'
userSaysSomethingReasonRenote: '{name} fremhevet en post som inneholdt {reason}' userSaysSomethingReasonRenote: '{name} delte en post som inneholdt {reason}'
userSaysSomethingReasonQuote: '{name} siterte en post som inneholdt {reason}' userSaysSomethingReasonQuote: '{name} siterte en post som inneholdt {reason}'
userSaysSomething: '{name} sa noe' userSaysSomething: '{name} sa noe'
metrics: Metrikker metrics: Metrikker
@ -557,7 +557,7 @@ home: Hjem
emptyDrive: Disken din er tom emptyDrive: Disken din er tom
unableToDelete: Klarte ikke å slette unableToDelete: Klarte ikke å slette
inputNewFileName: Oppgi nytt filnavn inputNewFileName: Oppgi nytt filnavn
hasChildFilesOrFolders: Katalogen er ikke tom og kan derfor ikke slettes. hasChildFilesOrFolders: Katalogen kan ikke slettes fordi den ikke er tom.
avoidMultiCaptchaConfirm: Å bruke flere Captcha-systemer kan forårsake uønskede krysseffekter avoidMultiCaptchaConfirm: Å bruke flere Captcha-systemer kan forårsake uønskede krysseffekter
mellom dem. Ønsker å du slå av det andre aktive CAPTCHA-systemet? Om du ønsker å mellom dem. Ønsker å du slå av det andre aktive CAPTCHA-systemet? Om du ønsker å
ha begge på, trykk "Avbryt". ha begge på, trykk "Avbryt".
@ -619,7 +619,7 @@ keepOriginalUploadingDescription: Lagrer det opprinnelige opplastedet bildet sli
ved opplasting. ved opplasting.
startMessaging: Start en ny chat startMessaging: Start en ny chat
group: Gruppe group: Gruppe
renote: Fremhev renote: Del
banner: Topp-bilde banner: Topp-bilde
nsfw: Sensitivt innhold nsfw: Sensitivt innhold
bannerUrl: URL til fane-bilde bannerUrl: URL til fane-bilde
@ -776,6 +776,88 @@ _mfm:
play: Spill animert markeringsspråk (MFM) play: Spill animert markeringsspråk (MFM)
intro: MFM er et markeringsspråk som burkes av Misskey, Firefish, Akkoma og andre. intro: MFM er et markeringsspråk som burkes av Misskey, Firefish, Akkoma og andre.
Her kan du se en liste over tilgjengelig MFM-syntaks. Her kan du se en liste over tilgjengelig MFM-syntaks.
sparkle: Gnistrende
dummy: Firefish utvider Allheimen
tadaDescription: Gir innholdet en "Tada!"-lignende animasjon.
emoji: Egendefinert emoji
flip: Speil
jelly: Animasjon (gelé)
rotate: Rotér
foregroundDescription: Endre forgrunnsfargen på tekst.
quoteDescription: Viser innhold som et sitat.
link: Lenke
search: Søk
scale: Skalér
centerDescription: Viser innhold sentrert.
advancedDescription: Hvis avslått vil bare enkel tekstmarkering være tillatt med
mindre du spiller av animert tekstmarkering
fontDescription: Setter skriftsnittet for innholdet.
inlineCode: Kode (inline)
x2Description: Viser innholdet større.
backgroundDescription: Endre bakgrunnsfargen til tekst.
blur: Uklar
rainbowDescription: Viser innholdet i regnbuefarger.
center: Sentrert
mentionDescription: Du kan spesifisere en bruker ved å skrive krøllalfa foran brukernavnet.
emojiDescription: Ved å omslutte navnet på en egendefinert emoji med kolon vil de
vises i teksten.
x3: Veldig stor
rainbow: Regnbue
mention: Nevn
shakeDescription: Gir innholdet en ristende animasjon.
advanced: Avansert MFM (tekstmarkering)
hashtag: Emneknagg
urlDescription: URL-er kan vises.
blockMath: Matematikk (blokk)
smallDescription: Viser innhold lite og tynt.
x2: Stor
font: Skriftsnitt
crop: Beskjær
fadeDescription: Fader innholdet inn og ut.
x4Description: Viser innholdet større enn større enn stor.
sparkleDescription: Gir innholdet en gnist-effekt.
scaleDescription: Skalér innholdet.
inlineMath: Matematikk (inline)
shake: Animasjon (risting)
tada: Animasjon (Tada)
background: Bakgrunnsfarge
twitchDescription: Gir innholdet en sterk animert rykning.
blockCodeDescription: Viser syntaksmarkering for programkode over flere linjer i
en blokk.
position: Posisjon
hashtagDescription: Du kan spesifisere en emneknakk ved å bruke nummer-tegn (#)
og tekst.
small: Liten
positionDescription: Flytter innholdet en angitt distanse.
cropDescription: Beskjær innhold.
x4: Utrolig stor
jellyDescription: Gir innholdet med en gelé-lignende animasjon.
flipDescription: Speiler innhold horisontalt eller vertikalt.
twitch: Animasjon (rykning)
plainDescription: Slår av effenten av all formatering innenfor denne markeringen.
url: URL
jumpDescription: Viser innholdet med animert hopping.
blockMathDescription: Viser matematiske formler (KaTeX) i en blokk
inlineMathDescription: Viser matematiske formler (KaTeX) inline
linkDescription: Spesifikke deler av tekst kan vises som en URL.
blurDescription: Viser innholdet uklart. Det vil bli vist klart når muspekeren er
over.
foreground: Forgrunnsfarge
quote: Sitat
bounceDescription: Viser innholdet med en sprettende animasjon.
bold: Fet
inlineCodeDescription: Viser inline syntaksmarkering for kildekode.
jump: Animasjon (hopp)
fade: Fade
spin: Animasjon (spinn)
searchDescription: Viser en søkeboks med forhåndsutfylt tekst.
spinDescription: Gir innholdet en spinnende animasjon.
blockCode: Kodeblokk
boldDescription: Fremhever tekst ved å gjøre den fyldigere.
bounce: Animasjon (sprett)
rotateDescription: Vrir innholdet i en gitt vinkel.
plain: Enkel
x3Description: Viser innholdet enda større.
reactionPickerSkinTone: Foretrukket hudfarge i emojier reactionPickerSkinTone: Foretrukket hudfarge i emojier
switchUi: Visningsoppsett switchUi: Visningsoppsett
usageAmount: Bruk usageAmount: Bruk
@ -840,6 +922,14 @@ saveAs: Lagre som...
swipeOnMobile: Tillat sveiping mellom sider swipeOnMobile: Tillat sveiping mellom sider
_accountDelete: _accountDelete:
inProgress: Sletting pågår inProgress: Sletting pågår
requestAccountDelete: Be kom sletting av konto
started: Sletting har startet.
accountDelete: Slett konto
mayTakeTime: Å slette en konto er en ressurskrevende prosess. Det kan ta litt tid
å fullføre den, avhengig av hvor mye du har postet og hvor mange filer du har
lastet opp.
sendEmail: Når kontoen er ferdig slettet vil du få en epost til adressen som er
registrert for kontoen.
remote: Ekstern remote: Ekstern
total: Total total: Total
registry: Register registry: Register
@ -946,8 +1036,7 @@ reporteeOrigin: Kilden til den som rapporteres
accountInfo: Kontoinformasjon accountInfo: Kontoinformasjon
driveUsage: Brukt diskplass driveUsage: Brukt diskplass
noCrawle: Stopp robot-indeksering noCrawle: Stopp robot-indeksering
noCrawleDescription: Be søkemotorer om å ikke indeksere din profil, poster, Sider noCrawleDescription: Be eksterne søkemotorer om å ikke indeksere innholdet ditt.
etc.
narrow: Smal narrow: Smal
reloadToApplySetting: Denne innstillingen aktiveres ikke før du laster siden på nytt. reloadToApplySetting: Denne innstillingen aktiveres ikke før du laster siden på nytt.
Vil du gjøre det nå? Vil du gjøre det nå?
@ -1120,3 +1209,724 @@ preventAiLearningDescription: Ber tredjeparts AI-språkmodeller om å ikke bruke
du laster opp, sliks om poster og bilder. du laster opp, sliks om poster og bilder.
enableCustomKaTeXMacro: Slå på egne KaTeX-makroer enableCustomKaTeXMacro: Slå på egne KaTeX-makroer
showPopup: Varsle brukere med oppsprettsvindu showPopup: Varsle brukere med oppsprettsvindu
_postForm:
_placeholders:
a: Hva skjer?
d: Hva vil du si?
f: Venter på at du skriver...
e: Start skrivingen...
b: Hva skjer rundt deg?
c: Hva tenker du på?
channelPlaceholder: Post til kanal...
quotePlaceholder: Siter denne posten...
replyPlaceholder: Svar på denne posten...
_notification:
_types:
pollEnded: Ferdige avstemninger
all: Alle
renote: Delinger
quote: Sitater
reaction: Reaksjoner
receiveFollowRequest: Mottatte følgerforespørsler
groupInvited: Gruppeinvitasjoner
app: Varsler fra lenkede apper
followRequestAccepted: Aksepterte følgerforespørsler
mention: Noen nevner deg
reply: Svar
follow: Nye følgere
pollVote: Stemmer i avstemninger
yourFollowRequestAccepted: Følgerforespøreslen din er godkjent
_actions:
renote: Delinger
followBack: følger deg også
reply: Svar
youGotQuote: '{name} siterte deg'
fileUploaded: Lastet opp fil
youGotMessagingMessageFromUser: '{name} har sendt deg en chatmelding'
emptyPushNotificationMessage: Pushvarsler har blitt oppdatert
pollEnded: Resultatene fra en avstemning er tilgjengelige
youGotReply: '{name} svarte deg'
youGotMessagingMessageFromGroup: En chatmelding er sendt til gruppen "{name}"
youWereInvitedToGroup: '{userName} har invitert deg til en gruppe'
youReceivedFollowRequest: Du har fått en følgerforespørsel
youRenoted: Deling fra {name}
reacted: reagerte på posten din
voted: stemte i avstemningen din
renoted: delte posten din
youGotPoll: '{name} stemte i avstemningen din'
youGotMention: '{name} nevnte deg'
youWereFollowed: følger deg
_tutorial:
step3_2: "Tidslinjene Hjem og Sosialt er basert på de du følger, så prøv nå følge
noen kontoer for å komme i gang.\nKlikk pluss-sirkelen oppe til høyre på en profil
for å følge den."
step6_1: Så, hva er dette stedet?
step5_7: Tidslinjen Global {icon} viser poster fra alle tilkoblede tjenere.
step4_1: La oss få deg ut der.
step5_1: Tidslinjer! Tidslinjer overalt!
step1_2: La oss gjøre deg klar. Du vil være oppe og gå på et øyeblikk!
step1_1: Velkommen!
title: Hvordan bruke Firefish
step5_6: Tidslinjen Forslag {icon} er hvor du kan se poster fra tjenere administratorene
foreslår.
step5_5: Tidslinjen Sosialt {icon} er en kombinasjon av tidslinjene Hjem og Lokal.
step5_2: Tjeneren din har {timelines} ulike tidslinjer.
step5_3: Tidslinjen Hjem {icon} er hvor du ser poster fra kontoer du følger.
step2_1: Først, fyll ut litt profil-informasjon.
step4_2: Noen liker å skrive en {introduction}-post eller en enkel "Hei, verden!"
step5_4: Tidslinjen Lokal {icon} er hvor du kan se poster fra alle andre på denne
tjeneren.
step6_2: Vel, du har ikke bare blitt med i Firefish. Du har trådt inn porten til
Allheimen, et sammenkoblet nettverk av tusenvis av tjenere.
step6_3: Hver tjener er satt opp og fungerer litt ulikt, denne og noen andre kjører
Firefish, men ikke alle. Komplisert? Bittelitt, men du skjønner det fort når du
tar det i bruk.
step2_2: Å gi litt informasjon om hvem du er vil gjøre det lettere for andre å vite
om de er interessert i å lese postene dine eller følge deg.
step3_1: Nå er det tid for å følge noen!
step6_4: Nå kan du utforske og ha det gøy!
deletePasskeysConfirm: Dette vil slette alle adgangsnøkler og sikkerhetsnøkler for
denne kontoen. Fortsette?
_pages:
fontSerif: Antikva
featured: Populære
contentBlocks: Innhold
viewPage: Vis dine Sider
blocks:
section: Seksjon
_note:
idDescription: Alternativt kan du legge inn URL til posten her.
id: Post-id
detailed: Detaljert visning
radioButton: Valg
_button:
colored: Farget
_action:
_pushEvent:
message: Melding som skal vises når aktivert
variable: Variabel som skal sendes
no-variable: Ingen
callAiScript: Kjør AiScript
_callAiScript:
functionName: Funksjonsnavn
resetRandom: Nullstill starten for tilfeldige tall
dialog: Vis en dialogboks
_dialog:
content: Innhold
action: Oppførsel når knappen er trykket inn
text: Tittel
note: Innfelt post
button: Knapp
textInput: Tekst-innput
if: Hvis
_canvas:
width: Bredde
height: Høyde
_switch:
name: Variabelnavn
default: Standardverdi
text: Tittel
_counter:
text: Tittel
name: Variabelnavn
_textInput:
name: Variabelnavn
text: Tittel
default: Standardverdi
_textareaInput:
default: Standardverdi
name: Variabelnavn
text: Tittel
_numberInput:
default: Standardverdi
name: Variabelnavn
text: Tittel
text: Tekst
counter: Teller
textareaInput: Flerlinjers tekstfelt
image: Bilder
switch: Bytt
numberInput: Numerisk innput
_if:
variable: Variabel
textarea: Tekstområde
_post:
text: Innhold
post: Post-skjema
created: Siden er opprettet
contents: Innhold
like: Lik
nameAlreadyExists: Den angitte URL-en er allerede i bruk
viewSource: Vis kilde
eyeCatchingImageSet: Sett miniatyrbilde
fontSansSerif: Grotesk
invalidNameTitle: Angitt URL er ugyldig
specialBlocks: Spesielle
chooseBlock: Legg til en blokk
url: URL til Side
invalidNameText: Tittelen på Siden kan ikke være tom
liked: Likte Sider
updated: Redigereringer lagret
editThisPage: Rediger denne Siden
my: Mine Sider
enterVariableName: Skriv inn et variabelnavn
newPage: Lag en ny Side
summary: Sammendrag
inputBlocks: Innput
readPage: Vis kilden til denne Siden
eyeCatchingImageRemove: Slett miniatyrbilde
variables: Variabler
inspector: Inspektør
unlike: Avlik
content: Side-blokk
font: Skriftsnitt
hideTitleWhenPinned: Skjul tittel når Siden er festet til profilen
selectType: Velg en type
alignCenter: Sentrer elementer
title: Tittel
pageSetting: Innstillinger for Side
variableNameIsAlreadyUsed: Variabelnavnet er allerede i bruk
deleted: Siden er slettet
editPage: Rediger denne Siden
_channel:
featured: Trendende
removeBanner: Fjern banner
setBanner: Sett banner
owned: Eid
create: Opprett kanal
nameOnly: Bare navn
notesCount: '{n} poster'
usersCount: '{n} deltakere'
nameAndDescription: Navn og beskrivelse
following: Fulgt
edit: Redigér kanal
_charts:
usersIncDec: Forskjell i antall brukere
apRequest: Forespørsler
storageUsageTotal: Total brukt lagringsplass
usersTotal: Totalt antall brukere
federation: Føderasjon
remoteNotesIncDec: Forskjell i antall eksterne poster
storageUsageIncDec: Forskjell i bruk av lagringsplass
notesIncDec: Forskjell i antall poster
notesTotal: Totalt antall poster
activeUsers: Aktive brukere
filesTotal: Totalt antall filer
localNotesIncDec: Forskjell i antall lokale poster
filesIncDec: Forskjell i antall filer
_deck:
swapLeft: Flytt kolonnen til venstre
configureColumn: Kolonneinnstillinger
stackLeft: Stable med kolonnen til venstre
columnAlign: Juster kolonner
swapUp: Flytt kolonnen oppover
swapRight: Flytt kolonnen til høyre
alwaysShowMainColumn: Vis alltid hovedkolonne
swapDown: Flytt kolonnen nedover
addColumn: Legg til kolonne
nameAlreadyExists: Navnet er allerede i bruk på et annet arbeidsområde.
introduction: Gjør brukergrensesnittet perfekt ved å sette kolonnene slik du vil
ha dem!
introduction2: Klikk pluss-tegnet til høyre på skjermen for å legge til nye kolonner
når du trenger dem.
profile: Arbeidsområde
newProfile: Nytt arbeidsområde
renameProfile: Gi arbeidsområdet nytt navn
deleteProfile: Slett arbeidsområde
_columns:
list: Liste
main: Standard
notifications: Varsler
widgets: Skjermelementer
mentions: Nevninger
channel: Kanal
direct: Direktemeldinger
antenna: Antenne
tl: Tidslinje
widgetsIntroduction: Trykk "Rediger skjermelementer" i kolonnemenyen og legg til
et element.
popRight: Flytt kolonnen ut av stabelen og til høyre
_serverDisconnectedBehavior:
reload: Automatisk omlasting
quiet: Vis ikkeforstyrrende advarsel
nothing: Ikke gjør noe
dialog: Vis advarselsdialog
cannotUploadBecauseNoFreeSpace: Opplasting feilet. Ikke nok plass på Disk.
customMOTD: Egendefinert dagens melding i oppstartsbildet.
_sfx:
channel: Kanal-varsler
chatBg: Chat (Bakgrunn)
antenna: Antenner
notification: Varsler
noteMy: Egen post
chat: Chat
note: Ny post
_sensitiveMediaDetection:
setSensitiveFlagAutomaticallyDescription: Resultatene av intern deteksjon vil bli
beholdt selv om innstillingen slås av.
analyzeVideosDescription: Analyserer videoer i tillegg til bilder. Dette vil øke
lasten på tjeneren litt.
sensitivity: Sensitivitiet i deteksjonen
analyzeVideos: Slå på videoanalyse
sensitivityDescription: Å redusere sensitiviteten vil føre til færre falske positive
(mistenkt NSFW som viser seg å ikke være det). Å øke den vil føre til færre falske
negative.
description: Reduserer arbeidet med moderering på tjeneren ved å automatisk gjenkjenne
NSFW-innhold med maskinlæring. Dette vil gi en litt økt last på tjeneren.
setSensitiveFlagAutomatically: Merk som NSFW
_profile:
name: Navn
username: Brukernavn
metadataContent: Innhold
metadataEdit: Rediger ekstra informasjon
metadata: Ekstra informasjon
metadataLabel: Etikett
description: Bio
metadataDescription: Her kan du legge inn ekstra felt med informasjon i profilen
din.
locationDescription: Om du oppgir stedet du bor først vil det vise din lokale tid
til andre brukere.
changeAvatar: Endre avatar
youCanIncludeHashtags: Du kan bruke emneknagger i bioen også.
changeBanner: Endre brukerfane
removeRecipient: Fjern mottaker
seperateRenoteQuote: Skill knappene for deling og sitering
_permissions:
"read:gallery": Se galleriet ditt
"read:pages": Se siden din
"write:channels": Redigere kanalene dine
"write:gallery-likes": Endre hvilke galleriposter du liker
"read:reactions": Vise reaksjonene dine
"read:user-groups": Se gruppene dine
"read:notifications": Vise varslene dine
"write:reactions": Endre reaksjonene dine
"write:pages": Redigere eller slette siden din
"write:mutes": Redigere listen over stummede brukere
"write:following": Følge eller avfølge andre kontoer
"read:messaging": Vise chat-ene dine
"write:account": Rediger kontoinformasjon
"read:favorites": Vis bokmerkene dine
"write:messaging": Sende eller slette meldinger i chat
"read:account": Se informasjon om kontoen din
"read:channels": Se kanalene dine
"write:drive": Redigere og slette filer og kataloger på Disk
"read:mutes": Vise listen over stummede brukere
"write:blocks": Rediger listen av blokkerte brukere
"read:page-likes": Se hvilke sider du liker
"write:notifications": Styre varslene dine
"read:following": Vise informasjon om hvem du følger
"write:votes": Stemme i en avstemning
"read:gallery-likes": Se listen over galleriposter du liker
"write:gallery": Redigere galleriet ditt
"read:blocks": Vis listen av brukere du blokkerer
"write:favorites": Redigere bokmerkene dine
"read:drive": Tilgang til dine filer og kataloger på Disk
"write:notes": Sende eller slette poster
"write:page-likes": Redigere hvilke sider du liker
"write:user-groups": Redigere eller slette gruppene dine
unlikeConfirm: Slutte å like?
_theme:
installed: '{name} er blitt installert'
keys:
fg: Tekst
infoBg: Informasjonsbakgrunn
mention: Nevn
listItemHoverBg: Bakgrunn for listeelementer (Svevende)
accentDarken: Uthevet (Mørkere)
focus: Fokus
navFg: Tekst i sidefelt
mentionMe: Nevning (Meg)
hashtag: Emneknagg
buttonBg: Bakgrunn i knapper
driveFolderBg: Bakgrunn i Disk-katalog
infoWarnBg: Advarselsbakgrunn
dateLabelFg: Tekst i dato-etikett
indicator: Indikator
header: Hode
bg: Bakgrunn
inputBorder: Ramme rundt input-felt
navBg: Bakgrunn i sidefelt
link: Lenke
infoFg: Informasjonstekst
navActive: Tekst i sidefelt (Aktivt)
scrollbarHandle: Håndtak i rullefelt
messageBg: Bakgrunn i chat
cwBg: Bakgrunn på innholdsadvarsel-knapp
renote: Del
fgHighlighted: Markert tekst
toastBg: Bakgrunn for varsler
buttonHoverBg: Bakgrunn i knapper (Svevende)
panel: Panel
toastFg: Tekst i varsler
accentLighten: Uthevet (Lysere)
scrollbarHandleHover: Håndtak i rullefelt (Svevende)
shadow: Skygge
cwFg: Tekst i innholdsadvarsel-knapp
cwHoverBg: Bakgrunn i innholdsadvarsel-knapp (Svevende)
navHoverFg: Tekst i sidefelt (Svevende)
accent: Uthev
navIndicator: Indikator i sidefelt
divider: Skille
badge: Merker
modalBg: Modal bakgrunn
infoWarnFg: Advarselstekst
darken: Gjør mørkere
refConst: Referer til en konstant
builtinThemes: Innebygde temaer
lighten: Gjør lysere
defaultValue: Standardverdi
basedProp: Referert egenskap
install: Installer et tema
base: Base
addConstant: Legg til konstant
argument: Argument
key: Nøkkel
inputConstantName: Gi konstanten et navn
constant: Konstant
color: Farge
explore: Utforsk temaer
refProp: Referer til en egenskap
invalid: Formatet til dette temaet er ugyldig
code: Tema-kode
installedThemes: Installerte temaer
make: Lag et tema
func: Funksjoner
description: Beskrivelse
alpha: Ugjennomsiktighet
importInfo: Om du legger en temakode her kan du importere den til tema-redigeringen
deleteConstantConfirm: Sikker på at du vil slette konstanten {const}?
funcKind: Funksjonstype
manage: Styr temaer
alreadyInstalled: Dette temaet er allerede installert
_emailUnavailable:
disposable: Engangs-epost-adresser er ikke tillatt
smtp: Epost-tjeneren svarer ikke
mx: Epost-tjeneren er ugyldig
format: Dette ser ikke ut som en gyldig epost-adresse
used: Denne epost-adressen er allerede brukt
_ago:
weeksAgo: '{n} uker siden'
minutesAgo: '{n}m siden'
daysAgo: '{n} dager siden'
secondsAgo: '{n}s siden'
hoursAgo: '{n} timer siden'
justNow: Nettopp
yearsAgo: '{n} år siden'
monthsAgo: '{n} mnd siden'
future: Fremtid
_exportOrImport:
blockingList: Blokkerte brukere
excludeInactiveUsers: Ekskluder inaktive brukere
muteList: Stummede brukere
followingList: Brukere du følger
userLists: Brukerlister
allNotes: Alle poster
excludeMutingUsers: Eksluder stummede brukere
_antennaSources:
homeTimeline: Poster fra brukere du følger
all: Alle poster
userList: Poster fra en spesifisert liste av brukere
userGroup: Poster fra brukere i en bestemt gruppe
users: Poster fra spesifikke brukere
instances: Poster fra alle brukerne på denne tjeneren
_widgets:
timeline: Tidslinje
meiliSize: Indeks-størrelse
instanceCloud: Tjenersky
onlineUsers: Påloggede brukere
clock: Klokke
userList: Brukerliste
rss: RSS-leser
serverMetric: Tjenermetrikker
meiliIndexCount: Indekserte poster
button: Knapp
unixClock: Unix-klokke
calendar: Kalender
trends: Trender
serverInfo: Tjenerinformasjon
jobQueue: Jobbkø
_userList:
chooseList: Velg en liste
photos: Bilder
rssTicker: RSS-rulletekst
aiscript: AiScript-konsoll
meiliStatus: Tjenerstatus
memo: Notatlapp
notifications: Varsler
postForm: Ny post
digitalClock: Digital klokke
activity: Aktivitet
federation: Føderering
slideshow: Bildeviser
_wordMute:
muteWordsDescription: Skill med mellomrom for logisk "OG" og med linjeskift for
logisk "ELLER".
hardDescription: Hindrer poster som oppfyller vilkårene fra å bli lagt til i tidslinjen.
De vil heller ikke bli lagt i tidslinjen senere dersom vilkårene endres i ettertid.
mutedNotes: Stummede poster
softDescription: Skjul poster som oppfyller disse vilkårene fra tidlinjen.
soft: Svakt
muteWordsDescription2: Sett skråstreker rundt nøkkelord for å bruke regulære uttrykk.
hard: Hardt
muteWords: Stummede ord
showFeaturedNotesInTimeline: Vis framheved poster i tidslinjen
rateLimitExceeded: For mange forsøk på kort tid
_2fa:
securityKeyInfo: Utover fingeravtrykk og PIN-koder kan du sette opp autentisering
via fysiske sikkerhetsnøkler som støtter FIDO2 for å sikre kontoen ytterligere.
removeKey: Fjern sikkerhetsnøkkel
alreadyRegistered: Du har allerede registrert en enhet for tofaktor-autentisering.
token: 2FA-tegn
step3: Oppgi en engangskode for å fullføre oppsettet.
step1: Først, installer en app for engangskoder (slik som {a} eller {b}) på en enhet.
step3Title: Skriv inn en autentiseringskode
renewTOTPCancel: Avbryt
securityKeyName: Oppgi et nøkkelnavn
renewTOTPOk: Rekonfigurer
registerSecurityKey: Registrer en sikkerhetsnøkkel
step2: Deretter kan du scanne QR-koden som vises på skjermen.
registerTOTP: Registrer engangskode-app
removeKeyConfirm: Sikker på at du vil slette nøkkelen {name}?
registerTOTPBeforeKey: Sett opp en autentiserings-app for å registrere en sikkerhetsnøkkel.
chromePasskeyNotSupported: Passkeys i Chrome er foreløpig ikke støttet.
renewTOTPConfirm: Dette vil føre til at kodene fra den forrige appen din vil slutte
å virke
renewTOTP: Rekonfigurer autentiserings-app
securityKeyNotSupported: Nettleseren din tillater ikke sikkerhetsnøkler.
step2Click: Når du klikker på QR-koden kan du registrere den som en tofaktor-pålogging
på en sikkerhetsnøkkel eller engangskode-app.
tapSecurityKey: Følg nettleseren for å registrere en sikkerhetsnøkkel eller passkey
step4: Fra nå av vil du alltid bli bedt om en engangskode når du logger inn.
step2Url: 'Du kan også skrive inn denne URL-en hvis du bruker et program på en PC
til tofaktor-pålogging:'
inputNotMatch: Input stemmer ikke
_ffVisibility:
public: Offentlig
private: Privat
followers: Synlig bare for følgere
_weekday:
wednesday: Onsdag
tuesday: Tirsdag
sunday: Søndag
saturday: Lørdag
monday: Mandag
friday: Fredag
thursday: Torsdag
poll: Avstemning
_instanceCharts:
users: Forskjell i antall brukere
usersTotal: Kumulativt antall brukere
cacheSize: Forskjell i mellomlagring
ffTotal: Kumulativt antall fulgte brukere / følgere
requests: Forespørsler
cacheSizeTotal: Kumulativ total mellomlagring
ff: 'Forskjell i antall fulgte brukere / følgere '
filesTotal: Kumulativt antall filer
files: Forskjell i antall filer
notes: Forskjell i antall poster
notesTotal: Kumulativt antall poster
localOnly: Bare lokalt
navbar: Navigasjonsfelt
_visibility:
localOnly: Bare lokalt
followersDescription: Synlig bare for dine følgere og brukere nevnt i posten
specified: Direkte
home: Unotert
localOnlyDescription: Bare synlig for brukere på denne tjeneren
specifiedDescription: Synlig bare for spesifiserte brukere
public: Offentlig
homeDescription: Bare post til hjem-tidslinjen
followers: Følgere
publicDescription: Postene dine være bli synlige i alle offentlige tidslinjer
_poll:
at: Sett slutt-tidspunkt
votesCount: '{n} stemmer'
infinite: Aldri
expiration: Avslutt avstemning
totalVotes: '{n} stemmer totalt'
remainingDays: '{d} dager og {h} timer igjen'
deadlineTime: Tid
remainingHours: '{h} timer og {m} minutter igjen'
canMultipleVote: Tillat flervalg
closed: Avsluttet
showResult: Vis resultat
after: Slutt etter gitt tid
deadlineDate: Sluttdato
noOnlyOneChoice: Du må legge inn minst to valg
remainingMinutes: '{m} minutter og {s} sekunder igjen'
voted: Stemt
vote: Stem
remainingSeconds: '{s} sekunder igjen'
choiceN: Valg {n}
noMore: Du kan ikke legge til flere valg
duration: Varighet
move: Flytt
_filters:
notesAfter: Poster etter
followersOnly: Bare de som følger deg
fromDomain: Fra domene
withFile: Med fil
notesBefore: Poster før
fromUser: Fra bruker
followingOnly: Bare de du følger
_feeds:
copyFeed: Kopier nyhetsstrøm
atom: Atom
rss: RSS
jsonFeed: JSON-strøm
sendPushNotificationReadMessageCaption: Et varsel med teksten "{emptyPushNotificationMessage}"
vil snart bli vist. Dette kan øke batteribruken på enheten dersom den er batteridrevet.
customSplashIconsDescription: URL-er til egendefinerte ikoner til oppstartsskjermbildet.
En av dem vil bli lastet tilfeldig når en bruker laster inn siden. Pass på at bildene
er på statiske URL-er, fortrinnsvis i størrelsen 192x192 piksler.
instanceDefaultThemeDescription: Skriv inn temakode i objektformat.
deletePasskeys: Slett passkeys
_cw:
chars: '{count} tegn'
show: Vis innhold
files: '{count} fil(er)'
hide: Skjul
_timelines:
local: Lokal
home: Hjem
recommended: Foreslått
global: Global
social: Sosial
serviceworkerInfo: Må være påslått for push-varsler.
detectPostLanguage: Automatisk detekter språk og vis en oversettelsesknapp for poster
på andre språk
_auth:
copyAsk: 'Lim inn denne koden i applikasjonen:'
callback: Går tilbake til applikasjonen
shareAccess: Vil du gi "{name}" tilgang til denne kontoen?
denied: Tilgang avvist
permissionAsk: 'Denne applikasjonen ber om følgende tilganger:'
allPermissions: Full tilgang til kontoen
pleaseGoBack: Gå tilbake til applikasjonen
shareAccessAsk: Er du sikker på at du vil gi denne applikasjonen lov til å gå inn
på kontoen din?
youHaveUnreadAnnouncements: Du har uleste kunngjøringer
featured: Fremhevet
pushNotification: Push-varsler
delete2faConfirm: Dette vil slå av 2FA på denne kontoen, og kan ikke omgjøres. Fortsette?
indexableDescription: Tillat innebygget søk å vise dine offentlige poster
splash: Oppstartsskjerm
_menuDisplay:
sideIcon: Side (Ikoner)
hide: Skjul
sideFull: Side
top: Topp
enableIdenticonGeneration: Slå på generering av identikoner
_skinTones:
medium: Medium
dark: Mørk
yellow: Gul
mediumDark: Middels mørk
light: Lys
mediumLight: Middels lys
_instanceMute:
instanceMuteDescription2: Skill med linjeskift
instanceMuteDescription: Dette vil stumme alle poster og deling fra de opplistede
tjenerne, inkludert poster som svarer på en post fra en stummet tjener.
title: Skjuler poster fra opplistede tjenere.
heading: Liste av tjenere som skal stummes
_messaging:
groups: Grupper
dms: Privat
_time:
day: Dag(er)
hour: Time(r)
minute: Minutt(er)
second: Sekund(er)
addRe: Legg til "re:" i begynnelsen av en kommentar til en post med innholdsadvarsel
donationLink: Lenke til donasjons-side
createNewClip: Nytt utklipp
removeQuote: Fjern sitat
showUpdates: Vis et sprettoppvindu når Firefish oppdateres
renotesCount: Antall sendte delinger
type: Type
remoteOnly: Bare eksternt
clipsDesc: Utklipp er som kategoriserte bokmerker du kan dele med andre. Du kan lage
utklipp fra menyen til individuelle poster.
unclip: Fjern utklipp
quitFullView: Forlat full visning
exportZip: Eksporter zip-fil
desktop: Skrivebord
botProtection: Bot-beskyttelse
_signup:
almostThere: Nesten der
emailAddressInfo: Oppgi epost-adressen din. Den vil ikke bli synlig for andre.
emailSent: En bekreftelses-epost er sendt til epost-adressen ({email}). Klikk på
lenken i eposten for å fullføre registrering.
beta: Beta
renotedCount: Antall delinger av dine poster
unsubscribePushNotification: Slå av push-varsler
sample: Eksempel
cannotUploadBecauseExceedsFileSizeLimit: Filen kan ikke lastes opp fordi den er større
enn maks tillatt filstørrelse.
remindMeLater: Kanskje senere
adminCustomCssWarn: Denne innstillingen må bare brukes hvis du vet hva den gjør. Ugyldige
verdier kan få klienten til å feile for alle brukere. Bruk brukerinnstillingene
for å teste at CSS fungerer som den skal.
account: Konto
activeEmailValidationDescription: Slår på strengere sjekk av epost-adresser, inkludert
sjekking for engangs-adresser og om den faktisk kan kommuniseres med. Når innstillingen
er av vil det bare sjekkes at epost-adressen er syntaktisk riktig.
clips: Utklipp
verifiedLink: Verifisert lenke
secureModeInfo: Ved forespørsler fra andre tjenere, ikke send tilbake uten bevis.
isPatron: Firefish-patron
cannotUploadBecauseInappropriate: Filen ble ikke lastet opp fordi den er detektert
som mulig NSFW.
showAds: Vis samfunns-banner
enterSendsMessage: Trykk enter for å sende meldinger. (Hvis avslått må du trykke Ctrl
+ enter)
confirmToUnclipAlreadyClippedNote: Denne posten er allerede en del av utklippet "{name}".
Vil du fjerne den fra dette utklippet i stedet?
showWithSparkles: Vis med gnister
emojiPackCreator: Emoji-pakke-bygger
importZip: Importer zip-fil
pushNotificationAlreadySubscribed: Push-varsler er allerede påslått
ratio: Forhold
pushNotificationNotSupported: Nettleseren eller tjeneren tillater ikke push-varsler
accountDeletionInProgress: Sletting av konto pågår
indexable: Indekserbar
shuffle: Stokket
sensitiveMediaDetection: Deteksjon av NSFW-media
delete2fa: Slå av tofaktorautentisering (2FA)
isLocked: Denne kontoen har følgende godkjenninger
languageForTranslation: Oversettelsesspråk for poster
customSplashIcons: URL-er til egendefinerte oppstartsskjerm-ikoner
_instanceTicker:
always: Alltid vis
none: Aldri vis
remote: Vis for eksterne brukere
confirm: Bekreft
origin: Kilde
label: Etikett
customMOTDDescription: Egendefinerte meldinger for dagens melding på oppstartsskjermbildet.
Linjeskift mellom meldinger. En tilfeldig melding vil bli vist hver gang en bruker
laster inn siden.
enableAutoSensitive: Automatisk markering av NSFW
neverShow: Ikke vis igjen
removeMember: Fjern medlem
clip: Utklipp
sendPushNotificationReadMessage: Slett push-varsler når den relevante informasjonen
er lest
enableAutoSensitiveDescription: Tillat automatisk deteksjon og markering av NSFW basert
på maskinlære der det er mulig. Selv om du slår av dette kan det være påslått for
hele tjeneren.
subscribePushNotification: Slå på push-varsler
failedToUpload: Opplasting feilet
speed: Fart
undeck: Forlat kolonnevisning
deck: Kolonner
_experiments:
title: Eksperimenter
enablePostImports: Slå på post-importer
postImportsCaption: Lar brukere importere poster fra kontoer på Firefish, Misskey,
Mastodon, Akkoma eller Pleroma. Den ekstra lasten kan forårsake tregere tjeneste.
_dialog:
charactersBelow: 'Ikke nok tagn! Skrevet: {current}/Grense: {min}'
charactersExceeded: 'Maks antall tegn er overskredet! Skrevet: {current}/Grense:
{max}'

View File

@ -82,7 +82,7 @@ followers: "Följare"
followsYou: "Följer dig" followsYou: "Följer dig"
createList: "Skapa lista" createList: "Skapa lista"
manageLists: "Hantera lista" manageLists: "Hantera lista"
error: "Fel!" error: "Fel"
somethingHappened: "Ett fel har uppstått" somethingHappened: "Ett fel har uppstått"
retry: "Försök igen" retry: "Försök igen"
pageLoadError: "Det gick inte att ladda sidan." pageLoadError: "Det gick inte att ladda sidan."
@ -146,7 +146,7 @@ addEmoji: "Lägg till emoji"
settingGuide: "Rekommenderade inställningar" settingGuide: "Rekommenderade inställningar"
cacheRemoteFiles: "Spara externa filer till cachen" cacheRemoteFiles: "Spara externa filer till cachen"
cacheRemoteFilesDescription: "När denna inställning är avstängd kommer externa filer cacheRemoteFilesDescription: "När denna inställning är avstängd kommer externa filer
laddas direkt från den externa instansen. Genom att stänga av detta kommer lagringsutrymme laddas direkt från den externa servern. Genom att stänga av detta kommer lagringsutrymme
minska i användning men kommer öka datatrafiken eftersom miniatyrer inte kommer minska i användning men kommer öka datatrafiken eftersom miniatyrer inte kommer
genereras." genereras."
flagAsBot: "Markera konto som bot" flagAsBot: "Markera konto som bot"
@ -181,7 +181,7 @@ selectUser: "Välj användare"
recipient: "Mottagare" recipient: "Mottagare"
annotation: "Kommentarer" annotation: "Kommentarer"
federation: "Federation" federation: "Federation"
instances: "Instanser" instances: "Servrar"
registeredAt: "Registrerad på" registeredAt: "Registrerad på"
latestRequestSentAt: "Senaste förfrågan skickad" latestRequestSentAt: "Senaste förfrågan skickad"
latestRequestReceivedAt: "Senaste begäran mottagen" latestRequestReceivedAt: "Senaste begäran mottagen"
@ -191,7 +191,7 @@ charts: "Diagram"
perHour: "Per timme" perHour: "Per timme"
perDay: "Per dag" perDay: "Per dag"
stopActivityDelivery: "Sluta skicka aktiviteter" stopActivityDelivery: "Sluta skicka aktiviteter"
blockThisInstance: "Blockera instans" blockThisInstance: "Blockera denna server"
operations: "Operationer" operations: "Operationer"
software: "Mjukvara" software: "Mjukvara"
version: "Version" version: "Version"
@ -201,7 +201,7 @@ jobQueue: "Jobbkö"
cpuAndMemory: "CPU och minne" cpuAndMemory: "CPU och minne"
network: "Nätverk" network: "Nätverk"
disk: "Disk" disk: "Disk"
instanceInfo: "Instansinformation" instanceInfo: "Serverninformation"
statistics: "Statistik" statistics: "Statistik"
clearQueue: "Rensa kö" clearQueue: "Rensa kö"
clearQueueConfirmTitle: "Är du säker att du vill rensa kön?" clearQueueConfirmTitle: "Är du säker att du vill rensa kön?"
@ -209,9 +209,9 @@ clearQueueConfirmText: "Om någon not är olevererad i kön kommer den inte fede
Vanligtvis behövs inte denna handling." Vanligtvis behövs inte denna handling."
clearCachedFiles: "Rensa cache" clearCachedFiles: "Rensa cache"
clearCachedFilesConfirm: "Är du säker att du vill radera alla cachade externa filer?" clearCachedFilesConfirm: "Är du säker att du vill radera alla cachade externa filer?"
blockedInstances: "Blockerade instanser" blockedInstances: "Blockerade servrar"
blockedInstancesDescription: "Lista adressnamn av instanser som du vill blockera. blockedInstancesDescription: "Lista adressnamn av servrar som du vill blockera. Listade
Listade instanser kommer inte längre kommunicera med denna instans." servrarna kommer inte längre kommunicera med denna servern."
muteAndBlock: "Tystningar och blockeringar" muteAndBlock: "Tystningar och blockeringar"
mutedUsers: "Tystade användare" mutedUsers: "Tystade användare"
blockedUsers: "Blockerade användare" blockedUsers: "Blockerade användare"
@ -221,7 +221,7 @@ noteDeleteConfirm: "Är du säker på att du vill ta bort denna not?"
pinLimitExceeded: "Du kan inte fästa fler noter" pinLimitExceeded: "Du kan inte fästa fler noter"
intro: "Firefish har installerats! Vänligen skapa en adminanvändare." intro: "Firefish har installerats! Vänligen skapa en adminanvändare."
done: "Klar" done: "Klar"
processing: "Bearbetar..." processing: "Bearbetar"
preview: "Förhandsvisning" preview: "Förhandsvisning"
default: "Standard" default: "Standard"
defaultValueIs: "Standard: {value}" defaultValueIs: "Standard: {value}"
@ -234,12 +234,12 @@ all: "Allt"
subscribing: "Prenumererar" subscribing: "Prenumererar"
publishing: "Publiceras" publishing: "Publiceras"
notResponding: "Svarar inte" notResponding: "Svarar inte"
instanceFollowing: "Följer på instans" instanceFollowing: "Följer på server"
instanceFollowers: "Följare av instans" instanceFollowers: "Följare av server"
instanceUsers: "Användare av denna instans" instanceUsers: "Användare av denna server"
changePassword: "Ändra lösenord" changePassword: "Ändra lösenord"
security: "Säkerhet" security: "Säkerhet"
retypedNotMatch: "Inmatningen matchar inte" retypedNotMatch: "Inmatningen matchar inte."
currentPassword: "Nuvarande lösenord" currentPassword: "Nuvarande lösenord"
newPassword: "Nytt lösenord" newPassword: "Nytt lösenord"
newPasswordRetype: "Bekräfta lösenord" newPasswordRetype: "Bekräfta lösenord"
@ -394,7 +394,7 @@ clientSettings: Klientinställningar
promote: Befordra promote: Befordra
numberOfDays: Antalet dagar numberOfDays: Antalet dagar
objectStorageUseSSL: Använd SSL objectStorageUseSSL: Använd SSL
objectStorageUseSSLDesc: Stäng av detta om du inte kommer använda HTTPS för API anslutningar. objectStorageUseSSLDesc: Stäng av detta om du inte kommer använda HTTPS för API anslutningar
recentUsed: Senast använd recentUsed: Senast använd
nothing: Det finns inget att visa här nothing: Det finns inget att visa här
lastUsedDate: Senaste använd vid lastUsedDate: Senaste använd vid
@ -481,7 +481,7 @@ cacheClear: Rensa cache
markAsReadAllTalkMessages: Markera alla meddelanden som lästa markAsReadAllTalkMessages: Markera alla meddelanden som lästa
uiLanguage: Användargränssnitt uiLanguage: Användargränssnitt
disableDrawer: Använd inte byrålådor-stil menyer disableDrawer: Använd inte byrålådor-stil menyer
tapSecurityKey: Klicka in din säkerhetsnyckel. tapSecurityKey: Klicka in din säkerhetsnyckel
language: Språk language: Språk
objectStorageRegionDesc: Ange en region som 'xx-east-1'. Om du anger din tjänst som objectStorageRegionDesc: Ange en region som 'xx-east-1'. Om du anger din tjänst som
inte skiljer mellan regioner, lämna detta blankt eller ange som 'us-east-1'. inte skiljer mellan regioner, lämna detta blankt eller ange som 'us-east-1'.
@ -576,7 +576,7 @@ accept: Acceptera
tosUrl: Användarvillkor URL tosUrl: Användarvillkor URL
pages: Sidor pages: Sidor
disablingTimelinesInfo: Administratörer och moderatorer har alltid tillgång till alla disablingTimelinesInfo: Administratörer och moderatorer har alltid tillgång till alla
tidslinjer, även om de inte är aktiverade tidslinjer, även om de inte är aktiverade.
registration: Registrera registration: Registrera
enableRegistration: Aktivera ny användarregistrering enableRegistration: Aktivera ny användarregistrering
driveCapacityPerRemoteAccount: Enhetskapacitet per extern användare driveCapacityPerRemoteAccount: Enhetskapacitet per extern användare
@ -631,7 +631,7 @@ birthday: Födelsedag
theme: Teman theme: Teman
avatar: Avatar avatar: Avatar
uploadFromUrlDescription: URL av filen som du vill ladda upp uploadFromUrlDescription: URL av filen som du vill ladda upp
remoteUserCaution: Informationen från en avlägsen användare kan inte slutföras remoteUserCaution: Informationen från en avlägsen användare kan inte slutföras.
yearsOld: '{age} år gammal' yearsOld: '{age} år gammal'
location: Plats location: Plats
selectFile: Välj en fil selectFile: Välj en fil
@ -652,7 +652,7 @@ basicInfo: Grundläggande info
pinnedUsers: Pinnade användare pinnedUsers: Pinnade användare
backgroundImageUrl: Bakgrundsbild URL backgroundImageUrl: Bakgrundsbild URL
pinnedUsersDescription: Lista användarnamn separerade med radbrytning att bli fäst pinnedUsersDescription: Lista användarnamn separerade med radbrytning att bli fäst
i "Utforska" fliken i "Utforska" fliken.
recaptchaSiteKey: Sid nyckel recaptchaSiteKey: Sid nyckel
pinnedClipId: ID av klippet du vill fästa pinnedClipId: ID av klippet du vill fästa
avoidMultiCaptchaConfirm: Användning av flera Captcha system kan orsaka problem. Vill avoidMultiCaptchaConfirm: Användning av flera Captcha system kan orsaka problem. Vill
@ -681,7 +681,7 @@ nUsersMentioned: Benämnd av {n} användare
securityKeyName: Nyckelnamn securityKeyName: Nyckelnamn
share: Dela share: Dela
reduceUiAnimation: Minska UI animeringar reduceUiAnimation: Minska UI animeringar
notFoundDescription: Ingen sida som korresponderar med denna URL kunde hittas notFoundDescription: Ingen sida som korresponderar med denna URL kunde hittas.
close: Stäng close: Stäng
group: Grupp group: Grupp
transfer: Överför transfer: Överför
@ -759,7 +759,7 @@ lastUsed: Senast använd
unregister: Avregistrera unregister: Avregistrera
addInstance: Lägg till server addInstance: Lägg till server
objectStorageBucketDesc: Vänligen ange hink-namn som du använder som din leverantör. objectStorageBucketDesc: Vänligen ange hink-namn som du använder som din leverantör.
accountMoved: Användaren har flyttat till ett nytt konto accountMoved: 'Användaren har flyttat till ett nytt konto:'
hideThisNote: Dölj denna post hideThisNote: Dölj denna post
showFeaturedNotesInTimeline: Visa presenterade poster i tidslinjen showFeaturedNotesInTimeline: Visa presenterade poster i tidslinjen
objectStorageBucket: Hink objectStorageBucket: Hink
@ -781,3 +781,5 @@ removeAllFollowing: Sluta följa alla följda användare
medium: Mellan medium: Mellan
integration: Integreringar integration: Integreringar
xl: XL xl: XL
desktop: Skrivbord
createNew: Skapa nya

View File

@ -226,7 +226,7 @@ searchPlaceholder: Firefish'de Ara
reply: Yanıtla reply: Yanıtla
jumpToPrevious: Öncekini görüntüle jumpToPrevious: Öncekini görüntüle
deleted: Silindi deleted: Silindi
editNote: Notu düzenle editNote: Gönderiyi düzenle
noThankYou: Hayır, teşekkürler noThankYou: Hayır, teşekkürler
addInstance: Bir sunucu ekle addInstance: Bir sunucu ekle
cantFavorite: Favorilere eklenemedi. cantFavorite: Favorilere eklenemedi.
@ -262,7 +262,7 @@ blockedInstancesDescription: Engellemek istediğiniz sunucuların domain adları
Listelenen sunucular artık bu sunucularla iletişim kuramayacak. Listelenen sunucular artık bu sunucularla iletişim kuramayacak.
blockedUsers: Engellenmiş kullanıcılar blockedUsers: Engellenmiş kullanıcılar
editProfile: Profilini düzenle editProfile: Profilini düzenle
intro: Firefish'in kururlumj tamamlandı! Lütfen yönetici hesap oluşturun. intro: Firefish'in kurulumu tamamlandı! Lütfen yönetici hesap oluşturun.
instanceUsers: Sunucunun kullanıcıları instanceUsers: Sunucunun kullanıcıları
changePassword: Şifreyi değiştir changePassword: Şifreyi değiştir
security: Güvenlik security: Güvenlik
@ -272,7 +272,7 @@ syncDeviceDarkMode: Karanlık modu cihazının ayarları ile senkronize et
renameFolder: Bu klasörü yeniden adlandır renameFolder: Bu klasörü yeniden adlandır
emptyFolder: Bu klasör boş emptyFolder: Bu klasör boş
unableToDelete: Silinemiyor unableToDelete: Silinemiyor
inputNewDescription: Yeni başlık gir inputNewDescription: Yeni ıklama gir
hasChildFilesOrFolders: Bu klasör boş olmadığından dolayı silinemez. hasChildFilesOrFolders: Bu klasör boş olmadığından dolayı silinemez.
disconnectedFromServer: Sunucuyla bağlantı kesildi disconnectedFromServer: Sunucuyla bağlantı kesildi
reload: Yenile reload: Yenile
@ -300,7 +300,7 @@ messagingWithGroup: Grup sohbeti
next: Sonraki next: Sonraki
retype: Tekrar gir retype: Tekrar gir
dashboard: Panel dashboard: Panel
objectStorageBucket: Bucket objectStorageBucket: Kova
objectStorageBucketDesc: Sağlayıcınız tarafından kullanınan bucket ismini yazın. objectStorageBucketDesc: Sağlayıcınız tarafından kullanınan bucket ismini yazın.
showFixedPostForm: Gönderim formunu zaman çizelgesinin en üstünde görüntüleyin showFixedPostForm: Gönderim formunu zaman çizelgesinin en üstünde görüntüleyin
newNoteRecived: Yeni gönderiler mevcut newNoteRecived: Yeni gönderiler mevcut
@ -585,8 +585,7 @@ onlyOneFileCanBeAttached: Bir mesaja sadece 1 dosya ekleyebilirsin
install: İndir install: İndir
uninstall: kALDIR uninstall: kALDIR
send: Gönder send: Gönder
noCrawleDescription: Arama motorlarından profil sayfanızı, gönderilerinizi, Sayfalarınızı noCrawleDescription: Harici arama motorlarından içeriğinizi endekslememesini isteyin.
vb. indekslememesini isteyin.
emailNotification: Mail bildirimleri emailNotification: Mail bildirimleri
goBack: Geri goBack: Geri
online: Çevrimiçi online: Çevrimiçi
@ -751,7 +750,7 @@ upload: Yükle
fromUrl: URL'den fromUrl: URL'den
agreeTo: '{0} kabul ediyorum' agreeTo: '{0} kabul ediyorum'
tos: Kullanım Koşulları tos: Kullanım Koşulları
drive: Drive drive: Sürücü
selectFolder: Klasör seç selectFolder: Klasör seç
inputNewFileName: Yeni dosya ismi gir inputNewFileName: Yeni dosya ismi gir
whenServerDisconnected: Sunucuyla bağlantı kesildiğinde whenServerDisconnected: Sunucuyla bağlantı kesildiğinde
@ -917,7 +916,7 @@ aboutX: '{x} Hakkında'
doing: İşleniyor... doing: İşleniyor...
category: Kategori category: Kategori
deleteAll: Hepsini sil deleteAll: Hepsini sil
objectStorageEndpoint: Endpoint objectStorageEndpoint: Uç noktası
output: Çıkış output: Çıkış
userSuspended: Bu kullanıcı askıya alındı. userSuspended: Bu kullanıcı askıya alındı.
userSilenced: Bu kullanıcı susturuldu. userSilenced: Bu kullanıcı susturuldu.
@ -1106,7 +1105,7 @@ clips: Ataçlar
experimentalFeatures: Deneysel özellikler experimentalFeatures: Deneysel özellikler
developer: Geliştirici developer: Geliştirici
left: Sol left: Sol
center: Orta center: Merkez
wide: Geniş wide: Geniş
narrow: Dar narrow: Dar
reloadToApplySetting: Bu ayar yalnızca bir sayfa yeniden yüklendikten sonra geçerli reloadToApplySetting: Bu ayar yalnızca bir sayfa yeniden yüklendikten sonra geçerli
@ -1152,7 +1151,7 @@ shuffle: Karıştır
pushNotification: Push bildirimleri pushNotification: Push bildirimleri
unsubscribePushNotification: Push bildirimlerini kapat unsubscribePushNotification: Push bildirimlerini kapat
pushNotificationNotSupported: Tarayıcınız veya sunucunuz push bildirimleri desteklemiyor pushNotificationNotSupported: Tarayıcınız veya sunucunuz push bildirimleri desteklemiyor
caption: Otomatik Başlık caption: Otomatik ıklama
moveToLabel: 'Taşıyacağın hesap:' moveToLabel: 'Taşıyacağın hesap:'
moveFromDescription: Bu, eski hesabınızın bir takma adını belirleyecek ve böylece moveFromDescription: Bu, eski hesabınızın bir takma adını belirleyecek ve böylece
o hesaptan bu mevcut hesaba geçebileceksiniz. Bunu eski hesabınızdan taşınmadan o hesaptan bu mevcut hesaba geçebileceksiniz. Bunu eski hesabınızdan taşınmadan
@ -1242,9 +1241,9 @@ deletedNote: Silinmiş Gönderi
visibility: Görünürlük visibility: Görünürlük
poll: Anket poll: Anket
themeEditor: Tema düzenleyicisi themeEditor: Tema düzenleyicisi
enterFileDescription: Başlık gir enterFileDescription: ıklama gir
description: ıklama description: ıklama
describeFile: Başlık ekle describeFile: ıklama ekle
system: Sistem system: Sistem
desktop: Masaüstü desktop: Masaüstü
confirmToUnclipAlreadyClippedNote: Bu gönderi zaten "{name}" atacının bir parçası. confirmToUnclipAlreadyClippedNote: Bu gönderi zaten "{name}" atacının bir parçası.
@ -1952,6 +1951,7 @@ _aboutFirefish:
bağış yapmayı da düşünün. bağış yapmayı da düşünün.
donateHost: '{ev sahibi} için bağış yapın' donateHost: '{ev sahibi} için bağış yapın'
sponsors: Firefish sponsorları sponsors: Firefish sponsorları
misskeyContributors: Misskey'e katkıda bulunanlar
_weekday: _weekday:
saturday: Cumartesi saturday: Cumartesi
sunday: Pazar sunday: Pazar
@ -2136,7 +2136,7 @@ _feeds:
origin: Kaynak origin: Kaynak
objectStorageS3ForcePathStyle: Path temelli bir endpoint URL'leri kullan objectStorageS3ForcePathStyle: Path temelli bir endpoint URL'leri kullan
objectStorageS3ForcePathStyleDesc: Bunu açarak 's3.amazonaws.com/<bucket>/' over '<bucket>.s3.amazonaws.com' objectStorageS3ForcePathStyleDesc: Bunu açarak 's3.amazonaws.com/<bucket>/' over '<bucket>.s3.amazonaws.com'
formatında URL'lerle endpointleri kurabilirsin. formatında endpoint URL'leri kurabilirsin.
delete2fa: 2FA'yı devre dışı bırak delete2fa: 2FA'yı devre dışı bırak
deletePasskeys: Passkey'leri sil deletePasskeys: Passkey'leri sil
inputNotMatch: Girdi eşleşmiyor inputNotMatch: Girdi eşleşmiyor
@ -2144,3 +2144,15 @@ deletePasskeysConfirm: Bu işlem geri alınamaz bir şekilde bu hesapta ki tüm
ve güvenlik anahtarlarını silecektir. İşleme devam ediyor musunuz? ve güvenlik anahtarlarını silecektir. İşleme devam ediyor musunuz?
delete2faConfirm: Bu işlem geri alınamaz bir şekilde 2FA'yı bu hesaptan silecektir. delete2faConfirm: Bu işlem geri alınamaz bir şekilde 2FA'yı bu hesaptan silecektir.
İşleme devam ediyor musunuz? İşleme devam ediyor musunuz?
detectPostLanguage: Yabancı dildeki gönderiler için dili otomatik olarak tespit et
ve bir tercüme düğmesi göster
indexableDescription: Yerleşik aramanın herkese açık gönderilerinizi göstermesine
izin ver
addRe: İçerik uyarısına sahip bir gönderiye yanıt olarak paylaşılan gönderinin başına
"re:" ekle
exportZip: ZIP dışa aktar
emojiPackCreator: Emoji paketi oluşturucu
importZip: ZIP içe aktar
indexable: Endekslenebilir
languageForTranslation: Çeviri sonrası dili
confirm: Onayla

View File

@ -673,7 +673,7 @@ no: "否"
driveFilesCount: "网盘的文件数" driveFilesCount: "网盘的文件数"
driveUsage: "网盘的空间用量" driveUsage: "网盘的空间用量"
noCrawle: "要求搜索引擎不索引该用户" noCrawle: "要求搜索引擎不索引该用户"
noCrawleDescription: "要求搜索引擎不要收录(索引)您的用户页面,帖子,页面等。" noCrawleDescription: "要求外部搜索引擎不收录(索引)您的内容。"
lockedAccountInfo: "即使通过了关注请求,只要您不将帖子可见范围设置成“关注者”,任何人都可以看到您的帖子。" lockedAccountInfo: "即使通过了关注请求,只要您不将帖子可见范围设置成“关注者”,任何人都可以看到您的帖子。"
alwaysMarkSensitive: "默认将媒体文件标记为敏感内容" alwaysMarkSensitive: "默认将媒体文件标记为敏感内容"
loadRawImages: "加载原始图像而不是显示缩略图" loadRawImages: "加载原始图像而不是显示缩略图"
@ -1110,11 +1110,16 @@ _menuDisplay:
hide: "隐藏" hide: "隐藏"
_wordMute: _wordMute:
muteWords: "过滤词" muteWords: "过滤词"
muteLangs: "过滤语言"
muteWordsDescription: "AND 条件用空格分隔OR 条件用换行符分隔。" muteWordsDescription: "AND 条件用空格分隔OR 条件用换行符分隔。"
muteWordsDescription2: "将关键字用斜线括起来表示正则表达式。" muteWordsDescription2: "将关键字用斜线括起来表示正则表达式。"
muteLangsDescription: "OR 条件用空格,换行符分隔"
muteLangsDescription2: "使用语言代码。例: en, fr, ja, zh."
softDescription: "隐藏时间线中指定条件的帖子。" softDescription: "隐藏时间线中指定条件的帖子。"
langDescription: "从时间线中隐藏与设置语言匹配的帖子。"
hardDescription: "防止将具有指定条件的帖子添加到时间线。 即使您更改条件,原先未添加的帖文也会被排除在外。" hardDescription: "防止将具有指定条件的帖子添加到时间线。 即使您更改条件,原先未添加的帖文也会被排除在外。"
soft: "软过滤" soft: "软过滤"
lang: "语言"
hard: "硬过滤" hard: "硬过滤"
mutedNotes: "已过滤的帖子" mutedNotes: "已过滤的帖子"
_instanceMute: _instanceMute:
@ -1980,3 +1985,7 @@ inputNotMatch: 输入不匹配
deletePasskeys: 删除通行密钥 deletePasskeys: 删除通行密钥
delete2faConfirm: 这将不可逆转地删除此账户上的 2FA。是否继续 delete2faConfirm: 这将不可逆转地删除此账户上的 2FA。是否继续
addRe: 在回复有内容警告的帖子时,在评论开头添加 "re:" addRe: 在回复有内容警告的帖子时,在评论开头添加 "re:"
detectPostLanguage: 自动检测语言,并显示外文帖子的翻译按钮
indexableDescription: 允许内置搜索显示您的公开帖子
indexable: 可索引的
languageForTranslation: 帖子翻译语言

View File

@ -236,7 +236,7 @@ imageUrl: "圖片URL"
remove: "刪除" remove: "刪除"
removed: "已成功刪除" removed: "已成功刪除"
removeAreYouSure: "確定要刪掉「{x}」嗎?" removeAreYouSure: "確定要刪掉「{x}」嗎?"
deleteAreYouSure: "確定要刪「{x}」嗎?" deleteAreYouSure: "確定要刪「{x}」嗎?"
resetAreYouSure: "確定要重設嗎?" resetAreYouSure: "確定要重設嗎?"
saved: "已儲存" saved: "已儲存"
messaging: "訊息" messaging: "訊息"
@ -292,7 +292,7 @@ inputNewFileName: "輸入檔案名稱"
inputNewDescription: "請輸入新標題" inputNewDescription: "請輸入新標題"
inputNewFolderName: "輸入新資料夾的名稱" inputNewFolderName: "輸入新資料夾的名稱"
circularReferenceFolder: "目標文件夾是您要移動的文件夾的子文件夾。" circularReferenceFolder: "目標文件夾是您要移動的文件夾的子文件夾。"
hasChildFilesOrFolders: "此文件夾不是空的,無法刪除。" hasChildFilesOrFolders: "此資料夾不是空的,無法刪除。"
copyUrl: "複製網址" copyUrl: "複製網址"
rename: "重新命名" rename: "重新命名"
avatar: "大頭貼" avatar: "大頭貼"
@ -525,14 +525,14 @@ state: "狀態"
sort: "排序" sort: "排序"
ascendingOrder: "昇冪" ascendingOrder: "昇冪"
descendingOrder: "降冪" descendingOrder: "降冪"
scratchpad: "暫存記憶體" scratchpad: "AiScript控制台"
scratchpadDescription: "AiScript控制台為AiScript提供了實驗環境。您可以在此編寫、執行和確認代碼與Firefish互動的结果。" scratchpadDescription: "AiScript控制台為AiScript提供了實驗環境。您可以在此編寫、執行和確認代碼與Firefish互動的结果。"
output: "輸出" output: "輸出"
script: "腳本" script: "腳本"
disablePagesScript: "停用頁面的AiScript腳本" disablePagesScript: "停用頁面的AiScript腳本"
updateRemoteUser: "更新遠端使用者資訊" updateRemoteUser: "更新遠端使用者資訊"
deleteAllFiles: "刪除所有檔案" deleteAllFiles: "刪除所有檔案"
deleteAllFilesConfirm: "要删除所有檔案嗎?" deleteAllFilesConfirm: "確定要刪除所有檔案嗎?"
removeAllFollowing: "解除所有追蹤" removeAllFollowing: "解除所有追蹤"
removeAllFollowingDescription: "解除{host}所有的追蹤。在伺服器不再存在時執行。" removeAllFollowingDescription: "解除{host}所有的追蹤。在伺服器不再存在時執行。"
userSuspended: "此使用者已被停用。" userSuspended: "此使用者已被停用。"
@ -773,7 +773,7 @@ gallery: "相簿"
recentPosts: "最新貼文" recentPosts: "最新貼文"
popularPosts: "熱門的貼文" popularPosts: "熱門的貼文"
shareWithNote: "在貼文中分享" shareWithNote: "在貼文中分享"
ads: "廣告" ads: "社群橫幅"
expiration: "期限" expiration: "期限"
memo: "備忘錄" memo: "備忘錄"
priority: "優先級" priority: "優先級"
@ -895,11 +895,11 @@ navbar: "導覽列"
shuffle: "隨機" shuffle: "隨機"
account: "帳戶" account: "帳戶"
move: "移動" move: "移動"
customKaTeXMacro: "自定義 KaTeX 宏" customKaTeXMacro: "自訂KaTeX巨集"
customKaTeXMacroDescription: "使用宏來輕鬆的輸入數學表達式吧!宏的用法與 LaTeX 中的命令定義相同。你可以使用 \\newcommand{\\ customKaTeXMacroDescription: "使用巨集來輕鬆輸入數學表達式吧!巨集的用法與 LaTeX 中的命令定義相同。你可以使用 \\newcommand{\\
name}{content} 或 \\newcommand{\\name}[number of arguments]{content} 來輸入數學表達式。舉個例子\\ name}{content} 或 \\newcommand{\\name}[number of arguments]{content} 來輸入數學表達式。舉例來說\\
newcommand{\\add}[2]{#1 + #2} 會將 \\add{3}{foo} 展開為 3 + foo。此外,宏名稱外的花括號 {} 可以被替換為圓括號 newcommand{\\add}[2]{#1 + #2} 會將 \\add{3}{foo} 展開為 3 + foo。巨集名稱除了可用大括號 {} 括起來之外,也可使用小括號
() 和方括號 [],這會影響用於參數的括號。每行只能夠定義一個宏,無法在中間換行,且無效的行將被忽略。只支持簡單字符串替換功能,不支持高級語法,如條件分支等。" () 和中括號 [],但使用於巨集參數的括號會有所變更。每行只能夠定義一個巨集,巨集中間無法間換。無效的行將被忽略。只支援簡單字串的替換功能,不支援條件分歧的高級語法。"
enableCustomKaTeXMacro: "啟用自定義 KaTeX 宏" enableCustomKaTeXMacro: "啟用自定義 KaTeX 宏"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。" description: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。"
@ -990,6 +990,7 @@ _aboutFirefish:
pleaseDonateToFirefish: 請考慮向 Firefish 贊助以支持其發展。 pleaseDonateToFirefish: 請考慮向 Firefish 贊助以支持其發展。
pleaseDonateToHost: 還請考慮捐贈給您在使用的伺服器 {host},以支援龐大的運營成本。 pleaseDonateToHost: 還請考慮捐贈給您在使用的伺服器 {host},以支援龐大的運營成本。
donateHost: 贊助給 {host} donateHost: 贊助給 {host}
misskeyContributors: Misskey的貢獻者
_nsfw: _nsfw:
respect: "隱藏敏感內容" respect: "隱藏敏感內容"
ignore: "不隱藏敏感內容" ignore: "不隱藏敏感內容"
@ -1258,6 +1259,8 @@ _2fa:
token: 兩步驟驗證金鑰 token: 兩步驟驗證金鑰
registerTOTPBeforeKey: 請設置身份驗證器應用程式以註冊安全金鑰或密碼。 registerTOTPBeforeKey: 請設置身份驗證器應用程式以註冊安全金鑰或密碼。
renewTOTPOk: 重新配置 renewTOTPOk: 重新配置
step3Title: 輸入驗證碼
securityKeyNotSupported: 您使用的瀏覧器不支援安全金鑰(Security key)。
_permissions: _permissions:
"read:account": "查看我的帳戶資訊" "read:account": "查看我的帳戶資訊"
"write:account": "更改我的帳戶資訊" "write:account": "更改我的帳戶資訊"
@ -1802,6 +1805,7 @@ _deck:
list: "清單" list: "清單"
mentions: "提及" mentions: "提及"
direct: "指定使用者" direct: "指定使用者"
channel: 頻道
secureMode: 安全模式(授權獲取) secureMode: 安全模式(授權獲取)
instanceSecurity: 伺服器安全性 instanceSecurity: 伺服器安全性
privateMode: 私人模式 privateMode: 私人模式
@ -1819,13 +1823,13 @@ adminCustomCssWarn: 除非你知道它的作用,否則請不要使用此設定
CSS 正常工作。 CSS 正常工作。
showUpdates: Firefish 更新時顯示彈出視窗 showUpdates: Firefish 更新時顯示彈出視窗
recommendedInstances: 建議的伺服器 recommendedInstances: 建議的伺服器
caption: 自動字幕 caption: 自動加上替代文字(alt)
enterSendsMessage: 在 Messaging 中按 Return 發送消息 (如關閉則是 Ctrl + Return) enterSendsMessage: 在 Messaging 中按 Return 發送消息 (如關閉則是 Ctrl + Return)
migrationConfirm: "您確定要將你的帳戶遷移到 {account} 嗎? 一旦這樣做,你將無法復原,而你將無法再次正常使用您的帳戶。\n另外請確保你已將此當前帳戶設置為您要遷移的帳戶。" migrationConfirm: "您確定要將你的帳戶遷移到 {account} 嗎? 一旦這樣做,你將無法復原,而你將無法再次正常使用您的帳戶。\n另外請確保你已將此當前帳戶設置為您要遷移的帳戶。"
customSplashIconsDescription: 每次用戶加載/重新加載頁面時,以換行符號分隔的自定啟動畫面圖標的網址將隨機顯示。請確保圖片位於靜態網址上,最好所有圖片解析度調整為 customSplashIconsDescription: 每次用戶加載/重新加載頁面時,以換行符號分隔的自定啟動畫面圖標的網址將隨機顯示。請確保圖片位於靜態網址上,最好所有圖片解析度調整為
192x192。 192x192。
accountMoved: '該使用者已遷移至新帳戶:' accountMoved: '該使用者已遷移至新帳戶:'
showAds: 顯示廣告 showAds: 顯示社群橫幅
noThankYou: 不用了,謝謝 noThankYou: 不用了,謝謝
selectInstance: 選擇伺服器 selectInstance: 選擇伺服器
enableRecommendedTimeline: 啟用推薦時間線 enableRecommendedTimeline: 啟用推薦時間線
@ -1869,7 +1873,7 @@ hiddenTags: 隱藏主題標籤
indexPosts: 索引貼文 indexPosts: 索引貼文
indexNotice: 現在開始索引。 這可能需要一段時間,請不要在一個小時內重啟你的伺服器。 indexNotice: 現在開始索引。 這可能需要一段時間,請不要在一個小時內重啟你的伺服器。
deleted: 已刪除 deleted: 已刪除
editNote: 編輯筆記 editNote: 編輯貼文
edited: '於 {date} {time} 編輯' edited: '於 {date} {time} 編輯'
userSaysSomethingReason: '{name} 說了 {reason}' userSaysSomethingReason: '{name} 說了 {reason}'
allowedInstancesDescription: 要加入聯邦白名單的服務器,每台伺服器用新行分隔(僅適用於私有模式)。 allowedInstancesDescription: 要加入聯邦白名單的服務器,每台伺服器用新行分隔(僅適用於私有模式)。
@ -1892,7 +1896,7 @@ listsDesc: 清單可以創建一個只有您指定用戶的時間線。 可以
flagSpeakAsCatDescription: 在喵咪模式下你的貼文會被喵化ヾ(•ω•`)o flagSpeakAsCatDescription: 在喵咪模式下你的貼文會被喵化ヾ(•ω•`)o
antennasDesc: "天線會顯示符合您設置條件的新貼文!\n 可以從時間線訪問它們。" antennasDesc: "天線會顯示符合您設置條件的新貼文!\n 可以從時間線訪問它們。"
expandOnNoteClick: 點擊以打開貼文 expandOnNoteClick: 點擊以打開貼文
expandOnNoteClickDesc: 如果禁用,您仍然可以通過右鍵單擊菜單或單擊時間戳來打開貼文。 expandOnNoteClickDesc: 即使停用,您仍然可以從右鍵選單或單擊發文時間來打開貼文。
hiddenTagsDescription: '列出您希望隱藏趨勢和探索的主題標籤(不帶 #)。 隱藏的主題標籤仍然可以通過其他方式發現。' hiddenTagsDescription: '列出您希望隱藏趨勢和探索的主題標籤(不帶 #)。 隱藏的主題標籤仍然可以通過其他方式發現。'
userSaysSomethingReasonQuote: '{name} 引用了一篇包含 {reason} 的貼文' userSaysSomethingReasonQuote: '{name} 引用了一篇包含 {reason} 的貼文'
silencedInstancesDescription: 列出您想要靜音的伺服器的網址。 您列出的伺服器內的帳戶將被視為“沉默”,只能發出追隨請求,如果不追隨則不能提及本地帳戶。 silencedInstancesDescription: 列出您想要靜音的伺服器的網址。 您列出的伺服器內的帳戶將被視為“沉默”,只能發出追隨請求,如果不追隨則不能提及本地帳戶。
@ -1943,3 +1947,18 @@ _filters:
withFile: 有檔案 withFile: 有檔案
alt: 替代文字 alt: 替代文字
xl: 特大 xl: 特大
inputNotMatch: 輸入不一致
delete2faConfirm: 二階段認證(2FA)將被完全刪除。是否繼續?
_dialog:
charactersBelow: 字數不足! 當前 {current} / 限制 {min}
charactersExceeded: 超過字數限制! 當前 {current} / 限制 {max}
_skinTones:
yellow: 黃色
exportZip: 匯出ZIP
_feeds:
atom: Atom
rss: RSS
emojiPackCreator: 表情包的作者
importZip: 匯入ZIP
delete2fa: 停用二階段認證(2FA)
confirm: 確認

View File

@ -1,12 +1,12 @@
{ {
"name": "firefish", "name": "firefish",
"version": "1.0.4-beta2", "version": "1.0.4-beta3",
"codename": "aqua", "codename": "aqua",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://git.joinfirefish.org/firefish/firefish.git" "url": "https://git.joinfirefish.org/firefish/firefish.git"
}, },
"packageManager": "pnpm@8.7.1", "packageManager": "pnpm@8.7.6",
"private": true, "private": true,
"scripts": { "scripts": {
"rebuild": "pnpm run clean && ./scripts/build-greet.sh && pnpm -r --parallel run build && pnpm run gulp", "rebuild": "pnpm run clean && ./scripts/build-greet.sh && pnpm -r --parallel run build && pnpm run gulp",
@ -47,6 +47,10 @@
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "1.0.0", "@biomejs/biome": "1.0.0",
"@biomejs/cli-darwin-arm64": "^1.0.0",
"@biomejs/cli-darwin-x64": "^1.0.0",
"@biomejs/cli-linux-arm64": "^1.0.0",
"@biomejs/cli-linux-x64": "^1.0.0",
"@types/gulp": "4.0.13", "@types/gulp": "4.0.13",
"@types/gulp-rename": "2.0.2", "@types/gulp-rename": "2.0.2",
"@types/node": "20.5.8", "@types/node": "20.5.8",

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -6,6 +6,7 @@ mod m20230531_180824_drop_reversi;
mod m20230627_185451_index_note_url; mod m20230627_185451_index_note_url;
mod m20230709_000510_move_antenna_to_cache; mod m20230709_000510_move_antenna_to_cache;
mod m20230806_170616_fix_antenna_stream_ids; mod m20230806_170616_fix_antenna_stream_ids;
mod m20230904_013244_is_indexable;
pub struct Migrator; pub struct Migrator;
@ -17,6 +18,7 @@ impl MigratorTrait for Migrator {
Box::new(m20230627_185451_index_note_url::Migration), Box::new(m20230627_185451_index_note_url::Migration),
Box::new(m20230709_000510_move_antenna_to_cache::Migration), Box::new(m20230709_000510_move_antenna_to_cache::Migration),
Box::new(m20230806_170616_fix_antenna_stream_ids::Migration), Box::new(m20230806_170616_fix_antenna_stream_ids::Migration),
Box::new(m20230904_013244_is_indexable::Migration),
] ]
} }
} }

View File

@ -0,0 +1,74 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.alter_table(
Table::alter()
.table(User::Table)
.add_column(
ColumnDef::new(User::IsIndexable)
.boolean()
.not_null()
.default(true),
)
.to_owned(),
)
.await?;
manager
.alter_table(
Table::alter()
.table(UserProfile::Table)
.add_column(
ColumnDef::new(UserProfile::IsIndexable)
.boolean()
.not_null()
.default(true),
)
.to_owned(),
)
.await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.alter_table(
Table::alter()
.table(User::Table)
.drop_column(User::IsIndexable)
.to_owned(),
)
.await?;
manager
.alter_table(
Table::alter()
.table(UserProfile::Table)
.drop_column(UserProfile::IsIndexable)
.to_owned(),
)
.await?;
Ok(())
}
}
/// Learn more at https://docs.rs/sea-query#iden
#[derive(Iden)]
enum User {
Table,
#[iden = "isIndexable"]
IsIndexable,
}
#[derive(Iden)]
enum UserProfile {
Table,
#[iden = "isIndexable"]
IsIndexable,
}

View File

@ -71,6 +71,8 @@ pub struct Model {
pub also_known_as: Option<String>, pub also_known_as: Option<String>,
#[sea_orm(column_name = "speakAsCat")] #[sea_orm(column_name = "speakAsCat")]
pub speak_as_cat: bool, pub speak_as_cat: bool,
#[sea_orm(column_name = "isIndexable")]
pub is_indexable: bool,
} }
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

View File

@ -75,6 +75,8 @@ pub struct Model {
pub moderation_note: String, pub moderation_note: String,
#[sea_orm(column_name = "preventAiLearning")] #[sea_orm(column_name = "preventAiLearning")]
pub prevent_ai_learning: bool, pub prevent_ai_learning: bool,
#[sea_orm(column_name = "isIndexable")]
pub is_indexable: bool,
} }
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

View File

@ -67,12 +67,12 @@
"fluent-ffmpeg": "2.1.2", "fluent-ffmpeg": "2.1.2",
"got": "13.0.0", "got": "13.0.0",
"gunzip-maybe": "^1.4.2", "gunzip-maybe": "^1.4.2",
"happy-dom": "^11.0.2",
"hpagent": "1.2.0", "hpagent": "1.2.0",
"ioredis": "5.3.2", "ioredis": "5.3.2",
"ip-cidr": "3.1.0", "ip-cidr": "3.1.0",
"is-svg": "5.0.0", "is-svg": "5.0.0",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"jsdom": "22.1.0",
"json5": "2.2.3", "json5": "2.2.3",
"jsonld": "8.2.1", "jsonld": "8.2.1",
"jsrsasign": "10.8.6", "jsrsasign": "10.8.6",
@ -131,6 +131,7 @@
"tar-stream": "^3.1.6", "tar-stream": "^3.1.6",
"tesseract.js": "^4.1.1", "tesseract.js": "^4.1.1",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tinyld": "^1.3.4",
"tmp": "0.2.1", "tmp": "0.2.1",
"twemoji-parser": "14.0.0", "twemoji-parser": "14.0.0",
"typeorm": "0.3.17", "typeorm": "0.3.17",
@ -142,13 +143,12 @@
}, },
"devDependencies": { "devDependencies": {
"@swc/cli": "^0.1.62", "@swc/cli": "^0.1.62",
"@swc/core": "^1.3.75", "@swc/core": "1.3.82",
"@types/adm-zip": "^0.5.0", "@types/adm-zip": "^0.5.0",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/escape-regexp": "0.0.1", "@types/escape-regexp": "0.0.1",
"@types/fluent-ffmpeg": "2.1.21", "@types/fluent-ffmpeg": "2.1.21",
"@types/js-yaml": "4.0.5", "@types/js-yaml": "4.0.5",
"@types/jsdom": "21.1.1",
"@types/jsonld": "1.5.9", "@types/jsonld": "1.5.9",
"@types/jsrsasign": "10.5.8", "@types/jsrsasign": "10.5.8",
"@types/koa": "2.13.8", "@types/koa": "2.13.8",

View File

@ -0,0 +1,7 @@
declare module "langdetect" {
interface DetectResult {
lang: string;
prob: number;
}
export function detect(words: string): DetectResult[];
}

View File

@ -23,7 +23,7 @@ export default async function () {
process.env.mode && ["web", "queue"].includes(process.env.mode) process.env.mode && ["web", "queue"].includes(process.env.mode)
? `(${process.env.mode})` ? `(${process.env.mode})`
: ""; : "";
const type = cluster.isPrimary ? "(master)" : "(worker)" const type = cluster.isPrimary ? "(master)" : "(worker)";
process.title = `Firefish ${mode} ${type}`; process.title = `Firefish ${mode} ${type}`;
if (cluster.isPrimary || envOption.disableClustering) { if (cluster.isPrimary || envOption.disableClustering) {

View File

@ -214,7 +214,9 @@ async function spawnWorkers(
workers.fill("web", 0, clusterLimits?.web); workers.fill("web", 0, clusterLimits?.web);
workers.fill("queue", clusterLimits?.web); workers.fill("queue", clusterLimits?.web);
bootLogger.info(`Starting ${clusterLimits?.web} web workers and ${clusterLimits?.queue} queue workers (total ${total})...`); bootLogger.info(
`Starting ${clusterLimits?.web} web workers and ${clusterLimits?.queue} queue workers (total ${total})...`,
);
await Promise.all(workers.map((mode) => spawnWorker(mode))); await Promise.all(workers.map((mode) => spawnWorker(mode)));
bootLogger.succ("All workers started"); bootLogger.succ("All workers started");
} }

View File

@ -1,2 +1,2 @@
// biome-ignore lint/suspicious/noExplicitAny: i have no idea // rome-ignore lint/suspicious/noExplicitAny: i have no idea
type FIXME = any; type FIXME = any;

View File

@ -1,4 +1,4 @@
import { JSDOM } from "jsdom"; import { Window } from "happy-dom";
import type * as mfm from "mfm-js"; import type * as mfm from "mfm-js";
import config from "@/config/index.js"; import config from "@/config/index.js";
import { intersperse } from "@/prelude/array.js"; import { intersperse } from "@/prelude/array.js";
@ -12,7 +12,7 @@ export function toHtml(
return null; return null;
} }
const { window } = new JSDOM(""); const { window } = new Window();
const doc = window.document; const doc = window.document;

View File

@ -12,29 +12,38 @@ const retryDelay = 100;
* @param timeout Lock timeout (ms), The timeout releases previous lock. * @param timeout Lock timeout (ms), The timeout releases previous lock.
* @returns Unlock function * @returns Unlock function
*/ */
export async function getApLock(uri: string, timeout = 30 * 1000) { export async function getApLock(
uri: string,
timeout = 30 * 1000,
): Promise<Mutex> {
const lock = new Mutex(redisClient, `ap-object:${uri}`, { const lock = new Mutex(redisClient, `ap-object:${uri}`, {
lockTimeout: timeout, lockTimeout: timeout,
retryInterval: retryDelay, retryInterval: retryDelay,
}); });
await lock.acquire(); await lock.acquire();
return lock;
} }
export async function getFetchInstanceMetadataLock( export async function getFetchInstanceMetadataLock(
host: string, host: string,
timeout = 30 * 1000, timeout = 30 * 1000,
) { ): Promise<Mutex> {
const lock = new Mutex(redisClient, `instance:${host}`, { const lock = new Mutex(redisClient, `instance:${host}`, {
lockTimeout: timeout, lockTimeout: timeout,
retryInterval: retryDelay, retryInterval: retryDelay,
}); });
await lock.acquire(); await lock.acquire();
return lock;
} }
export async function getChartInsertLock(lockKey: string, timeout = 30 * 1000) { export async function getChartInsertLock(
lockKey: string,
timeout = 30 * 1000,
): Promise<Mutex> {
const lock = new Mutex(redisClient, `chart-insert:${lockKey}`, { const lock = new Mutex(redisClient, `chart-insert:${lockKey}`, {
lockTimeout: timeout, lockTimeout: timeout,
retryInterval: retryDelay, retryInterval: retryDelay,
}); });
await lock.acquire(); await lock.acquire();
return lock;
} }

View File

@ -33,7 +33,9 @@ function checkWordMute(
if ( if (
keywords.length > 0 && keywords.length > 0 &&
keywords.every((keyword) => text.includes(keyword)) keywords.every((keyword) =>
text.toLowerCase().includes(keyword.toLowerCase()),
)
) )
return true; return true;
} else { } else {

View File

@ -167,6 +167,12 @@ export class UserProfile {
}) })
public noCrawle: boolean; public noCrawle: boolean;
@Column("boolean", {
default: true,
comment: "Whether User is indexable.",
})
public isIndexable: boolean;
@Column("boolean", { @Column("boolean", {
default: true, default: true,
}) })

View File

@ -265,6 +265,13 @@ export class User {
}) })
public driveCapacityOverrideMb: number | null; public driveCapacityOverrideMb: number | null;
@Index()
@Column("boolean", {
default: true,
comment: "Whether the User is indexable.",
})
public isIndexable: boolean;
constructor(data: Partial<User>) { constructor(data: Partial<User>) {
if (data == null) return; if (data == null) return;

View File

@ -27,6 +27,7 @@ import {
} from "@/misc/populate-emojis.js"; } from "@/misc/populate-emojis.js";
import { db } from "@/db/postgre.js"; import { db } from "@/db/postgre.js";
import { IdentifiableError } from "@/misc/identifiable-error.js"; import { IdentifiableError } from "@/misc/identifiable-error.js";
import { detect as detectLanguage_ } from "tinyld";
export async function populatePoll(note: Note, meId: User["id"] | null) { export async function populatePoll(note: Note, meId: User["id"] | null) {
const poll = await Polls.findOneByOrFail({ noteId: note.id }); const poll = await Polls.findOneByOrFail({ noteId: note.id });
@ -201,6 +202,8 @@ export const NoteRepository = db.getRepository(Note).extend({
note.emojis.concat(reactionEmojiNames), note.emojis.concat(reactionEmojiNames),
host, host,
); );
const lang = detectLanguage_(`${note.cw ?? ''}\n${note.text ?? ''}`) ?? "unknown"
const reactionEmoji = await populateEmojis(reactionEmojiNames, host); const reactionEmoji = await populateEmojis(reactionEmojiNames, host);
const packed: Packed<"Note"> = await awaitAll({ const packed: Packed<"Note"> = await awaitAll({
id: note.id, id: note.id,
@ -260,6 +263,7 @@ export const NoteRepository = db.getRepository(Note).extend({
: undefined, : undefined,
} }
: {}), : {}),
lang: lang,
}); });
if (packed.user.isCat && packed.user.speakAsCat && packed.text) { if (packed.user.isCat && packed.user.speakAsCat && packed.text) {

View File

@ -454,6 +454,7 @@ export const UserRepository = db.getRepository(User).extend({
isModerator: user.isModerator || falsy, isModerator: user.isModerator || falsy,
isBot: user.isBot || falsy, isBot: user.isBot || falsy,
isLocked: user.isLocked, isLocked: user.isLocked,
isIndexable: user.isIndexable,
isCat: user.isCat || falsy, isCat: user.isCat || falsy,
speakAsCat: user.speakAsCat || falsy, speakAsCat: user.speakAsCat || falsy,
instance: user.host instance: user.host

View File

@ -66,6 +66,11 @@ export const packedUserLiteSchema = {
nullable: false, nullable: false,
optional: true, optional: true,
}, },
isIndexable: {
type: "boolean",
nullable: false,
optional: true,
},
speakAsCat: { speakAsCat: {
type: "boolean", type: "boolean",
nullable: false, nullable: false,

View File

@ -32,6 +32,8 @@ export default async function (
// Interrupt if you block the announcement destination // Interrupt if you block the announcement destination
if (await shouldBlockInstance(extractDbHost(uri))) return; if (await shouldBlockInstance(extractDbHost(uri))) return;
const lock = await getApLock(uri);
try { try {
// Check if something with the same URI is already registered // Check if something with the same URI is already registered
const exist = await fetchNote(uri); const exist = await fetchNote(uri);
@ -78,6 +80,6 @@ export default async function (
uri, uri,
}); });
} finally { } finally {
await getApLock(uri); await lock.release();
} }
} }

View File

@ -31,6 +31,8 @@ export default async function (
} }
} }
const lock = await getApLock(uri);
try { try {
const exist = await fetchNote(note); const exist = await fetchNote(note);
if (exist) return "skip: note exists"; if (exist) return "skip: note exists";
@ -44,6 +46,6 @@ export default async function (
throw e; throw e;
} }
} finally { } finally {
await getApLock(uri); await lock.release();
} }
} }

View File

@ -13,6 +13,8 @@ export default async function (
): Promise<string> { ): Promise<string> {
logger.info(`Deleting the Note: ${uri}`); logger.info(`Deleting the Note: ${uri}`);
const lock = await getApLock(uri);
try { try {
const dbResolver = new DbResolver(); const dbResolver = new DbResolver();
const note = await dbResolver.getNoteFromApId(uri); const note = await dbResolver.getNoteFromApId(uri);
@ -37,6 +39,6 @@ export default async function (
await deleteNode(actor, note); await deleteNode(actor, note);
return "ok: note deleted"; return "ok: note deleted";
} finally { } finally {
await getApLock(uri); await lock.release();
} }
} }

View File

@ -415,6 +415,8 @@ export async function resolveNote(
`host ${extractDbHost(uri)} is blocked`, `host ${extractDbHost(uri)} is blocked`,
); );
const lock = await getApLock(uri);
try { try {
//#region Returns if already registered with this server //#region Returns if already registered with this server
const exist = await fetchNote(uri); const exist = await fetchNote(uri);
@ -437,7 +439,7 @@ export async function resolveNote(
// Since the attached Note Object may be disguised, always specify the uri and fetch it from the server. // Since the attached Note Object may be disguised, always specify the uri and fetch it from the server.
return await createNote(uri, resolver, true); return await createNote(uri, resolver, true);
} finally { } finally {
await getApLock(uri); await lock.release();
} }
} }

View File

@ -205,10 +205,10 @@ export async function createPerson(
if (typeof person.followers === "string") { if (typeof person.followers === "string") {
try { try {
let data = await fetch(person.followers, { const data = await fetch(person.followers, {
headers: { Accept: "application/json" }, headers: { Accept: "application/json" },
}); });
let json_data = JSON.parse(await data.text()); const json_data = JSON.parse(await data.text());
followersCount = json_data.totalItems; followersCount = json_data.totalItems;
} catch { } catch {
@ -220,10 +220,10 @@ export async function createPerson(
if (typeof person.following === "string") { if (typeof person.following === "string") {
try { try {
let data = await fetch(person.following, { const data = await fetch(person.following, {
headers: { Accept: "application/json" }, headers: { Accept: "application/json" },
}); });
let json_data = JSON.parse(await data.text()); const json_data = JSON.parse(await data.text());
followingCount = json_data.totalItems; followingCount = json_data.totalItems;
} catch (e) { } catch (e) {
@ -235,10 +235,10 @@ export async function createPerson(
if (typeof person.outbox === "string") { if (typeof person.outbox === "string") {
try { try {
let data = await fetch(person.outbox, { const data = await fetch(person.outbox, {
headers: { Accept: "application/json" }, headers: { Accept: "application/json" },
}); });
let json_data = JSON.parse(await data.text()); const json_data = JSON.parse(await data.text());
notesCount = json_data.totalItems; notesCount = json_data.totalItems;
} catch (e) { } catch (e) {
@ -302,6 +302,8 @@ export async function createPerson(
tags, tags,
isBot, isBot,
isCat: (person as any).isCat === true, isCat: (person as any).isCat === true,
speakAsCat: person.speakAsCat,
isIndexable: person.indexable,
}), }),
)) as IRemoteUser; )) as IRemoteUser;
@ -547,6 +549,7 @@ export async function updatePerson(
tags, tags,
isBot: getApType(object) !== "Person", isBot: getApType(object) !== "Person",
isCat: (person as any).isCat === true, isCat: (person as any).isCat === true,
isIndexable: person.indexable,
isLocked: !!person.manuallyApprovesFollowers, isLocked: !!person.manuallyApprovesFollowers,
movedToUri: person.movedTo || null, movedToUri: person.movedTo || null,
alsoKnownAs: person.alsoKnownAs || null, alsoKnownAs: person.alsoKnownAs || null,

View File

@ -30,6 +30,7 @@ export const renderActivity = (x: any): IActivity | null => {
Emoji: "toot:Emoji", Emoji: "toot:Emoji",
featured: "toot:featured", featured: "toot:featured",
discoverable: "toot:discoverable", discoverable: "toot:discoverable",
indexable: "toot:indexable",
// schema // schema
schema: "http://schema.org#", schema: "http://schema.org#",
PropertyValue: "schema:PropertyValue", PropertyValue: "schema:PropertyValue",

View File

@ -81,6 +81,8 @@ export async function renderPerson(user: ILocalUser) {
discoverable: !!user.isExplorable, discoverable: !!user.isExplorable,
publicKey: renderKey(user, keypair, "#main-key"), publicKey: renderKey(user, keypair, "#main-key"),
isCat: user.isCat, isCat: user.isCat,
speakAsCat: user.speakAsCat,
indexable: user.isIndexable,
attachment: attachment.length ? attachment : undefined, attachment: attachment.length ? attachment : undefined,
} as any; } as any;

View File

@ -190,8 +190,9 @@ export interface IActor extends IObject {
movedTo?: string; movedTo?: string;
alsoKnownAs?: string[]; alsoKnownAs?: string[];
discoverable?: boolean; discoverable?: boolean;
indexable?: boolean;
inbox: string; inbox: string;
sharedInbox?: string; // backward compatibility.. ig sharedInbox?: string; // Backwards compatibility
publicKey?: { publicKey?: {
id: string; id: string;
publicKeyPem: string; publicKeyPem: string;

View File

@ -60,6 +60,7 @@ export default define(meta, paramDef, async (ps, me) => {
emailVerified: profile.emailVerified, emailVerified: profile.emailVerified,
autoAcceptFollowed: profile.autoAcceptFollowed, autoAcceptFollowed: profile.autoAcceptFollowed,
noCrawle: profile.noCrawle, noCrawle: profile.noCrawle,
isIndexable: profile.isIndexable,
preventAiLearning: profile.preventAiLearning, preventAiLearning: profile.preventAiLearning,
alwaysMarkNsfw: profile.alwaysMarkNsfw, alwaysMarkNsfw: profile.alwaysMarkNsfw,
autoSensitive: profile.autoSensitive, autoSensitive: profile.autoSensitive,

View File

@ -120,6 +120,7 @@ export const paramDef = {
isBot: { type: "boolean" }, isBot: { type: "boolean" },
isCat: { type: "boolean" }, isCat: { type: "boolean" },
speakAsCat: { type: "boolean" }, speakAsCat: { type: "boolean" },
isIndexable: { type: "boolean" },
injectFeaturedNote: { type: "boolean" }, injectFeaturedNote: { type: "boolean" },
receiveAnnouncementEmail: { type: "boolean" }, receiveAnnouncementEmail: { type: "boolean" },
alwaysMarkNsfw: { type: "boolean" }, alwaysMarkNsfw: { type: "boolean" },
@ -206,6 +207,10 @@ export default define(meta, paramDef, async (ps, _user, token) => {
if (typeof ps.preventAiLearning === "boolean") if (typeof ps.preventAiLearning === "boolean")
profileUpdates.preventAiLearning = ps.preventAiLearning; profileUpdates.preventAiLearning = ps.preventAiLearning;
if (typeof ps.isCat === "boolean") updates.isCat = ps.isCat; if (typeof ps.isCat === "boolean") updates.isCat = ps.isCat;
if (typeof ps.isIndexable === "boolean") {
updates.isIndexable = ps.isIndexable;
profileUpdates.isIndexable = ps.isIndexable;
}
if (typeof ps.speakAsCat === "boolean") updates.speakAsCat = ps.speakAsCat; if (typeof ps.speakAsCat === "boolean") updates.speakAsCat = ps.speakAsCat;
if (typeof ps.injectFeaturedNote === "boolean") if (typeof ps.injectFeaturedNote === "boolean")
profileUpdates.injectFeaturedNote = ps.injectFeaturedNote; profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;

View File

@ -608,7 +608,7 @@ export default define(meta, paramDef, async (ps, user) => {
throw new ApiError(meta.errors.noSuchNote); throw new ApiError(meta.errors.noSuchNote);
} }
if (publishing) { if (publishing && user.isIndexable) {
index(note, true); index(note, true);
// Publish update event for the updated note details // Publish update event for the updated note details

View File

@ -87,8 +87,12 @@ export default define(meta, paramDef, async (ps, user) => {
.andWhere( .andWhere(
new Brackets((qb) => { new Brackets((qb) => {
qb.where("note.userId = :meId", { meId: user.id }); qb.where("note.userId = :meId", { meId: user.id });
if (hasFollowing) if (hasFollowing) {
qb.orWhere(`note.userId IN (${followingQuery.getQuery()})`); qb.orWhere(
`note.userId IN (${followingQuery.getQuery()})`,
followingQuery.getParameters(),
);
}
}), }),
) )
.innerJoinAndSelect("note.user", "user") .innerJoinAndSelect("note.user", "user")

View File

@ -4,7 +4,6 @@ import config from "@/config/index.js";
import { Converter } from "opencc-js"; import { Converter } from "opencc-js";
import { getAgentByUrl } from "@/misc/fetch.js"; import { getAgentByUrl } from "@/misc/fetch.js";
import { fetchMeta } from "@/misc/fetch-meta.js"; import { fetchMeta } from "@/misc/fetch-meta.js";
import { Notes } from "@/models/index.js";
import { ApiError } from "../../error.js"; import { ApiError } from "../../error.js";
import { getNote } from "../../common/getters.js"; import { getNote } from "../../common/getters.js";
import define from "../../define.js"; import define from "../../define.js";
@ -12,7 +11,7 @@ import define from "../../define.js";
export const meta = { export const meta = {
tags: ["notes"], tags: ["notes"],
requireCredential: false, requireCredential: true,
requireCredentialPrivateMode: true, requireCredentialPrivateMode: true,
res: { res: {

View File

@ -1,19 +1,13 @@
import { Entity } from "megalodon"; import { Entity } from "megalodon";
import config from "@/config/index.js"; import config from "@/config/index.js";
import { fetchMeta } from "@/misc/fetch-meta.js"; import { fetchMeta } from "@/misc/fetch-meta.js";
import { Users, Notes } from "@/models/index.js";
import { IsNull } from "typeorm";
import { MAX_NOTE_TEXT_LENGTH, FILE_TYPE_BROWSERSAFE } from "@/const.js"; import { MAX_NOTE_TEXT_LENGTH, FILE_TYPE_BROWSERSAFE } from "@/const.js";
export async function getInstance( export async function getInstance(
response: Entity.Instance, response: Entity.Instance,
contact: Entity.Account, contact: Entity.Account,
) { ) {
const [meta, totalUsers, totalStatuses] = await Promise.all([ const meta = await fetchMeta(true);
fetchMeta(true),
Users.count({ where: { host: IsNull() } }),
Notes.count({ where: { userHost: IsNull() } }),
]);
return { return {
uri: response.uri, uri: response.uri,
@ -27,8 +21,8 @@ export async function getInstance(
version: `3.0.0 (compatible; Firefish ${config.version})`, version: `3.0.0 (compatible; Firefish ${config.version})`,
urls: response.urls, urls: response.urls,
stats: { stats: {
user_count: await totalUsers, user_count: response.stats.user_count,
status_count: await totalStatuses, status_count: response.stats.status_count,
domain_count: response.stats.domain_count, domain_count: response.stats.domain_count,
}, },
thumbnail: response.thumbnail || "/static-assets/transparent.png", thumbnail: response.thumbnail || "/static-assets/transparent.png",

View File

@ -23,7 +23,7 @@ export default class extends Channel {
return; return;
} }
this.withReplies = params.withReplies as boolean; this.withReplies = params != null ? !!params.withReplies : true;
// Subscribe events // Subscribe events
this.subscriber.on("notesStream", this.onNote); this.subscriber.on("notesStream", this.onNote);

View File

@ -16,7 +16,7 @@ export default class extends Channel {
} }
public async init(params: any) { public async init(params: any) {
this.withReplies = params.withReplies as boolean; this.withReplies = params != null ? !!params.withReplies : true;
// Subscribe events // Subscribe events
this.subscriber.on("notesStream", this.onNote); this.subscriber.on("notesStream", this.onNote);

View File

@ -25,7 +25,7 @@ export default class extends Channel {
) )
return; return;
this.withReplies = params.withReplies as boolean; this.withReplies = params != null ? !!params.withReplies : true;
// Subscribe events // Subscribe events
this.subscriber.on("notesStream", this.onNote); this.subscriber.on("notesStream", this.onNote);

View File

@ -22,7 +22,7 @@ export default class extends Channel {
return; return;
} }
this.withReplies = params.withReplies as boolean; this.withReplies = params != null ? !!params.withReplies : true;
// Subscribe events // Subscribe events
this.subscriber.on("notesStream", this.onNote); this.subscriber.on("notesStream", this.onNote);

View File

@ -25,7 +25,7 @@ export default class extends Channel {
) )
return; return;
this.withReplies = params.withReplies as boolean; this.withReplies = params != null ? !!params.withReplies : true;
// Subscribe events // Subscribe events
this.subscriber.on("notesStream", this.onNote); this.subscriber.on("notesStream", this.onNote);

View File

@ -22,7 +22,7 @@
"purpose": "any" "purpose": "any"
}, },
{ {
"src": "/static-assets/icons/512.png", "src": "/static-assets/icons/maskable.png",
"sizes": "512x512", "sizes": "512x512",
"type": "image/png", "type": "image/png",
"purpose": "maskable" "purpose": "maskable"

View File

@ -24,9 +24,11 @@ block meta
unless privateMode unless privateMode
if profile.noCrawle if profile.noCrawle
meta(name='robots' content='noindex') meta(name='robots' content='noindex')
if profile.preventAiLearning if profile.preventAiLearning
meta(name='robots' content='noai') meta(name='robots' content='noai')
meta(name='robots' content='noimageai') meta(name='robots' content='noimageai')
meta(name='GPTBot' content='noindex')
meta(name='misskey:user-username' content=user.username) meta(name='misskey:user-username' content=user.username)
meta(name='misskey:user-id' content=user.id) meta(name='misskey:user-id' content=user.id)

View File

@ -430,6 +430,7 @@ export default abstract class Chart<T extends Schema> {
? `${this.name}:${date}:${span}:${group}` ? `${this.name}:${date}:${span}:${group}`
: `${this.name}:${date}:${span}`; : `${this.name}:${date}:${span}`;
const lock = await getChartInsertLock(lockKey);
try { try {
// ロック内でもう1回チェックする // ロック内でもう1回チェックする
const currentLog = (await repository.findOneBy({ const currentLog = (await repository.findOneBy({
@ -465,14 +466,14 @@ export default abstract class Chart<T extends Schema> {
return log; return log;
} finally { } finally {
await getChartInsertLock(lockKey); await lock.release();
} }
} }
protected commit(diff: Commit<T>, group: string | null = null): void { protected commit(diff: Commit<T>, group: string | null = null): void {
for (const [k, v] of Object.entries(diff)) { for (const [k, v] of Object.entries(diff)) {
if (v == null || v === 0 || (Array.isArray(v) && v.length === 0)) if (v == null || v === 0 || (Array.isArray(v) && v.length === 0))
// biome-ignore lint/performance/noDelete: needs to be deleted not just set to undefined // rome-ignore lint/performance/noDelete: needs to be deleted not just set to undefined
delete diff[k]; delete diff[k];
} }
this.buffer.push({ this.buffer.push({

View File

@ -1,13 +1,12 @@
import { URL } from "node:url"; import { URL } from "node:url";
import { JSDOM } from "jsdom"; import { Window } from "happy-dom";
import fetch from "node-fetch"; import fetch from "node-fetch";
import tinycolor from "tinycolor2"; import tinycolor from "tinycolor2";
import { getJson, getHtml, getAgentByUrl } from "@/misc/fetch.js"; import { getJson, getAgentByUrl } from "@/misc/fetch.js";
import type { Instance } from "@/models/entities/instance.js"; import type { Instance } from "@/models/entities/instance.js";
import { Instances } from "@/models/index.js"; import { Instances } from "@/models/index.js";
import { getFetchInstanceMetadataLock } from "@/misc/app-lock.js"; import { getFetchInstanceMetadataLock } from "@/misc/app-lock.js";
import Logger from "./logger.js"; import Logger from "./logger.js";
import type { DOMWindow } from "jsdom";
const logger = new Logger("metadata", "cyan"); const logger = new Logger("metadata", "cyan");
@ -15,6 +14,8 @@ export async function fetchInstanceMetadata(
instance: Instance, instance: Instance,
force = false, force = false,
): Promise<void> { ): Promise<void> {
const lock = await getFetchInstanceMetadataLock(instance.host);
if (!force) { if (!force) {
const _instance = await Instances.findOneBy({ host: instance.host }); const _instance = await Instances.findOneBy({ host: instance.host });
const now = Date.now(); const now = Date.now();
@ -22,7 +23,7 @@ export async function fetchInstanceMetadata(
_instance?.infoUpdatedAt && _instance?.infoUpdatedAt &&
now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 24 now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 24
) { ) {
await getFetchInstanceMetadataLock(instance.host); await lock.release();
return; return;
} }
} }
@ -78,7 +79,7 @@ export async function fetchInstanceMetadata(
} catch (e) { } catch (e) {
logger.error(`Failed to update metadata of ${instance.host}: ${e}`); logger.error(`Failed to update metadata of ${instance.host}: ${e}`);
} finally { } finally {
await getFetchInstanceMetadataLock(instance.host); await lock.release();
} }
} }
@ -149,14 +150,12 @@ async function fetchNodeinfo(instance: Instance): Promise<NodeInfo> {
} }
} }
async function fetchDom(instance: Instance): Promise<DOMWindow["document"]> { async function fetchDom(instance: Instance): Promise<Window["document"]> {
logger.info(`Fetching HTML of ${instance.host} ...`); logger.info(`Fetching HTML of ${instance.host} ...`);
const url = `https://${instance.host}`; const window = new Window({
url: `https://${instance.host}`,
const html = await getHtml(url); });
const { window } = new JSDOM(html);
const doc = window.document; const doc = window.document;
return doc; return doc;
@ -176,7 +175,7 @@ async function fetchManifest(
async function fetchFaviconUrl( async function fetchFaviconUrl(
instance: Instance, instance: Instance,
doc: DOMWindow["document"] | null, doc: Window["document"] | null,
): Promise<string | null> { ): Promise<string | null> {
const url = `https://${instance.host}`; const url = `https://${instance.host}`;
@ -208,7 +207,7 @@ async function fetchFaviconUrl(
async function fetchIconUrl( async function fetchIconUrl(
instance: Instance, instance: Instance,
doc: DOMWindow["document"] | null, doc: Window["document"] | null,
manifest: Record<string, any> | null, manifest: Record<string, any> | null,
): Promise<string | null> { ): Promise<string | null> {
if (manifest?.icons && manifest.icons.length > 0 && manifest.icons[0].src) { if (manifest?.icons && manifest.icons.length > 0 && manifest.icons[0].src) {
@ -240,7 +239,7 @@ async function fetchIconUrl(
async function getThemeColor( async function getThemeColor(
info: NodeInfo | null, info: NodeInfo | null,
doc: DOMWindow["document"] | null, doc: Window["document"] | null,
manifest: Record<string, any> | null, manifest: Record<string, any> | null,
): Promise<string | null> { ): Promise<string | null> {
const themeColor = const themeColor =
@ -258,9 +257,9 @@ async function getThemeColor(
async function getSiteName( async function getSiteName(
info: NodeInfo | null, info: NodeInfo | null,
doc: DOMWindow["document"] | null, doc: Window["document"] | null,
manifest: Record<string, any> | null, manifest: Record<string, any> | null,
): Promise<string | null> { ): Promise<string | undefined | null> {
if (info?.metadata) { if (info?.metadata) {
if (info.metadata.nodeName || info.metadata.name) { if (info.metadata.nodeName || info.metadata.name) {
return info.metadata.nodeName || info.metadata.name; return info.metadata.nodeName || info.metadata.name;
@ -286,7 +285,7 @@ async function getSiteName(
async function getDescription( async function getDescription(
info: NodeInfo | null, info: NodeInfo | null,
doc: DOMWindow["document"] | null, doc: Window["document"] | null,
manifest: Record<string, any> | null, manifest: Record<string, any> | null,
): Promise<string | null> { ): Promise<string | null> {
if (info?.metadata) { if (info?.metadata) {

View File

@ -1,11 +1,12 @@
import { getHtml } from "@/misc/fetch.js"; import { getHtml } from "@/misc/fetch.js";
import { JSDOM } from "jsdom"; import { Window } from "happy-dom";
import config from "@/config/index.js"; import config from "@/config/index.js";
async function getRelMeLinks(url: string): Promise<string[]> { async function getRelMeLinks(url: string): Promise<string[]> {
try { try {
const html = await getHtml(url); const dom = new Window({
const dom = new JSDOM(html); url: url,
});
const allLinks = [...dom.window.document.querySelectorAll("a, link")]; const allLinks = [...dom.window.document.querySelectorAll("a, link")];
const relMeLinks = allLinks const relMeLinks = allLinks
.filter((a) => { .filter((a) => {

View File

@ -165,11 +165,12 @@ export default async (
createdAt: User["createdAt"]; createdAt: User["createdAt"];
isBot: User["isBot"]; isBot: User["isBot"];
inbox?: User["inbox"]; inbox?: User["inbox"];
isIndexable?: User["isIndexable"];
}, },
data: Option, data: Option,
silent = false, silent = false,
) => ) =>
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: FIXME // rome-ignore lint/suspicious/noAsyncPromiseExecutor: FIXME
new Promise<Note>(async (res, rej) => { new Promise<Note>(async (res, rej) => {
const dontFederateInitially = data.visibility === "hidden"; const dontFederateInitially = data.visibility === "hidden";
@ -652,7 +653,9 @@ export default async (
} }
// Register to search database // Register to search database
if (user.isIndexable) {
await index(note, false); await index(note, false);
}
}); });
async function renderNoteOrRenoteActivity(data: Option, note: Note) { async function renderNoteOrRenoteActivity(data: Option, note: Note) {

View File

@ -151,6 +151,7 @@ describe("ユーザー", () => {
carefulBot: user.carefulBot, carefulBot: user.carefulBot,
autoAcceptFollowed: user.autoAcceptFollowed, autoAcceptFollowed: user.autoAcceptFollowed,
noCrawle: user.noCrawle, noCrawle: user.noCrawle,
isIndexable: user.isIndexable,
preventAiLearning: user.preventAiLearning, preventAiLearning: user.preventAiLearning,
isExplorable: user.isExplorable, isExplorable: user.isExplorable,
isDeleted: user.isDeleted, isDeleted: user.isDeleted,
@ -529,6 +530,8 @@ describe("ユーザー", () => {
{ parameters: (): object => ({ autoAcceptFollowed: false }) }, { parameters: (): object => ({ autoAcceptFollowed: false }) },
{ parameters: (): object => ({ noCrawle: true }) }, { parameters: (): object => ({ noCrawle: true }) },
{ parameters: (): object => ({ noCrawle: false }) }, { parameters: (): object => ({ noCrawle: false }) },
{ parameters: (): object => ({ isIndexable: true }) },
{ parameters: (): object => ({ isIndexable: false }) },
{ parameters: (): object => ({ preventAiLearning: false }) }, { parameters: (): object => ({ preventAiLearning: false }) },
{ parameters: (): object => ({ preventAiLearning: true }) }, { parameters: (): object => ({ preventAiLearning: true }) },
{ parameters: (): object => ({ isBot: true }) }, { parameters: (): object => ({ isBot: true }) },

View File

@ -63,7 +63,7 @@
"katex": "0.16.8", "katex": "0.16.8",
"matter-js": "0.19.0", "matter-js": "0.19.0",
"mfm-js": "0.23.3", "mfm-js": "0.23.3",
"photoswipe": "5.3.8", "photoswipe": "5.3.9",
"prettier": "3.0.3", "prettier": "3.0.3",
"prettier-plugin-vue": "1.1.6", "prettier-plugin-vue": "1.1.6",
"prismjs": "1.29.0", "prismjs": "1.29.0",
@ -81,6 +81,7 @@
"three": "0.156.0", "three": "0.156.0",
"throttle-debounce": "5.0.0", "throttle-debounce": "5.0.0",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tinyld": "^1.3.4",
"tsc-alias": "1.8.7", "tsc-alias": "1.8.7",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"twemoji-parser": "14.0.0", "twemoji-parser": "14.0.0",

View File

@ -28,6 +28,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, onMounted, ref } from "vue"; import { nextTick, onMounted, ref } from "vue";
import { vibrate } from "@/scripts/vibrate";
const props = defineProps<{ const props = defineProps<{
type?: "button" | "submit" | "reset"; type?: "button" | "submit" | "reset";
@ -93,6 +94,8 @@ function onMousedown(evt: MouseEvent): void {
circleCenterY, circleCenterY,
); );
vibrate(10);
window.setTimeout(() => { window.setTimeout(() => {
ripple.style.transform = "scale(" + scale / 2 + ")"; ripple.style.transform = "scale(" + scale / 2 + ")";
}, 1); }, 1);

View File

@ -782,6 +782,8 @@ onBeforeUnmount(() => {
overflow: auto; overflow: auto;
font-size: 0.9em; font-size: 0.9em;
box-shadow: 0 1px 0 var(--divider); box-shadow: 0 1px 0 var(--divider);
position: fixed;
background-color: var(--bg);
&, &,
* { * {
@ -841,6 +843,7 @@ onBeforeUnmount(() => {
flex: 1; flex: 1;
overflow: auto; overflow: auto;
padding: var(--margin); padding: var(--margin);
margin-top: 40px;
&, &,
* { * {

View File

@ -23,6 +23,7 @@
<button <button
v-for="emoji in searchResultCustom" v-for="emoji in searchResultCustom"
:key="emoji.id" :key="emoji.id"
v-vibrate="50"
class="_button item" class="_button item"
:title="emoji.name" :title="emoji.name"
tabindex="0" tabindex="0"

View File

@ -4,6 +4,7 @@
<div class="title"><slot name="header"></slot></div> <div class="title"><slot name="header"></slot></div>
<div class="divider"></div> <div class="divider"></div>
<button <button
v-vibrate="5"
class="_button" class="_button"
:aria-expanded="showBody" :aria-expanded="showBody"
:aria-controls="bodyId" :aria-controls="bodyId"

View File

@ -69,6 +69,7 @@ import { i18n } from "@/i18n";
import { $i } from "@/account"; import { $i } from "@/account";
import { getUserMenu } from "@/scripts/get-user-menu"; import { getUserMenu } from "@/scripts/get-user-menu";
import { useRouter } from "@/router"; import { useRouter } from "@/router";
import { vibrate } from "@/scripts/vibrate";
const router = useRouter(); const router = useRouter();
@ -154,6 +155,7 @@ async function onClick() {
await os.api("following/create", { await os.api("following/create", {
userId: props.user.id, userId: props.user.id,
}); });
vibrate([30, 40, 100]);
hasPendingFollowRequestFromYou.value = true; hasPendingFollowRequestFromYou.value = true;
} }
} }

View File

@ -4,6 +4,7 @@
v-tooltip="capitalize(instance.softwareName)" v-tooltip="capitalize(instance.softwareName)"
class="hpaizdrt" class="hpaizdrt"
:style="bg" :style="bg"
@click.stop="openServerInfo"
> >
<img class="icon" :src="getInstanceIcon(instance)" aria-hidden="true" /> <img class="icon" :src="getInstanceIcon(instance)" aria-hidden="true" />
<span class="name">{{ instance.name }}</span> <span class="name">{{ instance.name }}</span>
@ -16,6 +17,8 @@ import { ref } from "vue";
import { instanceName } from "@/config"; import { instanceName } from "@/config";
import { instance as Instance } from "@/instance"; import { instance as Instance } from "@/instance";
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy"; import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
import { defaultStore } from "@/store";
import { pageWindow } from "@/os";
const props = defineProps<{ const props = defineProps<{
instance?: { instance?: {
@ -24,6 +27,7 @@ const props = defineProps<{
themeColor?: string; themeColor?: string;
softwareName?: string; softwareName?: string;
}; };
host: string | null;
}>(); }>();
const ticker = ref<HTMLElement | null>(null); const ticker = ref<HTMLElement | null>(null);
@ -83,6 +87,13 @@ function getInstanceIcon(instance): string {
"/client-assets/dummy.png" "/client-assets/dummy.png"
); );
} }
function openServerInfo() {
if (!defaultStore.state.openServerInfo) return;
const instanceInfoUrl =
props.host == null ? "/about" : `/instance-info/${props.host}`;
pageWindow(instanceInfoUrl);
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -8,6 +8,7 @@
<div> <div>
<div <div
ref="itemsEl" ref="itemsEl"
v-vibrate="5"
class="rrevdjwt _popup _shadow" class="rrevdjwt _popup _shadow"
:class="{ center: align === 'center', asDrawer }" :class="{ center: align === 'center', asDrawer }"
:style="{ :style="{

View File

@ -6,6 +6,7 @@
ref="el" ref="el"
v-hotkey="keymap" v-hotkey="keymap"
v-size="{ max: [500, 350] }" v-size="{ max: [500, 350] }"
v-vibrate="5"
:aria-label="accessibleLabel" :aria-label="accessibleLabel"
class="tkcbzcuz note-container" class="tkcbzcuz note-container"
:tabindex="!isDeleted ? '-1' : null" :tabindex="!isDeleted ? '-1' : null"
@ -111,7 +112,7 @@
></MkSubNoteContent> ></MkSubNoteContent>
<div v-if="translating || translation" class="translation"> <div v-if="translating || translation" class="translation">
<MkLoading v-if="translating" mini /> <MkLoading v-if="translating" mini />
<div v-else class="translated"> <div v-else-if="translation != null" class="translated">
<b <b
>{{ >{{
i18n.t("translatedFrom", { i18n.t("translatedFrom", {
@ -219,6 +220,18 @@
<i class="ph-minus ph-bold ph-lg"></i> <i class="ph-minus ph-bold ph-lg"></i>
</button> </button>
<XQuoteButton class="button" :note="appearNote" /> <XQuoteButton class="button" :note="appearNote" />
<button
v-if="
$i != null &&
isForeignLanguage &&
translation == null
"
v-tooltip.noDelay.bottom="i18n.ts.translate"
class="button _button"
@click.stop="translate"
>
<i class="ph-translate ph-bold ph-lg"></i>
</button>
<button <button
ref="menuButton" ref="menuButton"
v-tooltip.noDelay.bottom="i18n.ts.more" v-tooltip.noDelay.bottom="i18n.ts.more"
@ -259,6 +272,7 @@ import { computed, inject, onMounted, ref } from "vue";
import * as mfm from "mfm-js"; import * as mfm from "mfm-js";
import type { Ref } from "vue"; import type { Ref } from "vue";
import type * as misskey from "firefish-js"; import type * as misskey from "firefish-js";
import { detect as detectLanguage_ } from "tinyld";
import MkSubNoteContent from "./MkSubNoteContent.vue"; import MkSubNoteContent from "./MkSubNoteContent.vue";
import MkNoteSub from "@/components/MkNoteSub.vue"; import MkNoteSub from "@/components/MkNoteSub.vue";
import XNoteHeader from "@/components/MkNoteHeader.vue"; import XNoteHeader from "@/components/MkNoteHeader.vue";
@ -340,12 +354,68 @@ const isMyRenote = $i && $i.id === note.value.userId;
const showContent = ref(false); const showContent = ref(false);
const isDeleted = ref(false); const isDeleted = ref(false);
const muted = ref( const muted = ref(
getWordSoftMute(note.value, $i, defaultStore.state.mutedWords), getWordSoftMute(
note.value,
$i,
defaultStore.state.mutedWords,
defaultStore.state.mutedLangs,
),
); );
const translation = ref(null); const translation = ref(null);
const translating = ref(false); const translating = ref(false);
const enableEmojiReactions = defaultStore.state.enableEmojiReactions; const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
const expandOnNoteClick = defaultStore.state.expandOnNoteClick; const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
const lang = localStorage.getItem("lang");
const translateLang = localStorage.getItem("translateLang");
function detectLanguage(text: string) {
const nodes = mfm.parse(text);
const filtered = mfm.extract(nodes, (node) => {
return node.type === "text" || node.type === "quote";
});
const purified = mfm.toString(filtered);
return detectLanguage_(purified);
}
const isForeignLanguage: boolean =
defaultStore.state.detectPostLanguage &&
appearNote.value.text != null &&
(() => {
const targetLang = (translateLang || lang || navigator.language)?.slice(
0,
2,
);
const postLang = detectLanguage(appearNote.value.text);
return postLang !== "" && postLang !== targetLang;
})();
async function translate_(noteId, targetLang: string) {
return await os.api("notes/translate", {
noteId,
targetLang,
});
}
async function translate() {
if (translation.value != null) return;
translating.value = true;
translation.value = await translate_(
appearNote.value.id,
translateLang || lang || navigator.language,
);
// use UI language as the second translation language
if (
translateLang != null &&
lang != null &&
translateLang !== lang &&
(!translation.value ||
translation.value.sourceLang.toLowerCase() ===
translateLang.slice(0, 2))
)
translation.value = await translate_(appearNote.value.id, lang);
translating.value = false;
}
const keymap = { const keymap = {
r: () => reply(true), r: () => reply(true),

View File

@ -210,7 +210,12 @@ const reactButton = ref<HTMLElement>();
const showContent = ref(false); const showContent = ref(false);
const isDeleted = ref(false); const isDeleted = ref(false);
const muted = ref( const muted = ref(
getWordSoftMute(note.value, $i, defaultStore.state.mutedWords), getWordSoftMute(
note.value,
$i,
defaultStore.state.mutedWords,
defaultStore.state.mutedLangs,
),
); );
const translation = ref(null); const translation = ref(null);
const translating = ref(false); const translating = ref(false);

View File

@ -40,6 +40,7 @@
v-if="showTicker" v-if="showTicker"
class="ticker" class="ticker"
:instance="note.user.instance" :instance="note.user.instance"
:host="note.user.host"
/> />
</div> </div>
</div> </div>

View File

@ -124,6 +124,18 @@
<i class="ph-minus ph-bold ph-lg"></i> <i class="ph-minus ph-bold ph-lg"></i>
</button> </button>
<XQuoteButton class="button" :note="appearNote" /> <XQuoteButton class="button" :note="appearNote" />
<button
v-if="
$i != null &&
isForeignLanguage &&
translation == null
"
v-tooltip.noDelay.bottom="i18n.ts.translate"
class="button _button"
@click.stop="translate"
>
<i class="ph-translate ph-bold ph-lg"></i>
</button>
<button <button
ref="menuButton" ref="menuButton"
v-tooltip.noDelay.bottom="i18n.ts.more" v-tooltip.noDelay.bottom="i18n.ts.more"
@ -180,6 +192,8 @@
import { computed, inject, ref } from "vue"; import { computed, inject, ref } from "vue";
import type { Ref } from "vue"; import type { Ref } from "vue";
import type * as misskey from "firefish-js"; import type * as misskey from "firefish-js";
import * as mfm from "mfm-js";
import { detect as detectLanguage_ } from "tinyld";
import XNoteHeader from "@/components/MkNoteHeader.vue"; import XNoteHeader from "@/components/MkNoteHeader.vue";
import MkSubNoteContent from "@/components/MkSubNoteContent.vue"; import MkSubNoteContent from "@/components/MkSubNoteContent.vue";
import XReactionsViewer from "@/components/MkReactionsViewer.vue"; import XReactionsViewer from "@/components/MkReactionsViewer.vue";
@ -252,7 +266,12 @@ const appearNote = computed(() =>
); );
const isDeleted = ref(false); const isDeleted = ref(false);
const muted = ref( const muted = ref(
getWordSoftMute(note.value, $i, defaultStore.state.mutedWords), getWordSoftMute(
note.value,
$i,
defaultStore.state.mutedWords,
defaultStore.state.mutedLangs,
),
); );
const translation = ref(null); const translation = ref(null);
const translating = ref(false); const translating = ref(false);
@ -266,6 +285,57 @@ const replies: misskey.entities.Note[] =
.reverse() ?? []; .reverse() ?? [];
const enableEmojiReactions = defaultStore.state.enableEmojiReactions; const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
const expandOnNoteClick = defaultStore.state.expandOnNoteClick; const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
const lang = localStorage.getItem("lang");
const translateLang = localStorage.getItem("translateLang");
function detectLanguage(text: string) {
const nodes = mfm.parse(text);
const filtered = mfm.extract(nodes, (node) => {
return node.type === "text" || node.type === "quote";
});
const purified = mfm.toString(filtered);
return detectLanguage_(purified);
}
const isForeignLanguage: boolean =
defaultStore.state.detectPostLanguage &&
appearNote.value.text != null &&
(() => {
const targetLang = (translateLang || lang || navigator.language)?.slice(
0,
2,
);
const postLang = detectLanguage(appearNote.value.text);
return postLang !== "" && postLang !== targetLang;
})();
async function translate_(noteId, targetLang: string) {
return await os.api("notes/translate", {
noteId,
targetLang,
});
}
async function translate() {
if (translation.value != null) return;
translating.value = true;
translation.value = await translate_(
appearNote.value.id,
translateLang || lang || navigator.language,
);
// use UI language as the second translation language
if (
translateLang != null &&
lang != null &&
translateLang !== lang &&
(!translation.value ||
translation.value.sourceLang.toLowerCase() ===
translateLang.slice(0, 2))
)
translation.value = await translate_(appearNote.value.id, lang);
translating.value = false;
}
useNoteCapture({ useNoteCapture({
rootEl: el, rootEl: el,

View File

@ -29,7 +29,7 @@
>{{ maxTextLength - textLength }}</span >{{ maxTextLength - textLength }}</span
> >
<span v-if="localOnly" class="local-only" <span v-if="localOnly" class="local-only"
><i class="ph-hand-fist ph-bold ph-lg"></i ><i class="ph-users ph-bold ph-lg"></i
></span> ></span>
<button <button
ref="visibilityButton" ref="visibilityButton"
@ -275,6 +275,7 @@ import { uploadFile } from "@/scripts/upload";
import { deepClone } from "@/scripts/clone"; import { deepClone } from "@/scripts/clone";
import XCheatSheet from "@/components/MkCheatSheetDialog.vue"; import XCheatSheet from "@/components/MkCheatSheetDialog.vue";
import { preprocess } from "@/scripts/preprocess"; import { preprocess } from "@/scripts/preprocess";
import { vibrate } from "@/scripts/vibrate";
const modal = inject("modal"); const modal = inject("modal");
@ -937,6 +938,7 @@ async function post() {
text: err.message + "\n" + (err as any).id, text: err.message + "\n" + (err as any).id,
}); });
}); });
vibrate([10, 20, 10, 20, 10, 20, 60]);
} }
function cancel() { function cancel() {

View File

@ -3,6 +3,7 @@
v-if="count > 0" v-if="count > 0"
ref="buttonRef" ref="buttonRef"
v-ripple="canToggle" v-ripple="canToggle"
v-vibrate="[10, 30, 40]"
class="hkzvhatu _button" class="hkzvhatu _button"
:class="{ :class="{
reacted: note.myReaction == reaction, reacted: note.myReaction == reaction,

View File

@ -32,6 +32,7 @@ import { useTooltip } from "@/scripts/use-tooltip";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
import type { MenuItem } from "@/types/menu"; import type { MenuItem } from "@/types/menu";
import { vibrate } from "@/scripts/vibrate";
const props = defineProps<{ const props = defineProps<{
note: misskey.entities.Note; note: misskey.entities.Note;
@ -194,9 +195,10 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
if (canRenote.value) { if (canRenote.value) {
buttonActions.push({ buttonActions.push({
text: `${i18n.ts.renote} (${i18n.ts.local})`, text: `${i18n.ts.renote} (${i18n.ts.local})`,
icon: "ph-hand-fist ph-bold ph-lg", icon: "ph-users ph-bold ph-lg",
danger: false, danger: false,
action: () => { action: () => {
vibrate([30, 30, 60]);
os.api( os.api(
"notes/create", "notes/create",
props.note.visibility === "specified" props.note.visibility === "specified"

View File

@ -1,6 +1,7 @@
<template> <template>
<button <button
v-tooltip.noDelay.bottom="i18n.ts._gallery.like" v-tooltip.noDelay.bottom="i18n.ts._gallery.like"
v-vibrate="[30, 50, 50]"
class="button _button" class="button _button"
@click.stop="star($event)" @click.stop="star($event)"
> >

View File

@ -2,6 +2,7 @@
<button <button
ref="buttonRef" ref="buttonRef"
v-tooltip.noDelay.bottom="i18n.ts._gallery.like" v-tooltip.noDelay.bottom="i18n.ts._gallery.like"
v-vibrate="[30, 50, 50]"
class="button _button" class="button _button"
:class="$style.root" :class="$style.root"
@click.stop="toggleStar($event)" @click.stop="toggleStar($event)"

View File

@ -19,7 +19,7 @@
<span v-if="note.localOnly" :class="$style.localOnly" <span v-if="note.localOnly" :class="$style.localOnly"
><i ><i
v-tooltip="i18n.ts._visibility.localOnly" v-tooltip="i18n.ts._visibility.localOnly"
class="ph-hand-fist ph-bold ph-lg" class="ph-users ph-bold ph-lg"
></i ></i
></span> ></span>
</template> </template>

View File

@ -97,7 +97,7 @@
@click="localOnly = !localOnly" @click="localOnly = !localOnly"
> >
<div :class="$style.icon"> <div :class="$style.icon">
<i class="ph-hand-fist ph-bold ph-lg"></i> <i class="ph-users ph-bold ph-lg"></i>
</div> </div>
<div :class="$style.body"> <div :class="$style.body">
<span :class="$style.itemTitle">{{ <span :class="$style.itemTitle">{{

View File

@ -12,6 +12,7 @@
<button <button
v-if="displayBackButton" v-if="displayBackButton"
v-tooltip.noDelay="i18n.ts.goBack" v-tooltip.noDelay="i18n.ts.goBack"
v-vibrate="5"
class="_buttonIcon button icon backButton" class="_buttonIcon button icon backButton"
@click.stop="goBack()" @click.stop="goBack()"
@touchstart="preventDrag" @touchstart="preventDrag"
@ -20,6 +21,7 @@
</button> </button>
<MkAvatar <MkAvatar
v-if="narrow && props.displayMyAvatar && $i" v-if="narrow && props.displayMyAvatar && $i"
v-vibrate="5"
class="avatar button" class="avatar button"
:user="$i" :user="$i"
:disable-preview="true" :disable-preview="true"
@ -77,6 +79,7 @@
v-for="tab in tabs" v-for="tab in tabs"
:ref="(el) => (tabRefs[tab.key] = el)" :ref="(el) => (tabRefs[tab.key] = el)"
v-tooltip.noDelay="tab.title" v-tooltip.noDelay="tab.title"
v-vibrate="5"
class="tab _button" class="tab _button"
:class="{ :class="{
active: tab.key != null && tab.key === props.tab, active: tab.key != null && tab.key === props.tab,
@ -108,6 +111,7 @@
<template v-for="action in actions"> <template v-for="action in actions">
<button <button
v-tooltip.noDelay="action.text" v-tooltip.noDelay="action.text"
v-vibrate="5"
class="_buttonIcon button" class="_buttonIcon button"
:class="{ highlighted: action.highlighted }" :class="{ highlighted: action.highlighted }"
@click.stop="action.handler" @click.stop="action.handler"

View File

@ -44,15 +44,15 @@ const relative = computed<string>(() => {
const ago = (now.value - _time) / 1000; /* ms */ const ago = (now.value - _time) / 1000; /* ms */
return ago >= 31536000 return ago >= 31536000
? i18n.t("_ago.yearsAgo", { n: Math.round(ago / 31536000).toString() }) ? i18n.t("_ago.yearsAgo", { n: Math.floor(ago / 31536000).toString() })
: ago >= 2592000 : ago >= 2592000
? i18n.t("_ago.monthsAgo", { n: Math.round(ago / 2592000).toString() }) ? i18n.t("_ago.monthsAgo", { n: Math.floor(ago / 2592000).toString() })
: ago >= 604800 : ago >= 604800
? i18n.t("_ago.weeksAgo", { n: Math.round(ago / 604800).toString() }) ? i18n.t("_ago.weeksAgo", { n: Math.floor(ago / 604800).toString() })
: ago >= 86400 : ago >= 86400
? i18n.t("_ago.daysAgo", { n: Math.round(ago / 86400).toString() }) ? i18n.t("_ago.daysAgo", { n: Math.floor(ago / 86400).toString() })
: ago >= 3600 : ago >= 3600
? i18n.t("_ago.hoursAgo", { n: Math.round(ago / 3600).toString() }) ? i18n.t("_ago.hoursAgo", { n: Math.floor(ago / 3600).toString() })
: ago >= 60 : ago >= 60
? i18n.t("_ago.minutesAgo", { n: (~~(ago / 60)).toString() }) ? i18n.t("_ago.minutesAgo", { n: (~~(ago / 60)).toString() })
: ago >= 10 : ago >= 10

View File

@ -12,6 +12,7 @@ import clickAnime from "./click-anime";
import panel from "./panel"; import panel from "./panel";
import adaptiveBorder from "./adaptive-border"; import adaptiveBorder from "./adaptive-border";
import focus from "./focus"; import focus from "./focus";
import vibrate from "./vibrate";
export default function (app: App) { export default function (app: App) {
app.directive("userPreview", userPreview); app.directive("userPreview", userPreview);
@ -27,4 +28,5 @@ export default function (app: App) {
app.directive("panel", panel); app.directive("panel", panel);
app.directive("adaptive-border", adaptiveBorder); app.directive("adaptive-border", adaptiveBorder);
app.directive("focus", focus); app.directive("focus", focus);
app.directive("vibrate", vibrate);
} }

View File

@ -0,0 +1,11 @@
import type { Directive } from "vue";
import { vibrate } from "../scripts/vibrate";
export default {
mounted(el, binding) {
const pattern = (binding.value as VibratePattern) ?? 20;
el.addEventListener("mousedown", () => {
vibrate(pattern);
});
},
} as Directive;

View File

@ -431,7 +431,7 @@ const headerActions = computed(() => [
const headerTabs = computed(() => [ const headerTabs = computed(() => [
{ {
key: "local", key: "local",
icon: "ph-hand-fist ph-bold ph-lg", icon: "ph-users ph-bold ph-lg",
title: i18n.ts.local, title: i18n.ts.local,
}, },
{ {

View File

@ -45,61 +45,47 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent } from "vue"; import { onMounted, ref } from "vue";
import XForm from "./auth.form.vue"; import XForm from "./auth.form.vue";
import MkSignin from "@/components/MkSignin.vue"; import MkSignin from "@/components/MkSignin.vue";
import MkKeyValue from "@/components/MkKeyValue.vue"; import MkKeyValue from "@/components/MkKeyValue.vue";
import * as os from "@/os"; import * as os from "@/os";
import { login } from "@/account"; import { $i, login } from "@/account";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
export default defineComponent({ const props = defineProps<{
components: { token: string;
XForm, }>();
MkSignin, const state = ref("");
MkKeyValue, const session = ref();
}, const fetching = ref(true);
props: ["token"], const auth_code = ref("");
data() {
return {
state: null,
session: null,
fetching: true,
i18n,
auth_code: null,
};
},
mounted() {
if (!this.$i) return;
// Fetch session onMounted(() => {
os.api("auth/session/show", { if (!$i) return;
token: this.token,
})
.then((session) => {
this.session = session;
this.fetching = false;
// os.api("auth/session/show", { token: props.token })
if (this.session.app.isAuthorized) { .then((sess: any) => {
os.api("auth/accept", { session.value = sess;
token: this.session.token, fetching.value = false;
}).then(() => {
this.accepted(); if (session.value.app.isAuthorized) {
}); os.api("auth/accept", { token: session.value.token }).then(
() => {
accepted();
},
);
} else { } else {
this.state = "waiting"; state.value = "waiting";
} }
}) })
.catch((error) => { .catch((error) => {
this.state = "fetch-session-error"; state.value = "fetch-session-error";
this.fetching = false; fetching.value = false;
}); });
}, });
methods: {
accepted() {
this.state = "accepted";
const getUrlParams = () => const getUrlParams = () =>
window.location.search window.location.search
.substring(1) .substring(1)
@ -109,44 +95,42 @@ export default defineComponent({
result[k] = decodeURI(v); result[k] = decodeURI(v);
return result; return result;
}, {}); }, {});
const accepted = () => {
state.value = "accepted";
const isMastodon = !!getUrlParams().mastodon; const isMastodon = !!getUrlParams().mastodon;
if (this.session.app.callbackUrl && isMastodon) { if (session.value.app.callbackUrl && isMastodon) {
const callbackUrl = new URL(this.session.app.callbackUrl); const redirectUri = decodeURIComponent(getUrlParams().redirect_uri);
callbackUrl.searchParams.append("code", this.session.token); if (!session.value.app.callbackUrl.split("\n").includes(redirectUri)) {
state.value = "fetch-session-error";
fetching.value = false;
throw new Error("Callback URI doesn't match registered app");
}
const callbackUrl = new URL(redirectUri);
callbackUrl.searchParams.append("code", session.value.token);
if (getUrlParams().state) if (getUrlParams().state)
callbackUrl.searchParams.append( callbackUrl.searchParams.append("state", getUrlParams().state);
"state",
getUrlParams().state,
);
location.href = callbackUrl.toString(); location.href = callbackUrl.toString();
} else if (this.session.app.callbackUrl) { } else if (session.value.app.callbackUrl) {
const url = new URL(this.session.app.callbackUrl); const url = new URL(session.value.app.callbackUrl);
if ( if (
[ ["javascript:", "file:", "data:", "mailto:", "tel:"].includes(
"javascript:", url.protocol,
"file:",
"data:",
"mailto:",
"tel:",
].includes(url.protocol)
) )
throw new Error("invalid url");
if (
this.session.app.callbackUrl === "urn:ietf:wg:oauth:2.0:oob"
) { ) {
this.auth_code = this.session.token; throw new Error("Invalid URL");
}
if (session.value.app.callbackUrl === "urn:ietf:wg:oauth:2.0:oob") {
auth_code.value = session.value.token;
} else { } else {
location.href = `${this.session.app.callbackUrl}?token=${ location.href = `${session.value.app.callbackUrl}?token=${
this.session.token session.value.token
}&code=${this.session.token}&state=${ }&code=${session.value.token}&state=${getUrlParams().state || ""}`;
getUrlParams().state || ""
}`;
} }
} }
}, };
onLogin(res) {
const onLogin = (res) => {
login(res.i); login(res.i);
}, };
},
});
</script> </script>

View File

@ -95,6 +95,7 @@ const paginationComponent = ref<InstanceType<typeof MkPagination>>();
const pagination = { const pagination = {
endpoint: "following/requests/list" as const, endpoint: "following/requests/list" as const,
limit: 10, limit: 10,
noPaging: true,
}; };
function accept(user) { function accept(user) {

View File

@ -120,6 +120,7 @@ import {
import * as os from "@/os"; import * as os from "@/os";
import { stream } from "@/stream"; import { stream } from "@/stream";
import * as sound from "@/scripts/sound"; import * as sound from "@/scripts/sound";
import { vibrate } from "@/scripts/vibrate";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { $i } from "@/account"; import { $i } from "@/account";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
@ -251,6 +252,7 @@ function onDrop(ev: DragEvent): void {
function onMessage(message) { function onMessage(message) {
sound.play("chat"); sound.play("chat");
vibrate([30, 30, 30]);
const _isBottom = isBottomVisible(rootEl.value, 64); const _isBottom = isBottomVisible(rootEl.value, 64);

View File

@ -17,6 +17,15 @@
</template> </template>
</FormSelect> </FormSelect>
<FormSelect v-model="translateLang" class="_formBlock">
<template #label>
{{ i18n.ts.languageForTranslation }}
</template>
<option v-for="x in langs" :key="x[0]" :value="x[0]">
{{ x[1] }}
</option>
</FormSelect>
<FormRadios v-model="overridedDeviceKind" class="_formBlock"> <FormRadios v-model="overridedDeviceKind" class="_formBlock">
<template #label>{{ i18n.ts.overridedDeviceKind }}</template> <template #label>{{ i18n.ts.overridedDeviceKind }}</template>
<option :value="null">{{ i18n.ts.auto }}</option> <option :value="null">{{ i18n.ts.auto }}</option>
@ -71,6 +80,12 @@
{{ i18n.ts.reflectMayTakeTime }}</template {{ i18n.ts.reflectMayTakeTime }}</template
></FormSwitch ></FormSwitch
> >
<FormSwitch v-model="detectPostLanguage" class="_formBlock">{{
i18n.ts.detectPostLanguage
}}</FormSwitch>
<FormSwitch v-model="openServerInfo" class="_formBlock">{{
i18n.ts.openServerInfo
}}</FormSwitch>
<FormSelect v-model="serverDisconnectedBehavior" class="_formBlock"> <FormSelect v-model="serverDisconnectedBehavior" class="_formBlock">
<template #label>{{ i18n.ts.whenServerDisconnected }}</template> <template #label>{{ i18n.ts.whenServerDisconnected }}</template>
@ -121,6 +136,12 @@
class="_formBlock" class="_formBlock"
>{{ i18n.ts.disableShowingAnimatedImages }}</FormSwitch >{{ i18n.ts.disableShowingAnimatedImages }}</FormSwitch
> >
<FormSwitch
v-model="vibrate"
class="_formBlock"
@click="demoVibrate"
>{{ i18n.ts.vibrate }}
</FormSwitch>
<FormRadios v-model="fontSize" class="_formBlock"> <FormRadios v-model="fontSize" class="_formBlock">
<template #label>{{ i18n.ts.fontSize }}</template> <template #label>{{ i18n.ts.fontSize }}</template>
<option :value="null"> <option :value="null">
@ -258,7 +279,7 @@ import FormSection from "@/components/form/section.vue";
import FormLink from "@/components/form/link.vue"; import FormLink from "@/components/form/link.vue";
import MkLink from "@/components/MkLink.vue"; import MkLink from "@/components/MkLink.vue";
import { langs } from "@/config"; import { langs } from "@/config";
import { defaultStore } from "@/store"; import { ColdDeviceStorage, defaultStore } from "@/store";
import * as os from "@/os"; import * as os from "@/os";
import { unisonReload } from "@/scripts/unison-reload"; import { unisonReload } from "@/scripts/unison-reload";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
@ -266,6 +287,7 @@ import { definePageMetadata } from "@/scripts/page-metadata";
import { deviceKind } from "@/scripts/device-kind"; import { deviceKind } from "@/scripts/device-kind";
const lang = ref(localStorage.getItem("lang")); const lang = ref(localStorage.getItem("lang"));
const translateLang = ref(localStorage.getItem("translateLang"));
const fontSize = ref(localStorage.getItem("fontSize")); const fontSize = ref(localStorage.getItem("fontSize"));
const useSystemFont = ref(localStorage.getItem("useSystemFont") != null); const useSystemFont = ref(localStorage.getItem("useSystemFont") != null);
@ -279,6 +301,10 @@ async function reloadAsk() {
unisonReload(); unisonReload();
} }
function demoVibrate() {
window.navigator.vibrate(100);
}
const overridedDeviceKind = computed( const overridedDeviceKind = computed(
defaultStore.makeGetterSetter("overridedDeviceKind"), defaultStore.makeGetterSetter("overridedDeviceKind"),
); );
@ -315,6 +341,7 @@ const disableDrawer = computed(defaultStore.makeGetterSetter("disableDrawer"));
const disableShowingAnimatedImages = computed( const disableShowingAnimatedImages = computed(
defaultStore.makeGetterSetter("disableShowingAnimatedImages"), defaultStore.makeGetterSetter("disableShowingAnimatedImages"),
); );
const vibrate = computed(ColdDeviceStorage.makeGetterSetter("vibrate"));
const loadRawImages = computed(defaultStore.makeGetterSetter("loadRawImages")); const loadRawImages = computed(defaultStore.makeGetterSetter("loadRawImages"));
const imageNewTab = computed(defaultStore.makeGetterSetter("imageNewTab")); const imageNewTab = computed(defaultStore.makeGetterSetter("imageNewTab"));
const nsfw = computed(defaultStore.makeGetterSetter("nsfw")); const nsfw = computed(defaultStore.makeGetterSetter("nsfw"));
@ -357,6 +384,12 @@ const showAdminUpdates = computed(
const showTimelineReplies = computed( const showTimelineReplies = computed(
defaultStore.makeGetterSetter("showTimelineReplies"), defaultStore.makeGetterSetter("showTimelineReplies"),
); );
const detectPostLanguage = computed(
defaultStore.makeGetterSetter("detectPostLanguage"),
);
const openServerInfo = computed(
defaultStore.makeGetterSetter("openServerInfo"),
);
watch(swipeOnDesktop, () => { watch(swipeOnDesktop, () => {
defaultStore.set("swipeOnMobile", true); defaultStore.set("swipeOnMobile", true);
@ -367,6 +400,10 @@ watch(lang, () => {
localStorage.removeItem("locale"); localStorage.removeItem("locale");
}); });
watch(translateLang, () => {
localStorage.setItem("translateLang", translateLang.value as string);
});
watch(fontSize, () => { watch(fontSize, () => {
if (fontSize.value == null) { if (fontSize.value == null) {
localStorage.removeItem("fontSize"); localStorage.removeItem("fontSize");
@ -386,6 +423,7 @@ watch(useSystemFont, () => {
watch( watch(
[ [
lang, lang,
translateLang,
fontSize, fontSize,
useSystemFont, useSystemFont,
enableInfiniteScroll, enableInfiniteScroll,

View File

@ -9,7 +9,7 @@
<template #empty <template #empty
><FormInfo>{{ i18n.ts.noUsers }}</FormInfo></template ><FormInfo>{{ i18n.ts.noUsers }}</FormInfo></template
> >
<template #default="{ items }"> <template #default="{ items }" class="_formlinks">
<FormLink <FormLink
v-for="mute in items" v-for="mute in items"
:key="mute.id" :key="mute.id"
@ -25,7 +25,7 @@
<template #empty <template #empty
><FormInfo>{{ i18n.ts.noUsers }}</FormInfo></template ><FormInfo>{{ i18n.ts.noUsers }}</FormInfo></template
> >
<template #default="{ items }"> <template #default="{ items }" class="_formlinks">
<FormLink <FormLink
v-for="block in items" v-for="block in items"
:key="block.id" :key="block.id"

View File

@ -117,6 +117,8 @@ const defaultStoreSaveKeys: (keyof (typeof defaultStore)["state"])[] = [
"enableEmojiReactions", "enableEmojiReactions",
"showEmojisInReactionNotifications", "showEmojisInReactionNotifications",
"showTimelineReplies", "showTimelineReplies",
"detectPostLanguage",
"openServerInfo",
]; ];
const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [ const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [
"lightTheme", "lightTheme",
@ -124,6 +126,7 @@ const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [
"syncDeviceDarkMode", "syncDeviceDarkMode",
"plugins", "plugins",
"mediaVolume", "mediaVolume",
"vibrate",
"sound_masterVolume", "sound_masterVolume",
"sound_note", "sound_note",
"sound_noteMy", "sound_noteMy",

View File

@ -52,6 +52,14 @@
i18n.ts.hideOnlineStatusDescription i18n.ts.hideOnlineStatusDescription
}}</template> }}</template>
</FormSwitch> </FormSwitch>
<FormSwitch
v-model="isIndexable"
class="_formBlock"
@update:modelValue="save()"
>
{{ i18n.ts.indexable }}
<template #caption>{{ i18n.ts.indexableDescription }}</template>
</FormSwitch>
<FormSwitch <FormSwitch
v-model="noCrawle" v-model="noCrawle"
class="_formBlock" class="_formBlock"
@ -155,6 +163,7 @@ import { definePageMetadata } from "@/scripts/page-metadata";
const isLocked = ref($i.isLocked); const isLocked = ref($i.isLocked);
const autoAcceptFollowed = ref($i.autoAcceptFollowed); const autoAcceptFollowed = ref($i.autoAcceptFollowed);
const noCrawle = ref($i.noCrawle); const noCrawle = ref($i.noCrawle);
const isIndexable = ref($i.isIndexable);
const isExplorable = ref($i.isExplorable); const isExplorable = ref($i.isExplorable);
const hideOnlineStatus = ref($i.hideOnlineStatus); const hideOnlineStatus = ref($i.hideOnlineStatus);
const publicReactions = ref($i.publicReactions); const publicReactions = ref($i.publicReactions);
@ -178,6 +187,7 @@ function save() {
isLocked: !!isLocked.value, isLocked: !!isLocked.value,
autoAcceptFollowed: !!autoAcceptFollowed.value, autoAcceptFollowed: !!autoAcceptFollowed.value,
noCrawle: !!noCrawle.value, noCrawle: !!noCrawle.value,
isIndexable: !!isIndexable.value,
isExplorable: !!isExplorable.value, isExplorable: !!isExplorable.value,
hideOnlineStatus: !!hideOnlineStatus.value, hideOnlineStatus: !!hideOnlineStatus.value,
publicReactions: !!publicReactions.value, publicReactions: !!publicReactions.value,

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