Merge branch 'develop' into beta
This commit is contained in:
commit
09dc0bb550
@ -3,7 +3,13 @@
|
||||
**What does this PR do?** _(Please give us a brief description of what this PR does.)_
|
||||
|
||||
**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 have made sure to test 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> -->
|
||||
|
@ -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.
|
||||
|
||||
## 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.
|
||||
PRs that do not have a clear set of do's and don'ts tend to be bloated and difficult to review.
|
||||
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.
|
||||
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.
|
||||
|
||||
## Well-known branches
|
||||
- The **`main`** branch is tracking the latest release and used for production purposes.
|
||||
- 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.
|
||||
- **`feature/*`** branches are reserved for the development of a specific feature
|
||||
|
||||
## Creating a PR
|
||||
Thank you for your PR! Before creating a PR, please check the following:
|
||||
- If possible, prefix the title with a keyword that identifies the type of this PR, 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.
|
||||
- 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.
|
||||
- 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`
|
||||
## Creating a merge request (MR)
|
||||
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 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 MR is rejected.
|
||||
- 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 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.
|
||||
- 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.
|
||||
- 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)
|
||||
- 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 🤗
|
||||
|
||||
@ -56,12 +57,12 @@ Be willing to comment on the good points and not just the things you want fixed
|
||||
|
||||
### Review perspective
|
||||
- Scope
|
||||
- Are the goals of the PR clear?
|
||||
- Is the granularity of the PR appropriate?
|
||||
- Are the goals of the MR clear?
|
||||
- Is the granularity of the MR appropriate?
|
||||
- Security
|
||||
- Does merging this PR create a vulnerability?
|
||||
- Does merging this MR create a vulnerability?
|
||||
- Performance
|
||||
- Will merging this PR cause unexpected performance degradation?
|
||||
- Will merging this MR cause unexpected performance degradation?
|
||||
- Is there a more efficient way?
|
||||
- Testing
|
||||
- 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?
|
||||
|
||||
## 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>
|
||||
```
|
||||
An actual domain will be assigned so you can test the federation.
|
||||
|
||||
# THE FOLLOWING IS OUTDATED:
|
||||
|
||||
## Merge
|
||||
|
||||
## 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.
|
||||
In addition, it will also automatically start the Misskey server process.
|
||||
|
||||
|
||||
# THE FOLLOWING IS OUTDATED:
|
||||
|
||||
## Testing
|
||||
- Test codes are located in [`/test`](/test).
|
||||
|
||||
|
@ -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 sounds, place only mp3 files in the `./custom/assets/sounds` directory.
|
||||
- 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
|
||||
|
||||
|
14
custom/assets/robots.txt
Normal file
14
custom/assets/robots.txt
Normal 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
9
docs/api-change.md
Normal 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.
|
@ -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.
|
@ -474,3 +474,4 @@ _preferencesBackups:
|
||||
cannotLoad: Неуспешно зареждане
|
||||
editWidgetsExit: Готово
|
||||
done: Готово
|
||||
emailRequiredForSignup: Изискване за адрес на е-поща за регистриране
|
||||
|
@ -1311,8 +1311,7 @@ yes: Sí
|
||||
no: No
|
||||
noCrawle: Rebutjar la indexació dels restrejadors
|
||||
driveUsage: Espai fet servir al Disk
|
||||
noCrawleDescription: No permetre que els buscadors guardin la informació de les pàgines
|
||||
de perfil, publicacions, Pàgines, etc.
|
||||
noCrawleDescription: Demanar als motors de cerca externs no indexar el teu contingut.
|
||||
alwaysMarkSensitive: Marcar per defecte com a NSFW
|
||||
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
|
||||
@ -2186,3 +2185,10 @@ confirm: Confirmar
|
||||
importZip: Importar ZIP
|
||||
exportZip: Exportar ZIP
|
||||
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
|
||||
|
@ -737,8 +737,7 @@ no: "Nein"
|
||||
driveFilesCount: "Anzahl der Dateien in Cloud-Drive"
|
||||
driveUsage: "Cloud-Drive-Auslastung"
|
||||
noCrawle: "Crawler-Indexierung ablehnen"
|
||||
noCrawleDescription: "Suchmaschinen bitten, die eigene Profilseite, Beiträge, Nutzer-Seiten
|
||||
usw. nicht zu indexieren."
|
||||
noCrawleDescription: "Externe Suchmaschinen auffordern, Ihre Inhalte nicht zu indizieren."
|
||||
lockedAccountInfo: "Auch wenn du Follow-Anfragen auf manuelle Bestätigung setzt, wird
|
||||
jeder deiner Posts öffentlich sichtbar sein, sofern du ihre Sichtbarkeit nicht auf
|
||||
\"Nur Follower\" setzt."
|
||||
@ -2104,7 +2103,7 @@ pwa: PWA installieren
|
||||
cw: Inhaltswarnung
|
||||
older: älter
|
||||
newer: neuer
|
||||
accessibility: Erreichbarkeit
|
||||
accessibility: Barrierefreiheit
|
||||
jumpToPrevious: Zum Vorherigen springen
|
||||
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.
|
||||
@ -2207,4 +2206,12 @@ addRe: Ein "re:" am Anfang des Kommentars hinzufügen, um einem Beitrag mit eine
|
||||
confirm: Bestätigen
|
||||
importZip: ZIP Importieren
|
||||
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
|
||||
|
@ -675,7 +675,7 @@ emptyToDisableSmtpAuth: "Leave username and password empty to disable SMTP verif
|
||||
smtpSecure: "Use implicit SSL/TLS for SMTP connections"
|
||||
smtpSecureInfo: "Turn this off when using STARTTLS"
|
||||
testEmail: "Test email delivery"
|
||||
wordMute: "Word mute"
|
||||
wordMute: "Word and language mutes"
|
||||
regexpError: "Regular Expression error"
|
||||
regexpErrorDescription: "An error occurred in the regular expression on line {line}
|
||||
of your {tab} word mutes:"
|
||||
@ -762,8 +762,7 @@ no: "No"
|
||||
driveFilesCount: "Number of Drive files"
|
||||
driveUsage: "Drive space usage"
|
||||
noCrawle: "Reject crawler indexing"
|
||||
noCrawleDescription: "Ask search engines to not index your profile page, posts, Pages,
|
||||
etc."
|
||||
noCrawleDescription: "Ask external search engines to not index your content."
|
||||
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."
|
||||
alwaysMarkSensitive: "Mark as NSFW by default"
|
||||
@ -1139,6 +1138,12 @@ confirm: "Confirm"
|
||||
importZip: "Import ZIP"
|
||||
exportZip: "Export ZIP"
|
||||
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:
|
||||
description: "Reduces the effort of server moderation through automatically recognizing
|
||||
@ -1370,14 +1375,19 @@ _menuDisplay:
|
||||
hide: "Hide"
|
||||
_wordMute:
|
||||
muteWords: "Muted words"
|
||||
muteLangs: "Muted Languages"
|
||||
muteWordsDescription: "Separate with spaces for an AND condition or with line breaks
|
||||
for an OR condition."
|
||||
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."
|
||||
langDescription: "Hide posts that match set language from the timeline."
|
||||
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
|
||||
if the conditions are changed."
|
||||
soft: "Soft"
|
||||
lang: "Language"
|
||||
hard: "Hard"
|
||||
mutedNotes: "Muted posts"
|
||||
_instanceMute:
|
||||
|
@ -874,7 +874,7 @@ pubSub: "Cuentas Pub/Sub"
|
||||
lastCommunication: "Última comunicación"
|
||||
resolved: "Resuelto"
|
||||
unresolved: "Sin resolver"
|
||||
breakFollow: "Dejar de seguir"
|
||||
breakFollow: "Quitar seguidor"
|
||||
itsOn: "¡Está encendido!"
|
||||
itsOff: "¡Está apagado!"
|
||||
emailRequiredForSignup: "Se requere una dirección de correo electrónico para el registro
|
||||
|
@ -309,11 +309,11 @@ emptyDrive: "Le Drive est vide"
|
||||
emptyFolder: "Le dossier est vide"
|
||||
unableToDelete: "Suppression impossible"
|
||||
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"
|
||||
circularReferenceFolder: "Le dossier de destination est un sous-dossier du dossier
|
||||
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 l’URL"
|
||||
rename: "Renommer"
|
||||
avatar: "Avatar"
|
||||
@ -605,7 +605,7 @@ disablePlayer: "Fermer le lecteur vidéo"
|
||||
expandTweet: "Étendre le tweet"
|
||||
themeEditor: "Éditeur de thèmes"
|
||||
description: "Description"
|
||||
describeFile: "Ajouter une description d'image"
|
||||
describeFile: "Ajouter une description"
|
||||
enterFileDescription: "Saisissez une description"
|
||||
author: "Auteur·rice"
|
||||
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."
|
||||
abuseReported: "Le rapport est envoyé. Merci."
|
||||
reporter: "Signalé par"
|
||||
reporteeOrigin: "Origine du signalement"
|
||||
reporteeOrigin: "Origine du signalé"
|
||||
reporterOrigin: "Origine du signalement"
|
||||
forwardReport: "Transférer le signalement à l’instance distante"
|
||||
send: "Envoyer"
|
||||
@ -724,8 +724,8 @@ no: "Non"
|
||||
driveFilesCount: "Nombre de fichiers dans le Drive"
|
||||
driveUsage: "Utilisation du Drive"
|
||||
noCrawle: "Refuser l'indexation par les robots"
|
||||
noCrawleDescription: "Demandez aux moteurs de recherche de ne pas indexer votre page
|
||||
de profil, vos publications, vos pages, etc."
|
||||
noCrawleDescription: "Demandez aux moteurs de recherche externes de ne pas indexer
|
||||
votre contenu."
|
||||
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
|
||||
que les demandes d'abonnement soient approuvées manuellement."
|
||||
@ -2085,7 +2085,7 @@ silenceThisInstance: Masquer ce serveur
|
||||
silencedInstances: Serveurs masqués
|
||||
silenced: Masqué
|
||||
deleted: Effacé
|
||||
editNote: Modifier publication
|
||||
editNote: Modifier la publication
|
||||
edited: 'Modifié à {date} {time}'
|
||||
flagShowTimelineRepliesDescription: Si activé, affiche dans le fil les réponses des
|
||||
utilisatieur·rice·s aux publications des autres.
|
||||
@ -2209,4 +2209,12 @@ addRe: Ajouter "re:" au début d’un avertissement de contenu (CW) en réponse
|
||||
confirm: Confirmer
|
||||
importZip: Importer ZIP
|
||||
exportZip: Exporter ZIP
|
||||
emojiPackCreator: Créateur de pack d’emoji
|
||||
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 d’afficher vos publications
|
||||
publiques
|
||||
openServerInfo: Afficher les informations du serveur en cliquant sur le bandeau de
|
||||
serveur d’une publication
|
||||
indexable: Indexable
|
||||
languageForTranslation: Langage post-traduction
|
||||
|
@ -81,7 +81,7 @@ deleteAndEdit: Eliminar e editar
|
||||
blockConfirm: Tes a certeza de querer bloquear esta conta?
|
||||
deleteAndEditConfirm: Tes a certeza de querer eliminar esta publicación e editala?
|
||||
Perderás todas as súas reaccións, promocións e respostas.
|
||||
editNote: Editar nota
|
||||
editNote: Editar publicación
|
||||
edited: Editado o {date} {time}
|
||||
sendMessage: Enviar unha mensaxe
|
||||
copyUsername: Copiar identificador
|
||||
@ -282,3 +282,76 @@ storageUsage: Uso da almacenaxe
|
||||
charts: Gráficas
|
||||
perHour: Por hora
|
||||
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.
|
||||
|
@ -245,7 +245,7 @@ currentPassword: "Kata sandi saat ini"
|
||||
newPassword: "Kata sandi baru"
|
||||
newPasswordRetype: "Ulangi kata sandi baru"
|
||||
attachFile: "Lampirkan berkas"
|
||||
more: "Lagi !"
|
||||
more: "Lagi!"
|
||||
featured: "Sorotan"
|
||||
usernameOrUserId: "Nama pengguna atau User ID"
|
||||
noSuchUser: "Pengguna tidak ditemukan"
|
||||
@ -721,8 +721,7 @@ no: "Tidak"
|
||||
driveFilesCount: "Jumlah berkas drive"
|
||||
driveUsage: "Penggunaan ruang penyimpanan drive"
|
||||
noCrawle: "Tolak pengindeksan crawler"
|
||||
noCrawleDescription: "Minta mesin pencari untuk tidak mengindeks halaman profil, postingan,
|
||||
Halaman kamu, dll."
|
||||
noCrawleDescription: "Minta mesin pencari eksternal untuk tidak mengindeks kontenmu."
|
||||
lockedAccountInfo: "Kecuali kamu mengatur visibilitas postingan milikmu ke \"Hanya
|
||||
pengikut\", postingan milikmu akan dapat dilihat oleh siapa saja, bahkan jika kamu
|
||||
memerlukan pengikut untuk disetujui secara manual."
|
||||
@ -1251,7 +1250,7 @@ _tutorial:
|
||||
step2_2: "Menyediakan beberapa informasi tentang siapa kamu akan membuatnya lebih
|
||||
mudah bagi orang lain untuk menentukan jika mereka ingin postinganmu atau mengikutimu."
|
||||
step3_1: "Sekarang saatnya mengikuti beberapa orang!"
|
||||
step3_2: "Linimasa beranda dan sosial berdasarkan siapa yang kamu ikuti, jadi coba
|
||||
step3_2: "Lini masa beranda dan sosial berdasarkan siapa yang kamu ikuti, jadi coba
|
||||
ikuti beberapa akun untuk memulai.\nKlik lingkaran plus pada pojok kanan atas
|
||||
pada profil untuk mengikuti mereka."
|
||||
step3_3: "Isilah di dalam modal dan tekan tombol pada atas kanan untuk memcatat
|
||||
@ -1280,7 +1279,7 @@ _tutorial:
|
||||
step5_6: Linimasa Rekomendasi {icon} adalah tempat kamu dapat melihat postingan
|
||||
dari server yang direkomendasikan admin.
|
||||
step6_4: Sekarang pergilah, bereksplorasi, dan senang-senang!
|
||||
step5_5: Linimasa Sosial {icon} adalah kombinasi antara linimasa Beranda dan Lokal.
|
||||
step5_5: Lini masa Sosial {icon} adalah kombinasi antara lini masa Beranda dan Lokal.
|
||||
step5_7: Linimasa Global {icon} adalah tempat kamu dapat melihat postingan orang-orang
|
||||
dari server yang terkoneksi.
|
||||
_2fa:
|
||||
@ -1436,7 +1435,7 @@ _visibility:
|
||||
public: "Publik"
|
||||
publicDescription: "Postinganmu akan terlihat di semua linimasa publik"
|
||||
home: "Tidak terdaftar"
|
||||
homeDescription: "Posting ke linimasa beranda saja"
|
||||
homeDescription: "Posting ke lini masa beranda saja"
|
||||
followers: "Pengikut"
|
||||
followersDescription: "Tampilkan ke pengikut dan pengguna yang disebut saja"
|
||||
specified: "Langsung"
|
||||
@ -1462,7 +1461,7 @@ _profile:
|
||||
metadata: "Informasi tambahan"
|
||||
metadataEdit: "Sunting 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!"
|
||||
metadataLabel: "Label"
|
||||
metadataContent: "Isi"
|
||||
@ -2169,3 +2168,10 @@ confirm: Konfirmasi
|
||||
importZip: Impor ZIP
|
||||
exportZip: Ekspor ZIP
|
||||
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
|
||||
|
@ -70,20 +70,12 @@ module.exports = Object.entries(locales).reduce(
|
||||
(a, [k, v]) => (
|
||||
(a[k] = (() => {
|
||||
const [lang] = k.split("-");
|
||||
switch (k) {
|
||||
case "en-US":
|
||||
return v;
|
||||
case "ja-JP":
|
||||
case "ja-KS":
|
||||
return merge(locales["en-US"], v);
|
||||
default:
|
||||
return merge(
|
||||
locales["en-US"],
|
||||
locales["ja-JP"],
|
||||
locales[`${lang}-${primaries[lang]}`] || {},
|
||||
v,
|
||||
);
|
||||
}
|
||||
return k === "en-US" ? v :
|
||||
merge(
|
||||
locales["en-US"],
|
||||
locales[`${lang}-${primaries[lang]}`] || {},
|
||||
v,
|
||||
);
|
||||
})()),
|
||||
a
|
||||
),
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -66,7 +66,7 @@ import: "インポート"
|
||||
export: "エクスポート"
|
||||
files: "ファイル"
|
||||
download: "ダウンロード"
|
||||
driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?これにより、このファイルが添付されている投稿も削除されます。"
|
||||
driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?これにより、添付ファイルとして含まれているすべての投稿から削除されます。"
|
||||
unfollowConfirm: "{name}さんのフォローを解除しますか?"
|
||||
exportRequested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。"
|
||||
importRequested: "インポートをリクエストしました。これには時間がかかる場合があります。"
|
||||
@ -988,6 +988,8 @@ youHaveUnreadAnnouncements: "未読のお知らせがあります"
|
||||
neverShow: "今後表示しない"
|
||||
remindMeLater: "また後で"
|
||||
addRe: "閲覧注意の投稿への返信で、注釈の先頭に\"re:\"を追加する"
|
||||
languageForTranslation: "投稿翻訳に使用する言語"
|
||||
detectPostLanguage: "投稿の言語を自動検出し、外国語の投稿に翻訳ボタンを表示する"
|
||||
|
||||
_sensitiveMediaDetection:
|
||||
description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てられます。サーバーの負荷が少し増えます。"
|
||||
@ -1198,11 +1200,16 @@ _menuDisplay:
|
||||
hide: "隠す"
|
||||
_wordMute:
|
||||
muteWords: "ミュートするワード"
|
||||
muteLangs: "ミュートされた言語"
|
||||
muteWordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。"
|
||||
muteWordsDescription2: "キーワードをスラッシュで囲むと正規表現になります。"
|
||||
muteLangsDescription: "OR 条件の場合はスペースまたは改行で区切ります。"
|
||||
muteLangsDescription2: "言語コードを使用します。例: en, fr, ja, zh."
|
||||
softDescription: "指定した条件の投稿をタイムラインから隠します。"
|
||||
langDescription: "設定した言語に一致する投稿をタイムラインから非表示にします。"
|
||||
hardDescription: "指定した条件の投稿をタイムラインに追加しないようにします。追加されなかった投稿は、条件を変更しても除外されたままになります。"
|
||||
soft: "ソフト"
|
||||
lang: "言語"
|
||||
hard: "ハード"
|
||||
mutedNotes: "ミュートされた投稿"
|
||||
_instanceMute:
|
||||
@ -1970,7 +1977,7 @@ _feeds:
|
||||
rss: RSS
|
||||
jsonFeed: JSONフィード
|
||||
copyFeed: フィードのURLをコピー
|
||||
origin: 起源
|
||||
origin: 元のサーバー
|
||||
delete2fa: 2要素認証を無効化
|
||||
deletePasskeys: パスキーを削除
|
||||
delete2faConfirm: これで、このアカウントの2要素認証は完全に削除されます。続行しますか?
|
||||
@ -1980,3 +1987,4 @@ importZip: ZIPをインポート
|
||||
emojiPackCreator: 絵文字パックの作者
|
||||
confirm: 確認
|
||||
exportZip: ZIPをエクスポート
|
||||
openServerInfo: "投稿内のサーバー名をクリックでサーバー情報を開く"
|
||||
|
@ -82,10 +82,8 @@ somethingHappened: "なんやアカンことが起きたで"
|
||||
retry: "もっぺんやる?"
|
||||
pageLoadError: "ページの読み込みに失敗してもた… えろうすんまへん"
|
||||
pageLoadErrorDescription: "これは普通、ネットワークかブラウザキャッシュが原因やからね。キャッシュをクリアするか、もうちっとだけ待ってくれへんか?"
|
||||
serverIsDead: "The server is not responding. Please wait for a while before trying
|
||||
again."
|
||||
youShouldUpgradeClient: "To display this page, please reload and use a new version
|
||||
client. "
|
||||
serverIsDead: "サーバーの応答がおまへん。ちーとの間待ってからもっかい試してみぃな。"
|
||||
youShouldUpgradeClient: "このページを表示するには、リロードして新しいバージョンのクライアントを使うてや。"
|
||||
enterListName: "リスト名を入れてや"
|
||||
privacy: "プライバシー"
|
||||
makeFollowManuallyApprove: "自分が認めた人だけがこのアカウントをフォローできるようにする"
|
||||
@ -109,7 +107,7 @@ clickToShow: "押したら見えるで"
|
||||
sensitive: "ちょっとアカンやつやで"
|
||||
add: "増やす"
|
||||
reaction: "リアクション"
|
||||
reactionSetting: "Reaction that will be displayed in Picker. "
|
||||
reactionSetting: "ピッカーに表示しはるリアクション"
|
||||
reactionSettingDescription2: "ドラッグで並び替え、クリックで削除、+を押して追加やで。"
|
||||
rememberNoteVisibility: "公開範囲覚えといて"
|
||||
attachCancel: "のっけるのやめる"
|
||||
@ -144,9 +142,8 @@ flagAsBot: "ワイはBotや 🤖"
|
||||
flagAsBotDescription: "もしこのアカウントがプログラムによって運用されるんやったら、このフラグをオンにしてたのむで。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Firefishのシステム上での扱いがBotに合ったもんになったりするんやで。"
|
||||
flagAsCat: "ワイはCatや 🐯"
|
||||
flagAsCatDescription: "自分、猫ちゃんならこのフラグつけてみ?"
|
||||
flagShowTimelineReplies: "It will display the reply to the note in the timeline. "
|
||||
flagShowTimelineRepliesDescription: "It will display the reply to notes other than
|
||||
the user notes in the timeline when you turn it on. "
|
||||
flagShowTimelineReplies: "タイムラインに返信を表示させたる"
|
||||
flagShowTimelineRepliesDescription: "有効にすると、タイムラインに他のユーザー宛ての投稿も表示したるで。"
|
||||
autoAcceptFollowed: "フォローしとるユーザーからのフォローリクエストを勝手に許可しとく"
|
||||
addAccount: "アカウントを追加"
|
||||
loginFailed: "ログインに失敗してしもうた…"
|
||||
@ -241,9 +238,8 @@ resetAreYouSure: "リセットしてええん?"
|
||||
saved: "保存したで!"
|
||||
messaging: "チャット"
|
||||
upload: "アップロード"
|
||||
keepOriginalUploading: "Retain the original image. "
|
||||
keepOriginalUploadingDescription: "When uploading the clip, the original version will
|
||||
be retained. Turning it of then uploading will produce images for public use. "
|
||||
keepOriginalUploading: "画質をそのまんまにする"
|
||||
keepOriginalUploadingDescription: "オリジナルの画像をそのまんまアップロードするで。オフにすると、ファイルサイズを削減したWeb公開用画像を生成したるで。"
|
||||
fromDrive: "ドライブから"
|
||||
fromUrl: "URLから"
|
||||
uploadFromUrl: "URLアップロード"
|
||||
@ -582,7 +578,7 @@ disableAll: "全部使えへんようにする"
|
||||
tokenRequested: "アカウントへのアクセス許可"
|
||||
pluginTokenRequestedDescription: "このプラグインはここで設定した権限を使えるようになるで。"
|
||||
notificationType: "通知の種類"
|
||||
edit: "編集"
|
||||
edit: "投稿をいじる"
|
||||
emailServer: "メールサーバー"
|
||||
enableEmail: "メール配信を受け取る"
|
||||
emailConfigInfo: "メールアドレスの確認とかパスワードリセットの時に使うで"
|
||||
@ -739,7 +735,7 @@ showingPastTimeline: "過去のタイムラインを表示してるで"
|
||||
clear: "クリア"
|
||||
markAllAsRead: "もうみな読んでもうたわ"
|
||||
goBack: "戻る"
|
||||
unlikeConfirm: "いいね解除するんか?"
|
||||
unlikeConfirm: "ええやんを解除するんけ?"
|
||||
fullView: "フルビュー"
|
||||
quitFullView: "フルビュー解除"
|
||||
addDescription: "説明を追加するで"
|
||||
@ -814,7 +810,7 @@ searchByGoogle: "探す"
|
||||
indefinitely: "無期限"
|
||||
file: "ファイル"
|
||||
requireAdminForView: "これを見るには管理者アカウントでログインしとらなあかんで。"
|
||||
isSystemAccount: "システムが自動で作成・管理しとるアカウントやで。"
|
||||
isSystemAccount: "システムが自動で作成・管理しとるアカウントやで。モデレーション・編集・削除するとサーバーの動作が不正になる可能性があるので、操作せんといてください。"
|
||||
typeToConfirm: "この操作をやるんなら {x} と入力してなー"
|
||||
deleteAccount: "アカウント削除するで"
|
||||
document: "ドキュメント"
|
||||
@ -857,7 +853,9 @@ _ffVisibility:
|
||||
_ad:
|
||||
back: "戻る"
|
||||
_gallery:
|
||||
unlike: "良くないわ"
|
||||
unlike: "やっぱよくないわ"
|
||||
like: ええやん!
|
||||
liked: ええやんと思った投稿
|
||||
_email:
|
||||
_follow:
|
||||
title: "フォローされたで"
|
||||
@ -1073,9 +1071,16 @@ _poll:
|
||||
votesCount: "{n}票"
|
||||
vote: "投票する"
|
||||
_visibility:
|
||||
publicDescription: "みんなに公開"
|
||||
home: "ホーム"
|
||||
followers: "フォロワー"
|
||||
publicDescription: "うちの投稿、みんな見てや"
|
||||
home: "ホームタイムラインのみ"
|
||||
followers: "フォロワーのみ"
|
||||
localOnly: ローカルのみ
|
||||
followersDescription: フォロワーと返信相手だけに見せたる
|
||||
specified: ダイレクト
|
||||
localOnlyDescription: 他のサーバーには見せとうない
|
||||
specifiedDescription: 指定した相手だけに見せたる
|
||||
public: 公開
|
||||
homeDescription: ローカルTLやグローバルTLには流さへん
|
||||
_profile:
|
||||
name: "名前"
|
||||
username: "ユーザー名"
|
||||
@ -1125,7 +1130,7 @@ _pages:
|
||||
pageSetting: "ページ設定"
|
||||
viewPage: "ページを見る"
|
||||
like: "ええやん"
|
||||
unlike: "良くないわ"
|
||||
unlike: "やっぱ気に入らん"
|
||||
liked: "ええと思ったページ"
|
||||
contents: "コンテンツ"
|
||||
summary: "ページの要約"
|
||||
@ -1443,6 +1448,8 @@ _postForm:
|
||||
c: なに考えとりまっか?
|
||||
d: なんや言いたいんちゃいますか?
|
||||
f: あんさん書くんを待っとるんどす...
|
||||
a: いまなにしとん?
|
||||
flagSpeakAsCat: 猫弁で話す
|
||||
flagSpeakAsCatDescription: 猫モードが有効の場合にオンにすると、ワレの投稿の「な」を「にゃ」に変換するで。
|
||||
flagSpeakAsCatDescription: オンにすると、ワレの投稿の「な」を「にゃ」に変換したるで。
|
||||
welcomeBackWithName: おおきに、{name}はん
|
||||
migration: アカウントの引っ越し
|
||||
|
@ -9,7 +9,7 @@ password: "비밀번호"
|
||||
forgotPassword: "비밀번호 재설정"
|
||||
fetchingAsApObject: "연합에서 조회 중"
|
||||
ok: "OK"
|
||||
gotIt: "알겠어요"
|
||||
gotIt: "알겠어요!"
|
||||
cancel: "취소"
|
||||
enterUsername: "유저명 입력"
|
||||
renotedBy: "{user}님이 부스트"
|
||||
@ -22,21 +22,21 @@ otherSettings: "기타 설정"
|
||||
openInWindow: "창으로 열기"
|
||||
profile: "프로필"
|
||||
timeline: "타임라인"
|
||||
noAccountDescription: "자기소개가 없습니다"
|
||||
noAccountDescription: "자기소개가 없습니다."
|
||||
login: "로그인"
|
||||
loggingIn: "로그인 중"
|
||||
logout: "로그아웃"
|
||||
signup: "회원 가입"
|
||||
uploading: "업로드 중"
|
||||
uploading: "업로드 중..."
|
||||
save: "저장"
|
||||
users: "유저"
|
||||
addUser: "유저 추가"
|
||||
favorite: "즐겨찾기"
|
||||
favorites: "즐겨찾기"
|
||||
unfavorite: "즐겨찾기에서 제거"
|
||||
favorited: "즐겨찾기에 등록했습니다"
|
||||
alreadyFavorited: "이미 즐겨찾기에 등록되어 있습니다"
|
||||
cantFavorite: "즐겨찾기에 등록하지 못했습니다"
|
||||
favorited: "즐겨찾기에 등록했습니다."
|
||||
alreadyFavorited: "이미 즐겨찾기에 등록되어 있습니다."
|
||||
cantFavorite: "즐겨찾기에 등록하지 못했습니다."
|
||||
pin: "프로필에 고정"
|
||||
unpin: "프로필에서 고정 해제"
|
||||
copyContent: "내용 복사"
|
||||
@ -96,7 +96,7 @@ followRequestPending: "팔로우 허가 대기중"
|
||||
enterEmoji: "이모지 입력"
|
||||
renote: "부스트"
|
||||
unrenote: "부스트 취소"
|
||||
renoted: "부스트 하였습니다"
|
||||
renoted: "부스트 하였습니다."
|
||||
cantRenote: "이 게시물은 부스트할 수 없습니다."
|
||||
cantReRenote: "부스트를 부스트할 수 없습니다."
|
||||
quote: "인용"
|
||||
@ -143,7 +143,7 @@ flagAsBot: "나는 봇입니다"
|
||||
flagAsBotDescription: "이 계정을 자동화된 수단으로 운용할 경우에 활성화해 주세요. 이 플래그를 활성화하면, 다른 봇이 이를 참고하여
|
||||
봇 끼리의 무한 연쇄 반응을 회피하거나, 이 계정의 시스템 상에서의 취급이 Bot 운영에 최적화되는 등의 변화가 생깁니다."
|
||||
flagAsCat: "나는 고양이다냥"
|
||||
flagAsCatDescription: "이 계정이 고양이라면 활성화 해주세요."
|
||||
flagAsCatDescription: "고양이귀를 쓰고 냥냥거려요!"
|
||||
flagShowTimelineReplies: "타임라인에 게시물의 답글을 표시하기"
|
||||
flagShowTimelineRepliesDescription: "이 설정을 활성화하면 타임라인에 다른 유저 간의 답글을 표시합니다."
|
||||
autoAcceptFollowed: "팔로우 중인 유저로부터의 팔로우 요청을 자동 수락"
|
||||
@ -202,7 +202,7 @@ blockedUsers: "차단한 유저"
|
||||
noUsers: "아무도 없습니다"
|
||||
editProfile: "프로필 수정"
|
||||
noteDeleteConfirm: "이 게시물을 삭제하시겠습니까?"
|
||||
pinLimitExceeded: "더 이상 고정할 수 없습니다."
|
||||
pinLimitExceeded: "더 이상 고정할 수 없습니다"
|
||||
intro: "Firefish의 설치가 완료되었습니다! 관리자 계정을 생성해주세요."
|
||||
done: "완료"
|
||||
processing: "처리중"
|
||||
@ -361,7 +361,7 @@ name: "이름"
|
||||
antennaSource: "받을 소스"
|
||||
antennaKeywords: "받을 키워드"
|
||||
antennaExcludeKeywords: "제외할 키워드"
|
||||
antennaKeywordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다"
|
||||
antennaKeywordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다."
|
||||
notifyAntenna: "새로운 글을 알림"
|
||||
withFileAntenna: "파일이 첨부된 게시물만"
|
||||
enableServiceworker: "ServiceWorker 사용"
|
||||
@ -437,10 +437,10 @@ onlyOneFileCanBeAttached: "메시지에 첨부할 수 있는 파일은 하나까
|
||||
signinRequired: "로그인 해주세요"
|
||||
invitations: "초대"
|
||||
invitationCode: "초대 코드"
|
||||
checking: "확인하는 중입니다"
|
||||
checking: "확인하는 중입니다..."
|
||||
available: "사용 가능합니다"
|
||||
unavailable: "사용할 수 없습니다"
|
||||
usernameInvalidFormat: "a~z, A~Z, 0-9, _를 사용할 수 있습니다"
|
||||
usernameInvalidFormat: "a~z, A~Z, 0-9, _를 사용할 수 있습니다."
|
||||
tooShort: "너무 짧습니다"
|
||||
tooLong: "너무 깁니다"
|
||||
weakPassword: "약한 비밀번호"
|
||||
@ -463,7 +463,7 @@ joinOrCreateGroup: "다른 그룹의 초대를 받거나, 직접 새 그룹을
|
||||
noHistory: "기록이 없습니다"
|
||||
signinHistory: "로그인 기록"
|
||||
disableAnimatedMfm: "움직임이 있는 MFM을 비활성화"
|
||||
doing: "잠시만요"
|
||||
doing: "기다려 주세요..."
|
||||
category: "카테고리"
|
||||
tags: "태그"
|
||||
docSource: "이 문서의 소스"
|
||||
@ -491,7 +491,7 @@ objectStorage: "오브젝트 스토리지"
|
||||
useObjectStorage: "오브젝트 스토리지를 사용"
|
||||
objectStorageBaseUrl: "Base URL"
|
||||
objectStorageBaseUrlDesc: "오브젝트 (미디어) 참조 URL 을 만들 때 사용되는 URL입니다. CDN 또는 프록시를 사용하는
|
||||
경우 그 URL을 지정하고, 그 외의 경우 사용할 서비스의 가이드에 따라 공개적으로 액세스 할 수 있는 주소를 지정해 주세요. 예를 들어, AWS
|
||||
경우 그 URL을 지정하고, 그 외의 경우 사용할 서비스의 가이드에 따라 공개적으로 액세스 할 수 있는 주소를 지정해 주세요.\n예를 들어, AWS
|
||||
S3의 경우 'https://<bucket>.s3.amazonaws.com', GCS등의 경우 'https://storage.googleapis.com/<bucket>'
|
||||
와 같이 지정합니다."
|
||||
objectStorageBucket: "Bucket"
|
||||
@ -596,7 +596,7 @@ notificationType: "알림 유형"
|
||||
edit: "편집"
|
||||
emailServer: "메일 서버"
|
||||
enableEmail: "이메일 송신 기능 활성화"
|
||||
emailConfigInfo: "가입 시 메일 주소 확인이나 비밀번호 초기화 시에 사용합니다."
|
||||
emailConfigInfo: "가입 시 메일 주소 확인이나 비밀번호 초기화 시에 사용합니다"
|
||||
email: "이메일"
|
||||
emailAddress: "메일 주소"
|
||||
smtpConfig: "SMTP 서버 설정"
|
||||
@ -604,9 +604,9 @@ smtpHost: "호스트"
|
||||
smtpPort: "포트"
|
||||
smtpUser: "유저명"
|
||||
smtpPass: "비밀번호"
|
||||
emptyToDisableSmtpAuth: "SMTP 인증을 사용하지 않으려면 공란으로 비워둡니다."
|
||||
emptyToDisableSmtpAuth: "SMTP 인증을 사용하지 않으려면 공란으로 비워둡니다"
|
||||
smtpSecure: "SMTP 연결에 Implicit SSL/TTS 사용"
|
||||
smtpSecureInfo: "STARTTLS 사용 시에는 해제합니다."
|
||||
smtpSecureInfo: "STARTTLS 사용 시에는 해제합니다"
|
||||
testEmail: "이메일 전송 테스트"
|
||||
wordMute: "단어 뮤트"
|
||||
regexpError: "정규 표현식 오류"
|
||||
@ -683,14 +683,14 @@ no: "아니오"
|
||||
driveFilesCount: "드라이브 파일 개수"
|
||||
driveUsage: "드라이브 사용량"
|
||||
noCrawle: "검색엔진의 인덱싱 거부"
|
||||
noCrawleDescription: "검색엔진에 사용자 페이지, 게시물, 페이지 등의 콘텐츠를 인덱싱되지 않게 합니다."
|
||||
noCrawleDescription: "검색엔진이 나의 컨텐츠를 인덱싱하지 않게 합니다."
|
||||
lockedAccountInfo: "팔로우를 승인으로 승인받더라도 게시물의 공개 범위를 '팔로워'로 하지 않는 한 누구나 당신의 글을 볼 수 있습니다."
|
||||
alwaysMarkSensitive: "미디어를 항상 열람 주의로 설정"
|
||||
loadRawImages: "첨부한 이미지의 썸네일을 원본화질로 표시"
|
||||
disableShowingAnimatedImages: "움직이는 이미지를 자동으로 재생하지 않음"
|
||||
verificationEmailSent: "확인 메일을 발송하였습니다. 설정을 완료하려면 메일에 첨부된 링크를 확인해 주세요."
|
||||
notSet: "설정되지 않음"
|
||||
emailVerified: "메일 주소가 확인되었습니다."
|
||||
emailVerified: "메일 주소가 확인되었습니다"
|
||||
noteFavoritesCount: "즐겨찾기한 게시물 수"
|
||||
pageLikesCount: "좋아요 한 Page 수"
|
||||
pageLikedCount: "Page에 받은 좋아요 수"
|
||||
@ -891,7 +891,7 @@ lastActiveDate: "마지막 이용"
|
||||
pleaseSelect: "선택해 주세요"
|
||||
reverse: "플립"
|
||||
colored: "색 입히기"
|
||||
refreshInterval: "업데이트 주기"
|
||||
refreshInterval: "업데이트 주기 "
|
||||
label: "라벨"
|
||||
type: "종류"
|
||||
speed: "속도"
|
||||
@ -954,7 +954,7 @@ _forgotPassword:
|
||||
_gallery:
|
||||
my: "내 갤러리"
|
||||
liked: "좋아요 한 갤러리"
|
||||
like: "좋아요!"
|
||||
like: "좋아요"
|
||||
unlike: "좋아요 취소"
|
||||
_email:
|
||||
_follow:
|
||||
@ -982,7 +982,7 @@ _preferencesBackups:
|
||||
createdAt: "생성 날짜: {date} {time}"
|
||||
updatedAt: "갱신 날짜: {date} {time}"
|
||||
cannotLoad: "가져오기에 실패했습니다"
|
||||
invalidFile: "파일 형식이 올바르지 않습니다."
|
||||
invalidFile: "파일 형식이 올바르지 않습니다"
|
||||
_registry:
|
||||
scope: "범위"
|
||||
key: "키"
|
||||
@ -1033,9 +1033,9 @@ _mfm:
|
||||
blockCode: "코드(블록)"
|
||||
blockCodeDescription: "여러 행의 코드를 문법 강조를 적용하여 블록으로 표시합니다."
|
||||
inlineMath: "수식(인라인)"
|
||||
inlineMathDescription: "수식(KaTeX)를 인라인으로 보이게 합니다."
|
||||
inlineMathDescription: "수식(KaTeX)를 인라인으로 보이게 합니다"
|
||||
blockMath: "수식(블록)"
|
||||
blockMathDescription: "수식(KaTeX)을 블록으로 보이게 합니다."
|
||||
blockMathDescription: "수식(KaTeX)을 블록으로 보이게 합니다"
|
||||
quote: "인용"
|
||||
quoteDescription: "내용을 인용문으로 표시합니다."
|
||||
emoji: "커스텀 이모지"
|
||||
@ -1163,7 +1163,7 @@ _theme:
|
||||
darken: "어두움"
|
||||
lighten: "밝음"
|
||||
inputConstantName: "상수 이름을 입력하세요"
|
||||
importInfo: "여기에 테마 코드를 붙여 넣어 에디터로 불러올 수 있습니다."
|
||||
importInfo: "여기에 테마 코드를 붙여 넣어 에디터로 불러올 수 있습니다"
|
||||
deleteConstantConfirm: "상수 {const}를 삭제하시겠습니까?"
|
||||
keys:
|
||||
accent: "강조 색상"
|
||||
@ -1294,7 +1294,7 @@ _permissions:
|
||||
_auth:
|
||||
shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?"
|
||||
shareAccessAsk: "이 애플리케이션이 계정에 접근하는 것을 허용하시겠습니까?"
|
||||
permissionAsk: "이 앱은 다음의 권한을 요청합니다"
|
||||
permissionAsk: "이 앱은 다음의 권한을 요청합니다:"
|
||||
pleaseGoBack: "앱으로 돌아가서 시도해 주세요"
|
||||
callback: "앱으로 돌아갑니다"
|
||||
denied: "접근이 거부되었습니다"
|
||||
@ -1436,7 +1436,7 @@ _instanceCharts:
|
||||
usersTotal: "누적 유저 수"
|
||||
notes: "게시물 수 증감"
|
||||
notesTotal: "누적 게시물 수"
|
||||
ff: "팔로잉/팔로워 증감"
|
||||
ff: "팔로잉/팔로워 증감 "
|
||||
ffTotal: "누적 팔로잉/팔로워 수"
|
||||
cacheSize: "캐시 용량 증감"
|
||||
cacheSizeTotal: "누적 캐시 용량"
|
||||
@ -1793,7 +1793,7 @@ _deck:
|
||||
deleteProfile: "작업 공간 삭제"
|
||||
introduction: "칼럼을 조합해서 나만의 인터페이스를 구성해 보아요!"
|
||||
introduction2: "나중에라도 화면 우측의 + 버튼을 눌러 새 칼럼을 추가할 수 있습니다."
|
||||
widgetsIntroduction: "칼럼 메뉴의 \"위젯 편집\"에서 위젯을 추가해 주세요"
|
||||
widgetsIntroduction: "칼럼 메뉴의 \"위젯 편집\"에서 위젯을 추가해 주세요."
|
||||
_columns:
|
||||
main: "메인"
|
||||
widgets: "위젯"
|
||||
@ -2016,3 +2016,7 @@ deletePasskeys: 보안 키 삭제
|
||||
deletePasskeysConfirm: 이 계정에서 모든 보안 키를 영구히 삭제합니다. 계속하시겠습니까?
|
||||
inputNotMatch: 입력이 일치하지 않습니다
|
||||
addRe: 열람주의로 표시된 게시물의 답장에 're:' 붙이기
|
||||
detectPostLanguage: 외국어로 인식된 게시물에 번역 버튼을 표시
|
||||
indexableDescription: Firefish 검색에서 나의 공개 게시물에 대한 검색을 허용합니다
|
||||
indexable: 인덱스 허용
|
||||
languageForTranslation: 게시물 번역에 사용할 언어
|
||||
|
@ -80,17 +80,17 @@ showMore: Vis mer
|
||||
followRequestAccepted: Følgeforespørsel godtatt
|
||||
import: Importer
|
||||
export: Eksporter
|
||||
logout: Logger ut
|
||||
logout: Logg ut
|
||||
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
|
||||
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.
|
||||
privacy: Personvern
|
||||
enterEmoji: Legg inn emoji
|
||||
renoted: Fremhevet.
|
||||
cantRenote: Denne posten kan ikke fremheves.
|
||||
cantReRenote: En fremheving kan ikke fremheves.
|
||||
renoted: Delt.
|
||||
cantRenote: Denne posten kan ikke deles.
|
||||
cantReRenote: En deling kan ikke deles.
|
||||
quote: Sitér
|
||||
pinned: Fest til profil
|
||||
clickToShow: Klikk for å vise
|
||||
@ -107,7 +107,7 @@ reaction: Reaksjoner
|
||||
driveFileDeleteConfirm: Er du sikker på at du vil slette filen «{name}»? Den vil fjernes
|
||||
fra alle poster den er vedlagt i.
|
||||
defaultNoteVisibility: Standard synlighet
|
||||
unrenote: Fjern fremmheving
|
||||
unrenote: Trekk tilbake deling
|
||||
unpin: Løsne fra profilen
|
||||
youGotNewFollower: følger deg
|
||||
mention: Omtale
|
||||
@ -149,7 +149,7 @@ images: Bilder
|
||||
birthday: Fødselsdag
|
||||
yearsOld: '{age} år gammel'
|
||||
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
|
||||
drive: Disk
|
||||
renameFile: Omdøp fil
|
||||
@ -167,7 +167,7 @@ popularUsers: Populære brukere
|
||||
moderator: Moderator
|
||||
groupName: Gruppenavn
|
||||
transfer: Overfør
|
||||
preferencesBackups: Foretrukne sikkerhetskopier
|
||||
preferencesBackups: Sikkerhetskopi av innstillinger
|
||||
edit: Rediger
|
||||
emailServer: Eposttjener
|
||||
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.
|
||||
attachCancel: Slett vedegg
|
||||
markAsSensitive: Merk som sensitivt innhold
|
||||
renoteMute: Stum fremhevinger
|
||||
renoteUnmute: Vis boosts fra bruker
|
||||
renoteMute: Stum delinger
|
||||
renoteUnmute: Vis delinger
|
||||
addEmoji: Legg til
|
||||
settingGuide: Foreslåtte innstillinger
|
||||
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".
|
||||
withReplies: Ta med svar
|
||||
connectedTo: Følgende konto(er) er sammenkoblet
|
||||
withFiles: Inkluder filer
|
||||
withFiles: Med filer
|
||||
lastUsed: Sist brukt
|
||||
unregister: Avregistrer
|
||||
passwordLessLogin: Passordløs innlogging
|
||||
@ -512,7 +512,7 @@ useFullReactionPicker: Bruk reaksjonsvelger i full størrelse
|
||||
width: Bredde
|
||||
regexpErrorDescription: 'En feil oppsto under det regulære uttrykket på linje {line}
|
||||
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}'
|
||||
userSaysSomething: '{name} sa noe'
|
||||
metrics: Metrikker
|
||||
@ -557,7 +557,7 @@ home: Hjem
|
||||
emptyDrive: Disken din er tom
|
||||
unableToDelete: Klarte ikke å slette
|
||||
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
|
||||
mellom dem. Ønsker å du slå av det andre aktive CAPTCHA-systemet? Om du ønsker å
|
||||
ha begge på, trykk "Avbryt".
|
||||
@ -619,7 +619,7 @@ keepOriginalUploadingDescription: Lagrer det opprinnelige opplastedet bildet sli
|
||||
ved opplasting.
|
||||
startMessaging: Start en ny chat
|
||||
group: Gruppe
|
||||
renote: Fremhev
|
||||
renote: Del
|
||||
banner: Topp-bilde
|
||||
nsfw: Sensitivt innhold
|
||||
bannerUrl: URL til fane-bilde
|
||||
@ -776,6 +776,88 @@ _mfm:
|
||||
play: Spill animert markeringsspråk (MFM)
|
||||
intro: MFM er et markeringsspråk som burkes av Misskey, Firefish, Akkoma og andre.
|
||||
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
|
||||
switchUi: Visningsoppsett
|
||||
usageAmount: Bruk
|
||||
@ -840,6 +922,14 @@ saveAs: Lagre som...
|
||||
swipeOnMobile: Tillat sveiping mellom sider
|
||||
_accountDelete:
|
||||
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
|
||||
total: Total
|
||||
registry: Register
|
||||
@ -946,8 +1036,7 @@ reporteeOrigin: Kilden til den som rapporteres
|
||||
accountInfo: Kontoinformasjon
|
||||
driveUsage: Brukt diskplass
|
||||
noCrawle: Stopp robot-indeksering
|
||||
noCrawleDescription: Be søkemotorer om å ikke indeksere din profil, poster, Sider
|
||||
etc.
|
||||
noCrawleDescription: Be eksterne søkemotorer om å ikke indeksere innholdet ditt.
|
||||
narrow: Smal
|
||||
reloadToApplySetting: Denne innstillingen aktiveres ikke før du laster siden på nytt.
|
||||
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.
|
||||
enableCustomKaTeXMacro: Slå på egne KaTeX-makroer
|
||||
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}'
|
||||
|
@ -82,7 +82,7 @@ followers: "Följare"
|
||||
followsYou: "Följer dig"
|
||||
createList: "Skapa lista"
|
||||
manageLists: "Hantera lista"
|
||||
error: "Fel!"
|
||||
error: "Fel"
|
||||
somethingHappened: "Ett fel har uppstått"
|
||||
retry: "Försök igen"
|
||||
pageLoadError: "Det gick inte att ladda sidan."
|
||||
@ -146,7 +146,7 @@ addEmoji: "Lägg till emoji"
|
||||
settingGuide: "Rekommenderade inställningar"
|
||||
cacheRemoteFiles: "Spara externa filer till cachen"
|
||||
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
|
||||
genereras."
|
||||
flagAsBot: "Markera konto som bot"
|
||||
@ -181,7 +181,7 @@ selectUser: "Välj användare"
|
||||
recipient: "Mottagare"
|
||||
annotation: "Kommentarer"
|
||||
federation: "Federation"
|
||||
instances: "Instanser"
|
||||
instances: "Servrar"
|
||||
registeredAt: "Registrerad på"
|
||||
latestRequestSentAt: "Senaste förfrågan skickad"
|
||||
latestRequestReceivedAt: "Senaste begäran mottagen"
|
||||
@ -191,7 +191,7 @@ charts: "Diagram"
|
||||
perHour: "Per timme"
|
||||
perDay: "Per dag"
|
||||
stopActivityDelivery: "Sluta skicka aktiviteter"
|
||||
blockThisInstance: "Blockera instans"
|
||||
blockThisInstance: "Blockera denna server"
|
||||
operations: "Operationer"
|
||||
software: "Mjukvara"
|
||||
version: "Version"
|
||||
@ -201,7 +201,7 @@ jobQueue: "Jobbkö"
|
||||
cpuAndMemory: "CPU och minne"
|
||||
network: "Nätverk"
|
||||
disk: "Disk"
|
||||
instanceInfo: "Instansinformation"
|
||||
instanceInfo: "Serverninformation"
|
||||
statistics: "Statistik"
|
||||
clearQueue: "Rensa kö"
|
||||
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."
|
||||
clearCachedFiles: "Rensa cache"
|
||||
clearCachedFilesConfirm: "Är du säker att du vill radera alla cachade externa filer?"
|
||||
blockedInstances: "Blockerade instanser"
|
||||
blockedInstancesDescription: "Lista adressnamn av instanser som du vill blockera.
|
||||
Listade instanser kommer inte längre kommunicera med denna instans."
|
||||
blockedInstances: "Blockerade servrar"
|
||||
blockedInstancesDescription: "Lista adressnamn av servrar som du vill blockera. Listade
|
||||
servrarna kommer inte längre kommunicera med denna servern."
|
||||
muteAndBlock: "Tystningar och blockeringar"
|
||||
mutedUsers: "Tystade 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"
|
||||
intro: "Firefish har installerats! Vänligen skapa en adminanvändare."
|
||||
done: "Klar"
|
||||
processing: "Bearbetar..."
|
||||
processing: "Bearbetar"
|
||||
preview: "Förhandsvisning"
|
||||
default: "Standard"
|
||||
defaultValueIs: "Standard: {value}"
|
||||
@ -234,12 +234,12 @@ all: "Allt"
|
||||
subscribing: "Prenumererar"
|
||||
publishing: "Publiceras"
|
||||
notResponding: "Svarar inte"
|
||||
instanceFollowing: "Följer på instans"
|
||||
instanceFollowers: "Följare av instans"
|
||||
instanceUsers: "Användare av denna instans"
|
||||
instanceFollowing: "Följer på server"
|
||||
instanceFollowers: "Följare av server"
|
||||
instanceUsers: "Användare av denna server"
|
||||
changePassword: "Ändra lösenord"
|
||||
security: "Säkerhet"
|
||||
retypedNotMatch: "Inmatningen matchar inte"
|
||||
retypedNotMatch: "Inmatningen matchar inte."
|
||||
currentPassword: "Nuvarande lösenord"
|
||||
newPassword: "Nytt lösenord"
|
||||
newPasswordRetype: "Bekräfta lösenord"
|
||||
@ -394,7 +394,7 @@ clientSettings: Klientinställningar
|
||||
promote: Befordra
|
||||
numberOfDays: Antalet dagar
|
||||
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
|
||||
nothing: Det finns inget att visa här
|
||||
lastUsedDate: Senaste använd vid
|
||||
@ -481,7 +481,7 @@ cacheClear: Rensa cache
|
||||
markAsReadAllTalkMessages: Markera alla meddelanden som lästa
|
||||
uiLanguage: Användargränssnitt
|
||||
disableDrawer: Använd inte byrålådor-stil menyer
|
||||
tapSecurityKey: Klicka in din säkerhetsnyckel.
|
||||
tapSecurityKey: Klicka in din säkerhetsnyckel
|
||||
language: Språk
|
||||
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'.
|
||||
@ -576,7 +576,7 @@ accept: Acceptera
|
||||
tosUrl: Användarvillkor URL
|
||||
pages: Sidor
|
||||
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
|
||||
enableRegistration: Aktivera ny användarregistrering
|
||||
driveCapacityPerRemoteAccount: Enhetskapacitet per extern användare
|
||||
@ -631,7 +631,7 @@ birthday: Födelsedag
|
||||
theme: Teman
|
||||
avatar: Avatar
|
||||
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'
|
||||
location: Plats
|
||||
selectFile: Välj en fil
|
||||
@ -652,7 +652,7 @@ basicInfo: Grundläggande info
|
||||
pinnedUsers: Pinnade användare
|
||||
backgroundImageUrl: Bakgrundsbild URL
|
||||
pinnedUsersDescription: Lista användarnamn separerade med radbrytning att bli fäst
|
||||
i "Utforska" fliken
|
||||
i "Utforska" fliken.
|
||||
recaptchaSiteKey: Sid nyckel
|
||||
pinnedClipId: ID av klippet du vill fästa
|
||||
avoidMultiCaptchaConfirm: Användning av flera Captcha system kan orsaka problem. Vill
|
||||
@ -681,7 +681,7 @@ nUsersMentioned: Benämnd av {n} användare
|
||||
securityKeyName: Nyckelnamn
|
||||
share: Dela
|
||||
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
|
||||
group: Grupp
|
||||
transfer: Överför
|
||||
@ -759,7 +759,7 @@ lastUsed: Senast använd
|
||||
unregister: Avregistrera
|
||||
addInstance: Lägg till server
|
||||
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
|
||||
showFeaturedNotesInTimeline: Visa presenterade poster i tidslinjen
|
||||
objectStorageBucket: Hink
|
||||
@ -781,3 +781,5 @@ removeAllFollowing: Sluta följa alla följda användare
|
||||
medium: Mellan
|
||||
integration: Integreringar
|
||||
xl: XL
|
||||
desktop: Skrivbord
|
||||
createNew: Skapa nya
|
||||
|
@ -226,7 +226,7 @@ searchPlaceholder: Firefish'de Ara
|
||||
reply: Yanıtla
|
||||
jumpToPrevious: Öncekini görüntüle
|
||||
deleted: Silindi
|
||||
editNote: Notu düzenle
|
||||
editNote: Gönderiyi düzenle
|
||||
noThankYou: Hayır, teşekkürler
|
||||
addInstance: Bir sunucu ekle
|
||||
cantFavorite: Favorilere eklenemedi.
|
||||
@ -262,7 +262,7 @@ blockedInstancesDescription: Engellemek istediğiniz sunucuların domain adları
|
||||
Listelenen sunucular artık bu sunucularla iletişim kuramayacak.
|
||||
blockedUsers: Engellenmiş kullanıcılar
|
||||
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ı
|
||||
changePassword: Şifreyi değiştir
|
||||
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
|
||||
emptyFolder: Bu klasör boş
|
||||
unableToDelete: Silinemiyor
|
||||
inputNewDescription: Yeni başlık gir
|
||||
inputNewDescription: Yeni açıklama gir
|
||||
hasChildFilesOrFolders: Bu klasör boş olmadığından dolayı silinemez.
|
||||
disconnectedFromServer: Sunucuyla bağlantı kesildi
|
||||
reload: Yenile
|
||||
@ -300,7 +300,7 @@ messagingWithGroup: Grup sohbeti
|
||||
next: Sonraki
|
||||
retype: Tekrar gir
|
||||
dashboard: Panel
|
||||
objectStorageBucket: Bucket
|
||||
objectStorageBucket: Kova
|
||||
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
|
||||
newNoteRecived: Yeni gönderiler mevcut
|
||||
@ -585,8 +585,7 @@ onlyOneFileCanBeAttached: Bir mesaja sadece 1 dosya ekleyebilirsin
|
||||
install: İndir
|
||||
uninstall: kALDIR
|
||||
send: Gönder
|
||||
noCrawleDescription: Arama motorlarından profil sayfanızı, gönderilerinizi, Sayfalarınızı
|
||||
vb. indekslememesini isteyin.
|
||||
noCrawleDescription: Harici arama motorlarından içeriğinizi endekslememesini isteyin.
|
||||
emailNotification: Mail bildirimleri
|
||||
goBack: Geri
|
||||
online: Çevrimiçi
|
||||
@ -751,7 +750,7 @@ upload: Yükle
|
||||
fromUrl: URL'den
|
||||
agreeTo: '{0} kabul ediyorum'
|
||||
tos: Kullanım Koşulları
|
||||
drive: Drive
|
||||
drive: Sürücü
|
||||
selectFolder: Klasör seç
|
||||
inputNewFileName: Yeni dosya ismi gir
|
||||
whenServerDisconnected: Sunucuyla bağlantı kesildiğinde
|
||||
@ -917,7 +916,7 @@ aboutX: '{x} Hakkında'
|
||||
doing: İşleniyor...
|
||||
category: Kategori
|
||||
deleteAll: Hepsini sil
|
||||
objectStorageEndpoint: Endpoint
|
||||
objectStorageEndpoint: Uç noktası
|
||||
output: Çıkış
|
||||
userSuspended: Bu kullanıcı askıya alındı.
|
||||
userSilenced: Bu kullanıcı susturuldu.
|
||||
@ -1106,7 +1105,7 @@ clips: Ataçlar
|
||||
experimentalFeatures: Deneysel özellikler
|
||||
developer: Geliştirici
|
||||
left: Sol
|
||||
center: Orta
|
||||
center: Merkez
|
||||
wide: Geniş
|
||||
narrow: Dar
|
||||
reloadToApplySetting: Bu ayar yalnızca bir sayfa yeniden yüklendikten sonra geçerli
|
||||
@ -1152,7 +1151,7 @@ shuffle: Karıştır
|
||||
pushNotification: Push bildirimleri
|
||||
unsubscribePushNotification: Push bildirimlerini kapat
|
||||
pushNotificationNotSupported: Tarayıcınız veya sunucunuz push bildirimleri desteklemiyor
|
||||
caption: Otomatik Başlık
|
||||
caption: Otomatik Açıklama
|
||||
moveToLabel: 'Taşıyacağın hesap:'
|
||||
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
|
||||
@ -1242,9 +1241,9 @@ deletedNote: Silinmiş Gönderi
|
||||
visibility: Görünürlük
|
||||
poll: Anket
|
||||
themeEditor: Tema düzenleyicisi
|
||||
enterFileDescription: Başlık gir
|
||||
enterFileDescription: Açıklama gir
|
||||
description: Açıklama
|
||||
describeFile: Başlık ekle
|
||||
describeFile: Açıklama ekle
|
||||
system: Sistem
|
||||
desktop: Masaüstü
|
||||
confirmToUnclipAlreadyClippedNote: Bu gönderi zaten "{name}" atacının bir parçası.
|
||||
@ -1952,6 +1951,7 @@ _aboutFirefish:
|
||||
bağış yapmayı da düşünün.
|
||||
donateHost: '{ev sahibi} için bağış yapın'
|
||||
sponsors: Firefish sponsorları
|
||||
misskeyContributors: Misskey'e katkıda bulunanlar
|
||||
_weekday:
|
||||
saturday: Cumartesi
|
||||
sunday: Pazar
|
||||
@ -2136,7 +2136,7 @@ _feeds:
|
||||
origin: Kaynak
|
||||
objectStorageS3ForcePathStyle: Path temelli bir endpoint URL'leri kullan
|
||||
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
|
||||
deletePasskeys: Passkey'leri sil
|
||||
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?
|
||||
delete2faConfirm: Bu işlem geri alınamaz bir şekilde 2FA'yı bu hesaptan silecektir.
|
||||
İş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
|
||||
|
@ -673,7 +673,7 @@ no: "否"
|
||||
driveFilesCount: "网盘的文件数"
|
||||
driveUsage: "网盘的空间用量"
|
||||
noCrawle: "要求搜索引擎不索引该用户"
|
||||
noCrawleDescription: "要求搜索引擎不要收录(索引)您的用户页面,帖子,页面等。"
|
||||
noCrawleDescription: "要求外部搜索引擎不收录(索引)您的内容。"
|
||||
lockedAccountInfo: "即使通过了关注请求,只要您不将帖子可见范围设置成“关注者”,任何人都可以看到您的帖子。"
|
||||
alwaysMarkSensitive: "默认将媒体文件标记为敏感内容"
|
||||
loadRawImages: "加载原始图像而不是显示缩略图"
|
||||
@ -1110,11 +1110,16 @@ _menuDisplay:
|
||||
hide: "隐藏"
|
||||
_wordMute:
|
||||
muteWords: "过滤词"
|
||||
muteLangs: "过滤语言"
|
||||
muteWordsDescription: "AND 条件用空格分隔,OR 条件用换行符分隔。"
|
||||
muteWordsDescription2: "将关键字用斜线括起来表示正则表达式。"
|
||||
muteLangsDescription: "OR 条件用空格,换行符分隔"
|
||||
muteLangsDescription2: "使用语言代码。例: en, fr, ja, zh."
|
||||
softDescription: "隐藏时间线中指定条件的帖子。"
|
||||
langDescription: "从时间线中隐藏与设置语言匹配的帖子。"
|
||||
hardDescription: "防止将具有指定条件的帖子添加到时间线。 即使您更改条件,原先未添加的帖文也会被排除在外。"
|
||||
soft: "软过滤"
|
||||
lang: "语言"
|
||||
hard: "硬过滤"
|
||||
mutedNotes: "已过滤的帖子"
|
||||
_instanceMute:
|
||||
@ -1980,3 +1985,7 @@ inputNotMatch: 输入不匹配
|
||||
deletePasskeys: 删除通行密钥
|
||||
delete2faConfirm: 这将不可逆转地删除此账户上的 2FA。是否继续?
|
||||
addRe: 在回复有内容警告的帖子时,在评论开头添加 "re:"
|
||||
detectPostLanguage: 自动检测语言,并显示外文帖子的翻译按钮
|
||||
indexableDescription: 允许内置搜索显示您的公开帖子
|
||||
indexable: 可索引的
|
||||
languageForTranslation: 帖子翻译语言
|
||||
|
@ -236,7 +236,7 @@ imageUrl: "圖片URL"
|
||||
remove: "刪除"
|
||||
removed: "已成功刪除"
|
||||
removeAreYouSure: "確定要刪掉「{x}」嗎?"
|
||||
deleteAreYouSure: "確定要刪掉「{x}」嗎?"
|
||||
deleteAreYouSure: "確定要刪除「{x}」嗎?"
|
||||
resetAreYouSure: "確定要重設嗎?"
|
||||
saved: "已儲存"
|
||||
messaging: "訊息"
|
||||
@ -292,7 +292,7 @@ inputNewFileName: "輸入檔案名稱"
|
||||
inputNewDescription: "請輸入新標題"
|
||||
inputNewFolderName: "輸入新資料夾的名稱"
|
||||
circularReferenceFolder: "目標文件夾是您要移動的文件夾的子文件夾。"
|
||||
hasChildFilesOrFolders: "此文件夾不是空的,無法刪除。"
|
||||
hasChildFilesOrFolders: "此資料夾不是空的,無法刪除。"
|
||||
copyUrl: "複製網址"
|
||||
rename: "重新命名"
|
||||
avatar: "大頭貼"
|
||||
@ -525,14 +525,14 @@ state: "狀態"
|
||||
sort: "排序"
|
||||
ascendingOrder: "昇冪"
|
||||
descendingOrder: "降冪"
|
||||
scratchpad: "暫存記憶體"
|
||||
scratchpad: "AiScript控制台"
|
||||
scratchpadDescription: "AiScript控制台為AiScript提供了實驗環境。您可以在此編寫、執行和確認代碼與Firefish互動的结果。"
|
||||
output: "輸出"
|
||||
script: "腳本"
|
||||
disablePagesScript: "停用頁面的AiScript腳本"
|
||||
updateRemoteUser: "更新遠端使用者資訊"
|
||||
deleteAllFiles: "刪除所有檔案"
|
||||
deleteAllFilesConfirm: "要删除所有檔案嗎?"
|
||||
deleteAllFilesConfirm: "確定要刪除所有檔案嗎?"
|
||||
removeAllFollowing: "解除所有追蹤"
|
||||
removeAllFollowingDescription: "解除{host}所有的追蹤。在伺服器不再存在時執行。"
|
||||
userSuspended: "此使用者已被停用。"
|
||||
@ -773,7 +773,7 @@ gallery: "相簿"
|
||||
recentPosts: "最新貼文"
|
||||
popularPosts: "熱門的貼文"
|
||||
shareWithNote: "在貼文中分享"
|
||||
ads: "廣告"
|
||||
ads: "社群橫幅"
|
||||
expiration: "期限"
|
||||
memo: "備忘錄"
|
||||
priority: "優先級"
|
||||
@ -895,11 +895,11 @@ navbar: "導覽列"
|
||||
shuffle: "隨機"
|
||||
account: "帳戶"
|
||||
move: "移動"
|
||||
customKaTeXMacro: "自定義 KaTeX 宏"
|
||||
customKaTeXMacroDescription: "使用宏來輕鬆的輸入數學表達式吧!宏的用法與 LaTeX 中的命令定義相同。你可以使用 \\newcommand{\\
|
||||
name}{content} 或 \\newcommand{\\name}[number of arguments]{content} 來輸入數學表達式。舉個例子,\\
|
||||
newcommand{\\add}[2]{#1 + #2} 會將 \\add{3}{foo} 展開為 3 + foo。此外,宏名稱外的花括號 {} 可以被替換為圓括號
|
||||
() 和方括號 [],這會影響用於參數的括號。每行只能夠定義一個宏,無法在中間換行,且無效的行將被忽略。只支持簡單字符串替換功能,不支持高級語法,如條件分支等。"
|
||||
customKaTeXMacro: "自訂KaTeX巨集"
|
||||
customKaTeXMacroDescription: "使用巨集來輕鬆輸入數學表達式吧!巨集的用法與 LaTeX 中的命令定義相同。你可以使用 \\newcommand{\\
|
||||
name}{content} 或 \\newcommand{\\name}[number of arguments]{content} 來輸入數學表達式。舉例來說,\\
|
||||
newcommand{\\add}[2]{#1 + #2} 會將 \\add{3}{foo} 展開為 3 + foo。巨集名稱除了可用大括號 {} 括起來之外,也可使用小括號
|
||||
() 和中括號 [],但使用於巨集參數的括號會有所變更。每行只能夠定義一個巨集,巨集中間無法間換。無效的行將被忽略。只支援簡單字串的替換功能,不支援條件分歧的高級語法。"
|
||||
enableCustomKaTeXMacro: "啟用自定義 KaTeX 宏"
|
||||
_sensitiveMediaDetection:
|
||||
description: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。"
|
||||
@ -990,6 +990,7 @@ _aboutFirefish:
|
||||
pleaseDonateToFirefish: 請考慮向 Firefish 贊助以支持其發展。
|
||||
pleaseDonateToHost: 還請考慮捐贈給您在使用的伺服器 {host},以支援龐大的運營成本。
|
||||
donateHost: 贊助給 {host}
|
||||
misskeyContributors: Misskey的貢獻者
|
||||
_nsfw:
|
||||
respect: "隱藏敏感內容"
|
||||
ignore: "不隱藏敏感內容"
|
||||
@ -1258,6 +1259,8 @@ _2fa:
|
||||
token: 兩步驟驗證金鑰
|
||||
registerTOTPBeforeKey: 請設置身份驗證器應用程式以註冊安全金鑰或密碼。
|
||||
renewTOTPOk: 重新配置
|
||||
step3Title: 輸入驗證碼
|
||||
securityKeyNotSupported: 您使用的瀏覧器不支援安全金鑰(Security key)。
|
||||
_permissions:
|
||||
"read:account": "查看我的帳戶資訊"
|
||||
"write:account": "更改我的帳戶資訊"
|
||||
@ -1802,6 +1805,7 @@ _deck:
|
||||
list: "清單"
|
||||
mentions: "提及"
|
||||
direct: "指定使用者"
|
||||
channel: 頻道
|
||||
secureMode: 安全模式(授權獲取)
|
||||
instanceSecurity: 伺服器安全性
|
||||
privateMode: 私人模式
|
||||
@ -1819,13 +1823,13 @@ adminCustomCssWarn: 除非你知道它的作用,否則請不要使用此設定
|
||||
CSS 正常工作。
|
||||
showUpdates: Firefish 更新時顯示彈出視窗
|
||||
recommendedInstances: 建議的伺服器
|
||||
caption: 自動字幕
|
||||
caption: 自動加上替代文字(alt)
|
||||
enterSendsMessage: 在 Messaging 中按 Return 發送消息 (如關閉則是 Ctrl + Return)
|
||||
migrationConfirm: "您確定要將你的帳戶遷移到 {account} 嗎? 一旦這樣做,你將無法復原,而你將無法再次正常使用您的帳戶。\n另外,請確保你已將此當前帳戶設置為您要遷移的帳戶。"
|
||||
customSplashIconsDescription: 每次用戶加載/重新加載頁面時,以換行符號分隔的自定啟動畫面圖標的網址將隨機顯示。請確保圖片位於靜態網址上,最好所有圖片解析度調整為
|
||||
192x192。
|
||||
accountMoved: '該使用者已遷移至新帳戶:'
|
||||
showAds: 顯示廣告
|
||||
showAds: 顯示社群橫幅
|
||||
noThankYou: 不用了,謝謝
|
||||
selectInstance: 選擇伺服器
|
||||
enableRecommendedTimeline: 啟用推薦時間線
|
||||
@ -1869,7 +1873,7 @@ hiddenTags: 隱藏主題標籤
|
||||
indexPosts: 索引貼文
|
||||
indexNotice: 現在開始索引。 這可能需要一段時間,請不要在一個小時內重啟你的伺服器。
|
||||
deleted: 已刪除
|
||||
editNote: 編輯筆記
|
||||
editNote: 編輯貼文
|
||||
edited: '於 {date} {time} 編輯'
|
||||
userSaysSomethingReason: '{name} 說了 {reason}'
|
||||
allowedInstancesDescription: 要加入聯邦白名單的服務器,每台伺服器用新行分隔(僅適用於私有模式)。
|
||||
@ -1892,7 +1896,7 @@ listsDesc: 清單可以創建一個只有您指定用戶的時間線。 可以
|
||||
flagSpeakAsCatDescription: 在喵咪模式下你的貼文會被喵化ヾ(•ω•`)o
|
||||
antennasDesc: "天線會顯示符合您設置條件的新貼文!\n 可以從時間線訪問它們。"
|
||||
expandOnNoteClick: 點擊以打開貼文
|
||||
expandOnNoteClickDesc: 如果禁用,您仍然可以通過右鍵單擊菜單或單擊時間戳來打開貼文。
|
||||
expandOnNoteClickDesc: 即使停用,您仍然可以從右鍵選單或單擊發文時間來打開貼文。
|
||||
hiddenTagsDescription: '列出您希望隱藏趨勢和探索的主題標籤(不帶 #)。 隱藏的主題標籤仍然可以通過其他方式發現。'
|
||||
userSaysSomethingReasonQuote: '{name} 引用了一篇包含 {reason} 的貼文'
|
||||
silencedInstancesDescription: 列出您想要靜音的伺服器的網址。 您列出的伺服器內的帳戶將被視為“沉默”,只能發出追隨請求,如果不追隨則不能提及本地帳戶。
|
||||
@ -1943,3 +1947,18 @@ _filters:
|
||||
withFile: 有檔案
|
||||
alt: 替代文字
|
||||
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: 確認
|
||||
|
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "firefish",
|
||||
"version": "1.0.4-beta2",
|
||||
"version": "1.0.4-beta3",
|
||||
"codename": "aqua",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.joinfirefish.org/firefish/firefish.git"
|
||||
},
|
||||
"packageManager": "pnpm@8.7.1",
|
||||
"packageManager": "pnpm@8.7.6",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"rebuild": "pnpm run clean && ./scripts/build-greet.sh && pnpm -r --parallel run build && pnpm run gulp",
|
||||
@ -47,6 +47,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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-rename": "2.0.2",
|
||||
"@types/node": "20.5.8",
|
||||
|
BIN
packages/backend/assets/icons/maskable.png
Normal file
BIN
packages/backend/assets/icons/maskable.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
@ -6,6 +6,7 @@ mod m20230531_180824_drop_reversi;
|
||||
mod m20230627_185451_index_note_url;
|
||||
mod m20230709_000510_move_antenna_to_cache;
|
||||
mod m20230806_170616_fix_antenna_stream_ids;
|
||||
mod m20230904_013244_is_indexable;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
@ -17,6 +18,7 @@ impl MigratorTrait for Migrator {
|
||||
Box::new(m20230627_185451_index_note_url::Migration),
|
||||
Box::new(m20230709_000510_move_antenna_to_cache::Migration),
|
||||
Box::new(m20230806_170616_fix_antenna_stream_ids::Migration),
|
||||
Box::new(m20230904_013244_is_indexable::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
@ -71,6 +71,8 @@ pub struct Model {
|
||||
pub also_known_as: Option<String>,
|
||||
#[sea_orm(column_name = "speakAsCat")]
|
||||
pub speak_as_cat: bool,
|
||||
#[sea_orm(column_name = "isIndexable")]
|
||||
pub is_indexable: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
@ -75,6 +75,8 @@ pub struct Model {
|
||||
pub moderation_note: String,
|
||||
#[sea_orm(column_name = "preventAiLearning")]
|
||||
pub prevent_ai_learning: bool,
|
||||
#[sea_orm(column_name = "isIndexable")]
|
||||
pub is_indexable: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
@ -67,12 +67,12 @@
|
||||
"fluent-ffmpeg": "2.1.2",
|
||||
"got": "13.0.0",
|
||||
"gunzip-maybe": "^1.4.2",
|
||||
"happy-dom": "^11.0.2",
|
||||
"hpagent": "1.2.0",
|
||||
"ioredis": "5.3.2",
|
||||
"ip-cidr": "3.1.0",
|
||||
"is-svg": "5.0.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsdom": "22.1.0",
|
||||
"json5": "2.2.3",
|
||||
"jsonld": "8.2.1",
|
||||
"jsrsasign": "10.8.6",
|
||||
@ -131,6 +131,7 @@
|
||||
"tar-stream": "^3.1.6",
|
||||
"tesseract.js": "^4.1.1",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tinyld": "^1.3.4",
|
||||
"tmp": "0.2.1",
|
||||
"twemoji-parser": "14.0.0",
|
||||
"typeorm": "0.3.17",
|
||||
@ -142,13 +143,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@swc/cli": "^0.1.62",
|
||||
"@swc/core": "^1.3.75",
|
||||
"@swc/core": "1.3.82",
|
||||
"@types/adm-zip": "^0.5.0",
|
||||
"@types/bcryptjs": "2.4.2",
|
||||
"@types/escape-regexp": "0.0.1",
|
||||
"@types/fluent-ffmpeg": "2.1.21",
|
||||
"@types/js-yaml": "4.0.5",
|
||||
"@types/jsdom": "21.1.1",
|
||||
"@types/jsonld": "1.5.9",
|
||||
"@types/jsrsasign": "10.5.8",
|
||||
"@types/koa": "2.13.8",
|
||||
|
7
packages/backend/src/@types/langdetect.d.ts
vendored
Normal file
7
packages/backend/src/@types/langdetect.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
declare module "langdetect" {
|
||||
interface DetectResult {
|
||||
lang: string;
|
||||
prob: number;
|
||||
}
|
||||
export function detect(words: string): DetectResult[];
|
||||
}
|
@ -23,7 +23,7 @@ export default async function () {
|
||||
process.env.mode && ["web", "queue"].includes(process.env.mode)
|
||||
? `(${process.env.mode})`
|
||||
: "";
|
||||
const type = cluster.isPrimary ? "(master)" : "(worker)"
|
||||
const type = cluster.isPrimary ? "(master)" : "(worker)";
|
||||
process.title = `Firefish ${mode} ${type}`;
|
||||
|
||||
if (cluster.isPrimary || envOption.disableClustering) {
|
||||
|
@ -214,7 +214,9 @@ async function spawnWorkers(
|
||||
workers.fill("web", 0, 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)));
|
||||
bootLogger.succ("All workers started");
|
||||
}
|
||||
|
2
packages/backend/src/global.d.ts
vendored
2
packages/backend/src/global.d.ts
vendored
@ -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;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { JSDOM } from "jsdom";
|
||||
import { Window } from "happy-dom";
|
||||
import type * as mfm from "mfm-js";
|
||||
import config from "@/config/index.js";
|
||||
import { intersperse } from "@/prelude/array.js";
|
||||
@ -12,7 +12,7 @@ export function toHtml(
|
||||
return null;
|
||||
}
|
||||
|
||||
const { window } = new JSDOM("");
|
||||
const { window } = new Window();
|
||||
|
||||
const doc = window.document;
|
||||
|
||||
|
@ -12,29 +12,38 @@ const retryDelay = 100;
|
||||
* @param timeout Lock timeout (ms), The timeout releases previous lock.
|
||||
* @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}`, {
|
||||
lockTimeout: timeout,
|
||||
retryInterval: retryDelay,
|
||||
});
|
||||
await lock.acquire();
|
||||
return lock;
|
||||
}
|
||||
|
||||
export async function getFetchInstanceMetadataLock(
|
||||
host: string,
|
||||
timeout = 30 * 1000,
|
||||
) {
|
||||
): Promise<Mutex> {
|
||||
const lock = new Mutex(redisClient, `instance:${host}`, {
|
||||
lockTimeout: timeout,
|
||||
retryInterval: retryDelay,
|
||||
});
|
||||
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}`, {
|
||||
lockTimeout: timeout,
|
||||
retryInterval: retryDelay,
|
||||
});
|
||||
await lock.acquire();
|
||||
return lock;
|
||||
}
|
||||
|
@ -33,7 +33,9 @@ function checkWordMute(
|
||||
|
||||
if (
|
||||
keywords.length > 0 &&
|
||||
keywords.every((keyword) => text.includes(keyword))
|
||||
keywords.every((keyword) =>
|
||||
text.toLowerCase().includes(keyword.toLowerCase()),
|
||||
)
|
||||
)
|
||||
return true;
|
||||
} else {
|
||||
|
@ -167,6 +167,12 @@ export class UserProfile {
|
||||
})
|
||||
public noCrawle: boolean;
|
||||
|
||||
@Column("boolean", {
|
||||
default: true,
|
||||
comment: "Whether User is indexable.",
|
||||
})
|
||||
public isIndexable: boolean;
|
||||
|
||||
@Column("boolean", {
|
||||
default: true,
|
||||
})
|
||||
|
@ -265,6 +265,13 @@ export class User {
|
||||
})
|
||||
public driveCapacityOverrideMb: number | null;
|
||||
|
||||
@Index()
|
||||
@Column("boolean", {
|
||||
default: true,
|
||||
comment: "Whether the User is indexable.",
|
||||
})
|
||||
public isIndexable: boolean;
|
||||
|
||||
constructor(data: Partial<User>) {
|
||||
if (data == null) return;
|
||||
|
||||
|
@ -27,6 +27,7 @@ import {
|
||||
} from "@/misc/populate-emojis.js";
|
||||
import { db } from "@/db/postgre.js";
|
||||
import { IdentifiableError } from "@/misc/identifiable-error.js";
|
||||
import { detect as detectLanguage_ } from "tinyld";
|
||||
|
||||
export async function populatePoll(note: Note, meId: User["id"] | null) {
|
||||
const poll = await Polls.findOneByOrFail({ noteId: note.id });
|
||||
@ -201,6 +202,8 @@ export const NoteRepository = db.getRepository(Note).extend({
|
||||
note.emojis.concat(reactionEmojiNames),
|
||||
host,
|
||||
);
|
||||
|
||||
const lang = detectLanguage_(`${note.cw ?? ''}\n${note.text ?? ''}`) ?? "unknown"
|
||||
const reactionEmoji = await populateEmojis(reactionEmojiNames, host);
|
||||
const packed: Packed<"Note"> = await awaitAll({
|
||||
id: note.id,
|
||||
@ -260,6 +263,7 @@ export const NoteRepository = db.getRepository(Note).extend({
|
||||
: undefined,
|
||||
}
|
||||
: {}),
|
||||
lang: lang,
|
||||
});
|
||||
|
||||
if (packed.user.isCat && packed.user.speakAsCat && packed.text) {
|
||||
|
@ -454,6 +454,7 @@ export const UserRepository = db.getRepository(User).extend({
|
||||
isModerator: user.isModerator || falsy,
|
||||
isBot: user.isBot || falsy,
|
||||
isLocked: user.isLocked,
|
||||
isIndexable: user.isIndexable,
|
||||
isCat: user.isCat || falsy,
|
||||
speakAsCat: user.speakAsCat || falsy,
|
||||
instance: user.host
|
||||
|
@ -66,6 +66,11 @@ export const packedUserLiteSchema = {
|
||||
nullable: false,
|
||||
optional: true,
|
||||
},
|
||||
isIndexable: {
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
optional: true,
|
||||
},
|
||||
speakAsCat: {
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
|
@ -32,6 +32,8 @@ export default async function (
|
||||
// Interrupt if you block the announcement destination
|
||||
if (await shouldBlockInstance(extractDbHost(uri))) return;
|
||||
|
||||
const lock = await getApLock(uri);
|
||||
|
||||
try {
|
||||
// Check if something with the same URI is already registered
|
||||
const exist = await fetchNote(uri);
|
||||
@ -78,6 +80,6 @@ export default async function (
|
||||
uri,
|
||||
});
|
||||
} finally {
|
||||
await getApLock(uri);
|
||||
await lock.release();
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,8 @@ export default async function (
|
||||
}
|
||||
}
|
||||
|
||||
const lock = await getApLock(uri);
|
||||
|
||||
try {
|
||||
const exist = await fetchNote(note);
|
||||
if (exist) return "skip: note exists";
|
||||
@ -44,6 +46,6 @@ export default async function (
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
await getApLock(uri);
|
||||
await lock.release();
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ export default async function (
|
||||
): Promise<string> {
|
||||
logger.info(`Deleting the Note: ${uri}`);
|
||||
|
||||
const lock = await getApLock(uri);
|
||||
|
||||
try {
|
||||
const dbResolver = new DbResolver();
|
||||
const note = await dbResolver.getNoteFromApId(uri);
|
||||
@ -37,6 +39,6 @@ export default async function (
|
||||
await deleteNode(actor, note);
|
||||
return "ok: note deleted";
|
||||
} finally {
|
||||
await getApLock(uri);
|
||||
await lock.release();
|
||||
}
|
||||
}
|
||||
|
@ -415,6 +415,8 @@ export async function resolveNote(
|
||||
`host ${extractDbHost(uri)} is blocked`,
|
||||
);
|
||||
|
||||
const lock = await getApLock(uri);
|
||||
|
||||
try {
|
||||
//#region Returns if already registered with this server
|
||||
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.
|
||||
return await createNote(uri, resolver, true);
|
||||
} finally {
|
||||
await getApLock(uri);
|
||||
await lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,10 +205,10 @@ export async function createPerson(
|
||||
|
||||
if (typeof person.followers === "string") {
|
||||
try {
|
||||
let data = await fetch(person.followers, {
|
||||
const data = await fetch(person.followers, {
|
||||
headers: { Accept: "application/json" },
|
||||
});
|
||||
let json_data = JSON.parse(await data.text());
|
||||
const json_data = JSON.parse(await data.text());
|
||||
|
||||
followersCount = json_data.totalItems;
|
||||
} catch {
|
||||
@ -220,10 +220,10 @@ export async function createPerson(
|
||||
|
||||
if (typeof person.following === "string") {
|
||||
try {
|
||||
let data = await fetch(person.following, {
|
||||
const data = await fetch(person.following, {
|
||||
headers: { Accept: "application/json" },
|
||||
});
|
||||
let json_data = JSON.parse(await data.text());
|
||||
const json_data = JSON.parse(await data.text());
|
||||
|
||||
followingCount = json_data.totalItems;
|
||||
} catch (e) {
|
||||
@ -235,10 +235,10 @@ export async function createPerson(
|
||||
|
||||
if (typeof person.outbox === "string") {
|
||||
try {
|
||||
let data = await fetch(person.outbox, {
|
||||
const data = await fetch(person.outbox, {
|
||||
headers: { Accept: "application/json" },
|
||||
});
|
||||
let json_data = JSON.parse(await data.text());
|
||||
const json_data = JSON.parse(await data.text());
|
||||
|
||||
notesCount = json_data.totalItems;
|
||||
} catch (e) {
|
||||
@ -302,6 +302,8 @@ export async function createPerson(
|
||||
tags,
|
||||
isBot,
|
||||
isCat: (person as any).isCat === true,
|
||||
speakAsCat: person.speakAsCat,
|
||||
isIndexable: person.indexable,
|
||||
}),
|
||||
)) as IRemoteUser;
|
||||
|
||||
@ -547,6 +549,7 @@ export async function updatePerson(
|
||||
tags,
|
||||
isBot: getApType(object) !== "Person",
|
||||
isCat: (person as any).isCat === true,
|
||||
isIndexable: person.indexable,
|
||||
isLocked: !!person.manuallyApprovesFollowers,
|
||||
movedToUri: person.movedTo || null,
|
||||
alsoKnownAs: person.alsoKnownAs || null,
|
||||
|
@ -30,6 +30,7 @@ export const renderActivity = (x: any): IActivity | null => {
|
||||
Emoji: "toot:Emoji",
|
||||
featured: "toot:featured",
|
||||
discoverable: "toot:discoverable",
|
||||
indexable: "toot:indexable",
|
||||
// schema
|
||||
schema: "http://schema.org#",
|
||||
PropertyValue: "schema:PropertyValue",
|
||||
|
@ -81,6 +81,8 @@ export async function renderPerson(user: ILocalUser) {
|
||||
discoverable: !!user.isExplorable,
|
||||
publicKey: renderKey(user, keypair, "#main-key"),
|
||||
isCat: user.isCat,
|
||||
speakAsCat: user.speakAsCat,
|
||||
indexable: user.isIndexable,
|
||||
attachment: attachment.length ? attachment : undefined,
|
||||
} as any;
|
||||
|
||||
|
@ -190,8 +190,9 @@ export interface IActor extends IObject {
|
||||
movedTo?: string;
|
||||
alsoKnownAs?: string[];
|
||||
discoverable?: boolean;
|
||||
indexable?: boolean;
|
||||
inbox: string;
|
||||
sharedInbox?: string; // backward compatibility.. ig
|
||||
sharedInbox?: string; // Backwards compatibility
|
||||
publicKey?: {
|
||||
id: string;
|
||||
publicKeyPem: string;
|
||||
|
@ -60,6 +60,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||
emailVerified: profile.emailVerified,
|
||||
autoAcceptFollowed: profile.autoAcceptFollowed,
|
||||
noCrawle: profile.noCrawle,
|
||||
isIndexable: profile.isIndexable,
|
||||
preventAiLearning: profile.preventAiLearning,
|
||||
alwaysMarkNsfw: profile.alwaysMarkNsfw,
|
||||
autoSensitive: profile.autoSensitive,
|
||||
|
@ -120,6 +120,7 @@ export const paramDef = {
|
||||
isBot: { type: "boolean" },
|
||||
isCat: { type: "boolean" },
|
||||
speakAsCat: { type: "boolean" },
|
||||
isIndexable: { type: "boolean" },
|
||||
injectFeaturedNote: { type: "boolean" },
|
||||
receiveAnnouncementEmail: { type: "boolean" },
|
||||
alwaysMarkNsfw: { type: "boolean" },
|
||||
@ -206,6 +207,10 @@ export default define(meta, paramDef, async (ps, _user, token) => {
|
||||
if (typeof ps.preventAiLearning === "boolean")
|
||||
profileUpdates.preventAiLearning = ps.preventAiLearning;
|
||||
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.injectFeaturedNote === "boolean")
|
||||
profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
|
||||
|
@ -608,7 +608,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
throw new ApiError(meta.errors.noSuchNote);
|
||||
}
|
||||
|
||||
if (publishing) {
|
||||
if (publishing && user.isIndexable) {
|
||||
index(note, true);
|
||||
|
||||
// Publish update event for the updated note details
|
||||
|
@ -87,8 +87,12 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where("note.userId = :meId", { meId: user.id });
|
||||
if (hasFollowing)
|
||||
qb.orWhere(`note.userId IN (${followingQuery.getQuery()})`);
|
||||
if (hasFollowing) {
|
||||
qb.orWhere(
|
||||
`note.userId IN (${followingQuery.getQuery()})`,
|
||||
followingQuery.getParameters(),
|
||||
);
|
||||
}
|
||||
}),
|
||||
)
|
||||
.innerJoinAndSelect("note.user", "user")
|
||||
|
@ -4,7 +4,6 @@ import config from "@/config/index.js";
|
||||
import { Converter } from "opencc-js";
|
||||
import { getAgentByUrl } from "@/misc/fetch.js";
|
||||
import { fetchMeta } from "@/misc/fetch-meta.js";
|
||||
import { Notes } from "@/models/index.js";
|
||||
import { ApiError } from "../../error.js";
|
||||
import { getNote } from "../../common/getters.js";
|
||||
import define from "../../define.js";
|
||||
@ -12,7 +11,7 @@ import define from "../../define.js";
|
||||
export const meta = {
|
||||
tags: ["notes"],
|
||||
|
||||
requireCredential: false,
|
||||
requireCredential: true,
|
||||
requireCredentialPrivateMode: true,
|
||||
|
||||
res: {
|
||||
|
@ -1,19 +1,13 @@
|
||||
import { Entity } from "megalodon";
|
||||
import config from "@/config/index.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";
|
||||
|
||||
export async function getInstance(
|
||||
response: Entity.Instance,
|
||||
contact: Entity.Account,
|
||||
) {
|
||||
const [meta, totalUsers, totalStatuses] = await Promise.all([
|
||||
fetchMeta(true),
|
||||
Users.count({ where: { host: IsNull() } }),
|
||||
Notes.count({ where: { userHost: IsNull() } }),
|
||||
]);
|
||||
const meta = await fetchMeta(true);
|
||||
|
||||
return {
|
||||
uri: response.uri,
|
||||
@ -27,8 +21,8 @@ export async function getInstance(
|
||||
version: `3.0.0 (compatible; Firefish ${config.version})`,
|
||||
urls: response.urls,
|
||||
stats: {
|
||||
user_count: await totalUsers,
|
||||
status_count: await totalStatuses,
|
||||
user_count: response.stats.user_count,
|
||||
status_count: response.stats.status_count,
|
||||
domain_count: response.stats.domain_count,
|
||||
},
|
||||
thumbnail: response.thumbnail || "/static-assets/transparent.png",
|
||||
|
@ -23,7 +23,7 @@ export default class extends Channel {
|
||||
return;
|
||||
}
|
||||
|
||||
this.withReplies = params.withReplies as boolean;
|
||||
this.withReplies = params != null ? !!params.withReplies : true;
|
||||
|
||||
// Subscribe events
|
||||
this.subscriber.on("notesStream", this.onNote);
|
||||
|
@ -16,7 +16,7 @@ export default class extends Channel {
|
||||
}
|
||||
|
||||
public async init(params: any) {
|
||||
this.withReplies = params.withReplies as boolean;
|
||||
this.withReplies = params != null ? !!params.withReplies : true;
|
||||
|
||||
// Subscribe events
|
||||
this.subscriber.on("notesStream", this.onNote);
|
||||
|
@ -25,7 +25,7 @@ export default class extends Channel {
|
||||
)
|
||||
return;
|
||||
|
||||
this.withReplies = params.withReplies as boolean;
|
||||
this.withReplies = params != null ? !!params.withReplies : true;
|
||||
|
||||
// Subscribe events
|
||||
this.subscriber.on("notesStream", this.onNote);
|
||||
|
@ -22,7 +22,7 @@ export default class extends Channel {
|
||||
return;
|
||||
}
|
||||
|
||||
this.withReplies = params.withReplies as boolean;
|
||||
this.withReplies = params != null ? !!params.withReplies : true;
|
||||
|
||||
// Subscribe events
|
||||
this.subscriber.on("notesStream", this.onNote);
|
||||
|
@ -25,7 +25,7 @@ export default class extends Channel {
|
||||
)
|
||||
return;
|
||||
|
||||
this.withReplies = params.withReplies as boolean;
|
||||
this.withReplies = params != null ? !!params.withReplies : true;
|
||||
|
||||
// Subscribe events
|
||||
this.subscriber.on("notesStream", this.onNote);
|
||||
|
@ -22,7 +22,7 @@
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/static-assets/icons/512.png",
|
||||
"src": "/static-assets/icons/maskable.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
|
@ -24,9 +24,11 @@ block meta
|
||||
unless privateMode
|
||||
if profile.noCrawle
|
||||
meta(name='robots' content='noindex')
|
||||
|
||||
if profile.preventAiLearning
|
||||
meta(name='robots' content='noai')
|
||||
meta(name='robots' content='noimageai')
|
||||
meta(name='GPTBot' content='noindex')
|
||||
|
||||
meta(name='misskey:user-username' content=user.username)
|
||||
meta(name='misskey:user-id' content=user.id)
|
||||
|
@ -430,6 +430,7 @@ export default abstract class Chart<T extends Schema> {
|
||||
? `${this.name}:${date}:${span}:${group}`
|
||||
: `${this.name}:${date}:${span}`;
|
||||
|
||||
const lock = await getChartInsertLock(lockKey);
|
||||
try {
|
||||
// ロック内でもう1回チェックする
|
||||
const currentLog = (await repository.findOneBy({
|
||||
@ -465,14 +466,14 @@ export default abstract class Chart<T extends Schema> {
|
||||
|
||||
return log;
|
||||
} finally {
|
||||
await getChartInsertLock(lockKey);
|
||||
await lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
protected commit(diff: Commit<T>, group: string | null = null): void {
|
||||
for (const [k, v] of Object.entries(diff)) {
|
||||
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];
|
||||
}
|
||||
this.buffer.push({
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { URL } from "node:url";
|
||||
import { JSDOM } from "jsdom";
|
||||
import { Window } from "happy-dom";
|
||||
import fetch from "node-fetch";
|
||||
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 { Instances } from "@/models/index.js";
|
||||
import { getFetchInstanceMetadataLock } from "@/misc/app-lock.js";
|
||||
import Logger from "./logger.js";
|
||||
import type { DOMWindow } from "jsdom";
|
||||
|
||||
const logger = new Logger("metadata", "cyan");
|
||||
|
||||
@ -15,6 +14,8 @@ export async function fetchInstanceMetadata(
|
||||
instance: Instance,
|
||||
force = false,
|
||||
): Promise<void> {
|
||||
const lock = await getFetchInstanceMetadataLock(instance.host);
|
||||
|
||||
if (!force) {
|
||||
const _instance = await Instances.findOneBy({ host: instance.host });
|
||||
const now = Date.now();
|
||||
@ -22,7 +23,7 @@ export async function fetchInstanceMetadata(
|
||||
_instance?.infoUpdatedAt &&
|
||||
now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 24
|
||||
) {
|
||||
await getFetchInstanceMetadataLock(instance.host);
|
||||
await lock.release();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -78,7 +79,7 @@ export async function fetchInstanceMetadata(
|
||||
} catch (e) {
|
||||
logger.error(`Failed to update metadata of ${instance.host}: ${e}`);
|
||||
} 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} ...`);
|
||||
|
||||
const url = `https://${instance.host}`;
|
||||
|
||||
const html = await getHtml(url);
|
||||
|
||||
const { window } = new JSDOM(html);
|
||||
const window = new Window({
|
||||
url: `https://${instance.host}`,
|
||||
});
|
||||
const doc = window.document;
|
||||
|
||||
return doc;
|
||||
@ -176,7 +175,7 @@ async function fetchManifest(
|
||||
|
||||
async function fetchFaviconUrl(
|
||||
instance: Instance,
|
||||
doc: DOMWindow["document"] | null,
|
||||
doc: Window["document"] | null,
|
||||
): Promise<string | null> {
|
||||
const url = `https://${instance.host}`;
|
||||
|
||||
@ -208,7 +207,7 @@ async function fetchFaviconUrl(
|
||||
|
||||
async function fetchIconUrl(
|
||||
instance: Instance,
|
||||
doc: DOMWindow["document"] | null,
|
||||
doc: Window["document"] | null,
|
||||
manifest: Record<string, any> | null,
|
||||
): Promise<string | null> {
|
||||
if (manifest?.icons && manifest.icons.length > 0 && manifest.icons[0].src) {
|
||||
@ -240,7 +239,7 @@ async function fetchIconUrl(
|
||||
|
||||
async function getThemeColor(
|
||||
info: NodeInfo | null,
|
||||
doc: DOMWindow["document"] | null,
|
||||
doc: Window["document"] | null,
|
||||
manifest: Record<string, any> | null,
|
||||
): Promise<string | null> {
|
||||
const themeColor =
|
||||
@ -258,9 +257,9 @@ async function getThemeColor(
|
||||
|
||||
async function getSiteName(
|
||||
info: NodeInfo | null,
|
||||
doc: DOMWindow["document"] | null,
|
||||
doc: Window["document"] | null,
|
||||
manifest: Record<string, any> | null,
|
||||
): Promise<string | null> {
|
||||
): Promise<string | undefined | null> {
|
||||
if (info?.metadata) {
|
||||
if (info.metadata.nodeName || info.metadata.name) {
|
||||
return info.metadata.nodeName || info.metadata.name;
|
||||
@ -286,7 +285,7 @@ async function getSiteName(
|
||||
|
||||
async function getDescription(
|
||||
info: NodeInfo | null,
|
||||
doc: DOMWindow["document"] | null,
|
||||
doc: Window["document"] | null,
|
||||
manifest: Record<string, any> | null,
|
||||
): Promise<string | null> {
|
||||
if (info?.metadata) {
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { getHtml } from "@/misc/fetch.js";
|
||||
import { JSDOM } from "jsdom";
|
||||
import { Window } from "happy-dom";
|
||||
import config from "@/config/index.js";
|
||||
|
||||
async function getRelMeLinks(url: string): Promise<string[]> {
|
||||
try {
|
||||
const html = await getHtml(url);
|
||||
const dom = new JSDOM(html);
|
||||
const dom = new Window({
|
||||
url: url,
|
||||
});
|
||||
const allLinks = [...dom.window.document.querySelectorAll("a, link")];
|
||||
const relMeLinks = allLinks
|
||||
.filter((a) => {
|
||||
|
@ -165,11 +165,12 @@ export default async (
|
||||
createdAt: User["createdAt"];
|
||||
isBot: User["isBot"];
|
||||
inbox?: User["inbox"];
|
||||
isIndexable?: User["isIndexable"];
|
||||
},
|
||||
data: Option,
|
||||
silent = false,
|
||||
) =>
|
||||
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: FIXME
|
||||
// rome-ignore lint/suspicious/noAsyncPromiseExecutor: FIXME
|
||||
new Promise<Note>(async (res, rej) => {
|
||||
const dontFederateInitially = data.visibility === "hidden";
|
||||
|
||||
@ -652,7 +653,9 @@ export default async (
|
||||
}
|
||||
|
||||
// Register to search database
|
||||
await index(note, false);
|
||||
if (user.isIndexable) {
|
||||
await index(note, false);
|
||||
}
|
||||
});
|
||||
|
||||
async function renderNoteOrRenoteActivity(data: Option, note: Note) {
|
||||
|
@ -151,6 +151,7 @@ describe("ユーザー", () => {
|
||||
carefulBot: user.carefulBot,
|
||||
autoAcceptFollowed: user.autoAcceptFollowed,
|
||||
noCrawle: user.noCrawle,
|
||||
isIndexable: user.isIndexable,
|
||||
preventAiLearning: user.preventAiLearning,
|
||||
isExplorable: user.isExplorable,
|
||||
isDeleted: user.isDeleted,
|
||||
@ -529,6 +530,8 @@ describe("ユーザー", () => {
|
||||
{ parameters: (): object => ({ autoAcceptFollowed: false }) },
|
||||
{ parameters: (): object => ({ noCrawle: true }) },
|
||||
{ parameters: (): object => ({ noCrawle: false }) },
|
||||
{ parameters: (): object => ({ isIndexable: true }) },
|
||||
{ parameters: (): object => ({ isIndexable: false }) },
|
||||
{ parameters: (): object => ({ preventAiLearning: false }) },
|
||||
{ parameters: (): object => ({ preventAiLearning: true }) },
|
||||
{ parameters: (): object => ({ isBot: true }) },
|
||||
|
@ -63,7 +63,7 @@
|
||||
"katex": "0.16.8",
|
||||
"matter-js": "0.19.0",
|
||||
"mfm-js": "0.23.3",
|
||||
"photoswipe": "5.3.8",
|
||||
"photoswipe": "5.3.9",
|
||||
"prettier": "3.0.3",
|
||||
"prettier-plugin-vue": "1.1.6",
|
||||
"prismjs": "1.29.0",
|
||||
@ -81,6 +81,7 @@
|
||||
"three": "0.156.0",
|
||||
"throttle-debounce": "5.0.0",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tinyld": "^1.3.4",
|
||||
"tsc-alias": "1.8.7",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"twemoji-parser": "14.0.0",
|
||||
|
@ -28,6 +28,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, ref } from "vue";
|
||||
import { vibrate } from "@/scripts/vibrate";
|
||||
|
||||
const props = defineProps<{
|
||||
type?: "button" | "submit" | "reset";
|
||||
@ -93,6 +94,8 @@ function onMousedown(evt: MouseEvent): void {
|
||||
circleCenterY,
|
||||
);
|
||||
|
||||
vibrate(10);
|
||||
|
||||
window.setTimeout(() => {
|
||||
ripple.style.transform = "scale(" + scale / 2 + ")";
|
||||
}, 1);
|
||||
|
@ -782,6 +782,8 @@ onBeforeUnmount(() => {
|
||||
overflow: auto;
|
||||
font-size: 0.9em;
|
||||
box-shadow: 0 1px 0 var(--divider);
|
||||
position: fixed;
|
||||
background-color: var(--bg);
|
||||
|
||||
&,
|
||||
* {
|
||||
@ -841,6 +843,7 @@ onBeforeUnmount(() => {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: var(--margin);
|
||||
margin-top: 40px;
|
||||
|
||||
&,
|
||||
* {
|
||||
|
@ -23,6 +23,7 @@
|
||||
<button
|
||||
v-for="emoji in searchResultCustom"
|
||||
:key="emoji.id"
|
||||
v-vibrate="50"
|
||||
class="_button item"
|
||||
:title="emoji.name"
|
||||
tabindex="0"
|
||||
|
@ -4,6 +4,7 @@
|
||||
<div class="title"><slot name="header"></slot></div>
|
||||
<div class="divider"></div>
|
||||
<button
|
||||
v-vibrate="5"
|
||||
class="_button"
|
||||
:aria-expanded="showBody"
|
||||
:aria-controls="bodyId"
|
||||
|
@ -69,6 +69,7 @@ import { i18n } from "@/i18n";
|
||||
import { $i } from "@/account";
|
||||
import { getUserMenu } from "@/scripts/get-user-menu";
|
||||
import { useRouter } from "@/router";
|
||||
import { vibrate } from "@/scripts/vibrate";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
@ -154,6 +155,7 @@ async function onClick() {
|
||||
await os.api("following/create", {
|
||||
userId: props.user.id,
|
||||
});
|
||||
vibrate([30, 40, 100]);
|
||||
hasPendingFollowRequestFromYou.value = true;
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
v-tooltip="capitalize(instance.softwareName)"
|
||||
class="hpaizdrt"
|
||||
:style="bg"
|
||||
@click.stop="openServerInfo"
|
||||
>
|
||||
<img class="icon" :src="getInstanceIcon(instance)" aria-hidden="true" />
|
||||
<span class="name">{{ instance.name }}</span>
|
||||
@ -16,6 +17,8 @@ import { ref } from "vue";
|
||||
import { instanceName } from "@/config";
|
||||
import { instance as Instance } from "@/instance";
|
||||
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
|
||||
import { defaultStore } from "@/store";
|
||||
import { pageWindow } from "@/os";
|
||||
|
||||
const props = defineProps<{
|
||||
instance?: {
|
||||
@ -24,6 +27,7 @@ const props = defineProps<{
|
||||
themeColor?: string;
|
||||
softwareName?: string;
|
||||
};
|
||||
host: string | null;
|
||||
}>();
|
||||
|
||||
const ticker = ref<HTMLElement | null>(null);
|
||||
@ -83,6 +87,13 @@ function getInstanceIcon(instance): string {
|
||||
"/client-assets/dummy.png"
|
||||
);
|
||||
}
|
||||
|
||||
function openServerInfo() {
|
||||
if (!defaultStore.state.openServerInfo) return;
|
||||
const instanceInfoUrl =
|
||||
props.host == null ? "/about" : `/instance-info/${props.host}`;
|
||||
pageWindow(instanceInfoUrl);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -8,6 +8,7 @@
|
||||
<div>
|
||||
<div
|
||||
ref="itemsEl"
|
||||
v-vibrate="5"
|
||||
class="rrevdjwt _popup _shadow"
|
||||
:class="{ center: align === 'center', asDrawer }"
|
||||
:style="{
|
||||
|
@ -6,6 +6,7 @@
|
||||
ref="el"
|
||||
v-hotkey="keymap"
|
||||
v-size="{ max: [500, 350] }"
|
||||
v-vibrate="5"
|
||||
:aria-label="accessibleLabel"
|
||||
class="tkcbzcuz note-container"
|
||||
:tabindex="!isDeleted ? '-1' : null"
|
||||
@ -111,7 +112,7 @@
|
||||
></MkSubNoteContent>
|
||||
<div v-if="translating || translation" class="translation">
|
||||
<MkLoading v-if="translating" mini />
|
||||
<div v-else class="translated">
|
||||
<div v-else-if="translation != null" class="translated">
|
||||
<b
|
||||
>{{
|
||||
i18n.t("translatedFrom", {
|
||||
@ -219,6 +220,18 @@
|
||||
<i class="ph-minus ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<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
|
||||
ref="menuButton"
|
||||
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 type { Ref } from "vue";
|
||||
import type * as misskey from "firefish-js";
|
||||
import { detect as detectLanguage_ } from "tinyld";
|
||||
import MkSubNoteContent from "./MkSubNoteContent.vue";
|
||||
import MkNoteSub from "@/components/MkNoteSub.vue";
|
||||
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
||||
@ -340,12 +354,68 @@ const isMyRenote = $i && $i.id === note.value.userId;
|
||||
const showContent = ref(false);
|
||||
const isDeleted = ref(false);
|
||||
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 translating = ref(false);
|
||||
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
||||
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 = {
|
||||
r: () => reply(true),
|
||||
|
@ -210,7 +210,12 @@ const reactButton = ref<HTMLElement>();
|
||||
const showContent = ref(false);
|
||||
const isDeleted = ref(false);
|
||||
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 translating = ref(false);
|
||||
|
@ -40,6 +40,7 @@
|
||||
v-if="showTicker"
|
||||
class="ticker"
|
||||
:instance="note.user.instance"
|
||||
:host="note.user.host"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -124,6 +124,18 @@
|
||||
<i class="ph-minus ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<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
|
||||
ref="menuButton"
|
||||
v-tooltip.noDelay.bottom="i18n.ts.more"
|
||||
@ -180,6 +192,8 @@
|
||||
import { computed, inject, ref } from "vue";
|
||||
import type { Ref } from "vue";
|
||||
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 MkSubNoteContent from "@/components/MkSubNoteContent.vue";
|
||||
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
|
||||
@ -252,7 +266,12 @@ const appearNote = computed(() =>
|
||||
);
|
||||
const isDeleted = ref(false);
|
||||
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 translating = ref(false);
|
||||
@ -266,6 +285,57 @@ const replies: misskey.entities.Note[] =
|
||||
.reverse() ?? [];
|
||||
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
||||
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({
|
||||
rootEl: el,
|
||||
|
@ -29,7 +29,7 @@
|
||||
>{{ maxTextLength - textLength }}</span
|
||||
>
|
||||
<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>
|
||||
<button
|
||||
ref="visibilityButton"
|
||||
@ -275,6 +275,7 @@ import { uploadFile } from "@/scripts/upload";
|
||||
import { deepClone } from "@/scripts/clone";
|
||||
import XCheatSheet from "@/components/MkCheatSheetDialog.vue";
|
||||
import { preprocess } from "@/scripts/preprocess";
|
||||
import { vibrate } from "@/scripts/vibrate";
|
||||
|
||||
const modal = inject("modal");
|
||||
|
||||
@ -937,6 +938,7 @@ async function post() {
|
||||
text: err.message + "\n" + (err as any).id,
|
||||
});
|
||||
});
|
||||
vibrate([10, 20, 10, 20, 10, 20, 60]);
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
|
@ -3,6 +3,7 @@
|
||||
v-if="count > 0"
|
||||
ref="buttonRef"
|
||||
v-ripple="canToggle"
|
||||
v-vibrate="[10, 30, 40]"
|
||||
class="hkzvhatu _button"
|
||||
:class="{
|
||||
reacted: note.myReaction == reaction,
|
||||
|
@ -32,6 +32,7 @@ import { useTooltip } from "@/scripts/use-tooltip";
|
||||
import { i18n } from "@/i18n";
|
||||
import { defaultStore } from "@/store";
|
||||
import type { MenuItem } from "@/types/menu";
|
||||
import { vibrate } from "@/scripts/vibrate";
|
||||
|
||||
const props = defineProps<{
|
||||
note: misskey.entities.Note;
|
||||
@ -194,9 +195,10 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
|
||||
if (canRenote.value) {
|
||||
buttonActions.push({
|
||||
text: `${i18n.ts.renote} (${i18n.ts.local})`,
|
||||
icon: "ph-hand-fist ph-bold ph-lg",
|
||||
icon: "ph-users ph-bold ph-lg",
|
||||
danger: false,
|
||||
action: () => {
|
||||
vibrate([30, 30, 60]);
|
||||
os.api(
|
||||
"notes/create",
|
||||
props.note.visibility === "specified"
|
||||
|
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<button
|
||||
v-tooltip.noDelay.bottom="i18n.ts._gallery.like"
|
||||
v-vibrate="[30, 50, 50]"
|
||||
class="button _button"
|
||||
@click.stop="star($event)"
|
||||
>
|
||||
|
@ -2,6 +2,7 @@
|
||||
<button
|
||||
ref="buttonRef"
|
||||
v-tooltip.noDelay.bottom="i18n.ts._gallery.like"
|
||||
v-vibrate="[30, 50, 50]"
|
||||
class="button _button"
|
||||
:class="$style.root"
|
||||
@click.stop="toggleStar($event)"
|
||||
|
@ -19,7 +19,7 @@
|
||||
<span v-if="note.localOnly" :class="$style.localOnly"
|
||||
><i
|
||||
v-tooltip="i18n.ts._visibility.localOnly"
|
||||
class="ph-hand-fist ph-bold ph-lg"
|
||||
class="ph-users ph-bold ph-lg"
|
||||
></i
|
||||
></span>
|
||||
</template>
|
||||
|
@ -97,7 +97,7 @@
|
||||
@click="localOnly = !localOnly"
|
||||
>
|
||||
<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 :class="$style.body">
|
||||
<span :class="$style.itemTitle">{{
|
||||
|
@ -12,6 +12,7 @@
|
||||
<button
|
||||
v-if="displayBackButton"
|
||||
v-tooltip.noDelay="i18n.ts.goBack"
|
||||
v-vibrate="5"
|
||||
class="_buttonIcon button icon backButton"
|
||||
@click.stop="goBack()"
|
||||
@touchstart="preventDrag"
|
||||
@ -20,6 +21,7 @@
|
||||
</button>
|
||||
<MkAvatar
|
||||
v-if="narrow && props.displayMyAvatar && $i"
|
||||
v-vibrate="5"
|
||||
class="avatar button"
|
||||
:user="$i"
|
||||
:disable-preview="true"
|
||||
@ -77,6 +79,7 @@
|
||||
v-for="tab in tabs"
|
||||
:ref="(el) => (tabRefs[tab.key] = el)"
|
||||
v-tooltip.noDelay="tab.title"
|
||||
v-vibrate="5"
|
||||
class="tab _button"
|
||||
:class="{
|
||||
active: tab.key != null && tab.key === props.tab,
|
||||
@ -108,6 +111,7 @@
|
||||
<template v-for="action in actions">
|
||||
<button
|
||||
v-tooltip.noDelay="action.text"
|
||||
v-vibrate="5"
|
||||
class="_buttonIcon button"
|
||||
:class="{ highlighted: action.highlighted }"
|
||||
@click.stop="action.handler"
|
||||
|
@ -44,15 +44,15 @@ const relative = computed<string>(() => {
|
||||
|
||||
const ago = (now.value - _time) / 1000; /* ms */
|
||||
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
|
||||
? i18n.t("_ago.monthsAgo", { n: Math.round(ago / 2592000).toString() })
|
||||
? i18n.t("_ago.monthsAgo", { n: Math.floor(ago / 2592000).toString() })
|
||||
: ago >= 604800
|
||||
? i18n.t("_ago.weeksAgo", { n: Math.round(ago / 604800).toString() })
|
||||
? i18n.t("_ago.weeksAgo", { n: Math.floor(ago / 604800).toString() })
|
||||
: ago >= 86400
|
||||
? i18n.t("_ago.daysAgo", { n: Math.round(ago / 86400).toString() })
|
||||
? i18n.t("_ago.daysAgo", { n: Math.floor(ago / 86400).toString() })
|
||||
: ago >= 3600
|
||||
? i18n.t("_ago.hoursAgo", { n: Math.round(ago / 3600).toString() })
|
||||
? i18n.t("_ago.hoursAgo", { n: Math.floor(ago / 3600).toString() })
|
||||
: ago >= 60
|
||||
? i18n.t("_ago.minutesAgo", { n: (~~(ago / 60)).toString() })
|
||||
: ago >= 10
|
||||
|
@ -12,6 +12,7 @@ import clickAnime from "./click-anime";
|
||||
import panel from "./panel";
|
||||
import adaptiveBorder from "./adaptive-border";
|
||||
import focus from "./focus";
|
||||
import vibrate from "./vibrate";
|
||||
|
||||
export default function (app: App) {
|
||||
app.directive("userPreview", userPreview);
|
||||
@ -27,4 +28,5 @@ export default function (app: App) {
|
||||
app.directive("panel", panel);
|
||||
app.directive("adaptive-border", adaptiveBorder);
|
||||
app.directive("focus", focus);
|
||||
app.directive("vibrate", vibrate);
|
||||
}
|
||||
|
11
packages/client/src/directives/vibrate.ts
Normal file
11
packages/client/src/directives/vibrate.ts
Normal 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;
|
@ -431,7 +431,7 @@ const headerActions = computed(() => [
|
||||
const headerTabs = computed(() => [
|
||||
{
|
||||
key: "local",
|
||||
icon: "ph-hand-fist ph-bold ph-lg",
|
||||
icon: "ph-users ph-bold ph-lg",
|
||||
title: i18n.ts.local,
|
||||
},
|
||||
{
|
||||
|
@ -45,108 +45,92 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import XForm from "./auth.form.vue";
|
||||
import MkSignin from "@/components/MkSignin.vue";
|
||||
import MkKeyValue from "@/components/MkKeyValue.vue";
|
||||
import * as os from "@/os";
|
||||
import { login } from "@/account";
|
||||
import { $i, login } from "@/account";
|
||||
import { i18n } from "@/i18n";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
XForm,
|
||||
MkSignin,
|
||||
MkKeyValue,
|
||||
},
|
||||
props: ["token"],
|
||||
data() {
|
||||
return {
|
||||
state: null,
|
||||
session: null,
|
||||
fetching: true,
|
||||
i18n,
|
||||
auth_code: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (!this.$i) return;
|
||||
const props = defineProps<{
|
||||
token: string;
|
||||
}>();
|
||||
const state = ref("");
|
||||
const session = ref();
|
||||
const fetching = ref(true);
|
||||
const auth_code = ref("");
|
||||
|
||||
// Fetch session
|
||||
os.api("auth/session/show", {
|
||||
token: this.token,
|
||||
})
|
||||
.then((session) => {
|
||||
this.session = session;
|
||||
this.fetching = false;
|
||||
onMounted(() => {
|
||||
if (!$i) return;
|
||||
|
||||
// 既に連携していた場合
|
||||
if (this.session.app.isAuthorized) {
|
||||
os.api("auth/accept", {
|
||||
token: this.session.token,
|
||||
}).then(() => {
|
||||
this.accepted();
|
||||
});
|
||||
} else {
|
||||
this.state = "waiting";
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.state = "fetch-session-error";
|
||||
this.fetching = false;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
accepted() {
|
||||
this.state = "accepted";
|
||||
const getUrlParams = () =>
|
||||
window.location.search
|
||||
.substring(1)
|
||||
.split("&")
|
||||
.reduce((result, query) => {
|
||||
const [k, v] = query.split("=");
|
||||
result[k] = decodeURI(v);
|
||||
return result;
|
||||
}, {});
|
||||
const isMastodon = !!getUrlParams().mastodon;
|
||||
if (this.session.app.callbackUrl && isMastodon) {
|
||||
const callbackUrl = new URL(this.session.app.callbackUrl);
|
||||
callbackUrl.searchParams.append("code", this.session.token);
|
||||
if (getUrlParams().state)
|
||||
callbackUrl.searchParams.append(
|
||||
"state",
|
||||
getUrlParams().state,
|
||||
);
|
||||
location.href = callbackUrl.toString();
|
||||
} else if (this.session.app.callbackUrl) {
|
||||
const url = new URL(this.session.app.callbackUrl);
|
||||
if (
|
||||
[
|
||||
"javascript:",
|
||||
"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;
|
||||
} else {
|
||||
location.href = `${this.session.app.callbackUrl}?token=${
|
||||
this.session.token
|
||||
}&code=${this.session.token}&state=${
|
||||
getUrlParams().state || ""
|
||||
}`;
|
||||
}
|
||||
os.api("auth/session/show", { token: props.token })
|
||||
.then((sess: any) => {
|
||||
session.value = sess;
|
||||
fetching.value = false;
|
||||
|
||||
if (session.value.app.isAuthorized) {
|
||||
os.api("auth/accept", { token: session.value.token }).then(
|
||||
() => {
|
||||
accepted();
|
||||
},
|
||||
);
|
||||
} else {
|
||||
state.value = "waiting";
|
||||
}
|
||||
},
|
||||
onLogin(res) {
|
||||
login(res.i);
|
||||
},
|
||||
},
|
||||
})
|
||||
.catch((error) => {
|
||||
state.value = "fetch-session-error";
|
||||
fetching.value = false;
|
||||
});
|
||||
});
|
||||
|
||||
const getUrlParams = () =>
|
||||
window.location.search
|
||||
.substring(1)
|
||||
.split("&")
|
||||
.reduce((result, query) => {
|
||||
const [k, v] = query.split("=");
|
||||
result[k] = decodeURI(v);
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
const accepted = () => {
|
||||
state.value = "accepted";
|
||||
const isMastodon = !!getUrlParams().mastodon;
|
||||
if (session.value.app.callbackUrl && isMastodon) {
|
||||
const redirectUri = decodeURIComponent(getUrlParams().redirect_uri);
|
||||
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)
|
||||
callbackUrl.searchParams.append("state", getUrlParams().state);
|
||||
location.href = callbackUrl.toString();
|
||||
} else if (session.value.app.callbackUrl) {
|
||||
const url = new URL(session.value.app.callbackUrl);
|
||||
if (
|
||||
["javascript:", "file:", "data:", "mailto:", "tel:"].includes(
|
||||
url.protocol,
|
||||
)
|
||||
) {
|
||||
throw new Error("Invalid URL");
|
||||
}
|
||||
if (session.value.app.callbackUrl === "urn:ietf:wg:oauth:2.0:oob") {
|
||||
auth_code.value = session.value.token;
|
||||
} else {
|
||||
location.href = `${session.value.app.callbackUrl}?token=${
|
||||
session.value.token
|
||||
}&code=${session.value.token}&state=${getUrlParams().state || ""}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onLogin = (res) => {
|
||||
login(res.i);
|
||||
};
|
||||
</script>
|
||||
|
@ -95,6 +95,7 @@ const paginationComponent = ref<InstanceType<typeof MkPagination>>();
|
||||
const pagination = {
|
||||
endpoint: "following/requests/list" as const,
|
||||
limit: 10,
|
||||
noPaging: true,
|
||||
};
|
||||
|
||||
function accept(user) {
|
||||
|
@ -120,6 +120,7 @@ import {
|
||||
import * as os from "@/os";
|
||||
import { stream } from "@/stream";
|
||||
import * as sound from "@/scripts/sound";
|
||||
import { vibrate } from "@/scripts/vibrate";
|
||||
import { i18n } from "@/i18n";
|
||||
import { $i } from "@/account";
|
||||
import { defaultStore } from "@/store";
|
||||
@ -251,6 +252,7 @@ function onDrop(ev: DragEvent): void {
|
||||
|
||||
function onMessage(message) {
|
||||
sound.play("chat");
|
||||
vibrate([30, 30, 30]);
|
||||
|
||||
const _isBottom = isBottomVisible(rootEl.value, 64);
|
||||
|
||||
|
@ -17,6 +17,15 @@
|
||||
</template>
|
||||
</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">
|
||||
<template #label>{{ i18n.ts.overridedDeviceKind }}</template>
|
||||
<option :value="null">{{ i18n.ts.auto }}</option>
|
||||
@ -71,6 +80,12 @@
|
||||
{{ i18n.ts.reflectMayTakeTime }}</template
|
||||
></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">
|
||||
<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
|
||||
@ -121,6 +136,12 @@
|
||||
class="_formBlock"
|
||||
>{{ i18n.ts.disableShowingAnimatedImages }}</FormSwitch
|
||||
>
|
||||
<FormSwitch
|
||||
v-model="vibrate"
|
||||
class="_formBlock"
|
||||
@click="demoVibrate"
|
||||
>{{ i18n.ts.vibrate }}
|
||||
</FormSwitch>
|
||||
<FormRadios v-model="fontSize" class="_formBlock">
|
||||
<template #label>{{ i18n.ts.fontSize }}</template>
|
||||
<option :value="null">
|
||||
@ -258,7 +279,7 @@ import FormSection from "@/components/form/section.vue";
|
||||
import FormLink from "@/components/form/link.vue";
|
||||
import MkLink from "@/components/MkLink.vue";
|
||||
import { langs } from "@/config";
|
||||
import { defaultStore } from "@/store";
|
||||
import { ColdDeviceStorage, defaultStore } from "@/store";
|
||||
import * as os from "@/os";
|
||||
import { unisonReload } from "@/scripts/unison-reload";
|
||||
import { i18n } from "@/i18n";
|
||||
@ -266,6 +287,7 @@ import { definePageMetadata } from "@/scripts/page-metadata";
|
||||
import { deviceKind } from "@/scripts/device-kind";
|
||||
|
||||
const lang = ref(localStorage.getItem("lang"));
|
||||
const translateLang = ref(localStorage.getItem("translateLang"));
|
||||
const fontSize = ref(localStorage.getItem("fontSize"));
|
||||
const useSystemFont = ref(localStorage.getItem("useSystemFont") != null);
|
||||
|
||||
@ -279,6 +301,10 @@ async function reloadAsk() {
|
||||
unisonReload();
|
||||
}
|
||||
|
||||
function demoVibrate() {
|
||||
window.navigator.vibrate(100);
|
||||
}
|
||||
|
||||
const overridedDeviceKind = computed(
|
||||
defaultStore.makeGetterSetter("overridedDeviceKind"),
|
||||
);
|
||||
@ -315,6 +341,7 @@ const disableDrawer = computed(defaultStore.makeGetterSetter("disableDrawer"));
|
||||
const disableShowingAnimatedImages = computed(
|
||||
defaultStore.makeGetterSetter("disableShowingAnimatedImages"),
|
||||
);
|
||||
const vibrate = computed(ColdDeviceStorage.makeGetterSetter("vibrate"));
|
||||
const loadRawImages = computed(defaultStore.makeGetterSetter("loadRawImages"));
|
||||
const imageNewTab = computed(defaultStore.makeGetterSetter("imageNewTab"));
|
||||
const nsfw = computed(defaultStore.makeGetterSetter("nsfw"));
|
||||
@ -357,6 +384,12 @@ const showAdminUpdates = computed(
|
||||
const showTimelineReplies = computed(
|
||||
defaultStore.makeGetterSetter("showTimelineReplies"),
|
||||
);
|
||||
const detectPostLanguage = computed(
|
||||
defaultStore.makeGetterSetter("detectPostLanguage"),
|
||||
);
|
||||
const openServerInfo = computed(
|
||||
defaultStore.makeGetterSetter("openServerInfo"),
|
||||
);
|
||||
|
||||
watch(swipeOnDesktop, () => {
|
||||
defaultStore.set("swipeOnMobile", true);
|
||||
@ -367,6 +400,10 @@ watch(lang, () => {
|
||||
localStorage.removeItem("locale");
|
||||
});
|
||||
|
||||
watch(translateLang, () => {
|
||||
localStorage.setItem("translateLang", translateLang.value as string);
|
||||
});
|
||||
|
||||
watch(fontSize, () => {
|
||||
if (fontSize.value == null) {
|
||||
localStorage.removeItem("fontSize");
|
||||
@ -386,6 +423,7 @@ watch(useSystemFont, () => {
|
||||
watch(
|
||||
[
|
||||
lang,
|
||||
translateLang,
|
||||
fontSize,
|
||||
useSystemFont,
|
||||
enableInfiniteScroll,
|
||||
|
@ -9,7 +9,7 @@
|
||||
<template #empty
|
||||
><FormInfo>{{ i18n.ts.noUsers }}</FormInfo></template
|
||||
>
|
||||
<template #default="{ items }">
|
||||
<template #default="{ items }" class="_formlinks">
|
||||
<FormLink
|
||||
v-for="mute in items"
|
||||
:key="mute.id"
|
||||
@ -25,7 +25,7 @@
|
||||
<template #empty
|
||||
><FormInfo>{{ i18n.ts.noUsers }}</FormInfo></template
|
||||
>
|
||||
<template #default="{ items }">
|
||||
<template #default="{ items }" class="_formlinks">
|
||||
<FormLink
|
||||
v-for="block in items"
|
||||
:key="block.id"
|
||||
|
@ -117,6 +117,8 @@ const defaultStoreSaveKeys: (keyof (typeof defaultStore)["state"])[] = [
|
||||
"enableEmojiReactions",
|
||||
"showEmojisInReactionNotifications",
|
||||
"showTimelineReplies",
|
||||
"detectPostLanguage",
|
||||
"openServerInfo",
|
||||
];
|
||||
const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [
|
||||
"lightTheme",
|
||||
@ -124,6 +126,7 @@ const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [
|
||||
"syncDeviceDarkMode",
|
||||
"plugins",
|
||||
"mediaVolume",
|
||||
"vibrate",
|
||||
"sound_masterVolume",
|
||||
"sound_note",
|
||||
"sound_noteMy",
|
||||
|
@ -52,6 +52,14 @@
|
||||
i18n.ts.hideOnlineStatusDescription
|
||||
}}</template>
|
||||
</FormSwitch>
|
||||
<FormSwitch
|
||||
v-model="isIndexable"
|
||||
class="_formBlock"
|
||||
@update:modelValue="save()"
|
||||
>
|
||||
{{ i18n.ts.indexable }}
|
||||
<template #caption>{{ i18n.ts.indexableDescription }}</template>
|
||||
</FormSwitch>
|
||||
<FormSwitch
|
||||
v-model="noCrawle"
|
||||
class="_formBlock"
|
||||
@ -155,6 +163,7 @@ import { definePageMetadata } from "@/scripts/page-metadata";
|
||||
const isLocked = ref($i.isLocked);
|
||||
const autoAcceptFollowed = ref($i.autoAcceptFollowed);
|
||||
const noCrawle = ref($i.noCrawle);
|
||||
const isIndexable = ref($i.isIndexable);
|
||||
const isExplorable = ref($i.isExplorable);
|
||||
const hideOnlineStatus = ref($i.hideOnlineStatus);
|
||||
const publicReactions = ref($i.publicReactions);
|
||||
@ -178,6 +187,7 @@ function save() {
|
||||
isLocked: !!isLocked.value,
|
||||
autoAcceptFollowed: !!autoAcceptFollowed.value,
|
||||
noCrawle: !!noCrawle.value,
|
||||
isIndexable: !!isIndexable.value,
|
||||
isExplorable: !!isExplorable.value,
|
||||
hideOnlineStatus: !!hideOnlineStatus.value,
|
||||
publicReactions: !!publicReactions.value,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user